You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Keep the session name at a stable horizontal baseline in the left sidebar as a session toggles between idle / running / waiting-on-permission / errored / has-unseen-messages. At the same time, make the Pin and Sort (filter) icons visually match the rest of the sidebar icon set instead of rendering noticeably bolder.
What do you do today?
Two independent issues on the left side of each session row.
1. Status slot width is conditional.packages/app/src/pages/layout/sidebar-items.tsx:116-136 wraps the entire status indicator (size-6) in <Show when={props.isWorking() || props.hasPermissions() || props.hasError() || props.unseenCount() > 0}>. When a session has none of those four states the whole size-6 block is removed from layout, so the session title starts roughly one slot-width plus the gap-2 further left (≈32px) than an adjacent row that does have one of those states. As states come and go (a run starts, a permission prompt clears, an error is acknowledged) the title shifts horizontally.
Note that the Pin button slot to the left of the status slot is not the source of the shift. Its base class at packages/app/src/pages/layout/pawwork-sidebar.tsx:146 ("inline-flex size-6 ...": true) is unconditional; Pin toggles opacity and pointer-events but always reserves size-6.
2. Pin and Filter SVGs are off-family.packages/app/src/pages/layout/pawwork-sidebar.tsx ships two hand-drawn SVGs at viewBox="0 0 12 12": FilterIcon (L20-27) uses stroke-width="1.2" + stroke-linecap="round", and PinIcon (L29-41) uses stroke-width="1.1" + stroke-linejoin="round". The majority of sidebar icons in packages/ui/src/components/icon.tsx use viewBox="0 0 20 20" with default stroke width and per-glyph linecap, so the Pin and Filter icons render noticeably bolder and less uniform than their neighbours.
What would a good result look like?
Every session row reserves a stable size-6 status slot whether or not any status is currently active; the session title starts at the same horizontal position on every row at the same nesting level. The existing priority switch inside the slot is unchanged, including its tint from messageAgentColor(...) and its behaviour on child sessions. (Effective priority is permission → running → error → unseen — hasPermissions() forces isWorking() to false at sidebar-items.tsx:173, so permission wins over running even though the <Switch> lists isWorking first.)
Pin and Filter icons sit in the same visual weight family as the rest of the sidebar: 20×20 grid, default stroke width, per-glyph linecap chosen to match neighbouring glyphs.
Which audience does this matter to most?
Both
Extra context
Scope — ship as one PR with two commits, one per section below.
Commit 1 — fix(app): reserve sidebar status slot width to keep session title baseline stable
packages/app/src/pages/layout/sidebar-items.tsx:116-136 — change the outer <Show when={...}> into an unconditional size-6 container. The inner <Switch> (running / permission / error / unseen) stays conditional, so when no state is active the container renders empty but still occupies size-6 of width.
No behaviour change for active states: spinner keeps its tint, dot colours unchanged, priority order unchanged.
Child sessions are automatically covered for the status slot because SessionRow is the same component for every session regardless of level. The Pin slot remains top-level only by design — SessionItem gates the Pin leadingSlot on !props.level at sidebar-items.tsx:231, and this issue does not change that. Child rows therefore still have a narrower leading offset than top-level rows; that is intentional, not a regression.
Commit 2 — refactor(app): redraw sidebar Pin/Filter icons on the 20×20 grid
packages/app/src/pages/layout/pawwork-sidebar.tsx:20-27 (FilterIcon) and :29-41 (PinIcon) — change viewBox to "0 0 20 20" and rewrite the path coordinates to the 20-unit grid (changing only the viewBox attribute would render the glyph at ~60% of its visual size). Drop the explicit stroke-width. Pick stroke-linecap per glyph to match the visual weight of the neighbouring icons it sits beside (new-session, dot-grid, archive); upstream icon.tsx uses both square and round on different glyphs, so there is no single "correct" value to paste.
The icons stay file-local in pawwork-sidebar.tsx. Do not add them to packages/ui/src/components/icon.tsx, which follows upstream.
Promoting Pin / Filter into packages/ui/src/components/icon.tsx.
Manual UI verification (run bun dev:desktop)
After Commit 1:
Open a project with a mix of idle, running, pinned, and unpinned sessions. Scroll through and hover each row. Titles do not shift horizontally — every row at the same nesting level starts at the same x-position regardless of status slot activity.
Start a session. Spinner appears in the status slot (in agent tint); title does not move.
A session reporting a permission request, an error, or unseen messages shows the yellow / red / blue dot respectively in the status slot; title does not move.
Expand a parent session that has child sessions. Child rows obey the same stable baseline; their status indicators (when any) appear in the reserved slot.
After Commit 2:
5. Place the sidebar next to rows that use the new-session glyph and the dot-grid action menu. Pin and Filter icons read as the same visual weight — no bolder stroke, no tighter curves.
6. Pinned, no hover → filled pin in text-accent-brand on a 20×20 grid.
7. Unpinned, hover → outline pin in text-text-weak on the 20×20 grid.
8. Sort button active (sort=project) → filter icon in text-accent-brand on the 20×20 grid.
9. Keyboard-only: Tab through a pinned session row → focus lands on pin button, Enter toggles unpin. Tab through an unpinned row → pin button is not in the tab order (unchanged behaviour, just re-verify the redraw did not regress the contract).
What task are you trying to do?
Keep the session name at a stable horizontal baseline in the left sidebar as a session toggles between idle / running / waiting-on-permission / errored / has-unseen-messages. At the same time, make the Pin and Sort (filter) icons visually match the rest of the sidebar icon set instead of rendering noticeably bolder.
What do you do today?
Two independent issues on the left side of each session row.
1. Status slot width is conditional.
packages/app/src/pages/layout/sidebar-items.tsx:116-136wraps the entire status indicator (size-6) in<Show when={props.isWorking() || props.hasPermissions() || props.hasError() || props.unseenCount() > 0}>. When a session has none of those four states the wholesize-6block is removed from layout, so the session title starts roughly one slot-width plus thegap-2further left (≈32px) than an adjacent row that does have one of those states. As states come and go (a run starts, a permission prompt clears, an error is acknowledged) the title shifts horizontally.Note that the Pin button slot to the left of the status slot is not the source of the shift. Its base class at
packages/app/src/pages/layout/pawwork-sidebar.tsx:146("inline-flex size-6 ...": true) is unconditional; Pin togglesopacityandpointer-eventsbut always reservessize-6.2. Pin and Filter SVGs are off-family.
packages/app/src/pages/layout/pawwork-sidebar.tsxships two hand-drawn SVGs atviewBox="0 0 12 12":FilterIcon(L20-27) usesstroke-width="1.2"+stroke-linecap="round", andPinIcon(L29-41) usesstroke-width="1.1"+stroke-linejoin="round". The majority of sidebar icons inpackages/ui/src/components/icon.tsxuseviewBox="0 0 20 20"with default stroke width and per-glyph linecap, so the Pin and Filter icons render noticeably bolder and less uniform than their neighbours.What would a good result look like?
size-6status slot whether or not any status is currently active; the session title starts at the same horizontal position on every row at the same nesting level. The existing priority switch inside the slot is unchanged, including itstintfrommessageAgentColor(...)and its behaviour on child sessions. (Effective priority ispermission → running → error → unseen—hasPermissions()forcesisWorking()tofalseatsidebar-items.tsx:173, so permission wins over running even though the<Switch>listsisWorkingfirst.)Which audience does this matter to most?
Both
Extra context
Scope — ship as one PR with two commits, one per section below.
Commit 1 —
fix(app): reserve sidebar status slot width to keep session title baseline stablepackages/app/src/pages/layout/sidebar-items.tsx:116-136— change the outer<Show when={...}>into an unconditionalsize-6container. The inner<Switch>(running / permission / error / unseen) stays conditional, so when no state is active the container renders empty but still occupiessize-6of width.tint, dot colours unchanged, priority order unchanged.SessionRowis the same component for every session regardless oflevel. The Pin slot remains top-level only by design —SessionItemgates the PinleadingSloton!props.levelatsidebar-items.tsx:231, and this issue does not change that. Child rows therefore still have a narrower leading offset than top-level rows; that is intentional, not a regression.Commit 2 —
refactor(app): redraw sidebar Pin/Filter icons on the 20×20 gridpackages/app/src/pages/layout/pawwork-sidebar.tsx:20-27(FilterIcon) and:29-41(PinIcon) — changeviewBoxto"0 0 20 20"and rewrite the path coordinates to the 20-unit grid (changing only theviewBoxattribute would render the glyph at ~60% of its visual size). Drop the explicitstroke-width. Pickstroke-linecapper glyph to match the visual weight of the neighbouring icons it sits beside (new-session,dot-grid,archive); upstreamicon.tsxuses bothsquareandroundon different glyphs, so there is no single "correct" value to paste.pawwork-sidebar.tsx. Do not add them topackages/ui/src/components/icon.tsx, which follows upstream.Relationship to #77
text-accent-brandtotext-text-strongortext-accent-brand/60) stays in [Feature] UI polish batch: cohesion, affordance, hierarchy #77 for a later polish pass.Not in scope
size-6unconditional, no change needed.tabIndex={isPinned ? 0 : -1}+aria-hidden={isPinned ? undefined : "true"}, hover-to-discover on unpinned rows) — unchanged.packages/ui/src/components/icon.tsx.Manual UI verification (run
bun dev:desktop)After Commit 1:
tint); title does not move.After Commit 2:
5. Place the sidebar next to rows that use the
new-sessionglyph and thedot-gridaction menu. Pin and Filter icons read as the same visual weight — no bolder stroke, no tighter curves.6. Pinned, no hover → filled pin in
text-accent-brandon a 20×20 grid.7. Unpinned, hover → outline pin in
text-text-weakon the 20×20 grid.8. Sort button active (sort=project) → filter icon in
text-accent-brandon the 20×20 grid.9. Keyboard-only: Tab through a pinned session row → focus lands on pin button, Enter toggles unpin. Tab through an unpinned row → pin button is not in the tab order (unchanged behaviour, just re-verify the redraw did not regress the contract).