@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
@@ -0,0 +1,561 @@
1
+ /**
2
+ * Theme presets: the parametric layer on top of the token system.
3
+ *
4
+ * Every color in the dashboard resolves through the CSS custom properties
5
+ * declared in `apps/ui/src/styles/globals.css` (`--color-primary`,
6
+ * `--color-background`, ...). A preset is nothing but a curated bundle of
7
+ * overrides for a SUBSET of those properties, emitted as `[data-theme="<id>"]`
8
+ * rules, so a theme can be applied at ANY scope: on `<html>` (the whole
9
+ * dashboard, via the Appearance settings) or on a wrapper element (one
10
+ * swarm-app's canvas, via `definition.theme` / the viewer's `$theme` override).
11
+ *
12
+ * Deliberate constraints:
13
+ * - `hive` (the Mission Control identity from DESIGN.md) is the base: its
14
+ * values ARE the globals.css defaults. An app inherits the dashboard theme
15
+ * by carrying NO `data-theme` attribute; `data-theme="hive"` is an explicit
16
+ * reset back to the stock look (meaningful inside a themed dashboard).
17
+ * - Every emitted preset block is SELF-CONTAINED: the builder spreads the
18
+ * stock base values (`BASE_SCOPE_VARS`) under each preset's own overrides,
19
+ * so a scoped preset fully resets the canvas instead of blending with
20
+ * whatever full-field preset the surrounding dashboard runs (an accent
21
+ * preset under an Ember dashboard must deliver its promised zinc field,
22
+ * and a classic dashboard's status palette must not leak into an app).
23
+ * - Action (`--color-action-*`) and destructive tokens are NEVER themed.
24
+ * Status (`--color-status-*`) is themed ONLY by the classic-theme presets
25
+ * (classics.ts) whose identity includes a status palette; hue
26
+ * SEMANTICS stay fixed everywhere (success=green, error=red, …) so a failed
27
+ * task chip reads as "failed" under every preset.
28
+ * - Light and dark values are both declared per preset; the global `.dark`
29
+ * class keeps choosing the mode, so scoped themes follow the mode switch
30
+ * for free.
31
+ *
32
+ * Selector shape (specificity matters):
33
+ * - light: `html[data-theme="x"], [data-theme="x"]`. The `html`-qualified
34
+ * form (0,1,1) outranks the base `.dark` block (0,1,0) by column, so rule
35
+ * order against the compiled stylesheet is irrelevant; the bare attribute
36
+ * form covers scoped wrappers.
37
+ * - dark: `html.dark[data-theme="x"], .dark [data-theme="x"]`. (0,2,1)/(0,2,0)
38
+ * outranks every light form above.
39
+ * This only works because `globals.css` scopes dark overrides to `.dark`
40
+ * itself (inheritance), NOT `.dark *` (per-element stamping). Do not
41
+ * reintroduce the descendant form there.
42
+ *
43
+ * This module is pure (no DOM), so the API server and the dashboard share it.
44
+ * The dashboard re-exports it from `apps/ui/src/lib/themes.ts` and adds the
45
+ * `<head>` injection there. HTML pages read the same presets, contract keys
46
+ * only, through `page-theme.ts`.
47
+ */
48
+
49
+ import { CLASSIC_THEME_PRESETS } from "./classics";
50
+
51
+ /**
52
+ * Stock (Hive) values for every custom property that ANY preset overrides,
53
+ * copied from the `@theme` / `.dark` blocks in `apps/ui/src/styles/globals.css`
54
+ * (keep in sync when a base token there changes; `themes-presets.test.ts`
55
+ * fails on drift). Spread under each preset's own overrides at emit time so
56
+ * every `[data-theme]` block is self-contained: tokens a preset doesn't
57
+ * override reset to stock instead of inheriting the surrounding dashboard
58
+ * preset. Tokens NO preset touches (destructive, action-*, chart-2…5) are
59
+ * deliberately absent: inheriting those is always inheriting the base.
60
+ */
61
+ export const BASE_SCOPE_VARS: { light: Record<string, string>; dark: Record<string, string> } = {
62
+ light: {
63
+ "--color-background": "oklch(1 0 0)",
64
+ "--color-foreground": "oklch(0.141 0.005 285.823)",
65
+ "--color-card": "oklch(1 0 0)",
66
+ "--color-card-foreground": "oklch(0.141 0.005 285.823)",
67
+ "--color-surface": "oklch(0.985 0.0015 286)",
68
+ "--color-popover": "oklch(1 0 0)",
69
+ "--color-popover-foreground": "oklch(0.141 0.005 285.823)",
70
+ "--color-primary": "oklch(0.555 0.163 48.998)",
71
+ "--color-primary-foreground": "oklch(0.985 0 0)",
72
+ "--color-secondary": "oklch(0.967 0.001 286.375)",
73
+ "--color-secondary-foreground": "oklch(0.21 0.006 285.885)",
74
+ "--color-muted": "oklch(0.967 0.001 286.375)",
75
+ "--color-muted-foreground": "oklch(0.552 0.016 285.938)",
76
+ "--color-accent": "oklch(0.943 0.003 286.375)",
77
+ "--color-accent-foreground": "oklch(0.21 0.006 285.885)",
78
+ "--color-border": "oklch(0.92 0.004 286.32)",
79
+ "--color-border-subtle": "oklch(0.945 0.003 286)",
80
+ "--color-input": "oklch(0.92 0.004 286.32)",
81
+ "--color-ring": "oklch(0.555 0.163 48.998)",
82
+ "--color-chart-1": "oklch(0.646 0.222 41.116)",
83
+ "--color-sidebar": "oklch(0.985 0 0)",
84
+ "--color-sidebar-foreground": "oklch(0.141 0.005 285.823)",
85
+ "--color-sidebar-primary": "oklch(0.555 0.163 48.998)",
86
+ "--color-sidebar-primary-foreground": "oklch(0.985 0 0)",
87
+ "--color-sidebar-accent": "oklch(0.943 0.003 286.375)",
88
+ "--color-sidebar-accent-foreground": "oklch(0.21 0.006 285.885)",
89
+ "--color-sidebar-border": "oklch(0.945 0.003 286)",
90
+ "--color-sidebar-ring": "oklch(0.555 0.163 48.998)",
91
+ "--color-status-success": "oklch(0.74 0.1 163)",
92
+ "--color-status-success-strong": "oklch(0.5 0.09 163)",
93
+ "--color-status-success-foreground": "oklch(0.21 0.006 285.885)",
94
+ "--color-status-active": "oklch(0.81 0.11 75)",
95
+ "--color-status-active-strong": "oklch(0.55 0.1 68)",
96
+ "--color-status-active-foreground": "oklch(0.21 0.006 285.885)",
97
+ "--color-status-error": "oklch(0.72 0.12 25)",
98
+ "--color-status-error-strong": "oklch(0.51 0.14 25)",
99
+ "--color-status-error-foreground": "oklch(0.21 0.006 285.885)",
100
+ "--color-status-info": "oklch(0.74 0.09 235)",
101
+ "--color-status-info-strong": "oklch(0.5 0.09 240)",
102
+ "--color-status-info-foreground": "oklch(0.21 0.006 285.885)",
103
+ "--color-status-pending": "oklch(0.84 0.1 95)",
104
+ "--color-status-pending-strong": "oklch(0.55 0.09 90)",
105
+ "--color-status-pending-foreground": "oklch(0.21 0.006 285.885)",
106
+ "--color-status-warning": "oklch(0.76 0.11 55)",
107
+ "--color-status-warning-strong": "oklch(0.53 0.11 50)",
108
+ "--color-status-warning-foreground": "oklch(0.21 0.006 285.885)",
109
+ "--color-status-paused": "oklch(0.71 0.1 262)",
110
+ "--color-status-paused-strong": "oklch(0.5 0.1 262)",
111
+ "--color-status-paused-foreground": "oklch(0.21 0.006 285.885)",
112
+ "--color-status-neutral": "oklch(0.62 0.014 286)",
113
+ "--color-status-neutral-strong": "oklch(0.48 0.014 286)",
114
+ "--color-status-neutral-foreground": "oklch(0.21 0.006 285.885)",
115
+ },
116
+ dark: {
117
+ "--color-background": "oklch(0.141 0.005 285.823)",
118
+ "--color-foreground": "oklch(0.985 0 0)",
119
+ "--color-card": "oklch(0.21 0.006 285.885)",
120
+ "--color-card-foreground": "oklch(0.985 0 0)",
121
+ "--color-surface": "oklch(0.185 0.006 286)",
122
+ "--color-popover": "oklch(0.21 0.006 285.885)",
123
+ "--color-popover-foreground": "oklch(0.985 0 0)",
124
+ "--color-primary": "oklch(0.769 0.188 70.08)",
125
+ "--color-primary-foreground": "oklch(0.21 0.006 285.885)",
126
+ "--color-secondary": "oklch(0.274 0.006 286.033)",
127
+ "--color-secondary-foreground": "oklch(0.985 0 0)",
128
+ "--color-muted": "oklch(0.274 0.006 286.033)",
129
+ "--color-muted-foreground": "oklch(0.705 0.015 286.067)",
130
+ "--color-accent": "oklch(0.274 0.006 286.033)",
131
+ "--color-accent-foreground": "oklch(0.985 0 0)",
132
+ "--color-border": "oklch(1 0 0 / 10%)",
133
+ "--color-border-subtle": "oklch(1 0 0 / 8%)",
134
+ "--color-input": "oklch(1 0 0 / 15%)",
135
+ "--color-ring": "oklch(0.769 0.188 70.08)",
136
+ "--color-chart-1": "oklch(0.488 0.243 264.376)",
137
+ "--color-sidebar": "oklch(0.21 0.006 285.885)",
138
+ "--color-sidebar-foreground": "oklch(0.985 0 0)",
139
+ "--color-sidebar-primary": "oklch(0.769 0.188 70.08)",
140
+ "--color-sidebar-primary-foreground": "oklch(0.985 0 0)",
141
+ "--color-sidebar-accent": "oklch(0.274 0.006 286.033)",
142
+ "--color-sidebar-accent-foreground": "oklch(0.985 0 0)",
143
+ "--color-sidebar-border": "oklch(1 0 0 / 8%)",
144
+ "--color-sidebar-ring": "oklch(0.769 0.188 70.08)",
145
+ "--color-status-success": "oklch(0.78 0.1 163)",
146
+ "--color-status-success-strong": "oklch(0.78 0.1 163)",
147
+ "--color-status-success-foreground": "oklch(0.21 0.006 285.885)",
148
+ "--color-status-active": "oklch(0.84 0.1 80)",
149
+ "--color-status-active-strong": "oklch(0.84 0.1 80)",
150
+ "--color-status-active-foreground": "oklch(0.21 0.006 285.885)",
151
+ "--color-status-error": "oklch(0.74 0.11 22)",
152
+ "--color-status-error-strong": "oklch(0.74 0.11 22)",
153
+ "--color-status-error-foreground": "oklch(0.21 0.006 285.885)",
154
+ "--color-status-info": "oklch(0.78 0.09 235)",
155
+ "--color-status-info-strong": "oklch(0.78 0.09 235)",
156
+ "--color-status-info-foreground": "oklch(0.21 0.006 285.885)",
157
+ "--color-status-pending": "oklch(0.86 0.09 95)",
158
+ "--color-status-pending-strong": "oklch(0.86 0.09 95)",
159
+ "--color-status-pending-foreground": "oklch(0.21 0.006 285.885)",
160
+ "--color-status-warning": "oklch(0.79 0.1 55)",
161
+ "--color-status-warning-strong": "oklch(0.79 0.1 55)",
162
+ "--color-status-warning-foreground": "oklch(0.21 0.006 285.885)",
163
+ "--color-status-paused": "oklch(0.74 0.09 260)",
164
+ "--color-status-paused-strong": "oklch(0.74 0.09 260)",
165
+ "--color-status-paused-foreground": "oklch(0.21 0.006 285.885)",
166
+ "--color-status-neutral": "oklch(0.72 0.012 286)",
167
+ "--color-status-neutral-strong": "oklch(0.72 0.012 286)",
168
+ "--color-status-neutral-foreground": "oklch(0.21 0.006 285.885)",
169
+ },
170
+ };
171
+
172
+ export interface ThemePreset {
173
+ id: string;
174
+ name: string;
175
+ description: string;
176
+ /** Representative accent per mode, picker swatches only, never rendering. */
177
+ accent: { light: string; dark: string };
178
+ /** Representative field (background) per mode, picker swatches only. */
179
+ field: { light: string; dark: string };
180
+ /**
181
+ * CSS custom-property overrides, keyed by full var name.
182
+ *
183
+ * INVARIANT: every key set in `light` must also be set in `dark`. The light
184
+ * rule is not mode-guarded (it matches in dark mode too and only loses to
185
+ * the higher-specificity dark rule), so a key missing from `dark` leaks its
186
+ * LIGHT value into dark mode. (Dark-only keys are fine: in light mode the
187
+ * base token simply applies.)
188
+ */
189
+ vars: { light: Record<string, string>; dark: Record<string, string> };
190
+ }
191
+
192
+ export const DEFAULT_THEME_ID = "hive";
193
+
194
+ /**
195
+ * Accent-swap preset: keeps the zinc field and replaces the one voice of
196
+ * color (primary / ring / sidebar accent / lead chart series).
197
+ */
198
+ function accentPreset(
199
+ id: string,
200
+ name: string,
201
+ description: string,
202
+ light: { primary: string; primaryForeground: string },
203
+ dark: { primary: string; primaryForeground: string },
204
+ ): ThemePreset {
205
+ return {
206
+ id,
207
+ name,
208
+ description,
209
+ accent: { light: light.primary, dark: dark.primary },
210
+ field: { light: "oklch(1 0 0)", dark: "oklch(0.141 0.005 285.823)" },
211
+ vars: {
212
+ light: {
213
+ "--color-primary": light.primary,
214
+ "--color-primary-foreground": light.primaryForeground,
215
+ "--color-ring": light.primary,
216
+ "--color-sidebar-primary": light.primary,
217
+ "--color-sidebar-primary-foreground": light.primaryForeground,
218
+ "--color-sidebar-ring": light.primary,
219
+ "--color-chart-1": light.primary,
220
+ },
221
+ dark: {
222
+ "--color-primary": dark.primary,
223
+ "--color-primary-foreground": dark.primaryForeground,
224
+ "--color-ring": dark.primary,
225
+ "--color-sidebar-primary": dark.primary,
226
+ "--color-sidebar-primary-foreground": dark.primaryForeground,
227
+ "--color-sidebar-ring": dark.primary,
228
+ "--color-chart-1": dark.primary,
229
+ },
230
+ },
231
+ };
232
+ }
233
+
234
+ const NEAR_WHITE = "oklch(0.985 0 0)";
235
+ const ZINC_900 = "oklch(0.21 0.006 285.885)";
236
+
237
+ export const THEME_PRESETS: ThemePreset[] = [
238
+ {
239
+ id: DEFAULT_THEME_ID,
240
+ name: "Hive",
241
+ description: "The stock Mission Control look: zinc field, one amber voice.",
242
+ accent: { light: "oklch(0.555 0.163 48.998)", dark: "oklch(0.769 0.188 70.08)" },
243
+ field: { light: "oklch(1 0 0)", dark: "oklch(0.141 0.005 285.823)" },
244
+ // The base theme IS globals.css: no overrides of its own. The builder
245
+ // still emits a `[data-theme="hive"]` block (the BASE_SCOPE_VARS spread)
246
+ // so an explicit Hive selection resets a canvas inside a themed
247
+ // dashboard; "inherit the surrounding theme" is the ABSENCE of the
248
+ // attribute, not this preset.
249
+ vars: { light: {}, dark: {} },
250
+ },
251
+ accentPreset(
252
+ "meadow",
253
+ "Meadow",
254
+ "Zinc field with an emerald voice.",
255
+ { primary: "oklch(0.596 0.145 163.225)", primaryForeground: NEAR_WHITE },
256
+ { primary: "oklch(0.765 0.177 163.223)", primaryForeground: ZINC_900 },
257
+ ),
258
+ accentPreset(
259
+ "iris",
260
+ "Iris",
261
+ "Zinc field with a violet voice.",
262
+ { primary: "oklch(0.541 0.281 293.009)", primaryForeground: NEAR_WHITE },
263
+ { primary: "oklch(0.702 0.183 293.541)", primaryForeground: ZINC_900 },
264
+ ),
265
+ accentPreset(
266
+ "rose",
267
+ "Rose",
268
+ "Zinc field with a deep rose voice.",
269
+ { primary: "oklch(0.586 0.253 17.585)", primaryForeground: NEAR_WHITE },
270
+ { primary: "oklch(0.712 0.194 13.428)", primaryForeground: ZINC_900 },
271
+ ),
272
+ {
273
+ id: "cobalt",
274
+ name: "Cobalt",
275
+ description: "Cool slate field with a saturated blue voice.",
276
+ accent: { light: "oklch(0.546 0.245 262.881)", dark: "oklch(0.707 0.165 254.624)" },
277
+ field: { light: "oklch(1 0 0)", dark: "oklch(0.129 0.042 264.695)" },
278
+ vars: {
279
+ light: {
280
+ "--color-foreground": "oklch(0.129 0.042 264.695)",
281
+ "--color-card-foreground": "oklch(0.129 0.042 264.695)",
282
+ "--color-popover-foreground": "oklch(0.129 0.042 264.695)",
283
+ "--color-surface": "oklch(0.984 0.003 247.858)",
284
+ "--color-primary": "oklch(0.546 0.245 262.881)",
285
+ "--color-primary-foreground": NEAR_WHITE,
286
+ "--color-secondary": "oklch(0.968 0.007 247.896)",
287
+ "--color-secondary-foreground": "oklch(0.208 0.042 265.755)",
288
+ "--color-muted": "oklch(0.968 0.007 247.896)",
289
+ "--color-muted-foreground": "oklch(0.554 0.046 257.417)",
290
+ // Hover/highlight fill one step below muted (slate-100 → ~slate-150)
291
+ // so light-mode hovers read (same rationale as the base accent).
292
+ "--color-accent": "oklch(0.945 0.01 251.7)",
293
+ "--color-accent-foreground": "oklch(0.208 0.042 265.755)",
294
+ "--color-border": "oklch(0.929 0.013 255.508)",
295
+ "--color-border-subtle": "oklch(0.948 0.01 252)",
296
+ "--color-input": "oklch(0.929 0.013 255.508)",
297
+ "--color-ring": "oklch(0.546 0.245 262.881)",
298
+ "--color-chart-1": "oklch(0.546 0.245 262.881)",
299
+ "--color-sidebar": "oklch(0.984 0.003 247.858)",
300
+ "--color-sidebar-foreground": "oklch(0.129 0.042 264.695)",
301
+ "--color-sidebar-primary": "oklch(0.546 0.245 262.881)",
302
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
303
+ "--color-sidebar-accent": "oklch(0.945 0.01 251.7)",
304
+ "--color-sidebar-accent-foreground": "oklch(0.208 0.042 265.755)",
305
+ "--color-sidebar-border": "oklch(0.948 0.01 252)",
306
+ "--color-sidebar-ring": "oklch(0.546 0.245 262.881)",
307
+ },
308
+ dark: {
309
+ "--color-background": "oklch(0.129 0.042 264.695)",
310
+ "--color-foreground": NEAR_WHITE,
311
+ "--color-card": "oklch(0.208 0.042 265.755)",
312
+ "--color-card-foreground": NEAR_WHITE,
313
+ "--color-surface": "oklch(0.17 0.042 265)",
314
+ "--color-popover": "oklch(0.208 0.042 265.755)",
315
+ "--color-popover-foreground": NEAR_WHITE,
316
+ "--color-primary": "oklch(0.707 0.165 254.624)",
317
+ "--color-primary-foreground": "oklch(0.129 0.042 264.695)",
318
+ "--color-secondary": "oklch(0.279 0.041 260.031)",
319
+ "--color-secondary-foreground": NEAR_WHITE,
320
+ "--color-muted": "oklch(0.279 0.041 260.031)",
321
+ "--color-muted-foreground": "oklch(0.704 0.04 256.788)",
322
+ "--color-accent": "oklch(0.279 0.041 260.031)",
323
+ "--color-accent-foreground": NEAR_WHITE,
324
+ "--color-border": "oklch(1 0 0 / 10%)",
325
+ "--color-border-subtle": "oklch(1 0 0 / 8%)",
326
+ "--color-input": "oklch(1 0 0 / 15%)",
327
+ "--color-ring": "oklch(0.707 0.165 254.624)",
328
+ "--color-chart-1": "oklch(0.707 0.165 254.624)",
329
+ "--color-sidebar": "oklch(0.208 0.042 265.755)",
330
+ "--color-sidebar-foreground": NEAR_WHITE,
331
+ "--color-sidebar-primary": "oklch(0.707 0.165 254.624)",
332
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
333
+ "--color-sidebar-accent": "oklch(0.279 0.041 260.031)",
334
+ "--color-sidebar-accent-foreground": NEAR_WHITE,
335
+ "--color-sidebar-border": "oklch(1 0 0 / 8%)",
336
+ "--color-sidebar-ring": "oklch(0.707 0.165 254.624)",
337
+ },
338
+ },
339
+ },
340
+ {
341
+ id: "ember",
342
+ name: "Ember",
343
+ description: "Warm stone field with a burnt orange voice.",
344
+ accent: { light: "oklch(0.646 0.222 41.116)", dark: "oklch(0.75 0.183 55.934)" },
345
+ field: { light: "oklch(0.985 0.001 106.423)", dark: "oklch(0.147 0.004 49.25)" },
346
+ vars: {
347
+ light: {
348
+ "--color-background": "oklch(0.985 0.001 106.423)",
349
+ "--color-foreground": "oklch(0.147 0.004 49.25)",
350
+ "--color-card": "oklch(1 0 0)",
351
+ "--color-card-foreground": "oklch(0.147 0.004 49.25)",
352
+ "--color-surface": "oklch(0.97 0.001 106.424)",
353
+ "--color-popover": "oklch(1 0 0)",
354
+ "--color-popover-foreground": "oklch(0.147 0.004 49.25)",
355
+ "--color-primary": "oklch(0.646 0.222 41.116)",
356
+ "--color-primary-foreground": NEAR_WHITE,
357
+ "--color-secondary": "oklch(0.97 0.001 106.424)",
358
+ "--color-secondary-foreground": "oklch(0.216 0.006 56.043)",
359
+ "--color-muted": "oklch(0.97 0.001 106.424)",
360
+ "--color-muted-foreground": "oklch(0.553 0.013 58.071)",
361
+ // Hover/highlight fill one step below muted (stone-100 → ~stone-150);
362
+ // the sidebar-accent below was already at stone-200 and stays.
363
+ "--color-accent": "oklch(0.946 0.002 48.7)",
364
+ "--color-accent-foreground": "oklch(0.216 0.006 56.043)",
365
+ "--color-border": "oklch(0.923 0.003 48.717)",
366
+ "--color-border-subtle": "oklch(0.947 0.002 48.717)",
367
+ "--color-input": "oklch(0.923 0.003 48.717)",
368
+ "--color-ring": "oklch(0.646 0.222 41.116)",
369
+ "--color-chart-1": "oklch(0.646 0.222 41.116)",
370
+ "--color-sidebar": "oklch(0.97 0.001 106.424)",
371
+ "--color-sidebar-foreground": "oklch(0.147 0.004 49.25)",
372
+ "--color-sidebar-primary": "oklch(0.646 0.222 41.116)",
373
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
374
+ "--color-sidebar-accent": "oklch(0.923 0.003 48.717)",
375
+ "--color-sidebar-accent-foreground": "oklch(0.216 0.006 56.043)",
376
+ "--color-sidebar-border": "oklch(0.947 0.002 48.717)",
377
+ "--color-sidebar-ring": "oklch(0.646 0.222 41.116)",
378
+ },
379
+ dark: {
380
+ "--color-background": "oklch(0.147 0.004 49.25)",
381
+ "--color-foreground": NEAR_WHITE,
382
+ "--color-card": "oklch(0.216 0.006 56.043)",
383
+ "--color-card-foreground": NEAR_WHITE,
384
+ "--color-surface": "oklch(0.185 0.005 49)",
385
+ "--color-popover": "oklch(0.216 0.006 56.043)",
386
+ "--color-popover-foreground": NEAR_WHITE,
387
+ "--color-primary": "oklch(0.75 0.183 55.934)",
388
+ "--color-primary-foreground": "oklch(0.147 0.004 49.25)",
389
+ "--color-secondary": "oklch(0.268 0.007 34.298)",
390
+ "--color-secondary-foreground": NEAR_WHITE,
391
+ "--color-muted": "oklch(0.268 0.007 34.298)",
392
+ "--color-muted-foreground": "oklch(0.709 0.01 56.259)",
393
+ "--color-accent": "oklch(0.268 0.007 34.298)",
394
+ "--color-accent-foreground": NEAR_WHITE,
395
+ "--color-border": "oklch(1 0 0 / 10%)",
396
+ "--color-border-subtle": "oklch(1 0 0 / 8%)",
397
+ "--color-input": "oklch(1 0 0 / 15%)",
398
+ "--color-ring": "oklch(0.75 0.183 55.934)",
399
+ "--color-chart-1": "oklch(0.75 0.183 55.934)",
400
+ "--color-sidebar": "oklch(0.216 0.006 56.043)",
401
+ "--color-sidebar-foreground": NEAR_WHITE,
402
+ "--color-sidebar-primary": "oklch(0.75 0.183 55.934)",
403
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
404
+ "--color-sidebar-accent": "oklch(0.268 0.007 34.298)",
405
+ "--color-sidebar-accent-foreground": NEAR_WHITE,
406
+ "--color-sidebar-border": "oklch(1 0 0 / 8%)",
407
+ "--color-sidebar-ring": "oklch(0.75 0.183 55.934)",
408
+ },
409
+ },
410
+ },
411
+ {
412
+ id: "carbon",
413
+ name: "Carbon",
414
+ description: "Pure monochrome. The interface recedes, the data speaks.",
415
+ accent: { light: ZINC_900, dark: NEAR_WHITE },
416
+ field: { light: "oklch(1 0 0)", dark: "oklch(0.141 0.005 285.823)" },
417
+ vars: {
418
+ light: {
419
+ "--color-primary": ZINC_900,
420
+ "--color-primary-foreground": NEAR_WHITE,
421
+ "--color-ring": "oklch(0.552 0.016 285.938)",
422
+ "--color-sidebar-primary": ZINC_900,
423
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
424
+ "--color-sidebar-ring": "oklch(0.552 0.016 285.938)",
425
+ },
426
+ dark: {
427
+ "--color-primary": NEAR_WHITE,
428
+ "--color-primary-foreground": ZINC_900,
429
+ "--color-ring": "oklch(0.705 0.015 286.067)",
430
+ "--color-sidebar-primary": NEAR_WHITE,
431
+ "--color-sidebar-primary-foreground": ZINC_900,
432
+ "--color-sidebar-ring": "oklch(0.705 0.015 286.067)",
433
+ },
434
+ },
435
+ },
436
+ {
437
+ // The warm editorial look of the original pages skill: off-white canvas
438
+ // (#F7F6F3), white cards, warm borders (#DEDBD2), charcoal text (#18181B),
439
+ // muted text (#62646A), one blue voice (#2563EB). Full-field like Ember;
440
+ // status tokens stay stock.
441
+ id: "paper",
442
+ name: "Paper",
443
+ description: "Warm editorial paper: off-white field, white cards, one blue voice.",
444
+ accent: { light: "oklch(0.546 0.215 262.881)", dark: "oklch(0.714 0.143 254.624)" },
445
+ field: { light: "oklch(0.973 0.004 91.446)", dark: "oklch(0.21 0.005 91.5)" },
446
+ vars: {
447
+ light: {
448
+ "--color-background": "oklch(0.973 0.004 91.446)",
449
+ "--color-foreground": ZINC_900,
450
+ "--color-card": "oklch(1 0 0)",
451
+ "--color-card-foreground": ZINC_900,
452
+ "--color-surface": "oklch(0.988 0.001 106.423)",
453
+ "--color-popover": "oklch(1 0 0)",
454
+ "--color-popover-foreground": ZINC_900,
455
+ "--color-primary": "oklch(0.546 0.215 262.881)",
456
+ "--color-primary-foreground": NEAR_WHITE,
457
+ "--color-secondary": "oklch(0.951 0.006 91.5)",
458
+ "--color-secondary-foreground": ZINC_900,
459
+ "--color-muted": "oklch(0.951 0.006 91.5)",
460
+ "--color-muted-foreground": "oklch(0.504 0.01 271.22)",
461
+ "--color-accent": "oklch(0.932 0.008 91.5)",
462
+ "--color-accent-foreground": ZINC_900,
463
+ "--color-border": "oklch(0.891 0.013 91.529)",
464
+ "--color-border-subtle": "oklch(0.925 0.009 91.5)",
465
+ "--color-input": "oklch(0.891 0.013 91.529)",
466
+ "--color-ring": "oklch(0.546 0.215 262.881)",
467
+ "--color-chart-1": "oklch(0.546 0.215 262.881)",
468
+ "--color-sidebar": "oklch(0.951 0.006 91.5)",
469
+ "--color-sidebar-foreground": ZINC_900,
470
+ "--color-sidebar-primary": "oklch(0.546 0.215 262.881)",
471
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
472
+ "--color-sidebar-accent": "oklch(0.915 0.01 91.5)",
473
+ "--color-sidebar-accent-foreground": ZINC_900,
474
+ "--color-sidebar-border": "oklch(0.925 0.009 91.5)",
475
+ "--color-sidebar-ring": "oklch(0.546 0.215 262.881)",
476
+ },
477
+ dark: {
478
+ "--color-background": "oklch(0.21 0.005 91.5)",
479
+ "--color-foreground": "oklch(0.94 0.008 91.5)",
480
+ "--color-card": "oklch(0.25 0.006 91.5)",
481
+ "--color-card-foreground": "oklch(0.94 0.008 91.5)",
482
+ "--color-surface": "oklch(0.23 0.0055 91.5)",
483
+ "--color-popover": "oklch(0.25 0.006 91.5)",
484
+ "--color-popover-foreground": "oklch(0.94 0.008 91.5)",
485
+ "--color-primary": "oklch(0.714 0.143 254.624)",
486
+ "--color-primary-foreground": "oklch(0.21 0.005 91.5)",
487
+ "--color-secondary": "oklch(0.3 0.007 91.5)",
488
+ "--color-secondary-foreground": "oklch(0.94 0.008 91.5)",
489
+ "--color-muted": "oklch(0.3 0.007 91.5)",
490
+ "--color-muted-foreground": "oklch(0.72 0.012 91.5)",
491
+ "--color-accent": "oklch(0.3 0.007 91.5)",
492
+ "--color-accent-foreground": "oklch(0.94 0.008 91.5)",
493
+ "--color-border": "oklch(0.95 0.02 91.5 / 12%)",
494
+ "--color-border-subtle": "oklch(0.95 0.02 91.5 / 8%)",
495
+ "--color-input": "oklch(0.95 0.02 91.5 / 16%)",
496
+ "--color-ring": "oklch(0.714 0.143 254.624)",
497
+ "--color-chart-1": "oklch(0.714 0.143 254.624)",
498
+ "--color-sidebar": "oklch(0.25 0.006 91.5)",
499
+ "--color-sidebar-foreground": "oklch(0.94 0.008 91.5)",
500
+ "--color-sidebar-primary": "oklch(0.714 0.143 254.624)",
501
+ "--color-sidebar-primary-foreground": NEAR_WHITE,
502
+ "--color-sidebar-accent": "oklch(0.3 0.007 91.5)",
503
+ "--color-sidebar-accent-foreground": "oklch(0.94 0.008 91.5)",
504
+ "--color-sidebar-border": "oklch(0.95 0.02 91.5 / 8%)",
505
+ "--color-sidebar-ring": "oklch(0.714 0.143 254.624)",
506
+ },
507
+ },
508
+ },
509
+ // Classic editor/platform themes: the only presets that override
510
+ // `--color-status-*` (their identity includes a status palette; hue
511
+ // semantics stay fixed). See classics.ts.
512
+ ...CLASSIC_THEME_PRESETS,
513
+ ];
514
+
515
+ const PRESETS_BY_ID = new Map(THEME_PRESETS.map((preset) => [preset.id, preset]));
516
+
517
+ /** Every preset id, in catalog order (server-side validation and error messages). */
518
+ export const THEME_PRESET_IDS: readonly string[] = THEME_PRESETS.map((preset) => preset.id);
519
+
520
+ export function getThemePreset(id: string | null | undefined): ThemePreset | null {
521
+ if (!id) return null;
522
+ return PRESETS_BY_ID.get(id) ?? null;
523
+ }
524
+
525
+ export function isThemePresetId(id: string): boolean {
526
+ return PRESETS_BY_ID.has(id);
527
+ }
528
+
529
+ /**
530
+ * DASHBOARD-ROOT normalization of an untrusted stored id: a known preset id,
531
+ * or null for unknown ids AND for `hive` (at the `<html>` level the base
532
+ * needs no attribute: there is nothing outer to reset from). Scoped app
533
+ * canvases must NOT use this: there `hive` is a meaningful explicit reset
534
+ * (see the resolution in `apps/ui/src/components/apps/app-surface.tsx`).
535
+ */
536
+ export function resolveThemeId(id: string | null | undefined): string | null {
537
+ const preset = getThemePreset(id);
538
+ return preset && preset.id !== DEFAULT_THEME_ID ? preset.id : null;
539
+ }
540
+
541
+ function declarations(vars: Record<string, string>, indent: string): string {
542
+ return Object.entries(vars)
543
+ .map(([name, value]) => `${indent}${name}: ${value};`)
544
+ .join("\n");
545
+ }
546
+
547
+ export function buildThemePresetCss(): string {
548
+ const blocks: string[] = [];
549
+ for (const preset of THEME_PRESETS) {
550
+ // Self-contained blocks: base values under the preset's own overrides,
551
+ // so a scoped canvas resets fully instead of blending with the outer
552
+ // dashboard preset. `hive` emits the bare base: the explicit reset.
553
+ const light = { ...BASE_SCOPE_VARS.light, ...preset.vars.light };
554
+ const dark = { ...BASE_SCOPE_VARS.dark, ...preset.vars.dark };
555
+ blocks.push(
556
+ `html[data-theme="${preset.id}"],\n[data-theme="${preset.id}"] {\n${declarations(light, " ")}\n}`,
557
+ `html.dark[data-theme="${preset.id}"],\n.dark [data-theme="${preset.id}"] {\n${declarations(dark, " ")}\n}`,
558
+ );
559
+ }
560
+ return blocks.join("\n\n");
561
+ }