@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +2 -2
- package/docs/plugins.md +832 -0
- package/examples/plugins/echo/README.md +60 -61
- package/examples/plugins/echo/package.json +2 -1
- package/examples/plugins/echo/register.ts +143 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
- package/openapi.yaml +5 -15
- package/package.json +2 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -4
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
- package/src/__tests__/conversation-agent-loop.test.ts +39 -42
- package/src/__tests__/conversation-clean-command.test.ts +2 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
- package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
- package/src/__tests__/conversation-starter-routes.test.ts +6 -14
- package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
- package/src/__tests__/db-acp-history.test.ts +0 -101
- package/src/__tests__/dynamic-page-surface.test.ts +0 -31
- package/src/__tests__/file-write-tool.test.ts +0 -63
- package/src/__tests__/gateway-only-guard.test.ts +2 -12
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/injector-background-turn.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +6 -34
- package/src/__tests__/injector-disk-pressure.test.ts +34 -77
- package/src/__tests__/injector-document-comments.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
- package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
- package/src/__tests__/pipeline-runner.test.ts +554 -0
- package/src/__tests__/plugin-api-shim.test.ts +6 -3
- package/src/__tests__/plugin-bootstrap.test.ts +23 -12
- package/src/__tests__/plugin-registry.test.ts +49 -3
- package/src/__tests__/plugin-types.test.ts +70 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +1 -4
- package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +3 -1
- package/src/__tests__/subagent-notify-parent.test.ts +3 -1
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +286 -54
- package/src/acp/__tests__/client-handler.test.ts +0 -40
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
- package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
- package/src/acp/agent-process.ts +1 -61
- package/src/acp/client-handler.ts +0 -31
- package/src/acp/prepare-agent-env.ts +29 -83
- package/src/acp/resolve-agent.test.ts +7 -320
- package/src/acp/resolve-agent.ts +18 -182
- package/src/acp/session-manager.ts +73 -495
- package/src/acp/types.ts +0 -8
- package/src/agent/compaction-circuit.ts +102 -60
- package/src/agent/loop.ts +59 -32
- package/src/api/responses/conversation-message.ts +1 -7
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +0 -1
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +31 -83
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
- package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +5 -14
- package/src/config/schemas/heartbeat.ts +0 -9
- package/src/context/strip-injections.ts +2 -8
- package/src/context/window-manager.ts +1 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
- package/src/daemon/conversation-agent-loop.ts +279 -62
- package/src/daemon/conversation-runtime-assembly.ts +69 -106
- package/src/daemon/conversation-store.ts +90 -9
- package/src/daemon/conversation-workspace.ts +0 -17
- package/src/daemon/conversation.ts +6 -0
- package/src/daemon/external-plugins-bootstrap.ts +11 -11
- package/src/daemon/handlers/conversations.ts +1 -3
- package/src/daemon/handlers/skills.ts +1 -4
- package/src/daemon/lifecycle.ts +0 -21
- package/src/daemon/server.ts +0 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
- package/src/heartbeat/heartbeat-run-store.ts +1 -23
- package/src/heartbeat/heartbeat-service.ts +0 -26
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
- package/src/ipc/skill-routes/memory.ts +2 -4
- package/src/memory/conversation-starter-checkpoints.ts +0 -1
- package/src/memory/db-init.ts +0 -2
- package/src/memory/job-handlers/conversation-starters.ts +2 -13
- package/src/memory/jobs-worker.ts +1 -1
- package/src/memory/migrations/index.ts +0 -1
- package/src/memory/schema/acp.ts +0 -4
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +4 -13
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
- package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
- package/src/plugin-api/index.ts +5 -0
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
- package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
- package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +19 -8
- package/src/plugins/defaults/compaction/terminal.ts +73 -0
- package/src/plugins/defaults/index.ts +5 -3
- package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
- package/src/plugins/defaults/overflow-reduce/package.json +15 -0
- package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +293 -6
- package/src/plugins/registry.ts +37 -9
- package/src/plugins/types.ts +336 -32
- package/src/plugins/user-loader.ts +127 -30
- package/src/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +0 -6
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
- package/src/runtime/__tests__/agent-wake.test.ts +5 -5
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +3 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
- package/src/runtime/routes/acp-routes.test.ts +25 -89
- package/src/runtime/routes/acp-routes.ts +29 -81
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +10 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -15
- package/src/runtime/routes/conversation-starter-routes.ts +7 -13
- package/src/runtime/routes/conversations-import-routes.ts +7 -24
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -18
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/memory-v3-routes.ts +6 -16
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
- package/src/runtime/routes/work-items-routes.ts +4 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +4 -2
- package/src/subagent/manager.ts +5 -17
- package/src/tools/acp/list-agents.test.ts +1 -7
- package/src/tools/acp/spawn.test.ts +55 -158
- package/src/tools/acp/spawn.ts +72 -47
- package/src/tools/acp/steer.test.ts +8 -105
- package/src/tools/acp/steer.ts +17 -48
- package/src/tools/apps/executors.ts +8 -13
- package/src/tools/filesystem/write.ts +0 -34
- package/src/tools/subagent/spawn.ts +4 -2
- package/src/tools/ui-surface/definitions.ts +4 -25
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
- package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
- package/examples/plugins/echo/hooks/stop.ts +0 -16
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
- package/examples/plugins/echo/src/emit.ts +0 -19
- package/src/__tests__/compaction-circuit.test.ts +0 -258
- package/src/__tests__/compaction-direct.test.ts +0 -132
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
- package/src/acp/__tests__/agent-process.test.ts +0 -161
- package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
- package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
- package/src/acp/auto-install.test.ts +0 -196
- package/src/acp/auto-install.ts +0 -177
- package/src/acp/feature-gate.test.ts +0 -48
- package/src/acp/feature-gate.ts +0 -34
- package/src/acp/resume-hint.ts +0 -25
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
- package/src/daemon/conversation-registry.ts +0 -159
- package/src/daemon/overflow-reduction-loop.ts +0 -230
- package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
- package/src/plugins/defaults/compaction/compact.ts +0 -59
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
- package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
- package/src/tools/acp/context.ts +0 -20
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
- /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
|
@@ -1,355 +1,512 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: app-builder
|
|
3
|
-
description: Build
|
|
3
|
+
description: Build interactive apps, dashboards, calculators, games, trackers, tools, landing pages, and data visualizations with Preact/TypeScript/CSS
|
|
4
|
+
compatibility: "Designed for Vellum personal assistants"
|
|
4
5
|
metadata:
|
|
5
|
-
emoji: "
|
|
6
|
+
emoji: "🏗️"
|
|
6
7
|
vellum:
|
|
7
8
|
display-name: "App Builder"
|
|
8
9
|
activation-hints:
|
|
9
|
-
- "User asks to build
|
|
10
|
-
- "User asks to visualize
|
|
11
|
-
- "
|
|
12
|
-
avoid-when:
|
|
13
|
-
- "User wants a complex app, a multi-user app, or something to publish, deploy, or hand off to others — route to a local project folder + coding agent instead (see Scope)"
|
|
10
|
+
- "User asks to build an app, landing page, website, dashboard, tool, calculator, game, tracker, or interactive page"
|
|
11
|
+
- "User asks to visualize data or says 'let's visualize this' — use the app sandbox to build interactive visualizations"
|
|
12
|
+
- "ALWAYS prefer the app sandbox over building standalone web apps, local servers, or outputting raw HTML/CSS/JS in chat — even when the user says 'make this an app' or 'turn this into an app'"
|
|
14
13
|
---
|
|
15
14
|
|
|
16
|
-
You
|
|
15
|
+
You are an expert app builder and visual designer. When the user asks you to create an app, tool, or utility, you immediately design a data schema, choose a stunning visual direction, build the interface, and open it - all in one step. You don't discuss or ask for permission to be creative. You ARE the designer: you pick the colors, the layout, the atmosphere, the micro-interactions. Your apps should make users stop and say "whoa" - they should feel designed, not generated.
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
**Every app gets its own visual identity.** A plant tracker should feel earthy and green. A finance dashboard should feel precise and navy. A fitness app should feel energetic and purple. Apps should look like they were designed by a boutique studio for that specific domain - not like generic branded tools. Think standalone premium product, not template.
|
|
19
18
|
|
|
20
|
-
**
|
|
19
|
+
**Your default behavior:** Build immediately. The user types "build me a habit tracker" and you deliver a complete, polished app with a domain-matched color palette, atmospheric background, and thoughtful interactions. Don't ask what colors they want. Don't show wireframes. Just build something stunning and let them refine from there.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
**Design quality is delegated to the `frontend-design` skill, so you must also load/install that before proceeding.** That skill defines your aesthetic principles: typography, color strategy, motion, spatial composition, and visual detail. Follow it completely for every build. This skill (app-builder) handles the technical infrastructure: sandbox constraints, data persistence, widget API, app lifecycle, and interaction patterns.
|
|
23
22
|
|
|
24
|
-
##
|
|
23
|
+
## Filesystem Layout
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Apps live under `{workspaceDir}/data/apps/`. Each app has a slug-based layout:
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
```
|
|
28
|
+
{workspaceDir}/data/apps/
|
|
29
|
+
<slug>.json # App metadata
|
|
30
|
+
<slug>/ # App directory (contains all app files)
|
|
31
|
+
index.html # Legacy single-file entry point (do not create for new apps)
|
|
32
|
+
pages/ # Legacy additional pages (do not create for new apps)
|
|
33
|
+
records/ # Data records (one JSON file per record)
|
|
34
|
+
src/ # Source files (multi-file TSX apps, formatVersion: 2)
|
|
35
|
+
dist/ # Compiled output (multi-file TSX apps)
|
|
36
|
+
<slug>.preview # Preview image (auto-generated)
|
|
37
|
+
```
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
### Metadata JSON (`<slug>.json`)
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
2. **Establish a project folder** (propose a path, or use one they name).
|
|
34
|
-
3. **Hand off to a coding agent:** `skill_load("acp")` → `acp_spawn({ task: "<what to build>", cwd: "<folder>" })` (agent defaults to `claude`), then follow the `acp` skill.
|
|
41
|
+
Fields: `id`, `name`, `description`, `icon`, `schemaJson`, `createdAt`, `updatedAt`, `formatVersion`, `dirName`.
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
**Important:** Legacy `htmlDefinition` and `pages` content is NOT stored in the metadata JSON — it lives as separate files inside the app directory (`index.html` and `pages/`). Do not create new single-file apps or new `pages/` directories.
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
### Records
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
Each record is a JSON file at `<slug>/records/<uuid>.json` with shape:
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
```json
|
|
50
|
+
{ "id": "<uuid>", "appId": "<app-id>", "data": { ... }, "createdAt": "...", "updatedAt": "..." }
|
|
51
|
+
```
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
2. Otherwise `app_list(query: "<what they said>")` returns matches with `app_id` + `name`. `app_list()` with no query lists everything.
|
|
46
|
-
3. One match → open it. Multiple → list them and ask which. None → say so, show what exists, offer to build it.
|
|
53
|
+
### Multi-file TSX Apps
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
All new apps use `formatVersion: 2`: source files live under `src/` and compiled output lives under `dist/`. The build system compiles TSX to JS automatically when `app_refresh` is called.
|
|
49
56
|
|
|
50
|
-
##
|
|
57
|
+
## Responsive Baseline & Mobile-First Mode
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
Every app must be responsive across the full width range — phone (~360px) to desktop (~1400px+). The conversation context's `<turn_context>` block carries an `interface:` field. Visual interfaces are `macos`, `ios`, and `web`; the field doesn't toggle responsiveness on or off — it shifts the **design priority**. Non-visual values like `phone` represent voice channels that can't render apps at all and don't need to be considered here.
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
/
|
|
56
|
-
|
|
57
|
-
<slug>/
|
|
58
|
-
src/ # Source files (TSX) — what you write
|
|
59
|
-
dist/ # Compiled output — auto-generated by app_refresh
|
|
60
|
-
records/ # Data records (one JSON file per record)
|
|
61
|
-
<slug>.preview # Preview image (auto-generated)
|
|
62
|
-
```
|
|
61
|
+
- **`interface: ios`** (or any future mobile-web / android identifier) — mobile-first build. Design the narrow viewport first and progressively enhance upward at wider widths.
|
|
62
|
+
- **`interface: macos` / `web`** — desktop-first build. Design the larger composition first; the narrow-width fallback must still meet the universal baseline below but doesn't need to feel like a native mobile app.
|
|
63
|
+
- **Field absent or ambiguous** — default to desktop-first unless the user's request itself implies phone use ("for my iPhone home screen", "a tap-tracker I'll use on the go").
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
### Universal baseline (every build, regardless of interface)
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
These rules aren't mobile-specific — they're touch / responsive a11y baselines that any user-resizable WebView needs.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
**Viewport & safe areas**
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
- Viewport meta: `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">`. Never set `user-scalable=no` — it blocks accessibility zoom.
|
|
72
|
+
- Pad the root container with `env(safe-area-inset-*)` so content clears the notch / home indicator when the app is opened on a notched device: `padding-top: max(var(--v-spacing-lg), env(safe-area-inset-top))`, mirrored for `-bottom`/`-left`/`-right`. On desktop the env vars resolve to `0` and the `max()` falls through to the design-system value — no-op.
|
|
73
|
+
- Use `100dvh` (dynamic viewport height), not `100vh`, for full-height containers. `100vh` creates a scroll-jump on every mobile browser regardless of build mode.
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
**Form controls**
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
- `<input>`, `<textarea>`, `<select>` must be `font-size: 16px` or larger, or iOS Safari will zoom on focus and break the layout. This applies to every build — anyone may open a desktop-built app on their phone.
|
|
78
|
+
- Add `inputmode` to text fields with structured input: `numeric` for integers, `decimal` for amounts, `email`, `tel`, `url`. Add matching `autocomplete` and `autocapitalize` hints where appropriate.
|
|
75
79
|
|
|
76
|
-
**
|
|
77
|
-
- Viewport meta: `width=device-width, initial-scale=1, viewport-fit=cover`. Never `user-scalable=no` (blocks accessibility zoom).
|
|
78
|
-
- Pad the root with `env(safe-area-inset-*)` so content clears the notch: `padding-top: max(var(--v-spacing-lg), env(safe-area-inset-top))`, mirrored for the other sides.
|
|
79
|
-
- Full-height containers use `100dvh`, not `100vh`.
|
|
80
|
-
- Form controls (`input`/`textarea`/`select`) must be `font-size: 16px`+ or iOS Safari zooms on focus. Add `inputmode` (`numeric`/`decimal`/`email`/`tel`/`url`).
|
|
81
|
-
- Interactive elements ≥44×44pt (`.v-button` already complies; custom controls set `min-height: 44px`). Gate hover behind `@media (hover: hover)`.
|
|
82
|
-
- Fluid widths only — `%`, `fr`, `minmax`, `clamp()`, never fixed `px` on containers. Size chart containers in `vw`/`%`. At narrow widths, collapse tables into stacked label-value cards.
|
|
80
|
+
**Touch & hover**
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
- Interactive elements (buttons, list rows, nav items, toggles, icon buttons) must be ≥44×44pt. `.v-button` already meets this; for custom controls, set `min-height: 44px` explicitly.
|
|
83
|
+
- Gate hover affordances behind `@media (hover: hover)` so they don't stick on touch devices visiting a desktop-built app.
|
|
84
|
+
- Disable text selection on app chrome (headers, nav, buttons) with `user-select: none; -webkit-user-select: none` so long-press doesn't pop the iOS selection menu over interactive elements.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
**Layout fluidity**
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
- Fluid widths only — no fixed-pixel layouts. Use `%`, `fr`, `minmax`, `clamp()` instead of `px` on container widths.
|
|
89
|
+
- Horizontal-scroll tables don't work on narrow screens. At narrow widths, collapse rows into stacked cards with labels and values arranged vertically. (Mobile-first builds can use cards everywhere; desktop-first builds can keep the table at wide widths and switch to cards below a breakpoint.)
|
|
90
|
+
- `vellum.widgets.*` chart containers should be sized in `vw`/`%`, not fixed `px`. Prefer simpler chart types (sparkline, bar) at narrow widths — dense multi-series charts lose detail.
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
### Mobile-first priorities (`interface: ios` or future mobile identifier)
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
| --- | --- |
|
|
94
|
-
| Backgrounds | `--v-bg`, `--v-surface`, `--v-surface-border` |
|
|
95
|
-
| Text | `--v-text`, `--v-text-secondary`, `--v-text-muted` |
|
|
96
|
-
| Accent | `--v-accent`, `--v-accent-hover` |
|
|
97
|
-
| Status | `--v-success`, `--v-danger`, `--v-warning` |
|
|
98
|
-
| Spacing | `--v-spacing-xxs`(2) `-xs`(4) `-sm`(8) `-md`(12) `-lg`(16) `-xl`(24) `-xxl`(32) `-xxxl`(48) |
|
|
99
|
-
| Radius | `--v-radius-xs`(2) `-sm`(4) `-md`(8) `-lg`(12) `-xl`(16) `-pill`(999) |
|
|
100
|
-
| Shadows | `--v-shadow-sm/md/lg` |
|
|
101
|
-
| Typography | `--v-font-family`, `--v-font-mono`, `--v-font-size-xs`(10) `-sm`(11) `-base`(14) `-lg`(17) `-xl`(22) `-2xl`(26) |
|
|
102
|
-
| Animation | `--v-duration-fast`(.15s) `-standard`(.25s) `-slow`(.4s) |
|
|
103
|
-
| Palettes | `--v-slate/emerald/violet/indigo/rose/amber-{950..50}` |
|
|
104
|
-
| Constant | `--v-aux-white` (always `#FFF` both modes — text on filled/accent backgrounds) |
|
|
94
|
+
These are the **design priority differences** that mobile-first builds adopt on top of the universal baseline. They reflect "narrow viewport is the primary experience, wider widths progressively enhance."
|
|
105
95
|
|
|
106
|
-
**
|
|
96
|
+
**Typography**
|
|
107
97
|
|
|
108
|
-
|
|
98
|
+
- Default body text to `--v-font-size-lg` (17px), not `--v-font-size-base` (14px) — the desktop base is too small to read comfortably on a phone. At wider widths the same 17px reads fine.
|
|
109
99
|
|
|
110
|
-
|
|
100
|
+
**Spacing**
|
|
111
101
|
|
|
112
|
-
|
|
102
|
+
- Bump default vertical rhythm one step (e.g. `--v-spacing-md` → `--v-spacing-lg` between cards and sections) so users can comfortably scroll-stop on each item.
|
|
113
103
|
|
|
114
|
-
|
|
104
|
+
**Layout**
|
|
115
105
|
|
|
116
|
-
|
|
106
|
+
- One column as the **default**, not as a narrow-width fallback. `flex-direction: column` first; opt into a multi-column grid only above a width breakpoint (`@media (min-width: 720px)`). No side rails, no two-pane master/detail, no fixed-width sidebars in the default view.
|
|
107
|
+
- Bottom-anchor the primary action (e.g. "Add", "Save") so the thumb can reach it: `position: sticky; bottom: env(safe-area-inset-bottom)` over the scrolling list. On wider widths you may re-flow it back inline.
|
|
108
|
+
- Replace side modals and popovers with bottom sheets that animate up from the bottom edge.
|
|
117
109
|
|
|
118
|
-
|
|
110
|
+
**Interaction**
|
|
119
111
|
|
|
120
|
-
|
|
112
|
+
- Skip the Tab/Enter/Esc keyboard pattern from "Interaction Standards" as the primary affordance — on mobile, focus comes from taps, submit from the soft keyboard's `return`, dismissal from a swipe down on bottom sheets. Keyboard support is still allowed (external-keyboard users exist on iPad) but isn't the design driver.
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
vellum.widgets.toast("Saved!", "success", 4000); // success | error | warning | info
|
|
137
|
-
vellum.widgets.countdown("el", "2025-12-31T00:00:00Z", { onComplete:()=>{} });
|
|
114
|
+
### Desktop-first priorities (`interface: macos` / `web`)
|
|
115
|
+
|
|
116
|
+
The default behaviour the rest of this skill describes — multi-column composition, hover-rich affordances, denser information, side modals, inline primary actions. The universal baseline above is the floor: the narrow-width view must still work and follow the touch / responsive a11y rules, but it doesn't need to feel native to mobile.
|
|
117
|
+
|
|
118
|
+
Everything else in this skill applies unchanged.
|
|
119
|
+
|
|
120
|
+
## Workflow
|
|
121
|
+
|
|
122
|
+
### 0. Preflight — Pin to a high-quality model
|
|
123
|
+
|
|
124
|
+
App building is design-heavy judgment work — color palettes, layout decisions, component architecture, micro-interactions. A stronger model produces meaningfully better apps: more creative visual directions, cleaner component boundaries, fewer generic patterns. Before building, check whether the conversation is already pinned to the quality profile:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
assistant inference session list
|
|
138
128
|
```
|
|
139
129
|
|
|
140
|
-
|
|
130
|
+
If no session is active, check the current active profile:
|
|
141
131
|
|
|
142
|
-
|
|
132
|
+
```
|
|
133
|
+
assistant config get llm.activeProfile
|
|
134
|
+
```
|
|
143
135
|
|
|
144
|
-
|
|
136
|
+
If the profile is already `quality-optimized`, skip the rest of this step and proceed to Step 1.
|
|
145
137
|
|
|
146
|
-
|
|
138
|
+
**If the active profile is `balanced`, `cost-optimized`, or any non-quality profile, you MUST ask the user for permission before switching. Do NOT open an inference session without explicit user confirmation.** Use the `ui_show` tool to present an inline `confirmation` surface and wait for the action. Do not call the shell command `assistant ui confirm`; that CLI-mediated confirmation can block the build flow before the app work starts.
|
|
147
139
|
|
|
148
|
-
|
|
140
|
+
```
|
|
141
|
+
ui_show({
|
|
142
|
+
surface_type: "confirmation",
|
|
143
|
+
title: "Use quality model for this app?",
|
|
144
|
+
data: {
|
|
145
|
+
message: "The current model profile is `<profile>`. App building works best with `quality-optimized` because it makes better design decisions, writes cleaner components, and produces more visually polished results.",
|
|
146
|
+
detail: "Choose whether to switch for this build or keep the current profile and build now.",
|
|
147
|
+
confirmLabel: "Switch for this build",
|
|
148
|
+
cancelLabel: "Keep current profile"
|
|
149
|
+
},
|
|
150
|
+
display: "inline",
|
|
151
|
+
await_action: true
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If `ui_show` is unavailable or the current channel cannot render confirmation surfaces, ask the user directly in conversation as a fallback. Wait for the user's answer before proceeding.
|
|
156
|
+
|
|
157
|
+
**Only if the user confirms**, open an inference session:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
assistant inference session open quality-optimized --ttl 1h
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If `quality-optimized` isn't a profile name on this workspace, list the available profiles and open against the highest-quality one:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
assistant config get llm.profiles
|
|
167
|
+
assistant inference session open <profile-name> --ttl 1h
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The `--ttl 1h` gives comfortable headroom for a typical app build without leaving a forever-pinned session if the close in Step 6 is skipped.
|
|
149
171
|
|
|
150
|
-
|
|
172
|
+
**If the user declines, do not switch profiles.** Proceed with the current profile — the build still works, the model just won't be pinned. Skip the close in Step 6 too.
|
|
151
173
|
|
|
152
|
-
|
|
174
|
+
If `assistant inference session` isn't available on this binary, proceed without it.
|
|
153
175
|
|
|
154
|
-
###
|
|
176
|
+
### 1. Gather Requirements
|
|
155
177
|
|
|
156
|
-
|
|
178
|
+
**Default: just build.** When a user says "build me a habit tracker," don't ask what colors they want or how many fields to include. Immediately:
|
|
179
|
+
|
|
180
|
+
1. Envision the ideal version of this app - what would make someone excited to use it?
|
|
181
|
+
2. Pick a distinctive visual direction following the `frontend-design` skill
|
|
182
|
+
3. Design a clean data schema
|
|
183
|
+
4. Build the complete, polished app with animations, interactions, and empty states
|
|
184
|
+
|
|
185
|
+
**Make creative decisions on behalf of the user.** They want to be delighted, not consulted. Pick the accent color. Choose between a dark moody aesthetic or a light airy one. Decide if cards should have glassmorphism or layered shadows. Add a background pattern or gradient. These are YOUR decisions as the designer.
|
|
186
|
+
|
|
187
|
+
**Build all new apps as multi-file TSX projects.** They give you component reuse, TypeScript safety, and cleaner organization.
|
|
188
|
+
|
|
189
|
+
**Only ask questions when the request is genuinely ambiguous** - e.g., "build me an app" with no indication of what kind. Even then, prefer building something impressive based on context clues over asking a battery of questions.
|
|
190
|
+
|
|
191
|
+
**When in doubt, build something impressive** and let the user refine. The first impression matters most - a beautiful app with the wrong shade of blue is easy to fix. A correct but ugly app is hard to come back from.
|
|
192
|
+
|
|
193
|
+
**There are no "quick" builds.** Every app, regardless of complexity, gets the full design treatment. A 3-field form and a 20-section dashboard get the same design care. The only difference is scope, not quality.
|
|
194
|
+
|
|
195
|
+
### 2. Design the Data Schema
|
|
196
|
+
|
|
197
|
+
Create a JSON Schema that defines the structure of a single record. Every record automatically gets `id`, `appId`, `createdAt`, and `updatedAt` - you only define user-facing fields.
|
|
198
|
+
|
|
199
|
+
Schema guidelines:
|
|
200
|
+
|
|
201
|
+
- Use `type: "object"` at the top level
|
|
202
|
+
- Define `properties` for each field
|
|
203
|
+
- Supported types: `string`, `number`, `boolean`
|
|
204
|
+
- Add a `required` array for mandatory fields
|
|
205
|
+
- Keep schemas reasonably flat - encode complex nested data as JSON strings when needed
|
|
206
|
+
|
|
207
|
+
Example schema for a project tracker:
|
|
157
208
|
|
|
158
209
|
```json
|
|
159
210
|
{
|
|
160
211
|
"type": "object",
|
|
161
212
|
"properties": {
|
|
162
|
-
"title":
|
|
163
|
-
"status": {
|
|
213
|
+
"title": { "type": "string" },
|
|
214
|
+
"status": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"enum": ["backlog", "in-progress", "review", "done"]
|
|
217
|
+
},
|
|
218
|
+
"priority": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"enum": ["low", "medium", "high", "critical"]
|
|
221
|
+
},
|
|
222
|
+
"description": { "type": "string" },
|
|
223
|
+
"tags": { "type": "string" }
|
|
164
224
|
},
|
|
165
|
-
"required": ["title"]
|
|
225
|
+
"required": ["title", "status"]
|
|
166
226
|
}
|
|
167
227
|
```
|
|
168
228
|
|
|
169
|
-
|
|
229
|
+
### 3. Build the App
|
|
230
|
+
|
|
231
|
+
Apps are rendered inside a sandboxed WebView on macOS.
|
|
170
232
|
|
|
171
|
-
|
|
233
|
+
#### Multi-file TSX projects
|
|
172
234
|
|
|
173
|
-
|
|
235
|
+
Build apps as multi-file TSX projects. You get component reuse, TypeScript type-checking, and clean file organization. The build system uses esbuild to bundle everything automatically. Do not create root-level `index.html` files or `pages/` directories for new apps.
|
|
174
236
|
|
|
175
|
-
|
|
237
|
+
**Project structure:**
|
|
176
238
|
|
|
177
239
|
```
|
|
178
240
|
src/
|
|
179
|
-
index.html #
|
|
180
|
-
main.tsx
|
|
181
|
-
components/
|
|
182
|
-
|
|
241
|
+
index.html # Entry HTML - minimal shell, loads compiled bundle
|
|
242
|
+
main.tsx # App entry - renders root component into #app
|
|
243
|
+
components/ # Preact functional components
|
|
244
|
+
Header.tsx
|
|
245
|
+
RecordList.tsx
|
|
246
|
+
...
|
|
247
|
+
styles.css # Global styles (imported from TSX)
|
|
183
248
|
```
|
|
184
249
|
|
|
250
|
+
**Preact usage:**
|
|
251
|
+
|
|
185
252
|
```tsx
|
|
186
253
|
import { render } from "preact";
|
|
254
|
+
import { useState, useEffect } from "preact/hooks";
|
|
187
255
|
import { App } from "./components/App";
|
|
188
|
-
|
|
256
|
+
|
|
189
257
|
render(<App />, document.getElementById("app")!);
|
|
190
258
|
```
|
|
191
259
|
|
|
192
|
-
|
|
260
|
+
Functional components with hooks:
|
|
193
261
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
3. **`app_refresh`** ONCE at the end to compile.
|
|
262
|
+
```tsx
|
|
263
|
+
import { FunctionComponent } from "preact";
|
|
197
264
|
|
|
198
|
-
|
|
265
|
+
interface Props {
|
|
266
|
+
title: string;
|
|
267
|
+
count: number;
|
|
268
|
+
}
|
|
199
269
|
|
|
200
|
-
|
|
270
|
+
export const Header: FunctionComponent<Props> = ({ title, count }) => {
|
|
271
|
+
return (
|
|
272
|
+
<header>
|
|
273
|
+
<h1>{title}</h1>
|
|
274
|
+
<span className="badge">{count}</span>
|
|
275
|
+
</header>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**TypeScript:** Use types for props, state, and data records. Define shared types in a `types.ts` file when multiple components need them.
|
|
201
281
|
|
|
202
|
-
|
|
282
|
+
**CSS:** Import CSS files directly in TSX (`import './styles.css'`). You can also use inline styles via the `style` attribute on JSX elements.
|
|
203
283
|
|
|
204
|
-
|
|
284
|
+
**Custom routes in TSX:** Use `window.vellum.fetch()` to call custom route handlers from components — see the [Custom route handlers](#custom-route-handlers-user-defined-routes) section for full details:
|
|
205
285
|
|
|
206
|
-
|
|
286
|
+
```tsx
|
|
287
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
window.vellum.fetch("/v1/x/items")
|
|
291
|
+
.then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
|
|
292
|
+
.then(setItems)
|
|
293
|
+
.catch(console.error);
|
|
294
|
+
}, []);
|
|
295
|
+
```
|
|
207
296
|
|
|
208
|
-
|
|
297
|
+
**File workflow:** Pass all source files inline via the `source_files` parameter of `app_create`. This writes and compiles the real app in a single call — no scaffold placeholder, no separate `file_write` or `app_refresh` needed for initial creation. For subsequent edits, use `file_edit`/`file_write` then call `app_refresh` once.
|
|
209
298
|
|
|
210
|
-
-
|
|
211
|
-
- **`pages`** — retired. Multi-page apps use TSX components under `src/components/`.
|
|
212
|
-
- **`icon`** — NOT a top-level param. An emoji icon goes in `preview.icon` (e.g. `preview: { title: "Bean Coffee", icon: "☕" }`). For an AI-generated icon, call `app_generate_icon(app_id, description)` *after* the app exists.
|
|
213
|
-
- **A file path as a top-level key** (e.g. `"src/components/Header.tsx"`) — these go inside `source_files`, or in a `file_write` after `app_create`.
|
|
299
|
+
**Allowed third-party packages:** `date-fns`, `chart.js`, `lodash-es`, `zod`, `clsx`, `lucide`. Import them directly - esbuild resolves them at build time. No CDN imports. Note: `lucide` is the vanilla JS icon library (not `lucide-react`). Use its `createElement` or `createIcons` API, or manually inline SVG - do not import JSX icon components.
|
|
214
300
|
|
|
215
|
-
|
|
301
|
+
**Example - creating a multi-file project:**
|
|
216
302
|
|
|
217
303
|
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
304
|
+
app_create({
|
|
305
|
+
name: "Project Tracker",
|
|
306
|
+
description: "Track projects with status and priority",
|
|
307
|
+
schema_json: '{"type":"object","properties":{"title":{"type":"string"},"status":{"type":"string"}},"required":["title"]}',
|
|
308
|
+
preview: { title: "Project Tracker", icon: "📋" },
|
|
309
|
+
source_files: {
|
|
310
|
+
"src/index.html": `<!DOCTYPE html>
|
|
311
|
+
<html lang="en">
|
|
312
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
313
|
+
<title>Project Tracker</title></head>
|
|
314
|
+
<body><div id="app"></div></body>
|
|
315
|
+
</html>`,
|
|
316
|
+
"src/main.tsx": `import { render } from 'preact';
|
|
317
|
+
import { App } from './components/App';
|
|
318
|
+
import './styles.css';
|
|
319
|
+
|
|
320
|
+
render(<App />, document.getElementById('app')!);`,
|
|
321
|
+
"src/components/App.tsx": `import { FunctionComponent } from 'preact';
|
|
322
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
323
|
+
import { Header } from './Header';
|
|
324
|
+
|
|
325
|
+
export const App: FunctionComponent = () => {
|
|
326
|
+
const [records, setRecords] = useState([]);
|
|
327
|
+
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
window.vellum.fetch("/v1/x/projects")
|
|
330
|
+
.then((res) => res.ok ? res.json() : Promise.reject(res.status))
|
|
331
|
+
.then(setRecords)
|
|
332
|
+
.catch(console.error);
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<div className="app">
|
|
337
|
+
<Header title="Project Tracker" count={records.length} />
|
|
338
|
+
{/* ... */}
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
};`,
|
|
342
|
+
"src/components/Header.tsx": `import { FunctionComponent } from 'preact';
|
|
343
|
+
|
|
344
|
+
interface HeaderProps {
|
|
345
|
+
title: string;
|
|
346
|
+
count: number;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export const Header: FunctionComponent<HeaderProps> = ({ title, count }) => (
|
|
350
|
+
<header className="header">
|
|
351
|
+
<h1>{title}</h1>
|
|
352
|
+
<span className="badge">{count} items</span>
|
|
353
|
+
</header>
|
|
354
|
+
);`,
|
|
355
|
+
"src/styles.css": `.app { padding: var(--v-spacing-lg); }
|
|
356
|
+
.header { display: flex; justify-content: space-between; align-items: center; }
|
|
357
|
+
.badge { background: var(--v-accent); color: var(--v-aux-white); padding: var(--v-spacing-xs) var(--v-spacing-sm); border-radius: var(--v-radius-pill); }`
|
|
358
|
+
}
|
|
359
|
+
})
|
|
228
360
|
```
|
|
229
361
|
|
|
230
|
-
**
|
|
362
|
+
**Technical constraints (multi-file):**
|
|
231
363
|
|
|
232
|
-
|
|
364
|
+
- No CDN imports - use esbuild-resolved packages from the allowlist above
|
|
365
|
+
- Preact for UI (not React) - `import { render } from 'preact'`
|
|
366
|
+
- TypeScript encouraged for all `.tsx`/`.ts` files
|
|
367
|
+
- No external fonts, images, or resources - use system fonts and CSS/SVG for visuals
|
|
368
|
+
- Design responsively. Apps render at fluid, user-resizable widths — avoid fixed-pixel layouts
|
|
369
|
+
- The WebView blocks all navigation - links and form `action` attributes won't work
|
|
233
370
|
|
|
234
|
-
|
|
235
|
-
app_refresh(app_id)
|
|
236
|
-
```
|
|
371
|
+
#### Injected design system
|
|
237
372
|
|
|
238
|
-
|
|
373
|
+
A design system CSS is auto-injected inside a `@layer`, so your styles always take priority. It provides element defaults and automatic light/dark mode switching via `prefers-color-scheme`.
|
|
239
374
|
|
|
240
|
-
|
|
375
|
+
**Use `--v-*` variables and `.v-*` classes** - they handle light/dark mode automatically. No manual dark mode CSS needed.
|
|
241
376
|
|
|
242
|
-
|
|
243
|
-
|
|
377
|
+
Available design tokens:
|
|
378
|
+
|
|
379
|
+
| Category | Tokens |
|
|
380
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
381
|
+
| **Backgrounds** | `--v-bg`, `--v-surface`, `--v-surface-border` |
|
|
382
|
+
| **Text** | `--v-text`, `--v-text-secondary`, `--v-text-muted` |
|
|
383
|
+
| **Accent** | `--v-accent`, `--v-accent-hover` |
|
|
384
|
+
| **Status** | `--v-success`, `--v-danger`, `--v-warning` |
|
|
385
|
+
| **Spacing** | `--v-spacing-xxs` (2px) / `-xs` (4px) / `-sm` (8px) / `-md` (12px) / `-lg` (16px) / `-xl` (24px) / `-xxl` (32px) / `-xxxl` (48px) |
|
|
386
|
+
| **Radius** | `--v-radius-xs` (2px) / `-sm` (4px) / `-md` (8px) / `-lg` (12px) / `-xl` (16px) / `-pill` (999px) |
|
|
387
|
+
| **Shadows** | `--v-shadow-sm`, `--v-shadow-md`, `--v-shadow-lg` |
|
|
388
|
+
| **Typography** | `--v-font-family`, `--v-font-mono`, `--v-font-size-xs` (10px) / `-sm` (11px) / `-base` (14px) / `-lg` (17px) / `-xl` (22px) / `-2xl` (26px), `--v-line-height` |
|
|
389
|
+
| **Animation** | `--v-duration-fast` (0.15s) / `-standard` (0.25s) / `-slow` (0.4s) |
|
|
390
|
+
| **Palettes** | `--v-slate-{950..50}`, `--v-emerald-*`, `--v-violet-*`, `--v-indigo-*`, `--v-rose-*`, `--v-amber-*` |
|
|
391
|
+
| **Constant** | `--v-aux-white` (always `#FFFFFF` in both modes — use for text on filled/accent backgrounds) |
|
|
392
|
+
|
|
393
|
+
Utility classes: `.v-button` (`.secondary`/`.danger`/`.ghost`), `.v-card`, `.v-list`/`.v-list-item`, `.v-badge` (`.success`/`.warning`/`.danger`), `.v-input-row`, `.v-empty-state`, `.v-toggle`.
|
|
394
|
+
|
|
395
|
+
**Never hardcode `color: white` or `color: #fff`.** Use `var(--v-aux-white)` for text on filled/accent backgrounds, or `var(--v-text)` / `var(--v-text-secondary)` for text on surface backgrounds. Hardcoded white causes invisible text on light surfaces.
|
|
396
|
+
|
|
397
|
+
**Custom themes:** When the user wants a specific branded look, write complete CSS with hardcoded colors and `@media (prefers-color-scheme: dark)` for dark variants. Don't mix `--v-*` auto-switching variables with hardcoded colors in the same element.
|
|
398
|
+
|
|
399
|
+
**Theme detection in JavaScript:**
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
console.log(window.vellum.theme.mode); // 'light' or 'dark'
|
|
403
|
+
window.addEventListener("vellum-theme-change", (e) => {
|
|
404
|
+
console.log("Theme:", e.detail.mode);
|
|
405
|
+
});
|
|
244
406
|
```
|
|
245
407
|
|
|
246
|
-
|
|
408
|
+
#### Widget component library
|
|
247
409
|
|
|
248
|
-
|
|
410
|
+
A CSS/JS widget library is auto-injected alongside the design system. Use `.v-*` class names for standard UI patterns (tables, metrics, timelines, cards, etc.) and `window.vellum.widgets.*` JS utilities for charts, data formatting, and interactive behaviors. **ALWAYS use `vellum.widgets.*` chart functions** instead of hand-coding SVG/CSS charts.
|
|
249
411
|
|
|
250
|
-
|
|
412
|
+
For the full widget reference (class names, JS APIs, chart functions, formatting utilities), see **[Widget Component Library](references/WIDGETS.md)**.
|
|
251
413
|
|
|
252
|
-
|
|
253
|
-
- **`file_write`** — new files or full rewrites
|
|
254
|
-
- **Rename / metadata** — edit `/workspace/data/apps/<slug>.json` directly. Not a new app.
|
|
255
|
-
- **Full rebrand** — still iteration, edit the existing files.
|
|
414
|
+
#### Custom route handlers (user-defined routes)
|
|
256
415
|
|
|
257
|
-
|
|
416
|
+
When the app needs server-side persistence, custom API logic, or workspace file access, use **user-defined routes**. Route handlers are TypeScript/JavaScript files in the workspace `routes/` directory, served under `/v1/x/`. Call them from the frontend via `window.vellum.fetch("/v1/x/...")`. **Never use raw `fetch()` for `/v1/x/` routes** — it will fail in the sandboxed origin.
|
|
258
417
|
|
|
259
|
-
|
|
418
|
+
For handler conventions, examples, key rules, and frontend usage patterns, see **[Custom Route Handlers](references/CUSTOM_ROUTES.md)**.
|
|
260
419
|
|
|
261
|
-
|
|
420
|
+
For complete, copyable apps wiring this persistence pattern end-to-end (multi-file TSX frontend + `routes/*.ts` handler), see the **[example apps](references/examples/README.md)**: a [Focus Timer](references/examples/focus-timer.md) (append-only log), a [Habit Tracker](references/examples/habit-tracker.md) (full CRUD), and an [Expense Tracker](references/examples/expense-tracker.md) (create/read/delete + aggregation).
|
|
262
421
|
|
|
263
|
-
|
|
422
|
+
#### Client-side state management
|
|
264
423
|
|
|
265
|
-
|
|
424
|
+
`localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use custom routes for persistent app records, `localStorage` for UI preferences.
|
|
266
425
|
|
|
267
|
-
|
|
426
|
+
### 4. Create and Open the App
|
|
268
427
|
|
|
269
|
-
|
|
270
|
-
async function loadRecords() {
|
|
271
|
-
const res = await window.vellum.fetch("/v1/x/my-route");
|
|
272
|
-
if (!res.ok) { window.vellum.widgets.toast("Couldn't load", "error"); return []; }
|
|
273
|
-
return res.json();
|
|
274
|
-
}
|
|
275
|
-
```
|
|
428
|
+
Call `app_create` with:
|
|
276
429
|
|
|
277
|
-
|
|
430
|
+
- `name`: Short descriptive name
|
|
431
|
+
- `description`: One-sentence summary
|
|
432
|
+
- `schema_json`: JSON schema as string
|
|
433
|
+
- `source_files`: Map of relative file paths to contents (e.g. `{"src/main.tsx": "...", "src/styles.css": "..."}`). **Always include this** with the complete app source — it writes, compiles, and opens the real app in a single call.
|
|
434
|
+
- `auto_open`: (optional, defaults to `true`) Shows an inline preview card in chat after the app is built. Only fires when real source files are provided (not for scaffold-only apps).
|
|
435
|
+
- `preview`: Always include - `title` (required), `subtitle`, `description`, `icon` (image URL preferred, emoji fallback), `metrics` (up to 3 key-value pills)
|
|
278
436
|
|
|
279
|
-
|
|
280
|
-
useEffect(() => {
|
|
281
|
-
window.vellum.fetch("/v1/x/items")
|
|
282
|
-
.then(res => res.ok ? res.json() : Promise.reject(res.status))
|
|
283
|
-
.then(setItems)
|
|
284
|
-
.catch(() => window.vellum.widgets.toast("Couldn't load", "error"));
|
|
285
|
-
}, []);
|
|
286
|
-
```
|
|
437
|
+
Do not pass `html` or `pages` to `app_create`; those single-file shortcuts are retired.
|
|
287
438
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
// routes/items.ts
|
|
292
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
293
|
-
import { join } from "node:path";
|
|
294
|
-
export const description = "Item CRUD — JSON file storage"; // optional, for `assistant routes list`
|
|
295
|
-
const FILE = join(process.env.VELLUM_WORKSPACE_DIR!, "data", "items.json");
|
|
296
|
-
const load = () => existsSync(FILE) ? JSON.parse(readFileSync(FILE, "utf-8")) : [];
|
|
297
|
-
const save = (x:unknown[]) => { mkdirSync(join(process.env.VELLUM_WORKSPACE_DIR!,"data"),{recursive:true}); writeFileSync(FILE, JSON.stringify(x,null,2)); };
|
|
298
|
-
|
|
299
|
-
export function GET(): Response { return Response.json(load()); }
|
|
300
|
-
export async function POST(req: Request): Promise<Response> {
|
|
301
|
-
const item = { id: crypto.randomUUID(), ...(await req.json()), createdAt: new Date().toISOString() };
|
|
302
|
-
const items = load(); items.push(item); save(items);
|
|
303
|
-
return Response.json(item, { status: 201 });
|
|
304
|
-
}
|
|
305
|
-
```
|
|
439
|
+
The app is NOT opened in a workspace panel automatically - users open it via the 'Open App' button on the inline card.
|
|
306
440
|
|
|
307
|
-
|
|
441
|
+
### 5. Handle Iteration
|
|
308
442
|
|
|
309
|
-
|
|
443
|
+
When the user requests changes, prefer **`file_edit`** over rewriting the entire file.
|
|
310
444
|
|
|
311
|
-
|
|
445
|
+
- **`file_edit`** - preferred for targeted changes (styles, bugs, features). Provide the full file path (e.g. `{workspaceDir}/data/apps/<slug>/src/components/App.tsx`).
|
|
446
|
+
- **`file_write`** - for creating new files or full rewrites.
|
|
447
|
+
- **`app_refresh`** - call ONCE after all file changes are complete to trigger compilation and surface refresh.
|
|
448
|
+
- For metadata changes (`name`, `description`, `schemaJson`, etc.), edit the `<slug>.json` file directly with `file_edit`, then call `app_refresh`.
|
|
312
449
|
|
|
313
|
-
|
|
450
|
+
After making all file changes, call `app_refresh(app_id)` once to compile and refresh the UI. Do NOT call it after every individual file edit — batch your changes first.
|
|
314
451
|
|
|
315
|
-
|
|
316
|
-
- **Confirm destructive actions** — `window.vellum.confirm(title, message)` (returns `Promise<boolean>`) before deleting or resetting.
|
|
317
|
-
- **Validate forms** before submit, show errors inline, disable submit during async.
|
|
318
|
-
- **Loading states** — skeleton or spinner, never a blank screen.
|
|
319
|
-
- **Designed empty states** — `.v-empty-state` when there's no data.
|
|
452
|
+
Apps should have multiple source files under `src/` (`styles.css`, components, helpers, etc.). Import CSS and modules from TSX so esbuild includes them in the compiled output.
|
|
320
453
|
|
|
321
|
-
###
|
|
454
|
+
### 6. Close the inference session
|
|
322
455
|
|
|
323
|
-
|
|
456
|
+
If you opened an inference session in Step 0, close it now:
|
|
324
457
|
|
|
325
|
-
|
|
458
|
+
```
|
|
459
|
+
assistant inference session close
|
|
460
|
+
```
|
|
326
461
|
|
|
327
|
-
|
|
462
|
+
If you skipped the open in Step 0 (because the user declined, the CLI didn't have the command, or the profile was already quality), skip this step too.
|
|
328
463
|
|
|
329
|
-
|
|
464
|
+
## Interaction Standards
|
|
330
465
|
|
|
331
|
-
|
|
466
|
+
Every app must meet these baselines:
|
|
332
467
|
|
|
333
|
-
|
|
468
|
+
- **Feedback for every action:** Use `vellum.widgets.toast()` after creates, deletes, updates, and errors.
|
|
469
|
+
- **Confirmation for destructive actions:** Use `window.vellum.confirm(title, message)` before deleting or resetting. Returns `Promise<boolean>`.
|
|
470
|
+
- **Form validation:** Validate before submit, show errors inline, disable submit during async operations.
|
|
471
|
+
- **Loading states:** Never show a blank screen while data loads. Use skeleton shimmer or spinners.
|
|
472
|
+
- **Keyboard navigation:** `Tab` between elements, `Enter` to submit, `Escape` to close/cancel. *(De-prioritised on mobile-first builds — see [Responsive Baseline & Mobile-First Mode](#responsive-baseline--mobile-first-mode).)*
|
|
334
473
|
|
|
335
|
-
|
|
474
|
+
## Presentation Slide Design
|
|
336
475
|
|
|
337
|
-
|
|
476
|
+
Slides are a different domain from apps. Skip app-specific patterns (contextual headers, search/filter, toast notifications, form validation, custom routes). Slides are static content — build navigation and layouts with custom HTML/CSS.
|
|
338
477
|
|
|
339
|
-
|
|
340
|
-
- [ ] **Sandbox path:** `app_create` returned an `app_id`; all files written via `file_write`; `app_refresh` ran ONCE clean; `app_open(open_mode: "preview")` rendered the card; user told what was built (3-6 bullets); iterations reflected live
|
|
341
|
-
- [ ] **Handoff path:** project folder established; coding agent spawned via `acp_spawn({ task, cwd })`; user told work continues in the folder
|
|
478
|
+
**Key principles:**
|
|
342
479
|
|
|
343
|
-
|
|
480
|
+
- One idea per slide - understood in 3 seconds
|
|
481
|
+
- Layout variety - 3+ different types per deck, never consecutive same-type
|
|
482
|
+
- 8 layout types: Title, Stats, Bullets, Quote, Comparison, Timeline, Visual/Immersive, Closing/CTA
|
|
483
|
+
- Bold backgrounds - dark, gradient, or strongly tinted
|
|
484
|
+
- Max 6 bullets per slide, max 3 sentences body text
|
|
485
|
+
- Never go below 15px for any visible text
|
|
486
|
+
|
|
487
|
+
## Error Handling
|
|
488
|
+
|
|
489
|
+
- All `window.vellum.fetch()` calls to custom routes must be wrapped in `try/catch` with user-friendly feedback. Always check `res.ok` before parsing the response body.
|
|
490
|
+
- Never let a failed operation silently pass - always show a toast or inline error.
|
|
491
|
+
- If the page loads with no data, show a designed empty state (`.v-empty-state`).
|
|
492
|
+
- For forms, show validation errors inline next to the relevant field.
|
|
493
|
+
|
|
494
|
+
## App Interaction Hooks
|
|
495
|
+
|
|
496
|
+
Proactively wire `window.vellum.sendAction()` hooks so the assistant stays aware of meaningful user interactions. Two patterns: **reactive** hooks (trigger assistant response) and **silent** hooks (`state_update` — accumulate context without interrupting). Wire hooks during the initial build, don't wait for the user to ask.
|
|
497
|
+
|
|
498
|
+
For examples, reactive vs silent guidance, and per-app-type recommendations, see **[App Interaction Hooks](references/INTERACTION_HOOKS.md)**.
|
|
499
|
+
|
|
500
|
+
## Actionable UI
|
|
501
|
+
|
|
502
|
+
When the user wants to triage or bulk-act on items, generate an interactive UI with selectable items and action buttons.
|
|
344
503
|
|
|
345
|
-
|
|
504
|
+
1. Fetch data with relevant tools
|
|
505
|
+
2. Render a `dynamic_page` with selectable items and action buttons
|
|
506
|
+
3. User selects + clicks action - UI sends `surfaceAction` with action ID and selected IDs
|
|
507
|
+
4. Execute tools, update UI with `ui_update`, show feedback via `widgets.toast()`
|
|
508
|
+
5. Use `window.vellum.confirm()` for destructive actions
|
|
346
509
|
|
|
347
|
-
|
|
510
|
+
## External Links
|
|
348
511
|
|
|
349
|
-
|
|
350
|
-
- `DESIGN_SYSTEM.md` — token table, utility classes, theme detection
|
|
351
|
-
- `WIDGETS.md` — widget classes, chart utilities, formatting helpers
|
|
352
|
-
- `CUSTOM_ROUTES.md` — server-side persistence and custom API routes
|
|
353
|
-
- `examples/` — complete copyable example apps
|
|
354
|
-
- `INTERACTION_HOOKS.md` — sendAction patterns, reactive vs silent
|
|
355
|
-
- `SLIDES.md` — presentation slide design
|
|
512
|
+
Use `vellum.openLink(url, metadata)` to make items clickable. Construct deep-link URLs when possible. Include `metadata.provider` and `metadata.type` for context.
|