Skip to content

Keep Review health disclosed while retained rows render - #2911

Merged
Chris0Jeky merged 4 commits into
mainfrom
issue-2214/review-health-retained-rows
Sep 10, 2026
Merged

Keep Review health disclosed while retained rows render#2911
Chris0Jeky merged 4 commits into
mainfrom
issue-2214/review-health-retained-rows

Conversation

@Chris0Jeky

Copy link
Copy Markdown
Owner

Problem

PR #2823 scoped Review's refresh health to the rendered queue so board B's stale/refused
warning could not appear as though it described board C. It was parked at the two-round
review ceiling on a confirmed HIGH
(r3968119184):

A queue read only replaces proposals when it lands. So when a known-degraded
board-scoped queue is widened to All boards and that unscoped read fails (500), board B's
rows stay on screen — and an unscoped filter admits every one of them via
matchesActiveBoardFilter — while the scope reset retired both persistent health warnings.
Observed on 19a987800: retainedRows=[b-1], queueScopeLoaded=false, stale=false,
refused=false. Once the transient failure toast disappeared, Review presented a partial,
known-stale board queue as the ordinary all-boards queue. Withholding the count
(queueScopeLoaded=false) is necessary but not sufficient: the rows still render and are
still actionable.

The same shape existed on the narrowing transition (a degraded unscoped queue narrowed into a
board it already returned rows for) and on the third hop
(B degraded → C fails → back to All boards).

Per the resume note on #2214
(comment) no
third fix was authorized on #2823, so this is a fresh bounded slice from current main that
carries #2823's reviewed work in as a merge and re-scopes the contract on top.

Contract chosen

Keep the landed scope's health disclosed while its retained rows still render. The rows
are not retired, so held drafts and focus are untouched.

Refresh health is now owned by the queue that landed, not by the scope being requested:

  • queueRefreshHealthStillDescribesScreen(scope, healthScope) — health still describes the
    screen when it is the same read identity, or when the queue that landed for healthScope
    is still rendering (visibleProposals.value.length > 0) under the new filter.
  • A scope transition retires health only when the rows it describes stop rendering
    (e.g. B → C, where board C's filter hides every retained B row). The one-slot retained
    snapshot is kept, exactly as Scope Review refresh health and preserve retained warnings #2823 introduced it.
  • The snapshot is restored whenever those rows come back on screen — including by widening,
    not only by returning to the same board.
  • Both landing sites (loadProposals success, poll success) now hand health ownership to the
    scope that just landed, alongside landedQueueScope and clearing the retained snapshot.

The 403 path is deliberately untouched: recordQueueAccessRevoked drops the rows and
landedQueueScope, so the old health has nothing left to describe and the durable revoked
panel stays the single owner of that fact. queueScopeLoaded, the recovery-sentence rules
(#2638), the late-response/requestId guards and the two separate failure thresholds are
unchanged.

Composable-only. Both skins already render the disclosure straight off
queueRefreshStale / queueRefreshRefused gated on !queueAccessRevoked, so no view,
locale or decision-control change was needed.

Tests

Four new regressions in src/tests/composables/useReviewProposals.spec.ts, all red-first
against the merged #2823 head (4 failed / 6 passed in the refresh-health scope attribution
block before the fix):

  1. keeps board B %s health while its retained rows render as the All boards queue
    (stale and refused) — the parked HIGH. Asserts real composable state on both halves of
    the contract: visibleProposals still ['b-1'] and the matching disclosure still up,
    plus queueScopeLoaded === false and queueAccessRevoked === false. Asserting only the
    flag would pass if a future change silently dropped the rows instead, so the row assertion
    is the negative control.
  2. retires the retained board B warning once the All boards read lands — a real landing
    clears it and hands ownership over.
  3. restores retained board B health when a failed All boards read re-exposes its rows
    the B → C (rows hidden, warning correctly stands down) → All boards (rows back, warning
    back) third hop.
  4. does not resurrect a revoked board B warning when the All boards read fails — the
    403-refused intent: rows and landed scope are gone, so no health comes back.

The B degraded → C fails → back to B shape is covered by #2823's existing
restores retained board B %s stale/refused health after a failed board C detour pair, which
still passes unchanged.

Proving checks in this worktree at 60d7ec990:

  • npx vitest --run --maxWorkers=2 src/tests/composables/useReviewProposals.spec.ts
    190 passed / 190 (was 185 + the 4 new cases from 2 it.each entries + 3 its).
  • Every Review/queue spec:
    views/ReviewView.spec.ts, views/ReviewView.coverage.spec.ts, views/paper/review/**,
    composables/usePaperReviewSelectors|useReviewActions|useReviewActions.toastLabel|useReviewCadence|useReviewKeymap,
    store/queueStore{,.polling,.integration,.demo}, utils/queue, api/queueApi,
    router/workspaceReviewRoute31 files, 714 passed / 714.
  • npm run typecheck — clean. npm run build — clean. npm run lint — 0 errors,
    9 pre-existing warnings, none in the changed files.

What was NOT verified

  • No browser / Playwright run. This is a composable-only slice and both skins bind the
    disclosure directly to the two flags, but the rendered result was not observed.
  • The full frontend vitest suite was not re-run on this head (bare vitest --run OOMs on
    this box); only the composable spec plus the 31 Review/queue spec files above. Hosted
    ci-required.yml covers the rest.
  • No backend, MCP or E2E surface is touched, so none was exercised.
  • Health attribution across a live↔archived history toggle is exercised only through the
    existing scope-identity tests, not with a dedicated archived-mode retained-rows case.

Supersedes

Supersedes #2823 and #2824. #2823's reviewed commits (3484cf9bf, 19a987800) are carried in
here by merge, so its history is preserved rather than re-authored. #2824's other sources
(#2820, #2821, #2822) have all merged, which makes that integration PR redundant.

Refs #2214. (#2214 is a multi-item tracker — items 1 and 2 remain open, so this does not
close it.)

A queue read only replaces `proposals` when it LANDS. Widening a
known-degraded board queue to All boards and failing that read therefore
left board B's rows on screen -- an unscoped filter admits every one of
them -- while the scope reset retired B's stale/refused disclosure. Once
the transient failure toast went, Review presented a partial, known-stale
board queue as the ordinary all-boards queue.

Refresh health is now owned by the queue that LANDED rather than by the
scope being requested. A scope transition retires it only when the
retained rows it describes stop rendering; the retained snapshot is
restored whenever those rows come back on screen, including by widening
rather than by returning to the same board. A 403 keeps its behaviour:
`recordQueueAccessRevoked` drops the rows and the landed scope, so the
health has nothing left to describe and the revoked panel stays the
single owner of that fact.

Refs #2214.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T14:49:58.018377Z 60d7ec9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60d7ec990c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (healthScope === undefined) return false
if (landedQueueScope.value !== healthScope) return false
if (healthScope === scope) return true
return visibleProposals.value.length > 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include deep-linked rows in retained-health checks

When a degraded board queue is displaying a hash-pinned completed proposal with “Show completed” off, clearing the live board filter preserves the hash, and a failed All-boards load retains that proposal. Both review views resolve such a pin directly from proposals rather than visibleProposals, so this predicate returns false and clears the stale/refused disclosure even though the row remains rendered; after the transient toast expires, the partial queue appears healthy. Include the matching rendered hash target in this check.

AGENTS.md reference: frontend/AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

return visibleProposals.value.length > 0
}

function resetQueueRefreshHealthForScope(scope: string | null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge React when local filters reveal retained rows

Restoration is evaluated only when this reset function runs at the start of a load or poll. If board B’s degraded completed row is hidden, failed B → C → All transitions leave B’s health in retainedQueueRefreshHealth; subsequently enabling “Show completed” exposes that retained row without starting another read, so the snapshot remains dormant and the known-stale queue renders without its persistent error state until a later poll. Restore retained health reactively when visibility changes.

AGENTS.md reference: frontend/AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Independent review outcome — SHIP

One fresh-context adversarial review (read-only toolset: no command execution, no edits) against
head 60d7ec990, given the parked #2823 HIGH as its starting point and these lenses: every path
that clears or restores queueRefreshStale / queueRefreshRefused / the two counters / the
recovery sentence must either keep the affected rows hidden or keep the disclosure up; all scope
transitions (board→board, board→unscoped, unscoped→board, live↔archived, showCompleted, hash
target, expiry/dismissal); the queueRefreshScope owner now being able to differ from the
requested scope; late-response and abort guards; and no regression of the 403-refused intent.

CONFIRMED HIGH/CRITICAL: none. The reviewer explicitly reports failing to construct:

  • the Scope Review refresh health and preserve retained warnings #2823 defect reintroduced (rows visible, disclosure gone) — the clear branch only fires when
    visibleProposals.length === 0 under the new filter, traced across B→C→B, B→C→unscoped and
    unscoped→B→E→unscoped;
  • the inverse (a disclosure attributed to foreign rows) — both keep and restore are gated on
    landedQueueScope === healthScope, and only a landing replaces proposals;
  • a stranded owner or permanent suppression via the queueRefreshScope === scope early return;
  • a late/superseded answer landing health or ownership — the new retainedQueueRefreshHealth = null
    / queueRefreshScope = … lines sit after the requestId / isSupersededQueueRead guards on
    both landing sites;
  • a 403 regression — recordQueueAccessRevoked still drops rows and landedQueueScope, which
    forces both keep and restore down their false branches, and both skins still gate on
    !queueAccessRevoked.

On the test lens it found no assertion that would still hold if the rows silently vanished, and
noted the keep case carries its own negative control.

Non-blocking (2 MEDIUM, 3 LOW) — tracked in #2915 under global law 2c rather than fixed
here:

  1. MEDIUM — retained health is only re-evaluated at scope transitions, so rows re-exposed by the
    deferred clock / showCompleted / a hash target can render for up to ~60 s without their
    disclosure. Self-heals at the next landing or after the counter re-climbs.
  2. MEDIUM — the keep branch's early return skips retireQueueRecovery(), so a standing sr-only
    recovery sentence can outlive its scope by ≤2 poll intervals.
  3. LOW — clear and restore disagree about whether row count matters for a same-scope return.
  4. LOW — queueScopeOf's return type is wider than it can be, making undefined and null share
    a type on queueRefreshScope.
  5. LOW — the spec's normalizeBoardIdQueryParam double returns null where the producer returns a
    string. Nothing is masked today; noted for the next fixture extension.

No fix round was opened: nothing met law 2a's merge-blocking bar, and the two MEDIUMs are bounded,
self-healing and orthogonal to the defect this PR exists to close.

@Chris0Jeky
Chris0Jeky merged commit 46ac599 into main Sep 10, 2026
36 checks passed
@github-project-automation github-project-automation Bot moved this from Pending to Done in Taskdeck Execution Sep 10, 2026
@Chris0Jeky
Chris0Jeky deleted the issue-2214/review-health-retained-rows branch September 10, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant