diff --git a/CHANGELOG.md b/CHANGELOG.md
index 767ac2e..d131d82 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,83 @@
All notable changes to Switchboard Core are documented here.
+## v0.6.2 β Docs Polish + Dynamic OpenAPI
+
+Docs surface polish and dynamic OpenAPI spec with extension route merging.
+
+### Added
+
+- **Dynamic OpenAPI spec**: `GET /api/docs/openapi.json` builds a merged
+ spec at request time β kernel endpoints + auto-generated stubs for every
+ extension `api_routes` entry. Extensions may optionally declare `api_schema`
+ in `manifest.json` for richer path items (params, body, response examples).
+- **`api_schema` manifest field**: Optional array in `manifest.json`. Malformed
+ entries are logged and skipped β never blocks extension loading.
+- **Tests**: 7 new handler tests (zero extensions, stubs, rich schema,
+ multi-extension, malformed schema, disabled exclusion, required fields).
+
+### Fixed
+
+- **Dark mode contrast**: Replaced all hardcoded light-mode fallbacks in docs
+ CSS with theme-aware CSS variables. Tables, code blocks, nav items, and
+ headings now readable in dark mode.
+- **Docs loading state**: Replaced borrowed `settings-placeholder` animation
+ with docs-specific pulse skeleton. Added error state with retry button.
+- **Docs routing**: Default route (`/docs`) now redirects to
+ `/docs/GETTING-STARTED` instead of the blank `/:section` placeholder.
+- **Docs scrolling**: Content area now scrolls correctly β full document
+ visible without text overflowing viewport.
+- **Docs icon**: Changed from π§© to π in the user menu.
+- **Docs duplicate menu entry**: Docs was appearing twice (once from surfaces
+ API, once from hardcoded items). Added `'docs'` to `CORE_IDS` filter.
+- **Swagger UI**: Updated to point at dynamic `/api/docs/openapi.json`
+ endpoint. Static YAML preserved for backward compatibility.
+- **OpenAPI tests on Postgres**: `openapiTestStores()` was hardcoded to
+ `sqlite.NewStores()` β caused `pq: syntax error at or near ","` in CI.
+ Now uses `database.IsSQLite()` check like all other handler tests.
+
+### API
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/api/docs/openapi.json` | GET | Dynamic merged OpenAPI 3.0 spec |
+
+---
+
+## v0.6.1 β Backup/Restore + Documentation
+
+Operational tooling for data safety and extension authoring.
+
+### Added
+
+- **Backup/Restore**: Full-instance backup as `.swb` ZIP archive containing
+ core tables (JSONL), ext_data tables, and package assets. Stream to client
+ or save server-side. Restore wipes and rebuilds from archive. Dialect-neutral
+ (works across SQLite and Postgres).
+- **Admin backup section**: New "Backup" tab under `/admin` with create,
+ download, delete, and restore operations. Destructive restore requires
+ confirmation dialog.
+- **Documentation surface**: Builtin surface at `/docs` with sidebar navigation
+ and client-side markdown rendering. Ships with 5 documents: Getting Started,
+ Extension Guide, API Reference, Deployment, Package Format.
+- **Docs API**: `GET /api/v1/docs` lists available documents, `GET /api/v1/docs/:name`
+ returns raw markdown content. Authenticated (all users).
+- **Tests**: 6 backup handler tests + E2E script (`ci/e2e-backup-test.sh`).
+
+### API
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/api/v1/admin/backup` | POST | Create backup (stream or `?store=true`) |
+| `/api/v1/admin/backups` | GET | List server-side backups |
+| `/api/v1/admin/backups/:name` | GET | Download backup |
+| `/api/v1/admin/backups/:name` | DELETE | Delete backup |
+| `/api/v1/admin/restore` | POST | Restore from `.swb` upload |
+| `/api/v1/docs` | GET | List documentation |
+| `/api/v1/docs/:name` | GET | Get document content |
+
+---
+
## v0.6.0 β Cluster Registry + HA
MVP convergence point. PG-backed cluster registry for horizontal scaling β
diff --git a/Dockerfile b/Dockerfile
index 8dab240..0392981 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -77,6 +77,9 @@ COPY --from=backend /app/database/migrations /app/database/migrations
COPY src/ /usr/share/nginx/html/
COPY VERSION /VERSION
+# Documentation (v0.6.1) β served by Go backend via /api/v1/docs
+COPY docs/ /app/docs/
+
# Inject version and build hash into index.html and sw.js at build time
RUN APP_VERSION=$(cat /VERSION | tr -d '[:space:]') && \
BUILD_HASH=$(find /usr/share/nginx/html/js -name '*.js' -exec md5sum {} + | sort | md5sum | cut -c1-8) && \
diff --git a/ROADMAP.md b/ROADMAP.md
index 7a8f411..b0ba27f 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,415 +1,23 @@
# Switchboard Core β Roadmap
-## Current: v0.5.3 β Chat Polish + Integration Testing
+## Current: v0.6.2 β Docs Polish + Dynamic OpenAPI
-Fork of chat-switchboard, gutted to a pure extension platform. All AI/chat
-features removed from the kernel. What remains is the minimum viable
-platform that extensions build on.
+Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
+storage, realtime, and ops are kernel primitives. Everything else is an extension.
-### Retained kernel capabilities
+**Kernel capabilities:** Auth (builtin/mTLS/OIDC) Β· Users/teams/groups/RBAC Β·
+Surfaces/extensions/libraries/workflows Β· Starlark sandbox (capability-gated) Β·
+Object storage (PVC/S3) + ext_data tables Β· WebSocket hub + realtime pub/sub Β·
+Audit log Β· Notifications Β· Scheduled tasks
-- **Auth**: builtin (simple), mTLS, OIDC
-- **Identity**: users, teams, groups, permissions (RBAC)
-- **Packages**: surfaces, extensions, libraries, workflows
-- **Starlark sandbox**: capability-gated modules
-- **Storage**: object storage (PVC, S3), ext_data tables
-- **Realtime**: WebSocket hub, presence, multi-replica HA
-- **Ops**: audit log, notifications, maintenance goroutine
+**Completed history:** v0.2.xβv0.5.x fully documented in `CHANGELOG.md`.
+Highlights: RBAC + settings cascade, event bus + triggers, SDK stabilization,
+workflow engine (multi-stage, team roles, signoff gate, public entry, SLA),
+package distribution, Notes surface (CM6, folders, tags, backlinks, graph),
+realtime primitive, Chat surface (chat-core library + surface + polish),
+upgrade test harness, cluster registry + HA.
-### Phase 0 (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| 1. Module rename | β
| `chat-switchboard` β `switchboard-core` |
-| 2. Delete packages | β
| 15 Go packages, 29 handler files removed |
-| 3. Gut stores/models | β
| 40 β 20 store interfaces, kernel-only models |
-| 4. Fresh migrations | β
| 9 files Γ 2 dialects, 27 tables |
-| 5. Fix compilation | β
| `go build ./...` clean, 300+ stale route lines cut |
-| 6. Fix tests | β
| 8 test packages pass, ~12K stale test lines pruned |
-| 7. Frontend gut | β
| Shell + SDK only, 50+ files of chat/notes/projects code removed |
-| 8. New ICD | β
| Full OpenAPI 3.0.3 spec β 160 operations across 22 tag groups |
-| 9. CI/CD + Dockerfile | β
| Single unified image, FE/BE split removed, DB names updated, k8s var alignment fixes (resource quantities, image name, rollout deployment name) |
-| 10. Smoke test | β
| K8s deploy live at switchboard.gobha.ai/test, nginx BASE_PATH fixed, loginβadmin flow verified, branding updated |
-
-## v0.2.x β SDK & Triggers
-
-The contract that extensions build against. Three trigger primitives,
-SDK stabilization, and the first rebuilt extension (tasks).
-
-### v0.2.0 β RBAC + Settings Cascade (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Admin β RBAC group | β
| `surface.admin.access` permission + Admins system group replaces `role == "admin"` checks. Admin bypass removed from permission middleware. |
-| Settings cascade | β
| `user_overridable` flag, three-tier resolution (global β team β user), team settings API |
-| ~~Settings override model~~ | β
| Shipped with settings cascade above |
-
-### v0.2.1 β Default Surface + ICD
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Default surface routing | β
| `/` redirects to configurable default surface. No surfaces β admin. First install becomes default. Changeable in admin settings. |
-| ICD (API contract) | β
| Full OpenAPI 3.0.3 spec β 160 operations, 22 tag groups, reusable component schemas. Served at `/api/docs`. |
-
-### v0.2.2 β Event Bus + Triggers
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Event bus subscriptions | β
| Extensions register event patterns in manifest. Wired via `bus.Subscribe()` on startup. Async handler invocation. |
-| Webhook triggers | β
| Inbound HTTP at `/api/v1/hooks/:package_id/:slug`. HMAC-SHA256 verification. Synchronous Starlark handler response. |
-| Scheduled tasks | β
| User-created cron tasks with restricted sandbox (no raw HTTP, no DB table creation). Runs as creator identity. Templates from extensions. Dedicated schedules API. |
-| Trigger admin API | β
| CRUD for triggers + schedules. Enable/disable, execution logs, per-package listing. |
-
-### v0.2.3 β SDK + Task Extension
-
-| Step | Status | Description |
-|------|--------|-------------|
-| SDK stabilization | β
| `sw.api.ext()`, `sw.storage`, `sw.theme.tokens`, `sw.ui`, `sw.slots`, `sw.actions` β six new SDK modules for extension development |
-| Task extension | β
| Full task surface rebuilt as Starlark extension: CRUD API, kanban/list views, event triggers, webhook integration, notifications on completion |
-
-### v0.2.4 β Shell Navigation + Schedules
-
-| Step | Status | Description |
-|------|--------|-------------|
-| SDK Topbar | β
| `sw.shell.Topbar` β composable navigation bar (title + extension slot + bell + user menu). Surfaces get consistent nav for free. |
-| Schedules surface | β
| New `packages/schedules/` wrapping kernel cron API. Table view, cron preview, enable/disable, manual run, execution logs. |
-| Manifest icons | β
| `icon` field in manifest.json (emoji). Surfaces API returns icon. UserMenu renders per-surface icons. |
-| UserMenu cleanup | β
| Removed dead Chat/Notes/Projects links. Menu driven by surfaces API. Core surfaces filtered. |
-| isAdmin RBAC fix | β
| `can.js` isAdmin() now checks `surface.admin.access` grant instead of deprecated role column. |
-
-### v0.2.5 β UI Polish + Dead Code Audit
-
-| Step | Status | Description |
-|------|--------|-------------|
-| UI bug pass | β
| Reviewed admin, settings, login, welcome surfaces in light/dark themes. Fixed settings crash (models API undefined). Removed dead chat settings from both user and admin settings. |
-| Dead code sweep | β
| Removed ~700 lines: orphaned CSS, dead Go types, stale event code, unused test helpers, dead settings UI (chat defaults, system prompt, default model, policies, web search, compaction, memory). |
-| Template cleanup | β
| Removed stale CSS link tags and orphaned chat-pane.html. Updated doc comments throughout. |
-| Package proof-of-concept status | β
| Created README.md for tasks + schedules with graduation criteria. Added missing manifest icons. |
-| Welcome surface | β
| New fallback surface when no extensions installed. Topbar + welcome card with admin link. Replaces `/admin` as final redirect target. |
-| Default surface routing | β
| Resolution chain: user preference β global config β first extension β `/welcome`. Users can set personal default in Settings > General. Admin sets global default in Admin > Settings. |
-| Admin navigation | β
| Replaced Back button with UserMenu in admin topbar. Eliminates back-button infinite loop. |
-
-### v0.2.6 β Admin Settings Audit
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Admin settings E2E | β
| All 7 admin settings sections verified (default surface, registration, banner, message bar, footer, vault, email). Dead code removed: `sectionCategory()` pruned of AI/routing/channel vestiges, `PublicSettings()` stripped of chat-era fields (system_prompt, retention_ttl, paste_to_file, allow_user_personas), dead `PolicyDefaults` removed (allow_raw_model_access, default_model), dead policy lookups removed (kb_direct_access), test seed data cleaned. |
-| Packages surface | β
| Package list loads (17 total/17 enabled), type filters work, enable/disable visible, settings/export buttons present, core package (admin) protected. Removed dead `chat` from CORE_IDS. |
-
-### v0.2.7 β User Settings Audit
-
-| Step | Status | Description |
-|------|--------|-------------|
-| User settings E2E | β
| All 6 user settings sections verified (General, Appearance, Profile, Teams, Connections, Notifications). Dead code removed: BYOK nav section + state, personas gate filter, Message Font Size slider, `auth.permissions.changed` listener. localStorage key renamed `cs-appearance` β `sb-appearance` with one-time migration. |
-| Visibility gating | β
| Dead BYOK/personas policy lookups removed from bootstrap and permissions handlers. `allow_user_byok` removed from `PublicSettings`. `PolicyDefaults` cleaned of `allow_user_byok` and `allow_user_personas`. Dead `msgFont` early-apply removed from base template. Stale policy-gating test assertions replaced. No empty nav sections remain. |
-
-### v0.2.8 β Team Admin Settings Audit (Pass 1)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Team admin E2E | β
| Audited team member management, settings cascade, role assignment. Removed dead code: `HasPrivateProviderRequirement` (BYOK vestige), `UserRole` on `TeamMember` (deprecated role column), `allow_team_providers` policy default, dead personas/providers/models ICD tests. |
-| Workflow stage UI cleanup | β
| Removed dead personas dropdown, `history_mode` selector, stale `chat_only` mode. Updated `STAGE_MODES` to match backend CHECK constraint (`form_only`, `form_chat`, `review`, `custom`). Removed stale comments referencing deleted files. |
-
-### v0.2.9 β Builtin Extension Retirement
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Retire builtin seeder | β
| Removed `SeedBuiltinPackages`, `seed_packages.go`, Dockerfile COPY, and `extensions/builtin/` directory. These extensions are dormant until a chat surface exists to consume them. |
-| Convert to regular packages | β
| Repackaged 6 extensions as standard directories in `packages/` with `js/` layout and `"requires": ["chat"]` manifest metadata. Built via `build.sh` like all other packages. No auto-install β explicit install only. |
-
-## v0.3.x β Workflow Architecture
-
-Workflows are the core platform capability. This series implements the
-full multi-step automation system with team role integration and
-finalizes the extension lifecycle model.
-
-### v0.3.0 β Schema Redesign + Stage CRUD Modernization (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Workflow schema redesign | β
| Dropped `persona_id`, `history_mode` from `workflow_stages`. Renamed `transition_rules` β `stage_config`. Updated `stage_mode` CHECK to `(form, review, delegated, automated)`. Added `audience`, `stage_type`, `starlark_hook`, `branch_rules`. Both PG + SQLite. |
-| Model + store updates | β
| Go structs, constants, and PG/SQLite queries updated. New `ValidStageTypes`, `ValidAudiences` maps. Routing engine reads `branch_rules` directly. |
-| Handler + Starlark updates | β
| Stage CRUD validation for new fields. Package export/import updated. Hook handler reads `stage_config`. Starlark module exposes `audience`, `stage_type`. |
-| Frontend updates | β
| Team-admin and admin stage editors updated with new modes, audience selector, stage type selector, conditional Starlark hook input. Fixed admin `STAGE_MODES` bug. |
-
-### v0.3.1 β Instance + Assignment Tables + Store (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Instance schema | β
| `workflow_instances` table in 007_workflows.sql (PG + SQLite). Tracks execution state: workflow_version, current_stage, stage_data, status, entry_token. |
-| Assignment schema | β
| `workflow_assignments` table. Per-stage queue: instance_id, stage, team_id, assigned_to, status, review_data. Optimistic claim lock. |
-| Models + store interface | β
| `WorkflowInstance`, `WorkflowAssignment` structs. 15 store methods for instance lifecycle and assignments across PG + SQLite. |
-| Event types | β
| `workflow.started`, `workflow.cancelled`, `workflow.error` added to bus route table. |
-
-### v0.3.2 β Workflow Engine + Handlers (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Store tests | β
| Round-trip tests for all 15 v0.3.1 store methods (instance + assignment CRUD). TruncateAll updated for new tables. |
-| Stage execution engine | β
| `server/workflow/engine.go` β Start, Advance (with branch_rules), Cancel. Merges stage_data, creates assignments, emits events. |
-| Automated stages | β
| `server/workflow/automated.go` β fire Starlark hook, auto-advance, cycle guard (max 10 consecutive). |
-| Instance handlers | β
| HTTP API: Start, GetInstance, Advance, Cancel, ListInstances. Team-scoped mirrors. |
-| Assignment handlers | β
| HTTP API: Claim, Unclaim, Complete, Cancel, ListByTeam, ListMine. Claimer identity verification. |
-| Starlark module expansion | β
| `workflow.get_instance()`, `workflow.list_instances()` (read-only). Mutating builtins deferred pending interface extraction. |
-
-### v0.3.3 β Public Entry + Background Jobs (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Public entry | β
| `StartPublic`, `ResumePublic`, `AdvancePublic` β token-based anonymous workflow participation. Public routes at `/api/v1/public/workflows/`. Audience-gated: only `public` stages can be advanced anonymously. |
-| SLA scanner | β
| Background goroutine (5-min interval) checking active instances against per-stage `sla_seconds`. Fires `workflow.sla_breach` event, marks breach in instance metadata (idempotent). |
-| Staleness sweep | β
| Per-workflow `staleness_timeout_hours` column. Scanner marks idle instances as `stale`, cancels open assignments, fires `workflow.stale` event. |
-
-### v0.3.4 β Team Roles + Multi-party Validation (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Team roles | β
| Removed CHECK constraint on `team_members.role`. Custom roles stored in `teams.settings["roles"]`. Team roles API (`GET/PUT /teams/:teamId/roles`). Role-based stage assignment via `stage_config.required_role`. Frontend: dynamic role selects, role management panel. |
-| Multi-party sign-off | β
| `workflow_signoffs` table (both dialects). `StageConfig.validation` with `required_approvals`, `required_role`, `reject_action`. Engine validation gate in `advanceInternal`. `SubmitSignoff` engine method. Signoff HTTP API (`POST/GET /instances/:iid/signoffs`). Frontend signoff panel in monitor tab. |
-| Extension lifecycle | β
| Design doc: `docs/DESIGN-EXTENSION-LIFECYCLE.md`. Permanent vs PoC classification, graduation criteria, install model. |
-| Trigger composition | β
| Design doc: `docs/DESIGN-TRIGGER-COMPOSITION.md`. Triggers/schedules can start workflows, workflows emit events, no circular invocation. |
-
-### v0.3.5 β Settings Audit + ICD + Tests
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Clone endpoint | β
| `POST /api/v1/workflows/:id/clone` β deep copy workflow + stages. Handler-level composition of existing store methods. |
-| Integration tests | β
| 7 engine-level tests in `workflow_engine_test.go`: full lifecycle, branch routing, public entry, signoff gate, rejection, cancel-clears-assignments, error cases. 28 total tests. |
-| Settings audit pass 2 | β
| Added `staleness_timeout_hours` field to workflow editor. Added collapsible branch_rules JSON editor in stage form. |
-| ICD update | β
| Fixed stale Workflow/Stage schemas. Added WorkflowInstance, WorkflowAssignment, WorkflowSignoff schemas. Added ~20 new endpoints: instances, assignments, signoffs, public entry, clone, team roles. |
-
-### v0.3.6 β Example Workflows + Interactive Demo
-
-Four installable `.pkg` workflow packages that prove the engine works
-end-to-end, plus a demo surface for guided exploration.
-See `docs/DEMO-WORKFLOWS.md` for full stage definitions and Starlark code.
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Bug Report Triage | β
| Public entry, progressive fieldsets, severity-based branch routing, team assignment, SLA timer. Pure-manifest `.pkg`. |
-| Employee Onboarding | β
| Starlark automated stages (`db.insert`, `notifications.send`), signoff gate with `required_role`, rejection reroute. `.pkg` with `script.star`. |
-| Content Approval | β
| Multi-party signoff (quorum of 2), rejection reroute creating review cycle. `.pkg` demonstrating signoff + reroute loop. |
-| Webhook Notifier | β
| Starlark `http.post` + `connections.get` for outbound webhooks, delivery logging to ext_data. Proves HTTP + connections modules. |
-| Demo surface | β
| Browser-tier walkthrough: workflow cards, stage diagrams, Starlark viewer, "Try It" buttons, API curl examples. Auto-installed, removable. |
-| Engine context fix | β
| Add `started_by` to automated stage Starlark context dict (backward-compatible). |
-| SLA package installer | β
| Added `sla_seconds` support to `workflowPkgStage` struct and install/export paths. |
-| Snapshot format fix | β
| `parseSnapshotStages` helper handles both wrapped and legacy snapshot formats. |
-| Workflow adoption | β
| `POST /teams/:teamId/workflows/:id/adopt` + `GET .../available`. Team-admin "Adopt Global" button. `TeamID` in `WorkflowPatch`. |
-| Extension SDK boot | β
| `base.html` loads Preact globals + `boot()` for extension surfaces (was missing since Scorched Earth IV). |
-| Admin teams tab fix | β
| Extract `.data` from paginated response in admin teams list. |
-| Documentation | β
| `docs/DEMO-WORKFLOWS.md`: feature capability matrix, stage flows, API walkthrough, per-package READMEs. |
-| Review pass | | Docker E2E walkthrough: install all packages, adopt into team, activate, publish, run each workflow end-to-end. Fix remaining UI/UX issues. |
-
-### v0.3.7 β Package Audit (complete)
-
-Verified all 16 packages install correctly. Packages with unmet `requires` auto-set to dormant.
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Manifest fixes | β
| Fixed 6 chat-extension manifests (`"name"` β `"title"`, was blocking install). Added explicit `"type": "surface"` to hello-dashboard, icd-test-runner, sdk-test-runner. |
-| Dormant status | β
| Added `dormant` to `packages.status` CHECK constraint (both dialects). Installer auto-detects unmet `requires` and sets `status=dormant, enabled=false`. Enable endpoint returns 409 for dormant packages. |
-| Functional audit | β
| Navigated to every surface in browser. 7 working: schedules, tasks, team-activity-log, git-board, hello-dashboard, icd-test-runner, sdk-test-runner. 2 broken: dashboard + editor (depend on removed `sw.*` imperative SDK) β tagged `requires: ["legacy-sdk"]` β dormant. |
-| Chat-dependency tagging | β
| 6 chat-dependent extensions (csv-table, diff-viewer, js-sandbox, katex-renderer, mermaid-renderer, regex-tester) install as dormant. git-board and gitea-client are standalone. |
-| Dependency check fix | β
| Relaxed library dependency validation to allow `pending_review` libraries (gitea-client declares permissions). Only `suspended`/`dormant` libraries blocked. |
-| Admin UI | β
| Dormant badge, disabled Enable button with tooltip, Dormant stat card in admin packages view. |
-| Tests | β
| 4 handler tests (SetStatus dormant, enable blocked, surfaces exclude dormant, admin list includes dormant). All existing tests pass. |
-
-### v0.3.8 β Distribution
-
-Builder image for faster builds, bundled packages for zero-config first run.
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Builder image | β
| `Dockerfile.builder` with cached Go modules + Node vendor libs for faster custom builds. |
-| Bundled packages | β
| Dockerfile stage builds all 12 non-dormant packages into production image. `InstallBundledPackages` auto-installs on first run (skip-if-present). `SKIP_BUNDLED_PACKAGES=true` to disable. `BUNDLED_PACKAGES` allowlist for selective install (Helm/K8s). Migration 012 adds `bundled` source to CHECK constraint. |
-| Distribution docs | β
| `docs/DISTRIBUTION.md` β quick start, bundled packages, builder image, custom builds, production deployment. |
-| Tests | β
| 6 handler tests (fresh install, skip existing, missing dir, empty dir, dormant handling, allowlist filtering). All existing tests pass. |
-
-## v0.4.x β Notes Surface
-
-Obsidian-style notes rebuilt as an installable surface package.
-Zero platform special-casing. Proves the full extension stack E2E.
-
-### v0.4.0 β Core Notes CRUD + Markdown Editor
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Package scaffold | β
| `packages/notes/` with manifest, script.star, JS, CSS, README. Type: full, tier: starlark. |
-| Notes CRUD backend | β
| Starlark `on_request()` β list, create, get, update, delete, search, stats. Lightweight list projection (no body). |
-| Notes table | β
| `ext_notes_notes` β title, body (TEXT), folder_id, pinned, archived, creator_id, updated_at. |
-| Markdown editor | β
| Preact+htm frontend: sidebar note list, title input, markdown textarea, auto-save (1s debounce). |
-| Live preview | β
| Inline markdown renderer (~100 lines): headings, bold, italic, code, links, lists, blockquotes, hr. |
-| Search | β
| Client-side LIKE search over title/body. Search bar in sidebar. |
-| Pin/archive | β
| Pin notes to top, soft-delete via archive, hard delete with `?hard=1`. |
-
-### v0.4.1 β Folders + Navigation Tree (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Folders table | β
| `ext_notes_folders` β name, parent_id, creator_id, sort_order. Indexed on parent_id and creator_id. |
-| Folder CRUD API | β
| 5 Starlark handlers: list, create, update, delete folders + `POST /notes/move`. Delete cascade orphans notes and reparents child folders. |
-| Folder tree UI | β
| `FolderTree` + `FolderNode` components. Flat-to-tree builder, expand/collapse, depth indentation, inline rename via context menu. |
-| Folder filtering | β
| Click folder β filter notes. "All Notes" / "Unfiled" virtual views. New notes inherit active folder. |
-| Editor folder select | β
| Dropdown with nested hierarchy (`β` prefix). Moves notes between folders via move-note API. |
-| Updated stats | β
| Stats include `unfiled` and `folders` counts. |
-
-### v0.4.2 β Tags + Search (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Tags table | β
| `ext_notes_tags` β note_id, tag. Indexed on both columns for bidirectional lookup. |
-| Tag CRUD API | β
| 3 Starlark handlers: list all unique tags, get tags for note, replace tags for note (delete+reinsert). Tags normalized: lowercase, trimmed, deduped. |
-| Tags in list/get/search | β
| `_list_notes` batch-fetches all tags in one query, attaches `tags` array to each item. `_get_note` includes tags. `_search_notes` matches against tags. |
-| Tag cascade delete | β
| Hard-deleting a note also deletes its tag rows. Stats include `tags` count. |
-| Tag input in editor | β
| `TagInput` component: removable pills + text field, comma/Enter to add, autocomplete dropdown from all tags. |
-| Tag pills on cards | β
| `NoteCard` renders up to 3 tag pills with "+N" overflow. |
-| Tag filter in sidebar | β
| `TagFilter` component: clickable pills, toggle active tag, client-side note filtering. |
-| Drag-and-drop | β
| `NoteCard` is draggable. `FolderNode`, "All Notes", and "Unfiled" are drop targets. Uses existing move-note API. |
-| Folder context menu | β
| Replaced `prompt()` hack with proper right-click popup menu: Add subfolder, Rename, Delete. Positioned at click coordinates, dismissed on outside click. |
-
-### v0.4.3 β Backlinks + Wikilinks (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| Links table | β
| `ext_notes_links` β source_id, target_id, link_text. Indexed on both source_id and target_id for bidirectional lookup. |
-| Wikilink extraction | β
| `_extract_wikilinks()` parses `[[...]]` using `split("[[")` + `find("]]")` (no regex/while in Starlark). Deduplicates by lowercase. |
-| Link sync on save | β
| `_sync_links()` called from `_create_note` and `_update_note`. Delete-all + reinsert pattern (matches tags). Resolves titles to note IDs (case-insensitive). Unresolved links stored with `target_id=""`. |
-| Cascade delete | β
| Hard-deleting a note removes both outgoing links (source_id) and incoming backlinks (target_id). |
-| Link endpoints | β
| `GET /links/:note_id` (outgoing) and `GET /backlinks/:note_id` (incoming, enriched with source titles). |
-| Wikilink preview | β
| `[[Note Title]]` rendered as clickable accent-colored links in markdown preview. Unresolved links styled in danger/red. |
-| Wikilink navigation | β
| Clicking a resolved wikilink navigates to the target note. Clicking an unresolved (red) link creates the note, navigates to it, and re-saves the source so the link resolves. |
-| Backlinks panel | β
| Collapsible panel below editor showing all notes linking to current note. Click to navigate. Hidden when empty. |
-| Stats update | β
| `/stats` includes `links` count. Notes package version bumped to 0.4.0. |
-
-### v0.4.4 β Rich Editor + Import/Export (complete)
-
-| Step | Status | Description |
-|------|--------|-------------|
-| CM6 integration | β
| Load vendored CodeMirror 6 bundle via dynamic `
+
+
+{{end}}
diff --git a/server/static/swagger.html b/server/static/swagger.html
index 1556b74..b4795c3 100644
--- a/server/static/swagger.html
+++ b/server/static/swagger.html
@@ -256,7 +256,7 @@