mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
feat(sidebar): bold unread workspaces instead of bell icon
Replaces the per-worktree bell affordance with a bold-title treatment for unread workspaces, and drops the now-unused Mark Read/Unread context-menu entry. The 'unread' card property still gates the emphasis, so users can opt out. PR# label de-bolded to match the softer title weight on read cards. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
# Bold/Non-Bold Worktree Card Unread Indicator
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the yellow bell glyph on each sidebar workspace card with a
|
||||
**bold / non-bold** treatment of the card title — matching the visual
|
||||
language already used by the inline agent activity rows under the
|
||||
experimental agent-status toggle (`DashboardAgentRow.tsx:262`). Unread
|
||||
**state** (what flips `worktree.isUnread`, what clears it, where it's
|
||||
persisted) stays exactly as it is today. The change is purely visual
|
||||
plus the removal of the now-unused manual toggle paths.
|
||||
|
||||
## Why
|
||||
|
||||
The amber `FilledBellIcon` on the sidebar card is the only remaining
|
||||
surface in the sidebar that encodes "needs attention" as a colored
|
||||
glyph. Agent rows already express the same semantic as a weight change
|
||||
(`font-semibold text-foreground` vs `font-normal text-muted-foreground`
|
||||
— see `DashboardAgentRow.tsx:262` and the comment block above it: "one
|
||||
attention axis, not two"). Extending that rule to the workspace card
|
||||
collapses the two axes on the sidebar into one, reclaims the left-rail
|
||||
real estate the bell occupies, and removes a duplicate use of amber —
|
||||
which also signals CI `pending`, conflict operations, and sparse
|
||||
checkout on the same card.
|
||||
|
||||
This change is deliberately scoped to the **sidebar `WorktreeCard`**.
|
||||
`FilledBellIcon` also has a caller in `SortableTab.tsx:266` for
|
||||
tab-bar unread; that surface is **explicitly out of scope** (see
|
||||
"Out of scope" below) and the helper stays exported.
|
||||
|
||||
## Today's behavior (the bell)
|
||||
|
||||
- `worktree.isUnread` (a `WorktreeMeta` boolean, persisted) drives a
|
||||
bell icon rendered on the **left rail** of `WorktreeCard`, gated by
|
||||
the `'unread'` card property:
|
||||
- `src/renderer/src/components/sidebar/WorktreeCard.tsx:380–404`
|
||||
renders `<FilledBellIcon className="... text-amber-500 ...">` when
|
||||
unread, or a ghost `<Bell>` (hover-reveal) when read.
|
||||
- Clicking the icon toggles via `handleToggleUnreadQuick`
|
||||
(`WorktreeCard.tsx:320–327`) → `updateWorktreeMeta({ isUnread: !… })`.
|
||||
- The context menu has a parallel toggle
|
||||
(`WorktreeContextMenu.tsx:67–69, 172–175`) that is also going away
|
||||
(see "Remove manual toggle paths" below).
|
||||
- The unread flag is also flipped by activation/ack cascades in
|
||||
`src/renderer/src/store/slices/worktrees.ts` (around L372, L407, L480,
|
||||
L595, L669) and by the CLI orchestration handler
|
||||
(`src/cli/handlers/orchestration.ts:71`). **None of this changes.**
|
||||
Activation continues to clear unread automatically, which is the
|
||||
dominant way unread becomes read today — and after this change, the
|
||||
*only* manual way.
|
||||
- Property toggle in `SidebarHeader.tsx:29` is `{ id: 'unread', label:
|
||||
'Unread indicator' }`. Label may change; the id stays the same for
|
||||
settings persistence compatibility.
|
||||
|
||||
## Reference: how agent rows do bold/non-bold
|
||||
|
||||
`WorktreeCardAgents` derives a per-row `isUnvisited` flag from the ack
|
||||
map (`WorktreeCardAgents.tsx:151`) and passes it to
|
||||
`DashboardAgentRow`, which switches classes at
|
||||
`DashboardAgentRow.tsx:262`:
|
||||
|
||||
```
|
||||
isUnvisited ? 'font-semibold text-foreground' : 'font-normal text-muted-foreground'
|
||||
```
|
||||
|
||||
We apply the same two-class pattern to the workspace card's title row.
|
||||
|
||||
> Note: agent-row "unvisited" is derived at render time from an ack map
|
||||
> keyed on `stateStartedAt`; workspace `isUnread` is a persisted boolean
|
||||
> on `WorktreeMeta`. We're mirroring the *visual language*, not the
|
||||
> underlying state model. The two systems remain independent.
|
||||
|
||||
## Target behavior
|
||||
|
||||
### The rule: bold = unread
|
||||
|
||||
**On a read card (`worktree.isUnread === false`), no text in the card
|
||||
body is rendered at `font-semibold` or heavier.** Bold is reserved as
|
||||
the unread signal. This applies to:
|
||||
|
||||
- The title (`displayName`) — today `font-semibold text-foreground`
|
||||
(`WorktreeCard.tsx:430`) → on read cards becomes
|
||||
`font-normal text-muted-foreground`.
|
||||
- The repo badge label — today `font-semibold text-foreground`
|
||||
(`WorktreeCard.tsx:508`) → drops to `font-normal text-foreground`
|
||||
on read cards to preserve hierarchy (the repo label must not read
|
||||
heavier than the workspace title above it).
|
||||
|
||||
Small chrome labels already using `font-medium` (`primary`, `sparse`,
|
||||
`conflict`, Folder-kind badge) stay as-is — they're visually distinct
|
||||
from bold title copy and are *not* part of the "bold = unread" signal.
|
||||
|
||||
**On an unread card (`worktree.isUnread === true`) and the `'unread'`
|
||||
card property is enabled:**
|
||||
|
||||
- The title renders `font-semibold text-foreground`.
|
||||
- The repo badge label goes back to `font-semibold text-foreground`
|
||||
so the hierarchy stays coherent.
|
||||
- The yellow bell does **not** render anywhere on the card.
|
||||
|
||||
**When the card property `'unread'` is off:**
|
||||
|
||||
- The card is always rendered as if read, regardless of the store flag.
|
||||
This preserves the existing opt-out — users who previously hid the
|
||||
bell via this property retain an equivalent "quiet mode."
|
||||
|
||||
### Interactions
|
||||
|
||||
- **Activation remains the only way to clear unread manually.** Clicking
|
||||
the card activates the workspace, which runs the existing cascade in
|
||||
`worktrees.ts` and clears `isUnread`. The title de-bolds as part of
|
||||
the next render. This is how ~all read-state transitions already
|
||||
happen today; the bell's click target and the context-menu "Mark
|
||||
Read / Mark Unread" item were the manual escape hatches, and they
|
||||
both go away (see next section).
|
||||
- No Alt-click, middle-click, or other hidden gesture is introduced.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. `WorktreeCard.tsx`
|
||||
|
||||
- Delete the unread-button block (lines ~380–404):
|
||||
- The `<Tooltip>` + `<button onClick={handleToggleUnreadQuick}>` +
|
||||
`FilledBellIcon` / `Bell` tree.
|
||||
- Delete `handleToggleUnreadQuick` and `unreadTooltip` (lines ~320–329)
|
||||
entirely. No replacement surface.
|
||||
- Delete the `FilledBellIcon` import at `WorktreeCard.tsx:30`.
|
||||
`FilledBellIcon` itself stays exported from `WorktreeCardHelpers.tsx`
|
||||
because `SortableTab.tsx:266` still uses it.
|
||||
- Update the left-rail wrapper condition:
|
||||
- Current: `{(cardProps.includes('status') || cardProps.includes('unread')) && (…)}`
|
||||
- New: `{cardProps.includes('status') && (…)}`
|
||||
- The rail's only remaining child is the `StatusIndicator`; if
|
||||
`'status'` is off and `'unread'` is on, the rail disappears (status
|
||||
dot already doesn't render). That's correct — bold/non-bold needs
|
||||
no rail real estate.
|
||||
- Compute one derived flag near the top of the render body:
|
||||
```ts
|
||||
// Why: the 'unread' card property is the user's opt-out. When off,
|
||||
// we render as if the workspace is read so bold emphasis never appears,
|
||||
// matching the old "hide the bell" behavior exactly.
|
||||
const showUnreadEmphasis = cardProps.includes('unread') && worktree.isUnread
|
||||
```
|
||||
- Apply to the display name (`WorktreeCard.tsx:430`):
|
||||
```tsx
|
||||
{/* Why: bold = unread is the single attention signal on this card,
|
||||
mirroring DashboardAgentRow.tsx:262. The base class drops the
|
||||
always-on font-semibold that was safe when a separate bell carried
|
||||
the signal; now the title IS the signal, so it must de-bold on
|
||||
read cards. */}
|
||||
<div
|
||||
className={cn(
|
||||
'text-[12px] truncate leading-tight',
|
||||
showUnreadEmphasis
|
||||
? 'font-semibold text-foreground'
|
||||
: 'font-normal text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{worktree.displayName}
|
||||
</div>
|
||||
```
|
||||
- Apply to the repo badge label (`WorktreeCard.tsx:508`):
|
||||
```tsx
|
||||
{/* Why: repo label tracks the title's weight. If the title is muted
|
||||
(read), this stays non-bold so it doesn't out-weigh the workspace
|
||||
identifier above it. If the title is bold (unread), this bolds
|
||||
with it so the header row reads as one unit. */}
|
||||
<span
|
||||
className={cn(
|
||||
'text-[10px] truncate max-w-[6rem] leading-none lowercase',
|
||||
showUnreadEmphasis
|
||||
? 'font-semibold text-foreground'
|
||||
: 'font-normal text-foreground'
|
||||
)}
|
||||
>
|
||||
{repo.displayName}
|
||||
</span>
|
||||
```
|
||||
- Note: we keep `text-foreground` on the read state (not
|
||||
`text-muted-foreground`) because the badge already sits on a tinted
|
||||
`bg-accent` chip — muting the label as well would leave it nearly
|
||||
invisible. The weight change alone is enough to restore hierarchy.
|
||||
|
||||
### 2. Remove manual toggle paths
|
||||
|
||||
The bell click and the context-menu toggle were the two ways to flip
|
||||
unread without activating the workspace. In practice, activation covers
|
||||
the overwhelming majority of read-state transitions, and neither manual
|
||||
path is load-bearing. Both go away:
|
||||
|
||||
- **`WorktreeCard.tsx`**: `handleToggleUnreadQuick` / `unreadTooltip`
|
||||
deleted (see section 1).
|
||||
- **`WorktreeContextMenu.tsx`**:
|
||||
- Delete `handleToggleRead` (lines 67–69).
|
||||
- Delete the `DropdownMenuItem` for "Mark Read / Mark Unread"
|
||||
(lines 172–175).
|
||||
- Remove `Bell` and `BellOff` from the `lucide-react` import at
|
||||
lines 13–14; they have no other callers in this file.
|
||||
- `updateWorktreeMeta` stays imported — still used by the pin toggle.
|
||||
|
||||
`isUnread` continues to be set by terminal BEL and orchestration
|
||||
handlers and cleared by the activation cascade. The user-facing model
|
||||
becomes: *unread state is automatic; to clear it, open the workspace.*
|
||||
|
||||
### 3. `WorktreeCardHelpers.tsx`
|
||||
|
||||
- **No changes.** `FilledBellIcon` stays exported because
|
||||
`SortableTab.tsx:266` still uses it for tab-bar unread.
|
||||
- If a future change aligns tab-bar unread with the sidebar's
|
||||
bold-for-unread rule, this helper can be deleted then.
|
||||
|
||||
### 4. `SidebarHeader.tsx`
|
||||
|
||||
- Relabel the property option:
|
||||
`{ id: 'unread', label: 'Bold unread workspaces' }` (or similar
|
||||
copy reflecting the new mechanism).
|
||||
The `id` must stay `'unread'` so settings persistence and existing
|
||||
`cardProps.includes('unread')` checks keep working.
|
||||
- Tests referencing this property (`persistence.test.ts:571/590/610/629`)
|
||||
continue to pass — id is unchanged.
|
||||
|
||||
### 5. Tests to update / add
|
||||
|
||||
- `src/renderer/src/components/sidebar/WorktreeCard.test.tsx` (if it
|
||||
asserts on the bell): replace assertions with class-based checks on
|
||||
the title and repo-badge elements. If the file doesn't exist, add
|
||||
focused tests:
|
||||
- unread + property on → title has `font-semibold text-foreground`,
|
||||
repo badge label has `font-semibold text-foreground`.
|
||||
- unread + property off → title is `font-normal text-muted-foreground`,
|
||||
repo badge label is `font-normal text-foreground`.
|
||||
- read + property on → same as the unread + property-off case.
|
||||
- No `FilledBellIcon` renders in any of the above states.
|
||||
- `WorktreeContextMenu` test (if one exists): assert that the
|
||||
"Mark Read / Mark Unread" item is gone and that `handleToggleRead`
|
||||
is no longer referenced.
|
||||
- Keep `smart-sort.test.ts` / `visible-worktrees.test.ts` / the
|
||||
`isUnread` store tests unchanged — they verify the flag's semantics,
|
||||
which are unchanged.
|
||||
|
||||
### 6. Accessibility
|
||||
|
||||
- The bell's `aria-label` ("Mark as read / Mark as unread") disappears
|
||||
along with the button.
|
||||
- Add a visually-hidden prefix inside the title when unread so screen
|
||||
readers still surface the state:
|
||||
```tsx
|
||||
{showUnreadEmphasis && <span className="sr-only">Unread: </span>}
|
||||
{worktree.displayName}
|
||||
```
|
||||
The `sr-only` span is preferred over `aria-label` on the card root —
|
||||
the card is a non-interactive `<div>`, and `aria-label` on a bare
|
||||
`<div>` with no role is inconsistently announced across screen
|
||||
readers. A visible-text prefix in the accessible name is reliable.
|
||||
- No focusable toggle control exists anymore, so there's no keyboard
|
||||
affordance for flipping unread. That's consistent with removing the
|
||||
manual toggle paths entirely — if we later decide this regression
|
||||
is too sharp, a visible hover-reveal dot is the cleanest place to
|
||||
re-add one.
|
||||
|
||||
## Interaction-state matrix
|
||||
|
||||
| Card state | Title | Repo badge | Bell | Notes |
|
||||
|---|---|---|---|---|
|
||||
| unread + property on | `font-semibold text-foreground` | `font-semibold text-foreground` | — | Primary target behavior |
|
||||
| unread + property off | `font-normal text-muted-foreground` | `font-normal text-foreground` | — | Opt-out active; store flag unchanged |
|
||||
| read + property on | `font-normal text-muted-foreground` | `font-normal text-foreground` | — | Baseline read |
|
||||
| read + property off | `font-normal text-muted-foreground` | `font-normal text-foreground` | — | Same as read + property on (no signal to hide) |
|
||||
| SSH disconnected + unread | bold + `opacity-60` (inherited from card root) | bold + `opacity-60` | — | Dimmed but still bolder than the read cards next to it |
|
||||
| Deleting + unread | bold + `opacity-50 grayscale` | bold + `opacity-50 grayscale` | — | Deleting takes visual priority; acceptable |
|
||||
|
||||
Note: during `isDeleting`, a backdrop-blurred overlay
|
||||
(`WorktreeCard.tsx:~345`) sits over the card body with a "Deleting…"
|
||||
chip. The title's bold class is preserved but visually subordinate to
|
||||
the overlay — the matrix row describes the class state, not the
|
||||
compositional appearance.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] No yellow bell renders anywhere on the sidebar `WorktreeCard` at
|
||||
any state. (Tab bar's `SortableTab` bell is unaffected and out of
|
||||
scope.)
|
||||
- [ ] On a read card, no text in the card body renders at
|
||||
`font-semibold` or heavier.
|
||||
- [ ] On an unread card (with the `'unread'` property enabled), the
|
||||
title and repo badge label render at
|
||||
`font-semibold text-foreground`.
|
||||
- [ ] Turning off the `'unread'` property returns every card to the
|
||||
read visual state regardless of `isUnread`.
|
||||
- [ ] Activating a worktree still clears unread (existing cascade
|
||||
logic in `worktrees.ts` untouched).
|
||||
- [ ] The "Mark Read / Mark Unread" context-menu item is gone.
|
||||
- [ ] `handleToggleUnreadQuick` and `handleToggleRead` are deleted.
|
||||
- [ ] `FilledBellIcon` stays exported (still used by `SortableTab`);
|
||||
its import in `WorktreeCard.tsx` is removed.
|
||||
- [ ] Screen reader announces unread state via an `sr-only` prefix
|
||||
inside the title element.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Tab bar (`SortableTab`) unread styling.** `SortableTab.tsx:266`
|
||||
still uses `FilledBellIcon`. Aligning the tab bar with the
|
||||
bold-for-unread rule is a follow-up; this doc deliberately does not
|
||||
touch it. The acceptance criterion above is scoped to the sidebar
|
||||
`WorktreeCard` specifically.
|
||||
- Terminal bell, notifications pane, or any other bell site outside
|
||||
the sidebar card.
|
||||
- Redesigning the unread cascade rules (what sets `isUnread`).
|
||||
- Reintroducing a manual toggle affordance (hover dot, modifier click,
|
||||
keyboard shortcut). If triage-without-activation becomes a real
|
||||
user-reported regression, add it in a follow-up — do not ship a
|
||||
speculative affordance now.
|
||||
@@ -26,7 +26,7 @@ const GROUP_BY_OPTIONS = [
|
||||
|
||||
const PROPERTY_OPTIONS: { id: WorktreeCardProperty; label: string }[] = [
|
||||
{ id: 'status', label: 'Terminal status' },
|
||||
{ id: 'unread', label: 'Unread indicator' },
|
||||
{ id: 'unread', label: 'Bold unread workspaces' },
|
||||
{ id: 'ci', label: 'CI checks' },
|
||||
{ id: 'issue', label: 'Linked issue' },
|
||||
{ id: 'pr', label: 'Linked PR' },
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
||||
import { Bell, GitMerge, LoaderCircle, CircleCheck, CircleX, Server, ServerOff } from 'lucide-react'
|
||||
import { GitMerge, LoaderCircle, CircleCheck, CircleX, Server, ServerOff } from 'lucide-react'
|
||||
import StatusIndicator from './StatusIndicator'
|
||||
import CacheTimer from './CacheTimer'
|
||||
import WorktreeContextMenu from './WorktreeContextMenu'
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
checksLabel,
|
||||
CONFLICT_OPERATION_LABELS,
|
||||
EMPTY_TABS,
|
||||
EMPTY_BROWSER_TABS,
|
||||
FilledBellIcon
|
||||
EMPTY_BROWSER_TABS
|
||||
} from './WorktreeCardHelpers'
|
||||
import { IssueSection, PrSection, CommentSection } from './WorktreeCardMeta'
|
||||
|
||||
@@ -53,7 +52,6 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
hintNumber
|
||||
}: WorktreeCardProps) {
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const updateWorktreeMeta = useAppStore((s) => s.updateWorktreeMeta)
|
||||
const fetchPRForBranch = useAppStore((s) => s.fetchPRForBranch)
|
||||
const fetchIssue = useAppStore((s) => s.fetchIssue)
|
||||
const cardProps = useAppStore((s) => s.worktreeCardProperties)
|
||||
@@ -317,16 +315,11 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
})
|
||||
}, [worktree.id, worktree.displayName, worktree.linkedIssue, worktree.comment, openModal])
|
||||
|
||||
const handleToggleUnreadQuick = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
updateWorktreeMeta(worktree.id, { isUnread: !worktree.isUnread })
|
||||
},
|
||||
[worktree.id, worktree.isUnread, updateWorktreeMeta]
|
||||
)
|
||||
|
||||
const unreadTooltip = worktree.isUnread ? 'Mark read' : 'Mark unread'
|
||||
// Why: the 'unread' card property is the user's opt-out. When off, we render
|
||||
// as if the workspace is read so bold emphasis never appears — matching the
|
||||
// old "hide the bell" behavior exactly. The persisted `worktree.isUnread`
|
||||
// flag is unchanged; only the rendering changes.
|
||||
const showUnreadEmphasis = cardProps.includes('unread') && worktree.isUnread
|
||||
|
||||
const cardBody = (
|
||||
<div
|
||||
@@ -367,41 +360,14 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status indicator on the left */}
|
||||
{(cardProps.includes('status') || cardProps.includes('unread')) && (
|
||||
{/* Status indicator on the left.
|
||||
Why: bold-for-unread carries the attention signal in the title row
|
||||
now, so the rail only needs to render when the status dot is
|
||||
enabled — the 'unread' property is intentionally excluded here. */}
|
||||
{cardProps.includes('status') && (
|
||||
<div className="flex flex-col items-center justify-start pt-[2px] gap-2 shrink-0">
|
||||
{cardProps.includes('status') && (
|
||||
<>
|
||||
<StatusIndicator status={status} aria-hidden="true" />
|
||||
<span className="sr-only">{getWorktreeStatusLabel(status)}</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{cardProps.includes('unread') && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleUnreadQuick}
|
||||
className={cn(
|
||||
'group/unread flex size-4 cursor-pointer items-center justify-center rounded transition-all',
|
||||
'hover:bg-accent/80 active:scale-95',
|
||||
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
||||
)}
|
||||
aria-label={worktree.isUnread ? 'Mark as read' : 'Mark as unread'}
|
||||
>
|
||||
{worktree.isUnread ? (
|
||||
<FilledBellIcon className="size-[13px] text-amber-500 drop-shadow-sm" />
|
||||
) : (
|
||||
<Bell className="size-3 text-muted-foreground/40 opacity-0 group-hover:opacity-100 group-hover/unread:opacity-100 transition-opacity" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
<span>{unreadTooltip}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<StatusIndicator status={status} aria-hidden="true" />
|
||||
<span className="sr-only">{getWorktreeStatusLabel(status)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -427,7 +393,23 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<div className="text-[12px] font-semibold text-foreground truncate leading-tight">
|
||||
{/* Why: bold = unread is the single attention signal on this
|
||||
card, mirroring DashboardAgentRow.tsx:262. The base class
|
||||
drops the always-on font-semibold that was safe when a
|
||||
separate bell carried the signal; now the title IS the
|
||||
signal, so it must de-bold on read cards. */}
|
||||
<div
|
||||
className={cn(
|
||||
'text-[12px] truncate leading-tight',
|
||||
showUnreadEmphasis
|
||||
? 'font-semibold text-foreground'
|
||||
: 'font-normal text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{/* Why: the card root is a non-interactive <div>, so aria-label
|
||||
on it is announced inconsistently across screen readers.
|
||||
A visible-text prefix inside the accessible name is reliable. */}
|
||||
{showUnreadEmphasis && <span className="sr-only">Unread: </span>}
|
||||
{worktree.displayName}
|
||||
</div>
|
||||
|
||||
@@ -505,7 +487,19 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
{repo && !hideRepoBadge && (
|
||||
<div className="flex items-center gap-1.5 shrink-0 px-1.5 py-0.5 rounded-[4px] bg-accent border border-border dark:bg-accent/50 dark:border-border/60">
|
||||
<div className="size-1.5 rounded-full" style={{ backgroundColor: repo.badgeColor }} />
|
||||
<span className="text-[10px] font-semibold text-foreground truncate max-w-[6rem] leading-none lowercase">
|
||||
{/* Why: repo label tracks the title's weight. Keeps
|
||||
text-foreground on the read state (not muted) because this
|
||||
label sits on a tinted bg-accent chip — muting it would leave
|
||||
it nearly invisible; the weight change alone restores
|
||||
hierarchy against the title. */}
|
||||
<span
|
||||
className={cn(
|
||||
'text-[10px] truncate max-w-[6rem] leading-none lowercase',
|
||||
showUnreadEmphasis
|
||||
? 'font-semibold text-foreground'
|
||||
: 'font-normal text-foreground'
|
||||
)}
|
||||
>
|
||||
{repo.displayName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -97,7 +97,7 @@ export function PrSection({ pr, onClick }: PrSectionProps): React.JSX.Element {
|
||||
href={pr.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-foreground opacity-80 font-medium shrink-0 hover:text-foreground hover:underline"
|
||||
className="text-muted-foreground shrink-0 hover:text-foreground hover:underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
PR #{pr.number}
|
||||
|
||||
@@ -10,8 +10,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
||||
import {
|
||||
FolderOpen,
|
||||
Copy,
|
||||
Bell,
|
||||
BellOff,
|
||||
Link,
|
||||
MessageSquare,
|
||||
Moon,
|
||||
@@ -64,10 +62,6 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
|
||||
window.api.ui.writeClipboardText(worktree.path)
|
||||
}, [worktree.path])
|
||||
|
||||
const handleToggleRead = useCallback(() => {
|
||||
updateWorktreeMeta(worktree.id, { isUnread: !worktree.isUnread })
|
||||
}, [worktree.id, worktree.isUnread, updateWorktreeMeta])
|
||||
|
||||
const handleTogglePin = useCallback(() => {
|
||||
updateWorktreeMeta(worktree.id, { isPinned: !worktree.isPinned })
|
||||
}, [worktree.id, worktree.isPinned, updateWorktreeMeta])
|
||||
@@ -169,10 +163,6 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
|
||||
<Pencil className="size-3.5" />
|
||||
Rename
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={handleToggleRead} disabled={isDeleting}>
|
||||
{worktree.isUnread ? <BellOff className="size-3.5" /> : <Bell className="size-3.5" />}
|
||||
{worktree.isUnread ? 'Mark Read' : 'Mark Unread'}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={handleLinkIssue} disabled={isDeleting}>
|
||||
<Link className="size-3.5" />
|
||||
{worktree.linkedIssue ? 'Edit GH Issue' : 'Link GH Issue'}
|
||||
|
||||
@@ -259,9 +259,11 @@ export default function SortableTab({
|
||||
)}
|
||||
{showActivityAffordance ? (
|
||||
// Why: the activity marker sits to the LEFT of the tab title using
|
||||
// Orca's filled bell glyph (amber-500 with a subtle drop shadow)
|
||||
// so it matches the worktree-level bell in the sidebar — keeping
|
||||
// every "needs your attention" surface in Orca consistent.
|
||||
// Orca's filled bell glyph (amber-500 with a subtle drop shadow).
|
||||
// The sidebar WorktreeCard used to render the same bell; it now
|
||||
// uses a bold-title treatment (see docs/bold-instead-of-bell.md).
|
||||
// The tab bar keeps the bell for now — aligning the two surfaces
|
||||
// is a deliberate follow-up (out-of-scope in that doc).
|
||||
<span data-testid="tab-activity-bell" className="inline-flex shrink-0">
|
||||
<FilledBellIcon className="w-3 h-3 mr-1 text-amber-500 drop-shadow-sm" />
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user