Skip to content

fix(members): show the roster patrol badge only while a loop is active - #9422

Open
CrysisDeu wants to merge 1 commit into
mainfrom
fix/members-patrol-badge-active-only
Open

fix(members): show the roster patrol badge only while a loop is active#9422
CrysisDeu wants to merge 1 commit into
mainfrom
fix/members-patrol-badge-active-only

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Closes #9420. Follow-up to #8936 (which added the badge for the #8931 incident).

Why

Since #8936 the roster put a badge on a member's avatar whenever an auto-patrol record existed: accent goal glyph while active, warn-yellow pause glyph once stopped. Stopped is the resting state of most members — every cycle-capped, budget-expired or hand-stopped loop leaves a record — so most idle avatars wore a permanent yellow mark that read as "this member is broken". PM review of the shipped page: 做成如果有自动巡检才加一个标,不然有一个这个标很 confusing ("only add a mark when there IS an auto patrol; otherwise the mark is confusing").

Stopped is not an incident, and the roster already has the idiom for this: the presence dot (#6859) and the unread dot render only while there is something to say. The stop reason has a home — the drawer's Auto patrol block — and it stays there.

What

  • website/src/pages/members/MembersPage.tsx — roster badge is two-state. patrolBadgeOf (three-way verdict) becomes activePatrolOf, returning the loop record only while active; the render drops the stopped branch, the bg-warn fill, the Pause glyph and the colour-flip transition. Tooltip / aria-label keep the drawer's spelling ("On patrol · 3 of 24" / "61 · no limit"). data-state="active" is kept on the element so the harness and tests still address the badge by state.
  • Transition (ci(ux-review): blind-read pass + state-transition continuity lens #8718 lens): mount/unmount stays animated through the existing AnimatePresence — when a loop stops the badge fades out rather than vanishing, and no intermediate yellow state appears. New useReducedMotion() read: under prefers-reduced-motion the tween is skipped (initial: false, duration: 0) and the badge cuts straight to its new state.
  • Drawer block unchanged — active / stopped with reason + last wake / nothing scheduled, plus loading and failed states. Wake sources unchanged. The needs_you pill (feat(members): chat-profile projection and escalation cards in the DM thread #8614), presence dot and unread dot keep their positions; the badge's corner is unchanged.
  • i18n: pages.membersPage.patrol_badge_stopped removed from all 13 catalogs (line-anchored delete, no re-sort) and en-XA regenerated (i18n:pseudo, 13016 keys). Zero remaining references. lint:i18n and i18n:check green.
  • docs/feature-map/README.md — Crew Members row now says the badge shows only while a loop is active.
  • Scope: only the badge block and its verdict helper are touched — the drag-handle / surface-parity work (chat-703) and the React Query migration (chat-705) in the same file are not overlapped; base is current origin/main.

Tests

MembersPage.test.tsx:

  • the mixed roster case (radar active + running, scout stopped at cap, scribe never armed) now asserts exactly one badge, on radar, data-state="active", aria-label 3 of 24, beside the single presence dot, and no element titled "Patrol stopped".
  • new: a loop that stops in place (registry re-read flips active to false, what the websocket hook triggers) drops the badge after the exit tween with no "stopped" badge in between.
  • the drawer's stopped-with-reason case is untouched and still pins the reason line.

Local: tsc -b, eslint on both files, lint:i18n, i18n:check green. vitest left to CI per the lane rules.

Screenshot Evidence

Capture harness website/scripts/capture-members-patrol.mjs (real built SPA over stubDashboardApi, no gateway), updated to assert exactly one badge per active loop and none otherwise before every frame — a frame can only be written from the state it claims. Five-member roster: radar (active 3/24, running) and ledger (active, unlimited) beside scout (stopped at wake limit), scribe (stopped, approval stalled) and fixer (never armed).

Active and stopped members side by side, dark — radar and ledger carry the accent badge; scout / scribe / fixer show nothing. radar's drawer open:

active dark

Same roster, light:

active light

scout's drawer: the stop reason ("Patrol stopped. Reached its wake limit. Last wake 4h ago") lives here — and scout's avatar in the roster has no badge:

stopped dark

scribe stopped on a stalled approval — same: reason in the drawer, no roster mark:

stalled dark

fixer, nothing scheduled:

none dark

Unlimited cap tooltip spelling preserved on ledger:

unlimited dark

Registry read in flight / failed (unchanged behaviour, re-captured on this head):

loading

error

Transition recording — fixer's badge fades in when a loop arms, then fades out when the loop stops (real autonudge_state frames pushed over the page's own /api/ws); there is no yellow intermediate state:

Drawer block none → active → stopped (unchanged, re-recorded):

Pattern harvest

Rule candidate: design-lane / AUTOSDE — a per-item status mark on an avatar or row (dot, badge, pill) renders only while its positive state holds; a standing placeholder for the "off" state is a finding. Pattern: condition ? <on-mark/> : <off-mark/> where the off branch renders a persistent glyph.

  • Appearance is the signal. A roster marker for a per-member state should render only while the state is on; a standing "off" placeholder competes with the on marker and reads as an error. Third instance on this page (fix(members): render the presence dot only while a member is working #6859 presence dot, unread dot, now patrol). Candidate for a design-lane rule: an avatar/row status mark needs a positive state to exist.
  • Detail keeps the reason, roster keeps the presence. When a state has a reason string (stopped_reason), the roster shows presence only and the drawer shows the reason — one place for each, not both.
  • Removing a state still needs the transition. Dropping a rendered state is itself a state change the user sees (badge disappears); keep the exit tween and add the reduced-motion cut rather than deleting the animation along with the branch.

Since #8936 a member's avatar carried a badge whenever an auto-patrol
record existed for its thread: accent while active, warn-yellow with a
pause glyph once the loop had stopped. Stopped is the resting state of
most members (every cycle-capped or hand-stopped loop leaves a record),
so most idle avatars wore a permanent yellow mark that read as "this
member is broken" rather than "nothing is scheduled".

The roster badge is now two-state: the accent goal glyph with its
"On patrol · 3 of 24" tooltip while the loop is ACTIVE, and no badge at
all otherwise — a stopped loop and a never-armed member look the same
at the roster, the way the presence dot (#6859) and unread dot already
work: appearance is the signal. The drawer's Auto patrol block keeps
its full three verdicts (active / stopped with reason and last wake /
none); the stop reason lives there.

Fade-out on stop stays through AnimatePresence (no hard vanish, #8718);
`useReducedMotion` cuts the tween under prefers-reduced-motion. The
`patrol_badge_stopped` key is removed from all 13 catalogs and en-XA
is regenerated. Tests pin one badge on a mixed active/stopped/never
roster and no intermediate "stopped" badge when a loop stops in place.
The capture harness asserts exactly one badge per active loop.

Closes #9420
@CrysisDeu
CrysisDeu requested a review from a team September 8, 2026 08:48
@CrysisDeu
CrysisDeu requested a review from a team as a code owner September 8, 2026 08:48
@CrysisDeu
CrysisDeu requested a review from smeyffret September 8, 2026 08:48
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Design-level review of b9950c291da1b10aa7b35c23b55a6a9343b006cf — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: CONCERNS

Removing the stopped badge for ALL stop reasons quietly reverses the #8931/#8936 incident-visibility decision for stalled patrols, not just the noisy resting-state cases.

Watch

  • The deleted test pinned "the dead patrol must show at the roster, not only in the drawer" — the reason feat(members): show each member's auto-patrol (monitor loop) status #8936 existed. The quoted PM complaint targets the permanent warn mark on the common stops (cycle cap, budget, manual), but this PR also hides approval_stalled — a loop dead because a human never answered, i.e. exactly the unnoticed-dead-patrol class [insider regression] Crew Members page shows no auto-nudge / monitor status per member #8931 was about. Mechanism: stalled patrol → roster indistinguishable from never-armed → nobody opens the drawer → the incident recurs. Nothing in the PR says the PM weighed the stalled class, and no compensating roster signal (needs_you covers driven sessions, not the patrol loop) fills the gap.
    Clears when: a human (PM or maintainer) confirms stalled/anomalous stops are also meant to be roster-silent, or a follow-up restores a mark scoped to anomalous stop reasons only.

Suggestions

  • If the stalled case does need visibility, key the badge on stop reason (anomalous vs resting) rather than reintroducing the three-state verdict — that satisfies the PM's complaint and keeps the presence-only idiom for benign stops.

[DESIGN-REVIEWED] b9950c2

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

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

Review details

No findings.

[OPUS-REVIEWED] b9950c2

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

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

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of b9950c291da1b10aa7b35c23b55a6a9343b006cf — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All the counts I need are run: the removed i18n key has zero remaining references anywhere in the repo; temp-screenshots/ has dozens of sibling directories plus a README (established convention); useReducedMotion already appears in ~20 source files and prefers-reduced-motion handling is mandated by website/AGENTS.md; member-patrol-dot's data-state is read only by the page itself, its test, and the capture harness. The prior pin (#8936, "a dead patrol must show at the roster") is deleted with evidence: a linked issue (#9420) and a quoted PM review of the shipped page. Final review follows.

First-Principles-Verdict: PASS

Verify #9420 knowingly trades away the roster-level dead-patrol signal #8936 shipped for incident #8931 — drawer-only is now the sole surface for a stopped loop.

Not justified as shipped

  1. reduced-motion cut — rides along on the fix; harm-free and mandated by website/AGENTS.md ("prefers-reduced-motion drops the motion, not the continuity"), so inventory only.

What this change ships

Intent: stop marking every idle member with a permanent yellow "patrol stopped" badge that read as broken — a FIX (closes #9420, PM review of the shipped page).

  1. Stopped loops no longer show any roster badge; stopped and never-armed look identical — justified
  2. The active→stopped warn recolour is gone; the badge fades out instead — justified
  3. "Patrol stopped" string removed from all 13 catalogs — justified
  4. Badge tween cuts instantly under prefers-reduced-motion (new behaviour, mount included) — rides along
  5. Capture harness now asserts active-only badges before every frame — justified
  6. Feature-map row updated in the same commit — justified
  7. New screenshots/recordings under temp-screenshots/ — justified
  8. Test pin rewritten to the new behaviour, plus a stop-in-place test — justified

The deleted pin ("a dead patrol must show at the roster, not only after the drawer opens") is overturned with checkable evidence — linked issue #9420 and the PM's review of that shipped decision — so the framing is honest, not "a gap".

Subtractions

  • Drop the now-constant data-state="active" on the badge (MembersPage.tsx) and the data-state filter in expectBadges (capture-members-patrol.mjs): one value is ever rendered, so presence of member-patrol-dot IS the state — 0 non-test consumers (grepped member-patrol-dot: hits only the page, its test, the capture script).

[FIRST-PRINCIPLES-REVIEWED] b9950c2

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

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

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] b9950c2

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

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of b9950c291da1b10aa7b35c23b55a6a9343b006cf — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence checks out: eight screenshots cover every state (active dark/light, unlimited, both stop reasons, none, loading, error), two committed recordings show the arm/disarm fade and the drawer crossfade, the exit tween plus reduced-motion handling are in the diff, and the blind reader correctly read the screens. The one residual risk is that the removal flattens an actionable stop (approval stalled) into the same no-signal as a benign one (cap reached).

UX-Verdict: CONCERNS

Dropping the warn badge also silences the one stop that needs the user: an approval-stalled patrol now dies invisibly at the roster.

Watch

  • The badge removal is all-or-nothing: activePatrolOf returns nothing for any inactive loop, so scribe's "Stopped waiting for a tool approval in this conversation" (shot-05) shows the same bare avatar as scout's benign cap-reached stop — a patrol that is dead until the user acts has no roster signal, and no other row mark covers it (presence and unread dots are unrelated; the drawer must be opened per member to discover it). The blind reader already found the drawer end of this dead-ended: "I'd want a 'review the approval' button and there isn't one here." Moderate frequency × silent automation failure × persists until noticed. Smallest fix: keep a mark (warn, or the existing needs-approval vocabulary) only when stopped_reason is user-actionable, dropping it only for benign stops — this still satisfies the PM's "no mark without a patrol worth marking".

[UX-REVIEWED] b9950c2

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 8, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author
  • Dropping the warn badge also silences the one stop that needs the user: an approval-stalled patrol now dies invisibly at the roster — Rebut (accept as designed). The premise "dead until the user acts" does not hold for this stop reason: autonudge.notify_approval_stalled is only called when the approval prompt has ALREADY timed out with no decision, and the loop is stopped by _timer on the next wake — there is no approval waiting to be answered once the roster sees the record. The only remedy is a re-arm, identical to a cap / budget / manual stop, and the drawer has no restart control yet (deferred to fix/autonudge-member-self-loop, per feat(members): show each member's auto-patrol (monitor loop) status #8936 rounds 4–7) — the blind reader's "I'd want a 'review the approval' button" is that deferred gap, not something a roster mark can satisfy. A warn mark scoped to this reason would be a standing placeholder pointing at nothing actionable, which is the PM's [insider] Members roster shows a yellow "patrol stopped" badge that reads as an error; show a badge only while a loop is active #9420 complaint restated. The two-state badge is the PM's explicit directive for this page; the stop reason remains spelled in the drawer block (frame 05). When the re-arm control exists, a roster mark for user-actionable stops is the right follow-up and belongs with that PR.

@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 Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: passed Eligible automated validation passed for the current revision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[insider] Members roster shows a yellow "patrol stopped" badge that reads as an error; show a badge only while a loop is active

1 participant