fix(dashboard): the duplicate banner judged the canonical by legacy lane ids (#3032)

Found by applying the check I proposed on #3028: **re-test each
`ALLOWED_OMISSIONS` entry — does the omission still fail once the excuse
is removed?** It found a stale excuse on its first run.

## The entry's blocker was never tested

```
"…TaskDetailModal.tsx::isNearDuplicateCanonicalInactive"
  reason: "…Correct supply needs a fetch — a data change. See the note at the site."
```

The reasoning gets the hard part right: passing `detailColumnFlags`
would answer about the **modal's** task, not the canonical, and would
type-check while reading as a conversion. Rejecting that is correct.

Then it concludes the seam needs a fetch — without checking what is in
scope.

- `columnFlagsByTaskId` is **already a prop of this component**
(declared `:367`, destructured `:727`, used for the fan-out map at
`:3718`), keyed by task id.
- The canonical is `tasks.find((c) => c.id === nearDuplicateOf)` — drawn
from the same loaded set the map covers. If the banner can render at
all, the canonical is in `tasks`.

So `columnFlagsByTaskId?.get(canonical.id)` is the canonical's own
flags, no fetch. **`Column.tsx:307` already does exactly this**, with a
comment making the same point about not reusing the row's flags — a
sibling call site of the same function, solved.

## What was broken

The banner's "this duplicates X" warning stayed up when the canonical
had landed in a **renamed** complete lane, because
`isNearDuplicateCanonicalInactive` fell back to the legacy ids and never
saw it as finished. Same user-visible symptom #2997 fixed for the card
chip; this is the modal.

## Verification

| state | result |
|---|---|
| clean | seams gate exit 0; 123/123 across `TaskDetailModal.rendering`
+ `Column.neardup-flags-arrival` |
| revert the supply | **gate exit 1** —
`isNearDuplicateCanonicalInactive() — supplied by 10/11 call sites;
omitted at TaskDetailModal.tsx:1 (of 2)` |

That mutation is the point: with the allow-list entry present, this
exact omission passed silently. It is now defended by the gate rather
than excused by it.

`tsc -p tsconfig.app.json` 0 errors in the file, lint clean, FNXC gate
exit 0.

## The general point

This is the second allow-list entry in two PRs whose stated blocker was
wrong — #3028 removed the other one (*"needs a published-API change"*;
the SDK is `private: true` and every consumer was in-repo).

An `ALLOWED_OMISSIONS` entry is a deferral **carrying a gate's
authority**. It reads as settled, it lives inside the checker, and it
turns "nobody tested this" into "someone tested it and concluded no". A
stale baseline *number* invites a recount; a stale *paragraph* invites
agreement. Both entries this gate carried were stale, and the note at
this call site had even been revised once — the revision corrected which
flags were wrong to pass, and left the untested "needs a fetch"
conclusion standing.

Worth a periodic sweep of the remaining entries as they accumulate; with
these two gone the list is empty, so the cheapest time to
institutionalise it is now.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 01:41:13 -07:00
committed by GitHub
parent a460a9bbc0
commit e9b24b69e8
4 changed files with 27 additions and 11 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: The duplicate-warning banner in Task Detail now judges the canonical by its own lane, not the legacy ids.
category: fix
dev: `isNearDuplicateCanonicalInactive` in TaskDetailModal now receives `columnFlagsByTaskId.get(canonical.id)`; the allow-list entry claiming this needed a fetch is removed.

View File

@@ -944,15 +944,31 @@ export function TaskDetailContent({
vs union confusion that `column-role-degraded-flags.test.ts` exists to catch, and it would type-check vs union confusion that `column-role-degraded-flags.test.ts` exists to catch, and it would type-check
and read as a conversion. and read as a conversion.
Supplying it correctly needs the canonical's own resolved flags, which means fetching them: a data FNXC:WorkflowResolvedColumns 2026-07-31-03:10 (the "needs a fetch" blocker was never tested):
change, out of scope here. So the omission is deliberate and is exempted BY CALL SITE rather than by The paragraph above rejected passing `detailColumnFlags` — correctly, that would answer about the
function name, so core's sibling site stays guarded. wrong task — and then concluded the seam needs a data change. It does not. `columnFlagsByTaskId` is
already a prop of this component (declared :367, destructured :727, used for the fan-out map), and
it is keyed by task id. The canonical is `tasks.find(c => c.id === nearDuplicateOf)`, so it is
drawn from the same loaded set the map covers — a `.get(canonical.id)` is the canonical's OWN
flags, with no fetch.
`Column.tsx:307` already does exactly this, with a comment making the same point about not reusing
the row's flags. The blocker was asserted from the shape of the problem (two different tasks) rather
than tested against what was in scope.
A canonical the map does not cover yields `undefined`, which is the documented legacy fallback —
strictly better than always-legacy, never a fabricated answer.
*/ */
const showNearDuplicateWarning = Boolean(nearDuplicateOf) const showNearDuplicateWarning = Boolean(nearDuplicateOf)
&& workingTask.sourceMetadata?.nearDuplicateDismissed !== true && workingTask.sourceMetadata?.nearDuplicateDismissed !== true
&& task.column !== "archived" && task.column !== "archived"
&& task.column !== "done" && task.column !== "done"
&& !isNearDuplicateCanonicalInactive(nearDuplicateCanonical); && !isNearDuplicateCanonicalInactive(
nearDuplicateCanonical,
/* The CANONICAL's own flags, keyed by its id — never `detailColumnFlags`, which describes this
modal's task. Same shape as Column.tsx:307, the sibling site that already does this. */
nearDuplicateCanonical ? columnFlagsByTaskId?.get(nearDuplicateCanonical.id) : undefined,
);
const [sourceAgent, setSourceAgent] = useState<Agent | null>(null); const [sourceAgent, setSourceAgent] = useState<Agent | null>(null);
const [selectedSourceAgentId, setSelectedSourceAgentId] = useState<string | null>(null); const [selectedSourceAgentId, setSelectedSourceAgentId] = useState<string | null>(null);
const provenanceDisplay = getProvenanceLabel(workingTask, { const provenanceDisplay = getProvenanceLabel(workingTask, {

View File

@@ -150,12 +150,6 @@ const ALLOWED_OMISSIONS = new Map([
+ "change, not local wiring, which is the same 'needs a data change' category as the entry below. " + "change, not local wiring, which is the same 'needs a data change' category as the entry below. "
+ "Filed for the plugin-API owner rather than bodged here." }, + "Filed for the plugin-API owner rather than bodged here." },
], ],
[
"packages/dashboard/app/components/TaskDetailModal.tsx::isNearDuplicateCanonicalInactive",
{ count: 1, reason: "The flags in scope describe the MODAL'S task; the canonical is a different task on a column this "
+ "component never resolves. Passing them would type-check, read as a conversion, and answer "
+ "about the wrong task. Correct supply needs a fetch — a data change. See the note at the site." },
],
]); ]);
/* /*

View File

@@ -10,7 +10,6 @@
"packages/dashboard/src/routes/register-task-workflow-routes.ts": 1, "packages/dashboard/src/routes/register-task-workflow-routes.ts": 1,
"packages/dashboard/app/components/Lane.tsx": 1, "packages/dashboard/app/components/Lane.tsx": 1,
"packages/dashboard/app/components/ListView.tsx": 1, "packages/dashboard/app/components/ListView.tsx": 1,
"packages/dashboard/app/components/TaskDetailModal.tsx": 1,
"packages/dashboard/app/hooks/useBlockerFanout.ts": 1, "packages/dashboard/app/hooks/useBlockerFanout.ts": 1,
"packages/cli/src/commands/dashboard-tui/app.tsx": 1, "packages/cli/src/commands/dashboard-tui/app.tsx": 1,
"packages/cli/src/commands/dashboard-tui/bucket-mapping.ts": 1, "packages/cli/src/commands/dashboard-tui/bucket-mapping.ts": 1,