architecture

Architecture

Hudson is a monorepo with a web workspace, a web SDK, Apple-native Swift packages, and small runtime services.

Monorepo layout

hudson/
  apps/web/                         # Next.js 16 app + marketing + deploy (App Router)
    app/
      app/page.tsx                  # `/app` route — mounts <WorkspaceShell>
      layout.tsx                    # Root HTML + globals.css
      globals.css                   # Tailwind v4 + scrollbar styles
      apps/                         # App implementations
        registry.ts                 # Aggregates built-in + local apps
        stage-design/               # Compact reference app
        theme-designer/
        hudson-docs/
        hudson-ai/
        intent-explorer/
        hud-logger/
        services/
        terminal/
      workspaces/
        hudsonOS.ts                 # Default workspace composition
        index.ts                    # Re-exports
      local/
        apps.local.ts               # Gitignored — dev-local app registrations
        workspaces.json
      lib/                          # Shared utilities
      hooks/                        # Shared hooks
      services/                     # Service registry (hx, etc.)
      api/                          # Next.js API routes (AI, saves, etc.)

  packages/
    web/
      hudsonkit/                    # Web SDK (workspace-internal for now)
        src/
          index.ts                  # Public main entry — types, hooks, AI, platform
          app-shell.ts              # `hudsonkit/app-shell` subpath
          chrome.ts                 # `hudsonkit/chrome`
          overlays.ts               # `hudsonkit/overlays`
          context-menu.ts           # `hudsonkit/context-menu`
          canvas.ts                 # `hudsonkit/canvas`
          windows.ts                # `hudsonkit/windows`
          theme.ts                  # `hudsonkit/theme`
          terminal.ts               # `hudsonkit/terminal`
          shell.ts                  # `hudsonkit/shell` (back-compat barrel)
          workspace/shell/          # WorkspaceShell — multi-app orchestrator (`hudsonkit/workspace`)
          styles/bundle.css         # Source for the compiled CSS bundle
          dist/styles.css           # Compiled via `bun run build:css`
          components/
            AppShell.tsx            # Single-app shell (default)
            chrome/                 # Frame, NavigationBar, SidePanel, StatusBar, CommandDock, Minimap, ZoomControls, AnimationTimeline
            canvas/Canvas.tsx
            windows/AppWindow.tsx
            overlays/               # CommandPalette, TerminalDrawer, HudsonContextMenu
            AI.tsx, TerminalRelay.tsx
          types/
            app.ts                  # HudsonApp interface
            workspace.ts            # HudsonWorkspace interface
            intent.ts, port.ts, service.ts
          hooks/                    # usePersistentState, useAppSettings, useHudsonAI, useTerminalRelay
          platform/                 # Platform adapter (web, desktop)
          lib/                      # theme, sounds, logger, viewport, manifest
      create-hudson-app/            # App scaffold CLI
    native/
      apple/
        HudsonKit/                  # Swift package for iOS/macOS shells
    services/
      hudson-relay/                 # WebSocket PTY relay service
  docs/                             # Architecture, case study, builder notes

Data flow (WorkspaceShell)

apps/web/app/app/page.tsx
  └── <WorkspaceShell workspaces={allWorkspaces} defaultWorkspaceId="hudsonOS" />
        │
        ├── Nests every app's Provider recursively
        │     AppA.Provider → AppB.Provider → ... → WorkspaceShellInner
        │
        └── WorkspaceShellInner
              ├── Calls each app's hooks (useCommands, useStatus, useSearch, ...)
              ├── Merges commands from all apps + shell commands → CommandPalette
              ├── Renders Frame with chrome (NavigationBar, SidePanel, StatusBar, CommandDock)
              ├── Canvas mode: wraps windowed apps in AppWindow, renders native apps directly on canvas
              ├── Panel mode: single focused app fills the viewport inset
              └── Merges app intents → IntentCatalog (for LLM/voice/search)

Data flow (AppShell)

app/page.tsx (consumer app — e.g. premotion, external project)
  └── <AppShell app={catalogApp} />
        │
        └── app.Provider wraps everything
              └── AppShellInner reads app.hooks, fills slots
                    ├── NavigationBar (title + search + nav center + nav actions)
                    ├── SidePanel left (app.slots.LeftPanel + LeftFooter + CommandDock)
                    ├── SidePanel right (app.slots.Inspector | RightPanel + tools accordion)
                    ├── Content area (app.slots.Content)
                    ├── StatusBar (app.hooks.useStatus + terminal toggle + clock)
                    ├── TerminalDrawer (app.slots.Terminal or placeholder)
                    └── CommandPalette (fed by useCommands + shell shortcuts)

Key architectural decisions

Provider-first state

Each app owns its state via a React context Provider. The shell never touches app internals — it only reads through the app's declared hooks. Apps stay fully decoupled.

Hook Bridge pattern

The shell calls an app's hooks inside that app's Provider scope via a small internal Bridge component. This means hooks can call useMyAppContext() safely. The shell doesn't import app modules — it only calls the hook functions registered in the HudsonApp object.

Refs, not state, during drag/pan/resize

Window bounds, pan/zoom offsets, and resize deltas are tracked in useRef during interaction — React re-renders are suppressed. State gets flushed on a debounce (BOUNDS_FLUSH_MS = 500 in WorkspaceShell.tsx) so the minimap and persistence observe stable values without dragging the whole tree on every mouse move.

See perf-drag-resize-patterns.md for the full set of tricks.

Static intent declarations

Intents are declared as plain data (not runtime functions) so they can be indexed, serialized, and searched without executing app logic. An intent executor bridges discovered intents back to the app's live commands at runtime.

Recursive Provider nesting

All app Providers wrap the entire workspace content. This enables cross-app context sharing when needed; isolated state is the default.

Registry-driven app loading

Hudson's own demo registers its built-in apps in apps/web/app/apps/registry.ts and merges in optional developer-local apps from a gitignored apps/web/app/local/apps.local.ts (auto-created as an empty stub on first run). The same split — committed shared registry + gitignored local override — works for any consumer that wants a stable default workspace alongside per-developer private apps.

State persistence

All persistent UI state uses usePersistentState() backed by localStorage:

Key patternPurpose
hudson.ws.{workspaceId}.win.{appId}Window bounds (canvas mode)
hudson.ws.{workspaceId}.modeCurrent workspace mode
hudson.sessionActive session / focused app
appshell.{appId}.left / .rightSide panel collapsed state (AppShell)
appshell.{appId}.leftW / .rightWSide panel widths (AppShell)
{appId}.{key}App-specific state (owned by app Provider)

App code should prefer the {appId}.{key} namespace and use usePersistentState('myapp.notes', []) — the hook handles serialization, SSR safety, and cross-tab sync.

Build & dev

CommandPurpose
bun installInstall all workspace deps
bun devStart Hudson dev server on :3500
bun run buildProduction build
bun run lintESLint
bun run relayStart the terminal relay WS server
cd packages/web/hudsonkit && bun run build:cssRebuild the SDK's compiled CSS bundle
For AI agents