feat(sessions): redesign the session list rows - #3766
Conversation
Design Review (Fable 5) — 🟡 CONCERNSAdvisory design-level review of Design-Verdict: CONCERNS Sound consolidation, but row context now hangs entirely on the auto-tagger, and the unread signal's new ranking contradicts the PR's own precedence principle. Watch
Suggestions
[DESIGN-REVIEWED] 9c2403e |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — 🟡 CONCERNSAdvisory UX-level review of UX-Verdict: CONCERNS The redesign's flagship signal — tags as row context — ellipsizes to nothing at the demonstrated width, and the status gutter's own tooltip can never appear. Watch
Suggestions
[UX-REVIEWED] 9c2403e |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsAll variables are defined before use, the removed No findings. [OPUS-REVIEWED] 9c2403e Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSAdvisory premise-level review of Evidence gathered: I read the contract, the intent file, the full patch, the surrounding First-Principles-Verdict: CONCERNS The gutter re-spells the subtitle chain's seven-branch precedence a second time — the exact drift the comment claims it prevents. What this change shipsIntent: give every session-row signal one predictable home so a user scanning many rows finds status, context, and identity in fixed spots — an ADDITION (design-driven redesign), and the diff matches the stated job.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 9c2403e |
e66e240 to
48ca11c
Compare
48ca11c to
35ad90b
Compare
|
Dispositions for the advisory reviews (Design 🟡 / UX 🟡 / First Principles 🟡) as of Fixed
Accepted / documented
Needs a decision (maintainer) — not silently changed
Rebutted / declined (advisory suggestions)
|
35ad90b to
5f72b91
Compare
Rework the middle scrollable session list (Figma node 3209:1006): meta line shows agent + tags (tinted, no border) then timestamp + pin; all 8 status indicators consolidated into one reserved-width gutter left of the headline (spinner for running, Goal/ShieldCheck/MessageCircleQuestionMark glyphs); session colour bar flush-left with radius-derived length; tighter row padding (px-3/gap-1.5); inset row divider; folder rows px-2.5 with the inline left-pad override removed.
5f72b91 to
9c2403e
Compare
|
Dispositions for the advisory reviews as of Fixed
Accepted / deferred (out of scope for this PR)
Needs a decision (maintainer) — explicitly your calls, not silently changed
None of these block: the PR is green and mergeable; every item above is advisory. |
#3766 added the session-row status gutter as an in-flow flex child, which added its 12px width plus a 6px gap to where the row's content column starts. Measured on a real render, that pushed a child session's agent label, title and tool-call subtitle 16px right of the folder NAME above them, so a folder and the sessions filed under it no longer shared a left edge and the sidebar read as two ragged columns. Three guides now hold, at the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad, so the folder header goes px-2.5 -> px-3.5 and the nested body ml-2 -> ml-3; the row's own pad stays pl-3.5. The status gutter moves out of the content flow (absolutely positioned inside that pad) — in flow it is what broke guide 2, and no class-level assertion could see it. Guide 3 is why the pad is 14 rather than 10: at 10 a subfolder's glyph and its own connector line sat 4px left of the column its sibling sessions' text starts on. Because the resulting algebra has no per-depth term, it is scale-free — depth 3 nests exactly as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-#1211 guide as a side effect. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. Also drop the title's mt-0.5 (row height 74 -> 62px), centre the gutter glyph on the row instead of deriving the headline's y, use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the empty-folder "New chat in X" affordance onto the same column as the sessions it stands in for. The two invariant tests #3766 rewrote to accept the misalignment are restored, extended with guide 3 at depth 2, and now pin what actually moved: the gutter's out-of-flow-ness. The captured numbers and the reference frame each one is in are recorded in the test header, because every previous rewrite of that file quoted an indent step without saying which frame it was measured in.
added its 12px width plus a 6px gap to where the row's content column
starts. Measured on a real render, that pushed a child session's agent
label, title and tool-call subtitle 16px right of the folder NAME above
them, so a folder and the sessions filed under it no longer shared a left
edge and the sidebar read as two ragged columns.
Three guides now hold, at the root lane and at every nesting depth:
1. a folder glyph sits on its own border-l connector line
2. a folder name shares one edge with the agent label / title /
tool-call subtitle of every session inside it
3. a NESTED folder's glyph sits on the content column of the sessions
filed beside it, so a subfolder reads as their peer
Solving all three simultaneously forces header pad == session row pad, so
the folder header goes px-2.5 -> px-3.5 and the nested body ml-2 -> ml-3;
the row's own pad stays pl-3.5. The status gutter moves out of the content
flow (absolutely positioned inside that pad) — in flow it is what broke
guide 2, and no class-level assertion could see it.
Guide 3 is why the pad is 14 rather than 10: at 10 a subfolder's glyph and
its own connector line sat 4px left of the column its sibling sessions'
text starts on. Because the resulting algebra has no per-depth term, it is
scale-free — depth 3 nests exactly as depth 2 does, and root-lane sessions
land on the root folder's glyph, which restores the original pre-#1211
guide as a side effect.
Measured on the built SPA: depth 1 glyph/connector 259, name and all three
text lines 278; depth 2 glyph/connector 278 (== depth 1's content column),
name/content 297; root lane content 259.
Also drop the title's mt-0.5 (row height 74 -> 62px), centre the gutter
glyph on the row instead of deriving the headline's y, use lucide Loader
for the running spinner (at 12px the LoaderCircle arc reads as a broken
ring where the spokes read as motion), and pull the empty-folder "New chat
in X" affordance onto the same column as the sessions it stands in for.
The two invariant tests #3766 rewrote to accept the misalignment are
restored, extended with guide 3 at depth 2, and now pin what actually
moved: the gutter's out-of-flow-ness. The captured numbers and the
reference frame each one is in are recorded in the test header, because
every previous rewrite of that file quoted an indent step without saying
which frame it was measured in.
A folder in the session list and the sessions filed under it did not share a left edge. #3766 added the row's status gutter as an in-flow flex child, so its 12px width plus a gap landed on the content column: the agent label, title and tool-call subtitle of every session inside a folder sat well right of that folder's own name. #3903 then tightened the same rows and got closer, but measured on a real render it still left the folder name 1px past its sessions' text and a nested folder's glyph 2px short of their column. Three guides now hold, in the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad. With D = 2, a MEASURED container differential (the header's box starts 2px left of the nested body's box, and no class in the component expresses it): guide 1 P = D + M 14 = 2 + 12 guide 2 P + G + g = D + M + B + p + R 33 = 33 guide 3 P = R 14 = 14 The status gutter moves out of the content flow (absolutely positioned inside that pad) and is centred on the row rather than deriving the headline's y; in flow it is what broke guide 2, and no class-level assertion could see it. Because the algebra has no per-depth term it is scale-free: depth 3 nests as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-#1211 guide. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. This reverts #3903's row and folder geometry, agreed with its author. Its collapsed-folder unread dot goes back inline on the right of the name rather than into an absolute left gutter: that gutter is what forced the header pad to 18px, and the pad is not free — it has to equal the session row's. The 2px colour bar from #3903 is kept, being orthogonal to the guides. Also drop the title's mt-0.5 (row height 49.6 -> 43.6px idle, 68.1 -> 62.1 running), use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the divider inset and the empty-folder "New chat in X" affordance onto the same column as the sessions. The alignment invariant test has been rewritten by each of the last two PRs to accept the then-current misalignment. It now pins all three guides, adds guide 3 at depth 2 with a nested fixture, and the gutter test pins the gutter as out-of-flow and row-centred. Every number in the test header is a measurement with its frame named: two paper estimates during this fix were 2px and 3px out.
A folder in the session list and the sessions filed under it did not share a left edge. #3766 added the row's status gutter as an in-flow flex child, so its 12px width plus a gap landed on the content column: the agent label, title and tool-call subtitle of every session inside a folder sat well right of that folder's own name. #3903 then tightened the same rows and got closer, but measured on a real render it still left the folder name 1px past its sessions' text and a nested folder's glyph 2px short of their column. Three guides now hold, in the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad. With D = 2, a MEASURED container differential (the header's box starts 2px left of the nested body's box, and no class in the component expresses it): guide 1 P = D + M 14 = 2 + 12 guide 2 P + G + g = D + M + B + p + R 33 = 33 guide 3 P = R 14 = 14 The status gutter moves out of the content flow (absolutely positioned inside that pad) and is centred on the row rather than deriving the headline's y; in flow it is what broke guide 2, and no class-level assertion could see it. Because the algebra has no per-depth term it is scale-free: depth 3 nests as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-#1211 guide. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. This reverts #3903's row and folder geometry, agreed with its author. Its collapsed-folder unread dot goes back inline on the right of the name rather than into an absolute left gutter: that gutter is what forced the header pad to 18px, and the pad is not free — it has to equal the session row's. The 2px colour bar from #3903 is kept, being orthogonal to the guides. Also drop the title's mt-0.5 (row height 49.6 -> 43.6px idle, 68.1 -> 62.1 running), use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the divider inset and the empty-folder "New chat in X" affordance onto the same column as the sessions. The alignment invariant test has been rewritten by each of the last two PRs to accept the then-current misalignment. It now pins all three guides, adds guide 3 at depth 2 with a nested fixture, and the gutter test pins the gutter as out-of-flow and row-centred. Every number in the test header is a measurement with its frame named: two paper estimates during this fix were 2px and 3px out.
A folder in the session list and the sessions filed under it did not share a left edge. #3766 added the row's status gutter as an in-flow flex child, so its 12px width plus a gap landed on the content column: the agent label, title and tool-call subtitle of every session inside a folder sat well right of that folder's own name. #3903 then tightened the same rows and got closer, but measured on a real render it still left the folder name 1px past its sessions' text and a nested folder's glyph 2px short of their column. Three guides now hold, in the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad. With D = 2, a MEASURED container differential (the header's box starts 2px left of the nested body's box, and no class in the component expresses it): guide 1 P = D + M 14 = 2 + 12 guide 2 P + G + g = D + M + B + p + R 33 = 33 guide 3 P = R 14 = 14 The status gutter moves out of the content flow (absolutely positioned inside that pad) and is centred on the row rather than deriving the headline's y; in flow it is what broke guide 2, and no class-level assertion could see it. Because the algebra has no per-depth term it is scale-free: depth 3 nests as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-#1211 guide. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. This reverts #3903's row and folder geometry, agreed with its author. Its collapsed-folder unread dot goes back inline on the right of the name rather than into an absolute left gutter: that gutter is what forced the header pad to 18px, and the pad is not free — it has to equal the session row's. The 2px colour bar from #3903 is kept, being orthogonal to the guides. Also drop the title's mt-0.5 (row height 49.6 -> 43.6px idle, 68.1 -> 62.1 running), use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the divider inset and the empty-folder "New chat in X" affordance onto the same column as the sessions. The alignment invariant test has been rewritten by each of the last two PRs to accept the then-current misalignment. It now pins all three guides, adds guide 3 at depth 2 with a nested fixture, and the gutter test pins the gutter as out-of-flow and row-centred. Every number in the test header is a measurement with its frame named: two paper estimates during this fix were 2px and 3px out.
A folder in the session list and the sessions filed under it did not share a left edge. #3766 added the row's status gutter as an in-flow flex child, so its 12px width plus a gap landed on the content column: the agent label, title and tool-call subtitle of every session inside a folder sat well right of that folder's own name. #3903 then tightened the same rows and got closer, but measured on a real render it still left the folder name 1px past its sessions' text and a nested folder's glyph 2px short of their column. Three guides now hold, in the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad. With D = 2, a MEASURED container differential (the header's box starts 2px left of the nested body's box, and no class in the component expresses it): guide 1 P = D + M 14 = 2 + 12 guide 2 P + G + g = D + M + B + p + R 33 = 33 guide 3 P = R 14 = 14 The status gutter moves out of the content flow (absolutely positioned inside that pad) and is centred on the row rather than deriving the headline's y; in flow it is what broke guide 2, and no class-level assertion could see it. Because the algebra has no per-depth term it is scale-free: depth 3 nests as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-#1211 guide. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. This reverts #3903's row and folder geometry, agreed with its author. Its collapsed-folder unread dot goes back inline on the right of the name rather than into an absolute left gutter: that gutter is what forced the header pad to 18px, and the pad is not free — it has to equal the session row's. The 2px colour bar from #3903 is kept, being orthogonal to the guides. Also drop the title's mt-0.5 (row height 49.6 -> 43.6px idle, 68.1 -> 62.1 running), use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the divider inset and the empty-folder "New chat in X" affordance onto the same column as the sessions. The alignment invariant test has been rewritten by each of the last two PRs to accept the then-current misalignment. It now pins all three guides, adds guide 3 at depth 2 with a nested fixture, and the gutter test pins the gutter as out-of-flow and row-centred. Every number in the test header is a measurement with its frame named: two paper estimates during this fix were 2px and 3px out.
The sidebar's three folder alignment guides have regressed four times (#1211, #3766, #3903, and twice on paper inside #3905), every time via a Tailwind-class derivation that omitted the 2px FOLDER_BODY_INSET_PX container differential between the folder header's box and the nested body's box. The jsdom test (ChatSidebar.folderAlignment.test.tsx) cannot catch that class of break: jsdom has no layout engine, so it asserts class tokens and an arithmetic identity between constants, never the rendered geometry. Add a Playwright spec to the `E2E (stub ACP backend, offline)` gate that seeds one folder, one subfolder, and sessions in both plus one ungrouped session via the harness gateway API, then asserts the guides on getBoundingClientRect().left of the real rendered elements: - guide 1: folder glyph on its border-l connector line (depth 1 and 2) - guide 2: folder name on the agent label / title of its sessions (depth 1 and 2) - guide 3: nested folder glyph on its sibling sessions' content column, and the root-lane identity (ungrouped content on the root glyph) - depth invariance: the depth-2 connector on depth 1's content column Falsified against the historical regression class: rebuilding with FOLDER_BODY_INSET_PX bumped 2 -> 4 fails the spec with "fGlyph=259 vs fConnector=261" while the jsdom test stays green. The missing-selector diagnostics were falsified the same way (a broken selector fails with "missing: [aAgent, ...]", not a bare timeout). Pre-push review (GPT + Opus model-pinned): no blocking. Fixed advisory findings: absolute sanity anchors so difference-only assertions cannot hold vacuously at left=0; seeding requests assert response.ok() so a 4xx fails as a seeding fault, not an alignment fault; the settle poll subsumes the render wait (budget fits the 30s test timeout) and names missing selectors; the serial-run dependency on the wipe discipline in session-tags-folders.spec.ts is documented in the header. The tool-call subtitle is not asserted: it needs a live agent turn to render, is a sibling of the title inside the same block container (equal left by construction), and its class parity is pinned by the jsdom test. MIN_EXECUTED_SPECS raised 219 -> 220 for the added spec. Closes #3914
The sidebar's three folder alignment guides have regressed four times (#1211, #3766, #3903, and twice on paper inside #3905), every time via a Tailwind-class derivation that omitted the 2px FOLDER_BODY_INSET_PX container differential between the folder header's box and the nested body's box. The jsdom test (ChatSidebar.folderAlignment.test.tsx) cannot catch that class of break: jsdom has no layout engine, so it asserts class tokens and an arithmetic identity between constants, never the rendered geometry. Add a Playwright spec to the `E2E (stub ACP backend, offline)` gate that seeds one folder, one subfolder, and sessions in both plus one ungrouped session via the harness gateway API, then asserts the guides on getBoundingClientRect().left of the real rendered elements: - guide 1: folder glyph on its border-l connector line (depth 1 and 2) - guide 2: folder name on the agent label / title of its sessions (depth 1 and 2) - guide 3: nested folder glyph on its sibling sessions' content column, and the root-lane identity (ungrouped content on the root glyph) - depth invariance: the depth-2 connector on depth 1's content column Falsified against the historical regression class: rebuilding with FOLDER_BODY_INSET_PX bumped 2 -> 4 fails the spec with "fGlyph=259 vs fConnector=261" while the jsdom test stays green. The missing-selector diagnostics were falsified the same way (a broken selector fails with "missing: [aAgent, ...]", not a bare timeout). Pre-push review (GPT + Opus model-pinned): no blocking. Fixed advisory findings: absolute coherence anchors so difference-only assertions cannot hold vacuously at left=0; seeding requests assert response.ok() so a 4xx fails as a seeding fault, not an alignment fault; the settle poll subsumes the render wait (budget fits the 30s test timeout) and names missing selectors; the serial-run dependency on the wipe discipline in session-tags-folders.spec.ts is documented in the header. The tool-call subtitle is not asserted: it needs a live agent turn to render, is a sibling of the title inside the same block container (equal left by construction), and its class parity is pinned by the jsdom test. MIN_EXECUTED_SPECS raised 219 -> 220 for the added spec. Closes #3914
… (#3942) The sidebar's three folder alignment guides have regressed four times (#1211, #3766, #3903, and twice on paper inside #3905), every time via a Tailwind-class derivation that omitted the 2px FOLDER_BODY_INSET_PX container differential between the folder header's box and the nested body's box. The jsdom test (ChatSidebar.folderAlignment.test.tsx) cannot catch that class of break: jsdom has no layout engine, so it asserts class tokens and an arithmetic identity between constants, never the rendered geometry. Add a Playwright spec to the `E2E (stub ACP backend, offline)` gate that seeds one folder, one subfolder, and sessions in both plus one ungrouped session via the harness gateway API, then asserts the guides on getBoundingClientRect().left of the real rendered elements: - guide 1: folder glyph on its border-l connector line (depth 1 and 2) - guide 2: folder name on the agent label / title of its sessions (depth 1 and 2) - guide 3: nested folder glyph on its sibling sessions' content column, and the root-lane identity (ungrouped content on the root glyph) - depth invariance: the depth-2 connector on depth 1's content column Falsified against the historical regression class: rebuilding with FOLDER_BODY_INSET_PX bumped 2 -> 4 fails the spec with "fGlyph=259 vs fConnector=261" while the jsdom test stays green. The missing-selector diagnostics were falsified the same way (a broken selector fails with "missing: [aAgent, ...]", not a bare timeout). Pre-push review (GPT + Opus model-pinned): no blocking. Fixed advisory findings: absolute coherence anchors so difference-only assertions cannot hold vacuously at left=0; seeding requests assert response.ok() so a 4xx fails as a seeding fault, not an alignment fault; the settle poll subsumes the render wait (budget fits the 30s test timeout) and names missing selectors; the serial-run dependency on the wipe discipline in session-tags-folders.spec.ts is documented in the header. The tool-call subtitle is not asserted: it needs a live agent turn to render, is a sibling of the title inside the same block container (equal left by construction), and its class parity is pinned by the jsdom test. MIN_EXECUTED_SPECS raised 219 -> 220 for the added spec. Closes #3914 Co-authored-by: Joe Guo <zejiangg@amazon.com>
…dotdev#3905) A folder in the session list and the sessions filed under it did not share a left edge. kirodotdev#3766 added the row's status gutter as an in-flow flex child, so its 12px width plus a gap landed on the content column: the agent label, title and tool-call subtitle of every session inside a folder sat well right of that folder's own name. kirodotdev#3903 then tightened the same rows and got closer, but measured on a real render it still left the folder name 1px past its sessions' text and a nested folder's glyph 2px short of their column. Three guides now hold, in the root lane and at every nesting depth: 1. a folder glyph sits on its own border-l connector line 2. a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it 3. a NESTED folder's glyph sits on the content column of the sessions filed beside it, so a subfolder reads as their peer Solving all three simultaneously forces header pad == session row pad. With D = 2, a MEASURED container differential (the header's box starts 2px left of the nested body's box, and no class in the component expresses it): guide 1 P = D + M 14 = 2 + 12 guide 2 P + G + g = D + M + B + p + R 33 = 33 guide 3 P = R 14 = 14 The status gutter moves out of the content flow (absolutely positioned inside that pad) and is centred on the row rather than deriving the headline's y; in flow it is what broke guide 2, and no class-level assertion could see it. Because the algebra has no per-depth term it is scale-free: depth 3 nests as depth 2 does, and root-lane sessions land on the root folder's glyph, which restores the original pre-kirodotdev#1211 guide. Measured on the built SPA: depth 1 glyph/connector 259, name and all three text lines 278; depth 2 glyph/connector 278 (== depth 1's content column), name/content 297; root lane content 259. This reverts kirodotdev#3903's row and folder geometry, agreed with its author. Its collapsed-folder unread dot goes back inline on the right of the name rather than into an absolute left gutter: that gutter is what forced the header pad to 18px, and the pad is not free — it has to equal the session row's. The 2px colour bar from kirodotdev#3903 is kept, being orthogonal to the guides. Also drop the title's mt-0.5 (row height 49.6 -> 43.6px idle, 68.1 -> 62.1 running), use lucide Loader for the running spinner (at 12px the LoaderCircle arc reads as a broken ring where the spokes read as motion), and pull the divider inset and the empty-folder "New chat in X" affordance onto the same column as the sessions. The alignment invariant test has been rewritten by each of the last two PRs to accept the then-current misalignment. It now pins all three guides, adds guide 3 at depth 2 with a nested fixture, and the gutter test pins the gutter as out-of-flow and row-centred. Every number in the test header is a measurement with its frame named: two paper estimates during this fix were 2px and 3px out.
…dotdev#3914) (kirodotdev#3942) The sidebar's three folder alignment guides have regressed four times (kirodotdev#1211, kirodotdev#3766, kirodotdev#3903, and twice on paper inside kirodotdev#3905), every time via a Tailwind-class derivation that omitted the 2px FOLDER_BODY_INSET_PX container differential between the folder header's box and the nested body's box. The jsdom test (ChatSidebar.folderAlignment.test.tsx) cannot catch that class of break: jsdom has no layout engine, so it asserts class tokens and an arithmetic identity between constants, never the rendered geometry. Add a Playwright spec to the `E2E (stub ACP backend, offline)` gate that seeds one folder, one subfolder, and sessions in both plus one ungrouped session via the harness gateway API, then asserts the guides on getBoundingClientRect().left of the real rendered elements: - guide 1: folder glyph on its border-l connector line (depth 1 and 2) - guide 2: folder name on the agent label / title of its sessions (depth 1 and 2) - guide 3: nested folder glyph on its sibling sessions' content column, and the root-lane identity (ungrouped content on the root glyph) - depth invariance: the depth-2 connector on depth 1's content column Falsified against the historical regression class: rebuilding with FOLDER_BODY_INSET_PX bumped 2 -> 4 fails the spec with "fGlyph=259 vs fConnector=261" while the jsdom test stays green. The missing-selector diagnostics were falsified the same way (a broken selector fails with "missing: [aAgent, ...]", not a bare timeout). Pre-push review (GPT + Opus model-pinned): no blocking. Fixed advisory findings: absolute coherence anchors so difference-only assertions cannot hold vacuously at left=0; seeding requests assert response.ok() so a 4xx fails as a seeding fault, not an alignment fault; the settle poll subsumes the render wait (budget fits the 30s test timeout) and names missing selectors; the serial-run dependency on the wipe discipline in session-tags-folders.spec.ts is documented in the header. The tool-call subtitle is not asserted: it needs a live agent turn to render, is a sibling of the title inside the same block container (equal left by construction), and its class parity is pinned by the jsdom test. MIN_EXECUTED_SPECS raised 219 -> 220 for the added spec. Closes kirodotdev#3914 Co-authored-by: Joe Guo <zejiangg@amazon.com>
Problem
The middle scrollable session list in the sessions panel had drifted from the current design (Figma node
3209:1006). Status was scattered — a running dot before the secondary line, an unread dot pinned to the row's far right, six other status glyphs each rendered inline before their own subtitle — so a row had no single place to look for "what is this session doing". Session context was absent from the row's meta line, tags rendered as bordered chips below the title, and the left colour bar floated 5px inside the row rather than marking its edge.Why it matters
The session list is the primary navigation surface of the dashboard; a designer scanning 40+ rows needs one predictable spot per signal. Scattered status markers and duplicated context (folder chip + tag chip both spelling the project name) add visual noise that slows the scan, and a colour bar that doesn't touch the row edge reads as decoration rather than identity.
Fix (symptoms → root cause → change)
The row had accreted markers in whatever spot each feature landed in. The redesign gives each concern one home:
agent · tag · tag … timestamp [pin]. The session's tags render as tinted plain text (each in its own tag colour, no border) after the agent name; the pin moved to the right of the timestamp. Tags are the context the row needs — the auto-tagger already labels each session with its project — so no separate repo-name derivation is added.Loader2spinner (was a pulsing dot); the unread "your turn" dot moved into the gutter and now sits last in precedence (it yields to any more specific state). Distinct glyphs replace the former bare dots:Goal(loop),ShieldCheck(needs approval),MessageCircleQuestionMark(needs your answer).left:0) with length = row height − 2 × corner radius, expressed as avar(--radius-md)vertical inset so it tracks the theme's radius instead of a hardcoded pixel. Applied identically to the solid and gradient colour modes.px-3/gap-1.5; the row divider is inset to the content x (ml-[30px]) so it underlines the text block rather than boxing the whole row; folder rows use symmetricpx-2.5(the inline 16px left-pad override removed); the nested connector line indentsml-2.Tests
ChatSidebar.metaLine.test.tsx(new) — pins all-tags-inline rendering, tag order, tinted-no-border, the dangling-dot guard, pin-last ordering, and no-separate-chip-row.ChatSidebar.statusGutter.test.tsx(new) — pins one glyph per row, the subtitle-chain precedence (owed decision > working signal), the spinner-not-dot for running, the unread dot in the gutter, reserved width when idle, the accessible name, and each distinct glyph (ShieldCheck/MessageCircleQuestionMark/Goal).sessionColorBar.test.ts(new) — a rule-text drift guard overindex.css: flush-left, radius-derived inset, pill radius, both colour-mode selectors agree.ChatSidebar.folderAlignment.test.tsx— updated to the new folder-header geometry (px-2.5 pad, no inline override) with token-exact class assertions (substring matching would pass vacuously on a fractional Tailwind step).ChatSidebar.flatView.test.tsx,ChatSidebar.listTags.test.tsx— updated: the flat-view folder annotation and the list-view tag pills are gone; tags render in the meta line.Manual verification
Verified each increment against a loopback preview that serves the real built bundle with fixtures (meta line, status gutter, colour bar, spacing, folder rows) and read every frame. Screenshots below are from that preview. All local gates green on the final commit:
tsc -b,eslint(0 errors),I18N_BASE_REF=origin/main npm run i18n:check, brand gate, and 676 sidebar/chat/colour-bar tests.Screenshots
Tag run ellipsizes at a narrow sidebar width
Deliberately traded / not included
#1211regression guards) no longer hold: the status gutter shifts a session row's content 12px right, so the folder glyph/name no longer land on sibling/child session text. This was a measured guide; it is abandoned on purpose andChatSidebar.folderAlignment.test.tsxnow pins the folder row's own proportions instead. The folder row reads as a header over its sessions rather than a peer.no linked issue: iterative design-driven redesign, no tracked issue.