Follow-up to PR #2630 (#2214 degraded-queue disclosure), from the read-only verification pass on its round-2 fixes. Neither item blocks that merge; both are tracked here for the smallest follow-up PR.
1. The decision rail's sticky positioning is scoped to a container that never scrolls, so the "rail below the pinned warning" handshake is inert and its comments assert a browser fact that appears false. ReviewDecisionRail renders inside ReviewMain.vue's .paper-review-main, which is overflow: auto with auto height (ReviewMain.vue ~397-401). overflow: auto makes that element the rail's nearest scrollport even though it never overflows (the surrounding .paper-review-deep__main-col is the element that scrolls), so .paper-review-decision { position: sticky; top: var(--paper-review-sticky-offset, 0) } stays at its flow position. Consequences: the round-1 review premise that the rail covered the pinned warning, and the round-2 remedy (a ResizeObserver in PaperReviewView.vue ~1206-1237 writing --paper-review-sticky-offset on the main column, consumed by ReviewDecisionRail.vue ~305-311), target a rail that does not pin; the comments at PaperReviewView.vue ~1195-1200 and ~3027-3032 and ReviewDecisionRail.vue ~305-311 describe a two-sticky band that does not exist; the spec at PaperReviewView.spec.ts ~3392-3394 frames the property as true but unreachable. The pinned warning itself is a direct child of the scrolling column and does stick. This is a CSS reading, not a browser observation; confirm first in a real browser (DevTools shows the sticky badge and its scroll container), then either make the rail's sticky real (its intended scroller is the main column; check why .paper-review-main carries overflow: auto before changing it) or remove the offset machinery and rewrite the comments to say the rail is in flow.
2. The recovery sentence is retired on any successful load, so an explicit load within a poll interval of a recovery can blank the live region before it is announced. useReviewProposals.ts ~651-667 retires queueRefreshRecovered on the next recordQueueRefreshSuccess where queueRefreshStale is already false; explicit loads take the same path (loadProposalsWithOutcome ~588), and they are common right after a recovery: post-decision reloads in useReviewActions.ts (~563, ~591), the batch composables, the board-filter watcher, dismissSettledElsewhereNotice, the pre-decision refresh barrier. Scenario: the poll recovers and sets the text; the reviewer's already-clicked Approve completes ~150 ms later and its reload empties the region; a polite live region whose text is reverted within a few hundred milliseconds may never be spoken. Fix: retire only on a background poll success (not on explicit loads), or track a recovered-at tick and retire on the next poll after it; add the poll-recovery-then-explicit-load spec.
3. Coverage note. The ResizeObserver seam (PaperReviewView.vue ~1206-1237 and the unmount disconnect) has no spec beyond the presence of the custom property; if item 1 keeps it, cover the watch, the observe call and the disconnect.
Refs #2214, PR #2630, PR #2388.
Follow-up to PR #2630 (#2214 degraded-queue disclosure), from the read-only verification pass on its round-2 fixes. Neither item blocks that merge; both are tracked here for the smallest follow-up PR.
1. The decision rail's sticky positioning is scoped to a container that never scrolls, so the "rail below the pinned warning" handshake is inert and its comments assert a browser fact that appears false.
ReviewDecisionRailrenders insideReviewMain.vue's.paper-review-main, which isoverflow: autowith auto height (ReviewMain.vue~397-401).overflow: automakes that element the rail's nearest scrollport even though it never overflows (the surrounding.paper-review-deep__main-colis the element that scrolls), so.paper-review-decision { position: sticky; top: var(--paper-review-sticky-offset, 0) }stays at its flow position. Consequences: the round-1 review premise that the rail covered the pinned warning, and the round-2 remedy (a ResizeObserver inPaperReviewView.vue~1206-1237 writing--paper-review-sticky-offseton the main column, consumed byReviewDecisionRail.vue~305-311), target a rail that does not pin; the comments atPaperReviewView.vue~1195-1200 and ~3027-3032 andReviewDecisionRail.vue~305-311 describe a two-sticky band that does not exist; the spec atPaperReviewView.spec.ts~3392-3394 frames the property as true but unreachable. The pinned warning itself is a direct child of the scrolling column and does stick. This is a CSS reading, not a browser observation; confirm first in a real browser (DevTools shows the sticky badge and its scroll container), then either make the rail's sticky real (its intended scroller is the main column; check why.paper-review-maincarriesoverflow: autobefore changing it) or remove the offset machinery and rewrite the comments to say the rail is in flow.2. The recovery sentence is retired on any successful load, so an explicit load within a poll interval of a recovery can blank the live region before it is announced.
useReviewProposals.ts~651-667 retiresqueueRefreshRecoveredon the nextrecordQueueRefreshSuccesswherequeueRefreshStaleis already false; explicit loads take the same path (loadProposalsWithOutcome~588), and they are common right after a recovery: post-decision reloads inuseReviewActions.ts(~563, ~591), the batch composables, the board-filter watcher,dismissSettledElsewhereNotice, the pre-decision refresh barrier. Scenario: the poll recovers and sets the text; the reviewer's already-clicked Approve completes ~150 ms later and its reload empties the region; a polite live region whose text is reverted within a few hundred milliseconds may never be spoken. Fix: retire only on a background poll success (not on explicit loads), or track a recovered-at tick and retire on the next poll after it; add the poll-recovery-then-explicit-load spec.3. Coverage note. The ResizeObserver seam (
PaperReviewView.vue~1206-1237 and the unmount disconnect) has no spec beyond the presence of the custom property; if item 1 keeps it, cover the watch, the observe call and the disconnect.Refs #2214, PR #2630, PR #2388.