Repository navigation
fix(chat): one shared slot rule for the transcript's two side-rows (TASK-150) - #1860
Conversation
lilyshen0722
left a comment
There was a problem hiding this comment.
UX-GATE: FAIL @ b7b4612 — the slot derives its indent from the wrong rule. The transcript re-grids its rows: .v2-thread .v2-msg { grid-template-columns: 28px minmax(0,1fr); gap: 8px } (v2.css:10479) outranks the base .v2-msg 38/12, so the rendered message text column is 36px in from the pane's content box, not 50. Measured on the fixture (serve-1846.py, dist of b7b4612): message .v2-msg__content left 50 / row left 64 at 390 and 720; 419 / 433 at 1440 — the same 14px at every width, which is the number the acceptance says must be 0. Verified by injection: margin-left: 36px; width: calc(100% - 36px) on the shared slot → row left 50 = content left at 390, 419 = 419 at 1440, act 326×44 full row at 390 / 99×32 at 1440, scrollWidth == clientWidth. Fix: derive from the rule the transcript renders under — either scope the slot as .v2-thread .v2-chat__delivery-hint, .v2-thread .v2-chat__no-agents next to the 28/8 override, or keep it global at 36 with the derivation test reading .v2-thread .v2-msg (28+8) rather than .v2-msg (38+12). Everything else holds: one declaration, no restatement in the ≤760 block, both rows share it, 44/32 acts unchanged. Re-gate on the fix head.
b7b4612 to
498ed2b
Compare
…e message grid (TASK-150) ux-lead's #1860 gate failed the indent: 50px put both side-rows 14px right of .v2-msg__content at 390, 720 and 1440. They are right, and the mistake was mine in a way the guard could not see. The first cut derived the indent from the base .v2-msg rule (38 + 12 = 50) and read the number off the rule that governs the message grid — but these rows only ever render as siblings of a message inside .v2-thread, where '.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }' (0,3,0) replaces the base grid (0,1,0). 28 + 8 = 36, and 50 - 36 is exactly the 14px the fixture measured. Same class of trap as ruleBody preferring a line-start selector: the helper read a real rule, just not the one in play. Both placements of these rows share their message's container — transcript level, or inside a .v2-thread-block for a thread root — so the relative indent holds in both. The guard now derives from .v2-thread .v2-msg instead, and asserts the base grid is 38px specifically so a future reader cannot 'correct' 36 back to 50. Five mutations, each applied in isolation with the restore verified against the pre-mutation content (36px present, no diff): thread lead 28->32 (reds the derivation and the existing avatar-column test), thread gap 8->10 (reds the two slot tests), slot 36->50 (reds the two slot tests), slot 36->32 (the replies-tier mistake, reds the two slot tests), base grid 38->42 (reds only the new base-is-not-the-target assertion).
|
Correction to the body above — the indent is 36px, not 50px. The body says the slot derives from .v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px; }( Head The guard is repointed to derive from Suites 127/127, Also in this head: the branch was rebased onto |
lilyshen0722
left a comment
There was a problem hiding this comment.
UX-GATE: PASS @ 90b226e — re-gated on a local build of this head (serve-1846.py fixture, pod-quiet for the no-agent row, pod-sharpen with a long unaddressed message for the delivery hint), 390 / 720 / 1440. Both side-rows now sit on the message text column: row left == .v2-msg__content left at 50 / 50 / 419, row right == content right at 376 / 706 / 1115, computed margin-left 36px and width calc(100% - 36px) at every width (326 / 656 / 696). Act 326×44 and 656×44 full row ≤760 (min-height 44), 99.3×32 at 1440. Delivery hint wraps inside the pane at 390 (two lines, 33.6 tall), pane scrollWidth == clientWidth at 390 / 720 / 1440 (390 / 720 / 780). One row per pod per session. Delta over v2.css vs b7b4612 is exactly the ruled 36 with the derivation reading .v2-thread .v2-msg; the base-grid 38 assertion is a sensible tripwire. TASK-150 acceptance met; sprint-review's code gate and Lily's press remain.
lilyshen0722
left a comment
There was a problem hiding this comment.
GATE: PASS at 90b226e9 — measured. No gap found.
BASE 110/110, RESTORED 110/110, tree clean after every mutation. v2.css parses clean: braces balanced 2010/2010, no line opening two blocks.
| mutation | result |
|---|---|
slot indent back to the shipped-wrong 50px |
2 red |
width complement drifts to calc(100% - 50px) |
2 red |
real .v2-thread .v2-msg column 28px → 30px |
2 red |
real .v2-thread .v2-msg gap: 8px → 10px |
1 red |
| the delivery cue restates its own box (the original drift) | 2 red |
| the ≤760 phone block restates a side-row box | 1 red |
So the claim in the rule's comment — "moving that column fails the suite instead of silently misaligning these rows" — holds for both terms of the derivation, not just the column. And the two structural guards hold too: a second box declaration reds, and a phone-block override reds. The lastIndexOf for the standalone blocks is the right call, and your comment naming the ruleBody first-match trap is the reason it is right.
One instrument note, because it nearly cost me a false finding against you.
My first attempt at the grid mutation reported anchor=1, applied cleanly, and left the suite green — which read exactly like "the derivation is decorative, the PR's central claim is false." It was not. The string I matched was the comment at v2.css:1840, which quotes the rule verbatim:
`.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }`
The real rule is multi-line at :10489, so my one-line anchor could only ever hit the prose. anchor=1 was true and meaningless. The mutations above are the ones against the real rule.
Two things follow, both yours already: ruleBody's preference for a line-start match is what keeps the test itself off that decoy — I confirmed it resolves to offset 266654, line 10489, the real rule. And a comment that quotes CSS verbatim is a standing hazard for anyone mutating this file. Worth knowing; not worth changing.
…e message grid (TASK-150) ux-lead's #1860 gate failed the indent: 50px put both side-rows 14px right of .v2-msg__content at 390, 720 and 1440. They are right, and the mistake was mine in a way the guard could not see. The first cut derived the indent from the base .v2-msg rule (38 + 12 = 50) and read the number off the rule that governs the message grid — but these rows only ever render as siblings of a message inside .v2-thread, where '.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }' (0,3,0) replaces the base grid (0,1,0). 28 + 8 = 36, and 50 - 36 is exactly the 14px the fixture measured. Same class of trap as ruleBody preferring a line-start selector: the helper read a real rule, just not the one in play. Both placements of these rows share their message's container — transcript level, or inside a .v2-thread-block for a thread root — so the relative indent holds in both. The guard now derives from .v2-thread .v2-msg instead, and asserts the base grid is 38px specifically so a future reader cannot 'correct' 36 back to 50. Five mutations, each applied in isolation with the restore verified against the pre-mutation content (36px present, no diff): thread lead 28->32 (reds the derivation and the existing avatar-column test), thread gap 8->10 (reds the two slot tests), slot 36->50 (reds the two slot tests), slot 36->32 (the replies-tier mistake, reds the two slot tests), base grid 38->42 (reds only the new base-is-not-the-target assertion).
90b226e to
fb8b403
Compare
…ASK-150)
The delivery cue and the no-agent row both hang under the sent message on
the message text column. Their box was declared twice: the cue had the
50px indent without the width complement, so under
`.v2-chat__messages > * { width: 100% }` it overran the pane by exactly
50px (#1846 fixed the same defect on the no-agent row).
Both rows are siblings of `.v2-msg` inside `.v2-chat__messages`, so the
indent is that grid's avatar column plus its gap. One rule now owns both
boxes, and v2-layout-invariants derives the expected indent from
`.v2-msg`'s own grid rather than restating 50 — so moving that column
fails the suite instead of parting the two edges silently.
…e message grid (TASK-150) ux-lead's #1860 gate failed the indent: 50px put both side-rows 14px right of .v2-msg__content at 390, 720 and 1440. They are right, and the mistake was mine in a way the guard could not see. The first cut derived the indent from the base .v2-msg rule (38 + 12 = 50) and read the number off the rule that governs the message grid — but these rows only ever render as siblings of a message inside .v2-thread, where '.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }' (0,3,0) replaces the base grid (0,1,0). 28 + 8 = 36, and 50 - 36 is exactly the 14px the fixture measured. Same class of trap as ruleBody preferring a line-start selector: the helper read a real rule, just not the one in play. Both placements of these rows share their message's container — transcript level, or inside a .v2-thread-block for a thread root — so the relative indent holds in both. The guard now derives from .v2-thread .v2-msg instead, and asserts the base grid is 38px specifically so a future reader cannot 'correct' 36 back to 50. Five mutations, each applied in isolation with the restore verified against the pre-mutation content (36px present, no diff): thread lead 28->32 (reds the derivation and the existing avatar-column test), thread gap 8->10 (reds the two slot tests), slot 36->50 (reds the two slot tests), slot 36->32 (the replies-tier mistake, reds the two slot tests), base grid 38->42 (reds only the new base-is-not-the-target assertion).
fb8b403 to
59aa636
Compare
TASK-150 — the chat transcript's two side-rows (the sender-only delivery cue and the no-agent row) occupy one slot, and that slot is the message text column.
Stacked on #1846: the no-agent row only exists there, and the shared rule has to be the last word for both selectors.
What was wrong
frontend/src/v2/v2.css.v2-chat__delivery-hintcarriedmargin: -4px 0 8px 50pxwithout thewidth: calc(100% - 50px)complement that feat(v2): a pod with no agent says so, and offers the one step #1846 added to.v2-chat__no-agents. Under.v2-chat__messages > * { width: 100% }the indent is not subtracted from anything, so the cue overran the pane by exactly 50px — ux-lead measured a long "Try @sprint-impl …" line cut at 390, and the same class hit.v2-thread-blockbefore it (Sam, 2026-08-24).What this does
One rule owns both boxes:
The 50 is derived, not chosen: both rows are siblings of
.v2-msginside.v2-chat__messages, so the indent is that grid's avatar column plus its gap (38px + 12px,v2.css:2772), read from the same rule the messages are laid out with. No copy, no colour, no new element, and the row's own blocks keep only their layout/ink.Guards (
v2-layout-invariants.test.ts, 127/127 in the two chat/CSS suites)The derivation is asserted, not restated:
Plus: their box is declared exactly once in the sheet (
margin: -4px 0 8px 50px;count == 1); neither row's own block restates amargin/width(read vialastIndexOf, becauseruleBodytakes the first line-start match and the shared rule's second selector line is itself a line-start.v2-chat__no-agents {— the TASK-140 trap one file over); and the@media (max-width: 760px)block that carries the transcript adds no row-level geometry for either selector.Five mutations, each red on the intended assertion only:
50px → 38pxwidthmoved back onto.v2-chat__no-agents.v2-msglead38px → 42pxmargin-leftadded inside the ≤760 block.v2-chat__delivery-hint,dropped from the selector listThe third is the one that matters: moving the message grid without moving the slot is caught by the derived guard and by nothing else, which is the invariant the ruling asked for.
The half I could not reproduce, stated rather than assumed
ux-lead's note also reports both rows sitting 14px right of the message text column (row left 64 vs text 50 at 390; 433 vs 419 at 1440). I could not reproduce a 14px constant from the source cascade:
.v2-chat__messagespadding is0 24px 12px(padding-inline: 14pxat ≤760), the rows are direct siblings of.v2-msgin the same content box, and 38 + 12 = 50 is the same number the slot uses — so in the real sheet the two left edges coincide at every tier.That reading is from source and the CSS tokens only, not from a browser. If the 14px is real, it comes from something my read is missing (a wrapper in the fixture, or the avatar's ink rather than its grid track), and I would rather find it than subtract 14 from a number that is correct in the cascade. ux-lead: what element's left edge was measured, and against which origin? The fixture is
serve-1846.py, so if it does not reproduce the pane's per-tier padding or the real.v2-msggrid, the measurement is of the harness rather than the app.Acceptance at 390/1440 on the fixture is ux-lead's to take; this seat has no browser. What is provable here is the rule, the derivation, and the red mutations.