## What `full-suite.yml` shard 1 on main fails with **zero test failures** — it dies on a resolution error: ``` Failed to resolve import "@fusion/core/task-delete-attribution" from "packages/dashboard/app/api/client.ts" ``` **Root cause.** Vite string aliases match by **PREFIX**. So `find: "@fusion/core"` → `core/src/index.ts` rewrites `@fusion/core/task-delete-attribution` into `core/src/index.ts/task-delete-attribution`, which cannot resolve. The narrower subpath alias has to come *first*. The module exists and *is* correctly declared in `packages/core/package.json` exports — this is purely a test-config trap, and `packages/dashboard/vitest.config.ts` already documents it in a comment. Six configs alias `@fusion/dashboard` (whose `app/api/client.ts` imports that browser-safe leaf) while lacking the narrower alias, so they inherited the trap. This carries the same one-line pattern to all six. ## Measured `dependency-graph` — the project actually red on main: | | Test files | Tests collected | |---|---|---| | before | 3 failed \| 17 passed | 147 | | after | **20 passed** | **180** | **33 tests were never collected** — neither passing nor reported as failing. That is the part worth flagging: an unresolved import removes tests from the run silently, and the shard's own summary printed no `Tests N failed` line at all, which is why this red looked like infrastructure noise rather than a real defect. No regressions: `reports` 110, `cli-printing-press` 41, `compound-engineering` 317, **gate 726** — all green. `pnpm lint` clean. `@fusion/desktop` is `1 failed | 264 passed` **both before and after**; verified pre-existing on clean `origin/main` by reverting just that one config and re-running. Cause is `@fusion-plugin-examples/roadmap` entry resolution, unrelated — **flagged, not fixed.** ## Deliberately not changed Engine's *second* `@fusion/core` alias (the `.gate-bundle/core.mjs` entry) is untouched: that lane bundles core on purpose, and pointing it at source would defeat the isolation the gate bundle exists to provide. ## Full-suite triage this came out of (for whoever owns the rest) Reading the four red shards of the last completed run on main (`30523568756`): | Shard | Real cause | Owner | |---|---|---| | 1/4 | **this PR** — resolution error, 0 test failures | — | | 2/4 | 23 failed: `store-wedge-resolution.pg`, `central-archive-secrets`, `task-delete-caller-attribution`, `task-delete-nonblocking-cleanup` | #2669 / #2675 cover the first two | | 3/4 | **watchdog SIGKILL** mid-`@fusion/engine [1/2]` — no test failures, no summary | unowned | | 4/4 | 17 failed, all in `@runfusion/fusion` CLI (`project.test.ts` 8, `task.test.ts` 5, `extension.test.ts` 2, +2) | unowned | Two of the four shard reds contain **no failing test at all**, so "main's full-suite failure count" cannot be read off the shard conclusions — it has to be read off `Tests N failed` summary lines, and shards 1 and 3 emit none.
fusion-plugin-dependency-graph
Plugin-provided top-level Graph dashboard view for Fusion.
Host registration and bundled loading
The dependency graph view is registered as a bundled plugin view in the dashboard. This means:
registerBundledPluginViews()(called at dashboard startup) registers the view component in the static plugin view registry under keyplugin:fusion-plugin-dependency-graph:graph.- The view component is resolved via a literal-specifier lazy import (
import("@fusion-plugin-examples/dependency-graph/dashboard-view")) — this is critical for Vite/esbuild to emit a production chunk. - In Vite dev/build and Vitest,
@fusion-plugin-examples/dependency-graph/dashboard-viewis aliased toplugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx(source, notdist/). This avoids stale dist artifacts breaking the dashboard view. - The App.tsx graph route resolves to the bundled view via
isPluginViewRegisteredfallback, so the graph view works even when the plugin is not installed/loaded through the PluginLoader API (e.g. fresh DB). - The canonical route ID is
plugin:fusion-plugin-dependency-graph:graph.
Rendering approach
- Filtering: includes
triage,todo,in-progress,in-review; excludesdone,archived - Graph build: edges are resolved only from
task.dependenciesassource=dependent,target=dependency - Orphan dependency handling: if a visible task depends on an excluded/missing dependency (for example
done/archivedafter filtering), the missing edge is silently dropped and graph rendering continues without broken connectors - Auto-layout: Sugiyama-style layered layout (
computeAutoLayout) groups nodes by dependency depth; both vertical and horizontal orientations use measured per-node card heights to compute stacked offsets (preventing card overlap), and orientation still flips responsively (height > widthorwidth < 768) so depth flows left-to-right on tall/narrow viewports - Edge drawing: SVG bezier curves from source bottom-center to target top-center, with vertical mode anchoring source tails to each node's measured rendered height and arrowheads showing dependent → dependency direction
- Interaction: drag-to-pan canvas background, scroll/two-finger pan, Ctrl/Cmd-wheel zoom, pinch-to-zoom with stationary midpoint, drag-to-reposition nodes, keyboard shortcuts, zoom toolbar, reset, and fit-to-graph
- Zoomed navigation reachability: pan clamping now scales with the full rendered graph bounds (min/max node extents) at the active zoom level, so zooming in no longer traps off-screen content behind fixed viewport-only limits
- Fit-to-graph: computes node bounding box from both minimum and maximum node coordinates (including negative auto-layout origins) with layout node dimensions and applies zoom/pan so the graph fits in viewport with padding
- Initial auto-fit: when no saved scoped positions exist, the first non-empty render auto-fits once; subsequent updates preserve user navigation state
- Position persistence: dragged node positions are stored per project in browser localStorage and restored on reload
- Animated transitions: fit/reset operations animate
transform(var(--transition-normal)), while continuous drag/wheel/pinch stays transition-free for responsiveness - Node rendering: each graph node renders the real dashboard
TaskCardviaGraphTaskNode(no duplicated card markup) - Task detail integration: a primary non-drag click on a graph node surface opens the native dashboard task detail modal exactly once through host context (
openTaskDetail), matching board/list behavior - In-progress behavior: steps are visible by default and active-task glow (
agent-active) is preserved because node cards reuse TaskCard directly - Active-state indicator bar: active nodes render a compact top bar (
.graph-task-active-indicator) with the current execution status label (for exampleExecuting,Planning) and pulsing--in-progressemphasis - Current-step highlighting: active nodes set
data-current-stepfor valid native step indices so CSS selectors highlight the currently executing.card-step-itemand pulse its step dot - Zoom-out differentiation:
.graph-task-node--activeadds amplified glow and subtle scale/border tint so active nodes remain distinguishable at reduced zoom levels - In-review visual treatment:
in-reviewnodes get a static.graph-task-node--in-reviewleft accent in--in-reviewto distinguish waiting-review work from active execution nodes - Graph node classes:
.graph-task-node,.graph-task-node--active,.graph-task-node--in-review,.graph-task-node--highlighted,.graph-task-node--dimmed,.graph-node--highlighted, and.graph-node--dimmedare available for graph-specific layering/highlight states while card internals remain owned byTaskCard.css - Graph edge classes:
.graph-edge--highlightedand.graph-edge--dimmedare applied during dependency-chain emphasis states - Drag behavior: graph nodes pass
disableDrag={true}toTaskCardso card-level HTML5 drag does not conflict with canvas pan/zoom
Position persistence
- Canonical base key:
fusion-plugin-dependency-graph:positions - Storage key format:
kb:${projectId}:fusion-plugin-dependency-graph:positions(falls back tofusion-plugin-dependency-graph:positionswhen no project is selected) - Read path: positions load on graph mount and whenever
projectIdchanges, then merge with fresh auto-layout so new tasks still receive layout defaults - Write path: positions persist on drag end only (not on every drag frame), filtered to currently visible tasks for stale cleanup
- Reset behavior: Fit to graph / Reset view clear persisted positions and re-apply auto-layout
- Implementation detail: the plugin now reuses dashboard
projectStoragehelpers (getScopedItem/setScopedItem/removeScopedItem) instead of duplicating scoped localStorage logic
Dependency chain highlighting
- Hover a node to highlight the full transitive upstream + downstream chain for that task.
- Click a node to persist selection highlighting until the same node is clicked again or the canvas pane is clicked; this same click also opens task detail once through the host detail callback.
- Priority: hover state overrides selected state; when hover leaves, selected highlighting reappears.
- Dimming: when a chain is active, unrelated nodes and edges are dimmed.
- Neutral state: when nothing is hovered/selected, no highlight/dim classes are applied.
- Drag suppression: drag movements above the node drag threshold suppress the post-drag click, preventing accidental detail opens on pointer release.
- Edge rule: an edge is highlighted only when both its source and target nodes are in the active chain.
Controls
Toolbar (bottom-right)
- Zoom in (
ZoomIn) — button +Ctrl+=/Cmd+= - Zoom out (
ZoomOut) — button +Ctrl+-/Cmd+- - Zoom percent label — live readout (for example
100%,75%,250%) - Fit to graph (
Maximize) — button +Ctrl+Shift+F/Cmd+Shift+F - Reset view (
RotateCcw) — button +Ctrl+0/Cmd+0
Additional keyboard behavior
- Escape resets to default view (
zoom=1,pan=0,0) - Shortcuts are suppressed when focus is inside
input,textarea,select, orcontentEditableelements
All controls are rendered as floating .btn-icon actions in the bottom-right corner, with mobile-friendly 44px touch targets in the @media (max-width: 768px) override.