# 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 key `plugin: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-view` is aliased to `plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx` (source, not `dist/`). This avoids stale dist artifacts breaking the dashboard view.
- The App.tsx graph route resolves to the bundled view via `isPluginViewRegistered` fallback, 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`; excludes `done`, `archived`
- **Graph build**: edges are resolved only from `task.dependencies` as `source=dependent`, `target=dependency`
- **Orphan dependency handling**: if a visible task depends on an excluded/missing dependency (for example `done`/`archived` after 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 > width` or `width < 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 `TaskCard` via `GraphTaskNode` (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 example `Executing`, `Planning`) and pulsing `--in-progress` emphasis
- **Current-step highlighting**: active nodes set `data-current-step` for valid native step indices so CSS selectors highlight the currently executing `.card-step-item` and pulse its step dot
- **Zoom-out differentiation**: `.graph-task-node--active` adds amplified glow and subtle scale/border tint so active nodes remain distinguishable at reduced zoom levels
- **In-review visual treatment**: `in-review` nodes get a static `.graph-task-node--in-review` left accent in `--in-review` to 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--dimmed` are available for graph-specific layering/highlight states while card internals remain owned by `TaskCard.css`
- **Graph edge classes**: `.graph-edge--highlighted` and `.graph-edge--dimmed` are applied during dependency-chain emphasis states
- **Drag behavior**: graph nodes pass `disableDrag={true}` to `TaskCard` so 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 to `fusion-plugin-dependency-graph:positions` when no project is selected)
- Read path: positions load on graph mount and whenever `projectId` changes, 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 `projectStorage` helpers (`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`, or `contentEditable` elements

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.
