Skip to content

[Frontend][Inbox] Poll legacy batch triage to terminal detail - #2299

Merged
Chris0Jeky merged 28 commits into
mainfrom
issue-2230/legacy-batch-triage-poll
Sep 3, 2026
Merged

[Frontend][Inbox] Poll legacy batch triage to terminal detail#2299
Chris0Jeky merged 28 commits into
mainfrom
issue-2230/legacy-batch-triage-poll

Conversation

@Chris0Jeky

@Chris0Jeky Chris0Jeky commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • poll only successfully queued Legacy batch-triage captures every 3 seconds for at most 60 seconds
  • refresh the same Inbox board scope and reconcile cached terminal details on every tick so degraded notices become visible without a manual refresh
  • abort in-flight background reads on completion, scope change, unmount, permission loss, or deadline without disturbing the existing single-item poll

Implementation notes

  • capture list/detail reads accept optional abort and skip-retry options while ordinary callers keep their existing one-argument HTTP call shape
  • the batch poll is independent from the single-item poll and retries transient terminal-detail reconciliation failures
  • Paper Inbox has no batch-selection/action path; it shares the orchestrator only for list/scope state, so no Paper production change was needed

Tests added or updated

  • capture API option forwarding
  • store regression for Triaging cached detail -> terminal degraded notice on the second list read
  • transient detail retry, hard deadline, abort/late-response discard
  • successful-ID filtering plus board-scope, selection, scope-change, late-action, and unmount ownership

Verification

  • test-first regression: 8 expected failures before implementation
  • npx vitest --run --maxWorkers=2 src/tests/api/captureApi.spec.ts src/tests/store/captureStore.spec.ts src/tests/composables/useInboxOrchestrator.spec.ts: 117 passed
  • focused API/store/integration/Legacy/Paper command: 272 passed
  • npm run typecheck: passed
  • npm run build: passed (existing ineffective-dynamic-import warning only)
  • scoped ESLint with --max-warnings=0: passed
  • npx vitest --run --maxWorkers=2: 357 files, 5,279 tests passed
  • git diff --check: passed

Docs

Canonical status docs were not edited from the issue worktree; the coordinator owns the post-wave docs sync. docs/STATUS.md still contains the pre-fix sentence that the batch reconciliation is not a poll and needs updating after delivery.

Risks and follow-ups

  • the guarantee is deliberately bounded to terminal outcomes observed inside 60 seconds; [Inbox] Make transcript triage polling deadline provider-aware #1585 owns provider-aware behavior for longer-running triage
  • no browser, packaged-desktop, or live-provider run was performed because this is timer/store lifecycle behavior with no visual change

Advances #2230

Deliberately not "Closes". Changed by the coordinator on 2026-09-03 before merge. The independent
review at head 77225f441 confirmed the poll can still expire into the exact #2230 symptom on a
realistic batch, so auto-closing the issue on this merge would record a fix that is only partial.
Reasoning and the arithmetic are in the merge-disposition comment on this PR; the residual is tracked
separately. #2230 stays open until the deadline or the silent expiry is addressed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 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-03T02:11:03.614595Z 77225f4 New commits
ℹ️ 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: 309e625d84

ℹ️ 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".

Comment thread frontend/taskdeck-web/src/store/captureStore.ts
Comment thread frontend/taskdeck-web/src/composables/useInboxOrchestrator.ts
Comment thread frontend/taskdeck-web/src/store/captureStore.ts

@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: c8bcb7e793

ℹ️ 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".

Comment thread frontend/taskdeck-web/src/store/captureStore.ts Outdated
Comment thread frontend/taskdeck-web/src/store/captureStore.ts Outdated
Comment thread frontend/taskdeck-web/src/store/captureStore.ts Outdated
Comment thread frontend/taskdeck-web/src/store/captureStore.ts Outdated

@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: 707b178ab5

ℹ️ 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".

Comment thread frontend/taskdeck-web/src/store/captureStore.ts
@Chris0Jeky

Chris0Jeky commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

Parked at the bounded review ceiling

PR #2299 remains open and ready-for-review, but it is not merge-eligible. No third fix round will be started in this gate loop.

Exact state:

  • base: 42aec57e22a1794f78821b857e8bb99eb3c9ae43
  • head: 707b178ab5d85018603e315afb79d5936630ee8e
  • sole fix: 9c07a3503a7978b384f49b23007c12a28ac5948f
  • current-base diff: exactly six frontend capture API/store/orchestrator/spec files
  • local proof: focused 120/120; broader 275/275; full frontend 5,284/5,284 before the disjoint backend-only base merge; typecheck, scoped zero-warning lint, build, and diff checks green; all six frontend blobs unchanged through the merge

The original HIGH finding (successful POST followed by failed reconciliation preventing poll startup) is fixed and its thread is resolved. The completed current-head review then found two additional direct paths that recreate #2230's exact stale-until-manual-refresh behavior:

  1. Overlapping batches discard earlier tracked IDs: batch B stops batch A's active poll, so A can finish degraded/failed without detail or count reconciliation.
  2. A tracked row displaced beyond the capped 200-row page is never detail-fetched: the poll runs to its deadline while the cached detail remains stale.

Both are classified HIGH under the same causal bar as the repaired blocker: durable server state and manual-refresh recovery do not make a change correct when the changed surface's purpose is to remove that exact manual-refresh dependency. Both threads remain unresolved.

Non-blocking review findings were triaged once and resolved: the detail-cache write race is tracked under #2302; foreground loading interference under #2304; stale list-error recovery under #2305. Earlier MEDIUM residuals remain #2300-#2303.

Final hosted state: 19 checks succeeded, 12 intentionally skipped, and 1 failed, with nothing pending. Windows Frontend Unit failed before frontend install in the unchanged launcher suite, with the exact #2159 pipeline-cancellation ETIMEDOUT plus #1898 retained-cleanup-state signatures. Those occurrences are logged on their trackers. No rerun is requested because green CI cannot clear the correctness blockers.

Exact resume point:

  1. Merge the then-current origin/main into this preserved branch.
  2. Preserve every still-running same-scope batch's tracked IDs (independent lifecycles or a union with per-batch/per-ID deadlines); add a deterministic batch-A then batch-B fake-timer regression proving A still reconciles.
  3. Make tracked IDs independent of the visible 200-row page by preserving their summaries or authoritatively fetching missing IDs; add a deterministic full-page plus one-new-capture eviction regression proving the oldest tracked detail receives its terminal degradation/failure notice.
  4. Re-run the focused API/store/orchestrator suite, broader Inbox integrations, typecheck, scoped lint, build, diff check, and the full frontend suite if the changed seam warrants it.
  5. Publish one defect-only fresh-context review against the current base. Do not fold [Frontend][Inbox] Batch-triage detail-only 403 retries until the poll deadline #2300-[Frontend][Inbox] Successful batch poll recovery leaves a stale list error hiding rows #2305 into that blocker repair.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Resumed defect-only review checkpoint

Head: 22f317d0e50b101a3d5992817d7e811347207688
Current-base merge: 3802fe5202a58940ca4483dd4d87923bdad902c3 from origin/main a6cc459c90ce8fafe2e4785da5490f48b44cec19

Changed:

  • preserved batch A tracking when batch B starts
  • reconciled a tracked detail missing from the capped 200-row summary page without reinserting it
  • stopped all active batch polls on scope exit or unmount

Verified:

  • focused API/store/orchestrator: 123/123
  • coordinator rerun: 123/123
  • typecheck and production build: passed
  • full frontend: 357 files, 5,315 tests passed
  • independent exact-head review: no CRITICAL or HIGH findings

Review disposition:

  • LOW: completed stop callbacks remain retained until Inbox scope change or unmount. Classified non-blocking because no wrong-state, authorization, data-loss, or hidden-failure path was found in this repair. Deferred without another fix round.
  • Hosted Smart CI / Required Gate is green. The remaining exact-head jobs are still running.

NOT verified: manual browser or packaged-desktop behavior. This repair changes timer/store lifecycle and no visual contract.

CLAUDE_SYNC_PACKET

@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: 7fe38715c0

ℹ️ 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".

Comment thread frontend/taskdeck-web/src/store/captureStore.ts
Comment thread frontend/taskdeck-web/src/store/captureStore.ts
Comment thread frontend/taskdeck-web/src/store/captureStore.ts
@Chris0Jeky Chris0Jeky moved this from Blocked to Review in Taskdeck Execution Aug 30, 2026
@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Programme ruling: resume ratified, and the red Smart CI gate is not your blocker

Coordinator pass, 2026-09-03. The 2026-08-30T22:18:06Z park comment asked for "an explicit future programme-level resume" before another repair cycle. This is it.

1. The resumed repair round is authorized

This PR parked at the law-11 ceiling with a confirmed HIGH: after a successful retry enqueue, an exhausted immediate list refresh could leave matching cached Failed summary/detail state satisfying the batch poll preflight, so the poll stopped before its first authoritative read, reproducing the exact #2230 behaviour the PR exists to remove.

Head has since moved from fcbf7f4c4 to 99233772aff786cf38725bc4e15ccc9615db0f2c ("Require fresh batch observation before completion", pushed 2026-09-02T23:50:58Z). I verified the named blocker is fixed: pollBatchTriageCompletion now declares observedPostEnqueueList = false, isComplete()'s summary branch returns false while it is unset, and it is set to true only after a successful captureApi.listItems inside tick(). Cached terminal state can no longer satisfy completion before a current read.

This round is a programme-authorized repair, not an unauthorized third cascade. The authorization is narrow: it covers the one named preflight defect. A new blocker from here parks the PR again.

2. No base drift is owed

An earlier reading of this PR as "CLEAN but stale, checks ran 2026-08-30 against an old base" is withdrawn. git merge-base between this head and origin/main equals 0974eeb8a, which is main HEAD, and git merge-base --is-ancestor origin/main <head> succeeds. The base refresh already happened in merge commit ca50b3f81. The PR is 0 behind. Do not rebase and do not gh run rerun the old run 33337470437, which would re-prove the abandoned SHA against the abandoned base.

For the record, the drift would have been benign anyway: across the 148 commits main advanced, none touched this PR's six files, and PR #2381's service-worker change removes a latent staleness hazard for this poll rather than creating one.

3. Smart CI / Required Gate red does not block this merge

Smart CI / Required Gate is failure at 99233772a (run 33696951931, job 100468687697). Same ruling as on PR #2388:

  • .claude/rules/ci-control.md states smart-ci-shadow.yml is observation-only until the maintainer registers that check, and that ci-required.yml is the required merge gate.
  • Live branch protection on main requires exactly three contexts: Dependency Security / Dependency Security Signals, SAST Scan / SAST Scan (Semgrep), Secret Scan / Gitleaks Scan. All three are green here. Smart CI / Required Gate is not among them.

It is a planner defect against a stale expected base, tracked as #2401 (Priority I, ci, v0.3), owned by Beta under risk class R4. Do not ignore it, do not call it flaky, and do not read "advisory" as permission to merge without ci-required green.

4. What actually gates this PR

ci-required.yml completing green at exact head 99233772a. As of 2026-09-03T00:1xZ: 36 check runs, 1 failure (the advisory Smart CI gate), 1 in_progress (E2E Smoke / E2E Smoke). Note that the CI Extended run reported E2E Smoke as skipped, so the required run's own E2E result is the one that counts here; at the previous head fcbf7f4c4 E2E Smoke passed, and that evidence does not transfer.

Then one fresh-context review scoped to the repair diff, since the fix changes poll-lifecycle logic.

Two things to fold into that review rather than deferring:

  • Two Codex P2 findings posted 2026-09-02T23:55:20Z against captureStore.ts are not yet triaged, including line 612 "Report permission loss before stopping the poll", where a board-scoped 403 stops the poll silently.
  • Copilot's review at 2026-09-02T23:51:14Z did not run: "Copilot was unable to review this pull request because the user who requested the review has reached their quota limit." One of the two bot lenses is unavailable at this head, so the fresh-context review carries the gate alone.

A local re-prove needs npm install first: the lockfile moved 1149 lines in bump bc606e2d7, which also moved eslint and @typescript-eslint/eslint-plugin, so include a lint run alongside typecheck and build.

5. Sequencing value

Merging this releases six actionable Alpha issues (#2300 through #2305) that are currently frozen behind the inbox capture seam this branch locks. That is the largest single unblocking action in the Alpha pool right now, which is why this PR is ordered ahead of new Alpha work.

6. Docs

The 2026-08-30 sync packet asked for docs/STATUS.md to record #2230/#2299 as parked. That is now superseded by this resume, so no such edit will be made. docs/STATUS.md line 62 will need its correction when this merges; the coordinator owns it, do not add it here.

…dger

docs/IMPLEMENTATION_MASTERPLAN.md line 1546 still listed NetworkFirst
API caching as delivered PWA readiness. That is false and it is false in
a security-relevant direction: it describes authenticated API responses
being cached, which is exactly what #2350 removed.

Verified against main 0974eeb: frontend/taskdeck-web/vite.config.ts
now declares exactly two runtimeCaching handlers, StaleWhileRevalidate
for locale chunks (line 98) and CacheFirst for build-owned static assets
(line 118). There is no NetworkFirst rule. The runtime-cache contract
suite in src/tests/config/PwaMachinePathDenylist.spec.ts is
describe.skip('retired PWA runtime API cache contract'), so the removal
is deliberate and CI green is legitimate rather than a missed assertion.

docs/STATUS.md line 628 already carried the corrected capability text,
which PR #2381 wrote. The gap was the v0.3 post-RC integration ledger,
which enumerated the wave through 2026-09-02 but gave no entry to #2381,
the largest change in it. Added that bullet and bumped Last Updated.

Checks: check-docs-governance.mjs, check-golden-principles.mjs and
check-github-ops-governance.mjs all pass.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Current-base independent adversarial review at cbef558c06c29f1f0d4a24134e73ff767a757ffd against 57821f076f74c4715dd359ff29431df2d604bc5c: no CRITICAL or HIGH finding.

The cached terminal path cannot complete before a current post-enqueue list response sets observedPostEnqueueList. Poll generation and abort guards reject stale responses, the deadline aborts in-flight work, and the orchestrator passes only successful triage result IDs into polling.

Independent proof: focused Vitest 125/125, typecheck, scoped ESLint across all six changed files, and git diff --check passed. The worktree remained clean. Browser/manual runtime and a new simultaneous-batch concurrency test were not run. Lower-severity background response overlap remains possible in theory but has no direct changed-line path to a merge-blocking defect.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fresh-context adversarial review at exact head fa0f94d against current base b66da8f found no CRITICAL/HIGH defect.

Verified:

  • 125/125 focused capture API, store, and Inbox orchestrator tests passed.
  • Typecheck passed.
  • Production build passed, with only the existing dynamic-import warning.
  • Scoped ESLint and diff-check passed.
  • Completion requires a fresh post-enqueue list observation.
  • Capped/evicted items use detail refresh.
  • Abort and generation checks reject stale late results.
  • Polling is limited to successful enqueue IDs and cancels on scope/unmount.

Not verified:

  • Full Vitest was stopped after more than five minutes because no backend was listening on localhost:5000; this is not classified as flaky.
  • Hosted CI and live provider retry behavior.

Existing medium follow-ups remain tracked in #2301, #2302, #2305, and #1585. The only current merge blocker is the separately owned Smart CI stale-base defect in #2401 / PR #2404.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fresh-context current-base review at exact head 8a38594 against main 62f8686: no CRITICAL/HIGH blocker found.

The merge delta contains only the four already-landed Smart CI files and changes no Inbox product bytes. Static review confirms the cached-terminal completion path now requires a post-enqueue list observation before terminal success, and the evicted-detail plus abort/scope protections remain intact. Known snapshot-race and provider-aware Failed-state residuals remain non-blocking and already tracked.

Local current-base proof from the writer: 125 focused tests passed, typecheck passed, scoped ESLint passed, and both diff checks passed. Hosted exact-head CI is still being observed separately.

@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: 77225f4419

ℹ️ 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".

// A tracked item can fall beyond the newest-first list cap. Its detail
// is then the only authoritative surface, so fetch it directly even when
// the user selected the row without previously opening/caching it.
if (!summary) return true

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 Stop refetching terminal evicted details

When a tracked item falls outside the newest-200 list and its detail has already been refreshed to a terminal status, this unconditional branch still fetches that detail again on every three-second tick while any other tracked item remains Triaging. A batch with several evicted terminal items can therefore issue hundreds of redundant parallel detail requests during the 60-second window; use refreshedDetailIds and the cached terminal status to skip completed missing-summary items while continuing to poll nonterminal ones.

Useful? React with 👍 / 👎.

Correct the PWA cache record after #2350 and log #2381 in the wave ledger.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fresh-context final current-base review at exact head 77225f4 against main 4b80756: no CRITICAL/HIGH blocker found.

The current-base diff is exactly the six expected Inbox source/test files. The newly merged #2388 base delta is six Review files, with an empty path intersection and no merge loss. Static review reconfirmed the post-enqueue observation gate, capped-page terminal detail fetch, scope/unmount cancellation, late-result invalidation, and owner-scoped backend reads. The former cached-terminal completion path now reaches the scheduled poll rather than completing from stale cache.

Known #2301-#2305 client-freshness/UX residuals remain non-blocking. Writer proof: 125 focused tests, typecheck, scoped ESLint, and diff check passed. Smart CI resolved base 4b80756 and head 77225f4 on its first merge-ref attempt; hosted exact-head CI is observed separately.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fresh-context review at exact head 629fee0 against then-current main d629129 found no CRITICAL/HIGH blocker. The refresh delta was exactly docs/IMPLEMENTATION_MASTERPLAN.md and docs/STATUS.md; the PR diff remained exactly six Inbox source/test files, with no merge loss. Static review reconfirmed the post-enqueue observation gate, successful-ID filtering, scope/unmount cancellation, deadline, and late-result protections.

Main moved again afterward to 2e56a04 through merged #2403, changing only .codex/memories/00_ACTIVE.md. This head is therefore parked at the bounded base-refresh ceiling, not merge-eligible. Resume after the active coordination wave settles by merging current main once, rerunning the six-file Inbox proof, and obtaining exact-head hosted CI against that base.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Merge disposition at head 77225f441: merging, but this does not close #2230

Coordinator pass, 2026-09-03. The 2026-09-03T00:17:04Z ruling authorized one narrow repair round and said a new blocker would park the PR again. No new blocker appeared. The gate is satisfied and this merges.

Gate evidence

  • Exact-head CI green. All checks complete at 77225f441 with zero failures and zero cancellations, including E2E Smoke / E2E Smoke = success. mergeable_state = clean.
  • The authorized repair holds. The named HIGH (cached terminal state satisfying the batch-poll preflight before any authoritative read) was fixed by observedPostEnqueueList. The independent review re-derived it rather than taking it on trust: fetchDetail's default syncSummary: true seeds a matching Failed summary through upsertSummary, so the preflight's summary branch is genuinely the branch under test, and without the flag the regression would see one listItems call instead of two. The test is real, not shaped around the fix.
  • Base moved, source did not. Head went 99233772a to 77225f441 purely by merging main (bringing #2388, #2404, #2402). The PR's own six files are byte-identical between those two commits, so the verification of the repair still applies at the current head. CI was re-proven against the new base.
  • One fresh-context adversarial review at this head: 0 blockers. It hunted specifically for the failure classes the earlier rounds missed: premature termination, non-termination, unbounded retry, timers surviving teardown, overlapping polls, races with intervening capture writes, and whether observedPostEnqueueList could be set by a read that does not cover the enqueued batch. Nothing cleared the CRITICAL/HIGH bar.

The two Codex P2s: declined, with a reachability citation

Both describe 403 handling on the poll path. Neither state is reachable:

  • GET /capture/items is user-scoped with no board authorization at all. CaptureService.ListAsync:256-281 calls GetCapturesByUserAsync(userId, ...).
  • GET /capture/items/{id} returns 403 only when item.UserId != userId (CaptureService.cs:410-411).

Board-membership revocation therefore produces no 403 on either read, and a tracked id came from an enqueue that already proved ownership. There is no path from the changed lines to either scenario. Declining rather than fixing, per the non-blocking rule.

Why "Closes #2230" was changed to "Advances #2230"

This is the substantive finding, and it is the reason the issue stays open.

The poll's own deadline can expire into the exact symptom #2230 exists to remove. Arithmetic from committed configuration, not speculation:

  • BATCH_TRIAGE_POLL_MAX_DURATION_MS = 60_000 (captureStore.ts:37).
  • The worker drains at most MaxBatchSize items total per tick and then sleeps QueuePollIntervalSeconds. Shipped values are MaxBatchSize: 5, QueuePollIntervalSeconds: 5, MaxConcurrency: 2 (backend/src/Taskdeck.Api/appsettings.json:22-26), and BuildFairBatchItems shares that budget between capture-triage and non-capture work, so capture items can get fewer than five per tick.
  • The server accepts batches up to 50 (CaptureService.cs:673, MaxBatchSize = 50).

So a 50-item batch needs at least 50 seconds of pure queue-poll delay before any LLM latency is added, and a 20-item batch at least 20 seconds. Against a 60-second wall, realistic batches expire.

Expiry is silent: stop() (captureStore.ts:524-539) clears timers and aborts with no toast, no error state and no exposed ref, unlike triagePollingItemId on the single-item path. The user is left on a cached Triaging detail with the degradation notice hidden, which is #2230 verbatim.

Note the asymmetry: the single-item poll allows 450 x 2 s = 15 minutes (captureStore.ts:341-343); the batch poll, which by construction queues strictly more work, allows 1 minute.

This is not a regression, which is why it did not block the merge: main has no poll at all, so the PR is a strict improvement. But it means the acceptance criterion is not fully met, and auto-closing #2230 would have recorded a fix that is only partial. The residual is filed separately and #2230 stays open against it.

Tracked, not fixed here

Filed as a follow-up rather than a fix cascade: the deadline-versus-drain-rate mismatch and silent expiry above; the !summary staleness rule fanning out detail GETs on board-scoped batches (captureStore.ts:481 with :670-671, bounded at 50 by the server cap, request volume only); the background poll driving the user-facing loadingDetail spinner (:182, :202); completed polls never leaving activeBatchTriagePollStops (useInboxOrchestrator.ts:24,133-138, a retained closure, harmless); and the untested latestListLoadRequestId arm of isCurrent() (:579,587), which is the guard preventing a poll from clobbering a fresher explicit load and the highest-value uncovered branch in the new code.

Not verified

No runtime or browser run by me. The timing arithmetic is derived from committed configuration, not from an observed batch. docs/STATUS.md still contains the pre-fix sentence saying the batch reconciliation is not a poll; the coordinator owns that sync and it is not done in this PR.

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.

3 participants