@desplega.ai/agent-swarm 2.7.6 → 2.7.7

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.
Files changed (244) hide show
  1. package/dist/{acp-adapter-bp91d37f.js → acp-adapter-7b4vk3h5.js} +5 -4
  2. package/dist/{actions-yj0eeaqh.js → actions-3tt153af.js} +18 -18
  3. package/dist/{amp-adapter-4pg9h34q.js → amp-adapter-njpts0yr.js} +1 -1
  4. package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
  5. package/dist/{app-909e97cr.js → app-nky4378f.js} +3 -3
  6. package/dist/{assistant-qdyb5fyy.js → assistant-jnb669pa.js} +19 -19
  7. package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
  8. package/dist/{boot-reembed-fk0s9z18.js → boot-reembed-9mgvaztt.js} +1 -1
  9. package/dist/{boot-reembed-gs1rzrnh.js → boot-reembed-qs2djdej.js} +1 -1
  10. package/dist/{boot-scrub-sweep-4qfd4kfx.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
  11. package/dist/{boot-seeds-yr00k13d.js → boot-seeds-5cm6ambd.js} +4 -4
  12. package/dist/{boxd-vdmfssd9.js → boxd-nv507va8.js} +1 -1
  13. package/dist/{claude-adapter-qta1f1xn.js → claude-adapter-fn287zmm.js} +5 -4
  14. package/dist/{claude-managed-setup-sc6j4dm0.js → claude-managed-setup-wtdz2mj2.js} +1 -1
  15. package/dist/{claude-sdk-session-zgrz6yvc.js → claude-sdk-session-x91kqyhm.js} +8288 -8224
  16. package/dist/{cli-wp9sp1qb.js → cli-08bmz9z9.js} +1 -1
  17. package/dist/{cli-yy3rhsdw.js → cli-1c6tzt8g.js} +7 -7
  18. package/dist/{cli-q0k1w5zd.js → cli-35rsp8ye.js} +1 -1
  19. package/dist/{cli-xnrwazav.js → cli-3fg0bdcv.js} +1 -1
  20. package/dist/{cli-89knwags.js → cli-5b456bpf.js} +2 -2
  21. package/dist/{cli-drr5v6av.js → cli-5jnhv72d.js} +1 -1
  22. package/dist/{cli-dxae2yg7.js → cli-5zx699j9.js} +2 -2
  23. package/dist/{cli-0at8w34w.js → cli-6g0hb4cp.js} +8 -8
  24. package/dist/{cli-e3r2neck.js → cli-6pfd54vx.js} +5 -5
  25. package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
  26. package/dist/{cli-ekwks2vs.js → cli-72v799xn.js} +1 -1
  27. package/dist/cli-82j36f2m.js +45 -0
  28. package/dist/{cli-yx4kfngt.js → cli-8305gbq0.js} +29 -16
  29. package/dist/{cli-4necwtsv.js → cli-8sckt74b.js} +8 -8
  30. package/dist/{cli-vvpx3gsx.js → cli-95jzxcp3.js} +1 -1
  31. package/dist/{cli-hr2e2fft.js → cli-adeegh7d.js} +7 -7
  32. package/dist/{cli-m48g6p95.js → cli-afkqkrtw.js} +3 -3
  33. package/dist/{cli-0qatpy8q.js → cli-ayayvfg4.js} +24 -4
  34. package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
  35. package/dist/{cli-zm72anhg.js → cli-bfv7vdg6.js} +9 -9
  36. package/dist/{cli-hgaazk69.js → cli-caj33vcq.js} +5 -5
  37. package/dist/{cli-3ypk9vk1.js → cli-cb2a8pna.js} +1 -1
  38. package/dist/{cli-waqbxg08.js → cli-cppkf404.js} +17 -17
  39. package/dist/{cli-yn4kxs06.js → cli-ddaanqkd.js} +4 -4
  40. package/dist/{cli-386tyxxg.js → cli-e0kmphd6.js} +2 -2
  41. package/dist/{cli-54t00gt8.js → cli-e46hg0xw.js} +1 -1
  42. package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
  43. package/dist/{cli-436532ck.js → cli-excnnjg2.js} +165 -153
  44. package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
  45. package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
  46. package/dist/{cli-pd7vagab.js → cli-fyt2g4wd.js} +3 -3
  47. package/dist/{cli-gdhpcbhf.js → cli-g08zdaec.js} +1 -1
  48. package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
  49. package/dist/{cli-svj5090z.js → cli-gz6q4eve.js} +3 -3
  50. package/dist/{cli-8snvgs5r.js → cli-je518txm.js} +10 -10
  51. package/dist/{cli-rmmmrjkd.js → cli-jtjtabxe.js} +1 -1
  52. package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
  53. package/dist/{cli-es6va8jk.js → cli-k1nje3v3.js} +38 -43
  54. package/dist/{cli-p5gp8pcx.js → cli-k8196sh2.js} +6 -6
  55. package/dist/{cli-nwj0ee2w.js → cli-k899ehvh.js} +2 -2
  56. package/dist/{cli-decfb6v8.js → cli-m0da8j35.js} +23 -9
  57. package/dist/{cli-he9wchme.js → cli-mkk9rnzz.js} +4 -4
  58. package/dist/{cli-nhrvpaj7.js → cli-n6bv12jn.js} +510 -4
  59. package/dist/{cli-8bc1w0tw.js → cli-nmzhp1b5.js} +14 -12
  60. package/dist/{cli-0yv5z2pg.js → cli-p82cnnjq.js} +1 -1
  61. package/dist/{cli-c60vetmm.js → cli-pc6n7fre.js} +1 -1
  62. package/dist/{cli-xds5f77d.js → cli-pjctyea3.js} +2 -2
  63. package/dist/{cli-xbbndvjy.js → cli-pq73h5mz.js} +3 -3
  64. package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
  65. package/dist/{cli-xnqgpshy.js → cli-q3tkegby.js} +1 -1
  66. package/dist/cli-qghe0h66.js +67 -0
  67. package/dist/{cli-zbbwez0r.js → cli-qh4d4tvk.js} +4 -4
  68. package/dist/{cli-w6dt9ged.js → cli-rtt8g4v9.js} +1 -1
  69. package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
  70. package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
  71. package/dist/{cli-faxe6thg.js → cli-vz22fyqv.js} +186 -54
  72. package/dist/{cli-k2gzwsj2.js → cli-wk5yrfwd.js} +160 -26
  73. package/dist/{cli-shpbx5ee.js → cli-ws19768n.js} +1641 -49
  74. package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
  75. package/dist/{cli-kqdg1bsb.js → cli-yndmfxb3.js} +2 -2
  76. package/dist/{cli-bjb3z51z.js → cli-yyj3j5m4.js} +2 -2
  77. package/dist/cli.js +21 -21
  78. package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
  79. package/dist/{codex-adapter-ayd1fb6h.js → codex-adapter-cf2yxe9m.js} +3 -2
  80. package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
  81. package/dist/{codex-session-runner-jesa32xc.js → codex-session-runner-6vfzdgbd.js} +3 -2
  82. package/dist/{commands-hanewgf2.js → commands-t3mfy7vn.js} +3 -3
  83. package/dist/{controller-dt1n3c1q.js → controller-q7xrbaz0.js} +3 -3
  84. package/dist/{controllers-connect-xx8x64n5.js → controllers-connect-c6nm70kg.js} +19 -19
  85. package/dist/{cursor-adapter-9s4dwwdw.js → cursor-adapter-ftd81ga8.js} +1 -1
  86. package/dist/{db-3vttcbv4.js → db-dk572x25.js} +3 -3
  87. package/dist/{db-9dv0g1ab.js → db-vemtpmcc.js} +4 -2
  88. package/dist/{dispatcher-tx9hd1sv.js → dispatcher-kh7ecxh9.js} +17 -17
  89. package/dist/{dsh-adapter-007y4ejs.js → dsh-adapter-v8628r89.js} +1 -1
  90. package/dist/{e2b-we6hs6q5.js → e2b-fr12yccx.js} +1 -1
  91. package/dist/{e2b-a19d28rz.js → e2b-x6ywzak3.js} +1 -1
  92. package/dist/{easter-egg-3d-rgbrqb8s.js → easter-egg-3d-nenw4x63.js} +1 -1
  93. package/dist/{embedding-space-ny1e2940.js → embedding-space-0jepb239.js} +6 -6
  94. package/dist/{embeddings-2t386t13.js → embeddings-xxt49yxg.js} +1 -1
  95. package/dist/{execute-pksh0ceh.js → execute-1mhw6e4x.js} +1 -1
  96. package/dist/{external-channel-phgmhmd7.js → external-channel-55v12e60.js} +2 -2
  97. package/dist/{fx-adapter-fy8rr921.js → fx-adapter-bs8wqrk5.js} +6 -5
  98. package/dist/{google-generative-ai-8y3m3k4b.js → google-generative-ai-v94hr7h0.js} +5 -5
  99. package/dist/{google-vertex-w3v98n7b.js → google-vertex-g7vr6cf8.js} +5 -5
  100. package/dist/{grok-adapter-q0abvpaj.js → grok-adapter-gpjqmkcq.js} +6 -5
  101. package/dist/handlers-zxmp1ta4.js +32 -0
  102. package/dist/{heartbeat-kadyek8p.js → heartbeat-k42svnr0.js} +20 -20
  103. package/dist/{hook-9k551xnj.js → hook-114m7hap.js} +4 -3
  104. package/dist/{hook-5zs3msf6.js → hook-6nphweh0.js} +24 -9
  105. package/dist/{http-mhz9vw1f.js → http-r8cc4wz7.js} +1025 -173
  106. package/dist/index-a8phxqnk.js +37 -0
  107. package/dist/{index-vdygbq10.js → index-e3s8wmxy.js} +1 -1
  108. package/dist/{index-q361cg2f.js → index-fpk4bcd9.js} +1 -1
  109. package/dist/index-k6h00t4c.js +30 -0
  110. package/dist/{index-rvr00sg7.js → index-nh3wyqdt.js} +1017 -574
  111. package/dist/index-xrz7te8h.js +33 -0
  112. package/dist/{keepalive-w56f32cg.js → keepalive-x060nt9p.js} +3 -3
  113. package/dist/lead-956sd2yc.js +71 -0
  114. package/dist/{maintenance-7mdgxcea.js → maintenance-2xxfexhf.js} +1 -1
  115. package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
  116. package/dist/{oauth-refresh-sweep-29txy9vk.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
  117. package/dist/{onboard-kpsrkarj.js → onboard-zc48j237.js} +3 -3
  118. package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
  119. package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
  120. package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
  121. package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
  122. package/dist/{opencode-adapter-j25vm3rr.js → opencode-adapter-r690h1cr.js} +4 -3
  123. package/dist/{otel-impl-6kkct7a8.js → otel-impl-r5xbh81q.js} +4 -4
  124. package/dist/{pi-mono-adapter-exhkzhn2.js → pi-mono-adapter-c7v8sdec.js} +7 -6
  125. package/dist/{pricing-refresh-n2x7jh6k.js → pricing-refresh-3pmre11v.js} +3 -3
  126. package/dist/{rbac-roles-81ypx6es.js → rbac-roles-3vbshwsb.js} +1 -1
  127. package/dist/{rbac-roles-b5htkjby.js → rbac-roles-910mmkgz.js} +2 -2
  128. package/dist/{render-v2-qn6jmpk4.js → render-v2-pxykkbab.js} +3 -3
  129. package/dist/{resume-wd30wx23.js → resume-adda0bwy.js} +6 -6
  130. package/dist/{runtime-4hsg7h60.js → runtime-fnbm7stk.js} +1 -1
  131. package/dist/{session-access-5qvfevhk.js → session-access-2fvamrdc.js} +4 -4
  132. package/dist/{session-source-backfill-n1czavbr.js → session-source-backfill-08xvv0sm.js} +2 -2
  133. package/dist/{setup-dnb56qy5.js → setup-1aqmvmh1.js} +1 -1
  134. package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
  135. package/dist/{virtual-modules-76xxv9fs.js → virtual-modules-d2pjk9j1.js} +5 -5
  136. package/dist/{work-objects-4n21tfkm.js → work-objects-9s0yrm5j.js} +20 -20
  137. package/dist/{worker-follow-up-77hvzxww.js → worker-follow-up-3q78fqmr.js} +17 -17
  138. package/dist/worker-y15hy08h.js +71 -0
  139. package/openapi.json +197 -4
  140. package/package.json +5 -5
  141. package/src/apps/definition.ts +6 -5
  142. package/src/be/db/page-theme-tokens.ts +12 -0
  143. package/src/be/db/tasks/read.ts +9 -1
  144. package/src/be/db/tasks/write.ts +19 -0
  145. package/src/be/db-client.ts +107 -7
  146. package/src/be/db.ts +34 -2
  147. package/src/be/migrations/269_pages_design_system.sql +23 -0
  148. package/src/be/seed/schedules-seeder.ts +0 -10
  149. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
  150. package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
  151. package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
  152. package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
  153. package/src/be/seed-scripts/index.ts +9 -0
  154. package/src/be/seed-skills/bundled-files.generated.json +3 -3
  155. package/src/be/swarm-config-guard.ts +32 -0
  156. package/src/be/task-auto-title-listener.ts +127 -0
  157. package/src/commands/onboard/agent-fs-version.ts +1 -1
  158. package/src/commands/profile-sync.ts +151 -16
  159. package/src/commands/runner.ts +15 -7
  160. package/src/hooks/hook.ts +20 -9
  161. package/src/http/index.ts +3 -0
  162. package/src/http/pages-public.ts +93 -129
  163. package/src/http/pages.ts +94 -21
  164. package/src/pages/browser-sdk.ts +48 -21
  165. package/src/pages/color-literals.ts +115 -0
  166. package/src/pages/page-head.ts +766 -0
  167. package/src/pages/version.ts +3 -0
  168. package/src/providers/amp-adapter.ts +1 -1
  169. package/src/providers/codex-adapter.ts +1 -0
  170. package/src/providers/codex-app-server.ts +27 -1
  171. package/src/providers/pi-mono-extension.ts +30 -15
  172. package/src/tasks/follow-up-gate.ts +318 -0
  173. package/src/tasks/worker-follow-up.ts +21 -1
  174. package/src/tests/codex-app-server.test.ts +59 -0
  175. package/src/tests/codex-gateway-route.test.ts +50 -1
  176. package/src/tests/create-page-tool.test.ts +501 -0
  177. package/src/tests/db-client-busy-retry.test.ts +81 -0
  178. package/src/tests/db-task-read-contract.test.ts +1 -0
  179. package/src/tests/db-task-write-contract.test.ts +2 -0
  180. package/src/tests/db-tasks-read.test.ts +21 -0
  181. package/src/tests/fixtures/pages/head-v1.html +56 -0
  182. package/src/tests/follow-up-gate-shadow.test.ts +513 -0
  183. package/src/tests/identity-field-budget.test.ts +1 -1
  184. package/src/tests/internal-ai/credentials.test.ts +62 -0
  185. package/src/tests/internal-ai/task-title.test.ts +283 -0
  186. package/src/tests/merge-gate-detect-changes.test.ts +145 -0
  187. package/src/tests/page-proxy.test.ts +1 -1
  188. package/src/tests/page-session-dev-cookie.test.ts +188 -0
  189. package/src/tests/pages-frame-ancestors.test.ts +28 -1
  190. package/src/tests/pages-head-v2.test.ts +534 -0
  191. package/src/tests/pages-http.test.ts +330 -0
  192. package/src/tests/pages-public-html.test.ts +43 -0
  193. package/src/tests/pages-storage.test.ts +182 -0
  194. package/src/tests/pages-versioning.test.ts +72 -0
  195. package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
  196. package/src/tests/profile-sync.test.ts +11 -15
  197. package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
  198. package/src/tests/script-connections.test.ts +2 -0
  199. package/src/tests/seed-registry.test.ts +28 -6
  200. package/src/tests/seed-schedules.test.ts +61 -8
  201. package/src/tests/seed-scripts.test.ts +142 -2
  202. package/src/tests/swarm-diff.test.ts +121 -2
  203. package/src/tests/task-auto-title-config-guard.test.ts +37 -0
  204. package/src/tests/task-auto-title-listener.test.ts +165 -0
  205. package/src/tests/theme-import.test.ts +758 -0
  206. package/src/tests/themes-page-theme.test.ts +155 -0
  207. package/src/tests/themes-presets.test.ts +172 -0
  208. package/src/tests/workflow-script-principal.test.ts +2 -1
  209. package/src/themes/classics.ts +949 -0
  210. package/src/themes/import.ts +420 -0
  211. package/src/themes/page-fields.ts +40 -0
  212. package/src/themes/page-theme.ts +102 -0
  213. package/src/themes/presets.ts +561 -0
  214. package/src/themes/tokens.ts +218 -0
  215. package/src/tools/app-upsert.ts +1 -1
  216. package/src/tools/create-page.ts +118 -12
  217. package/src/tools/utils.ts +33 -0
  218. package/src/types.ts +45 -3
  219. package/src/utils/constants.ts +19 -0
  220. package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
  221. package/src/utils/internal-ai/credentials.ts +146 -93
  222. package/src/utils/internal-ai/index.ts +10 -0
  223. package/src/utils/internal-ai/models.ts +18 -0
  224. package/src/utils/internal-ai/task-title.ts +282 -0
  225. package/src/utils/page-session.ts +37 -0
  226. package/templates/schedules/daily-status-report/content.md +88 -2
  227. package/templates/schedules/daily-swarm-update-check/content.md +16 -40
  228. package/templates/skills/apps/SKILL.md +1 -1
  229. package/templates/skills/apps/content.md +1 -1
  230. package/templates/skills/heartbeat-runbook/content.md +1 -1
  231. package/templates/skills/pages/config.json +1 -1
  232. package/templates/skills/pages/content.md +539 -86
  233. package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
  234. package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
  235. package/templates/skills/pages/files/examples/report-page.html +125 -124
  236. package/dist/cli-2pnswgh8.js +0 -189
  237. package/dist/handlers-by3sfxyx.js +0 -32
  238. package/dist/index-4f2pvxae.js +0 -37
  239. package/dist/index-98gz7tmp.js +0 -30
  240. package/dist/index-k08z2cak.js +0 -33
  241. package/dist/lead-enq35nqp.js +0 -70
  242. package/dist/worker-gg1576j3.js +0 -70
  243. package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
  244. package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
@@ -1,12 +1,12 @@
1
- # Pages — DB-backed Static Artifacts
1
+ # Pages: DB-backed Static Artifacts
2
2
 
3
3
  DB-backed static content (HTML or JSON) served by the API directly. Cheap,
4
4
  versioned, share-able by URL. No server process, no tunnel, no port
5
5
  allocation.
6
6
 
7
7
  A page should be a clean human-facing artifact, not a raw dump with a URL. This
8
- skill covers both halves: **how to publish** a page, and **the taste baseline**
9
- every page should meet.
8
+ skill covers both halves: **how to publish** a page, and **the design system**
9
+ every page should use.
10
10
 
11
11
  > **Capability gate**: the `create_page` MCP tool is only available when the
12
12
  > agent's `CAPABILITIES` env var includes `pages`
@@ -29,10 +29,10 @@ in a single call), use pages. If it needs stored data and actions, use apps.
29
29
 
30
30
  Do **not** use pages for:
31
31
 
32
- - In-flight progress notes — use `store-progress.progress`
32
+ - In-flight progress notes: use `store-progress.progress`
33
33
  - Secrets, private credentials, or unapproved personal data
34
- - Large binary files — use agent-fs for PNG/MP4
35
- - Raw verbose logs — summarize, and link the raw file as an artifact
34
+ - Large binary files: use agent-fs for PNG/MP4
35
+ - Raw verbose logs: summarize, and link the raw file as an artifact
36
36
 
37
37
  ## Quick Start
38
38
 
@@ -47,7 +47,7 @@ Do **not** use pages for:
47
47
  "authMode": "authed",
48
48
  "body": "<!doctype html><html><body><h1>Q2 Status</h1>...</body></html>"
49
49
  }
50
- // → { id, version: 1, app_url, api_url }
50
+ // → { id, version: 1, designVersion: 2, app_url, api_url }
51
51
  ```
52
52
 
53
53
  Share `app_url` (the SPA route) for the general case; share `api_url` for a
@@ -56,8 +56,8 @@ no-SPA-required direct link.
56
56
  Use `authMode: "public"` only when the page is intentionally safe for anyone
57
57
  with the URL to view.
58
58
 
59
- A complete, styled report body that follows the baseline below is bundled at
60
- **`examples/report-page.html`** — copy it as your starting point rather than
59
+ A complete report built from the design system below is bundled at
60
+ **`examples/report-page.html`**. Copy it as your starting point instead of
61
61
  writing CSS from scratch.
62
62
 
63
63
  ### Authed JSON dashboard
@@ -80,28 +80,451 @@ swarm API.
80
80
 
81
81
  ---
82
82
 
83
- # Design baseline
83
+ # Design system
84
+
85
+ HTML pages with `designVersion: 2` get the swarm design system. It is the
86
+ dashboard's own look (the Hive preset), built from the dashboard's theme
87
+ tokens. One theme switch restyles the whole page. New pages get
88
+ `designVersion: 2` by default.
89
+
90
+ The design system styles HTML pages only. JSON pages render inside the
91
+ dashboard and use the dashboard theme.
92
+
93
+ Before you create a page, write a one-line design read for yourself:
94
+
95
+ > Reading this as: `<page kind>` for `<audience>`, using the design system plus `<density needs>`.
96
+
97
+ ## What a designVersion 2 page gets
98
+
99
+ The API injects these into the `<head>` of every designVersion 2 HTML page:
100
+
101
+ - **Element defaults**: plain HTML elements look finished with zero CSS. See Element defaults below for the full list.
102
+ - **Theme tokens**: CSS variables such as `var(--color-primary)`. Each color token has a light value and a dark value.
103
+ - **Primitives**: `swarm-*` classes for page structure, metrics, panels, badges, tables, buttons, and callouts.
104
+ - **Tailwind v4 utilities** that map to the tokens, such as `bg-card` and `text-muted-foreground`. Preflight stays off, so the element defaults stay.
105
+ - **Fonts**: Space Grotesk for text and Space Mono for code.
106
+ - **Light print**: print always uses the light tokens.
107
+
108
+ The element defaults, the primitives, and the Tailwind utilities sit in CSS
109
+ cascade layers. A Tailwind utility beats a primitive, and a primitive beats an
110
+ element default. Your own `<style>` rules are not in a layer, so they beat all
111
+ three. Use your own CSS for layout, and take every color from a token. For an
112
+ edge-to-edge layout, set `body { padding: 0 }`.
113
+
114
+ ## Element defaults
115
+
116
+ The head styles these elements. They need no class. Do not restyle an element
117
+ only to get the look that it already has.
118
+
119
+ | Element | Default look |
120
+ |---|---|
121
+ | `html`, `body` | The background and foreground tokens. The body uses `--font-sans` at 15px with a 1.55 line height. It has 24px of padding, and 16px at 640px wide and below. |
122
+ | `h1` to `h6` | Weight 600. `h1` is 30px, `h2` is 20px, `h3` is 16px, `h4` is 15px, and `h5` and `h6` are 13px. |
123
+ | `p`, `ul`, `ol`, `li` | Paragraphs and lists have a 12px bottom margin. List markers are muted. |
124
+ | `dl`, `dt`, `dd` | `dt` is weight 500. For a key and value grid, use `.swarm-kv`. |
125
+ | `a` | The primary color with a soft underline. The underline gets stronger on hover. |
126
+ | `strong`, `b` | Weight 600. |
127
+ | `mark` | A soft highlight in the pending tone. |
128
+ | `hr` | A 1px `--color-border-subtle` rule, with 32px above and below. |
129
+ | `blockquote` | Muted text with a 2px left rule. |
130
+ | `code`, `samp` | `--font-mono` at 0.85em. `code` has a muted fill. |
131
+ | `kbd` | A key with a 1px border and a thicker bottom edge, in `--font-mono`. |
132
+ | `pre` | A bordered surface block in `--font-mono`. It scrolls when a line is too long. |
133
+ | `img`, `video` | Block elements that are never wider than their container. |
134
+ | `figure`, `figcaption` | `figcaption` is 13px muted text, 8px below the content. |
135
+ | `table`, `th`, `td` | Full width, 14px, collapsed borders, and tabular digits. `th` is an 11px uppercase muted label. Each row has a subtle bottom rule. |
136
+ | `caption` | 13px muted text, aligned left. |
137
+ | `tfoot` | A totals row: weight 600 with a `--color-border` rule above it. Print shows it once, at the end of the table. |
138
+ | `th scope="row"` | A row header that looks like a normal cell. |
139
+ | `details`, `summary` | `summary` is weight 500 with a muted marker. |
140
+ | Text `input`, `select`, `textarea` | The `.swarm-input` look. `select` has its own arrow. `textarea` is at least 80px tall and resizes vertically. |
141
+ | `button`, and `input` of type `button`, `submit`, or `reset` | The outline `.swarm-button` look. A plain `<button>` needs no class. |
142
+ | `input type="file"` | A small outline button opens the file picker. |
143
+ | Checkbox, radio, range, `progress` | The primary color. |
144
+ | `fieldset`, `legend` | A bordered group with a 13px muted legend. |
145
+ | Form states | Placeholders are muted. Disabled controls have 50% opacity. `aria-invalid="true"` makes the border destructive. |
146
+ | `:focus-visible` | A 2px ring in `--color-ring`. |
147
+ | `::selection` | The primary fill. |
148
+ | Every element | `border: 0 solid var(--color-border)`. A bare `border` class draws a 1px line in the border token. |
149
+
150
+ Blocks such as `p`, lists, `table`, `pre`, and `figure` have a 12px or 16px
151
+ bottom margin. A heading that is the first child of its container has no top
152
+ margin. The last block in a container has no bottom margin.
153
+
154
+ The head sets `<base target="_blank">`, so a link to another page opens in a
155
+ new tab. A link inside the page (`href="#id"`) without a `target` stays in the
156
+ page.
157
+
158
+ ## No hardcoded colors
159
+
160
+ Take every color from a token. Inside the dashboard, the page follows the
161
+ viewer's theme and mode. A hardcoded color stays the same in every theme and
162
+ mode, so it can break contrast.
163
+
164
+ - Do not write color literals: hex values (`#2563eb`), `rgb()`, `rgba()`, `hsl()`, `hsla()`, `oklch()`, or named colors such as `white` and `black`.
165
+ - Do not use Tailwind palette classes such as `bg-blue-500`, `text-zinc-600`, `bg-white`, or `text-black`, or arbitrary colors such as `bg-[#fff]`. They ignore the theme. For example, a `bg-white` card in dark mode puts light text on white.
166
+ - In CSS, use `var(--color-*)`. In markup, use the Tailwind class of the token, such as `bg-primary` or `text-muted-foreground`.
167
+ - For a tint, add an opacity modifier: `bg-primary/10`. In CSS, write `color-mix(in oklab, var(--color-primary) 10%, transparent)`.
168
+ - In inline SVG, use `fill-chart-1`, `stroke-border`, or `currentColor`.
169
+ - For a canvas chart, read the token value at runtime: `getComputedStyle(document.documentElement).getPropertyValue("--color-chart-1")`.
170
+
171
+ `create_page` counts the hardcoded colors in your `<style>` blocks, `style`
172
+ attributes, and `class` attributes. At 8 or more, its result tells you to use
173
+ tokens. A page with 8 or more hardcoded colors stays light, also when the
174
+ viewer uses dark mode.
175
+
176
+ To restyle a page, set `theme` or `themeTokens` (see Theming). Do not write
177
+ your own palette.
178
+
179
+ ## Theme tokens
180
+
181
+ The tokens use the dashboard's names. The font and radius tokens have one
182
+ value for both modes.
183
+
184
+ | CSS variable | Tailwind class | Meaning |
185
+ |---|---|---|
186
+ | `--color-background` | `bg-background` | The page canvas. |
187
+ | `--color-foreground` | `text-foreground` | Main text. |
188
+ | `--color-card`, `--color-card-foreground` | `bg-card`, `text-card-foreground` | Panels and the text on them. |
189
+ | `--color-surface` | `bg-surface` | A recessed step between canvas and card, for nested blocks. |
190
+ | `--color-popover`, `--color-popover-foreground` | `bg-popover`, `text-popover-foreground` | Menus, tooltips, and other floating layers. |
191
+ | `--color-primary`, `--color-primary-foreground` | `bg-primary`, `text-primary`, `text-primary-foreground` | The one accent: links, primary buttons, and active state. The foreground is the text on a primary fill. |
192
+ | `--color-secondary`, `--color-secondary-foreground` | `bg-secondary`, `text-secondary-foreground` | Secondary buttons and quiet fills. |
193
+ | `--color-muted`, `--color-muted-foreground` | `bg-muted`, `text-muted-foreground` | Quiet resting fills. Secondary text, labels, and captions. |
194
+ | `--color-accent`, `--color-accent-foreground` | `hover:bg-accent`, `text-accent-foreground` | The hover and highlight fill. For the brand accent, use `primary`. |
195
+ | `--color-destructive`, `--color-destructive-foreground` | `bg-destructive`, `text-destructive` | Destructive actions only. |
196
+ | `--color-border` | `border-border` | Structure lines: panels, inputs, table frames. |
197
+ | `--color-border-subtle` | `border-border-subtle`, `divide-border-subtle` | Quiet lines: row rules and separators. |
198
+ | `--color-input` | `border-input` | Form control borders. |
199
+ | `--color-ring` | `ring-ring`, `outline-ring` | The focus ring. |
200
+ | `--color-chart-1` to `--color-chart-5` | `bg-chart-1`, `fill-chart-2`, `stroke-chart-3` | Chart series, in order. |
201
+ | `--color-status-<tone>` | `bg-status-success` | A pale status fill. |
202
+ | `--color-status-<tone>-strong` | `text-status-error-strong` | Status text on a neutral surface. |
203
+ | `--color-status-<tone>-foreground` | `text-status-success-foreground` | The text on a pale status fill. |
204
+ | `--color-status-success-solid`, `--color-status-active-solid` | `bg-status-success-solid` | Saturated stops for small dots and icons. |
205
+ | `--font-sans` | `font-sans` | Text. Space Grotesk by default. |
206
+ | `--font-mono` | `font-mono` | Code, IDs, logs, and other machine output. Space Mono by default. |
207
+ | `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl` | `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl` | Corner radii, from tags (`sm`) to panels (`xl`). |
208
+
209
+ `<tone>` is `success`, `active`, `error`, `info`, `pending`, `warning`,
210
+ `paused`, or `neutral`. Status colors carry meaning. Use them for states, not
211
+ for decoration.
212
+
213
+ The old `--swarm-bg`, `--swarm-card`, `--swarm-border`, `--swarm-text`,
214
+ `--swarm-muted`, and `--swarm-primary` variables still work. On designVersion
215
+ 2 pages, they are aliases of the tokens. Use the token names in new pages.
216
+
217
+ ## Primitives
218
+
219
+ The primitives are plain CSS classes. They use only tokens, so they work in
220
+ both modes and under every preset. Add Tailwind utilities for spacing and
221
+ layout.
222
+
223
+ **`.swarm-page`**: the reading spine. It centers the content at a maximum width of 1120px.
224
+
225
+ ```html
226
+ <main class="swarm-page">...</main>
227
+ ```
228
+
229
+ **`.swarm-eyebrow`**: a small uppercase label above a title.
230
+
231
+ ```html
232
+ <p class="swarm-eyebrow">Weekly report · 2026-10-31</p>
233
+ ```
234
+
235
+ **`.swarm-lede`**: the summary paragraph under the title. It is larger, muted, and about 68 characters wide.
236
+
237
+ ```html
238
+ <p class="swarm-lede">Error rates fell 40% after the retry fix shipped.</p>
239
+ ```
240
+
241
+ **`.swarm-section`**: a titled block with vertical rhythm.
242
+
243
+ ```html
244
+ <section class="swarm-section"><h2>Findings</h2><p>...</p></section>
245
+ ```
246
+
247
+ **`.swarm-metrics`** and **`.swarm-metric`**: a responsive grid of key numbers. Each metric holds a `.swarm-metric-value` (large, tabular digits) and a `.swarm-metric-label` (muted). Add a `data-tone` (see Tones) to a metric to color its value. Without it, the value uses the foreground color.
248
+
249
+ ```html
250
+ <div class="swarm-metrics">
251
+ <div class="swarm-metric"><div class="swarm-metric-value">18.6k</div><div class="swarm-metric-label">Clicks</div></div>
252
+ <div class="swarm-metric" data-tone="error"><div class="swarm-metric-value">7</div><div class="swarm-metric-label">Failed runs</div></div>
253
+ </div>
254
+ ```
255
+
256
+ **`.swarm-panel`** (alias **`.swarm-card`**): a flat surface with a 1px border, the `--radius-xl` radius, and padding. Do not put a panel inside a panel.
257
+
258
+ ```html
259
+ <div class="swarm-panel"><h3>Owner</h3><p>Platform team</p></div>
260
+ ```
261
+
262
+ **`.swarm-badge`**: a soft status pill that never wraps. Set `data-tone` to a tone (see Tones). The default is `neutral`. Add `data-dot` for a leading dot in the tone color. The badge has a 6px gap between the dot and the text, so do not add margins inside a badge.
263
+
264
+ ```html
265
+ <span class="swarm-badge" data-tone="success" data-dot>Healthy</span>
266
+ ```
267
+
268
+ For a category label (a team, a region, a product line), do not use a status tone. Use a neutral badge with a dot, and set the dot color with `--swarm-dot` and a chart token. Give each category its own token, from `--color-chart-1` to `--color-chart-5`.
269
+
270
+ ```html
271
+ <span class="swarm-badge" data-dot style="--swarm-dot: var(--color-chart-1)">Construction</span>
272
+ <span class="swarm-badge" data-dot style="--swarm-dot: var(--color-chart-2)">Interiors</span>
273
+ ```
84
274
 
85
- Before creating a page, write a one-line design read for yourself:
275
+ **`.swarm-table-wrap`** and **`.swarm-table`**: a scroll container and a compact table. The table has a sticky header, subtle row rules, and row hover. Add `.num` to number cells: right aligned, tabular, no wrap. Add `.nowrap` to keep any cell on one line.
86
276
 
87
- > Reading this as: `<page kind>` for `<audience>`, using minimalist taste plus `<density needs>`.
277
+ The wrap is already a bordered card surface: the card fill, a 1px border, and the `--radius-xl` radius. Do not put it inside a panel. Its maximum height is `min(70vh, 720px)`. A taller table scrolls inside the wrap, and the header stays in view. To remove the cap, add `max-h-none` to the wrap. Then the page scrolls instead, and the header does not stay in view. Print always removes the cap. When the table is wider than the wrap, a soft shadow shows at each side that hides columns.
278
+
279
+ ```html
280
+ <div class="swarm-table-wrap">
281
+ <table class="swarm-table">
282
+ <thead><tr><th>Route</th><th class="num">p95 ms</th></tr></thead>
283
+ <tbody><tr><td class="nowrap">/api/tasks</td><td class="num">182</td></tr></tbody>
284
+ </table>
285
+ </div>
286
+ ```
287
+
288
+ **`.swarm-button`**: a button look for `<button>` and `<a>`. Set `data-variant` to `outline` (the default), `primary`, or `ghost`. A plain `<button>` also gets the outline look. For a bare icon button, remove it with utilities: `border-0 bg-transparent p-0`.
289
+
290
+ ```html
291
+ <button class="swarm-button" data-variant="primary" type="button">Rerun checks</button>
292
+ ```
293
+
294
+ **`.swarm-input`**: the look for text inputs and selects. Plain form controls get the same base look.
295
+
296
+ ```html
297
+ <input class="swarm-input" type="search" placeholder="Filter rows" aria-label="Filter rows">
298
+ ```
299
+
300
+ **`.swarm-toolbar`**: a row for search, filters, and actions. It wraps on narrow screens. A text or search input placed directly in the toolbar grows to fill the free space, so do not give it a fixed width.
301
+
302
+ ```html
303
+ <div class="swarm-toolbar">
304
+ <input class="swarm-input" type="search" placeholder="Filter" aria-label="Filter">
305
+ <button class="swarm-button" type="button">Reset</button>
306
+ </div>
307
+ ```
308
+
309
+ **`.swarm-segmented`**: a segmented control. Mark the active item with `aria-pressed="true"` or `data-active`.
310
+
311
+ ```html
312
+ <div class="swarm-segmented" role="group" aria-label="Range">
313
+ <button type="button" aria-pressed="true">Week</button>
314
+ <button type="button" aria-pressed="false">Month</button>
315
+ </div>
316
+ ```
317
+
318
+ **`.swarm-callout`**: a box for a finding or a note. Set `data-tone` to a tone (see Tones). The default is `info`.
319
+
320
+ ```html
321
+ <div class="swarm-callout" data-tone="warning"><strong>Retry storm.</strong> Three agents retried one task 40 times.</div>
322
+ ```
323
+
324
+ **`.swarm-kv`**: a key and value grid for a `<dl>`. Each `<dt>` is a muted key, and the `<dd>` after it holds the value.
325
+
326
+ ```html
327
+ <dl class="swarm-kv"><dt>Owner</dt><dd>Platform team</dd><dt>Window</dt><dd>7 days</dd></dl>
328
+ ```
329
+
330
+ ### Tones
331
+
332
+ `.swarm-badge`, `.swarm-callout`, and `.swarm-metric` share one set of
333
+ `data-tone` values:
334
+
335
+ | `data-tone` | Use it for | Color token |
336
+ |---|---|---|
337
+ | `neutral` | Offline, backlog, cancelled, skipped, or not reviewed. The badge default. | `--color-status-neutral` |
338
+ | `primary` | A highlight that is not a state, in the page accent. | `--color-primary` |
339
+ | `success` | Completed, healthy, approved, or passed. | `--color-status-success` |
340
+ | `warning` | Timeouts, threshold warnings, and risks to fix soon. | `--color-status-warning` |
341
+ | `error` | Failed, unhealthy, rejected, or blocked. | `--color-status-error` |
342
+ | `info` | Facts and notes. The callout default. | `--color-status-info` |
343
+ | `pending` | Pending, waiting, or starting. | `--color-status-pending` |
344
+ | `paused` | Paused or in review. | `--color-status-paused` |
345
+ | `active` | Running, busy, or in progress. | `--color-status-active` |
346
+
347
+ Each status tone uses its token for fills and borders, and its `-strong` token
348
+ for text. `primary` uses `--color-primary` for both. A badge takes its text,
349
+ fill, and border from the tone. A callout takes only its fill and border from
350
+ the tone, so its text keeps the foreground color. A metric colors its value.
351
+
352
+ The bundled **`examples/report-page.html`** puts these together, with zero
353
+ color literals.
354
+
355
+ ## Theming
356
+
357
+ The API picks the preset of a designVersion 2 page in this order:
358
+
359
+ 1. The page's `theme`, when it is set.
360
+ 2. The viewer's dashboard preset, when the page opens inside the dashboard.
361
+ 3. `hive`.
362
+
363
+ Then the page's `themeTokens` apply on top of the preset. The mode (light or
364
+ dark) always comes from the viewer. `create_page`, `POST /api/pages`, and
365
+ `PUT /api/pages/:id` accept both fields.
366
+
367
+ ### `theme`: a preset id
368
+
369
+ | Id | Look |
370
+ |---|---|
371
+ | `hive` | The default: a zinc field with one amber accent. |
372
+ | `meadow`, `iris`, `rose` | The Hive field with an emerald, violet, or deep rose accent. |
373
+ | `cobalt` | A cool slate field with a saturated blue accent. |
374
+ | `ember` | A warm stone field with a burnt orange accent. |
375
+ | `carbon` | Pure monochrome. |
376
+ | `paper` | The warm editorial paper look: an off-white canvas, white cards, warm borders, and a blue accent. |
377
+ | `github`, `vscode`, `material`, `solarized`, `tokyo`, `monokai`, `gruvbox`, `catppuccin` | Classic editor and platform palettes, with their own status colors. |
378
+
379
+ An unknown id fails the write, and the error lists the valid ids. Set `theme`
380
+ only when the page needs a fixed look. Without it, the page matches the
381
+ viewer's dashboard. On an update, `theme: null` clears it. An update or upsert
382
+ that omits `theme` keeps the stored preset.
383
+
384
+ ### `themeTokens`: token overrides
385
+
386
+ `themeTokens` overrides individual tokens on top of the preset. Tokens that
387
+ you do not set keep their preset values. The input has two formats: a token
388
+ map (the default) and a CSS block (`"format": "css"`). Another `format` fails
389
+ the write.
390
+
391
+ **Set colors for both modes.** A color that you set only in `light` does not
392
+ reach dark mode. Dark mode keeps the preset's dark value for that token. So
393
+ set each color in both the `light` and the `dark` map, or add a `.dark` block
394
+ in the css format. Fonts and radius have one value for both modes. A font or
395
+ radius token that you set only in `light` applies to dark mode too.
396
+
397
+ **Send the full map.** A write that sends `themeTokens` replaces the stored
398
+ map. It does not merge with it. A token that you leave out goes back to its
399
+ preset value. An update or upsert that omits `themeTokens` keeps the stored
400
+ map. On an update, `themeTokens: null` clears it.
401
+
402
+ A token map:
403
+
404
+ ```jsonc
405
+ // Tool call: create_page
406
+ {
407
+ "title": "Launch review",
408
+ "contentType": "text/html",
409
+ "body": "<!doctype html>...",
410
+ "theme": "paper",
411
+ "themeTokens": {
412
+ "light": { "--color-primary": "oklch(0.52 0.15 150)", "--color-ring": "oklch(0.52 0.15 150)", "--radius-xl": "14px" },
413
+ "dark": { "--color-primary": "oklch(0.72 0.14 150)", "--color-ring": "oklch(0.72 0.14 150)" }
414
+ }
415
+ }
416
+ ```
417
+
418
+ A CSS block takes the output of shadcn, tweakcn, or a Tailwind v4 `@theme`
419
+ block without changes. A `@theme` block holds light values only, so add a
420
+ `.dark` block for dark mode. Send the block as one string:
421
+ `"themeTokens": { "format": "css", "css": "<the block>" }`. For example, this
422
+ shadcn paste:
423
+
424
+ ```css
425
+ :root {
426
+ --radius: 0.5rem;
427
+ --primary: oklch(0.205 0 0);
428
+ --primary-foreground: oklch(0.985 0 0);
429
+ --sidebar: oklch(0.985 0 0);
430
+ }
431
+ .dark {
432
+ --primary: oklch(0.922 0 0);
433
+ --primary-foreground: oklch(0.205 0 0);
434
+ }
435
+ ```
436
+
437
+ sets the primary colors for both modes and all four radius tokens. `--sidebar`
438
+ is not a page token, so the response lists it in `ignoredTokens`.
439
+
440
+ The importer rules:
441
+
442
+ - `--color-primary`, `--primary`, `color-primary`, and `primary` all name the same token. Font and radius names work the same way (`--font-sans`, `font-sans`).
443
+ - In a CSS block, `:root`, `:host`, `html`, `@theme`, and `@theme inline` set the light values. `.dark`, `:root.dark`, `html.dark`, and `[data-theme=dark]` (quoted or not) set the dark values. So does `:root`, `:host`, or `html` inside `@media (prefers-color-scheme: dark)`. A selector list such as `:root, :host` also works.
444
+ - The importer skips comments and blocks under other selectors. The response lists the custom properties of a skipped block in `ignoredTokens` with the block, for example `--ring (in .card)`. A CSS block with no light or dark block fails the write.
445
+ - A shadcn `--radius` sets all four radius tokens. `sm` is the radius minus 4px, `md` is minus 2px, `lg` is the radius, and `xl` is plus 4px.
446
+ - The importer wraps a bare HSL triplet from an older shadcn theme (`222.2 47.4% 11.2%`) as `hsl(222.2 47.4% 11.2%)`.
447
+ - The importer ignores names outside the token contract, such as `--sidebar`, `--shadow-sm`, and `--font-serif`. It does not reject them. The response lists them in `ignoredTokens`.
448
+ - The importer drops a token whose value references a variable outside the contract, such as `--font-sans: var(--font-geist-sans)`. The response lists it in `ignoredTokens` with the variable that it references, for example `--font-sans (references --font-geist-sans)`. Write the value itself instead, for example a font stack.
449
+ - A value has 1 to 200 characters. It must not contain `;`, `{`, `}`, `<`, `>`, `\`, or a line break. It must not contain `url(`, `expression(`, `@import`, or `javascript:`. A bad value fails the write.
450
+ - The API stores the tokens in normalized form.
451
+
452
+ When you change `--color-primary`, also set `--color-ring`. If the text on the
453
+ new accent needs more contrast, set `--color-primary-foreground` too.
454
+
455
+ Do not set tokens in your own `:root` rule. The theme's dark rule is more
456
+ specific, so your value reaches light mode only.
457
+
458
+ To use another font, load it from Google Fonts in your page `<head>`. Then set
459
+ `--font-sans` in `themeTokens`.
460
+
461
+ ## Dark mode
462
+
463
+ Inside the dashboard, a designVersion 2 page follows the viewer's mode. In dark
464
+ mode, the injected head adds the `dark` class to `<html>`, and the tokens
465
+ switch to their dark values. A page built on tokens needs no extra dark-mode
466
+ code.
467
+
468
+ - Do not set `color-scheme` yourself. The theme sets it.
469
+ - A `themeTokens` color that you set only in `light` does not reach dark mode. Set it in both maps (see `themeTokens`).
470
+ - Use the `dark:` variant only for a rare mode-specific change, such as a logo pair: `<img class="dark:hidden" ...>` and `<img class="hidden dark:block" ...>`.
471
+ - A direct link (`api_url`) renders in light mode, with the page `theme` or Hive.
472
+ - To check a page in dark mode, add `?__swarm-mode=dark` to its `api_url`. On a page without a `theme`, add `__swarm-theme=<preset id>` to try a preset.
473
+ - Do not use `__swarm-mode` or `__swarm-theme` as your own query parameters.
474
+
475
+ ## Design versions
476
+
477
+ Each page has a `designVersion`. It is separate from the edit `version`
478
+ counter.
479
+
480
+ - `1`: the old head, with the Tailwind v3 Play CDN and the `--swarm-*` hex tokens, in light mode only. Pages created before the design system have version 1. They keep their old look and ignore the viewer's theme and mode.
481
+ - `2`: the design system. New pages get version 2 by default.
482
+
483
+ An upsert or update that omits `designVersion` keeps the stored version. The
484
+ `create_page` result reports the saved `designVersion`. A page created before
485
+ the design system stays on version 1, also when you upsert it with design
486
+ system markup. So pass `designVersion: 2` when you rewrite an old page or
487
+ upsert a recurring report. On a version 2 page, it changes nothing.
488
+
489
+ To move a version 1 page, rewrite it and pass `designVersion: 2`. Then fix these
490
+ Tailwind v3 to v4 changes:
491
+
492
+ | v3 class (version 1) | v4 class (version 2) |
493
+ |---|---|
494
+ | `shadow-sm`, `shadow` | `shadow-xs`, `shadow-sm` |
495
+ | `rounded-sm`, `rounded` | `rounded-xs`, `rounded-sm` |
496
+ | `ring` (3px) | `ring-3`. A bare `ring` is now 1px. |
497
+ | `outline-none` | `outline-hidden`. `outline-none` now sets `outline-style: none`. |
498
+ | `bg-opacity-50` and the other `*-opacity-*` classes | Removed. Use an opacity modifier, such as `bg-primary/50`. |
499
+ | `bg-[--my-var]` | `bg-(--my-var)` |
500
+
501
+ Also check these points:
502
+
503
+ - A bare `border` draws a `--color-border` line (Tailwind v3 drew gray). For another line, name its token, such as `border-border-subtle`.
504
+ - Remove any Tailwind `<script>` or `tailwind.config` that the page loads itself. The head already loads Tailwind v4.
505
+ - Do not write any `@import` in a `text/tailwindcss` block. `@import "tailwindcss"` enables Preflight again, and the browser also requests every `@import` path as a file next to the page.
506
+ - Replace the color literals with tokens.
88
507
 
89
508
  ## Universal baseline
90
509
 
91
- The baseline is `taste-minimalist-skill`. Apply it to every page — reports,
92
- dashboards, tables, audits, public explainers, data-heavy summaries.
510
+ The design system overrides `taste-minimalist-skill`. It replaces every color,
511
+ font, spacing, and motion value in the taste skill. Use the taste skill only
512
+ for restraint in layout and copy.
93
513
 
94
- - Use a warm monochrome canvas (`#FFFFFF`, `#FBFBFA`, `#F7F6F3`) with charcoal text and scarce muted accents.
95
- - Build a clear type hierarchy with premium/system typography; use monospace for code, metadata, and `<kbd>`.
96
- - Use macro-whitespace and strong alignment. Give summaries room to breathe and keep tables compact enough to scan.
97
- - Prefer flat surfaces: crisp `1px` borders, `8px` or `12px` radii, and practically no shadows.
98
- - Use restrained motion only where it clarifies hierarchy or interaction state.
514
+ - Keep the canvas quiet. Let the theme supply the field and the text colors, and use the accent sparingly.
515
+ - Build a clear type hierarchy with the built-in heading scale. Use `font-mono` for IDs, metadata, and machine output. The head already styles `<code>`, `<pre>`, and `<kbd>`.
516
+ - Use whitespace and strong alignment. Give summaries room to breathe and keep tables compact enough to scan.
517
+ - Prefer flat surfaces: crisp `1px` borders and practically no shadows.
518
+ - Use motion only where it clarifies hierarchy or interaction state. Do not animate content blocks on scroll.
99
519
  - Keep visual language quiet and precise: no AI-purple gradient mesh, centered hero plus three equal cards, decorative glassmorphism, emojis as UI decoration, fake screenshots made from rectangles, oversized decorative art, nested cards, or low-contrast text.
100
520
  - Make responsive behavior explicit: readable type, stable grids, horizontal table scroll where needed, and no overlapping content.
101
521
 
102
- If the full minimalist source is available, use `taste-minimalist-skill` for the
103
- deeper primitives: bento grids, status badges, `<kbd>` keys, flat tables, code
104
- blocks, and the detailed anti-slop checks.
522
+ When the full `taste-minimalist-skill` source is available, take only its
523
+ restraint rules: the banned elements, and plain, specific copy with realistic
524
+ names and no AI clichés. Ignore its hex colors, font stacks, `py-24` and
525
+ `py-32` section padding, scroll animations, background gradients, fake window
526
+ chrome, and uppercase badges. The tokens, the element defaults, and the
527
+ `swarm-*` primitives replace them.
105
528
 
106
529
  ## Report density layer
107
530
 
@@ -113,16 +536,14 @@ information-architecture layer, not a separate aesthetic.
113
536
  Every dense page should be useful at a glance.
114
537
 
115
538
  - Put the page's point in the first viewport: title, one-sentence summary, and 3-6 key numbers or statuses.
116
- - Use a single-column reading spine with `max-width: 1120px`; keep prose measure around 65-75 characters and reserve denser grids for metrics/evidence.
117
- - Use premium/system typography unless there is a clear brand reason not to: `"SF Pro Display", "Geist Sans", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif`.
118
- - Use the minimalist palette: warm off-white background, charcoal text, white or near-white panels, light borders, one scarce accent, and semantic colors for statuses only.
119
- - Use a consistent spacing scale: `8, 12, 16, 24, 32, 48, 72`.
120
- - Use clear type hierarchy: page title 36-48px desktop / 30-36px mobile, section titles 22-28px, body 15-16px, supporting text 13-14px.
121
- - Keep tables readable: sticky/scannable headers where useful, padded cells, zebra-free or very subtle row borders, `tabular-nums` for numbers, horizontal scroll on narrow screens.
122
- - Prefer flat bordered cards only for repeated records or metrics. Do not nest cards inside cards.
123
- - Use shadows only when they solve a hierarchy problem; keep them ultra-diffuse and below `0.05` opacity.
539
+ - Use a single-column reading spine (`.swarm-page`, 1120px wide). Keep prose near 65-75 characters, and use denser grids only for metrics and evidence.
540
+ - For your own gaps and margins, use the steps that the primitives use most: 4, 8, 12, 16, 24, and 40px. In Tailwind units, that is `1`, `2`, `3`, `4`, `6`, and `10` (for example, `gap-4` is 16px).
541
+ - Keep the built-in type scale (`h1` about 30px, body 15px). Do not inflate titles to hero sizes.
542
+ - Keep tables readable: `.swarm-table` gives a sticky header, padded cells, subtle row rules, and tabular numbers. Put it in a `.swarm-table-wrap` so it scrolls on narrow screens.
543
+ - Prefer flat bordered panels only for repeated records or metrics. Do not nest panels inside panels. A `.swarm-table-wrap` is already a panel.
544
+ - Use a shadow only when it solves a hierarchy problem, and keep it barely visible (`shadow-xs`).
124
545
  - Hide raw JSON behind a collapsed `<details>` block at the bottom.
125
- - Make mobile explicit with media queries: single-column grids, reduced padding, no overflow except intentional table scroll.
546
+ - Mobile is built in. At 640px wide and below, the body padding drops to 16px. The metric grid and the toolbar wrap, and a toolbar search input grows to fill its row. Add responsive variants only for your own grids, such as `grid-cols-1 md:grid-cols-2`. Allow no overflow except table scroll.
126
547
 
127
548
  ## Content structure
128
549
 
@@ -150,9 +571,9 @@ Before publishing:
150
571
  - Raw JSON/logs are collapsed or linked, not the primary experience.
151
572
  - No nested cards, decorative gradients, oversized art, or cramped default browser styles.
152
573
  - No text overlaps, clipped buttons, or unreadable low-contrast text.
153
-
154
- Pages inject a default body theme. For a custom palette, set `body` background, color, font, and padding explicitly in your stylesheet.
155
- Setting these properties only on `:root` does not replace the injected body styles.
574
+ - No color literals. Every color comes from a token, in CSS or as a Tailwind class.
575
+ - The page reads well in light and dark mode. Add `?__swarm-mode=dark` to the `api_url` to check.
576
+ - The `create_page` result shows `designVersion: 2`. If it shows 1, publish again with `designVersion: 2`.
156
577
 
157
578
  ---
158
579
 
@@ -180,7 +601,7 @@ To check a page by screenshot, open it from an origin its data endpoints allow.
180
601
  | URL | Shape | Notes |
181
602
  |---|---|---|
182
603
  | `app_url` | `${APP_URL}/pages/:id` | SPA route. Renders HTML in a sandboxed iframe, JSON via `@json-render/react`. Default share target. |
183
- | `app_url` (full mode) | `${APP_URL}/pages/:id?mode=full` | Same SPA route, maximized — hides the SPA sidebar/header so the page body gets the full viewport. Slim header with title + Exit-Full button. Useful for embeds + standalone dashboards. |
604
+ | `app_url` (full mode) | `${APP_URL}/pages/:id?mode=full` | Same SPA route, maximized: hides the SPA sidebar/header so the page body gets the full viewport. Slim header with title + Exit-Full button. Useful for embeds + standalone dashboards. |
184
605
  | `api_url` | `${MCP_BASE_URL}/p/:id` | Direct API render. HTML inlines and serves; JSON 302-redirects to `app_url`. Useful for no-SPA-required links. |
185
606
 
186
607
  `${APP_URL}` is the deployment's SPA origin and `${MCP_BASE_URL}` is its API
@@ -199,14 +620,14 @@ where the unfurl preview only follows the API origin).
199
620
  Every overwrite (update via `update_page` or `PUT /api/pages/:id`) snapshots
200
621
  the **pre-update** state into `page_versions` and writes the new state to the
201
622
  parent row. The wire `version` field is a monotonically-increasing
202
- "edit counter" — version 1 is the initial create.
623
+ "edit counter": version 1 is the initial create.
203
624
 
204
625
  | Operation | Endpoint | Returns |
205
626
  |---|---|---|
206
627
  | List versions | `GET /api/pages/:id/versions` | `{ versions: PageVersion[] }` newest first |
207
628
  | Read a version | `GET /api/pages/:id/versions/:version` | Single snapshot |
208
629
 
209
- Snapshots are full body copies — keep this in mind for large pages (the
630
+ Snapshots are full body copies. Keep this in mind for large pages (the
210
631
  per-version body cap is 5 MiB).
211
632
 
212
633
  ## Browser SDK
@@ -214,8 +635,8 @@ per-version body cap is 5 MiB).
214
635
  Every HTML page automatically gets `window.SwarmSDK` (the class) and
215
636
  `window.swarmSdk` (a ready-to-use singleton) injected. The SDK routes through
216
637
  the `/@swarm/api/*` proxy, which resolves the page's own `page_session_<id>`
217
- cookie to a user identity and forwards with proper auth headers server-side —
218
- your page never sees or handles tokens. The SDK names its page in the
638
+ cookie to a user identity and forwards with proper auth headers server-side.
639
+ Your page never sees or handles tokens. The SDK names its page in the
219
640
  `X-Swarm-Page-Id` header. A direct `fetch('/@swarm/api/...')` from the page
220
641
  works too: the proxy falls back to the `/p/<id>` Referer.
221
642
 
@@ -239,7 +660,7 @@ Inline usage:
239
660
 
240
661
  ```html
241
662
  <script>
242
- // Singleton is ready immediately — no `new SwarmSDK()` needed.
663
+ // Singleton is ready immediately: no `new SwarmSDK()` needed.
243
664
  const tasks = await window.swarmSdk.tasks.list({ status: 'in_progress' });
244
665
  const agents = await window.swarmSdk.agents.list();
245
666
 
@@ -263,7 +684,7 @@ HTTP status.
263
684
 
264
685
  ### Full signature
265
686
 
266
- This is the entire surface — copy it into your page if you want autocomplete
687
+ This is the entire surface. Copy it into your page if you want autocomplete
267
688
  hints in an editor. The runtime version is auto-injected; you don't need to
268
689
  include this in the page source.
269
690
 
@@ -323,21 +744,25 @@ class SwarmSDK {
323
744
 
324
745
  For the full list of fields each endpoint accepts/returns, see
325
746
  [**docs.agent-swarm.dev/docs/api-reference**](https://docs.agent-swarm.dev/docs/api-reference).
326
- The SDK is a thin domain wrapper — anything documented there is reachable.
747
+ The SDK is a thin domain wrapper: anything documented there is reachable.
327
748
 
328
- ## Built-in primitives
749
+ ## Built-in web components
329
750
 
330
- Every HTML page automatically gets a small set of zero-dep web components
331
- auto-injected alongside the Browser SDK. Drop them into your page body —
332
- no `<script>` import, no bundling, no Tailwind required (though Tailwind
333
- Play CDN is loaded, so utility classes work too).
751
+ Every HTML page automatically gets a small set of zero-dep web components,
752
+ injected beside the Browser SDK. Put them in your page body. They need no
753
+ `<script>` import, no bundling, and no Tailwind. Tailwind utilities also work:
754
+ Tailwind v4 on designVersion 2 pages, and the Tailwind v3 Play CDN on
755
+ designVersion 1 pages. For the `swarm-*` CSS classes, see Primitives under
756
+ Design system.
334
757
 
335
- ### `<swarm-diff>` — unified diff renderer
758
+ ### `<swarm-diff>`: unified diff renderer
336
759
 
337
760
  Render a unified diff with a two-column gutter, severity annotations, and a
338
761
  deterministic anchor id per hunk (so deep-linking + jump lists work). The
339
762
  element reads its payload from its `textContent` as JSON of shape
340
763
  `{ hunks: [{ old_start, old_lines, new_start, new_lines, lines, annotations? }] }`.
764
+ On designVersion 2 pages, its colors and fonts follow the theme tokens and dark
765
+ mode.
341
766
 
342
767
  ```html
343
768
  <swarm-diff
@@ -345,21 +770,25 @@ element reads its payload from its `textContent` as JSON of shape
345
770
  base-sha="abc123"
346
771
  head-sha="def456">
347
772
  { "hunks": [
348
- { "old_start": 10, "old_lines": 3, "new_start": 10, "new_lines": 4,
773
+ { "old_start": 10, "old_lines": 2, "new_start": 10, "new_lines": 3,
349
774
  "lines": [
350
- { "type": "context", "text": " const x = 1;" },
351
- { "type": "del", "text": "- console.log(x);" },
352
- { "type": "add", "text": "+ logger.info({ x });" },
353
- { "type": "add", "text": "+ return x;" }
775
+ { "type": "context", "text": "export function load(id) {" },
776
+ { "type": "del", "text": " console.log(id);" },
777
+ { "type": "add", "text": " logger.info({ id });" },
778
+ { "type": "add", "text": " return cache.get(id);" }
354
779
  ],
355
780
  "annotations": [
356
- { "line": 12, "severity": "warn", "text": "Avoid raw console.log" }
781
+ { "line": 12, "severity": "warn", "text": "Returns undefined on a cache miss" }
357
782
  ]
358
783
  }
359
784
  ] }
360
785
  </swarm-diff>
361
786
  ```
362
787
 
788
+ The browser reads the JSON as HTML text first. Inside the JSON, write `<` as
789
+ `\u003c` and `&` as `\u0026`. Otherwise the browser turns
790
+ `Promise<void>` into a tag, and `&amp;` into `&`.
791
+
363
792
  **Inputs**
364
793
 
365
794
  | Attribute | Required | Notes |
@@ -368,28 +797,47 @@ element reads its payload from its `textContent` as JSON of shape
368
797
  | `base-sha` | no | Pre-change SHA. Rendered in the header next to `head-sha`. |
369
798
  | `head-sha` | no | Post-change SHA. |
370
799
 
800
+ **Hunk shape**
801
+
802
+ | Field | Notes |
803
+ |---|---|
804
+ | `old_start`, `new_start` | The first line number on the old side and on the new side. |
805
+ | `old_lines` | The number of context and deleted lines in the hunk. |
806
+ | `new_lines` | The number of context and added lines in the hunk. |
807
+
808
+ The hunk header shows `@@ -old_start,old_lines +new_start,new_lines @@`. The
809
+ component prints the counts that you send. It does not check them.
810
+
371
811
  **Line shape**
372
812
 
373
813
  | Field | Values | Notes |
374
814
  |---|---|---|
375
815
  | `type` | `context` \| `add` \| `del` | Drives row tint (green / red / neutral) and gutter line numbering. |
376
- | `text` | string | Rendered verbatim (HTML-escaped). |
816
+ | `text` | string | The line without its `+`, `-`, or space marker. The component draws the marker in its own column. Keep the indentation. The text is HTML-escaped. |
377
817
 
378
- **Annotation shape** — attaches to a NEW-side line by line number; rendered
379
- as a margin badge on that row.
818
+ **Annotation shape**: a margin badge on one row.
380
819
 
381
820
  | Field | Values | Notes |
382
821
  |---|---|---|
383
- | `line` | integer | New-side line number (falls back to old-side if no add for that line). |
822
+ | `line` | integer | The new-side line number of an added or context row. For a deleted row, use its old-side number. If a deleted row and another row have that number, the badge shows on both. |
384
823
  | `severity` | `error` \| `warn` \| `info` | Drives badge color. |
385
824
  | `text` | string | Badge body. |
386
825
 
387
826
  **Anchor id**
388
827
 
389
- Each hunk gets `id="swarm-diff-<file-slug>-<old_start>"` for deep-linking.
390
- Use `<swarm-diff-jumps></swarm-diff-jumps>` anywhere in the page body to
391
- render a tiny "Jump to" navigation of every diff hunk on the page —
392
- handy when an agent ships a multi-file annotated PR.
828
+ Each hunk gets an anchor with `id="swarm-diff-<slug>"`. The slug is
829
+ `<file>-<old_start>` in lowercase. Each run of characters other than `a-z` and
830
+ `0-9` becomes one `-`, and a `-` at the start or end is removed. For example,
831
+ `file="src/foo.ts"` with `old_start: 10` gives `id="swarm-diff-src-foo-ts-10"`.
832
+ Without `file`, the slug starts with `hunk`. When `old_start` is 0 or missing,
833
+ the slug ends with the hunk's position (1 for the first hunk). Two hunks with
834
+ the same file and `old_start` get the same id, so keep them unique.
835
+
836
+ Link to a hunk with `href="#swarm-diff-src-foo-ts-10"`. On a designVersion 1
837
+ page, add `target="_self"` to the link, or it opens in a new tab. Use
838
+ `<swarm-diff-jumps></swarm-diff-jumps>` anywhere in the page body to render a
839
+ tiny "Jump to" navigation of every diff hunk on the page. It is handy when an
840
+ agent ships a multi-file annotated PR.
393
841
 
394
842
  **Programmatic form**
395
843
 
@@ -409,21 +857,26 @@ A full annotated-PR page using `<swarm-diff>` is bundled at
409
857
 
410
858
  ## Print / PDF export
411
859
 
412
- Every HTML page also gets a `@media print` rule baked into the head defaults:
413
- - Light theme on print (white background, black text, underlined black links).
414
- - Anything with the `.no-print` class is hidden (annotation badges and the
415
- jump list already carry this class — use it on agent-emitted chrome you
416
- want suppressed in PDF exports).
417
- - `.swarm-card` and `<swarm-diff>` get `break-inside: avoid` so they don't
418
- split mid-element across pages.
419
-
420
- Trigger the export from the SPA's "Export PDF" button on `/pages/:id` — it
860
+ Every HTML page also gets `@media print` rules in its injected head. Print is
861
+ always light:
862
+ - A designVersion 2 page prints with its light tokens, also when the viewer
863
+ runs dark mode.
864
+ - The page prints on a white background, with black text and underlined
865
+ links.
866
+ - Anything with the `.no-print` class is hidden. Annotation badges and the
867
+ jump list already carry this class. Add it to page chrome that you want out
868
+ of the PDF.
869
+ - `.swarm-panel`, `.swarm-card`, `.swarm-metric`, and `<swarm-diff>` get
870
+ `break-inside: avoid`, so they do not split across pages. (On designVersion
871
+ 1 pages, only `.swarm-card` and `<swarm-diff>` get it.)
872
+
873
+ Trigger the export from the SPA's "Export PDF" button on `/pages/:id`. It
421
874
  opens the iframe's native print dialog (HTML pages) or the SPA's print
422
- dialog (JSON pages). The browser's "Print → Save as PDF" handles the actual
423
- file. No headless Chromium, no server-side rendering — zero infra weight.
875
+ dialog (JSON pages). The browser's "Print → Save as PDF" makes the actual
876
+ file. No headless Chromium, no server-side rendering, zero infra weight.
424
877
 
425
- > Want a custom print layout? Override the print styles in your page's own
426
- > `<style>` block — agent CSS always wins over the head defaults.
878
+ > Want a custom print layout? Add `@media print` rules to your page's own
879
+ > `<style>` block.
427
880
 
428
881
  ## Viewer feedback
429
882
 
@@ -458,7 +911,7 @@ redirecting to the SPA), `401`/`403` (auth gate), and `404` do NOT bump.
458
911
  The count is exposed on `GET /api/pages` listing (`viewCount` field) and the
459
912
  SPA `/pages` index renders it as a small eye-count badge per row.
460
913
 
461
- No per-viewer dedup — this is a coarse popularity signal, not analytics.
914
+ No per-viewer dedup: this is a coarse popularity signal, not analytics.
462
915
  Bumps are best-effort (wrapped in try/catch so a counter write never fails
463
916
  the response).
464
917
 
@@ -499,7 +952,7 @@ See the `@json-render/core` docs for the supported node types (`text`,
499
952
  `passwordHash`) and the page's own KV. Every other route returns 403, and every RBAC verb denies a guest.
500
953
  - Browser SDK memory operations use the page owner's agent scope through verified page execution context.
501
954
  Viewer authentication, permissions, and audit identity remain separate from this resource scope.
502
- - Treat agent-generated HTML / JSON like trusted code — the agent already
955
+ - Treat agent-generated HTML / JSON like trusted code: the agent already
503
956
  has equivalent MCP access, so a malicious page is no worse than a
504
957
  malicious tool call. But: don't ship pages to **external** users (via
505
958
  `password`) without reviewing the body first.
@@ -509,14 +962,14 @@ See the `@json-render/core` docs for the supported node types (`text`,
509
962
  page still has the SwarmSDK access of its session: full for user and
510
963
  operator sessions, page record and KV only for password guests.
511
964
  - All page bodies pass through `scrubSecrets` at the egress boundary
512
- (`/p/:id`, `/p/:id.json`, listing endpoint) — accidental secrets in
965
+ (`/p/:id`, `/p/:id.json`, listing endpoint). Accidental secrets in
513
966
  the body get masked at serve time, not at write time. Don't rely on
514
- scrubbing as a security boundary — keep secrets out of bodies.
967
+ scrubbing as a security boundary. Keep secrets out of bodies.
515
968
 
516
969
  ## Limits
517
970
 
518
971
  - **Body size**: 5 MiB per version (HTML or JSON). Bumping requires careful
519
- thought about SQLite write-amplification — full bodies are snapshotted on
972
+ thought about SQLite write-amplification: full bodies are snapshotted on
520
973
  every update.
521
974
  - **TTL**: none. Pages persist until explicitly deleted via `DELETE
522
975
  /api/pages/:id` (or the SPA listing UI when it gains a delete affordance).
@@ -538,11 +991,11 @@ human-facing summary.
538
991
 
539
992
  ## See Also
540
993
 
541
- - The **`apps`** skill — schema-backed records, named queries, and actions.
542
- - The **`agent-fs`** skill — durable file storage and share links.
543
- - The **`kv-storage`** skill — page-scoped state via `swarmSdk.kv`.
544
- - `runbooks/secret-scrubbing.md` — egress scrubbing details.
545
- - SPA listing: `${APP_URL}/pages` — the same `APP_URL` used for share links above.
994
+ - The **`apps`** skill: schema-backed records, named queries, and actions.
995
+ - The **`agent-fs`** skill: durable file storage and share links.
996
+ - The **`kv-storage`** skill: page-scoped state via `swarmSdk.kv`.
997
+ - `runbooks/secret-scrubbing.md`: egress scrubbing details.
998
+ - SPA listing: `${APP_URL}/pages`, the same `APP_URL` used for share links above.
546
999
  Prefer the `app_url` that `create_page` returns; build URLs by hand only when
547
1000
  you have no page id.
548
1001
 
@@ -609,7 +1062,7 @@ Array insertion takes `path`, `index`, and `values`. Text editing takes `path`,
609
1062
  Presence updates default to one transmission per 50 milliseconds. Presence never enters the persisted document.
610
1063
  Use `await room.close()` when the page no longer needs a room.
611
1064
 
612
- ### `<swarm-cursors>` — shared page pointers
1065
+ ### `<swarm-cursors>`: shared page pointers
613
1066
 
614
1067
  Add this element to opt a page into named cursors for an existing room:
615
1068