@iloveagents/foundry-web-ui 0.1.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 (88) hide show
  1. package/AGENTS.md +59 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +41 -0
  5. package/package.json +61 -0
  6. package/src/__tests__/no-spaces-imports.test.ts +59 -0
  7. package/src/__tests__/open-core-guards.test.ts +307 -0
  8. package/src/__tests__/theme-runtime.test.ts +81 -0
  9. package/src/__tests__/tool-fallback.test.tsx +109 -0
  10. package/src/components/ag-ui-runtime-provider.tsx +59 -0
  11. package/src/components/app-brand.tsx +38 -0
  12. package/src/components/assistant-chat.tsx +423 -0
  13. package/src/components/chat-attachments.tsx +68 -0
  14. package/src/components/chat-bubble.tsx +339 -0
  15. package/src/components/chat-header.tsx +44 -0
  16. package/src/components/client-tool-executor.tsx +230 -0
  17. package/src/components/collection-empty-state.tsx +37 -0
  18. package/src/components/collection-filter-bar.tsx +168 -0
  19. package/src/components/collection-search-input.tsx +41 -0
  20. package/src/components/collection-skeleton.tsx +55 -0
  21. package/src/components/collection-sort-menu.tsx +62 -0
  22. package/src/components/collection-surface.tsx +46 -0
  23. package/src/components/collection-toolbar.tsx +33 -0
  24. package/src/components/collection-view-toggle.tsx +61 -0
  25. package/src/components/confirm-dialog.tsx +49 -0
  26. package/src/components/confirmation-card.tsx +32 -0
  27. package/src/components/context-badges.tsx +124 -0
  28. package/src/components/context-bar.tsx +202 -0
  29. package/src/components/form-dialog.tsx +56 -0
  30. package/src/components/global-selection-popover.tsx +135 -0
  31. package/src/components/infinite-scroll-sentinel.tsx +48 -0
  32. package/src/components/json-viewer.tsx +232 -0
  33. package/src/components/loading-indicator.tsx +40 -0
  34. package/src/components/markdown-text.tsx +379 -0
  35. package/src/components/name-dialog.tsx +87 -0
  36. package/src/components/selection-popover.tsx +156 -0
  37. package/src/components/show-document-tool-ui.tsx +90 -0
  38. package/src/components/sidebar.test.tsx +182 -0
  39. package/src/components/sidebar.tsx +1174 -0
  40. package/src/components/surface-card.tsx +45 -0
  41. package/src/components/theme-runtime-provider.tsx +93 -0
  42. package/src/components/theme-toggle.tsx +27 -0
  43. package/src/components/tool-call-card.tsx +126 -0
  44. package/src/components/tool-fallback.tsx +169 -0
  45. package/src/components/tool-panel-layout.tsx +36 -0
  46. package/src/components/tool-panel.tsx +233 -0
  47. package/src/components/tooltip-icon-button.tsx +28 -0
  48. package/src/components/user-menu.tsx +60 -0
  49. package/src/index.ts +170 -0
  50. package/src/lib/__tests__/ag-ui-adapter.test.ts +182 -0
  51. package/src/lib/__tests__/app-store.test.ts +330 -0
  52. package/src/lib/__tests__/attachment-adapter.test.ts +48 -0
  53. package/src/lib/__tests__/chat-bubble-store.test.ts +67 -0
  54. package/src/lib/__tests__/client-tools.test.ts +124 -0
  55. package/src/lib/__tests__/dev-store.test.ts +78 -0
  56. package/src/lib/__tests__/nav-config.test.ts +135 -0
  57. package/src/lib/__tests__/nav-dnd.test.ts +24 -0
  58. package/src/lib/__tests__/nav-store.test.ts +59 -0
  59. package/src/lib/__tests__/sidebar-store.test.ts +144 -0
  60. package/src/lib/__tests__/theme-store.test.ts +83 -0
  61. package/src/lib/__tests__/tool-panel-store.test.ts +61 -0
  62. package/src/lib/__tests__/use-page-context.test.ts +58 -0
  63. package/src/lib/ag-ui-adapter.ts +362 -0
  64. package/src/lib/app-store.ts +294 -0
  65. package/src/lib/attachment-adapter.ts +92 -0
  66. package/src/lib/auth-provider.tsx +144 -0
  67. package/src/lib/chat-bubble-store.ts +46 -0
  68. package/src/lib/client-tools.ts +293 -0
  69. package/src/lib/dev-store.ts +69 -0
  70. package/src/lib/nav-config.ts +292 -0
  71. package/src/lib/nav-dnd.ts +14 -0
  72. package/src/lib/nav-store.ts +76 -0
  73. package/src/lib/sidebar-store.ts +113 -0
  74. package/src/lib/theme-runtime.ts +660 -0
  75. package/src/lib/theme-store.ts +67 -0
  76. package/src/lib/tool-panel-store.ts +121 -0
  77. package/src/lib/use-new-conversation.test.tsx +124 -0
  78. package/src/lib/use-new-conversation.ts +33 -0
  79. package/src/lib/use-page-tools.ts +40 -0
  80. package/src/ui/collapsible.tsx +7 -0
  81. package/src/ui/dialog.tsx +109 -0
  82. package/src/ui/dropdown-menu.tsx +71 -0
  83. package/src/ui/popover.tsx +34 -0
  84. package/src/ui/select.tsx +15 -0
  85. package/src/ui/tooltip.tsx +30 -0
  86. package/src/vite-env.d.ts +1 -0
  87. package/tsconfig.json +16 -0
  88. package/vitest.config.ts +8 -0
@@ -0,0 +1,660 @@
1
+ import type { CSSProperties } from "react";
2
+ import type { ThemeMode } from "./theme-store.ts";
3
+
4
+ export interface ThemeColorSlots {
5
+ background?: string;
6
+ foreground?: string;
7
+ card?: string;
8
+ cardForeground?: string;
9
+ popover?: string;
10
+ popoverForeground?: string;
11
+ primary?: string;
12
+ primaryForeground?: string;
13
+ secondary?: string;
14
+ secondaryForeground?: string;
15
+ muted?: string;
16
+ mutedForeground?: string;
17
+ accent?: string;
18
+ accentForeground?: string;
19
+ destructive?: string;
20
+ destructiveForeground?: string;
21
+ border?: string;
22
+ input?: string;
23
+ ring?: string;
24
+ }
25
+
26
+ export interface ThemeSidebarSlots {
27
+ sidebar?: string;
28
+ sidebarForeground?: string;
29
+ sidebarPrimary?: string;
30
+ sidebarPrimaryForeground?: string;
31
+ sidebarAccent?: string;
32
+ sidebarAccentForeground?: string;
33
+ sidebarSelected?: string;
34
+ sidebarSelectedForeground?: string;
35
+ sidebarBorder?: string;
36
+ sidebarRing?: string;
37
+ }
38
+
39
+ export interface ThemeStatusSlots {
40
+ success?: string;
41
+ successForeground?: string;
42
+ warning?: string;
43
+ warningForeground?: string;
44
+ info?: string;
45
+ infoForeground?: string;
46
+ syntaxKey?: string;
47
+ syntaxString?: string;
48
+ syntaxNumber?: string;
49
+ syntaxBoolean?: string;
50
+ }
51
+
52
+ export interface ThemeChartSlots {
53
+ chart1?: string;
54
+ chart2?: string;
55
+ chart3?: string;
56
+ chart4?: string;
57
+ chart5?: string;
58
+ }
59
+
60
+ export interface ThemeTypographySlots {
61
+ bodyFont?: string;
62
+ headingFont?: string;
63
+ monoFont?: string;
64
+ }
65
+
66
+ export interface ThemeRadiusSlots {
67
+ radius?: string;
68
+ }
69
+
70
+ export interface ThemeAppSlots {
71
+ codeBlock?: string;
72
+ codeBlockForeground?: string;
73
+ workflowDiagramSurface?: string;
74
+ workflowDiagramLine?: string;
75
+ workflowDiagramText?: string;
76
+ workflowDiagramBorder?: string;
77
+ workflowDiagramCompletedStroke?: string;
78
+ workflowDiagramCompletedText?: string;
79
+ workflowDiagramActiveFill?: string;
80
+ workflowDiagramActiveStroke?: string;
81
+ workflowDiagramActiveText?: string;
82
+ workflowDiagramFailedFill?: string;
83
+ workflowDiagramFailedStroke?: string;
84
+ workflowDiagramFailedText?: string;
85
+ workflowDiagramSkippedFill?: string;
86
+ workflowDiagramSkippedStroke?: string;
87
+ workflowDiagramSkippedText?: string;
88
+ workflowDiagramGlow?: string;
89
+ workflowDiagramShell?: string;
90
+ }
91
+
92
+ export interface ThemeBranding {
93
+ appName?: string;
94
+ appTitle?: string;
95
+ logoUrl?: string;
96
+ darkLogoUrl?: string;
97
+ }
98
+
99
+ export interface ThemeDefinition {
100
+ light?: ThemeColorSlots;
101
+ dark?: ThemeColorSlots;
102
+ sidebar?: {
103
+ light?: ThemeSidebarSlots;
104
+ dark?: ThemeSidebarSlots;
105
+ };
106
+ status?: {
107
+ light?: ThemeStatusSlots;
108
+ dark?: ThemeStatusSlots;
109
+ };
110
+ charts?: {
111
+ light?: ThemeChartSlots;
112
+ dark?: ThemeChartSlots;
113
+ };
114
+ typography?: ThemeTypographySlots;
115
+ radius?: ThemeRadiusSlots;
116
+ app?: {
117
+ light?: ThemeAppSlots;
118
+ dark?: ThemeAppSlots;
119
+ };
120
+ branding?: ThemeBranding;
121
+ }
122
+
123
+ export interface ThemeLayer {
124
+ id?: string;
125
+ name?: string;
126
+ basePreset?: string | null;
127
+ definition?: ThemeDefinition | null;
128
+ }
129
+
130
+ export interface ResolvedThemeRuntime {
131
+ mode: "light" | "dark";
132
+ definition: ThemeDefinition;
133
+ branding: ThemeBranding;
134
+ variables: CSSProperties;
135
+ sources: ThemeLayer[];
136
+ }
137
+
138
+ const COLOR_KEYS = [
139
+ "background",
140
+ "foreground",
141
+ "card",
142
+ "cardForeground",
143
+ "popover",
144
+ "popoverForeground",
145
+ "primary",
146
+ "primaryForeground",
147
+ "secondary",
148
+ "secondaryForeground",
149
+ "muted",
150
+ "mutedForeground",
151
+ "accent",
152
+ "accentForeground",
153
+ "destructive",
154
+ "destructiveForeground",
155
+ "border",
156
+ "input",
157
+ "ring",
158
+ ] as const satisfies readonly (keyof ThemeColorSlots)[];
159
+
160
+ const SIDEBAR_KEYS = [
161
+ "sidebar",
162
+ "sidebarForeground",
163
+ "sidebarPrimary",
164
+ "sidebarPrimaryForeground",
165
+ "sidebarAccent",
166
+ "sidebarAccentForeground",
167
+ "sidebarSelected",
168
+ "sidebarSelectedForeground",
169
+ "sidebarBorder",
170
+ "sidebarRing",
171
+ ] as const satisfies readonly (keyof ThemeSidebarSlots)[];
172
+
173
+ const STATUS_KEYS = [
174
+ "success",
175
+ "successForeground",
176
+ "warning",
177
+ "warningForeground",
178
+ "info",
179
+ "infoForeground",
180
+ "syntaxKey",
181
+ "syntaxString",
182
+ "syntaxNumber",
183
+ "syntaxBoolean",
184
+ ] as const satisfies readonly (keyof ThemeStatusSlots)[];
185
+
186
+ const CHART_KEYS = [
187
+ "chart1",
188
+ "chart2",
189
+ "chart3",
190
+ "chart4",
191
+ "chart5",
192
+ ] as const satisfies readonly (keyof ThemeChartSlots)[];
193
+
194
+ const TYPOGRAPHY_KEYS = [
195
+ "bodyFont",
196
+ "headingFont",
197
+ "monoFont",
198
+ ] as const satisfies readonly (keyof ThemeTypographySlots)[];
199
+
200
+ const RADIUS_KEYS = ["radius"] as const satisfies readonly (keyof ThemeRadiusSlots)[];
201
+
202
+ const APP_KEYS = [
203
+ "codeBlock",
204
+ "codeBlockForeground",
205
+ "workflowDiagramSurface",
206
+ "workflowDiagramLine",
207
+ "workflowDiagramText",
208
+ "workflowDiagramBorder",
209
+ "workflowDiagramCompletedStroke",
210
+ "workflowDiagramCompletedText",
211
+ "workflowDiagramActiveFill",
212
+ "workflowDiagramActiveStroke",
213
+ "workflowDiagramActiveText",
214
+ "workflowDiagramFailedFill",
215
+ "workflowDiagramFailedStroke",
216
+ "workflowDiagramFailedText",
217
+ "workflowDiagramSkippedFill",
218
+ "workflowDiagramSkippedStroke",
219
+ "workflowDiagramSkippedText",
220
+ "workflowDiagramGlow",
221
+ "workflowDiagramShell",
222
+ ] as const satisfies readonly (keyof ThemeAppSlots)[];
223
+
224
+ const BRANDING_KEYS = [
225
+ "appName",
226
+ "appTitle",
227
+ "logoUrl",
228
+ "darkLogoUrl",
229
+ ] as const satisfies readonly (keyof ThemeBranding)[];
230
+
231
+ function cleanObject<T extends object>(
232
+ value: unknown,
233
+ keys: readonly (keyof T)[],
234
+ ): Partial<T> | undefined {
235
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
236
+ const next: Partial<T> = {};
237
+ for (const key of keys) {
238
+ const raw = (value as Record<string, unknown>)[key as string];
239
+ if (typeof raw === "string" && raw.trim()) {
240
+ next[key] = raw.trim() as T[keyof T];
241
+ }
242
+ }
243
+ return Object.keys(next).length > 0 ? next : undefined;
244
+ }
245
+
246
+ function sanitizeThemeDefinition(value: ThemeDefinition | null | undefined): ThemeDefinition {
247
+ if (!value || typeof value !== "object") return {};
248
+ return {
249
+ light: cleanObject<ThemeColorSlots>(value.light, COLOR_KEYS),
250
+ dark: cleanObject<ThemeColorSlots>(value.dark, COLOR_KEYS),
251
+ sidebar: {
252
+ light: cleanObject<ThemeSidebarSlots>(value.sidebar?.light, SIDEBAR_KEYS),
253
+ dark: cleanObject<ThemeSidebarSlots>(value.sidebar?.dark, SIDEBAR_KEYS),
254
+ },
255
+ status: {
256
+ light: cleanObject<ThemeStatusSlots>(value.status?.light, STATUS_KEYS),
257
+ dark: cleanObject<ThemeStatusSlots>(value.status?.dark, STATUS_KEYS),
258
+ },
259
+ charts: {
260
+ light: cleanObject<ThemeChartSlots>(value.charts?.light, CHART_KEYS),
261
+ dark: cleanObject<ThemeChartSlots>(value.charts?.dark, CHART_KEYS),
262
+ },
263
+ typography: cleanObject<ThemeTypographySlots>(value.typography, TYPOGRAPHY_KEYS),
264
+ radius: cleanObject<ThemeRadiusSlots>(value.radius, RADIUS_KEYS),
265
+ app: {
266
+ light: cleanObject<ThemeAppSlots>(value.app?.light, APP_KEYS),
267
+ dark: cleanObject<ThemeAppSlots>(value.app?.dark, APP_KEYS),
268
+ },
269
+ branding: cleanObject<ThemeBranding>(value.branding, BRANDING_KEYS),
270
+ };
271
+ }
272
+
273
+ function mergePartial<T extends object>(
274
+ base: T | undefined,
275
+ patch: Partial<T> | undefined,
276
+ ): T | undefined {
277
+ if (!base && !patch) return undefined;
278
+ return { ...(base ?? {}), ...(patch ?? {}) } as T;
279
+ }
280
+
281
+ export function mergeThemeDefinitions(...definitions: (ThemeDefinition | null | undefined)[]): ThemeDefinition {
282
+ let current: ThemeDefinition = {};
283
+ for (const raw of definitions) {
284
+ const next = sanitizeThemeDefinition(raw);
285
+ current = {
286
+ light: mergePartial(current.light, next.light),
287
+ dark: mergePartial(current.dark, next.dark),
288
+ sidebar: {
289
+ light: mergePartial(current.sidebar?.light, next.sidebar?.light),
290
+ dark: mergePartial(current.sidebar?.dark, next.sidebar?.dark),
291
+ },
292
+ status: {
293
+ light: mergePartial(current.status?.light, next.status?.light),
294
+ dark: mergePartial(current.status?.dark, next.status?.dark),
295
+ },
296
+ charts: {
297
+ light: mergePartial(current.charts?.light, next.charts?.light),
298
+ dark: mergePartial(current.charts?.dark, next.charts?.dark),
299
+ },
300
+ typography: mergePartial(current.typography, next.typography),
301
+ radius: mergePartial(current.radius, next.radius),
302
+ app: {
303
+ light: mergePartial(current.app?.light, next.app?.light),
304
+ dark: mergePartial(current.app?.dark, next.app?.dark),
305
+ },
306
+ branding: mergePartial(current.branding, next.branding),
307
+ };
308
+ }
309
+ return current;
310
+ }
311
+
312
+ function toCssVars(
313
+ colors: ThemeColorSlots,
314
+ sidebar: ThemeSidebarSlots,
315
+ status: ThemeStatusSlots,
316
+ charts: ThemeChartSlots,
317
+ typography: ThemeTypographySlots,
318
+ radius: ThemeRadiusSlots,
319
+ app: ThemeAppSlots,
320
+ ): CSSProperties {
321
+ return {
322
+ ["--background" as string]: colors.background,
323
+ ["--foreground" as string]: colors.foreground,
324
+ ["--card" as string]: colors.card,
325
+ ["--card-foreground" as string]: colors.cardForeground,
326
+ ["--popover" as string]: colors.popover,
327
+ ["--popover-foreground" as string]: colors.popoverForeground,
328
+ ["--primary" as string]: colors.primary,
329
+ ["--primary-foreground" as string]: colors.primaryForeground,
330
+ ["--secondary" as string]: colors.secondary,
331
+ ["--secondary-foreground" as string]: colors.secondaryForeground,
332
+ ["--muted" as string]: colors.muted,
333
+ ["--muted-foreground" as string]: colors.mutedForeground,
334
+ ["--accent" as string]: colors.accent,
335
+ ["--accent-foreground" as string]: colors.accentForeground,
336
+ ["--destructive" as string]: colors.destructive,
337
+ ["--destructive-foreground" as string]: colors.destructiveForeground,
338
+ ["--border" as string]: colors.border,
339
+ ["--input" as string]: colors.input,
340
+ ["--ring" as string]: colors.ring,
341
+ ["--sidebar" as string]: sidebar.sidebar,
342
+ ["--sidebar-foreground" as string]: sidebar.sidebarForeground,
343
+ ["--sidebar-primary" as string]: sidebar.sidebarPrimary,
344
+ ["--sidebar-primary-foreground" as string]: sidebar.sidebarPrimaryForeground,
345
+ ["--sidebar-accent" as string]: sidebar.sidebarAccent,
346
+ ["--sidebar-accent-foreground" as string]: sidebar.sidebarAccentForeground,
347
+ ["--sidebar-selected" as string]: sidebar.sidebarSelected,
348
+ ["--sidebar-selected-foreground" as string]: sidebar.sidebarSelectedForeground,
349
+ ["--sidebar-border" as string]: sidebar.sidebarBorder,
350
+ ["--sidebar-ring" as string]: sidebar.sidebarRing,
351
+ ["--success" as string]: status.success,
352
+ ["--success-foreground" as string]: status.successForeground,
353
+ ["--warning" as string]: status.warning,
354
+ ["--warning-foreground" as string]: status.warningForeground,
355
+ ["--info" as string]: status.info,
356
+ ["--info-foreground" as string]: status.infoForeground,
357
+ ["--syntax-key" as string]: status.syntaxKey,
358
+ ["--syntax-string" as string]: status.syntaxString,
359
+ ["--syntax-number" as string]: status.syntaxNumber,
360
+ ["--syntax-boolean" as string]: status.syntaxBoolean,
361
+ ["--chart-1" as string]: charts.chart1,
362
+ ["--chart-2" as string]: charts.chart2,
363
+ ["--chart-3" as string]: charts.chart3,
364
+ ["--chart-4" as string]: charts.chart4,
365
+ ["--chart-5" as string]: charts.chart5,
366
+ ["--font-body" as string]: typography.bodyFont,
367
+ ["--font-heading" as string]: typography.headingFont,
368
+ ["--font-mono" as string]: typography.monoFont,
369
+ ["--radius" as string]: radius.radius,
370
+ ["--code-block" as string]: app.codeBlock,
371
+ ["--code-block-foreground" as string]: app.codeBlockForeground,
372
+ ["--workflow-diagram-surface" as string]: app.workflowDiagramSurface,
373
+ ["--workflow-diagram-line" as string]: app.workflowDiagramLine,
374
+ ["--workflow-diagram-text" as string]: app.workflowDiagramText,
375
+ ["--workflow-diagram-border" as string]: app.workflowDiagramBorder,
376
+ ["--workflow-diagram-completed-stroke" as string]: app.workflowDiagramCompletedStroke,
377
+ ["--workflow-diagram-completed-text" as string]: app.workflowDiagramCompletedText,
378
+ ["--workflow-diagram-active-fill" as string]: app.workflowDiagramActiveFill,
379
+ ["--workflow-diagram-active-stroke" as string]: app.workflowDiagramActiveStroke,
380
+ ["--workflow-diagram-active-text" as string]: app.workflowDiagramActiveText,
381
+ ["--workflow-diagram-failed-fill" as string]: app.workflowDiagramFailedFill,
382
+ ["--workflow-diagram-failed-stroke" as string]: app.workflowDiagramFailedStroke,
383
+ ["--workflow-diagram-failed-text" as string]: app.workflowDiagramFailedText,
384
+ ["--workflow-diagram-skipped-fill" as string]: app.workflowDiagramSkippedFill,
385
+ ["--workflow-diagram-skipped-stroke" as string]: app.workflowDiagramSkippedStroke,
386
+ ["--workflow-diagram-skipped-text" as string]: app.workflowDiagramSkippedText,
387
+ ["--workflow-diagram-glow" as string]: app.workflowDiagramGlow,
388
+ ["--workflow-diagram-shell" as string]: app.workflowDiagramShell,
389
+ };
390
+ }
391
+
392
+ export function getEffectiveThemeMode(mode: ThemeMode): "light" | "dark" {
393
+ if (mode === "light" || mode === "dark") return mode;
394
+ if (typeof window !== "undefined") {
395
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
396
+ }
397
+ return "light";
398
+ }
399
+
400
+ function defaultSidebar(colors: ThemeColorSlots): ThemeSidebarSlots {
401
+ return {
402
+ sidebar: colors.secondary!,
403
+ sidebarForeground: colors.foreground!,
404
+ sidebarPrimary: colors.primary!,
405
+ sidebarPrimaryForeground: colors.primaryForeground!,
406
+ sidebarAccent: colors.accent!,
407
+ sidebarAccentForeground: colors.accentForeground!,
408
+ sidebarSelected: colors.muted!,
409
+ sidebarSelectedForeground: colors.foreground!,
410
+ sidebarBorder: colors.border!,
411
+ sidebarRing: colors.ring!,
412
+ };
413
+ }
414
+
415
+ function defaultStatus(mode: "light" | "dark", colors: ThemeColorSlots): ThemeStatusSlots {
416
+ if (mode === "dark") {
417
+ return {
418
+ success: "oklch(0.72 0.14 155)",
419
+ successForeground: "oklch(0.16 0.01 155)",
420
+ warning: "oklch(0.72 0.13 84)",
421
+ warningForeground: "oklch(0.2 0.03 84)",
422
+ info: colors.primary!,
423
+ infoForeground: colors.primaryForeground!,
424
+ syntaxKey: "oklch(0.8 0.12 286)",
425
+ syntaxString: "oklch(0.82 0.12 155)",
426
+ syntaxNumber: "oklch(0.78 0.14 245)",
427
+ syntaxBoolean: "oklch(0.79 0.15 84)",
428
+ };
429
+ }
430
+ return {
431
+ success: "oklch(0.58 0.16 155)",
432
+ successForeground: "oklch(1 0 0)",
433
+ warning: "oklch(0.72 0.16 84)",
434
+ warningForeground: "oklch(0.3 0.05 84)",
435
+ info: colors.primary!,
436
+ infoForeground: colors.primaryForeground!,
437
+ syntaxKey: "oklch(0.48 0.17 286)",
438
+ syntaxString: "oklch(0.5 0.15 155)",
439
+ syntaxNumber: "oklch(0.52 0.16 245)",
440
+ syntaxBoolean: "oklch(0.58 0.15 84)",
441
+ };
442
+ }
443
+
444
+ function defaultCharts(mode: "light" | "dark"): ThemeChartSlots {
445
+ if (mode === "dark") {
446
+ return {
447
+ chart1: "oklch(0.72 0.17 286)",
448
+ chart2: "oklch(0.74 0.13 240)",
449
+ chart3: "oklch(0.76 0.12 195)",
450
+ chart4: "oklch(0.79 0.15 84)",
451
+ chart5: "oklch(0.74 0.14 20)",
452
+ };
453
+ }
454
+ return {
455
+ chart1: "oklch(0.55 0.19 286)",
456
+ chart2: "oklch(0.56 0.15 240)",
457
+ chart3: "oklch(0.58 0.13 195)",
458
+ chart4: "oklch(0.7 0.16 84)",
459
+ chart5: "oklch(0.63 0.17 20)",
460
+ };
461
+ }
462
+
463
+ function defaultApp(mode: "light" | "dark", colors: ThemeColorSlots): ThemeAppSlots {
464
+ if (mode === "dark") {
465
+ return {
466
+ codeBlock: "oklch(0.16 0.01 286)",
467
+ codeBlockForeground: "oklch(0.92 0.01 286)",
468
+ workflowDiagramSurface: "color-mix(in srgb, var(--background) 84%, var(--primary) 16%)",
469
+ workflowDiagramLine: "color-mix(in srgb, var(--primary) 90%, white 10%)",
470
+ workflowDiagramText: "oklch(0.95 0.01 286)",
471
+ workflowDiagramBorder: "color-mix(in srgb, var(--primary) 34%, var(--border) 66%)",
472
+ workflowDiagramCompletedStroke:
473
+ "color-mix(in srgb, var(--success) 82%, var(--primary) 18%)",
474
+ workflowDiagramCompletedText:
475
+ "color-mix(in srgb, var(--foreground) 76%, var(--success) 24%)",
476
+ workflowDiagramActiveFill:
477
+ "color-mix(in srgb, var(--background) 76%, var(--primary) 24%)",
478
+ workflowDiagramActiveStroke: "color-mix(in srgb, var(--primary) 92%, white 8%)",
479
+ workflowDiagramActiveText: "oklch(0.95 0.01 286)",
480
+ workflowDiagramFailedFill: "color-mix(in srgb, var(--destructive) 22%, var(--background) 78%)",
481
+ workflowDiagramFailedStroke: colors.destructive!,
482
+ workflowDiagramFailedText: "color-mix(in srgb, var(--destructive) 58%, white 42%)",
483
+ workflowDiagramSkippedFill: "color-mix(in srgb, var(--muted) 80%, var(--background) 20%)",
484
+ workflowDiagramSkippedStroke: "color-mix(in srgb, var(--muted-foreground) 36%, transparent)",
485
+ workflowDiagramSkippedText: colors.mutedForeground!,
486
+ workflowDiagramGlow: "rgba(182, 148, 255, 0.18)",
487
+ workflowDiagramShell: "rgba(182, 148, 255, 0.06)",
488
+ };
489
+ }
490
+ return {
491
+ codeBlock: "oklch(0.2 0 0)",
492
+ codeBlockForeground: "oklch(0.93 0 0)",
493
+ workflowDiagramSurface: "color-mix(in srgb, var(--background) 95%, var(--primary) 5%)",
494
+ workflowDiagramLine: "color-mix(in srgb, var(--primary) 84%, var(--foreground) 16%)",
495
+ workflowDiagramText: "color-mix(in srgb, var(--foreground) 88%, var(--primary) 12%)",
496
+ workflowDiagramBorder: "color-mix(in srgb, var(--primary) 18%, var(--border) 82%)",
497
+ workflowDiagramCompletedStroke:
498
+ "color-mix(in srgb, var(--success) 78%, var(--primary) 22%)",
499
+ workflowDiagramCompletedText:
500
+ "color-mix(in srgb, var(--foreground) 82%, var(--success) 18%)",
501
+ workflowDiagramActiveFill:
502
+ "color-mix(in srgb, var(--background) 90%, var(--primary) 10%)",
503
+ workflowDiagramActiveStroke: colors.primary!,
504
+ workflowDiagramActiveText: "color-mix(in srgb, var(--foreground) 86%, var(--primary) 14%)",
505
+ workflowDiagramFailedFill: "color-mix(in srgb, var(--destructive) 8%, var(--background) 92%)",
506
+ workflowDiagramFailedStroke: colors.destructive!,
507
+ workflowDiagramFailedText:
508
+ "color-mix(in srgb, var(--destructive) 62%, var(--foreground) 38%)",
509
+ workflowDiagramSkippedFill: "color-mix(in srgb, var(--muted) 65%, var(--background) 35%)",
510
+ workflowDiagramSkippedStroke: "color-mix(in srgb, var(--muted-foreground) 22%, transparent)",
511
+ workflowDiagramSkippedText: colors.mutedForeground!,
512
+ workflowDiagramGlow: "rgba(124, 58, 237, 0.18)",
513
+ workflowDiagramShell: "rgba(124, 58, 237, 0.04)",
514
+ };
515
+ }
516
+
517
+ export const FOUNDRY_VIOLET_THEME: ThemeDefinition = {
518
+ light: {
519
+ background: "oklch(100% 0 0)",
520
+ foreground: "oklch(21% 0.006 290)",
521
+ card: "oklch(100% 0 0)",
522
+ cardForeground: "oklch(21% 0.006 290)",
523
+ popover: "oklch(100% 0 0)",
524
+ popoverForeground: "oklch(21% 0.006 290)",
525
+ primary: "oklch(43% 0.24 286)",
526
+ primaryForeground: "oklch(100% 0 0)",
527
+ secondary: "oklch(96% 0.015 290)",
528
+ secondaryForeground: "oklch(35% 0.15 290)",
529
+ muted: "oklch(98% 0 0)",
530
+ mutedForeground: "oklch(45% 0.01 290)",
531
+ accent: "oklch(96% 0.02 290)",
532
+ accentForeground: "oklch(30% 0.1 290)",
533
+ destructive: "oklch(55% 0.18 15)",
534
+ destructiveForeground: "oklch(100% 0 0)",
535
+ border: "oklch(92% 0.008 290)",
536
+ input: "oklch(92% 0.008 290)",
537
+ ring: "oklch(43% 0.24 286)",
538
+ },
539
+ sidebar: {
540
+ light: {
541
+ sidebar: "oklch(99.2% 0 0)",
542
+ sidebarForeground: "oklch(24% 0.004 286)",
543
+ sidebarPrimary: "oklch(43% 0.24 286)",
544
+ sidebarPrimaryForeground: "oklch(100% 0 0)",
545
+ sidebarAccent: "color-mix(in srgb, var(--primary) 4%, var(--background) 96%)",
546
+ sidebarAccentForeground: "color-mix(in srgb, var(--foreground) 92%, var(--primary) 8%)",
547
+ sidebarSelected: "var(--muted)",
548
+ sidebarSelectedForeground: "var(--foreground)",
549
+ sidebarBorder: "color-mix(in srgb, var(--border) 96%, var(--foreground) 4%)",
550
+ sidebarRing: "oklch(43% 0.24 286)",
551
+ },
552
+ dark: {
553
+ sidebar: "oklch(0.18 0.004 286)",
554
+ sidebarForeground: "oklch(0.94 0.004 286)",
555
+ sidebarPrimary: "oklch(0.72 0.18 286)",
556
+ sidebarPrimaryForeground: "oklch(0.98 0 0)",
557
+ sidebarAccent: "color-mix(in srgb, var(--primary) 8%, var(--background) 92%)",
558
+ sidebarAccentForeground: "color-mix(in srgb, var(--foreground) 90%, white 10%)",
559
+ sidebarSelected: "color-mix(in srgb, var(--muted) 78%, var(--background) 22%)",
560
+ sidebarSelectedForeground: "var(--foreground)",
561
+ sidebarBorder: "color-mix(in srgb, var(--border) 90%, white 10%)",
562
+ sidebarRing: "oklch(0.72 0.18 286)",
563
+ },
564
+ },
565
+ dark: {
566
+ background: "oklch(0.145 0.008 286)",
567
+ foreground: "oklch(0.95 0.01 286)",
568
+ card: "oklch(0.19 0.012 286)",
569
+ cardForeground: "oklch(0.95 0.01 286)",
570
+ popover: "oklch(0.19 0.012 286)",
571
+ popoverForeground: "oklch(0.95 0.01 286)",
572
+ primary: "oklch(0.72 0.18 286)",
573
+ primaryForeground: "oklch(0.98 0 0)",
574
+ secondary: "oklch(0.24 0.02 286)",
575
+ secondaryForeground: "oklch(0.92 0.01 286)",
576
+ muted: "oklch(0.22 0.015 286)",
577
+ mutedForeground: "oklch(0.65 0.02 286)",
578
+ accent: "oklch(0.26 0.025 286)",
579
+ accentForeground: "oklch(0.92 0.01 286)",
580
+ destructive: "oklch(0.704 0.191 22.216)",
581
+ destructiveForeground: "oklch(0.985 0 0)",
582
+ border: "oklch(1 0.01 286 / 12%)",
583
+ input: "oklch(1 0.01 286 / 15%)",
584
+ ring: "oklch(0.72 0.18 286)",
585
+ },
586
+ typography: {
587
+ bodyFont:
588
+ "\"Segoe UI\", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
589
+ headingFont:
590
+ "\"Segoe UI\", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
591
+ monoFont:
592
+ "\"SFMono-Regular\", \"Cascadia Code\", \"JetBrains Mono\", ui-monospace, monospace",
593
+ },
594
+ radius: {
595
+ radius: "0.875rem",
596
+ },
597
+ branding: {
598
+ appName: "LastSpace.ai",
599
+ appTitle: "LastSpace.ai",
600
+ },
601
+ };
602
+
603
+ export function getThemePreset(name?: string | null): ThemeDefinition {
604
+ if (!name || name === "foundry-violet") return FOUNDRY_VIOLET_THEME;
605
+ return FOUNDRY_VIOLET_THEME;
606
+ }
607
+
608
+ export function resolveThemeRuntime(
609
+ layers: ThemeLayer[],
610
+ mode: ThemeMode,
611
+ ): ResolvedThemeRuntime {
612
+ const effectiveMode = getEffectiveThemeMode(mode);
613
+ const basePreset =
614
+ layers.find((layer) => layer.basePreset && layer.basePreset.trim())?.basePreset ??
615
+ "foundry-violet";
616
+ const definitions = [
617
+ getThemePreset(basePreset),
618
+ ...layers.map((layer) => layer.definition),
619
+ ];
620
+ const definition = mergeThemeDefinitions(...definitions);
621
+ const colors = {
622
+ ...(effectiveMode === "dark" ? definition.dark : definition.light),
623
+ } as ThemeColorSlots;
624
+ const sidebar = {
625
+ ...defaultSidebar(colors),
626
+ ...(effectiveMode === "dark" ? definition.sidebar?.dark : definition.sidebar?.light),
627
+ } as ThemeSidebarSlots;
628
+ const status = {
629
+ ...defaultStatus(effectiveMode, colors),
630
+ ...(effectiveMode === "dark" ? definition.status?.dark : definition.status?.light),
631
+ } as ThemeStatusSlots;
632
+ const charts = {
633
+ ...defaultCharts(effectiveMode),
634
+ ...(effectiveMode === "dark" ? definition.charts?.dark : definition.charts?.light),
635
+ } as ThemeChartSlots;
636
+ const typography = {
637
+ ...FOUNDRY_VIOLET_THEME.typography,
638
+ ...definition.typography,
639
+ } as ThemeTypographySlots;
640
+ const radius = {
641
+ ...FOUNDRY_VIOLET_THEME.radius,
642
+ ...definition.radius,
643
+ } as ThemeRadiusSlots;
644
+ const app = {
645
+ ...defaultApp(effectiveMode, colors),
646
+ ...(effectiveMode === "dark" ? definition.app?.dark : definition.app?.light),
647
+ } as ThemeAppSlots;
648
+ const branding = {
649
+ ...FOUNDRY_VIOLET_THEME.branding,
650
+ ...definition.branding,
651
+ } as ThemeBranding;
652
+
653
+ return {
654
+ mode: effectiveMode,
655
+ definition,
656
+ branding,
657
+ variables: toCssVars(colors, sidebar, status, charts, typography, radius, app),
658
+ sources: layers,
659
+ };
660
+ }