Skip to content

refactor(spaces): rework sidebar row status, pins and row menus - #4008

Merged
trunk-io[bot] merged 17 commits into
mainfrom
ux/task-icons-rework
Jul 31, 2026
Merged

refactor(spaces): rework sidebar row status, pins and row menus#4008
trunk-io[bot] merged 17 commits into
mainfrom
ux/task-icons-rework

Conversation

@adamleithp

@adamleithp adamleithp commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reworks the spaces sidebar: what a row says about itself, where its actions live, and what colour "unread" is.

Row status vocabulary

  • The quiet state reads "All caught up".
  • A task's state is a single dot — blue wants a decision, amber is working or unread, grey is quiet — drawn from one table (taskStatusVocabulary.ts) instead of a per-call-site glyph. A working row gets a DotRingSpinner in the same 8px box, so labels start in one place down the list.
  • Identity moves to a stacked avatar group at the row's right edge: origin, cloud run, PR/branch state. Origins deliberately share one glyph; the PR badge is the one that earns colour.
  • Tooltips share a per-row provider, so moving from the dot to a badge doesn't re-wait the open delay.
  • A canvas keeps its template glyph, now in canvas violet, in that same stack.
  • New /design-system route renders the whole vocabulary as a table (taskIconSpecs.ts), so a state that has no name is visible.
2026-07-30 23 23 33

Pins

  • The PINNED section is gone. Pins sit in the one sessions list, marked by an amber pin badge that joins the row's stack — first, which in a reversed stack puts it leftmost and underneath.
  • They sort ahead of newer items: a pin is a request not to lose something, and below recency order it would fall off the 30-row cap.
image

Row actions

  • The button is gone from rows — at rest a row shows status, not controls.
  • Actions live in the hover card and on right-click, both built from one item definition so they can't drift.
  • Canvases get that menu: pin, and delete via the existing deleteCanvasWithUndo window (8s toast, nothing sent to the host until it expires). Filing and command-centre cells are task-shaped, so a canvas's menu drops them rather than showing them dead.
  • The hover card is quill's Card/Item parts throughout — no hand-rolled shadow or type scale, and the card's own padding is off so its rules run edge to edge and the action rows highlight full width. It's controlled, so it survives its own portaled "File to…" flyout.
  • "File to…" is a searchable flyout (SearchableMenuFlyout), pinned to its trigger rather than re-aligning as the list filters, and it opens on hover in the card the way a submenu does on right-click.
  • Deleting a canvas confirms first, with the copy the artifacts grid and canvas header already use — it goes for everyone in the space — and the undo window follows the confirm. Delete… is quill's destructive button.
  • A canvas waiting out its undo window flashes a red dot labelled "Deleting…": it stays in the list rather than vanishing, so the row has to say what's happening to it.
  • No separators in either surface.
2026-07-30 22 59 50

Sidebar chrome

  • Project switcher moves to the top, above the nav row (channels layout only; the code layout keeps it in the footer).
  • "New session" leads the space pane's list of pages, as a sidebar row.
  • Space rows drop the cube glyph — it said nothing the name didn't. #me keeps its lock; the SPACES heading keeps its icon.
  • Feed / Context / Loops / Artifacts drop their icons for the same reason.
  • Back row, sessions-header buttons and the menu trigger are quill buttons now, at quill's sizes and radii; rows use quill's focus ring instead of a custom amber outline.
  • The "Created by" filter is hidden in #me, where it can only answer "you" — and its value is neutralised there, so a filter set in a shared space can't silently empty the list with no control left to undo it.
image image

Unread colour

  • Count badges, unread dots, the row's status dot and the working spinner all read --primary — yellow in dark, orange in light — so "there is something here for you" is one colour across the window. The numeral takes --primary-foreground, so contrast follows the theme rather than a token picked per scale. Red is left for failure.
  • The pin badge keeps its own amber: primary means a claim on your attention, and a pin is a shelf.
image

Fixes

  • ⌘-slot hints were clipped 4px: quill's .quill-button kbd hangs a shortcut into its button's padding, and the row's truncate wrapper cut it off. Diagnosed against the running app over CDP — extra row padding doesn't help (the ml-auto re-absorbs it), zeroing the margin does.
  • A task with an open PR no longer reads as "working": the cloud workflow holds a run in_progress while it babysits CI, which under a merge queue can outlast the agent by hours.
  • Base UI no longer warns on every task-row render (nativeButton={false} on the nested menu trigger).

Testing

pnpm typecheck (24 packages) and pnpm --filter @posthog/ui test (2367 tests) pass. Row/menu/glyph behaviour is covered by unit tests; the badge and hover-card changes were checked against the running app.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt

adamleithp and others added 4 commits July 29, 2026 21:11
The cloud workflow holds a run at in_progress while it babysits CI after
opening the PR ("Keeping CI green"). Under the Trunk merge queue that wait
ends only when someone comments /trunk merge, so a run can claim to be
working for hours after the agent stopped: two tasks in the code space had
been spinning for 104 and 220 minutes, one with all 29 checks green.

The dot now goes quiet once a PR exists, and the badge stack shows the PR
even when its GitHub state is unresolved. latest_run.output.pr_url is
written the moment the PR opens, while prState needs a lookup a cloud-only
task never gets (TaskPrStatusService discards fresh state when the task
has no local workspace row). A task that opened a PR must not look like it
did nothing, so the badge appears uncoloured rather than not at all.

Not sure this is the right cut. It treats "has a PR" as terminal, which is
a guess about intent: a run that opens a draft PR and keeps pushing to it
is genuinely still working, and this now draws it as quiet. The real fix is
upstream in products/tasks, where the run should reach a terminal status
rather than being held open for a merge the queue won't perform unattended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYPVPkN4Lm3gygchrpcoqr
The sidebar list is one list now: pins sit in it with everything else,
marked by a badge in the row's own stack rather than living in a section
of their own, and canvases carry the same menu tasks do.

Rows
- Pinned items merge into the sessions list, sorted ahead of newer ones
  so a pin can't fall off the recents cap.
- The pin joins the row's badge stack in the vocabulary's amber; it goes
  first, which in a reversed stack puts it leftmost and underneath.
- Canvas badges are violet, matching the canvas colour everywhere else,
  and are named on hover like the task badges beside them.
- The row's "…" is gone: at rest a row shows status, not controls.

Row menu
- Actions move into the hover card, built from the same item list the
  right-click menu uses.
- Canvases get that menu: pin, and delete through the existing
  deleteCanvasWithUndo window. Filing and command-centre cells are task
  shaped, so they're dropped rather than shown dead.
- The card is quill's Card/Item parts throughout — no hand-rolled shadow
  — and is controlled so it survives its own "File to…" flyout.

Chrome
- Space rows drop the cube glyph; #me keeps its lock and the Spaces
  heading keeps its icon.
- Project switcher moves to the top of the sidebar, above the nav row.
- "New session" leads the space pane's list of pages.
- Back row, header buttons and the row menu trigger use quill buttons
  rather than hand-rolled ones; rows use quill's focus ring.
- The "Created by" filter is hidden in #me, where it can only answer
  "you", and its value is neutralised so a stale filter can't empty the
  list with no control left to undo it.

Unread
- Counts and unread dots read amber, the same colour the status dots use
  for "something is owed to you". Red is left for failure.

Fixes a 4px clip on the ⌘-slot hints: quill's `.quill-button kbd` hangs
4px into a button's padding, and the row's truncate wrapper cut it off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt
@trunk-io

trunk-io Bot commented Jul 30, 2026

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

React Doctor found 4 issues in 3 files · 4 warnings.

4 warnings

src/features/canvas/components/ChannelSidebar.tsx

src/features/sidebar/components/items/TaskStatusDot.tsx

src/primitives/SearchableMenuFlyout.tsx

Reviewed by React Doctor for commit 3cd46cb.

Comment thread packages/ui/src/features/canvas/hooks/useChannelTaskStatus.ts
Comment thread packages/ui/src/features/canvas/components/ChannelItemRow.tsx
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
packages/ui/src/features/canvas/hooks/useChannelTaskStatus.ts:25-28
**Canvas rows query task status**

Every canvas row passes an empty task ID to `useTaskPrStatus`, leaving the query enabled and causing unnecessary empty-key database and worktree checks on the server.

### Issue 2
packages/ui/src/features/canvas/components/ChannelItemRow.tsx:184-193
**Filing menu loses current space**

The task menu omits the current `channelId`, so the filing flyout marks every space as non-current. Selecting the task's existing space then performs an idempotent no-op while displaying a misleading “Filed to …” success toast.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "refactor(spaces): rework sidebar rows, r..." | Re-trigger Greptile

@posthog

posthog Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

👋 Visual changes detected for this PR.

Review and approve in PostHog Visual Review

If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix.

adamleithp and others added 6 commits July 30, 2026 15:50
The card was carrying quill's card padding on top of its own sections,
which read as a lot of air around four short lines. Its padding is off
now and each section pays for its own inset, so the rules run edge to
edge and the action rows highlight full width.

- Deleting a canvas confirms first, with the same copy the artifacts
  grid and the canvas header use — it goes for everyone in the space.
  The undo window still follows the confirm.
- A canvas waiting out that window flashes a red dot labelled
  "Deleting…". It stays in the list rather than vanishing, so the row
  has to say what is happening to it.
- "File to…" opens on hover in the card, the way a submenu does on
  right-click. Delete is quill's destructive button.
- Menu separators are gone from both surfaces.
- The quiet dot reads "All caught up" rather than "Nothing owed to you".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt
Unread was amber picked by hand; it's `--primary` now — yellow in dark,
orange in light — so the count badges, the unread dots and the row's own
dot are one colour, and the numeral takes `--primary-foreground` rather
than a contrast token chosen per theme.

The pin keeps its own amber: primary means "there is something here for
you", and a pin is a shelf, not a claim on your attention.

Also stops every badge in a row's stack offering a pointer cursor —
quill gives an avatar rendered as a button one, which promised a click
that did nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt
Three things the review turned up:

- Canvas rows share `useChannelTaskStatus`, which passes an empty task id
  to `useTaskPrStatus` — every canvas row was spending a round trip to be
  told nothing. No id now means no query.
- The row's menu didn't carry the space it's listed under, so "File to…"
  ticked nothing and filing a task to where it already lives reported a
  move that didn't happen.
- `TaskRowMenuList` builds its item components from the callbacks it's
  given, so an inline `onAction` remounted every button in the hover card
  on each render of the row.

Also gates the pending-delete lookup on canvases, which are the only
things that can be in that map.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt
It was a scratch page for reading the task-status vocabulary while it was
being written; the vocabulary is settled and the page has no reader.

Takes the route, its view, the Settings entry that opened it and the tab
target with it. The dot-ring spinner stays — it's a primitive the rows
use, and only the comment pointing at its old home changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrvaQrZNBNSexddG78u4Nt
@puemos
puemos self-requested a review July 31, 2026 06:43

@puemos puemos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks 🔥 (in a good way)

I did add some feedback, but none of it is blocking

</Badge>
</div>
)}
{author && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 Nice-to-have:

I think this should be much smaller, it's taking so much space. In a team , people should know a person just by the avatar? not sure but I'm sure this can have smaller footprint

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tightened it up and aligned it with top icon

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but ya more can be done here. I want to spend some time redoing this preview card alone.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense

</button>
</Button>
<DropdownMenu>
<DropdownMenuTrigger

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 Nice-to-have:

I wonder if we should align these statuses with the new statuses we have (the groups, not each one, like, waiting, working, done).

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will address later, good idea though

* working or unread, grey is quiet. The trigger renders as a span because rows are
* `<button>`s — a nested button would be invalid HTML.
*/
export function TaskStatusDot({ dot }: { dot: TaskDot }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 Nice-to-have:

I really like the stack and the way you did it, but I do think that it's hard to see when something is pinned at a glance. I'm not sure if it needs to have maybe a color?

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// agent typing right now, not a run status we inferred.
const runClaimsWork =
props.taskRunStatus === "in_progress" || props.taskRunStatus === "queued";
if (props.isGenerating || (runClaimsWork && !hasPullRequest(props))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Non-blocking warning

When I run cloud or a local task, the progress icon appears, nice, but after the agent finishes a round, the icon stays in progress.

I’d prefer the yellow dot to indicate pending, since no work is being done.

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamleithp this works way better now, but there's another minor issue: when the task is starting, it shows it as a yellow dot, which is okay but not the best

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo nice catch, ill do a follow up

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the running state in light mode is not noticeable enough, will address in follow up

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super

});
}
if (badges.length === 0) {
badges.push({ key: "local", Icon: ChatCircle, label: "Local task" });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Non-blocking warning

From far, it's a bit difficult to distinguish between the icons for local and cloud.

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lovely

so under the layout it sits above the nav row rather than in the
footer. Its menu opens downward, which is the right direction
from the top of a sidebar. */}
<Box className="shrink-0 px-2 pb-1">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Non-blocking warning

I feel like this area became a bit loaded with different features that it's a bit hard.

My recommendation is to move the project selector to the top-right

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed, will push my changes through and we'll tweak it later (didn't move the project switcher, just made it more or less noticeable)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's looks more tidy now already with the change, thanks!

<PreviewCard.Root>
// One tooltip provider per task row, shared by its dot and badges so moving
// between them doesn't re-wait the open delay. Canvas rows have neither.
const row = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 Nice-to-have

I don't like using vars for components because it's harder to reason about later (but I'm only human...). Maybe this belongs in a component? I could be wrong

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ill think about this

@trunk-io
trunk-io Bot merged commit aa6b6ca into main Jul 31, 2026
34 checks passed
@trunk-io
trunk-io Bot deleted the ux/task-icons-rework branch July 31, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants