/** * defineToolsDock - factory for the ToolsDock API * * Creates a reactive {@link ToolsDockApi} instance backed by Svelte 5 runes * (`$state`) and registers it in Svelte context under {@link TOOLS_DOCK_KEY}. * Inside a `` provider tree, descendants (tools, deep panels, * arbitrary consumers) read the same instance via {@link useToolsDock}. * * The dock is intentionally domain-agnostic: * - Tool IDs are arbitrary strings (no enum). * - Availability is controlled via the optional `fetchAvailability` callback; * when omitted, every registered tool is available. * - Persistence is opt-in via `storageKey` and is SSR-safe. * - Inter-tool messaging happens through a typed pub/sub bus (`emit`/`on`) * in lieu of `window.dispatchEvent` patterns from older portals. * * @example Register a dock and a tool * ```ts * // somewhere in a +layout.svelte: * import { defineToolsDock } from '@happyvertical/smrt-svelte/workspace/legacy'; * import ChatTool from './ChatTool.svelte'; * * const dock = defineToolsDock({ * tools: [{ id: 'chat', label: 'Chat', component: ChatTool }], * storageKey: 'app-name:tools-dock:v1', * }); * ``` * * @remarks ModuleUIRegistry integration point * * Consumer apps that want tools to come from `@happyvertical/smrt-*` module * packages (commerce, content, etc.) can populate `options.tools` from the * `ModuleUIRegistry` (see {@link ../../../registry/module-registry}) — e.g. * by walking `ModuleUIRegistry.getModules()` and pulling components from a * conventional slot id like `'tools-dock'`. This is left to consumers so the * dock has zero coupling to the registry; the surface area exposed here * (`ToolDef[]`) is what such an adapter would produce. */ import type { AvailableTool, ToolDef, ToolsDockApi, ToolsDockContext } from '../types.js'; /** * Svelte context key for the active ToolsDock API instance. */ export declare const TOOLS_DOCK_KEY: unique symbol; /** * Options accepted by {@link defineToolsDock}. * * `TData` types the shape of `context.data` passed through `setContext()` / * `fetchAvailability` — narrow it at the factory site to get a typed * `ctx.data` inside the availability callback without manual casts. * Defaults to `Record` so existing callers keep compiling. * * `TActions` types the shape of `context.actions` and flows the same way. * Defaults to `Record unknown>` so the untyped * `dock.setContext({ actions: { triggerSave() {} } })` pattern keeps * compiling without a generic argument. The constraint is a self-mapped * `{ [K in keyof TActions]: (...args: never[]) => unknown }` so interface-style * action maps (without a string index signature) satisfy the bound — * see the JSDoc on {@link ToolsDockContext} for rationale. * * Tools themselves are stored as `ToolDef[]` (the generics are erased at * registration). Inside a tool component, type the prop locally — see the * JSDoc on {@link ToolDef.component} for the recommended pattern. */ export interface DefineToolsDockOptions, TActions extends { [K in keyof TActions]: (...args: never[]) => unknown; } = Record unknown>> { /** Registered tools. Order is preserved when rendering the activation rail/topbar. */ tools: ToolDef[]; /** * Optional backend-driven presentation-gating callback. Returns the subset * of tools that should be exposed for the current `context`. When omitted, * every registered tool is treated as available. * * This is not an authorization boundary. Failures intentionally keep the * dock usable, so every tool operation and server endpoint must enforce its * own permissions independently. * * The callback may return tool ids that aren't present in `tools` — these * are ignored. Conversely, tools missing from the callback's response are * hidden from the dock UI. * * A thrown error, rejected promise, or malformed response preserves the * last-known-good availability for the current context and is exposed * through `dock.availabilityError`. Before the first success, and whenever * context changes, the fallback is the registered tool set with registered * labels and badges. A later valid response clears the error and replaces * availability normally. * * The `ctx` parameter is typed against the factory's `TData` / `TActions` * generics — narrow them at the factory site for typed access to * `ctx.data` / `ctx.actions` here without manual casts. */ fetchAvailability?: (ctx: ToolsDockContext | null) => Promise; /** * Optional `localStorage` key. When provided, the dock will persist a small * `{ isOpen, activeTool }` blob and hydrate it on mount. Persistence is * SSR-safe — no `localStorage` access happens at module scope or during * initial render on the server. */ storageKey?: string; /** * Activation UI layout. Defaults to `'rail'`. * * - `'rail'`: vertical icon rail with the panel contained inside the * dock's own aside. Safe to compose alongside ``'s * `inspector` snippet — they don't fight for positioning. * - `'topbar'`: inline activation buttons with a SEPARATE * `position: fixed` panel anchored to the bottom-right of the * viewport. **Do not also use ``'s `inspector` * snippet in this mode** — the shell renders its own * `position: fixed` inspector with no z-index coordination, and the * two panels will visibly overlap. Render `` * inside the shell's `topbarActions` snippet and leave the inspector * slot unused. */ layout?: 'rail' | 'topbar'; /** Initial open state. Hydration from storage takes precedence. Defaults to `false`. */ initialOpen?: boolean; } /** * Internal extension of {@link ToolsDockApi} with framework-only members: * the registered tools, the configured layout, and the persistence key. * * Mirrors the same `` generics as {@link ToolsDockApi} so * the narrowed `context` / `setContext()` signatures from * `defineToolsDock(...)` flow through to consumers that * hold the returned instance directly. Defaults preserve back-compat for * untyped callers (`defineToolsDock({...})`). */ export interface ToolsDockInstance, TActions extends { [K in keyof TActions]: (...args: never[]) => unknown; } = Record unknown>> extends ToolsDockApi { readonly tools: ReadonlyArray; readonly layout: 'rail' | 'topbar'; readonly storageKey: string | null; /** Internal: hydrate persisted state from `localStorage` (called by `` on mount). */ hydrate(): void; /** Internal: persist current state to `localStorage`. */ persist(): void; } /** * Create a tools-dock instance and register it on Svelte context. * * Must be called inside a Svelte component initialization scope (`