fix(inbox): name only the board scope the Inbox list actually applies - #2580
Conversation
The Paper Inbox chip read "Board: {board} - Column: {column}" whenever the
route carried a columnId, while loadInboxInternal calls fetchItems({ limit:
200, boardId }) with no column key. The list was never column-filtered, so
the chip named a filter that did not exist, and the scoped empty state
interpolated the same label.
scopeLabel now always renders inbox.scope.board, and the untruthful
inbox.scope.boardAndColumn key is removed from en, it and es. The column is
not promoted to a second line: CaptureListQuery has no column key,
CreateCaptureItemDto has no ColumnId and triage targets the board's default
column, so there is no capture destination to disclose either.
useInboxOrchestrator is untouched, so activeColumnId and clearScope's delete
of query.columnId still handle a hand-written URL.
New spec inbox/PaperInboxScopeTruth.spec.ts mounts the view over the real
orchestrator and asserts the rendered chip and the outgoing fetchItems
argument in one test, which neither existing spec can do.
Refs #1984
…apture
The board column's "+ capture" pushed { boardId, columnId } to the Inbox and
named itself "Capture a note into Inbox for {column}". Nothing honours the
column: CaptureListQuery has no column key, CreateCaptureItemDto has no
ColumnId, and triage targets the board's default column. The pushed columnId
only fed the Inbox scope chip, which claimed a filter the list request never
applied.
The control now pushes the board scope alone, and the accessible name reads
"Capture a note into Inbox from {column}" in en, it and es: the destination
is the Inbox, and the column is only where the capture was taken from.
Refs #1984
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Review gate (Codex credits exhausted, SC-9): one fresh-context adversarial reviewer on head bb35651 (merge base 46fb41d). Verdict: SHIP. Confirmed: no surface still names a column as an applied Inbox filter or a capture destination (scope chip, empty state, eyebrow, route meta, Legacy inbox, nib/composer destination lines all board-only); the deleted inbox.scope.boardAndColumn key has zero remaining consumers and the three catalogs stay in parity; openCapture was the only producer of columnId into the Inbox route; no untouched spec or E2E pins the old contract; the coupled spec is the only place both halves (chip text and fetchItems call shape) can be observed together. Findings by bin:
Merge order note: this PR's locale hunk is adjacent to PR #2584's empty/eyebrow hunks in src/locales/{en,it,es}/inbox.ts, so whichever lands second gets a merge of main and a re-prove before merging. Evidence at the reviewed head (from the implementation run): PaperInboxScopeTruth.spec.ts red on exactly the defect then green, the standing Inbox glob 466 to 471 passed, typecheck, ESLint, build, diff check; ci-required green at bb35651. Not verified: Playwright, native it/es review. Merge after the fix commit ages and ci-required is green at the new head. |
Review of PR #2580 (MEDIUM). Rewording captureAria to "from {column}" still gave N functionally identical controls N different accessible names. Since openCapture now pushes { boardId } and nothing else, exactly like the board-level "Capture here", a screen-reader user heard "from Backlog" / "from Today" / "from Done" for one behaviour, announcing a distinction the system does not make. captureAria drops its placeholder in en, it and es and states the board-level truth: "Capture a note into this board's Inbox". addAria keeps {column}, because adding a card really does differ per column. The spec now asserts all three controls carry the same name and that addAria still carries its column. Refs #1984
Review of PR #2580 (LOW). HORIZON_JOURNEY Step 20 and residual row 3d record the "+ CAPTURE" deep link as /workspace/inbox?boardId=...&columnId=..., which the Paper board can no longer produce. Both get a one-line dated addendum. The historical observation and the BROKEN verdict are left exactly as written: the run happened, and the column branch of #1984 is still open. Refs #1984
|
Round 2 (head 6d08c38): the fix diff was verified by the coordinator reading it rather than a second reviewer pass, because it changes copy, one template binding and specs only: boardDetail.card.captureAria drops its placeholder in en, it and es together ("Capture a note into this board's Inbox" and its two translations), PaperBoardColumn passes no column to that key, the board-management spec asserts all three capture controls carry the same name while addAria still names its column (red first), the PR body states that only clearScope's delete still does real work and points at #2589 for the producer-less activeColumnId / activeColumnName pair, and docs/testing/HORIZON_JOURNEY.md Step 20 and row 3d carry a dated addendum with the historical text untouched. Counts at this head (implementation run): standing Inbox glob plus catalogs.spec.ts 12 files / 471 passed (unchanged), typecheck, scoped ESLint, diff check, docs governance. Not verified: npm run build this round, browser or screen-reader check of the new name. Round count: 2. Merge after ci-required is green at 6d08c38 and the head has aged; if PR #2584 lands first, this PR gets a merge of main and a re-prove because their locale hunks are adjacent. |
Summary
The Paper Inbox told the reader it had applied a column filter that it never applied. With a route query of
?boardId=B&columnId=Cthe scope chip readBoard: {board} · Column: {column}, whileuseInboxOrchestrator.loadInboxInternalcallscaptureStore.fetchItems({ limit: 200, boardId })with no column key at all. The scoped empty state interpolated the same label, so an empty result also blamed a filter that was never applied. The board column's+ capturecontrol fed that untruth: it pushed{ boardId, columnId }and named itself "Capture a note into Inbox for {column}", promising a column-scoped capture that nothing delivers.This PR removes the claim rather than building the contract. The chip and the empty state now name only the board, which is the scope the list request actually applies, and the
+ capturecontrol carries and promises only what the Inbox can honour.Nothing about honouring a column end to end is decided here.
CaptureListQueryhas no column key,CreateCaptureItemDtohas noColumnId, and triage targets the board's default column, so a real column contract needs a product ruling and its own slice.Refs #1984 — not
Closes. This lands finding 2 only. Finding 1 is struck as superseded by #2005, and the "honour columnId end-to-end" branch of the issue stays open pending that product ruling.Changes
Inbox side:
src/views/paper/PaperInboxView.vue—scopeLabelalways rendersinbox.scope.board.activeColumnIdandactiveColumnNameare no longer destructured from the orchestrator because the view no longer has a truthful use for them.src/locales/{en,it,es}/inbox.ts— theinbox.scope.boardAndColumnkey is deleted in all three locales. It had no remaining caller and no truthful one.Board side:
src/views/paper/PaperBoardView.vue—openCapturepushes{ boardId }only.src/locales/{en,it,es}/boardDetail.ts—boardDetail.card.captureAriais reworded away from "Capture a note into Inbox for {column}". Round 2 below carries the final wording, which drops the placeholder entirely.src/composables/useInboxOrchestrator.tsis deliberately untouched, but the two halves of that are not equal.clearScope'sdelete query.columnIdstill does real work: a bookmarked or hand-written URL that carries a column is still cleaned up.activeColumnIdandactiveColumnName, by contrast, now have no production consumer at all — a repo-wide grep finds them only in the orchestrator itself, its own spec, and the stub insidePaperInboxView.spec.ts. They are left in place rather than removed here because removing them is a separate slice with its own spec churn, and it is covered by the tracked follow-up issue #2589.Specs:
src/tests/views/paper/inbox/PaperInboxScopeTruth.spec.ts(new) — mountsPaperInboxViewover the REAL orchestrator with only the route, capture store, board store, session store and boards API stubbed, and asserts the rendered chip text and the outgoingfetchItemsargument in one test. Neither existing spec could hold both halves:PaperInboxView.spec.tsreplaces the orchestrator with a stub, anduseInboxOrchestrator.spec.tsmocks thevuemodule itself so nothing can be mounted there. It sits underviews/paper/inbox/so the region's standing verification glob already covers it.src/tests/views/paper/PaperInboxView.spec.ts— the pin that requiredColumn: Readyin the chip is rewritten to require its absence, and a new test asserts the scoped empty state names only the board while the route still carries a column.src/tests/views/paper/PaperBoardManagement.spec.ts— the+ capturequery pin is rewritten to the board-only query, and a new test pins the accessible name.src/tests/composables/useInboxOrchestrator.spec.ts— a characterization pin that acolumnIdin the route still produces a board-only request. It was green before this PR by design: it records the fact the copy was contradicting.Two previously green pins were rewritten on purpose. Both asserted the untruthful copy, so the suite was defending the defect.
Test plan
Verified in
frontend/taskdeck-webat headbb35651fa:npx vitest --run --maxWorkers=2 src/tests/views/paper/PaperInboxView.spec.ts src/tests/views/paper/inbox src/tests/views/paper/PaperBoardManagement.spec.ts src/tests/composables/useInboxOrchestrator.spec.ts src/tests/store/captureStore.spec.ts src/tests/i18n/catalogs.spec.ts src/tests/views/InboxView.spec.ts— 12 files, 471 tests passed. Baseline onorigin/mainat46fb41d53was 11 files, 466 tests passed, so this is +1 file and +5 tests with no test lost.expected 'Board: Payments API Migration · Colum…' not to contain 'Column', while itsfetchItemsassertion already passed.npm run typecheck— clean.npx eslintover all 12 changed files — clean.npm run build— succeeded.git diff --check— clean.src/tests/i18n/catalogs.spec.tsis inside the run above and is green, so ADR-0054 key, placeholder-set and plural-form-count parity hold across en, it and es.NOT verified:
Boundaries and risks
Choice made for the
+ captureaffordance — both halves, not one. The issue's reconciliation allowed rewording the aria string OR droppingcolumnIdfrom the pushed query. Doing only one leaves half the promise standing: dropping the query alone leaves the control still announcing itself as capturing "for {column}", and rewording alone leaves an inertcolumnIdin the route that the next reader will assume means something. So the control now pushes{ boardId }and its accessible name was reworded too. Round 1 kept the column in that name as "from {column}"; review showed that was still wrong, because the controls are now functionally identical, and Round 2 below replaces it with a board-level name carrying no column at all.No capture-destination line was added, and this is the one place this PR departs from the residual's literal wording. The residual allowed the column to survive as a "separately labelled capture-destination line" if
columnIdstayed in the route. It is not a capture destination. Nothing in the pipeline reads it, and triage targets the board's default column, so such a line would replace one untruth with a fresher one. Since the pushed query no longer carries a column either, the case does not arise for any in-app navigation, and a hand-written URL now simply shows the board scope. If the maintainer wants the column disclosed as a destination, that needs the real contract first, which is the open half of #1984.Risk that a link with
?columnId=is now inert. It always was. The only behaviour that changes for such a URL is that the chip and empty state stop naming the column; the rows returned are identical, because they were always board-only.Risk of removing a locale key.
inbox.scope.boardAndColumnhad exactly one caller, which this PR deletes. Greps oversrc/andtests/find no other reference, and the catalogs parity spec passes with the key gone from all three locales.Deliberately out of scope: the composer (finding 1, superseded by #2005),
PaperScopeDisclosure.vue(shared withviews/paper/review/ReviewQueueRail.vue, so its single-label prop is left alone), the Legacy skin,useInboxOrchestrator.ts, and every backend file.Round 2
Head
6d08c38a1c14b914f1fcf0541a3b32d76ddf2ceb. Three review items taken, two declined by the reviewer.MEDIUM — the accessible name still named the column. Rewording to "from {column}" left N functionally identical controls carrying N different names.
openCapturenow pushes{ boardId }and nothing else, exactly like the board-level "Capture here" (PaperBoardView.vue), so a screen-reader user heard "from Backlog" / "from Today" / "from Done" for one behaviour, announcing a distinction the system does not make.boardDetail.card.captureAriatherefore drops its placeholder entirely and states the board-level truth:Capture a note into this board's InboxPrendi un appunto nell'Inbox di questa bachecaToma una nota en el Inbox de este tableroThe placeholder set changed from
{column}to none in all three locales together, so parity holds.boardDetail.card.addAriakeeps its{column}, because adding a card really does differ per column. The board name was not used instead:PaperBoardColumnhas no board-name prop, and adding one would mean threading it throughPaperBoardView's template for no gain in truth.The spec was rewritten red first. It now asserts all three capture controls carry the same accessible name and that
addAriastill carries its column.PaperInboxScopeTruth.spec.tswas named in the review as also holding an aria assertion; it does not — it asserts only the chip text and thefetchItemsshape, so nothing there needed changing.Body correction. The paragraph claiming
useInboxOrchestrator.tswas left untouched "soactiveColumnIdandclearScope's delete still handle a hand-written URL" was half wrong and has been rewritten above.clearScope's delete does real work;activeColumnIdandactiveColumnNamenow have no production consumer, appearing only in the orchestrator, its spec and thePaperInboxView.spec.tsstub. Removing them is a separate slice, tracked in a follow-up issue.LOW — the journey doc recorded a link the app can no longer emit.
docs/testing/HORIZON_JOURNEY.mdStep 20 and residual row 3d record the+ CAPTUREdeep link as/workspace/inbox?boardId=...&columnId=.... Both now carry a one-line dated addendum (2026-09-05, PR #2580) saying the link is board-only and the column branch stays open on #1984. The historical observation and the BROKEN verdict are left exactly as written: the run happened, and the residual is still real.Declined by the reviewer, no action: the redundant
not.toHaveBeenCalledWithassertion, and the weaker'Column: Ready'negative (which is still red on revert).Verified at this head:
npx vitest --run --maxWorkers=2over the standing Inbox glob pluscatalogs.spec.ts— 12 files, 471 tests passed, unchanged from round 1, so the reworded aria and its rewritten spec cost no coverage.node scripts/check-docs-governance.mjs— passed.npm run typecheck— clean.npx eslintover the five files this round changed — clean.git diff --check— clean.NOT verified this round: unchanged from the original test plan. No browser or screen-reader check was run against the new accessible name; it is asserted in the unit spec only.