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:
7
.changeset/detail-modal-canonical-flags.md
Normal file
7
.changeset/detail-modal-canonical-flags.md
Normal 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.
|
||||
@@ -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
|
||||
and read as a conversion.
|
||||
|
||||
Supplying it correctly needs the canonical's own resolved flags, which means fetching them: a data
|
||||
change, out of scope here. So the omission is deliberate and is exempted BY CALL SITE rather than by
|
||||
function name, so core's sibling site stays guarded.
|
||||
FNXC:WorkflowResolvedColumns 2026-07-31-03:10 (the "needs a fetch" blocker was never tested):
|
||||
The paragraph above rejected passing `detailColumnFlags` — correctly, that would answer about the
|
||||
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)
|
||||
&& workingTask.sourceMetadata?.nearDuplicateDismissed !== true
|
||||
&& task.column !== "archived"
|
||||
&& 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 [selectedSourceAgentId, setSelectedSourceAgentId] = useState<string | null>(null);
|
||||
const provenanceDisplay = getProvenanceLabel(workingTask, {
|
||||
|
||||
@@ -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. "
|
||||
+ "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." },
|
||||
],
|
||||
]);
|
||||
|
||||
/*
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"packages/dashboard/src/routes/register-task-workflow-routes.ts": 1,
|
||||
"packages/dashboard/app/components/Lane.tsx": 1,
|
||||
"packages/dashboard/app/components/ListView.tsx": 1,
|
||||
"packages/dashboard/app/components/TaskDetailModal.tsx": 1,
|
||||
"packages/dashboard/app/hooks/useBlockerFanout.ts": 1,
|
||||
"packages/cli/src/commands/dashboard-tui/app.tsx": 1,
|
||||
"packages/cli/src/commands/dashboard-tui/bucket-mapping.ts": 1,
|
||||
|
||||
Reference in New Issue
Block a user