Skip to content

fix(sidebar): put folder rows and session rows on one left edge - #3905

Merged
bolichen97 merged 1 commit into
mainfrom
feat/sidebar-compact
Aug 16, 2026
Merged

fix(sidebar): put folder rows and session rows on one left edge#3905
bolichen97 merged 1 commit into
mainfrom
feat/sidebar-compact

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

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, and the sidebar read as two ragged columns.

#3903 then tightened the same rows and got closer, but measured on a real render it still leaves the folder name 1px past its sessions' text and a nested folder's glyph 2px short of its siblings' column.

Why it matters

The session list is the most-looked-at surface in the dashboard. A ragged left edge is read as "this app is sloppy" long before anyone can name the cause — and the cause has been mis-derived four times now, so each attempt paid a full review cycle.

The report that opened this work asked for two things, and the second one is why row height changes here rather than in a separate PR: the rows were called out as both misaligned and bulky ("can we compress the left side"). So the 6px height cut is declared intent, not a ride-along — horizontal alignment and vertical density were one request.

What changed (motivation → approach → change)

Fixing one number was not enough: the three edges are one system. Solving them together forces the folder header's left pad to equal the session row's left pad, and everything else follows.

With D = 2 = FOLDER_BODY_INSET_PX, the nested body's own left inset. FolderBody applies it so its collapse animation (grid-template-rows + overflow: hidden) does not clip its children; it is invisible in the class list, which is why four revisions derived this geometry from Tailwind classes and each landed 2px out. It is now a named export that FolderBody uses, the alignment test imports as D, and that test asserts against the rendered padding-left — so the algebra has no free empirical term and a change to it fails a unit test (verified: set it to 3 and guide 1 goes red with expected 14 to be 15):

guide
1 a folder glyph sits on its own border-l connector line P = D + M14 = 2 + 12
2 a folder name shares one edge with the agent label / title / tool-call subtitle of every session inside it P + G + g = D + M + B + p + R33 = 33
3 a nested folder's glyph sits on its sibling sessions' content column P = R14 = 14

The algebra has no per-depth term, so it is scale-free: the guides hold in the root lane and at every nesting depth.

  • status gutter: in-flow flex child → absolute left-px top-1/2 -translate-y-1/2, so it costs the content column nothing, and centred on the row instead of deriving the headline's y from repeated type classes (measured 10.25px above centre on a three-line row before)
  • folder header → px-3.5, nested body → ml-3 pl-1, session row → pl-3.5 pr-3 py-1.5, folder glyph→name gap gap-2gap-[5px] (at 8 the name overshoots the content column by 3px)
  • title: drop mt-0.5, row py-2py-1.5 — row height 49.6 → 43.6px idle, 68.1 → 62.1px running (the density half of the request)
  • running spinner Loader2/LoaderCircleLoader, requested by the repo owner for this row. Note the size argument for it (a 270° arc reads as a broken ring at 12px) would indict four other 12px Loader2 sites, so it is not offered as a general rule here — that rule is put to the maintainer in Decide one rule for small-size spinners: Loader vs LoaderCircle across 21 sites #3925 rather than applied to four unrelated files inside an alignment fix
  • divider inset ml-[20px]ml-[14px], and the empty-folder "New chat in X" affordance onto the same column as the sessions it stands in for
  • the collapsed-folder unread dot gains role="img" + an aria-label/title reusing the session row's own i18n key. It had neither: a colour-only dot beside a count is invisible to a screen reader and indistinguishable from styling, while the session-row marker it mirrors has been labelled since feat(sessions): redesign the session list rows #3766. Raised by the UX Review gate on this PR; in scope because this PR is what moved that dot

Relationship to #3903

#3903 landed on these same rows 20 minutes after this PR opened. Its row and folder geometry is reverted here, agreed with its author. Concretely:

  • its collapsed-folder unread dot goes back inline on the right of the folder name, where it lived before, rather than into an absolute left gutter. That gutter is what forced the header pad to pl-[18px], and the pad is not free — guide 3 requires it to equal the session row's pad. Putting the dot back is what lets the pad be 14.
  • its ml-4 body indent, gap-2 glyph→name gap and pl-1 pr-2 gap-1 row padding are replaced by the values the three guides solve to.
  • its 2px colour bar is kept (index.css), being orthogonal to the guides.

Board view rows do change (content offset 20 → 14, height 49.6 → 43.6): the row component is shared, and the absolute gutter makes the left pad no longer free to differ per scope. Board's own folder header keeps its 6px pad, so the guides are a tree-layout property there as before.

Measured x (CSS px, real renders)

connector folder glyph name / agent / title / subtitle
pre-#3766 (d926ca569^), pod 23 25 44 / 44
#3766, pod 19 19 38 / 54
#3903, built SPA 263 263 285 / 284 ← 1px apart
this branch, depth 1 259 259 278
this branch, depth 2 278 ← depth 1's content column 278 297
this branch, root lane 259 ← the root folder's glyph

Baselines rendered on isolated pods; the last three rows on the built SPA behind scripts/lib/serve-dist.mjs.

Tests

ChatSidebar.folderAlignment.test.tsx has been rewritten by each of the last two PRs to accept the then-current misalignment (#3766's header read "those guides no longer hold: … That is a decision, not drift"). It now pins all three guides, adds guide 3 asserted at depth 2 with a nested fixture, and ChatSidebar.statusGutter.test.tsx pins the gutter as out-of-flow and row-centred — that being the thing that actually moved the content, and the thing no class-token assertion could see.

Every number in the test header is a measurement, with the frame it was measured in named. Two paper estimates inside this PR were 2px and 3px out.

The one term that mattered most is no longer a number at all: D is imported from the component and checked against the rendered padding, so the offset behind all four historical breakages now fails a test when it moves.

Gates: tsc clean, eslint 0 errors, npm run i18n:check all green, 20445 frontend tests pass.

Manual verification

Rendered the built SPA and measured all four anchors with getBoundingClientRect at depth 1, depth 2 and the root lane; screenshots below are from that same render.

Screenshots / video

temp-screenshots/session-row-alignment/ — the frame, and the same frame with guides 1 and 2 drawn on. Guide 3 is visible in the second image as the nested Sidebar folder icon sitting on the red line.

Known cosmetic limit

Element boxes align exactly, which is the only thing measurable and assertable. The lucide folder glyph's path starts at x=2 in a 24 viewBox, so its ink is inset ~0.6 CSS px from its box and a nested folder icon reads a hair right of the text column. Both depths are inset identically, so it is consistent rather than ragged; correcting the ink would need a negative offset that fights the algebra.

Issue link

no linked issue: this is a regression fix on #3766 / #3903, filed as neither. Follow-up filed for the missing E2E assertion: #3914

@CrysisDeu
CrysisDeu requested a review from a team August 16, 2026 05:23
@CrysisDeu
CrysisDeu requested a review from a team as a code owner August 16, 2026 05:23
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 16, 2026
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

Advisory UX-level review of 7db2aa74bd84973d049a1c2e827537359c8b4dab — updated in place on each push; does not block merge.

UX-Verdict: PASS

Alignment fix is real and visible in the screenshots; density cut was requested; the folder unread dot even gains an accessible name it never had.

Watch

  • Unread state now lives in two places by row type: session rows mark it in the left gutter, collapsed folders mark it inline-right before the count (hasUnread && folder.collapsed dot moved beside {count}). A user scanning the left edge for "what needs me" misses collapsed folders every time. The stated blocker — the dot's gutter forced the 18px pad — is obsolete under this PR's own mechanism: an absolute left-px dot inside the 14px pad (exactly how session rows do it) costs the pad nothing. Frequency high (every unread folder) × impact low (friction, not failure) × persistent.

Suggestions

  • The running-row spinner (Loader size={12} at left-px, box ending at x=13) sits ~1–2px from the title at x=14 — in after-session-rows.png it visually touches "Session list row alignment". Drop the glyph box to left-0 or render the spinner at 10–11px inside the 12px box for breathing room.

[UX-REVIEWED] 7db2aa7

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 7db2aa74bd84973d049a1c2e827537359c8b4dab and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 7db2aa7

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 7db2aa74bd84973d049a1c2e827537359c8b4dab: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Advisory design-level review of 7db2aa74bd84973d049a1c2e827537359c8b4dab — updated in place on each push; does not block merge.

Design-Verdict: PASS

Fixes the root cause — the hidden FOLDER_BODY_INSET_PX term is named, exported, and test-asserted, so the recurring breakage class dies rather than repeating.

[DESIGN-REVIEWED] 7db2aa7

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 7db2aa74bd84973d049a1c2e827537359c8b4dab — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 7db2aa7

Verdict parsed from the review's SHA-scoped output markers for commit 7db2aa74bd84973d049a1c2e827537359c8b4dab.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 7db2aa74bd84973d049a1c2e827537359c8b4dab: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Advisory premise-level review of 7db2aa74bd84973d049a1c2e827537359c8b4dab — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push; does not block merge.

I've read the contract, the intent file, and the patch, and checked the repo for sibling patterns (the Loader2 sweep, the FOLDER_BODY_INSET_PX consumers, the screenshot-directory convention). Findings below; this is my final review output.

First-Principles-Verdict: CONCERNS

The alignment fix is cause-level and earns its place; a spinner-icon swap and a 6px row-density change ride along, and the spinner's own rationale indicts 21 untouched siblings.

What this change ships

Intent: make folder rows and the sessions inside them share one left edge — a FIX.

  1. Folder names and session text share one left edge at every depth — justified
  2. Status glyph moved out of the text flow, centred on the row — justified, cause-level
  3. Collapsed-folder unread dot moves from left gutter back inline after the name — justified (pad equality requires it)
  4. That dot now carries a screen-reader label it never had — undeclared
  5. Session rows shrink 6px (mt-0.5 dropped, py-2py-1.5) — rides along
  6. Running spinner icon Loader2Loader — rides along, symptom-level
  7. Row divider inset 20 → 14px — justified, follows from the algebra
  8. Board view rows shift left 6px and shrink — declared consequence of the shared row
  9. Empty-folder "New chat in X" aligns to the session column — justified
  10. FOLDER_BODY_INSET_PX exported and pinned by the alignment test — justified (four counted prior breakages)

Watch

  • The spinner swap's stated cause — "at 12px the arc reads as a broken ring" — is a property of Loader2 at small sizes, not of this row. Grepped Loader2 size={10|11|12}: 21 occurrences across 17 files (QueueStack, ErrorCard, SubagentProgressBar, WorkflowRunTree, …) all left unfixed. One-row point patch of a nameable general cause.
  • The 6px row-height reduction has no named harm anywhere in the description — nobody is reported as finding rows too tall; it is not a term in any of the three guides. It also drives the declared Board-view height change, so the blast radius of an unjustified item crosses surfaces.
  • Item 4 (dot role="img" + label) is a defensible accessibility floor but the description never mentions it — undeclared.

Subtractions

  • Drop the Loader2Loader swap from this PR; refile it as the general sweep of all 21 small-size Loader2 spinners its own rationale describes.
  • Drop the density change: keep py-2 and the title's mt-0.5 — neither appears in the guide algebra, and removing them shrinks the diff and the Board-view side effect.

[FIRST-PRINCIPLES-REVIEWED] 7db2aa7

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 16, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/sidebar-compact branch from cc70f15 to d2a0d3c Compare August 16, 2026 05:56
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Aug 16, 2026
@CrysisDeu CrysisDeu changed the title fix(sidebar): realign folder name with the session rows under it fix(sidebar): put folder rows and session rows on one left edge Aug 16, 2026
@github-actions github-actions Bot added merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 16, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/sidebar-compact branch from d2a0d3c to 6380d63 Compare August 16, 2026 06:26
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 16, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Dispositions

  • GPT 5.6 — ChatSidebar.tsx:2576, "Board calls share pl-3.5 pr-3 py-1.5, shifting and shrinking rows despite the stated untouched geometry → condition the new row geometry on list/flat scopes."partly fixed, partly rebutted.

    Board calls share "pl-3.5 pr-3 py-1.5", shifting and shrinking rows despite the stated untouched geometry

    The observation is correct and the PR body was wrong — it claimed board view was untouched. Fixed: that sentence is gone and the body now states board rows change too, and how.

    The suggested fix is rebutted. The row's left pad is no longer free to differ per scope, because the status gutter is now absolute left-px w-3 and renders in every scope: a board row conditioned back to a ~4px left pad would put the 12px glyph on top of the first characters of the agent label. Conditioning the pad therefore forces conditioning the gutter as well — two divergent geometries for the same row component, to buy back a 2px left offset and 4px of row height that are an improvement in board view too (content offset 20 → 18, row 49.6 → 43.6px, measured). No scope conditional added.

  • Design Review — 🟡 CONCERNS: the algebra hangs on D = 2, nothing in code owns it, and the jsdom arithmetic compares constants to each other rather than to the DOM.accepted-and-deferred, with the concern restated verbatim in the issue.

    Nothing in code owns or produces that 2px; if the wrapper generating it changes, all three guides break silently while every jsdom class assertion — including the expect(P).toBe(D + M) arithmetic, which compares constants declared in the test file to each other, not to the DOM — stays green.

    This is correct, it is the sharpest finding on the PR, and it names the mechanism behind all four historical breakages rather than any of their symptoms. It is also the one thing this PR cannot fix from inside its own scope: the assertion has to live in the browser E2E gate, where a real layout engine exists. Filed as a separate task rather than widening a geometry fix into website/playwright + the E2E workflow — that gate is serial and long, and a flaky x-assertion landing here would block a fix that is otherwise green.

    Recorded honestly: two paper estimates during this very PR were 2px and 3px out (a px-2 header pad, and a predicted 285/307 that measured 282/301). Every number in the PR body and the test header is a measurement for that reason.

Rebased onto origin/main after #3903 landed on the same rows 20 minutes after this PR opened; that collision, and how the two were reconciled, is described in the PR body.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 16, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/sidebar-compact branch from 6380d63 to 5537051 Compare August 16, 2026 06:45
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Aug 16, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Revert of #3903's row geometry — confirmed with its author

Rebased and re-solved: #3903's row and folder geometry is reverted here, with its author's agreement (confirmed by the repo owner out of band). What that means precisely, since a partial revert is easy to misread:

#3903 here why
folder header pl-[18px] pr-3 px-3.5 guide 3 requires header pad == session row pad; 18 breaks it
unread dot absolute left-1.5 inline, right of the name the left gutter is the only reason the pad had to be 18
body ml-4, no inner pad ml-3 pl-1 2 + 12 == 14, which is guide 1
row pl-1 pr-2 gap-1 pl-3.5 pr-3 the gutter is out of flow, so the pad is the whole content offset
glyph→name gap-2 gap-[5px] at 8 the name overshoots the content column by 3px
2px colour bar (index.css) kept orthogonal to the guides

Re-measured on the built SPA after the revert — all three guides hold at both depths and in the root lane: depth 1 connector/glyph 259, name/agent/title/subtitle 278; depth 2 connector/glyph 278 (== depth 1's content column), name/content 297; root-lane session content 259 (== the root folder's glyph). Gutter position: absolute, centre offset 0 on both a one-line and a three-line row.

Screenshots and the test header are updated to these numbers. tsc clean, eslint 0 errors, i18n green, 20445 frontend tests pass.

@github-actions github-actions Bot removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Aug 16, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/sidebar-compact branch from 5537051 to 6f9e23e Compare August 16, 2026 07:08
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Aug 16, 2026
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.
@CrysisDeu
CrysisDeu force-pushed the feat/sidebar-compact branch from 6f9e23e to 7db2aa7 Compare August 16, 2026 07:32
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Aug 16, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Dispositions — 6f9e23e18

UX Review 🟡 CONCERNS

  • The unread signal now scans in two positions, and the folder dot has no accessible name.the a11y half is fixed; the placement is the owner's explicit decision.

    the dot reads as color-only decoration next to the count with no aria-label/title (the session-row equivalent gets role="img" aria-label)

    Correct, and it was a real gap in code this PR touched. Fixed: the collapsed-folder dot now carries role="img" plus aria-label/title, reusing the same i18n key as the session-row marker (pages.chatSidebar.agent_finished_your_turn) so the two signals read identically to a screen reader and no new locale entry is needed.

    the collapsed-folder unread dot moves from the left gutter to a bare accent <span> inline before the count — session rows keep their unread dot on the LEFT, so the same "agent finished" signal now scans in two positions

    The observation holds, and the tradeoff is real — but the placement is not this PR's judgement to make: fix(sessions): tighten session-list spacing, 2px colour bar, left folder unread dot #3903 moved that dot into a left gutter, the repo owner asked for it back on the right (having checked with fix(sessions): tighten session-list spacing, 2px colour bar, left folder unread dot #3903's author), and the revert is what makes the pad 14 and guide 3 hold. So the left gutter is unavailable and the right position is the decided one. I have not accent-tinted the count: that is a visual design change on a surface whose owner has just stated a preference, so it belongs to them rather than to an alignment fix. Flagging it here so the choice is visible rather than silent.

  • "Board view rows do change" ships on shared-component algebra alone; no Board render exists.fixed, with a real render.

    One-time verification gap on a daily surface: attach a Board screenshot before merge.

    Fair, and it was the weakest claim in the PR — I had rebutted a GPT finding about that surface without ever rendering it. Rendered now: temp-screenshots/session-row-alignment/after-column-view.png, the tag-column view with a column, a folder inside it, and both a foldered and a root-lane row. Measured in that view: rows report data-session-scope="c1" (the column id, so it really is the column code path), content left edges 281 inside the folder and 266 in the column's root lane, row height 62.13 — i.e. the same geometry the list view gets, aligned within the column and with no glyph/text overlap. The claim is now evidenced rather than derived.

    Worth recording for the next person: the view needs a server-provided tag column, so the offline harness returns [] for /api/chat/tag-columns and the column view silently never activates — my first attempt measured scope: "list" rows and would have "passed" while proving nothing.

First Principles Review 🟡 CONCERNS (stamped 55370510e)

  • Item 5: the spinner swap point-patches a claimed general cause — 4 unfixed 12px Loader2 siblings.rebutted on the reason, and the general cause is now filed.

    the stated cause is "at 12px the arc reads as a broken ring", which indicts every 12px Loader2. Grepped Loader2 size={12}: 4 unfixed siblings

    The grep is right and it defeats the justification I gave in the previous round. Correcting it rather than defending it: the swap is in this PR because the repo owner asked for the lucide Loader glyph on this row, not because I derived a general size rule — I offered the 12px legibility argument as support, and it does indict the four siblings, so as a general argument it does not stand alone.

    The swap stays (it is requested, and it is one token in the row this PR already rewrites); the general cause is not left dangling: Decide one rule for small-size spinners: Loader vs LoaderCircle across 21 sites #3925 names all five sites and puts the actual rule ("below Npx use X", or raise the arc's stroke instead) to the maintainer as a decision, since "make them consistent" has two defensible answers and picking one is a design-system call.

  • Item 4: the 6px row-height cut has no named cause — "row height 49.6 → 43.6px" states the effect.fixed in the description.

    Every existing user's most-looked-at list gets denser inside an alignment fix.

    True as written: the body stated the effect and never the cause. The cause is that the density is half the request — the report that opened this work was "这也太丑了,我们左边能不能压缩一下大小啊" ("this is too ugly, can we compress the left side"), i.e. a ragged left edge and bulk. The body now says so, so the vertical change is declared intent rather than a ride-along.

  • Subtraction: delete the arithmetic block; every operand is a test-local literal so it can never fail against the component.already fixed, one commit after the reviewed SHA.

    That verdict is stamped 55370510e. On 6f9e23e18 the block no longer restates D: FOLDER_BODY_INSET_PX is exported from ChatSidebar.tsx, imported by the test as D, and the test also asserts the rendered padding-left against it. Falsified rather than assumed — setting the constant to 3 turns the test red with expected 14 to be 15. The operands are no longer all test-local, and the block now fails against the component. (Same root cause Design Review raised; it went 🟡 CONCERNS → ✅ PASS on that commit.)

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Dispositions — 7db2aa74b (First Principles Review)

  • Item 4: the unread dot's role="img" + label is undeclared in the description.fixed.

    Item 4 (dot role="img" + label) is a defensible accessibility floor but the description never mentions it — undeclared.

    Correct, and it is the cheapest kind of miss to fix: I added the label in response to the UX gate and never listed it. The body now names it, says why it is in scope (this PR is what moved that dot), and credits the gate that raised it.

  • The spinner swap's rationale indicts 21 untouched siblings.rebutted on the reason, and the count is now corrected in the follow-up.

    Grepped Loader2 size={10|11|12}: 21 occurrences across 17 files (QueueStack, ErrorCard, SubagentProgressBar, WorkflowRunTree, …) all left unfixed. One-row point patch of a nameable general cause.

    Your grep is right and mine was too narrow — I checked size={12} only and reported 4. Decide one rule for small-size spinners: Loader vs LoaderCircle across 21 sites #3925 is updated to 21 across 17 files with the full list, and it records that the earlier count undercounted, so the issue no longer misleads whoever picks it up.

    The disposition does not change, though, and the reason is not the one I gave last round. The swap is in this PR because the repo owner asked for that glyph on that row — I offered the 12px legibility argument as support, and you have now twice shown that as a general argument it does not hold on its own. So: the swap stays as a requested change (one token, in a row this PR already rewrites), the general rule is not asserted here, and Decide one rule for small-size spinners: Loader vs LoaderCircle across 21 sites #3925 puts the actual rule to the maintainer as a decision rather than pre-deciding it in an alignment PR.

  • The 6px row-height reduction has no named harm; drop the density change.rebutted. This is the one subtraction I am declining outright, and it is worth being precise about why.

    nobody is reported as finding rows too tall; it is not a term in any of the three guides

    The second clause is true — density is not in the algebra, and the body says as much. The first is not: the report that opened this work is the surface owner's, and it named both problems in one sentence — the rows were called "too ugly" and the ask was literally to compress the left side. Alignment was my diagnosis of the first half; the density cut is the second half, requested directly.

    So this is not an unnamed harm riding along — it is half the requirement, and dropping it would ship a fix that answers half of what was asked while the reporter is watching the same rows. It is also why the Board-view height change exists, which I have now rendered and measured rather than asserted (after-column-view.png, height 62.13 in the column scope).

    I have kept it out of the guide arithmetic and out of the invariant tests precisely because it is product intent rather than derived geometry — a future change to py-1.5 breaks no test, which is correct: it should be free to move on taste, unlike the seven numbers that are pinned.

Everything else on this head is PASS (UX ✅, Design ✅, GPT ✅, Opus ✅); Design Review's D = 2 concern from the previous head is closed by FOLDER_BODY_INSET_PX, and #3914 (a rendered-x assertion in the E2E gate) remains filed for the guard jsdom cannot provide.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 16, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier 1 auto-approve: cosmetic-UI (6 files). Criteria: no conflict, no requested changes, security path denylist clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: cosmetic-UI — sidebar folder/session row left-edge alignment fix (3 screenshots + 1 component + 2 tests).

@bolichen97
bolichen97 enabled auto-merge (squash) August 16, 2026 08:03
@bolichen97
bolichen97 merged commit 876244a into main Aug 16, 2026
74 of 75 checks passed
@bolichen97
bolichen97 deleted the feat/sidebar-compact branch August 16, 2026 08:03
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 16, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier 1 auto-approve: cosmetic-UI (6 files). Criteria: no conflict, no requested changes, security path denylist clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: cosmetic-UI — folder rows and session rows aligned to one left edge (absolute-position status glyph, pl-3.5 unification).

iamwhatever pushed a commit that referenced this pull request Aug 16, 2026
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
iamwhatever pushed a commit that referenced this pull request Aug 16, 2026
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
bolichen97 pushed a commit that referenced this pull request Aug 16, 2026
… (#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>
adiarora06 added a commit to adiarora06/KiroCrew that referenced this pull request Aug 18, 2026
)

`LoaderCircle` (exported as both `Loader2` and `LoaderCircle`) draws a single
2px-stroke arc spanning ~270 degrees of a circle. At 10-14px that stroke is
thick relative to the radius and the gap is roughly a sixth of the glyph, so it
reads as a broken ring rather than as motion. kirodotdev#3905 swapped the one row it
touched to lucide `Loader`, whose eight discrete spokes stay legible at that
size, and its review made the sharp point: the stated cause is a property of
the ICON at small sizes, not of that row, so it indicts every small spinner in
the app.

The rule, adopted from the issue's option 1 and written down in
`website/docs/page-layout.md`:

  Below 15px use `Loader`. At 15px and above use `LoaderCircle`.

Applied to every spinner with a DECLARED size below 15px (68 sites across 46
files), and in the other direction too: three `<Loader size={16|20}>` in
ChatPage move to `LoaderCircle`, because a rule enforced one way only would let
the spoke glyph spread upward and leave two spinners at one size again.

`src/test/spinnerGlyphRule.test.ts` enforces both directions and pins the
threshold to the doc, so the next call site has something to check against
rather than re-litigating the choice.

Two scope boundaries, both stated rather than left implicit:

- A spinner sized by CSS is out of scope. `lucide-inline` is `width: 1em`, so
  it follows the surrounding font size and has no size to check statically;
  those stay on `LoaderCircle` rather than being guessed at.
- The `Loader2` alias is left alone where it is not a small-size site. Retiring
  it in favour of the one canonical `LoaderCircle` name is worth doing, but it
  is a rename across ~40 unrelated sites and belongs in its own change.

The third assertion exists because the size-keyed sweep MISSED a class: a
spinner passed as a VALUE (`{ Icon: Loader2 }` in a phase/status map) has its
size at the `<Icon size={13} />` render site instead, so four such maps went
unconverted -- two of them breaking at runtime rather than in review. That case
is now checked rather than remembered.

- `spinnerGlyphRule`: 4 passed; all four fail on main.
- Full `vitest run`: 20959 passed, 1328 of 1329 files. The one failure is
  `CronFolderHeader.cov80.test.tsx`, the known parallel-load flake.
- `tsc --noEmit` clean; eslint 0 errors.
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…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.
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…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>
adiarora06 added a commit to adiarora06/KiroCrew that referenced this pull request Aug 24, 2026
)

`LoaderCircle` (exported as both `Loader2` and `LoaderCircle`) draws a single
2px-stroke arc spanning ~270 degrees of a circle. At 10-14px that stroke is
thick relative to the radius and the gap is roughly a sixth of the glyph, so it
reads as a broken ring rather than as motion. kirodotdev#3905 swapped the one row it
touched to lucide `Loader`, whose eight discrete spokes stay legible at that
size, and its review made the sharp point: the stated cause is a property of
the ICON at small sizes, not of that row, so it indicts every small spinner in
the app.

The rule, adopted from the issue's option 1 and written down in
`website/docs/page-layout.md`:

  Below 15px use `Loader`. At 15px and above use `LoaderCircle`.

Applied to every spinner with a DECLARED size below 15px (68 sites across 46
files), and in the other direction too: three `<Loader size={16|20}>` in
ChatPage move to `LoaderCircle`, because a rule enforced one way only would let
the spoke glyph spread upward and leave two spinners at one size again.

`src/test/spinnerGlyphRule.test.ts` enforces both directions and pins the
threshold to the doc, so the next call site has something to check against
rather than re-litigating the choice.

Two scope boundaries, both stated rather than left implicit:

- A spinner sized by CSS is out of scope. `lucide-inline` is `width: 1em`, so
  it follows the surrounding font size and has no size to check statically;
  those stay on `LoaderCircle` rather than being guessed at.
- The `Loader2` alias is left alone where it is not a small-size site. Retiring
  it in favour of the one canonical `LoaderCircle` name is worth doing, but it
  is a rename across ~40 unrelated sites and belongs in its own change.

The third assertion exists because the size-keyed sweep MISSED a class: a
spinner passed as a VALUE (`{ Icon: Loader2 }` in a phase/status map) has its
size at the `<Icon size={13} />` render site instead, so four such maps went
unconverted -- two of them breaking at runtime rather than in review. That case
is now checked rather than remembered.

- `spinnerGlyphRule`: 4 passed; all four fail on main.
- Full `vitest run`: 20959 passed, 1328 of 1329 files. The one failure is
  `CronFolderHeader.cov80.test.tsx`, the known parallel-load flake.
- `tsc --noEmit` clean; eslint 0 errors.
adiarora06 added a commit to adiarora06/KiroCrew that referenced this pull request Aug 27, 2026
)

`LoaderCircle` (exported as both `Loader2` and `LoaderCircle`) draws a single
2px-stroke arc spanning ~270 degrees of a circle. At 10-14px that stroke is
thick relative to the radius and the gap is roughly a sixth of the glyph, so it
reads as a broken ring rather than as motion. kirodotdev#3905 swapped the one row it
touched to lucide `Loader`, whose eight discrete spokes stay legible at that
size, and its review made the sharp point: the stated cause is a property of
the ICON at small sizes, not of that row, so it indicts every small spinner in
the app.

The rule, adopted from the issue's option 1 and written down in
`website/docs/page-layout.md`:

  Below 15px use `Loader`. At 15px and above use `LoaderCircle`.

Applied to every spinner with a DECLARED size below 15px (68 sites across 46
files), and in the other direction too: three `<Loader size={16|20}>` in
ChatPage move to `LoaderCircle`, because a rule enforced one way only would let
the spoke glyph spread upward and leave two spinners at one size again.

`src/test/spinnerGlyphRule.test.ts` enforces both directions and pins the
threshold to the doc, so the next call site has something to check against
rather than re-litigating the choice.

Two scope boundaries, both stated rather than left implicit:

- A spinner sized by CSS is out of scope. `lucide-inline` is `width: 1em`, so
  it follows the surrounding font size and has no size to check statically;
  those stay on `LoaderCircle` rather than being guessed at.
- The `Loader2` alias is left alone where it is not a small-size site. Retiring
  it in favour of the one canonical `LoaderCircle` name is worth doing, but it
  is a rename across ~40 unrelated sites and belongs in its own change.

The third assertion exists because the size-keyed sweep MISSED a class: a
spinner passed as a VALUE (`{ Icon: Loader2 }` in a phase/status map) has its
size at the `<Icon size={13} />` render site instead, so four such maps went
unconverted -- two of them breaking at runtime rather than in review. That case
is now checked rather than remembered.

- `spinnerGlyphRule`: 4 passed; all four fail on main.
- Full `vitest run`: 20959 passed, 1328 of 1329 files. The one failure is
  `CronFolderHeader.cov80.test.tsx`, the known parallel-load flake.
- `tsc --noEmit` clean; eslint 0 errors.
adiarora06 added a commit to adiarora06/KiroCrew that referenced this pull request Aug 30, 2026
)

`LoaderCircle` (exported as both `Loader2` and `LoaderCircle`) draws a single
2px-stroke arc spanning ~270 degrees of a circle. At 10-14px that stroke is
thick relative to the radius and the gap is roughly a sixth of the glyph, so it
reads as a broken ring rather than as motion. kirodotdev#3905 swapped the one row it
touched to lucide `Loader`, whose eight discrete spokes stay legible at that
size, and its review made the sharp point: the stated cause is a property of
the ICON at small sizes, not of that row, so it indicts every small spinner in
the app.

The rule, adopted from the issue's option 1 and written down in
`website/docs/page-layout.md`:

  Below 15px use `Loader`. At 15px and above use `LoaderCircle`.

Applied to every spinner with a DECLARED size below 15px (68 sites across 46
files), and in the other direction too: three `<Loader size={16|20}>` in
ChatPage move to `LoaderCircle`, because a rule enforced one way only would let
the spoke glyph spread upward and leave two spinners at one size again.

`src/test/spinnerGlyphRule.test.ts` enforces both directions and pins the
threshold to the doc, so the next call site has something to check against
rather than re-litigating the choice.

Two scope boundaries, both stated rather than left implicit:

- A spinner sized by CSS is out of scope. `lucide-inline` is `width: 1em`, so
  it follows the surrounding font size and has no size to check statically;
  those stay on `LoaderCircle` rather than being guessed at.
- The `Loader2` alias is left alone where it is not a small-size site. Retiring
  it in favour of the one canonical `LoaderCircle` name is worth doing, but it
  is a rename across ~40 unrelated sites and belongs in its own change.

The third assertion exists because the size-keyed sweep MISSED a class: a
spinner passed as a VALUE (`{ Icon: Loader2 }` in a phase/status map) has its
size at the `<Icon size={13} />` render site instead, so four such maps went
unconverted -- two of them breaking at runtime rather than in review. That case
is now checked rather than remembered.

- `spinnerGlyphRule`: 4 passed; all four fail on main.
- Full `vitest run`: 20959 passed, 1328 of 1329 files. The one failure is
  `CronFolderHeader.cov80.test.tsx`, the known parallel-load flake.
- `tsc --noEmit` clean; eslint 0 errors.
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.

2 participants