fix(ui): background polls no longer re-flash loaded content — gate on "no data yet", honest stale banners, store-owned auto-expand, bare-gate ratchet (#1927) - #2371
Merged
Conversation
…data yet", honest stale banners, store-owned auto-expand, bare-gate ratchet (#1927) Four surfaces gated their loading UI on a fetch-in-flight flag, so every 10–15 s poll swapped rendered content for a spinner (MobileAdmin ×4, SchedulesPanel execution history, InfoPanel on Start/Restart), and Operations re-expanded a deliberately collapsed card on every poll delta. - utils/loadingState.js (pure): viewState / staleBannerMessage / listFrom / decideAutoExpand — the one home of "loading means no data yet" (p13/p14/p15) - MobileAdmin: hasLoaded / fetchError / lastLoadedAt per dataset, Promise.allSettled, LoadFailed on a failed first fetch, sibling InlineError stale banner; fixes two pre-existing shape bugs (operator-queue {items,count} and execution-stats {agents} were parsed as arrays — a TypeError on every poll; the /m queue tab had always read "No pending items") - SchedulesPanel: per-schedule executionsLoading / Error / LoadedAt, honest chain, stale banner, agent-switch reset + poll stop - InfoPanel: re-gated on templateInfo === null; the Start/Restart refetch is silent - operatorQueue store: autoExpandArmed + maybeAutoExpand (disarmed by any human toggle, re-armed when the queue drains); Operations watches the open set - Loading-gate ratchet: scripts/scan-loading-gates.mjs + loading-gate-baseline.json + tests/unit/loadingGateRatchet.spec.js freeze the remaining 80 bare gates in 63 files (MobileAdmin 4→0, InfoPanel 1→0, SchedulesPanel 2→1) - Tests: vitest 56 files / 1176 (3 new specs); Playwright e2e/background-refresh-invisible.spec.js (14 tests — 7 fail on the pre-fix code for behaviour reasons); #1926 / #1634 specs still green - Docs: 4 flow docs, design-system §9 layer 4, contract self-check, CLAUDE.md rule 10, 2 learnings entries, cso --diff report (0 findings) Fixes #1927 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHRjk7Hdswaici8pmuaeRd
vybe
approved these changes
Aug 21, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Validated via /validate-pr — all categories green, falsified-before-fix e2e coverage, ratchet coherent end-to-end. Approving.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MobileAdmin×4,SchedulesPanelexecution history,InfoPanelon Start/Restart), andOperationsre-expanded a deliberately collapsed card on every poll delta (design-system p13/p14/p15/p5).utils/loadingState.js::viewState({loading, hasLoaded, error, count}) → loading|failed|empty|ready + stale— and the four surfaces call it: loading only before the first data,LoadFailed(never the empty copy) on a failed first fetch, and a siblingInlineErrorstale banner ("Couldn't refresh … — showing data from HH:MM", Retry/Dismiss) when a poll fails with data on screen. Background refreshes swap values in place.autoExpandArmed+maybeAutoExpand): once per armed episode, disarmed by any human toggle, re-armed when the queue drains — a poll delta / WS arrival / remount can never override a collapse./mshape bugs —GET /api/operator-queue({items,count}) andGET /api/agents/execution-stats({agents}) were parsed as arrays, a TypeError on every poll; the/mQueue tab had always read "No pending items".scripts/scan-loading-gates.mjs+loading-gate-baseline.json+tests/unit/loadingGateRatchet.spec.jsfreeze the remaining 80 barev-if="<loading flag>"gates in 63 files; counts may only shrink (this PR: MobileAdmin 4→0, InfoPanel 1→0, SchedulesPanel 2→1 — its remaining hit is a form-submit control spinner, refactor: replace bespoke spinners with the standard scanline loading motion (design-system p12) #1921's class).Changes
src/frontend/src/utils/loadingState.js(new) ·src/frontend/src/views/MobileAdmin.vue·src/frontend/src/components/SchedulesPanel.vue·src/frontend/src/components/InfoPanel.vue·src/frontend/src/stores/operatorQueue.js·src/frontend/src/views/Operations.vue·src/frontend/src/components/operator/QueueCard.vue(test hooks:data-testid,aria-expanded, collapsearia-label)src/frontend/scripts/scan-loading-gates.mjs,src/frontend/loading-gate-baseline.json,src/frontend/tests/unit/loadingGateRatchet.spec.jssrc/frontend/tests/unit/loadingState.spec.js,src/frontend/tests/unit/operatorQueueAutoExpand.spec.js,src/frontend/e2e/background-refresh-invisible.spec.jsfeature-flows/{mobile-admin-pwa,operating-room,agent-info-display,scheduling}.md,design-system.md§9 (layer 4),design-system-contract.mdself-check,CLAUDE.mdrule 10,learnings.md(2 entries),docs/security-reports/cso-diff-2026-08-21-1927-poll-reflash.mdTest Plan
npm run test:unit: 56 files / 1,176 tests green (3 new specs: 26 + 6 + ratchet)e2e/background-refresh-invisible.spec.jsagainst a live stack: 14 passed (9@smoke, 5@interactive); pre-fix run (source stashed, spec kept): 7 fail for behaviour reasons — "Loading…" re-flash on/m,.ops-cardcount 0 from the shape bug, no failed state on a failed first load, Operations re-expanding a collapsed carde2e/honest-failed-states.spec.js(bug: fetch failures render as empty states or vanish silently (design-system p15/p25) #1926) +e2e/schedules-toggle-scroll.spec.js(bug: toggling a schedule scrolls the Schedules tab back to the top #1634) still greennpm run check:tokensOK; raw-color counts identical todevon every touched file;vite buildOK/mAgents/Ops/System no longer flash and the Queue tab shows pending items; Schedules execution history polls in place; Info tab keeps content across Start/Restart/review(0 critical; 2 informational auto-fixed) ·/cso --diff(0 findings)Out of scope, tracked separately: the ~30-file bare-gate sweep (trinity-dev
DEBT_INBOXdebt:2026-08-21-bare-loading-gates-sweep), spinner→scanline adoption (#1921, deferral recorded there), and the/mapproval-card payload/parity bug found during verification (#2370).Fixes #1927
🤖 Generated with Claude Code
https://claude.ai/code/session_01WHRjk7Hdswaici8pmuaeRd