ompchamber 1.2.1 → 2.0.0

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 (223) hide show
  1. package/README.md +131 -147
  2. package/dist/client/index.html +4 -2
  3. package/dist/client/static/css/index.5f630421b7.css +2 -0
  4. package/dist/client/static/js/77.0d81c74cf0.js +139 -0
  5. package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
  6. package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
  7. package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
  8. package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
  9. package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
  10. package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
  11. package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
  12. package/dist/client/static/js/async/e.93214d622b.js +79 -0
  13. package/dist/client/static/js/async/g.74ff36419f.js +2 -0
  14. package/dist/client/static/js/async/{m.2e1ea3db9e.js.LICENSE.txt → g.74ff36419f.js.LICENSE.txt} +5 -0
  15. package/dist/client/static/js/async/h1.393f066d09.js +1 -0
  16. package/dist/client/static/js/async/jl.874d352278.js +1 -0
  17. package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
  18. package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
  19. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
  20. package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
  21. package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
  22. package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
  23. package/dist/client/static/js/async/px.fc6877924b.js +1 -0
  24. package/dist/client/static/js/async/q.33d6779f98.js +21 -0
  25. package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
  26. package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
  27. package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
  28. package/dist/client/static/js/async/vt.42416b707d.js +1 -0
  29. package/dist/client/static/js/async/x1.da8687159b.js +90 -0
  30. package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
  31. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
  32. package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
  33. package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
  34. package/dist/client/static/js/async/yv.d99d829136.js +1 -0
  35. package/dist/client/static/js/index.c05535dbde.js +38 -0
  36. package/package.json +3 -3
  37. package/src/client/components/common/ComposerTextarea.tsx +9 -1
  38. package/src/client/components/common/MarkdownRenderer.tsx +50 -12
  39. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
  40. package/src/client/components/settings/Modal.tsx +11 -2
  41. package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
  42. package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
  43. package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
  44. package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
  45. package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
  46. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
  47. package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
  48. package/src/client/components/workspace/SearchPanel.tsx +16 -3
  49. package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
  50. package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
  51. package/src/client/components/workspace/btw-panel/index.tsx +124 -45
  52. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
  53. package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
  54. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
  55. package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
  56. package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
  57. package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
  58. package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
  59. package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
  60. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
  61. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
  62. package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
  63. package/src/client/components/workspace/editor/index.tsx +11 -2
  64. package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
  65. package/src/client/components/workspace/file-explorer/index.tsx +86 -16
  66. package/src/client/components/workspace/git-panel/index.tsx +60 -78
  67. package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
  68. package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
  69. package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
  70. package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
  71. package/src/client/data/theme/terminal.ts +50 -52
  72. package/src/client/hooks/chat/btw/index.ts +139 -18
  73. package/src/client/hooks/chat/btw/intercept.ts +8 -8
  74. package/src/client/hooks/chat/btw/mode.ts +111 -16
  75. package/src/client/hooks/chat/composer/index.ts +10 -4
  76. package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
  77. package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
  78. package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
  79. package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
  80. package/src/client/hooks/chat/timeline/index.ts +8 -20
  81. package/src/client/hooks/chat/timeline/queue.ts +66 -9
  82. package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
  83. package/src/client/hooks/chat/timeline/scroll.ts +93 -20
  84. package/src/client/hooks/chat/timeline/session-load.ts +7 -1
  85. package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
  86. package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
  87. package/src/client/hooks/settings/providers.ts +34 -3
  88. package/src/client/hooks/ui/open-file.ts +7 -1
  89. package/src/client/hooks/ui/theme.ts +82 -57
  90. package/src/client/hooks/workspace/file-tabs.ts +5 -1
  91. package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
  92. package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
  93. package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
  94. package/src/client/hooks/workspace/search-stream.ts +24 -5
  95. package/src/client/main.tsx +6 -0
  96. package/src/client/tailwind.css +19 -56
  97. package/src/server/lib/btw/child.server.ts +41 -0
  98. package/src/server/lib/btw/lifecycle.server.ts +254 -0
  99. package/src/server/lib/btw/prompt.ts +13 -5
  100. package/src/server/lib/btw/registry.server.ts +13 -4
  101. package/src/server/lib/btw/runtime.server.ts +149 -156
  102. package/src/server/lib/btw/schema.server.ts +27 -0
  103. package/src/server/lib/btw/service.server.ts +106 -5
  104. package/src/server/lib/btw/store.server.ts +57 -6
  105. package/src/server/lib/db/schema.ts +4 -0
  106. package/src/server/lib/db/settings-store.test.ts +65 -0
  107. package/src/server/lib/db/settings-store.ts +25 -0
  108. package/src/server/lib/fs/atomic-write.ts +42 -4
  109. package/src/server/lib/fs/config-lock.ts +74 -0
  110. package/src/server/lib/fs/git-ignore.ts +55 -5
  111. package/src/server/lib/fs/ripgrep.ts +119 -0
  112. package/src/server/lib/lifecycle/identity.ts +10 -64
  113. package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
  114. package/src/server/lib/lifecycle/proc/index.ts +38 -0
  115. package/src/server/lib/lifecycle/proc/linux.ts +80 -0
  116. package/src/server/lib/lifecycle/proc/ps.ts +86 -0
  117. package/src/server/lib/lifecycle/proc/types.ts +33 -0
  118. package/src/server/lib/lifecycle/proc/win32.ts +53 -0
  119. package/src/server/lib/models/provider-registry.server.ts +2 -2
  120. package/src/server/lib/models/registry.server.ts +1 -1
  121. package/src/server/lib/omp/config/behavior.ts +17 -17
  122. package/src/server/lib/omp/config/disabled-providers.ts +84 -35
  123. package/src/server/lib/omp/config/document.ts +128 -0
  124. package/src/server/lib/omp/config/extensions.ts +11 -10
  125. package/src/server/lib/omp/config/mcp.ts +4 -57
  126. package/src/server/lib/omp/config/model-overrides.ts +123 -0
  127. package/src/server/lib/omp/config/models-config.ts +92 -0
  128. package/src/server/lib/omp/config/models-validation.ts +159 -0
  129. package/src/server/lib/omp/config/path-scoped.ts +74 -0
  130. package/src/server/lib/omp/config/providers.ts +241 -211
  131. package/src/server/lib/omp/config/roles.ts +6 -14
  132. package/src/server/lib/omp/core/auth-credentials.ts +1 -1
  133. package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
  134. package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
  135. package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
  136. package/src/server/lib/omp/rpc/manager.ts +21 -21
  137. package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
  138. package/src/server/lib/omp/rpc/session-commands.ts +17 -29
  139. package/src/server/lib/omp/rpc/session-registry.ts +5 -66
  140. package/src/server/lib/omp/rpc/web-state.ts +0 -6
  141. package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
  142. package/src/server/lib/omp/session/auto-title.server.ts +95 -20
  143. package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
  144. package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
  145. package/src/server/lib/queue/delivery-harness.ts +88 -0
  146. package/src/server/lib/queue/delivery.server.test.ts +78 -149
  147. package/src/server/lib/queue/delivery.server.ts +105 -13
  148. package/src/server/lib/queue/store.server.ts +30 -5
  149. package/src/server/lib/terminal/foreground.test.ts +164 -0
  150. package/src/server/lib/terminal/foreground.ts +34 -6
  151. package/src/server/lib/terminal/history.ts +8 -7
  152. package/src/server/plugins/compress.ts +33 -5
  153. package/src/server/plugins/ssr.ts +14 -7
  154. package/src/server/routes/btw/topics.ts +89 -2
  155. package/src/server/routes/fs/git.ts +5 -6
  156. package/src/server/routes/fs/search.ts +17 -26
  157. package/src/server/routes/sessions/queue.ts +17 -4
  158. package/src/server/routes/settings/index.ts +2 -0
  159. package/src/server/routes/settings/model-override.ts +69 -0
  160. package/src/server/routes/settings/provider-models.ts +35 -12
  161. package/src/server/routes/settings/providers.ts +31 -1
  162. package/src/server/routes/settings/root.ts +8 -3
  163. package/src/shared/lib/chat/btw/client.ts +34 -0
  164. package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
  165. package/src/shared/lib/code/shiki-themes.ts +9 -5
  166. package/src/shared/lib/fs/file-tree.ts +17 -12
  167. package/src/shared/lib/markdown/autolink.test.ts +32 -0
  168. package/src/shared/lib/markdown/autolink.ts +50 -0
  169. package/src/shared/lib/markdown/document-urls.test.ts +68 -0
  170. package/src/shared/lib/markdown/document-urls.ts +118 -0
  171. package/src/shared/lib/markdown/marked.ts +27 -15
  172. package/src/shared/lib/markdown/mermaid.ts +2 -2
  173. package/src/shared/lib/models/catalog.ts +1 -1
  174. package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
  175. package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
  176. package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
  177. package/src/shared/lib/settings/client.ts +10 -4
  178. package/src/shared/lib/settings/diff.test.ts +31 -0
  179. package/src/shared/lib/settings/diff.ts +37 -0
  180. package/src/shared/lib/theme/catalog.test.ts +168 -0
  181. package/src/shared/lib/theme/catalog.ts +98 -0
  182. package/src/shared/lib/theme/color.ts +88 -0
  183. package/src/shared/lib/theme/css.ts +70 -0
  184. package/src/shared/lib/theme/palettes/chamber.ts +61 -0
  185. package/src/shared/lib/theme/palettes/dark.ts +313 -0
  186. package/src/shared/lib/theme/palettes/light.ts +313 -0
  187. package/src/shared/types/btw.ts +83 -4
  188. package/src/shared/types/index.ts +1 -0
  189. package/src/shared/types/settings/provider.ts +8 -1
  190. package/src/shared/types/settings/state.ts +10 -4
  191. package/src/shared/types/theme.ts +48 -0
  192. package/dist/client/static/css/index.b938f4a693.css +0 -2
  193. package/dist/client/static/js/77.8022a800b5.js +0 -139
  194. package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
  195. package/dist/client/static/js/async/66.86544ad741.js +0 -93
  196. package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
  197. package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
  198. package/dist/client/static/js/async/e.6443e739a2.js +0 -79
  199. package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
  200. package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
  201. package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
  202. package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
  203. package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
  204. package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
  205. package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
  206. package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
  207. package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
  208. package/dist/client/static/js/async/vt.20406a5604.js +0 -1
  209. package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
  210. package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
  211. package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
  212. package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
  213. package/dist/client/static/js/index.5fe7a2cb64.js +0 -34
  214. package/src/client/components/workspace/btw-panel/Composer.tsx +0 -183
  215. package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
  216. /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
  217. /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
  218. /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
  219. /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
  220. /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
  221. /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.33d6779f98.js.LICENSE.txt} +0 -0
  222. /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
  223. /package/dist/client/static/js/{index.5fe7a2cb64.js.LICENSE.txt → index.c05535dbde.js.LICENSE.txt} +0 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The theme catalog: every palette the chamber can paint, the lookups the whole
8
+ * app resolves a stored theme id through, and the light/dark verdict that used
9
+ * to be re-derived by hand in four places (`hooks/ui/theme.ts`,
10
+ * `code/shiki-themes.ts`, `markdown/mermaid.ts`, `server/plugins/ssr.ts`) — each
11
+ * with its own hardcoded id list that a new theme would have silently missed.
12
+ *
13
+ * Pure data plus pure functions: no DOM, no CSS, no server imports, so the
14
+ * client, the SSR shell and the tests all read the same catalog.
15
+ */
16
+
17
+ import type { ThemePalette, ThemeSwatch } from '@/shared/types/theme';
18
+ import { CHAMBER_PALETTES } from '@/shared/lib/theme/palettes/chamber';
19
+ import { DARK_PALETTES } from '@/shared/lib/theme/palettes/dark';
20
+ import { LIGHT_PALETTES } from '@/shared/lib/theme/palettes/light';
21
+
22
+ /** The shipped default, and the fallback for any id the catalog does not know. */
23
+ export const DEFAULT_THEME_ID = 'paper';
24
+
25
+ /**
26
+ * Every id in the catalog, as a literal union. `SettingsState.theme` is typed
27
+ * with it, so a typo — or a theme dropped from the catalog — is a compile error
28
+ * at the writer instead of an unstyled page at runtime.
29
+ */
30
+ export type ThemeId =
31
+ | (typeof CHAMBER_PALETTES)[number]['id']
32
+ | (typeof LIGHT_PALETTES)[number]['id']
33
+ | (typeof DARK_PALETTES)[number]['id'];
34
+
35
+ const ALL_PALETTES = [...CHAMBER_PALETTES, ...LIGHT_PALETTES, ...DARK_PALETTES];
36
+
37
+ const BY_ID: Record<string, ThemePalette | undefined> = Object.fromEntries(
38
+ ALL_PALETTES.map((theme) => [theme.id, theme]),
39
+ );
40
+
41
+ const DEFAULT_PALETTE: ThemePalette = CHAMBER_PALETTES[0];
42
+
43
+ /**
44
+ * Every palette, in appearance-grid order: the chamber's own three first (the
45
+ * ones an existing install is already using), then each ported family with its
46
+ * light and dark entry adjacent.
47
+ *
48
+ * Pairing is by family key rather than by array index, so a re-port that
49
+ * updates one variant of a family cannot silently shift every pair after it.
50
+ */
51
+ export const THEMES: readonly ThemePalette[] = (() => {
52
+ const families = new Map<string, ThemePalette[]>();
53
+ for (const palette of [...LIGHT_PALETTES, ...DARK_PALETTES]) {
54
+ const group = families.get(palette.family);
55
+ if (group) group.push(palette);
56
+ else families.set(palette.family, [palette]);
57
+ }
58
+ return [...CHAMBER_PALETTES, ...[...families.values()].flat()];
59
+ })();
60
+
61
+ /**
62
+ * Resolve a stored theme id to a palette the catalog can actually paint.
63
+ *
64
+ * A settings row can name a theme this build no longer ships — a downgrade, or
65
+ * the `noir` alias that was byte-identical to `one-dark-pro-soft` and is gone.
66
+ * Every reader goes through here, so an unknown id renders the default instead
67
+ * of an unstyled page.
68
+ */
69
+ export function resolveTheme(id: string | undefined | null): ThemePalette {
70
+ return (id ? BY_ID[id] : undefined) ?? DEFAULT_PALETTE;
71
+ }
72
+
73
+ export function isDarkTheme(id: string | undefined | null): boolean {
74
+ return resolveTheme(id).variant === 'dark';
75
+ }
76
+
77
+ /**
78
+ * The catalog's own id for `id`, falling back to the default.
79
+ *
80
+ * The single narrowing point between a runtime string (a stored setting, a
81
+ * button's payload) and `ThemeId`: every palette's `id` is one of the union's
82
+ * members by construction, which is the fact the cast asserts.
83
+ */
84
+ export function resolveThemeId(id: string | undefined | null): ThemeId {
85
+ return resolveTheme(id).id as ThemeId;
86
+ }
87
+
88
+ /** The eight slots a palette card shows, in paint order. */
89
+ export const THEME_SWATCHES: readonly ThemeSwatch[] = [
90
+ { key: 'canvas', label: 'Canvas' },
91
+ { key: 'paper', label: 'Surface' },
92
+ { key: 'ink', label: 'Ink' },
93
+ { key: 'error', label: 'Error' },
94
+ { key: 'success', label: 'Success' },
95
+ { key: 'info', label: 'Info' },
96
+ { key: 'warning', label: 'Warning' },
97
+ { key: 'meta', label: 'Accent' },
98
+ ];
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Hex color arithmetic for the places that cannot use CSS.
8
+ *
9
+ * `color-mix()` covers every blend inside the document, but xterm parses theme
10
+ * colors itself (it accepts `#rgb`, `#rrggbb`, `rgb()`, `hsl()` and named
11
+ * colors — not `color-mix()`, in either the DOM or the WebGL renderer), so the
12
+ * terminal palette has to be computed here and handed over as literal hex.
13
+ *
14
+ * The palette values are ported verbatim from OpenChamber and are therefore not
15
+ * uniform: `#FFF` (jetbrains-light) and 8-digit alpha (`#dbd7caee`,
16
+ * vitesse-dark) both appear, and a blend has to composite the alpha rather than
17
+ * pretend it is opaque.
18
+ */
19
+
20
+ export interface Rgba {
21
+ r: number;
22
+ g: number;
23
+ b: number;
24
+ /** 0–1. */
25
+ a: number;
26
+ }
27
+
28
+ const HEX_SHORT = /^#?([0-9a-f]{3})$/i;
29
+ const HEX_LONG = /^#?([0-9a-f]{6})$/i;
30
+ const HEX_ALPHA = /^#?([0-9a-f]{8})$/i;
31
+
32
+ /** Parse `#rgb`, `#rrggbb` or `#rrggbbaa`. `null` for anything else. */
33
+ export function parseHexColor(value: string): Rgba | null {
34
+ const trimmed = value.trim();
35
+ const short = HEX_SHORT.exec(trimmed);
36
+ if (short) {
37
+ const [r, g, b] = short[1].split('');
38
+ return { r: parseInt(r + r, 16), g: parseInt(g + g, 16), b: parseInt(b + b, 16), a: 1 };
39
+ }
40
+ const long = HEX_LONG.exec(trimmed);
41
+ if (long) {
42
+ const n = parseInt(long[1], 16);
43
+ return { r: (n >> 16) & 0xff, g: (n >> 8) & 0xff, b: n & 0xff, a: 1 };
44
+ }
45
+ const alpha = HEX_ALPHA.exec(trimmed);
46
+ if (alpha) {
47
+ const n = parseInt(alpha[1], 16);
48
+ return { r: (n >>> 24) & 0xff, g: (n >> 16) & 0xff, b: (n >> 8) & 0xff, a: (n & 0xff) / 255 };
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function toHex2(value: number): string {
54
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, '0');
55
+ }
56
+
57
+ export function rgbaToHex({ r, g, b }: Rgba): string {
58
+ return `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`;
59
+ }
60
+
61
+ /**
62
+ * `ratio` of `blend` laid over `base` (0 = base, 1 = blend), compositing
63
+ * `blend`'s own alpha. Falls back to `base` when either value cannot be parsed,
64
+ * so a palette with an unexpected notation still yields a paintable color.
65
+ */
66
+ export function mixHex(base: string, blend: string, ratio: number): string {
67
+ const under = parseHexColor(base);
68
+ const over = parseHexColor(blend);
69
+ if (!under || !over) return base;
70
+ const weight = over.a * ratio;
71
+ return rgbaToHex({
72
+ r: under.r * (1 - weight) + over.r * weight,
73
+ g: under.g * (1 - weight) + over.g * weight,
74
+ b: under.b * (1 - weight) + over.b * weight,
75
+ a: 1,
76
+ });
77
+ }
78
+
79
+ /** WCAG relative luminance, used to order two palette colors light-to-dark. */
80
+ export function relativeLuminance(value: string): number {
81
+ const color = parseHexColor(value);
82
+ if (!color) return 0;
83
+ const channel = (raw: number) => {
84
+ const c = raw / 255;
85
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
86
+ };
87
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
88
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The `[data-theme]` CSS for every palette in the catalog, as one string.
8
+ *
9
+ * Generated rather than hand-written because the palette values already exist
10
+ * in TypeScript — the appearance cards render the same eight colors as chips —
11
+ * so a second hand-maintained copy in `tailwind.css` would be 45 blocks that
12
+ * drift from the cards the moment either side is edited.
13
+ *
14
+ * Two details are load-bearing:
15
+ *
16
+ * - **Selector specificity is `html[data-theme=…]`, not `[data-theme=…]`.**
17
+ * `tailwind.css` carries the default palette on `:root`, which is also
18
+ * (0,1,0); at equal specificity the later declaration wins by document order,
19
+ * so a bare attribute selector injected before the stylesheet would lose and
20
+ * every theme would render as `paper`.
21
+ * - **`color-scheme` is set per variant.** Scrollbars, form controls and the
22
+ * browser's own canvas background are painted by the UA from it; without it a
23
+ * dark theme keeps white scrollbars and a white overscroll area.
24
+ *
25
+ * No DOM here — the SSR shell inlines this, and the client injects the same
26
+ * string only when a served document predates it.
27
+ */
28
+
29
+ import type { ThemePalette } from '@/shared/types/theme';
30
+ import { THEMES } from '@/shared/lib/theme/catalog';
31
+
32
+ /** The `<style>` element's id: the SSR shell writes it, the client checks it. */
33
+ export const THEME_STYLE_ELEMENT_ID = 'omp-theme-palettes';
34
+
35
+ /**
36
+ * A hover shade of a signal color, mixed toward the theme's own ink so the
37
+ * direction follows the palette: darker on a light theme, lighter on a dark one.
38
+ * `--theme-info-hover` / `--theme-warning-hover` used to be fixed hex values in
39
+ * `tailwind.css` and could only be right for one palette.
40
+ */
41
+ const HOVER_MIXES: ReadonlyArray<readonly [string, string]> = [
42
+ ['--theme-info-hover', '--theme-info'],
43
+ ['--theme-warning-hover', '--theme-warning'],
44
+ ];
45
+
46
+ function paletteBlock(theme: ThemePalette): string {
47
+ const lines = [
48
+ ` --theme-paper: ${theme.paper};`,
49
+ ` --theme-canvas: ${theme.canvas};`,
50
+ ` --theme-ink: ${theme.ink};`,
51
+ ` --theme-error: ${theme.error};`,
52
+ ` --theme-success: ${theme.success};`,
53
+ ` --theme-info: ${theme.info};`,
54
+ ` --theme-warning: ${theme.warning};`,
55
+ ` --theme-meta: ${theme.meta};`,
56
+ ];
57
+ for (const [name, source] of HOVER_MIXES) {
58
+ lines.push(` ${name}: color-mix(in srgb, var(${source}) 85%, var(--theme-ink));`);
59
+ }
60
+ lines.push(` color-scheme: ${theme.variant};`);
61
+ return `html[data-theme="${theme.id}"] {\n${lines.join('\n')}\n}`;
62
+ }
63
+
64
+ /** Cached: the palette set is fixed for the life of the process. */
65
+ let cached: string | null = null;
66
+
67
+ export function themeStyleSheet(): string {
68
+ cached ??= `/* Generated from src/shared/lib/theme/palettes — do not edit by hand. */\n${THEMES.map(paletteBlock).join('\n')}\n`;
69
+ return cached;
70
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * The chamber's own palettes, carried over from the hand-written
8
+ * `[data-theme]` blocks in `tailwind.css` with their values unchanged.
9
+ *
10
+ * They stay in the catalog rather than becoming OpenChamber ports so an
11
+ * existing install keeps the exact colors it was using: `paper` is the shipped
12
+ * default and `one-dark-pro-soft` is what every dark-theme user has selected
13
+ * until now.
14
+ */
15
+
16
+ import type { ThemePalette } from '@/shared/types/theme';
17
+
18
+ export const CHAMBER_PALETTES = [
19
+ {
20
+ id: 'paper',
21
+ name: 'E-Ink Paper',
22
+ family: 'paper',
23
+ variant: 'light',
24
+ canvas: '#f4f1ea',
25
+ paper: '#faf8f3',
26
+ ink: '#141310',
27
+ error: '#c8321e',
28
+ success: '#047857',
29
+ info: '#0284c7',
30
+ warning: '#c2410c',
31
+ meta: '#4f46e5',
32
+ },
33
+ {
34
+ id: 'contrast',
35
+ name: 'High Contrast',
36
+ family: 'contrast',
37
+ variant: 'light',
38
+ canvas: '#f4f4f4',
39
+ paper: '#ffffff',
40
+ ink: '#111111',
41
+ error: '#b42318',
42
+ success: '#067647',
43
+ info: '#0369a1',
44
+ warning: '#b54708',
45
+ meta: '#4338ca',
46
+ },
47
+ {
48
+ id: 'one-dark-pro-soft',
49
+ name: 'One Dark Pro Soft',
50
+ family: 'one-dark-pro-soft',
51
+ variant: 'dark',
52
+ canvas: '#21252b',
53
+ paper: '#282c34',
54
+ ink: '#abb2bf',
55
+ error: '#e06c75',
56
+ success: '#34d399',
57
+ info: '#61afef',
58
+ warning: '#fb923c',
59
+ meta: '#818cf8',
60
+ },
61
+ ] as const satisfies readonly ThemePalette[];
@@ -0,0 +1,313 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /**
7
+ * Dark palettes ported verbatim from OpenChamber
8
+ * (packages/ui/src/lib/theme/themes/*.json, MIT, commit 5e6675b). Only the
9
+ * values the chamber consumes are carried: surface.background -> canvas,
10
+ * surface.elevated -> paper, surface.foreground -> ink, status.* -> signal
11
+ * colors, primary.base -> accent. Field order is the swatch order.
12
+ *
13
+ * Generated data — re-port rather than hand-editing a color here.
14
+ */
15
+
16
+ import type { ThemePalette } from '@/shared/types/theme';
17
+
18
+ export const DARK_PALETTES = [
19
+ {
20
+ id: 'aura-dark',
21
+ name: 'Aura',
22
+ family: 'aura',
23
+ variant: 'dark',
24
+ canvas: '#15141B',
25
+ paper: '#201e2b',
26
+ ink: '#EDECEE',
27
+ error: '#FF6767',
28
+ success: '#61FFCA',
29
+ info: '#82E2FF',
30
+ warning: '#FFCA85',
31
+ meta: '#A277FF',
32
+ },
33
+ {
34
+ id: 'ayu-dark',
35
+ name: 'Ayu',
36
+ family: 'ayu',
37
+ variant: 'dark',
38
+ canvas: '#0F1419',
39
+ paper: '#17202a',
40
+ ink: '#D6DAE0',
41
+ error: '#F58572',
42
+ success: '#78D05C',
43
+ info: '#66C6F1',
44
+ warning: '#E4A75C',
45
+ meta: '#3FB7E3',
46
+ },
47
+ {
48
+ id: 'carbonfox-dark',
49
+ name: 'Carbonfox',
50
+ family: 'carbonfox',
51
+ variant: 'dark',
52
+ canvas: '#161616',
53
+ paper: '#222222',
54
+ ink: '#F2F4F8',
55
+ error: '#FF8389',
56
+ success: '#42BE65',
57
+ info: '#78A9FF',
58
+ warning: '#F1C21B',
59
+ meta: '#33B1FF',
60
+ },
61
+ {
62
+ id: 'catppuccin-dark',
63
+ name: 'Catppuccin',
64
+ family: 'catppuccin',
65
+ variant: 'dark',
66
+ canvas: '#1E1E2E',
67
+ paper: '#282841',
68
+ ink: '#CDD6F4',
69
+ error: '#F38BA8',
70
+ success: '#A6D189',
71
+ info: '#89DCEB',
72
+ warning: '#F4B8E4',
73
+ meta: '#7d8fff',
74
+ },
75
+ {
76
+ id: 'dracula-dark',
77
+ name: 'Dracula',
78
+ family: 'dracula',
79
+ variant: 'dark',
80
+ canvas: '#14151F',
81
+ paper: '#161722',
82
+ ink: '#F8F8F2',
83
+ error: '#FF5555',
84
+ success: '#50FA7B',
85
+ info: '#8BE9FD',
86
+ warning: '#FFB86C',
87
+ meta: '#BD93F9',
88
+ },
89
+ {
90
+ id: 'fields-of-the-shire-dark',
91
+ name: 'Fields of the Shire',
92
+ family: 'fields-of-the-shire',
93
+ variant: 'dark',
94
+ canvas: '#1b1815',
95
+ paper: '#2b2622',
96
+ ink: '#ebe0d1',
97
+ error: '#b34d3b',
98
+ success: '#7f905e',
99
+ info: '#667c8a',
100
+ warning: '#c47a3a',
101
+ meta: '#7a8a5a',
102
+ },
103
+ {
104
+ id: 'flexoki-dark',
105
+ name: 'Flexoki',
106
+ family: 'flexoki',
107
+ variant: 'dark',
108
+ canvas: '#171515',
109
+ paper: '#1c1a19',
110
+ ink: '#CECDC3',
111
+ error: '#D14D41',
112
+ success: '#A0AF54',
113
+ info: '#4385BE',
114
+ warning: '#DA702C',
115
+ meta: '#DA702C',
116
+ },
117
+ {
118
+ id: 'gruvbox-dark',
119
+ name: 'Gruvbox',
120
+ family: 'gruvbox',
121
+ variant: 'dark',
122
+ canvas: '#282828',
123
+ paper: '#25292b',
124
+ ink: '#EBDBB2',
125
+ error: '#FB4934',
126
+ success: '#B8BB26',
127
+ info: '#D3869B',
128
+ warning: '#FABD2F',
129
+ meta: '#83A598',
130
+ },
131
+ {
132
+ id: 'jetbrains-dark',
133
+ name: 'JetBrains',
134
+ family: 'jetbrains',
135
+ variant: 'dark',
136
+ canvas: '#1E1F22',
137
+ paper: '#2B2D30',
138
+ ink: '#DFE1E5',
139
+ error: '#FA6675',
140
+ success: '#57965D',
141
+ info: '#3592C4',
142
+ warning: '#F2C55C',
143
+ meta: '#6796f5',
144
+ },
145
+ {
146
+ id: 'kanagawa-dark',
147
+ name: 'Kanagawa',
148
+ family: 'kanagawa',
149
+ variant: 'dark',
150
+ canvas: '#1F1F28',
151
+ paper: '#2A2A37',
152
+ ink: '#DCD7BA',
153
+ error: '#E82424',
154
+ success: '#98BB6C',
155
+ info: '#7E9CD8',
156
+ warning: '#FF9E3B',
157
+ meta: '#7FB4CA',
158
+ },
159
+ {
160
+ id: 'mono-dark',
161
+ name: 'Mono',
162
+ family: 'mono',
163
+ variant: 'dark',
164
+ canvas: '#000000',
165
+ paper: '#141414',
166
+ ink: '#E5E5E5',
167
+ error: '#666666',
168
+ success: '#E5E5E5',
169
+ info: '#A6A6A6',
170
+ warning: '#999999',
171
+ meta: '#FFFFFF',
172
+ },
173
+ {
174
+ id: 'mono-plus-dark',
175
+ name: 'Mono Plus',
176
+ family: 'mono-plus',
177
+ variant: 'dark',
178
+ canvas: '#000000',
179
+ paper: '#141414',
180
+ ink: '#E5E5E5',
181
+ error: '#9e6a6a',
182
+ success: '#6a8e6a',
183
+ info: '#6a7e9e',
184
+ warning: '#9e8a6a',
185
+ meta: '#a2bee8',
186
+ },
187
+ {
188
+ id: 'monokai-dark',
189
+ name: 'Monokai',
190
+ family: 'monokai',
191
+ variant: 'dark',
192
+ canvas: '#23241E',
193
+ paper: '#21221a',
194
+ ink: '#F8F8F2',
195
+ error: '#F92672',
196
+ success: '#A6E22E',
197
+ info: '#66D9EF',
198
+ warning: '#FD971F',
199
+ meta: '#AE81FF',
200
+ },
201
+ {
202
+ id: 'nightowl-dark',
203
+ name: 'Night Owl',
204
+ family: 'night-owl',
205
+ variant: 'dark',
206
+ canvas: '#011627',
207
+ paper: '#001122',
208
+ ink: '#D6DEEB',
209
+ error: '#EF5350',
210
+ success: '#C5E478',
211
+ info: '#82AAFF',
212
+ warning: '#ECC48D',
213
+ meta: '#82AAFF',
214
+ },
215
+ {
216
+ id: 'nord-dark',
217
+ name: 'Nord',
218
+ family: 'nord',
219
+ variant: 'dark',
220
+ canvas: '#1F2430',
221
+ paper: '#1C202A',
222
+ ink: '#E5E9F0',
223
+ error: '#BF616A',
224
+ success: '#A3BE8C',
225
+ info: '#81A1C1',
226
+ warning: '#D08770',
227
+ meta: '#88C0D0',
228
+ },
229
+ {
230
+ id: 'onedarkpro-dark',
231
+ name: 'One Dark Pro',
232
+ family: 'one-dark-pro',
233
+ variant: 'dark',
234
+ canvas: '#1E222A',
235
+ paper: '#232832',
236
+ ink: '#ABB2BF',
237
+ error: '#E06C75',
238
+ success: '#98C379',
239
+ info: '#56B6C2',
240
+ warning: '#E5C07B',
241
+ meta: '#61AFEF',
242
+ },
243
+ {
244
+ id: 'openchamber-dark',
245
+ name: 'OpenChamber',
246
+ family: 'openchamber',
247
+ variant: 'dark',
248
+ canvas: '#120f0e',
249
+ paper: '#181715',
250
+ ink: '#c9c5ba',
251
+ error: '#da5b4a',
252
+ success: '#76ad4f',
253
+ info: '#479fe6',
254
+ warning: '#c67f13',
255
+ meta: '#da7c47',
256
+ },
257
+ {
258
+ id: 'solarized-dark',
259
+ name: 'Solarized',
260
+ family: 'solarized',
261
+ variant: 'dark',
262
+ canvas: '#001e25',
263
+ paper: '#052832',
264
+ ink: '#93A1A1',
265
+ error: '#DC322F',
266
+ success: '#859900',
267
+ info: '#2AA198',
268
+ warning: '#B58900',
269
+ meta: '#278BD2',
270
+ },
271
+ {
272
+ id: 'tokyonight-dark',
273
+ name: 'Tokyonight',
274
+ family: 'tokyonight',
275
+ variant: 'dark',
276
+ canvas: '#0F111A',
277
+ paper: '#131629',
278
+ ink: '#C0CAF5',
279
+ error: '#F7768E',
280
+ success: '#9ECE6A',
281
+ info: '#7DCFFF',
282
+ warning: '#E0AF68',
283
+ meta: '#7AA2F7',
284
+ },
285
+ {
286
+ id: 'vesper-dark',
287
+ name: 'Vesper',
288
+ family: 'vesper',
289
+ variant: 'dark',
290
+ canvas: '#151515',
291
+ paper: '#242121',
292
+ ink: '#e8e5e5',
293
+ error: '#FF8080',
294
+ success: '#99FFE4',
295
+ info: '#FFC799',
296
+ warning: '#FFC799',
297
+ meta: '#FFC799',
298
+ },
299
+ {
300
+ id: 'vitesse-dark-dark',
301
+ name: 'Vitesse Dark',
302
+ family: 'vitesse',
303
+ variant: 'dark',
304
+ canvas: '#121212',
305
+ paper: '#1a1a1a',
306
+ ink: '#dbd7caee',
307
+ error: '#cb7676',
308
+ success: '#4d9375',
309
+ info: '#6394bf',
310
+ warning: '#d4976c',
311
+ meta: '#4d9375',
312
+ },
313
+ ] as const satisfies readonly ThemePalette[];