Repository navigation
feat(v2): a pod with no agent says so, and offers the one step - #1846
Conversation
lilyshen0722
left a comment
There was a problem hiding this comment.
UX-GATE: FAIL @ 6ae3bb0 — TASK-146, gated at 1440 + 390 on a production build of this head with the 1844 fixture plus an open team pod with zero agents (POST returns agentDelivery.agentsInPod 0). One blocker, in the CSS: the row inherits width: 100% from .v2-chat__messages > * (v2.css:1810) and keeps its own margin-left: 50px, so it is 100% + 50 wide and the transcript's overflow-x: hidden clips it. At 390 the full-width act renders 362 wide from x=64 to 426 on a 390 viewport (36 px of the button off-screen) and the body's line end is cut (…when they op); at 1440 the act is 99 px so nothing visible breaks, but scrollWidth is 830 in a 780 pane. Fix verified by style injection: .v2-chat__messages > .v2-chat__no-agents { width: calc(100% - 50px); } → 312 wide at 390 ending at the message text's right edge (376), 682 at 1440 ending at 1115, scrollWidth = clientWidth at both. Pin it with a width assertion in the invariants test (jsdom cannot see the overflow; the existing .v2-chat__delivery-hint has the same 100% + 50 clip — pre-existing, out of this diff, same one-line fix if you want to fold it in). Everything else passes: kicker Plex Mono 11 lowercase tertiary; body 12 secondary; act bordered white, ink, 12/600, radius 4, no shadow, min-height 32 at 1440 and 44 full-row at 390 (the min-height override holds); role=status; one row per pod per session (second send adds none; v2.noAgentsHint.pod-quiet=1 survives reload and suppresses); the act opens the invite modal with the Add agent tab selected; a pod with an active agent gets the delivery hint, not this row; no green anywhere. Taste, non-blocking: the slot's 50 px margin lands 14 px right of the message text column at both widths (row x 433 vs text 419 at 1440; 64 vs 50 at 390) — same as the delivery hint today; a slot-level fix later, not this PR.
37f374c to
09358ff
Compare
|
Fix for the UX-GATE FAIL at Exactly as ruled: The precedent is in this same file: Pinned in Flagged, not fixed: the pre-existing Re-gate requested at |
lilyshen0722
left a comment
There was a problem hiding this comment.
UX-GATE: PASS @ 09358ff — re-gate on a fresh production build of this head (index-DgYSS794) with the same zero-agent fixture pod, 1440 + 390. Delta over my scope since 6ae3bb0 is exactly the ruled fix: width: calc(100% - 50px) on .v2-chat__no-agents (patch-id over components + v2.css + locales b4d963d1 at merge-base 0c4f950; the other two files are tests, one of them backend from the rebase). Measured: 390 row 64→376 = the message text's right edge, act 312×44 fully on-screen, body line whole, transcript scrollWidth 390 = clientWidth; 1440 row 433→1115 = text right, act 99×32, scrollWidth 780 = clientWidth. Kicker 11 mono, act white/ink/radius 4/no shadow, one row per session, all unchanged from the first pass. The .v2-thread-block precedent cited in the comment is the right one. Delivery hint's identical 50 px indent without a width stays a follow-up, not this PR.
lilyshen0722
left a comment
There was a problem hiding this comment.
GATE: PASS at 09358ff0 — measured. Two gaps, both non-blocking: the code is right, the safety clears are unpinned.
BASE 124/124 (V2ThreadDeliveryHint + v2-layout-invariants), RESTORED 124/124, tree clean after every mutation. Anchor count 1 on each; the two ambiguous anchors were line-scoped.
What is pinned:
mutation on V2Thread.tsx |
result |
|---|---|
M1 delivery.agentsInPod === 0 → >= 0 |
1 red |
M2 drop the !isAgentDm exclusion |
1 red |
| M3 remove the once-per-pod guard (line 1369) | 1 red |
M5 noAgentsHint={noAgentsInPodLive ? … : null} → unconditional |
2 red |
Separating the two sessionStorage keys is the right call, and an agent joining does not suppress the mention hint in the same session is the test that makes it more than a comment.
Gap 1 — the typing clear (V2Thread.tsx:1149) is unpinned, and it is the #914 case
Removing setNoAgentsHint(null) from the agent_typing_start handler leaves 124/124 green. The existing #914 test covers only agentDeliveryHint; nothing covers the new row.
This is not an equivalent mutant — noAgentsInPodLive does not mask it, because a typing agent need not yet be in the agents prop. Measured with a probe (passes at head, fails under the mutation): render with agents: [] and agentsInPod: 0, send, then fire agent_typing_start. At head the row goes; with the line removed, no agent in this pod sits above the landing reply — exactly the defect your own comment on line 1147 says must not happen.
act(() => { handlers.agent_typing_start({ podId: 'pod-1', agentName: 'guide', displayName: 'Guide' }); });
await waitFor(() => expect(screen.queryByText('no agent in this pod')).not.toBeInTheDocument());Gap 2 — the pod-switch clear (V2Thread.tsx:548) is unpinned
Same result: removing it leaves 124/124 green. Also reachable, and the effect is keyed on [pod?._id], so the component survives a pod switch and the state really does persist.
Probe (passes at head, fails under the mutation): send in pod-1 and earn the row, rerender into pod-2 with its own transcript, then return to pod-1. With the line removed the stale row reappears on return, which the once-per-pod intent forbids. The harness in the existing suite hardcodes pod-1, so this one needs a podId-parameterised harness.
Neither blocks the merge. Both are one test each, and both defend a sentence you have already written as a comment.
9c58e74 to
1ad20eb
Compare
lilyshen0722
left a comment
There was a problem hiding this comment.
Both gaps closed. PASS stands at 1ad20eb9.
- Production code is untouched since the head I gated.
V2Thread.tsx,V2ThreadMessages.tsx,v2.cssanden.jsonare byte-identical to09358ff0. Thecli/files in a raw two-head diff are main's #1840 arriving, not this PR — the PR-only delta against the new merge-base is the one test file, as you said. So the earlier mutation results carry, and ux-lead's stamp has nothing to re-measure. - BASE 126/126, RESTORED 126/126, tree clean.
- Removing the pod-switch clear (
:548): 1 red. Removing the typing clear (:1149): 1 red. Each reds exactly its own new test and nothing else, so the two are independent rather than one test covering both paths.
Keeping agents: [] in the typing test is the detail that matters — it is what stops noAgentsInPodLive masking the assertion, which is the whole reason the gap survived the first suite. Nothing further from me.
Lily's smoke: created a team pod with no agent, posted once, got nothing,
created a second pod with an agent. The existing delivery hint ("No agent was
notified — @mention one") is gated on agentsInPod > 0 because it needs a
handle to suggest, so this exact pod is the one it skips — and the starter
panel's "Add an agent" act is already gone once a message exists.
When a send returns agentsInPod === 0 in a non-DM pod, a system row renders in
the same slot under the sent message: mono-11 kicker "no agent in this pod",
"Nobody here answers yet — people will see this when they open the pod.", and
one bordered act, Add an agent (32 desktop / full-width 44 at <=760) opening
the starter panel's own sheet on its agent tab.
Once per pod per browser session on its own key, so it and the mention hint
cannot cancel each other — both can happen in one pod in one session. It goes
when an agent joins, not on the next send: gated on the live pod detail.
Excluded deliberately: DM pods (an agent by construction; their empty states
name who is missing) and pods with no invite surface (the row is explanation
plus the one step, so half of it is worse than none).
The row's 50px indent has to come out of its width: the column's `> *` rule
sets width: 100%, so without `width: calc(100% - 50px)` it overruns the pane by
exactly 50px (ux-lead's 390 gate: the act ran 64->426 and the body line was
cut). Precedent in the same file — `.v2-thread-block` carries the identical
pair with Sam's 2026-08-24 note that indent + width must sum to 100%.
Tests: V2ThreadDeliveryHint 15/15 (7 new), 169/169 across 17 thread/shell
suites, layout invariants 109/109 (1 new). Nine mutations, each red on the
expected test — including the phone override as `height` instead of
`min-height` (would lose to the base 32 minimum, TASK-140) and removing the
width, which reproduces ux-lead's 390 overflow as a guard failure.
sprint-review's gate on 09358ff passed but measured two silent-deletion paths, one test each: - removing `setNoAgentsHint(null)` from the typing handler left all 15 green. This is the #914 shape: the row must not sit above a reply that is about to land, and a typing agent need not be in the pod detail's `agents` prop yet, so the live guard cannot mask it — the probe shows the row staying put. The new test keeps `agents: []` for exactly that reason, so only the clear can pass it. - removing the clear from the pod-change effect left all green too: leave the pod and come back, and the row is resurrected under a message the user did not just send, with pod-1's session key already spent so no send could re-earn it. Both are behaviour already implemented and documented in comments; neither was defended. Each mutation reds exactly one test — the new one — so neither is an equivalent mutant. V2ThreadDeliveryHint 17/17.
1ad20eb to
2278186
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.
…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.
…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.
…ASK-150) (Team-Commonly#1860) * fix(chat): one shared slot rule for the transcript's two side-rows (TASK-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 (Team-Commonly#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. * fix(chat): the side-row slot is 36px — the thread's grid, not the base message grid (TASK-150) ux-lead's Team-Commonly#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).
Closes TASK-146, built to ux-lead's draw on the row.
The gap
podChat.deliveryHint— "No agent was notified — @mention one to get a reply" — is gated ondelivery.agentsInPod > 0(V2Thread.tsx:1317), because it needs a handle to suggest. So the pod with zero agents is the one case it skips, and it is the case where the user most needs the sentence: they posted, nothing answered, and the starter panel's "Add an agent" act is already gone (starterPanelVisiblerequiresmessages.length === 0). Lily saw exactly this in a real signup — pod created, one post, silence, then a second pod.What it does
When a send returns
agentDelivery.agentsInPod === 0in a non-DM pod, a system row renders in the same slot, directly under the sent message:no agent in this podmin-height: 32pxon desktop, full width andmin-height: 44pxat ≤760 — opening the same sheet the starter panel opens, on its agent tab (onOpenInvite('agent')), where Scout is first and connect-your-own second.Ink and tint only: no accent, no toast, and it is not a
role="alert".Once per pod per browser session, like the delivery hint — but on its own key (
v2.noAgentsHint.<podId>, distinct fromv2.agentDeliveryHint.<podId>), because both rows can legitimately occur in the same pod in one session: the no-agent row before an agent is added, the mention hint after. Sharing a key would let either cancel the other.It goes when an agent joins, not on the next send: the row is gated on the live pod detail (
agents.some(status === 'active'), the client's counterpart of the server'sAgentInstallation.countDocuments({ podId, status: 'active' })), so the pod-detail re-read removes it.Deliberate exclusions (stated, not incidental)
agent-room,agent-dm): both have an agent by construction, and their empty states already name who is missing.onOpenInviteundefined, i.e. a pod type the shell cannot invite into): the row is explanation plus the one step. Without the step it would be a sentence pointing at nothing the user can do, so the row stays hidden rather than half-rendering.Proof
V2ThreadDeliveryHint15/15 (7 new), and 169/169 across 17 suites that mount the thread or the shell,v2-layout-invariants109/109 (1 new).height: 44pxinstead ofmin-heightThe last one is the TASK-140 lesson: a
heightoverride loses to the base rule's ownmin-height: 32pxand renders 32 on a phone. The guard assertsmin-height: 44pxandnot /[^-]height: 44px/, and it reads the ≤760 block through a newmediaBlockContaininghelper —ruleBodyprefers a line-start selector, so it would have pinned the desktop rule and gone green on a broken phone layout.Limit: the jsdom tests cannot see the rendered 390 layout — the mono kicker, the row's alignment under the message, and the 44px act are ux-lead's visual gate at 1440 + 390. The row also persists only until reload/session end (it is a once-per-session nudge like the hint beside it), and a pod whose detail has not yet loaded reads as empty, so the guard uses the loaded list rather than a server count.