fix(dashboard): plugin- and dock-rendered cards resolved no column traits at all (#3025)
`renderTaskCard` is how a plugin view or the right dock draws a real task card. **Both** producers built a `TaskCard` without `taskColumnFlags`, so every role helper inside that card fell back to the legacy id — archive/revert affordances, progress, the elapsed-time indicator, the planning badge — for every plugin view on every board. Both already had the per-task map in scope. `MainContent` uses it **two lines away** for the near-duplicate canonical lookup; `useRightDockController` reads `input.columnFlagsByTaskId` for the same purpose. The card was simply never given it. ## One affordance, two producers Fixed together rather than one-plus-a-follow-up — that's what the Surface Enumeration rule is for. Finding the second producer is the only thing that makes this an invariant fix rather than a repro-shaped one. ## Measured | check | result | |---|---| | new case in the existing MainContent plugin-host harness | the rendered card must carry its resolved traits; dropping the argument from either producer reads `"none"` | | mutation on MainContent's producer | fails exactly that case | | `dashboard/__tests__` + `useRightDockController` | **35 tests green** | | census · inert-seam · lane-wiring · FNXC | green; lint and `tsc` clean | ## Not done — and my earlier reason for it was wrong `fusion-plugin-dependency-graph` still calls `isTaskStuck` without flags, exempted in the inert-seam gate by #3002. I filed #3003 claiming supply needed a **published-API change**. That's wrong: `PluginDashboardViewContext` is dashboard-internal and `@fusion/plugin-sdk` is `private: true`. I checked this time instead of asserting it — which is how I found the actual obstacle. The real blocker is that the plugin compiles against **different dashboard type declarations** than the dashboard source does: it sees a `PluginDashboardViewContext` without the field I added and an `isTaskStuck` accepting only three arguments. I built the full chain (context field → host supply → plugin consumption), hit those three errors, and reverted the plugin half rather than guess at the type plumbing inside a behaviour fix. So the exemption stands with a corrected reason, and #3003 is updated. That's the second filing rationale of mine to turn out wrong on inspection this session — after #3020, which I ended up fixing in #3022. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
.changeset/plugin-rendered-cards-column-flags.md
Normal file
7
.changeset/plugin-rendered-cards-column-flags.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Cards drawn by plugin views and the right dock now use the board's own lane names.
|
||||||
|
category: fix
|
||||||
|
dev: Both `renderTaskCard` producers built a `TaskCard` without `taskColumnFlags` despite having the per-task map in scope.
|
||||||
@@ -375,6 +375,9 @@ export function MainContent({
|
|||||||
renderTaskCard: (task: Task | TaskDetail) => (
|
renderTaskCard: (task: Task | TaskDetail) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
task={task}
|
task={task}
|
||||||
|
/* Plugin-rendered cards resolved NO traits before this: every role helper inside the
|
||||||
|
card fell back to the legacy id for any view using `renderTaskCard`. */
|
||||||
|
taskColumnFlags={columnFlagsByTaskId?.get(task.id)}
|
||||||
projectId={currentProject?.id}
|
projectId={currentProject?.id}
|
||||||
onOpenDetail={openPluginTaskDetail}
|
onOpenDetail={openPluginTaskDetail}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
|
|||||||
@@ -23,8 +23,19 @@ vi.mock("../../../plugins/PluginDashboardViewHost", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../../TaskCard", () => ({
|
vi.mock("../../TaskCard", () => ({
|
||||||
TaskCard: ({ task, onOpenDetail }: { task: Task | TaskDetail; onOpenDetail: (task: Task | TaskDetail) => void }) => (
|
TaskCard: ({ task, onOpenDetail, taskColumnFlags }: {
|
||||||
<button type="button" onClick={() => onOpenDetail(task)}>Open rendered task card</button>
|
task: Task | TaskDetail;
|
||||||
|
onOpenDetail: (task: Task | TaskDetail) => void;
|
||||||
|
taskColumnFlags?: Record<string, boolean | undefined>;
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
/* The probe for the trait hand-off: absent means the card resolved nothing. */
|
||||||
|
data-column-flags={taskColumnFlags ? JSON.stringify(taskColumnFlags) : "none"}
|
||||||
|
onClick={() => onOpenDetail(task)}
|
||||||
|
>
|
||||||
|
Open rendered task card
|
||||||
|
</button>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -301,4 +312,34 @@ describe("MainContent graph task pop-out wiring", () => {
|
|||||||
act(() => result.current.popOut(otherTask));
|
act(() => result.current.popOut(otherTask));
|
||||||
expect(result.current.tasks.map((task) => task.id)).toEqual(["FN-GRAPH", "FN-OTHER"]);
|
expect(result.current.tasks.map((task) => task.id)).toEqual(["FN-GRAPH", "FN-OTHER"]);
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
|
FNXC:WorkflowResolvedColumns 2026-07-31-05:20:
|
||||||
|
A PLUGIN-RENDERED CARD RESOLVED NO COLUMN TRAITS AT ALL.
|
||||||
|
|
||||||
|
`renderTaskCard` is how a plugin view draws a real task card. It built a `TaskCard` without
|
||||||
|
`taskColumnFlags`, so every role helper inside that card fell back to the legacy id — archive and
|
||||||
|
revert affordances, progress, the elapsed-time indicator, the planning badge — for every plugin
|
||||||
|
view on every board. The map was already in this component's scope; the card was simply never
|
||||||
|
given it.
|
||||||
|
|
||||||
|
The same omission existed in `useRightDockController`'s `renderTaskCard`, which also had the map in
|
||||||
|
scope. Both are fixed together: this is one affordance with two producers, which is the shape the
|
||||||
|
Surface Enumeration rule exists for.
|
||||||
|
|
||||||
|
REVERT CHECK: drop `taskColumnFlags` from either `renderTaskCard` and this reads "none".
|
||||||
|
*/
|
||||||
|
it("hands a plugin-rendered card its own resolved column traits", () => {
|
||||||
|
hostContexts.length = 0;
|
||||||
|
render(
|
||||||
|
<MainContent
|
||||||
|
{...mainContentProps({
|
||||||
|
taskView: "graph",
|
||||||
|
columnFlagsByTaskId: new Map([[graphTask.id, { complete: true }]]),
|
||||||
|
})}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const card = screen.getByTestId("rendered-task-card").querySelector("button");
|
||||||
|
expect(card?.getAttribute("data-column-flags")).toBe(JSON.stringify({ complete: true }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ export function useRightDockController(input: RightDockControllerInput): RightDo
|
|||||||
const renderTaskCard = useCallback((task: Task | TaskDetail) => (
|
const renderTaskCard = useCallback((task: Task | TaskDetail) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
task={task}
|
task={task}
|
||||||
|
/* Plugin- and dock-rendered cards resolved NO traits before this, so every role helper inside
|
||||||
|
the card fell back to the legacy id. The map is already in scope for the canonical lookup
|
||||||
|
below — the card itself was simply never given it. */
|
||||||
|
taskColumnFlags={input.columnFlagsByTaskId?.get(task.id)}
|
||||||
projectId={input.projectId}
|
projectId={input.projectId}
|
||||||
onOpenDetail={(value: Task | TaskDetail) => input.openDetailTask(value)}
|
onOpenDetail={(value: Task | TaskDetail) => input.openDetailTask(value)}
|
||||||
onDeleteTask={input.onDeleteTask}
|
onDeleteTask={input.onDeleteTask}
|
||||||
|
|||||||
Reference in New Issue
Block a user