# Overview # Overview Hudson is a **multi-app canvas workspace platform** built with React 19, Next.js 16, and Tailwind CSS v4. It provides a shared shell where multiple applications coexist — each rendering in draggable, resizable windows on an infinite pan/zoom canvas, or in a static panel layout. Think of it as a desktop environment in the browser: apps register themselves, the shell provides chrome (navigation bar, side panels, command palette, status bar), and each app just focuses on its own UI and state. ## Minimal Example A Hudson app is a plain object satisfying the `HudsonApp` interface: ```tsx import type { HudsonApp } from '@hudson/sdk'; export const counterApp: HudsonApp = { id: 'counter', name: 'Counter', mode: 'panel', Provider: ({ children }) => {children}, slots: { Content: () =>
{count}
}, hooks: { useCommands: () => [{ id: 'counter:reset', label: 'Reset', action: () => setCount(0) }], useStatus: () => ({ label: 'OK', color: 'emerald' }), }, }; ``` Register it in a workspace and it immediately gets panels, command palette, status bar, and canvas windowing — all for free. See the [Quickstart](./quickstart.md) for a complete walkthrough. ## Architecture Hudson has three layers: ### 1. Frame UI (`packages/@hudson/sdk`) The component library and type system. Provides: - **Chrome components** — Frame, NavigationBar, SidePanel, StatusBar, CommandDock, ZoomControls - **Canvas system** — Pan/zoom engine with space-bar gestures - **Window system** — AppWindow with dragging, resizing, maximize/restore - **Overlays** — CommandPalette (Cmd+K), TerminalDrawer, HudsonContextMenu - **Type contracts** — `HudsonApp`, `HudsonWorkspace`, `AppIntent` - **Utilities** — Web Audio sounds, persistent state, viewport math ### 2. Shell (`app/shell/`) The runtime orchestrator. `WorkspaceShell` is the main entry point that: - Nests all app Providers recursively - Calls each app's hooks inside the correct context scope - Renders app slots into shell chrome (panels, content area, terminal) - Manages workspace switching, boot animations, and canvas state - Provides the command palette, merging commands from all active apps ### 3. Apps (`app/apps/`) Self-contained applications that implement the `HudsonApp` interface. Each app provides: - A **Provider** (React context) that owns all app state - **Slot components** (Content, LeftPanel, RightPanel, Terminal) rendered by the shell - **Hooks** that bridge app state into shell chrome (commands, status, search, nav) - Optional **intents** for LLM/voice/search indexing ## Key Concepts ### Workspaces A workspace is a collection of apps that coexist in a shared shell. Each workspace defines: - A **mode** (`canvas` or `panel`) — the global layout strategy - A list of **apps** with canvas participation settings - A **default focused app** Hudson supports multiple workspaces. Users switch between them at runtime. ### Frame Modes Apps can render in two modes: | Mode | Behavior | Use Case | |------|----------|----------| | `canvas` | Infinite pan/zoom world space | Editors, graph UIs, spatial tools | | `panel` | Static scrollable viewport | Dashboards, admin interfaces, docs | Individual apps can override the workspace-level mode via the `useLayoutMode` hook. ### Canvas Participation In canvas-mode workspaces, each app chooses how it appears: | Participation | Behavior | Example | |---------------|----------|---------| | `native` | Renders directly on canvas, no window frame | Hudson Docs | | `windowed` | Renders inside AppWindow with title bar, dragging, resizing | Shaper, Intent Explorer | ### Intent System Hudson includes an intent catalog for LLM/voice integration. Apps declare intents — structured metadata about their commands — which are indexed into a searchable catalog. An execution bridge maps intent `commandId` values to live command actions. ## Current Apps | App | Description | Mode | |-----|-------------|------| | **Shaper** | Bezier curve editor for vector shapes | Panel (overrides to canvas) | | **Hudson Docs** | Documentation browser | Canvas native | | **Intent Explorer** | Browsable intent catalog inspector | Canvas windowed | ## Next Steps - [Quickstart](./quickstart.md) — Get Hudson running locally and create your first app - [Building Apps](./building-apps.md) — Full integration guide (Provider, slots, hooks, intents, workspaces) - [API Reference](./api.md) — Complete reference for @hudson/sdk exports ## Tech Stack | Layer | Technology | |-------|-----------| | Framework | Next.js 16 (App Router) | | UI | React 19 | | Styling | Tailwind CSS v4 | | Icons | lucide-react | | Fonts | SF Rounded (UI), JetBrains Mono (monospace) | | Package manager | bun | | Dev server | Port 3500 | --- # Quickstart # Quickstart Get Hudson running locally and create your first app. ## Prerequisites - [Node.js](https://nodejs.org) >= 18 - [bun](https://bun.sh) (package manager and runtime) - git ## Setup ```bash # Clone the repository git clone https://github.com/arach/hudson.git cd hudson # Install dependencies bun install # Start the dev server (port 3500) bun dev ``` Open [http://localhost:3500](http://localhost:3500). You should see the Hudson workspace with the boot animation, then the canvas with existing apps. ## Project Structure ``` hudson/ app/ page.tsx # Entry point — mounts WorkspaceShell shell/ # Shell components (WorkspaceShell, HomeScreen, BootSplash) apps/ # App implementations shaper/ # Reference app — bezier curve editor hudson-docs/ # Docs browser intent-explorer/ # Intent catalog inspector workspaces/ # Workspace definitions hudsonOS.ts # Multi-app canvas workspace shaperDev.ts # Single-app panel workspace index.ts # Exports lib/ # Shared utilities (intent catalog, etc.) hooks/ # Shared hooks (intent executor, etc.) packages/ @hudson/sdk/ # Component library + types src/ components/ # Chrome, Canvas, Windows, Overlays types/ # HudsonApp, HudsonWorkspace, AppIntent hooks/ # usePersistentState lib/ # sounds, logger, viewport, chrome tokens ``` ## Create Your First App The fastest way to create an app is with the scaffolding CLI: ```bash bun run packages/create-hudson-app/src/index.ts my-app ``` This prompts for a description, tier, and mode, then generates all the files you need. See [Scaffolding](./scaffolding.md) for the full guide. For this quickstart, we'll use the **minimal** tier: ```bash bun run packages/create-hudson-app/src/index.ts my-app \ --tier minimal --mode panel --description "A counter app" ``` This generates 5 files + a dev workspace: ``` app/apps/my-app/ index.ts # HudsonApp declaration MyAppProvider.tsx # React context (owns state) MyAppContent.tsx # Main content slot hooks.ts # useCommands + useStatus types.ts # Type skeleton app/workspaces/myAppDev.ts # Dev workspace ``` ### Register the workspace Open `app/page.tsx` and add your workspace: ```tsx import { myAppDevWorkspace } from './workspaces/myAppDev'; // Add to the workspaces array: ``` ### Start building The scaffolded app renders a centered placeholder. Start editing: 1. **`MyAppProvider.tsx`** — Add your state (e.g. `count`, `increment`) 2. **`MyAppContent.tsx`** — Build your UI using that state 3. **`hooks.ts`** — Wire commands for the palette, update the status label The `HudsonApp` interface in `@hudson/sdk` enforces the contract — TypeScript will tell you if you're missing a required slot or hook. Save, run `bun dev`, and your app appears in the workspace switcher. See [Building Apps](./building-apps.md) for the full guide. --- # Scaffolding # Scaffolding `create-hudson-app` generates the boilerplate for a new Hudson app module. Hudson apps are declarative — you define a config object that satisfies the `HudsonApp` interface, and the shell handles all chrome, layout, and lifecycle. The CLI generates the files that fulfill that contract so you can skip straight to building your app's logic. ## Usage ```bash # Interactive — prompts for description, tier, mode bun run packages/create-hudson-app/src/index.ts my-browser # Non-interactive bun run packages/create-hudson-app/src/index.ts my-browser \ --tier standard --mode panel --description "A web browser" ``` ### Options | Flag | Values | Default | |------|--------|---------| | `--tier` | `minimal`, `standard`, `full` | prompted | | `--mode` | `panel`, `canvas` | `panel` | | `--description` | any string | prompted | | `--no-workspace` | — | generates workspace | | `-h`, `--help` | — | show help | ## Tiers Pick based on how many shell integration points your app needs. You can always promote a minimal app to standard later by adding slots and hooks — the `HudsonApp` interface will tell you what's missing. ### Minimal (5 files) Provider, Content slot, and 2 required hooks. Use this for simple single-pane apps. ```bash bun run packages/create-hudson-app/src/index.ts my-app --tier minimal ``` | File | Purpose | |------|---------| | `index.ts` | `HudsonApp` declaration with Content slot | | `Provider.tsx` | Context with `selectedItem` state | | `hooks.ts` | `useCommands` (empty), `useStatus` (READY) | | `Content.tsx` | Centered placeholder | | `types.ts` | Type skeleton | ### Standard (8 files) Adds sidebar, inspector, search, and intents. Use this for apps that need navigation and inspection. ```bash bun run packages/create-hudson-app/src/index.ts my-app --tier standard ``` Adds to minimal: | File | Purpose | |------|---------| | `LeftPanel.tsx` | Sidebar with placeholder item list | | `Inspector.tsx` | Right panel showing selected item | | `intents.ts` | Empty array with commented example | Also upgrades `index.ts` (adds panel config, `useSearch`/`useLayoutMode` hooks) and `Provider.tsx` (adds `searchQuery` state). ### Full (12 files) Everything — terminal drawer, footer, header actions, tools accordion, manifest. Use this for complex editor-style apps. ```bash bun run packages/create-hudson-app/src/index.ts my-app --tier full ``` Adds to standard: | File | Purpose | |------|---------| | `LeftFooter.tsx` | Footer widget below the sidebar | | `Terminal.tsx` | Terminal drawer with tab UI | | `HeaderActions.tsx` | Three-dot menu in left panel header | | `tools/SampleTool.tsx` | Example tool for the right sidebar accordion | Also upgrades `index.ts` (adds manifest, tools array, all 7 hooks) and `Provider.tsx` (adds `openSections`, `toggleSection`, `devMode`). ## The generated contract The core of every scaffolded app is a declarative export in `index.ts`: ```ts export const myBrowserApp: HudsonApp = { id: 'my-browser', name: 'My Browser', mode: 'panel', Provider: MyBrowserProvider, slots: { Content, LeftPanel, Inspector }, hooks: { useCommands, useStatus, useSearch, useLayoutMode }, }; ``` Your app never touches shell chrome. The shell reads this declaration and wires everything — sidebar, inspector, status bar, command palette, nav bar. ## After scaffolding 1. Import the generated workspace in `app/page.tsx`: ```ts import { myBrowserDevWorkspace } from './workspaces/myBrowserDev'; ``` 2. Add it to the workspaces array 3. `bun dev` 4. Open `http://localhost:3500` — your app appears in the workspace switcher ## Where to start editing 1. **`types.ts`** — Define your domain types 2. **`Provider.tsx`** — Add state, callbacks, data fetching to the context 3. **`Content.tsx`** / **`LeftPanel.tsx`** — Build your UI 4. **`hooks.ts`** — Return real commands from `useCommands`, real status from `useStatus` 5. **`intents.ts`** — Declare intents for command palette and LLM indexing ## Template variables For contributors modifying templates in `packages/create-hudson-app/templates/`: | Placeholder | Example (`my-browser`) | |---|---| | `__APP_NAME__` | `MyBrowser` | | `__APP_ID__` | `my-browser` | | `__APP_VAR__` | `myBrowser` | | `__APP_DISPLAY_NAME__` | `My Browser` | | `__APP_DESCRIPTION__` | user input | | `__APP_MODE__` | `panel` or `canvas` | | `__APP_ICON__` | auto-suggested Lucide icon | Templates are plain `.tsx.tmpl` / `.ts.tmpl` files with global string replacement — no special syntax, no AST transforms. ## Further reading - [Quickstart](./quickstart.md) — Get running and create a minimal app - [Building Apps](./building-apps.md) — Full guide to the Provider + Slots + Hooks architecture - [API Reference](./api.md) — Complete reference for `@hudson/sdk` --- # Building Apps # Building Apps This guide covers everything you need to build a Hudson app — from the interface contract to workspace registration. ## The HudsonApp Interface Every app implements the `HudsonApp` interface exported from `@hudson/sdk`: ```typescript import type { HudsonApp } from '@hudson/sdk'; ``` ### Full Interface ```typescript interface HudsonApp { // Identity id: string; // Unique ID (key + localStorage namespace) name: string; // Display name in app switcher description?: string; // Tooltip / palette description mode: 'canvas' | 'panel'; // Default frame mode // Panel configuration (optional) leftPanel?: { title: string; icon?: ReactNode; headerActions?: React.FC; // Rendered in left panel header }; rightPanel?: { // @deprecated — use Inspector + tools instead title: string; icon?: ReactNode; }; // State owner Provider: React.FC<{ children: ReactNode }>; // UI slots rendered by the shell slots: { Content: React.FC; // Main content area (required) LeftPanel?: React.FC; // Left sidebar content RightPanel?: React.FC; // @deprecated — use Inspector + tools instead LeftFooter?: React.FC; // Footer of left panel Terminal?: React.FC; // Terminal drawer content }; // Intent declarations (optional) intents?: AppIntent[]; // Hooks called inside Provider scope hooks: { useCommands: () => CommandOption[]; // Required useStatus: () => { label: string; color: StatusColor }; // Required useSearch?: () => SearchConfig; useNavCenter?: () => ReactNode | null; useNavActions?: () => ReactNode | null; useLayoutMode?: () => 'canvas' | 'panel'; }; } ``` ### Required vs Optional | Field | Required | Purpose | |-------|----------|---------| | `id`, `name`, `mode` | Yes | Identity and default layout | | `Provider` | Yes | Wraps all slots, owns state | | `slots.Content` | Yes | Main UI | | `hooks.useCommands` | Yes | Commands for palette (can return `[]`) | | `hooks.useStatus` | Yes | Status bar label and color | | `leftPanel`, `rightPanel` (deprecated) | No | Panel header config. `rightPanel` is deprecated — use `Inspector` + `tools` instead | | `slots.LeftPanel`, `RightPanel` (deprecated), `LeftFooter`, `Terminal` | No | Additional UI slots. `RightPanel` is deprecated — use `Inspector` + `tools` instead | | `hooks.useSearch`, `useNavCenter`, `useNavActions`, `useLayoutMode` | No | Nav bar integration | | `intents` | No | LLM/voice/search declarations | ## Architecture Pattern Hudson uses a **Provider + Slots + Hooks** architecture: ``` WorkspaceShell └── App.Provider ← Your context wraps everything ├── slots.Content ← Rendered in main area ├── slots.LeftPanel ← Rendered in left SidePanel ├── slots.RightPanel ← (deprecated) Rendered in right SidePanel — use Inspector + tools ├── slots.Terminal ← Rendered in TerminalDrawer └── hooks.* ← Called via Bridge component inside Provider ``` The shell nests Providers recursively for all apps in the workspace: ```typescript // Inside WorkspaceShell let tree = ; for (const { app } of workspace.apps.reverse()) { tree = {tree}; } ``` This means every app's hooks and slots have access to every app's context. However, apps should only access their own context — cross-app communication goes through the shell. ## Provider Pattern The Provider owns all app state via React context: ```tsx 'use client'; import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'; interface GlyphEditorState { // View state view: 'overview' | 'editor'; setView: (v: 'overview' | 'editor') => void; // Data state selectedGlyphId: string | null; selectGlyph: (id: string) => void; // Tool state activeTool: 'select' | 'pen' | 'eraser'; setTool: (t: 'select' | 'pen' | 'eraser') => void; } const Ctx = createContext(null); export function useGlyphEditor() { const ctx = useContext(Ctx); if (!ctx) throw new Error('useGlyphEditor must be inside GlyphEditorProvider'); return ctx; } export function GlyphEditorProvider({ children }: { children: ReactNode }) { const [view, setView] = useState<'overview' | 'editor'>('overview'); const [selectedGlyphId, setSelectedGlyphId] = useState(null); const [activeTool, setTool] = useState<'select' | 'pen' | 'eraser'>('select'); const selectGlyph = useCallback((id: string) => { setSelectedGlyphId(id); setView('editor'); }, []); return ( {children} ); } ``` ## Slot Components Slots are plain React components that use your app's context: ### Content (required) The main content area. In canvas mode, this renders in world space. In panel mode, it fills the viewport between the panels. ```tsx 'use client'; import { useGlyphEditor } from './GlyphEditorProvider'; import { GlyphOverview } from './components/GlyphOverview'; import { GlyphCanvas } from './components/GlyphCanvas'; export function GlyphEditorContent() { const { view } = useGlyphEditor(); return view === 'overview' ? : ; } ``` ### LeftPanel Rendered inside the left SidePanel. Good for navigation, project trees, tool palettes. ```tsx 'use client'; import { useGlyphEditor } from './GlyphEditorProvider'; export function GlyphEditorLeftPanel() { const { selectGlyph } = useGlyphEditor(); return (
{glyphs.map(g => ( ))}
); } ``` ### RightPanel (deprecated) > **Deprecated.** `RightPanel` is deprecated. Use `Inspector` combined with `tools` instead. The `Inspector` slot provides a structured way to display properties and metadata, while `tools` allows apps to register tool panels that appear in the right sidebar. See the Shaper app's `ShaperInspector.tsx` and `tools/` directory for a reference implementation. Previously used for inspector, properties, and metadata. Rendered inside the right SidePanel. ### LeftFooter Rendered at the bottom of the left panel. Shaper uses this for a minimap preview. ### Terminal Rendered inside the TerminalDrawer (toggled via Cmd+`). Good for logs, REPL, debug output. ## Hooks Hooks bridge your app state into the shell chrome. They are called inside your Provider's scope. ### useCommands (required) Return an array of `CommandOption` objects. These appear in the command palette (Cmd+K). ```tsx import { useMemo } from 'react'; import type { CommandOption } from '@hudson/sdk'; import { useGlyphEditor } from './GlyphEditorProvider'; export function useGlyphCommands(): CommandOption[] { const { setView, setTool, view } = useGlyphEditor(); return useMemo(() => [ { id: 'glyph:overview', label: 'Show Glyph Overview', action: () => setView('overview'), shortcut: 'Cmd+1', }, { id: 'glyph:editor', label: 'Open Glyph Editor', action: () => setView('editor'), shortcut: 'Cmd+2', }, { id: 'glyph:pen-tool', label: 'Pen Tool', action: () => setTool('pen'), shortcut: 'P', }, ], [setView, setTool, view]); } ``` ### useStatus (required) Return a label and color for the status bar. ```tsx export function useGlyphStatus() { const { view, activeTool } = useGlyphEditor(); if (view === 'editor') return { label: activeTool.toUpperCase(), color: 'emerald' as const }; return { label: 'OVERVIEW', color: 'neutral' as const }; } ``` Valid colors: `'emerald'`, `'amber'`, `'red'`, `'neutral'`. ### useSearch (optional) Provides a search bar in the navigation bar. ```tsx export function useGlyphSearch() { const [query, setQuery] = useState(''); return { value: query, onChange: setQuery, placeholder: 'Search glyphs...' }; } ``` ### useNavCenter (optional) Returns content rendered in the center of the navigation bar (between left/right actions). ### useNavActions (optional) Returns content rendered on the right side of the navigation bar. Good for action buttons. ### useLayoutMode (optional) Overrides the workspace-level mode for this app. Useful when an app needs canvas mode even in a panel workspace, or vice versa. ```tsx export function useGlyphLayoutMode(): 'canvas' | 'panel' { const { view } = useGlyphEditor(); return view === 'editor' ? 'canvas' : 'panel'; } ``` ## Intents Intents declare structured metadata about your commands for LLM/voice/search integration. ```typescript import type { AppIntent } from '@hudson/sdk'; export const glyphIntents: AppIntent[] = [ { commandId: 'glyph:pen-tool', // Must match a CommandOption.id title: 'Switch to Pen Tool', description: 'Activate the pen tool for drawing bezier paths', category: 'tool', keywords: ['pen', 'draw', 'bezier', 'path', 'curve'], shortcut: 'P', }, { commandId: 'glyph:export', title: 'Export Glyph', description: 'Export the current glyph as SVG', category: 'file', keywords: ['export', 'save', 'svg', 'download'], dangerous: true, // Requires confirmation params: [ { name: 'format', description: 'Export format', type: 'string', enum: ['svg', 'png'], default: 'svg' }, ], }, ]; ``` ### Intent Categories | Category | Use Case | |----------|----------| | `tool` | Tool switching (pen, select, eraser) | | `edit` | Data mutations (delete, duplicate, transform) | | `file` | I/O operations (save, export, import) | | `view` | View changes (zoom, pan, fit) | | `navigation` | Navigation (go to glyph, switch view) | | `toggle` | Boolean toggles (grid, snap, rulers) | | `workspace` | Workspace-level actions | | `settings` | Preference changes | ### Execution Bridge The shell automatically bridges intents to commands. When an intent is executed (via LLM, voice, or the Intent Explorer), the shell looks up the matching `commandId` in your `useCommands()` output and calls its `action()`. ## Workspace Registration ### Add to an existing workspace ```typescript // app/workspaces/hudsonOS.ts import { glyphEditorApp } from '../apps/glyph-editor'; export const hudsonOSWorkspace: HudsonWorkspace = { id: 'hudson-os', name: 'Hudson OS', mode: 'canvas', apps: [ // ... existing apps { app: glyphEditorApp, canvasMode: 'windowed', defaultWindowBounds: { x: -300, y: -200, w: 700, h: 500 }, }, ], }; ``` ### Create a standalone workspace ```typescript // app/workspaces/glyphDev.ts import type { HudsonWorkspace } from '@hudson/sdk'; import { glyphEditorApp } from '../apps/glyph-editor'; export const glyphDevWorkspace: HudsonWorkspace = { id: 'glyph-dev', name: 'Glyph Editor', description: 'Standalone glyph editing workspace', mode: 'panel', apps: [{ app: glyphEditorApp }], }; ``` ### Register the workspace ```typescript // app/page.tsx import { glyphDevWorkspace } from './workspaces/glyphDev'; export default function Page() { return ( ); } ``` ## Canvas vs Panel Mode ### Canvas mode (`mode: 'canvas'`) - Content renders in world space (infinite pan/zoom) - Mouse wheel zooms, space+drag pans - Option+drag on windows to move them - Window bounds persisted to localStorage - Best for: editors, spatial tools, graph UIs ### Panel mode (`mode: 'panel'`) - Content renders in viewport space (static, scrollable) - No pan/zoom controls - Full-width layout between side panels - Best for: dashboards, admin interfaces, documentation Apps can dynamically switch modes using `useLayoutMode()`. ## Persistent State Use `usePersistentState` from @hudson/sdk for state that survives page reloads: ```tsx import { usePersistentState } from '@hudson/sdk'; function MyComponent() { const [gridVisible, setGridVisible] = usePersistentState('my-app.grid', true); // Backed by localStorage with key 'my-app.grid' } ``` ## Sounds Hudson includes a Web Audio synthesizer for UI feedback: ```tsx import { sounds } from '@hudson/sdk'; // Available sounds sounds.blipUp(); // Positive feedback sounds.click(); // Button press sounds.whoosh(); // Transitions sounds.thock(); // Heavy press ``` ## File Structure Convention ``` app/apps/my-app/ index.ts # App definition (exports HudsonApp) MyAppProvider.tsx # Context provider hooks.ts # Hook implementations intents.ts # Intent declarations MyAppContent.tsx # Content slot MyAppLeftPanel.tsx # Left panel slot MyAppRightPanel.tsx # Right panel slot (deprecated — use Inspector + tools) MyAppInspector.tsx # Inspector slot (replaces RightPanel) tools/ # Tool panel implementations MyAppTerminal.tsx # Terminal slot components/ # App-specific components ComponentA.tsx ComponentB.tsx ``` ## Reference Implementation The **Shaper** app (`app/apps/shaper/`) is the most complete reference: - Full Provider with complex state (tools, shapes, layers, selections) - All 5 slot components implemented - 6 hooks bridging state to shell chrome - 25+ intents for LLM integration - Dynamic frame mode switching (panel default, canvas when editing) - Header actions in the left panel The **Intent Explorer** (`app/apps/intent-explorer/`) is a simpler example if you want a minimal starting point. ## Further Reading - [Overview](./overview.md) — Architecture and key concepts - [Quickstart](./quickstart.md) — Get running and create a minimal app - [Scaffolding](./scaffolding.md) — Generate apps with `create-hudson-app` - [API Reference](./api.md) — Complete reference for all @hudson/sdk exports --- # API Reference # API Reference Everything exported from the `@hudson/sdk` package. ```tsx import { Frame, NavigationBar, SidePanel, ... } from '@hudson/sdk'; ``` ## Types ### HudsonApp The core interface every app must implement. See [Building Apps](./building-apps.md) for full details. ```typescript import type { HudsonApp } from '@hudson/sdk'; ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `id` | `string` | Yes | Unique identifier | | `name` | `string` | Yes | Display name | | `description` | `string` | No | Short description | | `mode` | `'canvas' \| 'panel'` | Yes | Default frame mode | | `leftPanel` | `{ title, icon?, headerActions? }` | No | Left panel config | | `rightPanel` | `{ title, icon? }` | No | Right panel config | | `Provider` | `React.FC<{ children }>` | Yes | State owner | | `slots` | `{ Content, LeftPanel?, RightPanel?, LeftFooter?, Terminal? }` | Yes | UI slots | | `intents` | `AppIntent[]` | No | Intent declarations | | `hooks` | `{ useCommands, useStatus, ... }` | Yes | Shell bridge hooks | ### HudsonWorkspace Defines a collection of apps in a shared shell. ```typescript import type { HudsonWorkspace, WorkspaceAppConfig, CanvasParticipation } from '@hudson/sdk'; ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `id` | `string` | Yes | Unique workspace ID | | `name` | `string` | Yes | Display name | | `description` | `string` | No | Workspace description | | `mode` | `'canvas' \| 'panel'` | Yes | Global frame mode | | `apps` | `WorkspaceAppConfig[]` | Yes | App configurations | | `defaultFocusedAppId` | `string` | No | Initially focused app | **WorkspaceAppConfig:** | Field | Type | Required | Description | |-------|------|----------|-------------| | `app` | `HudsonApp` | Yes | The app instance | | `canvasMode` | `'native' \| 'windowed'` | No | Canvas participation (default: `'native'`) | | `defaultWindowBounds` | `{ x, y, w, h }` | No | Initial window position/size | ### AppIntent Structured metadata for LLM/voice/search integration. ```typescript import type { AppIntent, IntentCategory, IntentParameter } from '@hudson/sdk'; ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `commandId` | `string` | Yes | Must match a CommandOption.id | | `title` | `string` | Yes | Human-readable title | | `description` | `string` | Yes | Natural-language description | | `category` | `IntentCategory` | Yes | One of: tool, edit, file, view, navigation, toggle, workspace, settings | | `keywords` | `string[]` | Yes | Synonyms for matching | | `params` | `IntentParameter[]` | No | Typed parameters | | `shortcut` | `string` | No | Keyboard shortcut | | `dangerous` | `boolean` | No | Requires confirmation | ### StatusColor ```typescript type StatusColor = 'emerald' | 'amber' | 'red' | 'neutral'; ``` ### SearchConfig ```typescript interface SearchConfig { value: string; onChange: (value: string) => void; placeholder?: string; } ``` ### CommandOption ```typescript interface CommandOption { id: string; label: string; action: () => void; shortcut?: string; icon?: ReactNode; section?: string; } ``` ## Chrome Components ### Frame Root container. Manages viewport/world transforms and renders HUD elements. ```tsx void} onZoom={(scale) => void} hud={<>{/* NavigationBar, SidePanel, etc. */}} > {/* Content in world/viewport space */} ``` ### NavigationBar Top bar with title, optional search, and action slots. ```tsx {/* Center content */}} actions={<>{/* Right-aligned actions */}} /> ``` ### SidePanel Collapsible left or right panel with resize handle. ```tsx } headerActions={} footer={} collapsed={false} onToggle={() => void} width={280} onResize={(w) => void} > {/* Panel content */} ``` ### StatusBar Bottom bar showing app status. ```tsx ``` ### CommandDock Floating command button (bottom center). Opens the command palette on click. ```tsx void} /> ``` ### ZoomControls Zoom in/out/reset buttons for canvas mode. ```tsx void} onZoomOut={() => void} onReset={() => void} /> ``` ### Minimap Canvas minimap showing window positions and viewport indicator. ```tsx void} /> ``` ## Canvas ### Canvas Pan/zoom input layer. Handles scroll-to-zoom, space+drag-to-pan, and gesture events. ```tsx ``` ## Windows ### AppWindow Draggable, resizable window with title bar chrome. Used for `windowed` canvas participation. ```tsx void} onReportBounds={(bounds) => void} maximized={false} onMaximizedChange={(v) => void} panOffset={pan} scale={zoom} > {/* Window content */} ``` Features: - Drag via title bar (Option+drag anywhere) - 8-edge resize handles - Maximize/restore toggle - Context menu (Bring to Center, Maximize, Reset Window) - Bounds persisted to localStorage ## Overlays ### CommandPalette Searchable command menu triggered by Cmd+K. ```tsx void} commands={commandOptions} /> ``` ### TerminalDrawer Bottom slide-out panel, toggled via Cmd+`. ```tsx void}> {/* Terminal content */} ``` ### HudsonContextMenu Right-click context menu (powered by @base-ui/react + motion). ```tsx void, shortcut: 'Cmd+C' }, { type: 'separator' }, { type: 'action', label: 'Delete', action: () => void, destructive: true }, ]}> {/* Trigger element */} ``` ## Hooks ### usePersistentState localStorage-backed state hook. Works like `useState` but persists across reloads. ```tsx import { usePersistentState } from '@hudson/sdk'; const [value, setValue] = usePersistentState('storage-key', defaultValue); ``` ## Utilities ### sounds Web Audio synthesizer for UI feedback. ```tsx import { sounds } from '@hudson/sdk'; sounds.blipUp(); // Positive feedback / success sounds.click(); // Button press sounds.whoosh(); // Transition / navigation sounds.thock(); // Heavy press / confirm ``` ### logger Event bus for Frame activity logging. ```tsx import { logEvent, FRAME_LOG_EVENT } from '@hudson/sdk'; logEvent({ type: 'app:action', detail: 'something happened' }); // Listen for events window.addEventListener(FRAME_LOG_EVENT, (e) => { console.log(e.detail); }); ``` ### viewport Coordinate conversion between world and screen space. ```tsx import { worldToScreen, screenToWorld } from '@hudson/sdk'; const screenPos = worldToScreen(worldPos, panOffset, scale); const worldPos = screenToWorld(screenPos, panOffset, scale); ``` ### chrome Design tokens and styling constants. ```tsx import { CHROME, CHROME_BASE, PANEL_STYLES, EDGE_EFFECTS, Z_LAYERS, LAYOUT } from '@hudson/sdk'; // CHROME — computed styles (borders, backgrounds, shadows) // CHROME_BASE — raw color values // PANEL_STYLES — panel-specific styling // EDGE_EFFECTS — edge glow/shadow effects // Z_LAYERS — z-index layer map // LAYOUT — spacing and sizing constants ``` --- # Architecture # Architecture Hudson is a monorepo with two main packages: the **shell application** (Next.js) and the **frame-ui component library**. ## Monorepo Structure ``` hudson/ app/ # Next.js application (App Router) page.tsx # Entry: mounts WorkspaceShell layout.tsx # Root layout globals.css # Tailwind + global styles shell/ # Shell runtime WorkspaceShell.tsx # Main orchestrator (~40KB) HomeScreen.tsx # App launcher grid BootSplash.tsx # Boot animation SidebarSection.tsx # Reusable sidebar section apps/ # App implementations shaper/ # Bezier editor (reference app) index.ts # HudsonApp definition ShaperProvider.tsx # Context (~64KB, full state) ShaperContent.tsx # Canvas renderer ShaperLeftPanel.tsx # Project tree ShaperRightPanel.tsx # Inspector ShaperLeftFooter.tsx # Minimap ShaperTerminal.tsx # Log output ShaperHeaderActions.tsx # Panel header buttons hooks.ts # 6 hook implementations intents.ts # 25+ intent declarations components/ # Private components hudson-docs/ # Docs browser (canvas native) intent-explorer/ # Intent catalog viewer workspaces/ # Workspace definitions hudsonOS.ts # Multi-app canvas workspace shaperDev.ts # Shaper standalone workspace index.ts # Re-exports lib/ # Shared utilities intent-catalog.ts # buildIntentCatalog() hooks/ # Shared hooks useIntentExecutor.ts # Intent → command bridge api/ # API routes shaper/save/route.ts # Shaper save endpoint packages/ frame-ui/ # Component library src/ index.ts # Public exports components/ chrome/ # Frame, NavBar, SidePanel, StatusBar, CommandDock, ZoomControls, Minimap canvas/ # Canvas (pan/zoom engine) windows/ # AppWindow (draggable/resizable) overlays/ # CommandPalette, TerminalDrawer, ContextMenu types/ app.ts # HudsonApp interface workspace.ts # HudsonWorkspace interface intent.ts # AppIntent, IntentCatalog hooks/ usePersistentState.ts # localStorage-backed state lib/ chrome.ts # Design tokens (CHROME, Z_LAYERS, LAYOUT) sounds.ts # Web Audio synthesizer logger.ts # Event bus viewport.ts # Coordinate math (worldToScreen, screenToWorld) README.md package.json ``` ## Data Flow ``` page.tsx └── WorkspaceShell(workspaces, defaultWorkspaceId) │ ├── Nests all app Providers (recursive wrapping) │ AppA.Provider → AppB.Provider → ... → WorkspaceInner │ └── WorkspaceInner ├── Calls each app's hooks (useCommands, useStatus, etc.) ├── Merges commands from all apps + shell into CommandPalette ├── Renders Frame with chrome (NavBar, SidePanel, StatusBar) ├── Renders active app's slots into chrome slots │ ├── slots.Content → main area (canvas world or viewport) │ ├── slots.LeftPanel → left SidePanel │ ├── slots.RightPanel → right SidePanel │ └── slots.Terminal → TerminalDrawer └── In canvas mode: wraps windowed apps in AppWindow ``` ## Key Architectural Decisions ### Provider-first state management Each app owns its state via a React context Provider. The shell never accesses app state directly — it only reads through hooks. This keeps apps fully decoupled. ### Hook bridge pattern The shell calls app hooks inside the Provider's scope. This means hooks can call `useMyAppContext()` safely. The shell doesn't import app internals — it only calls the hook functions declared in the `HudsonApp` object. ### Ref-based window tracking Window positions are tracked in a `useRef` (not state) during drag operations to avoid re-renders on every mouse move. Positions are flushed to state via a 60ms debounce for minimap rendering. ### Static intent declarations Intents are declared as plain data (not runtime code) so they can be indexed, serialized, and searched without executing app logic. The execution bridge connects them to live commands at runtime. ### Recursive Provider nesting All app Providers wrap the entire workspace content. This enables cross-app context sharing if needed, while keeping the default pattern of isolated state per app. ## State Persistence All persistent state uses `usePersistentState()` backed by localStorage: | Key Pattern | Data | |-------------|------| | `hudson.ws.{workspaceId}.win.{appId}` | Window bounds | | `hudson.leftW` | Left panel width | | `hudson.rightW` | Right panel width | | `hudson.session` | Active session | | `{appId}.{key}` | App-specific state | ## Build & Dev | Command | Purpose | |---------|---------| | `bun install` | Install all dependencies | | `bun dev` | Start dev server (port 3500) | | `bun run build` | Production build | | `bun run lint` | ESLint | --- # Skills # Skills Pre-built skill definitions that agents can use when working with Hudson. ## hudson-app-creator **When to use:** When asked to create a new app for the Hudson platform. **Steps:** 1. Read `packages/frame-ui/src/types/app.ts` to understand the HudsonApp interface 2. Read `app/apps/shaper/index.ts` as the reference implementation 3. Follow the task template in `docs/prompts/create-app.md` 4. Create all required files (Provider, Content, hooks, index.ts) 5. Register in workspace and test with `bun dev` **Key rules:** - Every app needs at minimum: id, name, mode, Provider, slots.Content, hooks.useCommands, hooks.useStatus - Provider must use React context pattern - Hooks are called inside Provider scope by the shell - Never use purple, never use library UI components ## hudson-intent-author **When to use:** When asked to add LLM/voice intents to an existing app. **Steps:** 1. Read the app's `hooks.ts` to find all command IDs 2. Follow `docs/prompts/add-intents.md` template 3. Create `intents.ts` with AppIntent[] matching each command 4. Add `intents` field to the HudsonApp definition **Key rules:** - Every intent `commandId` must match a `CommandOption.id` from `useCommands()` - Include diverse keywords for fuzzy matching - Mark destructive actions as `dangerous: true` ## hudson-workspace-builder **When to use:** When asked to create or modify a workspace. **Steps:** 1. Read `packages/frame-ui/src/types/workspace.ts` for the HudsonWorkspace interface 2. Read `app/workspaces/hudsonOS.ts` as reference 3. Create workspace file in `app/workspaces/` 4. Register in `app/page.tsx` workspaces array **Key rules:** - Each workspace has a mode (canvas or panel) - Apps specify canvasMode: 'native' or 'windowed' - Windowed apps need defaultWindowBounds: { x, y, w, h } ## hudson-frame-ui-contributor **When to use:** When asked to add or modify frame-ui components. **Steps:** 1. Read `packages/frame-ui/src/index.ts` for current exports 2. Check `packages/frame-ui/src/lib/chrome.ts` for design tokens 3. Follow existing component patterns (stateless, callback-based) 4. Export from `packages/frame-ui/src/index.ts` **Key rules:** - Components are stateless — apps manage all state - Use callback pattern (props in, events out) - Dark theme only (hardcoded) - Use Tailwind v4 for styling - Use lucide-react for icons --- # Agent Overview # Hudson — Agent Context ## Identity | Field | Value | |-------|-------| | Name | Hudson | | Type | Multi-app canvas workspace platform | | Stack | React 19, Next.js 16, Tailwind v4, TypeScript | | Package manager | bun | | Dev server | `bun dev` → port 3500 | | Entry point | `app/page.tsx` → `WorkspaceShell` | ## Architecture (3 layers) | Layer | Location | Role | |-------|----------|------| | @hudson/sdk | `packages/hudson-sdk/src/` | Component library + type contracts | | Shell | `app/shell/` | Runtime orchestrator (WorkspaceShell) | | Apps | `app/apps/` | Self-contained apps implementing HudsonApp | ## HudsonApp Interface (required fields) ```typescript { id: string, // Unique ID name: string, // Display name mode: 'canvas'|'panel', Provider: React.FC<{children}>, // State owner (React context) slots: { Content: React.FC }, // Main UI (required) hooks: { useCommands: () => CommandOption[], // Palette commands useStatus: () => {label, color}, // Status bar } } ``` ## Optional HudsonApp fields | Field | Type | Purpose | |-------|------|---------| | `description` | string | Tooltip text | | `leftPanel` | {title, icon?, headerActions?} | Left panel config | | `rightPanel` | {title, icon?} | Right panel config | | `slots.LeftPanel` | React.FC | Left sidebar content | | `slots.RightPanel` | React.FC | Right sidebar content | | `slots.LeftFooter` | React.FC | Left panel footer | | `slots.Terminal` | React.FC | Terminal drawer content | | `hooks.useSearch` | () => SearchConfig | Nav bar search | | `hooks.useNavCenter` | () => ReactNode | Nav center content | | `hooks.useNavActions` | () => ReactNode | Nav right actions | | `hooks.useLayoutMode` | () => 'canvas'|'panel' | Mode override | | `intents` | AppIntent[] | LLM/voice declarations | ## StatusColor valid values `'emerald'` | `'amber'` | `'red'` | `'neutral'` ## IntentCategory valid values `'tool'` | `'edit'` | `'file'` | `'view'` | `'navigation'` | `'toggle'` | `'workspace'` | `'settings'` ## Canvas participation modes | Mode | Behavior | |------|----------| | `native` | Renders directly on canvas, no window frame | | `windowed` | Renders inside AppWindow with title bar + drag/resize | ## Existing apps | ID | Name | Canvas Mode | |----|------|-------------| | `shaper` | Shaper | windowed | | `hudson-docs` | Hudson Docs | native | | `intent-explorer` | Intent Explorer | windowed | ## File structure for new app ``` app/apps/{name}/ index.ts # HudsonApp export {Name}Provider.tsx # Context provider hooks.ts # useCommands, useStatus, etc. intents.ts # AppIntent[] (optional) {Name}Content.tsx # Content slot {Name}LeftPanel.tsx # LeftPanel slot (optional) {Name}RightPanel.tsx # RightPanel slot (optional) {Name}Terminal.tsx # Terminal slot (optional) components/ # Private components ``` ## Registration steps 1. Create app in `app/apps/{name}/` 2. Add to workspace in `app/workspaces/{workspace}.ts` 3. If new workspace, add to `app/page.tsx` workspaces array ## Critical constraints - Use bun, never npm/pnpm - Never use purple in designs - All UI components are custom-built — don't use library components - Use @base-ui/react for context menu only - Apps must not manage shell chrome