llms.txt

Getting Started

Build apps for the Hudson workspace platform using @hudsonos/sdk. This guide walks you through installation, creating a minimal app, and registering it in a workspace.

Installation

bun add @hudsonos/sdk

Peer Dependencies

The SDK requires React 19 and Lucide React for icons:

bun add react react-dom lucide-react

Your project should also use Tailwind CSS v4. See Utilities for Tailwind configuration.

Your First App

Every Hudson app implements the HudsonApp interface. At minimum, you need an id, name, mode, a Provider, slots.Content, and two hooks.

import type { HudsonApp, CommandOption, StatusColor } from '@hudsonos/sdk';

const counterApp: HudsonApp = {
  id: 'counter',
  name: 'Counter',
  description: 'A simple click counter',
  mode: 'panel',

  Provider: ({ children }) => <>{children}</>,

  slots: {
    Content: () => (
      <div className="flex items-center justify-center h-full">
        <button className="px-4 py-2 bg-cyan-600 rounded text-white">
          Click me
        </button>
      </div>
    ),
  },

  hooks: {
    useCommands: (): CommandOption[] => [],
    useStatus: (): { label: string; color: StatusColor } => ({
      label: 'READY',
      color: 'emerald',
    }),
  },
};

export default counterApp;

What Each Field Does

FieldPurpose
idUnique key used for localStorage namespacing and workspace routing
nameHuman-readable label shown in the app switcher and navigation bar
descriptionShort summary shown in tooltips and the command palette
mode'canvas' enables infinite pan/zoom; 'panel' renders scrollable content
ProviderReact component that wraps all slots and provides app state via context
slots.ContentThe main content area of your app
hooks.useCommandsReturns commands for the command palette (Cmd+K)
hooks.useStatusReturns the status bar label and color indicator

Adding State with a Provider

Real apps need state. The standard pattern wraps state in a React context and exposes it through a custom hook.

import { createContext, useContext, useState, type ReactNode } from 'react';
import { usePersistentState } from '@hudsonos/sdk';

interface CounterState {
  count: number;
  increment: () => void;
  reset: () => void;
}

const CounterContext = createContext<CounterState | null>(null);

function useCounter(): CounterState {
  const ctx = useContext(CounterContext);
  if (!ctx) throw new Error('useCounter must be used within CounterProvider');
  return ctx;
}

function CounterProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = usePersistentState('counter.count', 0);

  const increment = () => setCount((c) => c + 1);
  const reset = () => setCount(0);

  return (
    <CounterContext.Provider value={{ count, increment, reset }}>
      {children}
    </CounterContext.Provider>
  );
}

Then wire the Provider and hooks into the app definition:

import type { HudsonApp, CommandOption } from '@hudsonos/sdk';
import { sounds } from '@hudsonos/sdk';

const counterApp: HudsonApp = {
  id: 'counter',
  name: 'Counter',
  mode: 'panel',

  Provider: CounterProvider,

  slots: {
    Content: () => {
      const { count, increment } = useCounter();
      return (
        <div className="flex items-center justify-center h-full gap-4">
          <span className="text-4xl font-mono text-neutral-200">{count}</span>
          <button
            onClick={() => { increment(); sounds.tick(); }}
            className="px-4 py-2 bg-cyan-600 rounded text-white"
          >
            +1
          </button>
        </div>
      );
    },
  },

  hooks: {
    useCommands: (): CommandOption[] => {
      const { increment, reset } = useCounter();
      return [
        { id: 'counter:increment', label: 'Increment', action: increment },
        { id: 'counter:reset', label: 'Reset Counter', action: reset },
      ];
    },
    useStatus: () => {
      const { count } = useCounter();
      return { label: `COUNT: ${count}`, color: 'emerald' };
    },
  },
};

Registering in a Workspace

A workspace groups one or more apps into a shared shell. Create a workspace definition and include your app:

import type { HudsonWorkspace } from '@hudsonos/sdk';
import counterApp from './apps/counter';

export const myWorkspace: HudsonWorkspace = {
  id: 'my-workspace',
  name: 'My Workspace',
  mode: 'panel',
  apps: [
    { app: counterApp },
  ],
  defaultFocusedAppId: 'counter',
};

Multi-App Workspaces

Add multiple apps to the same workspace. Each app runs in its own Provider context, but they share the shell chrome (navigation bar, status bar, side panels).

export const designWorkspace: HudsonWorkspace = {
  id: 'design',
  name: 'Design Studio',
  mode: 'canvas',
  apps: [
    { app: shaperApp },
    {
      app: logoApp,
      canvasMode: 'windowed',
      defaultWindowBounds: { x: 100, y: 100, w: 600, h: 400 },
    },
  ],
  defaultFocusedAppId: 'shaper',
};

The canvasMode field controls how an app participates in a canvas workspace:

ValueBehavior
'native' (default)App renders directly on the canvas
'windowed'App renders inside a draggable/resizable window

Project Structure

A typical Hudson app follows this directory layout:

app/apps/my-app/
  index.ts           # HudsonApp definition (entry point)
  MyAppProvider.tsx   # React context provider with all state
  MyAppContent.tsx    # Content slot
  MyAppLeftPanel.tsx  # Left panel slot (optional)
  MyAppInspector.tsx  # Inspector slot (optional)
  hooks.ts           # Hook implementations (useCommands, useStatus, etc.)
  intents.ts         # Intent declarations (optional)
  ports.ts           # Port hooks (optional)
  types.ts           # App-specific types
  components/        # Internal components

Next Steps

  • Building Apps — deep dive into the Provider + Slots + Hooks pattern
  • API Reference — complete type, hook, and function reference
  • Systems — intents, services, and inter-app data piping
  • Utilities — platform adapters, design tokens, and UI sounds