AGENTS.md@packages/dashboard-plugin-runtime/src · git:20260827.41796b2 · 2026-08-27 · sha256 366f1b85203713dc

AGENTS.md@packages/dashboard-plugin-runtime/src git:20260827.41796b2A

Immutable. This exact content is served forever at /api/v1/blob/366f1b85203713dc.

# DOX — packages/dashboard-plugin-runtime/src

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `dependency-graph.ts` | Pure plugin-activation dependency graph. Exports `GraphNode`, `Graph`, `buildGraph`, `transitiveDependents`, `transitiveDependencies`, `computeToggleImpact` (returns `cascadeEnable`/`cascadeDisable`/`blockers`), `detectCycles`, `topologicalSort` (priority+id tier tiebreak, cyclic nodes appended last). Shared by server route handler + client cascade confirm dialog. |
| `index.ts` | Runtime barrel. Now also exports `getSessionEvents` (was internal). See change: add-goal-continuation-plugin. Exports `SlotPill`, `SlotPillProps`, `SlotAccent`. See change: redesign-directory-card. |
| `folder-menu-contributions.tsx` | Folder-actions-menu contribution registry — the sibling bridge from a pill-grid slot section to the folder HEADER's menu (callbacks are section-local closures, so a prop cannot carry them). Exports `FOLDER_MENU_GROUPS` (`workspace · directory · create · open · maintenance`), `FolderMenuGroup`, `isFolderMenuGroup`, `FolderMenuContribution` (`{id,group,label,icon,onSelect,badge?,disabled?}` — no `node`, no `pressed`), `RegisteredFolderMenuItem`, `isValidFolderMenuContribution` (5 required fields + known group; else DROPPED), `selectFolderMenuItems` (sort `pluginId` then `id`; cross-plugin `id` collision → lower `pluginId`, load-order independent), `createFolderMenuStore`/`FolderMenuStore`, `FolderMenuProvider` (+ module default store when unmounted), `useFolderMenuStore`, `useFolderMenuItem(scope, contribution)`, `useFolderMenuRefresher(scope, refresh)` (no item of its own), `useFolderMenuItems(scope)` (`useSyncExternalStore`, so an OPEN menu converges on a late registration), `useFolderMenuRefreshRunner()`. Store keyed `(pluginId, id)` — latest registration wins, dispose is identity-guarded so a superseded cleanup cannot evict the live entry; unmount deregisters. `pluginId` stamped from `useCurrentPluginId()`, never the payload. `scope` = folder cwd; `null` scope (e.g. `placement === "card"`) registers nothing. See change: move-slot-actions-to-menu. |
| `intent-renderer.tsx` | Shell-side React component. Walks `IntentNode` tree, resolves `primitive` name via `useUiPrimitiveOrNull`, recursively renders nested IntentNode props/arrays. Exports `isIntentNode`, `IntentActionSender`, `IntentRendererProps`, `UnknownPrimitive` (dashed-red placeholder), `IntentRenderer`. `wireActions` converts `ActionDescriptor` map into `send(action,payload)` handlers. |
| `intent-store.ts` | Client store for `plugin_intents` broadcasts. Exports `IntentKey`, `keyToString`, `IntentStoreEntry`, `IntentStore` class (Map keyed `pluginId|sessionId|slot`, memoized per-slot snapshots, `clearForSession`), module singleton `intentStore`, `useSlotIntents(slot,sessionId)` hook built on `useSyncExternalStore`. Stable `EMPTY_SLOT` fallback. |
| `manifest-validator.ts` | Hand-rolled manifest validator. Throws `ManifestValidationError` with `pluginId` + `reason`. No Zod dep. Validates optional `requires` shape: `piExtensions`/`binaries`/`services` each must be `string[]` when present. See change: add-plugin-activation-ui. Validates `shell-overlay-route` `presentation` is `"page"`\|`"dialog"` — FATAL on an unknown value (unlike `depth`, which warns and defaults) so a typo cannot silently restore the opted-out behaviour. See change: add-route-backed-overlay-dialogs. |
| `plugin-action-bridge.ts` | Client→server `plugin_action` bridge. Exports `PluginActionMessage`, `setSender(sender)` (called by `useWebSocket` on connect), `sendPluginAction(pluginId,sessionId,action,payload)` (no-op when disconnected). Lives in runtime to dodge circular import with client package. |
| `plugin-context.tsx` | `PluginContextProvider`, `CurrentPluginLayer`, `useCurrentPluginId()` (plugin identity for registration APIs; `null` outside a layer), `usePluginConfig<T>()`, `useAllSessions`, `useSessionState`, `usePluginLogger`, `usePluginSend`, `usePluginRouter`, `useSlotRegistry`, `applyPluginConfigUpdate`. Per-plugin context layer scopes hooks to contributing plugin's id. |
| `prompt-component-registry.ts` | PromptBus component registry. Built-in types `generic-dialog` (inline) + `architect-prompt` (widget-bar). Exports `PromptComponentInfo`, `getPromptComponentInfo` (falls back to generic-dialog), `registerPromptComponent` (idempotent under HMR), `isWidgetBarPrompt`, `useHasWidgetBarPrompt(sessionId)` reactive suppression hook. Moved from `packages/client/src/lib/`. |
| `session-data-store.ts` | Per-session client-side data store. Exports `subscribeSessionDataKey(key, cb)` — all-sessions-for-a-key subscription. Reset cleanup teardown. See change: add-flows-subcard. |
| `session-events-store.ts` | Module-level per-session `DashboardEvent` side-channel store for plugins. Exports `publishSessionEvent`, `publishSessionEvents` (batch, O(N) not O(N²)), `clearSessionEvents`, `getSessionEvents`, `subscribeSessionEvents` (stable frozen-array refs), `__resetSessionEventsStoreForTests`. Consumed by `useSessionEvents` hook in `plugin-context.tsx`. |
| `settings-draft-context.tsx` | Settings draft registry. `SettingsDraftProvider` + `useSettingsDraftSource({id,page,isDirty,commit,reset})`. Settings sections (built-in + plugin) buffer edits; host SettingsPanel commits dirty sources on unified Save. No-op without provider. See change: unify-settings-save-contract. |
| `shell-sessions-context.tsx` | React context exposing shell's `ReadonlyMap<string,DashboardSession>` to plugin claims. Exports `ShellSessionsValue`, `ShellSessionsProvider`, `useShellSession(sessionId)` (strict, throws outside provider), `useShellSessionOrNull(sessionId)` (soft). Used by `<ShellOverlayRouteSlot>` to resolve `DashboardSession` metadata by `config.sessionParam`. |
| `slot-consumers.tsx` | One component per slot id: `SidebarFolderSectionSlot`, `SessionCardBadgeSlot`, `SessionCardActionBarSlot`, `ContentViewSlot`, `ContentHeaderStickySlot`, `ContentInlineFooterSlot`, `AnchoredPopoverSlot`, `CommandRouteSlot`, `SettingsSectionSlot`, `ToolRendererSlot`. Each wraps contributions in `SlotErrorBoundary`. — Adds `SessionCardMemorySlot` + `WorkspaceActionBarSlot` consumers and `useSlotHasClaimsForSession(slotId, session)` hook for parent containers to conditionally render. See change: redesign-session-card-subcards. `useSlotHasClaimsForSession` + session-scoped slot consumers consult `shouldRender` via `forSessionRendered`. See change: auto-hide-empty-session-subcards. Adds `SessionCardFlowsSlot({session})` mirroring `SessionCardMemorySlot` (legacy + intent). See change: add-flows-subcard. Adds `SettingsSectionByPluginSlot({pluginId})` rendering settings-section claims scoped to a single plugin id; consumed by `PluginSettingsHost`. See change: add-plugin-activation-ui. `ToolRendererSlot` accepts + forwards expanded optional props (`status`, `result`, `toolDetails`, `images`, `context`) to claim Component and `FallbackComponent`. See change: wire-tool-renderer-slot. Adds `WorktreeCardSectionSlot({folder})` mirroring `SidebarFolderSectionSlot` for the `worktree-card-section` slot; renders folder-scoped claims inside a worktree session card scoped to the worktree cwd. See change: kb-row-on-worktree-session-card. `ShellOverlayRouteSlot` renders the matched claim body inside a `flex-1 min-h-0 relative` height wrapper and nothing else — no dialog chrome of its own. Container choice belongs to the HOST: it reads the claim's effective `presentation` (default `"dialog"`, `"page"` opts out) via the exported `useShellOverlayRoutePresentation` hook and lifts a dialog claim out of its content region, because the overlay underlay must cover the VIEWPORT and cannot be positioned from inside this slot. An earlier `dialogContainer` injection prop was removed for that reason (design D2a); a hook returning a string also avoids the `client-utils` → `dashboard-plugin-runtime` cycle a component import would close. See change: add-route-backed-overlay-dialogs. Adds `ComposerPanelSlot({draft,language?,sessionId?,sessionStatus?,onApplyText})` — renders `composer-panel` claims below the composer input, forwarding the read-only draft context + a bounded `onApplyText` draft-write; null when unclaimed. See change: make-grammar-fully-plugin-contained. |
| `SlotPill.tsx` | Shared single-concern directory-card slot pill. Exports `SlotPill`, `SlotPillProps`, `SlotAccent`, `SlotSurface`. Glyph + uppercase micro-label + bold count (children) + optional inline state; whole pill = one nav click target (`role="button"`, Enter/Space) that stopPropagation. Static accent map (blue/indigo/cyan/teal/purple/red). `surface`: `raised` = `--bg-secondary` + shadow; `flat` = NO fill, NO shadow (border only) for session-card placement. Capsule legend fill follows surface via `LEGEND_BG` (`--bg-tertiary` raised / `--bg-primary` flat) so it masks the border line. Home per design D1 so all four `Folder*Section`s share one source. STATE-ONLY: `actions?: ReactNode` REMOVED — compile-time break, no runtime shim; slot actions are `folder-menu-contributions.tsx` items. Fact markers (KB `⚠ N stale`) stay as children. See change: redesign-directory-card. See change: move-slot-actions-to-menu. |
| `slot-error-boundary.tsx` | Per-claim React error boundary. Logs `[slot-error-boundary] Plugin "<id>" slot "<slot>" threw:` + renders nothing for failing claim without suppressing siblings. |
| `slot-registry.ts` | `createSlotRegistry()` — typed `Map<SlotId, ClaimEntry[]>` pre-sorted by `(priority, pluginId)`. Filter helpers: `forSession`, `forFolder`, `forCommand`, `forToolName`. `ClaimEntry.shouldRender?: (props) => boolean` plus `forSessionRendered(claims, session)` apply predicate AND shouldRender. See change: auto-hide-empty-session-subcards. `ClaimEntry<S extends SlotId>` generic; `predicate?`/`shouldRender?` method-shorthand (bivariant). See change: slot-generic-claim-entry. Adds `setEnabledSet(ids: Set<string>)` + internal enabled-filter in `getClaims`/`getAllClaims` so disabled plugins contribute zero claims at runtime. Adds `getAllPluginsForActivationUi()` returning every registered plugin (enabled or not) for `PluginsSection` list. Driven from client by `usePluginEnabledSet`. See change: add-plugin-activation-ui. Adds `isPluginEnabled(id)` (true until the first `setEnabledSet`, else set membership) so consumers can gate NON-claim contributions — the internal filter covers claims only. `forTab` DELETED with its last caller: `settings-section` claims no longer route by `tab`. See change: reorganize-settings-into-pages, plugin-settings-pages. |
| `ui-primitive-context.tsx` | React context provider + lookup hooks for UI primitives. Exports `UiPrimitiveProvider`, `useUiPrimitive<K>(key)` (strict, throws if unregistered or outside provider), `useUiPrimitiveOrNull<K>(key)` (soft, null fallback). Both `@deprecated` for direct plugin use — plugins should emit server-side intents; hooks remain canonical resolver for shell's `IntentRenderer`. |
| `ui-primitive-registry.ts` | Runtime data structure for UI primitives. Exports `UiPrimitiveRegistry` (opaque, private `_impls` map), `createUiPrimitiveRegistry()`, `registerUiPrimitive(reg,key,impl)` (throws on double-registration, first-write-wins), `getUiPrimitiveImpl` (internal lookup, returns `undefined` if missing). Pairs with `ui-primitive-context.tsx`. |