Skip to content

feat(members): show each member's auto-patrol (monitor loop) status - #8936

Merged
bolichen97 merged 1 commit into
mainfrom
feat/members-monitor-status
Sep 6, 2026
Merged

feat(members): show each member's auto-patrol (monitor loop) status#8936
bolichen97 merged 1 commit into
mainfrom
feat/members-monitor-status

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Why

A real failure, not a hypothetical: the conductor member thread member-kirocrew-conductor stopped being woken last night and stayed silent until a person noticed the next morning. Nothing on the Crew Members page could have said so — website/src/pages/members/MembersPage.tsx had zero references to the auto-nudge service. Whether a member has an active loop, when it last woke, how many cycles it has run, or whether the last attempt to arm one was refused was invisible on the one page whose whole purpose is "is this standing member alive". The conductor died for a night and nobody knew because monitor state was not shown where the member lives.

This PR is the frontend half. It reads the existing GET /api/autonudge registry and does not depend on the backend fix that lets a member thread host its own loop (sister PR fix/autonudge-member-self-loop).

What

Detail drawer: an "Auto patrol" block next to "Sessions it's driving", filtered to the member's derived slot key (member-<slug>). Three verdicts, never conflated:

  • Active loop — "Patrolling", then interval, wakes (3 of 24 in the drawer — the badge keeps the compact 3/24 — or 61 · no limit under an unlimited cap), last wake (relative, absolute in the hover title), next wake ("Due in 13m 58s", the same deadline-preserving reading as the composer's goal chip), and the banner or the instruction's first line.
  • Stopped loop — "Patrol stopped." plus the recorded stopped_reason (wake limit, time budget, a tool approval left waiting in this conversation — the one open on screen — paused by hand; a code without a sentence renders as itself) and the last wake. A stopped loop is deliberately NOT collapsed into "nothing scheduled": that collapse is exactly how a dead patrol goes unnoticed.
  • Nothing armed — "No patrol scheduled."

A refused arm is reserved, not rendered: docs/system-specs/modules/learn-cron-dashboard.md pins an optional denied: [{slot_key, code, reason, ts}] on the list response — one entry per slot, cleared by a successful arm so a refusal can never mask a later stop, with the code vocabulary drawn from the arm chokepoint's refusal branches — for the sister PR fix/autonudge-member-self-loop to produce. No backend emits it today, so this PR types and renders nothing for it (round-2 consensus across GPT, design, first-principles and UX: a consumer must not ship a verdict with zero producers).

Data path. One React Query read (AUTONUDGE_LOOPS_QUERY_KEY), not a private fetch plus a frame merge. useWebSocket invalidates that key on every autonudge_state frame and on every (re)connect (same place seedGoalLoops runs), and a 60s refetchInterval floors it — so a stop that landed while the socket was down is re-read rather than trusted stale, and a transient mount-time failure recovers on the next signal. A refetch error after a good read keeps the last verdict. Loading renders a skeleton; a failed first read renders the shared ErrorNotice (inline, askAgent) in the drawer block AND at the roster level — a failed read blanks every badge, which must not read as "no member has a patrol" — both captured below.

Wake sources now lists an active patrol as a source ("Auto patrol · Every 20m"), so the card cannot say "Last wake 6m ago" above "Nothing wakes this member automatically".

Roster: a patrol badge on the avatar whenever a loop record exists — the goal-chip glyph, solid accent fill with the goal glyph while patrolling, solid warn fill with a pause glyph once stopped (the presence dot's fill idiom; shape as well as colour, so the two states read apart without the hover) (the dead patrol is the incident's at-a-glance case, so it must show at the roster, not only in the drawer), at the top-right corner so it coexists with the bottom-right presence dot and with the row's right-edge markers (unread dot; the needs_you pill from #8614). It animates in/out and the colour flip is transitioned; the drawer block cross-fades on a verdict change. data-testid="member-patrol-dot" with data-state; aria-label carries the wake readout in the drawer's own spelling ("On patrol · 3 of 24") or "Patrol stopped".

Shared readouts. The cycle spelling and the next-fire countdown move into website/src/components/autoNudgeLoop.ts; AutoNudgePopover now consumes them, so the composer chip and the drawer cannot drift into two spellings. The popover's AutoNudgeLoop type is re-exported unchanged for its importers.

Docs. Feature map rows for Crew Members and Monitor loops updated; module spec pins the list-read contract and the optional denied field.

i18n. 21 new keys under pages.membersPage.patrol_* / wake_patrol_every in all 13 catalogs (verified with i18n-translate.mjs verify, 0 findings per locale, hi/bn in तुम/তুমি form) plus the regenerated en-XA. Keys inserted line-anchored into each catalog's membersPage block, same as #8591merge would re-sort every 14k-line catalog.

Not touched: the composer (owned by #8852).

Screenshots and recordings

Capture harness: website/scripts/capture-members-patrol.mjs (real built SPA over stubDashboardApi, no gateway). Every frame asserts the block's data-state and the roster badge count before it is written; the recordings drive the production path — the harness holds the page's /api/ws socket and pushes real autonudge_state frames.

Active loop, dark — radar patrolling 3/24, badge on its avatar beside the green presence dot, patrol listed under Wake sources:

active dark

Same state, light:

active light

Unlimited cap — cycles read "61 · no cap":

unlimited

Stopped at its cycle cap — scout carries the warn badge:

stopped

Stopped because a tool approval went unanswered:

stalled

Nothing scheduled:

none

Registry read in flight (skeleton) and failed (shared ErrorNotice with the agent hand-off, on the roster and in the drawer block):

loading

error

Roster badge on fixer's avatar arming (accent), flipping to stopped (warn), then disarming — driven by real autonudge_state frames:

Drawer block cross-fading none → active → stopped:

Tests

MembersPage.test.tsx gains a describe pinning: active loop readouts (finite and unlimited cap — the latter caught a {{n}}/count interpolation bug in round 1 — banner vs instruction first line, next-wake wording), a loop on another slot not leaking in, a stopped loop keeping its reason, the patrol listed under Wake sources and the empty line surviving without one, a failed first read rendering ErrorNotice, a failed refetch keeping the last verdict, the roster badge accent on the active member and warn on the stopped one beside the presence dot, and query invalidation (what the websocket hook does) arming and disarming the badge. Local: tsc -b, eslint, npm run i18n:check (with I18N_BASE_REF=origin/main), brand gate, docs-lint all green; vitest left to CI.

Review round 2

  • GPT F1 feature-map-correctness: both rows updated. GPT/Opus F2 errors-use-error-notice: patrol load failure now renders ErrorNotice. GPT finding / Opus finding: React Query read + { n } interpolation.
  • Design: reconnect recovery via the existing WS invalidation path + interval floor; denied shape pinned in the module spec.
  • First principles: approval_stalled dropped from the type; denied kept (task scope) but pinned and code-mapped.
  • UX: wake-word collision (patrol listed as a wake source), raw refusal jargon (code → sentence), {{n}} bug, badge visibility (ring + 16px), stopped/none dead-end (arm hint), unlimited-cap screenshot and the two recordings added.

Review round 3

  • GPT blocking (feature map advertised an unreachable refusal verdict) + design/first-principles/UX on the same span: the refused-arm renderer, its code map and its catalog keys are removed; only the spec reservation remains, now with clearing semantics and an enumerated code vocabulary.
  • UX: the arm hint named a control the member composer does not have and would have routed into today's refusal — removed rather than reworded to a dead end; "Cycles" renamed "Wakes" (one vocabulary per list); stopped loops now carry a warn badge at the roster (first-principles' "signalled by absence" watch); loading-skeleton and ErrorNotice frames added.
  • First principles: durationParts unexported. Two client copies of the registry (redux goalLoops frame-merge vs this query) acknowledged and deferred, as the lane suggested.
  • Round-2 test reds were test-hygiene: a crons mockRejectedValue from an earlier wake-sources case leaked into the new describe; it now resets its mocks.
  • Rebased onto main (spec paragraph conflict resolved by re-applying the insertion onto main's text).

Review round 4

  • UX: badge is now a solid accent/warn fill on a 2px bg ring (presence-dot idiom); drawer wakes read "3 of 24"; loading skeleton uses the muted bg-bg-hover and its capture waits for the drawer spring to settle; "A tool approval went unanswered" reworded to name where ("Stopped waiting for a tool approval in its conversation"); stale captions fixed.
  • First principles: autonudge_stop → sentence mapping and its key removed (only research loops are stamped with it today; the raw-code fallback covers a future producer). The denied spec reservation is kept on purpose — the design lane asked for it to be pinned before the sister PR lands — see the per-lane disposition comment.
  • UX "stopped is a dead end": deliberate since round 3 (the only arm path is refused for member slots today); the restart affordance belongs to fix/autonudge-member-self-loop.

Review round 5

  • UX: "no cap" → "no limit"; the stalled-approval sentence points at this conversation (the thread on screen is where the approval waits); the badge tooltip reuses the drawer's "3 of 24" spelling.
  • First principles: the denied spec reservation and the two reconnect read paths are dispositioned (kept / deferred) in per-lane comments rather than changed.
  • Round-4 backend reds (test_main_ratchet_audit, test_snapshot timing assertions in shards 2/4 + Coverage Gate) are not this branch's: it touches no src/ or test/ file, and main's own run at d5955744f failed the same shard and gate. Rebased onto 0d65dc969.

Review round 6

  • GPT blocking (errors-use-error-notice, roster badges going silently blank on a failed registry read): a roster-level ErrorNotice now renders beside the member count whenever the read fails, drawer open or not; pinned by a test with no drawer open.
  • Round-5 backend red: Backend Tests (3.12, 4) + Coverage Gate on test_snapshot concurrency assertions again — no src//test/ change on this branch; main's d5955744f run failed the same shard and gate.

Review round 7

  • UX: stopped badge now carries a pause glyph (shape, not colour alone); "Reached its cycle cap" → "Reached its wake limit" so the stop reason uses the row's own vocabulary. The restart/approval affordance stays deferred to fix/autonudge-member-self-loop (the only arm path is refused for member slots today); a capped-out loop wearing warn is accepted — the cap is a stop the person set, and the reason line says so.
  • First principles: autoNudgeLoop.ts header no longer claims a shared "3/24" spelling; cycleText stays beside the record it reads (one consumer today). The denied reservation stays — the design lane asked for it and passed with it.
  • Round-6 backend red: Backend Tests (3.12, 4) on the same test_snapshot concurrency assertion as rounds 4–5 — inherited (no backend change here; main's d5955744f failed the same shard).

@CrysisDeu
CrysisDeu requested a review from a team September 6, 2026 07:54
@CrysisDeu
CrysisDeu requested a review from a team as a code owner September 6, 2026 07:54
@CrysisDeu
CrysisDeu requested a review from smeyffret September 6, 2026 07:54
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

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

Established conventions confirmed: temp-screenshots/ (1000+ files) and website/scripts/capture-*.mjs (450+ scripts) are both repo-standard, and the spec was updated in the same commit. The data path reuses the existing WS seed/invalidation moment rather than adding a third registry copy, the readouts are extracted rather than duplicated, and the two residual design tensions (consumer-pinned denied reservation, redux-vs-query registry copies) were explicitly dispositioned with the review lanes in the PR body. Nothing design-level remains that the author hasn't already surfaced and resolved.

Design-Verdict: PASS

A real incident (a member's dead patrol invisible for a night), solved at the right layer: one live registry read, shared readouts, stopped-state never collapsed into "none".

[DESIGN-REVIEWED] 8f5bc19

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 8f5bc196cb757064a6257573ec423166732ba70c — 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 facts are verified. The screenshots/harness follow the repo's documented PR convention (414 sibling capture-*.mjs scripts, PR template, cleanup workflow); the existing chat.goalLoops redux store genuinely deletes stopped loops (chatSlice.ts:4165) and holds only cycle counters, so the new full-registry read is meaningfully different. Two premise gaps survive: cycleText moved to the shared module with exactly one consumer (the popover it left — the drawer uses its own patrol_cycles_of keys, contradicting the anti-drift claim), and the spec reserves a denied field with zero producers and zero consumers.

First-Principles-Verdict: CONCERNS

Every surface earns its incident-derived place except two: a denied contract nothing produces, and a cycleText "share" only its old home consumes.

What this change ships

Intent: make a member's silently dead auto-nudge loop visible on the Crew Members page — an ADDITION (observability), honestly framed; the incident's cause is fixed in a declared sister PR.

  1. Drawer "Auto patrol" block, active/stopped/none never conflated — justified (named incident)
  2. Roster avatar badge, accent patrolling / warn stopped — justified (at-a-glance incident case)
  3. Active patrol listed under Wake sources — justified (removes a stated on-screen contradiction)
  4. Failed registry read shows ErrorNotice in drawer and on roster — justified (blank ≠ "no patrols")
  5. Live read: WS-frame + reconnect invalidation, 60s poll floor — justified; existing goalLoops store deletes stopped loops, so no duplicate
  6. Shared countdown readouts (nextCycle, 2 consumers) — justified
  7. cycleText moved/exported — one consumer, generalized
  8. Spec reserves un-emitted denied: [{slot_key, code, reason, ts}] — zero consumers, zero producers
  9. Feature-map + spec list-contract rows — mandated (same-commit spec rule)
  10. i18n keys, capture harness, temp-screenshots/ — mandated convention (PR template; 414 sibling capture-*.mjs)

Watch

  • The description claims the shared module means "the composer chip and the drawer cannot drift into two spellings", but the drawer never imports cycleText — it ships its own patrol_cycles_of/patrol_cycles_unlimited strings (grepped: 1 consumer, AutoNudgePopover.tsx:238). The two spellings the module was to prevent exist in this PR by design.
  • The denied paragraph in learn-cron-dashboard.md pins a code vocabulary for a field no backend emits and this PR deliberately doesn't type or render — permanent contract surface whose only consumer is an unmerged sister PR.

Subtractions

  • Move the reserved-denied spec paragraph into fix/autonudge-member-self-loop, the PR that produces the field — this PR's spec change then covers only what it ships.
  • Return cycleText to AutoNudgePopover.tsx (sole consumer) or drop its Pick<> generalization; keep only nextCycle/intervalText in autoNudgeLoop.ts.

[FIRST-PRINCIPLES-REVIEWED] 8f5bc19

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

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

This comment is updated in place on each push.

Review details

FINDING -- website/src/pages/members/MembersPage.tsx:1127 -- A pending or failed patrol read makes "patrolState !== 'active'" falsely render “Nothing wakes this member” -> Fix: require patrol.loaded && !patrol.failed before rendering the empty state.
[GPT-REVIEWED] 8f5bc19

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

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

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

I have everything I need. The reconciliation checks out: all three verdicts, the badge, both themes, loading and error states are screenshotted; both animated transitions have committed recordings; the blind reader correctly identified the patrol block, badge states, intervals, wake counts, and every stop reason. The residual risks are the stalled-approval dead end, the two dot systems merging, and the doubled error notice.

UX-Verdict: CONCERNS

Comprehension of the patrol block is solid, but the stalled-approval verdict points users at "this conversation" and then strands them there.

Watch

  • "Stopped waiting for a tool approval in this conversation." names a place, not an action — the blind reader: "I would not know where to click to give that approval; the chat in the middle is empty, which is confusing." Rare state × task failure (the unstick is the whole point of surfacing it) × persists until resolved. Smallest fix: render the pointer only when a pending approval actually exists in the thread, otherwise say the approval lapsed.
  • Two dot systems share one avatar and merged in the reader's mind — they read the accent patrol badge and the green presence dot as one scheme ("radar and ledger have a greenish one"), and in the error shot took the surviving presence dots for stale patrol badges ("I can't tell if those badges are stale or fine"). Every roster glance × mild misread × persistent; only the hover tooltip disambiguates. A human should confirm the corner separation is enough.
  • With the drawer open, a failed read shows the same failure twice with two identical "Ask the agent" links (patrol_error + patrol_error_roster); the reader "can't tell if clicking each … does the same thing or two different things." Suppress the drawer-level link (plain text) while the roster notice shows.
  • The stopped verdict has no restart path (deliberate, deferred to fix/autonudge-member-self-loop); the reader guessed "maybe that's what 'Edit in crew manager' is for" — a human should accept that interim dead end knowingly.

Suggestions

  • patrol_next_in renders seconds ("Due in 13m 59s") but PATROL_TICK_MS re-reads the clock every 15s, so the seconds digit visibly jumps in 15s steps; drop seconds from the drawer readout (minutes only) or tick per second while the drawer is open.

[UX-REVIEWED] 8f5bc19

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

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

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

Review details

No findings.

[OPUS-REVIEWED] 8f5bc19

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

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

@CrysisDeu
CrysisDeu force-pushed the feat/members-monitor-status branch from be5147f to e2ba61d Compare September 6, 2026 08:57
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/members-monitor-status branch from e2ba61d to 0ea2b7a Compare September 6, 2026 09:28
@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: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Sep 6, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/members-monitor-status branch from 0ea2b7a to 310437b Compare September 6, 2026 10:36
@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: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 6, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/members-monitor-status branch from 21649cd to 8f59f70 Compare September 6, 2026 12:01
@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 6, 2026
The Crew Members page had no reference to the auto-nudge service at all,
so a standing member whose loop had stopped or never armed looked
identical to one patrolling normally. The conductor member thread went
unwoken for a whole night and nothing on its own page said so.

The detail drawer gains an "Auto patrol" block beside "Sessions it's
driving", a React Query read of the existing GET /api/autonudge registry
filtered to the member's derived slot key (member-<slug>). The websocket
hook invalidates that query on every autonudge_state frame and on every
(re)connect, and a one-minute interval floors it, so a stop that landed
while the socket was down is re-read rather than trusted stale. Three
verdicts, never conflated: an active loop shows interval, wakes (bare
count under an unlimited cap), last and next wake and the
banner-or-instruction line; a stopped loop keeps its stopped_reason
visible instead of collapsing into "nothing scheduled"; otherwise "No
patrol scheduled." Loading renders a skeleton; a failed read renders the
shared ErrorNotice. A refused arm is reserved in the module spec as an
optional `denied` field with clearing semantics and a code vocabulary,
but no backend emits it yet, so nothing here renders one.

An active patrol is also listed under Wake sources, so the card cannot say
"Last wake 6m ago" above "Nothing wakes this member automatically".

The roster avatar carries a patrol badge (the composer's goal-chip glyph)
whenever a loop record exists — accent while it patrols, warn once it has
stopped, because the dead patrol is the case that must show at a glance —
top-right, so it coexists with the bottom-right presence dot and the
row's right-edge markers.

The cycle readout and the deadline-preserving countdown move into a
shared module (components/autoNudgeLoop.ts) that AutoNudgePopover now
consumes too, so the two surfaces cannot drift apart.

Twenty-one new catalog keys in all 13 locales plus the regenerated
pseudolocale; feature map and module spec updated; capture harness
scripts/capture-members-patrol.mjs (eight stills, two recordings of the
animated state changes).
@CrysisDeu
CrysisDeu force-pushed the feat/members-monitor-status branch from 8f59f70 to 8f5bc19 Compare September 6, 2026 12:42
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 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.

Tech Lead review — approved.

Accuracy of the displayed verdict. The three states are derived from the record itself, not inferred: active from loop.active, stopped from a record that exists but is inactive, none only from a genuine absence. Crucially loaded/failed are kept distinct from empty (data === undefined && isError), so a failed registry read can never render the affirmative "No patrol scheduled" — which is the exact false statement this feature exists to prevent — and the same failure is surfaced at the roster level too, so silently blanked badges cannot read as "no member has a patrol". A stopped loop keeps its stopped_reason instead of collapsing into "none". Liveness is real: the WS hook invalidates AUTONUDGE_LOOPS_QUERY_KEY on every autonudge_state frame and on every (re)connect, with a 60s refetchInterval as the floor, so a stop that landed while the socket was down is re-read rather than trusted stale. An unmapped stopped_reason falls back to the raw code rather than to a sentence no producer emits.

No cross-slot leakage. patrolLoopOf and activePatrol are exact-key dictionary lookups against the member's derived member-<slug> slot (slots[m.name] || m.slot_key) — no prefix or fuzzy match — so a loop on any other slot (including slack:/discord: session loops, which are the only records that could belong to a different human) is structurally unrepresentable in a member's block or badge. Pinned by a test. GET /api/autonudge was already fetched wholesale by the existing seedGoalLoops path, so this adds no new endpoint or auth surface; the message/banner it now renders is an owner-scoped member-slot instruction already redacted at the arm chokepoint.

Scope. One coherent feature: read + drawer block + roster badge + shared readouts, plus the mandated same-commit docs rows, the 13 i18n catalogs, and the repo-standard capture harness / temp-screenshots evidence. The PR Scope warning (1366 lines / 7 areas) is line-count breadth of one feature, not independent work that could be split. No src/ backend change.

i18n. 22 new keys under pages.membersPage.patrol_* / wake_patrol_every present in all 13 catalogs plus regenerated en-XA; every user-visible string goes through t() — no hardcoded English in the JSX — and the {{n}}/{{max}}/{{time}}/{{cycle}} interpolations match their call sites.

Findings. Zero blocking. GPT 5.6 and Opus 4.8: no blocking findings. Design: PASS. First Principles and UX: CONCERNS, explicitly advisory — and both residuals (cycleText having one consumer; the denied spec reservation with no producer) are dispositioned in the PR body, with the design lane having asked for the reservation to be pinned. The UX watch items (stalled-approval dead end, two dot systems on one avatar, doubled ErrorNotice link, 15s countdown tick) are polish on a state this PR only reports, and the restart affordance correctly belongs to the sister backend PR. None of it justifies withholding an observability fix for an incident that already cost a night of silence.

@bolichen97
bolichen97 merged commit 05a0c8b into main Sep 6, 2026
90 of 92 checks passed
@bolichen97
bolichen97 deleted the feat/members-monitor-status branch September 6, 2026 15:25
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 6, 2026
CrysisDeu added a commit that referenced this pull request Sep 8, 2026
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 added a commit that referenced this pull request Sep 8, 2026
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
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