fix(workspace): the sidebar date is flush right; an empty availability slot costs nothing (#2641) - #2646
Conversation
…y slot costs nothing (#2641) The row held a 72px availability slot after the date column, on every row, unconditionally. `availabilityChip()` returns null for every state except `stopped` and `unavailable`, so on a fleet where everything is running — the normal case — that strip rendered EMPTY on every row. Both halves of the reported defect follow from that one fact: the dates stopped 72px short of the row's right edge, and 72px per row was charged to the only element that wanted it, so names truncated (`Chief ...`, `Marke ...`) beside a blank strip. The reservation itself is worth keeping — it stops a row reflowing when an agent starts or stops between refreshes (#2196) — so it becomes a property of the LIST rather than of a row: `reservesAvailabilitySlot(rows)` reserves on every row iff any VISIBLE row can actually show a chip. Three details that are load-bearing rather than incidental: * Computed over the rows actually RENDERED, not the whole roster. A stopped agent hidden by search or by the collapse limit would otherwise reserve width on a list that shows no chip — the reported bug with extra steps. * The whole `<span>` goes, not just its width. A zero-width flex child still sits between the date and the row edge, and the row's `gap-2.5` would keep paying 10px for it, so the date still would not be flush. * The predicate derives from `availabilityChip` rather than re-listing the two states, so it cannot become a second copy of "which states get a chip" — a state neither has heard of yet reserves nothing, pinned by test. #2580 is untouched: the date column stays `w-14 text-right tabular-nums` and still renders on every row with its `v-if` INSIDE it, so the name's truncation point is identical down the list. Uniform reservation is what preserves that when a chip does appear — a per-row reservation would give a stopped row a different name width from its neighbours. Accepted cost, stated rather than discovered later: the 0→1 transition (the first agent in view stops) reflows the list once, where before it reflowed nothing. Within a populated list nothing moves — a second agent stopping, or one restarting while another is still stopped, changes only that row's chip. That is the trade the issue delegates to the implementer. Frontend suite: 112 files, 2516 tests, all passing; both ratchets green; vite build clean. Related to #2641 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bd71qsYbFodvofba8P69eP
|
merge-train 2026-09-09: merging. No criticals from either One mechanical fix was applied by the train: the body said Four things found that did not block, left for you rather than patched: 1. A background poll can still reflow the roster. 2. #2641's AC4 was narrowed, not met. The issue says "an agent going stopped or unavailable between refreshes must not reflow the rows around it", unconditionally; the body rewrites it to "within a populated list" and ticks it. Disclosed rather than hidden, but it wants the issue author's acceptance. The delegation cited is on AC2, not AC4. 3. One test is vacuous — proven, not suspected. 4. A pre-existing guard now over-claims. AC7 (rendered geometry) is unasserted at every layer. The "no component-mount harness" note is accurate — |
vybe
left a comment
There was a problem hiding this comment.
merge-train 2026-09-09: lane B, validated with /validate-pr + /review. No criticals; both ratchets re-scanned against the head blobs and match baseline exactly. Non-blocking findings recorded in the comment above.
The sidebar row held a 72px availability slot after the date column, on every row, unconditionally.
availabilityChip()returns null for every state exceptstoppedandunavailable, so on a fleet where everything is running — the normal case — that strip rendered empty on every row.Both halves of the reported defect follow from that one fact: the dates stopped 72px short of the row's right edge, and 72px per row was charged to the only element that wanted it, so names truncated (
Chief ...,Marke ...) beside a blank strip.The fix
The reservation is worth keeping — it stops a row reflowing when an agent starts or stops between refreshes (#2196). So it becomes a property of the list, not of a row:
Reserve on every row iff any visible row can actually show a chip. Absent entirely otherwise, which puts the date flush against the row's edge.
Three details that are load-bearing rather than incidental:
<span>goes, not just its width. A zero-width flex child still sits between the date and the row edge, and the row'sgap-2.5would keep paying 10px for it, so the date still would not be flush. The guard is av-if, never a conditional class — pinned by test.availabilityChiprather than re-listing the two states, so it cannot drift into a second copy of "which states get a chip". A state neither function has heard of yet reserves nothing.Acceptance criteria
flex-1name block at every sidebar width.w-14 text-right tabular-nums, rendered on every row with thev-ifINSIDE it. Uniform reservation is precisely what preserves that when a chip does appear; a per-row reservation would give a stopped row a different name width from its neighbours.The one cost, stated rather than discovered later
The 0→1 transition — the first agent in view stopping — reflows the list once, where before it reflowed nothing. That is the honest price of not charging every row for the empty case, and it is the trade the issue explicitly delegates ("implementer's choice, but the empty case must not be charged to the name"). The alternative the issue also names — putting the date last — makes it flush unconditionally but re-introduces the varying truncation point #2580 fixed, on every row that carries a chip. This keeps both properties everywhere except that single transition.
It is written into
workspace-sidebar-ia.mdbeside the reservation's original rationale, not left in a commit message.Verification
src/frontend/tests/unit/portalSidebarDateFlushRight.spec.js— 13 passed. The predicate as a pure table (all-running reserves nothing; one stopped reserves for all;unavailablecounts too; derived-not-re-listed, including unknown/empty/null states; thedetailedflag changes wording, never reservation; malformed input reserves nothing), plus source assertions for what only source can answer: thev-if(not a class), theshownAgentsdependency, the ordering, and bug(workspace): activity chart re-renders on every chat-tab switch; agent-list dates not right-aligned; copy/like/dislike misaligned; replies not rateable until reload #2580's column.rawColorRatchet+loadingGateRatchet: 14 passed (this change adds no colour classes at all).vite buildclean.No component-mount harness exists in this project (
package.jsoncarries no @vue/test-utils, jsdom or happy-dom; vitest runsenvironment: 'node'), which is why the decidable half lives inportalUtils.jsand is genuinely executed, and the source-regex assertions are scoped to the wiring — the same splitportalAvailabilityChip.spec.jsdocuments.Out of scope
useColumnResize.js'sSIDEBAR_MIN = 200— the issue names it as a side note. Freeing the 72px raises the effective floor for the name at every width without touching it.Fixes #2641
🤖 Generated with Claude Code
https://claude.ai/code/session_01Bd71qsYbFodvofba8P69eP