fleet: taskSorting + TaskReviewTab 8 → 0 — and Board was faking a column id to force done-sorting - #2744
Conversation
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR consistently replaces legacy workflow-column ID checks with resolved role flags.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the current identity guard prevents cross-task flags from reaching the review tab, and PR-feedback eligibility now uses the same resolved role flags as its callers.
|
| Filename | Overview |
|---|---|
| packages/dashboard/app/components/TaskDetailModal.tsx | Passes identity-guarded column flags to TaskReviewTab, resolving the previously reported cross-task stale-flags issue. |
| packages/dashboard/app/components/TaskReviewTab.tsx | Replaces legacy review and WIP column-ID predicates with role-based checks for review actions and auto-merge messaging. |
| packages/dashboard/app/utils/prFeedback.ts | Extends PR-feedback eligibility to use resolved review and WIP traits while retaining legacy-ID fallback behavior. |
| packages/dashboard/app/components/TaskCard.tsx | Supplies resolved task-column flags when evaluating the PR-feedback action. |
| packages/dashboard/app/components/taskSorting.ts | Makes complete and review sorting explicit traits rather than inferring them solely from built-in column IDs. |
| packages/dashboard/app/components/Board.tsx | Passes real column IDs and resolved sorting traits instead of synthesizing the done column ID. |
| packages/dashboard/app/components/tests/TaskReviewTab.test.tsx | Adds regression coverage for renamed review lanes and confirms non-review lanes remain excluded. |
| scripts/lib/lifecycle-column-census-baseline.json | Removes converted legacy-column references from the census baseline. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Workflow[Resolved workflow column] --> Flags[Column role flags]
Flags --> Modal[TaskDetailModal identity guard]
Modal --> ReviewTab[TaskReviewTab]
Flags --> Card[TaskCard]
ReviewTab --> Feedback[PR-feedback eligibility]
Card --> Feedback
Flags --> Board[Board sorting]
Board --> Sorter[Task display sorting]
Reviews (6): Last reviewed commit: "chore(census): re-record baseline after ..." | Re-trigger Greptile
…aim a finished card, and no card was ever 'stale' (#2746) Two core clusters. 6 converted, 2 flagged. ## Two silent failures **No card was ever stale.** `task-age-staleness.ts` applies its signal only to the mid-flight and review lanes — a card in a hold or terminal lane is waiting or finished, not stale. Both lanes were named by id, so on a renamed board the signal returned `undefined` for **every** card and the stale-card warning never appeared anywhere on the board. **An agent could claim a finished card.** `claimTaskForAgent`'s terminal guard was `column === "done" || column === "archived"`. On a renamed board neither matched, so the claim **succeeded** and the agent began work on completed output. ## The threshold selectors are a separate literal, and half-converting is worse than neither `task-age-staleness` has two independent uses of `in-progress`: the **lane gate** that decides whether the signal applies, and the **threshold selectors** that pick which warning/critical numbers to measure against. Converting only the gate admits a renamed-WIP card and then measures it against the **review** threshold — a wrong number, silently. Both are converted, and each is revert-proofed on its own: | reverted | result | |---|---| | the lane gate | 3 of the new cases fail (`expected undefined to be defined`) | | the threshold selectors | the threshold case fails — a renamed WIP card gets the review threshold | No new seam for either: the staleness signal already took a `context` object, and its one production caller (`task-store/reads.ts`) already holds a **per-pass IR cache** for precisely this kind of resolution. ## Cost stated rather than hidden The `reads.ts` resolution is **unconditional**, where the hold-column read directly beside it is gated on `task.paused`. That asymmetry is deliberate: the lanes this needs are exactly what decides whether the signal applies at all, so there is no cheaper gate available ahead of it. With the shared per-pass cache that is a struct build per card, not an IR read. ## Flagged and left counted `formatCurrentTaskLine` is a pure formatter over `Pick<Task, "column">` whose output **prints** the column name for a human reader — same class as `github-tracking-comments.ts:165`. It also degrades gracefully: the "(not active — X)" wording is lost on a renamed board, but "(X)" is still accurate, just less specific. Threading a resolution into a string builder to pick a word is the wrong trade. ## The recurring blind spot, fourth time **None of the 12 existing staleness cases could have caught this** — `lifecycle` is optional and they all omit it, so they assert the legacy fallback. Same for the reconciler's 33 (#2737) and `TaskReviewTab`'s 45 (#2744). This is now a consistent property of the optional-flags seam: **the existing suite stays green through the conversion and through a broken one.** Every file in this program needs at least one case that supplies flags, or the conversion is untested in both directions. Worth making an explicit review criterion rather than something each worker rediscovers. ## Verification `pnpm test:gate` **GREEN** (158 + 10 + 487 + 71) · **31 passed** across staleness / routing-policy / dispatch suites · core `tsc` clean · `pnpm lint` clean · census `--strict` exits 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both findings are real. I had fixes for both and the branch was taken by another worktree mid-change, so I am handing over the analysis rather than fighting for the ref. Everything below is verified against the current file contents, including one trap that cost me two failing tests. Finding 1 — stale flags cross tasks (
|
…f-conversion I shipped Both greptile P1s on #2744 were real. 1. STALE FLAGS ACROSS TASKS. I passed `workflowMoveMetadata?.currentColumnFlags` raw to TaskReviewTab. The file already has `detailColumnFlags`, which applies `detailFlagsAreForThisTask` (`workflowMoveMetadata?.taskId === task.id`) — because on the render where the modal switches tasks the state still holds the PREVIOUS card's payload. Passing the raw value resolves the review tab's roles from another task's workflow: confidently wrong rather than merely stale. That reasoning was already written six lines above my own change and I reviewed past it. 2. A HALF-CONVERSION. `canStartPrFeedbackAddressing` in utils/prFeedback.ts carries the SAME lane pair the tab asks about, and I converted only the caller. Consequence: on a renamed review or WIP lane, a task with actionable PR feedback but NO loaded display items kept the Address-PR-Feedback action hidden — the caller's role check passed and the helper returned false. Now takes optional flags on the same seam, with both callers (TaskReviewTab, TaskCard) supplying what they already hold. This is the third instance in this program of "both halves or neither": the archived gate (#2724), the tracking pair (#2715), and now the lane pair split between a component and its util. Converting the visible half first is what makes the remaining half invisible. REVERT PROOF: restoring the id pair in `canStartPrFeedbackAddressing` fails the new case. The case deliberately supplies EMPTY review items — with items present the `displayItems.length > 0` arm masks the helper and the bug cannot be seen. Two fixture details the first attempt got wrong and are now recorded in the test: the field read is `lastReviewDecision` (not `reviewDecision`), and `isPrMode` needs review source `pull-request`. Census: prFeedback.ts 2 -> 0 (re-recorded via --update-baseline, which reported exactly "TIGHTENED 1 entry"). Gate 158+10+487+71 GREEN. 463 passed across the review-tab, card, sorting and columnRoles suites; the 2 TaskCard failures are the known pre-existing CSS-var geometry assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
764baee to
252e1ef
Compare
|
Both P1s were real and are fixed in 1. Stale column flags across tasks — correct, and embarrassing. I passed 2. Legacy predicate blocks feedback addressing — correct, and it was a half-conversion I shipped. That is the third instance of "both halves or neither" in this program — the archived gate (#2724), the tracking pair (#2715), and now a lane pair split between a component and its util. Converting the visible half first is exactly what makes the remaining half invisible. Revert proofRestoring the id pair in Two fixture details my first attempt got wrong, now recorded in the test so the next reader does not repeat them: the field actually read is VerificationGate GREEN (158 + 10 + 487 + 71) · 463 passed across review-tab / card / sorting / columnRoles · census The 2 |
…f-conversion I shipped Both greptile P1s on #2744 were real. 1. STALE FLAGS ACROSS TASKS. I passed `workflowMoveMetadata?.currentColumnFlags` raw to TaskReviewTab. The file already has `detailColumnFlags`, which applies `detailFlagsAreForThisTask` (`workflowMoveMetadata?.taskId === task.id`) — because on the render where the modal switches tasks the state still holds the PREVIOUS card's payload. Passing the raw value resolves the review tab's roles from another task's workflow: confidently wrong rather than merely stale. That reasoning was already written six lines above my own change and I reviewed past it. 2. A HALF-CONVERSION. `canStartPrFeedbackAddressing` in utils/prFeedback.ts carries the SAME lane pair the tab asks about, and I converted only the caller. Consequence: on a renamed review or WIP lane, a task with actionable PR feedback but NO loaded display items kept the Address-PR-Feedback action hidden — the caller's role check passed and the helper returned false. Now takes optional flags on the same seam, with both callers (TaskReviewTab, TaskCard) supplying what they already hold. This is the third instance in this program of "both halves or neither": the archived gate (#2724), the tracking pair (#2715), and now the lane pair split between a component and its util. Converting the visible half first is what makes the remaining half invisible. REVERT PROOF: restoring the id pair in `canStartPrFeedbackAddressing` fails the new case. The case deliberately supplies EMPTY review items — with items present the `displayItems.length > 0` arm masks the helper and the bug cannot be seen. Two fixture details the first attempt got wrong and are now recorded in the test: the field read is `lastReviewDecision` (not `reviewDecision`), and `isPrMode` needs review source `pull-request`. Census: prFeedback.ts 2 -> 0 (re-recorded via --update-baseline, which reported exactly "TIGHTENED 1 entry"). Gate 158+10+487+71 GREEN. 463 passed across the review-tab, card, sorting and columnRoles suites; the 2 TaskCard failures are the known pre-existing CSS-var geometry assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
252e1ef to
a62b759
Compare
…f-conversion I shipped Both greptile P1s on #2744 were real. 1. STALE FLAGS ACROSS TASKS. I passed `workflowMoveMetadata?.currentColumnFlags` raw to TaskReviewTab. The file already has `detailColumnFlags`, which applies `detailFlagsAreForThisTask` (`workflowMoveMetadata?.taskId === task.id`) — because on the render where the modal switches tasks the state still holds the PREVIOUS card's payload. Passing the raw value resolves the review tab's roles from another task's workflow: confidently wrong rather than merely stale. That reasoning was already written six lines above my own change and I reviewed past it. 2. A HALF-CONVERSION. `canStartPrFeedbackAddressing` in utils/prFeedback.ts carries the SAME lane pair the tab asks about, and I converted only the caller. Consequence: on a renamed review or WIP lane, a task with actionable PR feedback but NO loaded display items kept the Address-PR-Feedback action hidden — the caller's role check passed and the helper returned false. Now takes optional flags on the same seam, with both callers (TaskReviewTab, TaskCard) supplying what they already hold. This is the third instance in this program of "both halves or neither": the archived gate (#2724), the tracking pair (#2715), and now the lane pair split between a component and its util. Converting the visible half first is what makes the remaining half invisible. REVERT PROOF: restoring the id pair in `canStartPrFeedbackAddressing` fails the new case. The case deliberately supplies EMPTY review items — with items present the `displayItems.length > 0` arm masks the helper and the bug cannot be seen. Two fixture details the first attempt got wrong and are now recorded in the test: the field read is `lastReviewDecision` (not `reviewDecision`), and `isPrMode` needs review source `pull-request`. Census: prFeedback.ts 2 -> 0 (re-recorded via --update-baseline, which reported exactly "TIGHTENED 1 entry"). Gate 158+10+487+71 GREEN. 463 passed across the review-tab, card, sorting and columnRoles suites; the 2 TaskCard failures are the known pre-existing CSS-var geometry assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
a62b759 to
71aef09
Compare
…f-conversion I shipped Both greptile P1s on #2744 were real. 1. STALE FLAGS ACROSS TASKS. I passed `workflowMoveMetadata?.currentColumnFlags` raw to TaskReviewTab. The file already has `detailColumnFlags`, which applies `detailFlagsAreForThisTask` (`workflowMoveMetadata?.taskId === task.id`) — because on the render where the modal switches tasks the state still holds the PREVIOUS card's payload. Passing the raw value resolves the review tab's roles from another task's workflow: confidently wrong rather than merely stale. That reasoning was already written six lines above my own change and I reviewed past it. 2. A HALF-CONVERSION. `canStartPrFeedbackAddressing` in utils/prFeedback.ts carries the SAME lane pair the tab asks about, and I converted only the caller. Consequence: on a renamed review or WIP lane, a task with actionable PR feedback but NO loaded display items kept the Address-PR-Feedback action hidden — the caller's role check passed and the helper returned false. Now takes optional flags on the same seam, with both callers (TaskReviewTab, TaskCard) supplying what they already hold. This is the third instance in this program of "both halves or neither": the archived gate (#2724), the tracking pair (#2715), and now the lane pair split between a component and its util. Converting the visible half first is what makes the remaining half invisible. REVERT PROOF: restoring the id pair in `canStartPrFeedbackAddressing` fails the new case. The case deliberately supplies EMPTY review items — with items present the `displayItems.length > 0` arm masks the helper and the bug cannot be seen. Two fixture details the first attempt got wrong and are now recorded in the test: the field read is `lastReviewDecision` (not `reviewDecision`), and `isPrMode` needs review source `pull-request`. Census: prFeedback.ts 2 -> 0 (re-recorded via --update-baseline, which reported exactly "TIGHTENED 1 entry"). Gate 158+10+487+71 GREEN. 463 passed across the review-tab, card, sorting and columnRoles suites; the 2 TaskCard failures are the known pre-existing CSS-var geometry assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71aef09 to
67c9dac
Compare
|
Rebased onto current main; MERGEABLE. Both findings are addressed on the branch and I verified them rather than trusting the commit messages. Finding 1 — the review tab now receives Finding 2 — Verified on the rebased tree: dashboard One note for whoever picks up the follow-up I flagged earlier — the five remaining unguarded reads of |
… synthetic column id
CONVERTED (8):
- taskSorting.ts 4 -> 0. Two more optional trait params in the shape this function
already established ("caller supplies the trait, default to the legacy id"), plus
the two existing defaults routed through the role helpers so the degraded id list
lives in one place. `isReviewColumn` decides whether merging cards float to the top
of a lane; keyed on the id it silently stopped doing that on any renamed review
lane, so the operator loses the "what is merging now" ordering with nothing failing.
- TaskReviewTab.tsx 4 -> 0. Three of its four questions were `column === "in-review"`,
driving the Create-PR button, the "frozen on entry to review" auto-merge hint, and
PR-feedback addressing. On a renamed review lane all three took their non-review
branch: the button was absent and the hint claimed the effective value was NOT frozen
when it was. `columnFlags` is optional and wired from TaskDetailModal, which already
resolved it for canEdit and the actions menu.
A CALLER-SIDE HACK RETIRED: Board.tsx forced done-sorting by passing the LITERAL
"done" as the column argument for any complete-flagged lane — a synthetic id standing
in for a trait, so a custom complete lane sorted correctly only because its caller
lied about its name. Both call sites now pass the real column id and state the trait.
Board.tsx's own census count is unchanged at 2 (those two literals were the synthetic
ids, and they are gone; the 2 that remain are different sites).
REVERT PROOF, both run: restoring `task.column === "in-review"` fails "shows create PR
action on a RENAMED review lane" with `Unable to find an element by:
[data-testid="task-review-create-pr"]`, and fails the hint case with
`expected 'Effective: Auto-merge off' to contain 'frozen on entry to review'`.
A third case pins that the widened test does not treat every column as review.
None of the 45 existing TaskReviewTab cases could have caught this: `columnFlags` is
optional and they all omit it, so they assert the legacy fallback.
PROCESS NOTE, because I lost this work once and had to redo it: I overwrote four files
with their origin/main versions to check whether a failing test was pre-existing, then
"restored" with `git checkout HEAD --`. HEAD was still origin/main because I had not
committed, so that discarded the conversion. Same class as the shared-stash incident:
a positional/implicit restore reference. This commit was created BEFORE the baseline
comparison so `git checkout HEAD -- <file>` restores my work, not main's.
Census 8 -> 0. Gate 158+10+487+71 GREEN. 232 passed across the TaskReviewTab, sorting
and Board suites. The 1 board-mobile failure is pre-existing, verified against clean
origin/main files. Dashboard tsc and lint clean; --strict exits 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f-conversion I shipped Both greptile P1s on #2744 were real. 1. STALE FLAGS ACROSS TASKS. I passed `workflowMoveMetadata?.currentColumnFlags` raw to TaskReviewTab. The file already has `detailColumnFlags`, which applies `detailFlagsAreForThisTask` (`workflowMoveMetadata?.taskId === task.id`) — because on the render where the modal switches tasks the state still holds the PREVIOUS card's payload. Passing the raw value resolves the review tab's roles from another task's workflow: confidently wrong rather than merely stale. That reasoning was already written six lines above my own change and I reviewed past it. 2. A HALF-CONVERSION. `canStartPrFeedbackAddressing` in utils/prFeedback.ts carries the SAME lane pair the tab asks about, and I converted only the caller. Consequence: on a renamed review or WIP lane, a task with actionable PR feedback but NO loaded display items kept the Address-PR-Feedback action hidden — the caller's role check passed and the helper returned false. Now takes optional flags on the same seam, with both callers (TaskReviewTab, TaskCard) supplying what they already hold. This is the third instance in this program of "both halves or neither": the archived gate (#2724), the tracking pair (#2715), and now the lane pair split between a component and its util. Converting the visible half first is what makes the remaining half invisible. REVERT PROOF: restoring the id pair in `canStartPrFeedbackAddressing` fails the new case. The case deliberately supplies EMPTY review items — with items present the `displayItems.length > 0` arm masks the helper and the bug cannot be seen. Two fixture details the first attempt got wrong and are now recorded in the test: the field read is `lastReviewDecision` (not `reviewDecision`), and `isPrMode` needs review source `pull-request`. Census: prFeedback.ts 2 -> 0 (re-recorded via --update-baseline, which reported exactly "TIGHTENED 1 entry"). Gate 158+10+487+71 GREEN. 463 passed across the review-tab, card, sorting and columnRoles suites; the 2 TaskCard failures are the known pre-existing CSS-var geometry assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67c9dac to
781385f
Compare
…parate review findings (#2761) ## The pattern `workflowMoveMetadata` outlives a task switch, so while the modal is open its flags describe the **previous** task for a render. This component gates editability, the execution-mode replan decision, the intake affordance, the actions menu and the review tab on them. **The guard already existed** at line 961: ```ts const detailFlagsAreForThisTask = workflowMoveMetadata?.taskId === task.id; const detailColumnFlags = detailFlagsAreForThisTask ? workflowMoveMetadata?.currentColumnFlags : undefined; ``` Five call sites read **around** it. Each was found separately — #2744 (review tab), #2696 (`handleDelete` deps), and the four converted here: | line | consumer | |---|---| | 1827 | `canEdit` | | 2147 | execution-mode replan on save | | 2285 | execution-mode replan on mode change | | 3156 | `isIntakeColumn` | | 3782 | actions-menu model | **Six review rounds for one root cause.** Converting them together retires the class instead of paying a round per site — the same arithmetic as the review-lane family, which took #2730 and #2750 to end rather than eight per-file patches. Only the definition line still reads the raw value, which is the point of it. ## Not covered by a new test — stated rather than papered over is internal state populated by a fetch, not a prop, so the stale-flags scenario needs the workflow-metadata request mocked **plus** a task switch mid-render. That is a real test worth writing. It is not a line I can add honestly in passing, and I have shipped four tests today that passed with the bug fully in place — I would rather flag the gap than repeat that. This file's suite also carries **6 pre-existing failures**, confirmed identical on clean by stashing and re-running, which would muddy the signal from a new case. ## What is verified - the narrowing is mechanical and **total** — one remaining raw read, the definition; - pre-existing failure count **unchanged at 6/45** with and without this change; - 106 passed + 5 skipped across both `TaskDetailModal` suites; - dashboard `tsc` clean (`tsconfig.app.json`), lint clean (0 errors), gate green (487 + 158 + 10 + 71). No census movement — this changes which value is read, not whether a column id is compared.
…EL and the error MESSAGE (#2774) Both red on main. `api:curated` goes **2 failed → 34 files / 1599 passed**. Neither is a product defect — both are conversions the tests had not followed. ## 1. The log channel moved `sse.test.ts` spied `console.log`. `sseDebug` routes through `createLogger("sse").debug` (`sse.ts:50-53`), and the shared logger writes debug lines to **`console.error`** carrying a `\0fnlvl=info\0` severity marker — that is the point of FN-8603's adapter. So the spy saw nothing, and the failure read `expected false to be true`, naming neither the channel nor the logger. The stderr in the run output showed the lines being emitted the whole time: ``` fnlvl=info [sse] [sse] + connection (active=1, hwm=2) fnlvl=info [sse] [sse] - connection (active=0) ``` ## 2. The error message is now built from resolved lanes `routes-tasks` asserted the substring `"in-review or in-progress"`. The message is now: ```ts const allowed = [...prFeedbackReviewColumns, prFeedbackWipColumn] .map((column) => `'${column}'`).join(" or "); throw badRequest(`PR feedback can only be addressed for tasks in ${allowed}`); ``` so it reads `'in-review' or 'in-progress'` — quoted, and derived from the resolved columns. **Asserted each lane separately rather than re-pinning the joined string.** The join order and separator are presentation; the lanes being the resolved review + wip columns is the fact this case owns. Re-pinning the punctuation would break again on the next formatting change *and* would not have caught a wrong lane — which is the failure this test exists to catch on a renamed board. ## Verification | check | result | |---|---| | `test:quality:api:curated` | 2 failed → **34 files / 1599 passed** | | `sse.test.ts` | **24 passed** | | `routes-tasks.test.ts` | **99 passed** | | `pnpm lint`, dashboard `tsc` | clean | ## Scope Fix-forward only, per the u9 lane. Found by re-scanning the packages after #2739 / #2744 / #2754 merged, rather than by waiting for a report. For the record on the other groups at the same commit: `components-a` **1195 passed**, core is **2 failed** — both already accounted for (`archived-column-gate-parity` is #2768's target, `agent-logs-and-monitor.pg` is the deferred funnel/analytics decision on #2669).
Two app-side clusters, 8 → 0, plus a caller-side hack retired.
What was broken
TaskReviewTab.tsx— three of its four questions weretask.column === "in-review", driving the Create-PR button, the "frozen on entry to review" auto-merge hint, and PR-feedback addressing. On a renamed review lane all three took their non-review branch: the button was absent, and the hint claimed the effective auto-merge value was not frozen when it was.taskSorting.ts—isReviewColumndecides whether merging cards float to the top of a lane. Keyed on the id it silently stopped doing that on any renamed review lane, so the operator loses the "what is merging right now" ordering with nothing failing.Both follow the shape this code already established: caller supplies the trait, default to the legacy id.
columnFlagson the review tab is optional and wired fromTaskDetailModal, which already resolved it forcanEditand the actions menu.A synthetic column id, retired
Board.tsxforced done-sorting by passing the literal"done"as the column argument for any complete-flagged lane:A synthetic id standing in for a trait — so a custom complete lane sorted correctly only because its caller lied about its name. Both call sites now pass the real column id and state the trait. (Board's own census count stays at 2: those two literals were the synthetic ids and are gone; the 2 remaining are different sites.)
Revert proof
task.column === "in-review"on the Create-PR guardUnable to find an element by: [data-testid="task-review-create-pr"]expected 'Effective: Auto-merge off' to contain 'frozen on entry to review'A third case pins that the widened test does not treat every column as review.
None of the 45 existing
TaskReviewTabcases could have caught this —columnFlagsis optional and they all omit it, so they assert the legacy fallback. That is the same blind spot as the reconciler's 33 in #2737, and it keeps recurring: an optional-flags seam means the existing suite stays green through the conversion and through a broken one.A process failure worth recording
I lost this conversion once and had to redo it. I overwrote four files with their
origin/mainversions to check whether a failing test was pre-existing, then "restored" withgit checkout HEAD -- <dir>. HEAD was stillorigin/mainbecause I had not committed, so that discarded the work.Same class as the shared-stash incident two PRs back: an implicit or positional restore reference. The fix is ordering, not care — commit before any baseline comparison, so
git checkout HEAD -- <file>restores my work rather than main's. This PR's commit was created before the comparison for exactly that reason, and the note is in the commit message so the next person hits it there too.Verification
pnpm test:gateGREEN (158 + 10 + 487 + 71) · 232 passed across TaskReviewTab / taskSorting / Board suites · dashboardtsc -p tsconfig.app.jsonclean ·pnpm lintclean · census--strictexits 0.The 1
board-mobilefailure is pre-existing — verified by swapping in cleanorigin/maincopies of all four files and reproducing it.🤖 Generated with Claude Code