fix(dashboard): expose column roles to browser bundle (#3151)

## Summary
- export the browser-safe `@fusion/core/column-roles` subpath
- keep Vite/Vitest aliases ahead of broad `@fusion/core` aliases
- restore production dashboard builds after task undo classification
adopted shared column-role helpers

## Test plan
- `node scripts/check-no-node-only-core-imports-in-dashboard.mjs`
- `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest
run app/utils/__tests__/taskRevert.test.ts --pool=threads
--maxWorkers=1`
- `pnpm --filter @fusion/core typecheck`
- `pnpm --filter @fusion/dashboard typecheck`
- `CI=true pnpm check:changesets`
- `pnpm build`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
  * Fixed dashboard build compatibility for browser-based environments.
* Improved reliability when importing column role functionality across
supported application components.

* **Refactor**
* Made column role utilities available through a dedicated browser-safe
entry point.

* **Chores**
* Updated development and test configurations to consistently resolve
the new entry point.
* Documented the browser-safe module classification and recorded the
release patch.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Phil Larson
2026-07-31 06:46:19 -07:00
committed by GitHub
parent 110d6fd150
commit 920d68e10f
14 changed files with 31 additions and 6 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix the dashboard build after task undo classification began using shared column-role helpers.
category: fix
dev: Export the browser-safe column-roles core leaf and mirror its alias across Vite and Vitest consumers so broad core aliases cannot swallow the subpath.

View File

@@ -29,6 +29,11 @@
"source": "./src/task-delete-attribution.ts",
"import": "./dist/task-delete-attribution.js"
},
"./column-roles": {
"types": "./src/column-roles.ts",
"source": "./src/column-roles.ts",
"import": "./dist/column-roles.js"
},
"./detect-content-language": {
"types": "./src/detect-content-language.ts",
"source": "./src/detect-content-language.ts",

View File

@@ -1,5 +1,5 @@
import type { Task } from "@fusion/core";
import { isTerminalColumnRole, type ColumnRoleTraitFlags } from "@fusion/core";
import { isTerminalColumnRole, type ColumnRoleTraitFlags } from "@fusion/core/column-roles";
/**
* FNXC:TaskRevert 2026-07-04-00:00:

View File

@@ -141,6 +141,7 @@ export default defineConfig({
Ordered before the `@fusion/core` entry because Vite matches aliases in order and the broader key would otherwise swallow this subpath — the exact failure this line was added to fix (`"FUSION_CLIENT_HEADER" is not exported by ../core/src/types.ts`).
*/
"@fusion/core/task-delete-attribution": resolve(__dirname, "../core/src/task-delete-attribution.ts"),
"@fusion/core/column-roles": resolve(__dirname, "../core/src/column-roles.ts"),
"@fusion/core/detect-content-language": resolve(__dirname, "../core/src/detect-content-language.ts"),
"@fusion/core": resolve(__dirname, "../core/src/types.ts"),
"@fusion/dashboard/app/components/TaskCard": resolve(__dirname, "app/components/TaskCard.tsx"),

View File

@@ -534,6 +534,7 @@ export default defineConfig({
Keep this exact alias before the broader core alias so Vite does not rewrite the subpath.
*/
"@fusion/core/task-delete-attribution": resolve(__dirname, "../core/src/task-delete-attribution.ts"),
"@fusion/core/column-roles": resolve(__dirname, "../core/src/column-roles.ts"),
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
"@fusion/engine": resolve(__dirname, "../engine/src/index.ts"),
"@fusion/plugin-sdk": resolve(__dirname, "../plugin-sdk/src/index.ts"),

View File

@@ -11,6 +11,7 @@ const fusionAliases = {
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
"@fusion/core/column-roles": resolve(__dirname, "../core/src/column-roles.ts"),
"@fusion/core/task-delete-attribution": resolve(__dirname, "../core/src/task-delete-attribution.ts"),
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
"@fusion/dashboard": resolve(__dirname, "../dashboard/src/index.ts"),

View File

@@ -168,7 +168,7 @@ pgDescribe("scheduler parked-column resolution against a live store", () => {
it("REGRESSION — a dependent in a RENAMED hold column IS unblocked when its blocker is deleted", async () => {
/*
FNXC:WorkflowResolvedColumns 2026-08-01-02:20 (fleet — the flip this test was written to catch):
FNXC:WorkflowResolvedColumns 2026-07-31-06:35 (fleet — the flip this test was written to catch):
This was a CHARACTERIZATION of the inert sync read: `resolveTaskParkedColumnsSync` answered
`{ hold: "todo" }` for a board whose hold column is `backlog`, so the reconciliation queried a
column that does not exist, found no dependents, and left `blockedBy` pointing at a deleted task.

View File

@@ -450,7 +450,7 @@ function mergeParkedColumns(
}
/*
FNXC:WorkflowResolvedColumns 2026-08-01-01:40 (fleet):
FNXC:WorkflowResolvedColumns 2026-07-31-06:35 (fleet):
The ASYNC twin. The sync one below cannot answer for a custom workflow in production, so any guard that
can reach this one must.
@@ -477,7 +477,7 @@ async function resolveTaskParkedColumns(store: TaskStore, taskId: string): Promi
archived,
terminal: new Set([complete, archived]),
/*
FNXC:WorkflowResolvedColumns 2026-08-01-02:05 (fleet):
FNXC:WorkflowResolvedColumns 2026-07-31-06:35 (fleet):
The wake set UNIONS the legacy ids rather than replacing them, and that is load-bearing rather
than defensive. Post-U11 the default lineage has no `triage` column, so a RESOLVED answer returns
`intake: "todo"` where the old inert path fell back to `"triage"`. Converting without the union
@@ -1268,7 +1268,7 @@ export class Scheduler {
schedulerLog.warn(`Failed to reset dispatch oscillation state for ${task.id} on unpause: ${error instanceof Error ? error.message : String(error)}`);
});
}
/* FNXC:WorkflowResolvedColumns 2026-08-01-01:40 (fleet): the answer only gates `schedule()`,
/* FNXC:WorkflowResolvedColumns 2026-07-31-06:35 (fleet): the answer only gates `schedule()`,
which is async and fire-and-forget, so resolving it properly costs nothing observable. */
void (async () => {
const unpausedParked = await resolveTaskParkedColumns(this.store, task.id);
@@ -1299,7 +1299,7 @@ export class Scheduler {
this.planningTaskIds.add(task.id);
} else if (this.planningTaskIds.has(task.id)) {
this.planningTaskIds.delete(task.id);
/* FNXC:WorkflowResolvedColumns 2026-08-01-01:40 (fleet): as with the unpause wake above, the
/* FNXC:WorkflowResolvedColumns 2026-07-31-06:35 (fleet): as with the unpause wake above, the
answer only gates `schedule()`. The `planningTaskIds.delete` stays SYNCHRONOUS — it is the
edge-trigger bookkeeping, and deferring it would let a second update re-enter this branch. */
void (async () => {

View File

@@ -14,6 +14,7 @@ export default defineConfig({
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
"@fusion/core/column-roles": resolve(__dirname, "../core/src/column-roles.ts"),
"@fusion/core/task-delete-attribution": resolve(__dirname, "../core/src/task-delete-attribution.ts"),
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
"@fusion/test-utils": resolve(__dirname, "../core/src/__test-utils__/workspace.ts"),

View File

@@ -32,6 +32,7 @@ export default defineConfig({
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
"@fusion/core/column-roles": fileURLToPath(new URL("../../packages/core/src/column-roles.ts", import.meta.url)),
"@fusion/core/task-delete-attribution": fileURLToPath(new URL("../../packages/core/src/task-delete-attribution.ts", import.meta.url)),
"@fusion/core": fileURLToPath(new URL("../../packages/core/src/index.ts", import.meta.url)),
"@fusion/plugin-sdk": fileURLToPath(new URL("../../packages/plugin-sdk/src/index.ts", import.meta.url)),

View File

@@ -52,6 +52,7 @@ export default defineConfig({
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
{ find: "@fusion/core/column-roles", replacement: fileURLToPath(new URL("../../packages/core/src/column-roles.ts", import.meta.url)) },
{ find: "@fusion/core/task-delete-attribution", replacement: fileURLToPath(new URL("../../packages/core/src/task-delete-attribution.ts", import.meta.url)) },
{ find: "@fusion/core", replacement: fileURLToPath(new URL("../../packages/core/src/index.ts", import.meta.url)) },
{

View File

@@ -33,6 +33,7 @@ export default defineConfig({
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
{ find: "@fusion/core/column-roles", replacement: fileURLToPath(new URL("../../packages/core/src/column-roles.ts", import.meta.url)) },
{ find: "@fusion/core/task-delete-attribution", replacement: fileURLToPath(new URL("../../packages/core/src/task-delete-attribution.ts", import.meta.url)) },
{ find: "@fusion/core", replacement: fileURLToPath(new URL("../../packages/core/src/index.ts", import.meta.url)) },
{

View File

@@ -32,6 +32,7 @@ export default defineConfig({
transitively — this project aliases `@fusion/dashboard`, and `app/api/client.ts` imports the
browser-safe delete-attribution leaf.
*/
"@fusion/core/column-roles": fileURLToPath(new URL("../../packages/core/src/column-roles.ts", import.meta.url)),
"@fusion/core/task-delete-attribution": fileURLToPath(new URL("../../packages/core/src/task-delete-attribution.ts", import.meta.url)),
"@fusion/core": fileURLToPath(new URL("../../packages/core/src/index.ts", import.meta.url)),
// FNXC:Clipboard 2026-07-12-00:00: The reports plugin imports the dashboard clipboard helper through its package subpath export; keep this exact alias ahead of the package root alias so vitest does not collapse the subpath to src/index.ts.

View File

@@ -60,6 +60,11 @@
"reason": "2026-07-26: Delete caller-attribution constants and the pure header resolver are shared by the browser client that stamps `x-fusion-client` and the route that reads it; the module has no imports at all.",
"verifiedAt": "2026-07-26"
},
{
"module": "column-roles",
"reason": "2026-07-31: Column role predicates depend only on erased trait types and provide browser-safe flags-first lifecycle classification.",
"verifiedAt": "2026-07-31"
},
{
"module": "live-agent-count",
"reason": "2026-07-21: FN-8453 shares pure workflow-trait-based Running and Waiting predicates with dashboard capacity indicators; its dependency graph has no Node-only modules.",