Skip to content

Workshop

Last verified against implementation: 2026-07-15

The Vita Workshop is the fast, agent-authored interface layer over Vita's capability system. It turns a prompt such as β€œbuild a workshop page for standing agendas” into a live, writable page without adding a React feature, a bespoke API, or another data store.

The central distinction is:

  • The web application is the product: durable, curated, tested, and higher ceremony.
  • The Workshop is the workbench: personal, prompt-speed, disposable, and expected to change quickly.

A useful Workshop page can graduate into the React application. A page that stops being useful can be deleted without a migration project because its domain state never belonged to the page in the first place.

This is different from the Forge Workshop, which is the problem-first idea-development pipeline. β€œWorkshop” in this document means the live HTML workbench served at /workshop/.


Why it exists

Before the Workshop, a personal dashboard usually forced one of two bad trade-offs:

  1. make a permanent product feature before the interaction has earned that permanence; or
  2. make a one-off page that reads files or invents private endpoints, creating another data island.

The Workshop avoids both. Pages are cheap, but their data access is not ad hoc. Every domain read or write goes through the same typed, provenance-stamped capability used by agents, the CLI, and other interfaces.

The design is based on four rules:

  1. No page-owned domain data. Pages compose capabilities; they do not own a database or read files directly.
  2. Freshness is visible. A page renders meta.as_of and meta.source so a stale or unknown answer cannot masquerade as a current zero.
  3. Rejections are visible. A policy rejection is a distinct amber state, not a silent failed click and not a generic red system error.
  4. Successful patterns graduate. Shared UI becomes a block; an enduring workflow becomes a product feature.

System shape

prompt: "build a workshop page for X"
                    β”‚
                    β–Ό
       workshop/pages/<name>/
       β”œβ”€β”€ index.html
       └── app.js
                    β”‚ imports
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β–Ό                   β–Ό
 workshop/lib/vita.v1.js   workshop/components/vita-blocks.js
   data + policy SDK          reusable UI vocabulary
          β”‚
          β–Ό
 POST /api/v1/capabilities/call/<name>
          β”‚
          β–Ό
 typed capability β†’ existing owner module β†’ canonical domain store
          β”‚
          β”œβ”€β”€ v1 result envelope + provenance
          └── usage / idempotency / policy records

discovery side path:
workshop/pages/* β†’ scan_pages()
                  β”œβ”€β”€ GET /api/v1/workshop/pages
                  β”œβ”€β”€ vita.workshop.pages
                  └── React /workbench index

There is no Workshop build system. api/src/main.py mounts the repository's workshop/ directory with FastAPI StaticFiles, so a new page is available as soon as its files exist. The web application on port 33801 proxies the same paths.

The two shared spines

Spine Location Responsibility
Data workshop/lib/vita.v1.js Capability discovery, page allowlist, calls, v1 envelopes, idempotency keys, page preferences
UI workshop/components/vita-blocks.js Framework-independent web components shared across pages

Keeping these separate matters. A block can change presentation without creating a second data path, while a page can use a new capability without adding a React dependency or bundler.


Page lifecycle

1. Demand appears

The trigger is normally natural language: β€œbuild a workshop page for X,” β€œput X on the workshop,” or β€œI want a HUD for Y.” The page-building workflow is defined in .claude/skills/workshop-page/SKILL.md.

Before authoring UI, the agent inspects the promoted vocabulary with:

./run capabilities list

or the generated reference under docs/api/capabilities/. If the required operation is absent, the page is treated as a demand signal for a new capability. The agent wraps the existing owner function in scripts/capabilities/namespaces/, regenerates the specification, adds contract tests, and only then uses it from the page. Reimplementing the domain logic in JavaScript would defeat the architecture.

2. The page is created

The minimum page is:

workshop/pages/<name>/index.html
workshop/pages/<name>/app.js

index.html supplies a meaningful <title> and a leading HTML comment. Those two fields become the title and description shown by the Workshop index.

The JavaScript establishes a page identity and a least-privilege vocabulary:

import { createVita } from '/workshop/lib/vita.v1.js';
import { registerVitaBlocks } from '/workshop/components/vita-blocks.js';

const vita = await createVita({
  page: 'standing-agendas',
  allow: ['vita.people.*', 'vita.workshop.*'],
});

registerVitaBlocks(vita);

The page name becomes write provenance (browser:standing-agendas) through the X-Vita-Page request header. The allow list prevents accidental use of unrelated capabilities in the browser SDK. It is a page-authoring guardrail, not a replacement for the server's transport policy or the internal-only host boundary.

3. The page calls capabilities

const result = await vita.call(
  'vita.people.agenda_add',
  { person: 'example-person', text: 'review the launch sequence' },
  { idempotencyKey: vita.newIdempotencyKey() },
);

The SDK returns the server's envelope rather than throwing for domain or policy failures:

{
  "ok": true,
  "data": { "status": "created" },
  "error": null,
  "meta": {
    "v": 1,
    "capability": "vita.people.agenda_add",
    "request_id": "req_example",
    "as_of": "2026-07-15T12:00:00Z",
    "source": "precedent agenda store",
    "transport": "js"
  }
}

Callers branch on ok and error.code. In addition to server errors, the SDK can synthesize three client-side failures:

Code Meaning
unknown_capability The requested name was absent from discovery
page_not_allowed The name was outside the page's declared allow patterns
sdk_blocked A browser attempted a send- or spawn-class capability
network_error Discovery or call transport failed

The server also rejects browser send and spawn calls. The SDK check merely fails faster. Write calls should always provide an idempotency key so a retry cannot duplicate the mutation.

4. The page is discovered automatically

scripts/workshop/pages_index.py::scan_pages() scans workshop/pages/* on every call and returns pages newest-updated first. It deliberately does not cache: static pages become live without a restart, so a cached index would be wrong immediately.

The same owner function feeds both:

  • GET /api/v1/workshop/pages, used by the React /workbench route; and
  • vita.workshop.pages, available through the capability layer.

This shared owner prevents the HTTP index and capability index from drifting. The older /workshop/ landing page is a small hand-maintained directory; the React /workbench index is automatic.

5. It is promoted or deleted

Usage is recorded centrally in data/capabilities/usage.db. Repeatedly useful page-local UI can move into workshop/components/vita-blocks.js. A mature workflow can graduate into the React app while retaining the same capability contracts.

Deleting workshop/pages/<name>/ removes the page from the live index. Its view-chrome rows become cold data in data/workshop/page-prefs.db; no domain records disappear.


State boundaries

Domain state

Anything another interface might need is domain state. It must be read or written through a capability and stored by that capability's existing owner module. Examples include tasks, agenda entries, links, health readings, and education records.

View chrome

Collapsed panels, sort order, and purely visual toggles may be stored through:

  • vita.workshop.prefs_get
  • vita.workshop.prefs_set

The browser SDK exposes these as:

const chrome = await vita.prefs.load();
await vita.prefs.set({ 'collapsed:scheduled': true });
await vita.prefs.remove(['show-completed']);

Preferences are scoped by page and stored as arbitrary JSON in data/workshop/page-prefs.db, owned by scripts/workshop/page_prefs.py. Server-side storage makes chrome follow the same person across devices. The store is intentionally second-class: it is not mined, joined into domain analysis, or used as an alternate source of truth.

page normally comes from X-Vita-Page. Python and CLI callers can pass it explicitly. Calls without either identity return invalid_input.


Shared blocks

workshop/components/vita-blocks.js is the promoted block vocabulary. Current examples include <vita-sleep-gate> and <vita-search>. They are native web components, so Workshop pages can reuse them without React or a build step.

Forking a shared block inside a page is allowed when a page genuinely needs a variant. The failure mode is an unmanaged family of near-identical forks. When a local component proves broadly useful, it is promoted back into the shared file.


Current pages

Page Purpose Primary capability domains
pages/tasks/ Task and habit workbench vita.tasks.*
pages/agendas/ Standing per-person agendas joined with upcoming meetings vita.people.*, calendar reads
pages/start/ Browser start page with link tiers, frequents, and a today HUD vita.links.*, health/calendar reads

These are examples, not a closed product catalog. The defining property of the Workshop is that a new personal surface can appear without changing the React route tree.


Exposure and security

The Workshop and capability HTTP endpoints are internal surfaces:

  • available on localhost and the private/Tailscale path;
  • excluded from public tunnel host allowlists by api/src/middleware/domain_guard.py;
  • not exposed through vita-docs.ham.xyz or other public report domains.

Static page code is trusted internal code, but it still receives mechanical constraints. Browser transport cannot invoke send or spawn; page identity is recorded; page authors declare an allowlist; mutations use idempotency; and capability-level failures return structured rejection codes.

Workshop pages must never embed secrets or personal source records in their HTML. They request the minimum data needed at runtime.


Verification contract

A new page is not complete merely because it renders. Verification exercises the real interface and then checks the canonical state:

  1. open the page through the internal API or web proxy;
  2. confirm blocks and data render with freshness metadata;
  3. perform one real write through the UI;
  4. verify the write in the capability provenance ledger or backing owner;
  5. reload and confirm page chrome persisted from the server;
  6. exercise a rejected state and confirm it is visibly distinct from an error.

The page-building skill uses agent-browser for this local-dev interaction. Element references invalidate after navigation and DOM snapshots, so every mutation is verified against provenance or backing state rather than trusting an automation tool's β€œclicked” response.


Known limitations and improvement opportunities

  • Two indexes exist. /workbench discovers pages automatically, while workshop/index.html has a hand-maintained list that can lag.
  • Metadata is inferred from HTML. The scanner reads the <title> and first leading comment from the first 4 KB; there is no explicit page manifest or validation command.
  • The page allowlist is client-side. It prevents accidental overreach, but same-origin trusted code can bypass the SDK. The hard controls are transport restrictions and the private host boundary.
  • Preferences have no garbage collector. Deleting a page leaves cold rows in page-prefs.db.
  • No automatic graduation rule exists. Usage can identify a durable page, but promotion into the product remains a judgment call.
  • Interaction testing is page-specific. There is no common automated contract test that loads every page and verifies freshness/rejection chrome.

Reasonable next improvements are a small page manifest/schema, one generated landing page instead of two indexes, an orphaned-preferences cleanup report, and a shared smoke test for the non-negotiable page contract.


File map

Purpose Path
Design doctrine workshop/README.md
Page-authoring workflow .claude/skills/workshop-page/SKILL.md
Static pages workshop/pages/<name>/
Browser capability SDK workshop/lib/vita.v1.js
Shared style layer workshop/lib/workshop.css
Shared blocks workshop/components/vita-blocks.js
Static mount and internal routes api/src/main.py
React product-side index web/src/routes/workbench.tsx
Page index router api/src/routers/workshop.py
Shared page scanner scripts/workshop/pages_index.py
Workshop capability namespace scripts/capabilities/namespaces/workshop.py
Preference owner scripts/workshop/page_prefs.py
Preference store data/workshop/page-prefs.db
Capability contract tests tests/test_capabilities.py

Changelog

  • 2026-07-15: Initial public system documentation for the Workshop, including capability composition, browser surfaces, page discovery, cross-device chrome, lifecycle, and security boundaries.