@nastechai/agent 0.16.0 → 0.17.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 (98) hide show
  1. package/eslint.config.js +23 -0
  2. package/index.html +24 -0
  3. package/package.json +54 -26
  4. package/package.json.bak +89 -0
  5. package/package.json.pub +88 -0
  6. package/src/App.tsx +1173 -0
  7. package/src/components/AuthWidget.tsx +150 -0
  8. package/src/components/AutoField.tsx +206 -0
  9. package/src/components/Backdrop.tsx +93 -0
  10. package/src/components/ChatSidebar.tsx +394 -0
  11. package/src/components/DeleteConfirmDialog.tsx +40 -0
  12. package/src/components/LanguageSwitcher.tsx +186 -0
  13. package/src/components/Markdown.tsx +383 -0
  14. package/src/components/ModelInfoCard.tsx +112 -0
  15. package/src/components/ModelPickerDialog.tsx +470 -0
  16. package/src/components/OAuthLoginModal.tsx +374 -0
  17. package/src/components/OAuthProvidersCard.tsx +287 -0
  18. package/src/components/PlatformsCard.tsx +97 -0
  19. package/src/components/ScheduleBuilder.tsx +273 -0
  20. package/src/components/SidebarFooter.tsx +42 -0
  21. package/src/components/SidebarStatusStrip.tsx +72 -0
  22. package/src/components/SlashPopover.tsx +171 -0
  23. package/src/components/ThemeSwitcher.tsx +243 -0
  24. package/src/components/ToolCall.tsx +228 -0
  25. package/src/components/ToolsetConfigDrawer.tsx +448 -0
  26. package/src/contexts/PageHeaderProvider.tsx +139 -0
  27. package/src/contexts/SystemActions.tsx +120 -0
  28. package/src/contexts/page-header-context.ts +12 -0
  29. package/src/contexts/system-actions-context.ts +18 -0
  30. package/src/contexts/usePageHeader.ts +10 -0
  31. package/src/contexts/useSystemActions.ts +15 -0
  32. package/src/hooks/useModalBehavior.ts +44 -0
  33. package/src/hooks/useSidebarStatus.ts +27 -0
  34. package/src/i18n/af.ts +702 -0
  35. package/src/i18n/context.tsx +123 -0
  36. package/src/i18n/de.ts +701 -0
  37. package/src/i18n/en.ts +708 -0
  38. package/src/i18n/es.ts +701 -0
  39. package/src/i18n/fr.ts +701 -0
  40. package/src/i18n/ga.ts +702 -0
  41. package/src/i18n/hu.ts +702 -0
  42. package/src/i18n/index.ts +2 -0
  43. package/src/i18n/it.ts +701 -0
  44. package/src/i18n/ja.ts +702 -0
  45. package/src/i18n/ko.ts +702 -0
  46. package/src/i18n/pt.ts +702 -0
  47. package/src/i18n/ru.ts +702 -0
  48. package/src/i18n/tr.ts +702 -0
  49. package/src/i18n/types.ts +710 -0
  50. package/src/i18n/uk.ts +702 -0
  51. package/src/i18n/zh-hant.ts +702 -0
  52. package/src/i18n/zh.ts +698 -0
  53. package/src/index.css +274 -0
  54. package/src/lib/api.ts +1585 -0
  55. package/src/lib/dashboard-flags.ts +15 -0
  56. package/src/lib/format.ts +9 -0
  57. package/src/lib/fuzzy.ts +192 -0
  58. package/src/lib/gatewayClient.ts +253 -0
  59. package/src/lib/nested.ts +23 -0
  60. package/src/lib/resolve-page-title.ts +41 -0
  61. package/src/lib/schedule.ts +382 -0
  62. package/src/lib/slashExec.ts +163 -0
  63. package/src/lib/utils.ts +35 -0
  64. package/src/main.tsx +25 -0
  65. package/src/pages/AnalyticsPage.tsx +601 -0
  66. package/src/pages/ChannelsPage.tsx +772 -0
  67. package/src/pages/ChatPage.tsx +889 -0
  68. package/src/pages/ConfigPage.tsx +660 -0
  69. package/src/pages/CronPage.tsx +524 -0
  70. package/src/pages/DocsPage.tsx +69 -0
  71. package/src/pages/EnvPage.tsx +918 -0
  72. package/src/pages/LogsPage.tsx +246 -0
  73. package/src/pages/McpPage.tsx +757 -0
  74. package/src/pages/ModelsPage.tsx +994 -0
  75. package/src/pages/PairingPage.tsx +276 -0
  76. package/src/pages/PluginsPage.tsx +580 -0
  77. package/src/pages/ProfilesPage.tsx +559 -0
  78. package/src/pages/SessionsPage.tsx +936 -0
  79. package/src/pages/SkillsPage.tsx +557 -0
  80. package/src/pages/SystemPage.tsx +1259 -0
  81. package/src/pages/WebhooksPage.tsx +483 -0
  82. package/src/plugins/PluginPage.tsx +64 -0
  83. package/src/plugins/index.ts +6 -0
  84. package/src/plugins/registry.ts +151 -0
  85. package/src/plugins/sdk.d.ts +160 -0
  86. package/src/plugins/slots.ts +199 -0
  87. package/src/plugins/types.ts +37 -0
  88. package/src/plugins/usePlugins.ts +133 -0
  89. package/src/themes/context.tsx +443 -0
  90. package/src/themes/fonts.ts +160 -0
  91. package/src/themes/index.ts +3 -0
  92. package/src/themes/presets.ts +477 -0
  93. package/src/themes/types.ts +187 -0
  94. package/tsconfig.app.json +34 -0
  95. package/tsconfig.json +7 -0
  96. package/tsconfig.node.json +26 -0
  97. package/vite.config.ts +124 -0
  98. package/vite.config.ts.timestamp-1780999102396-af6b77b30ebd8.mjs +105 -0
@@ -0,0 +1,477 @@
1
+ import type { DashboardTheme, ThemeTypography, ThemeLayout } from "./types";
2
+
3
+ /**
4
+ * Built-in dashboard themes.
5
+ *
6
+ * Each theme defines its own palette, typography, and layout so switching
7
+ * themes produces visible changes beyond just color — fonts, density, and
8
+ * corner-radius all shift to match the theme's personality.
9
+ *
10
+ * Theme names must stay in sync with the backend's
11
+ * `_BUILTIN_DASHBOARD_THEMES` list in `nastech_cli/web_server.py`.
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Shared typography / layout presets
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** Default system stack — neutral, safe fallback for every platform. */
19
+ const SYSTEM_SANS =
20
+ 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
21
+ const SYSTEM_MONO =
22
+ 'ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace';
23
+
24
+ const DEFAULT_TYPOGRAPHY: ThemeTypography = {
25
+ fontSans: SYSTEM_SANS,
26
+ fontMono: SYSTEM_MONO,
27
+ baseSize: "15px",
28
+ lineHeight: "1.55",
29
+ letterSpacing: "0",
30
+ };
31
+
32
+ const DEFAULT_LAYOUT: ThemeLayout = {
33
+ radius: "0.5rem",
34
+ density: "comfortable",
35
+ };
36
+
37
+ /** Rounded layout — softer, more playful corners everywhere. */
38
+ export const ROUNDED_LAYOUT: ThemeLayout = {
39
+ radius: "1.25rem",
40
+ density: "comfortable",
41
+ };
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Themes
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export const defaultTheme: DashboardTheme = {
48
+ name: "default",
49
+ label: "NasTech Teal",
50
+ description: "Classic dark teal — the canonical NasTech look",
51
+ palette: {
52
+ background: { hex: "#041c1c", alpha: 1 },
53
+ midground: { hex: "#ffe6cb", alpha: 1 },
54
+ foreground: { hex: "#ffffff", alpha: 0 },
55
+ warmGlow: "rgba(255, 189, 56, 0.35)",
56
+ noiseOpacity: 1,
57
+ },
58
+ typography: DEFAULT_TYPOGRAPHY,
59
+ layout: DEFAULT_LAYOUT,
60
+ };
61
+
62
+ export const midnightTheme: DashboardTheme = {
63
+ name: "midnight",
64
+ label: "Midnight",
65
+ description: "Deep blue-violet with cool accents",
66
+ palette: {
67
+ background: { hex: "#0a0a1f", alpha: 1 },
68
+ midground: { hex: "#d4c8ff", alpha: 1 },
69
+ foreground: { hex: "#ffffff", alpha: 0 },
70
+ warmGlow: "rgba(167, 139, 250, 0.32)",
71
+ noiseOpacity: 0.8,
72
+ },
73
+ typography: {
74
+ ...DEFAULT_TYPOGRAPHY,
75
+ fontSans: `"Inter", ${SYSTEM_SANS}`,
76
+ fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`,
77
+ fontUrl:
78
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap",
79
+ letterSpacing: "-0.005em",
80
+ },
81
+ layout: {
82
+ ...DEFAULT_LAYOUT,
83
+ radius: "0.75rem",
84
+ },
85
+ };
86
+
87
+ export const emberTheme: DashboardTheme = {
88
+ name: "ember",
89
+ label: "Ember",
90
+ description: "Warm crimson and bronze — forge vibes",
91
+ palette: {
92
+ background: { hex: "#1a0a06", alpha: 1 },
93
+ midground: { hex: "#ffd8b0", alpha: 1 },
94
+ foreground: { hex: "#ffffff", alpha: 0 },
95
+ warmGlow: "rgba(249, 115, 22, 0.38)",
96
+ noiseOpacity: 1,
97
+ },
98
+ typography: {
99
+ ...DEFAULT_TYPOGRAPHY,
100
+ fontSans: `"Spectral", Georgia, "Times New Roman", serif`,
101
+ fontMono: `"IBM Plex Mono", ${SYSTEM_MONO}`,
102
+ fontUrl:
103
+ "https://fonts.googleapis.com/css2?family=Spectral:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;700&display=swap",
104
+ },
105
+ layout: {
106
+ ...DEFAULT_LAYOUT,
107
+ radius: "0.25rem",
108
+ },
109
+ colorOverrides: {
110
+ destructive: "#c92d0f",
111
+ warning: "#f97316",
112
+ },
113
+ };
114
+
115
+ export const monoTheme: DashboardTheme = {
116
+ name: "mono",
117
+ label: "Mono",
118
+ description: "Clean grayscale — minimal and focused",
119
+ palette: {
120
+ background: { hex: "#0e0e0e", alpha: 1 },
121
+ midground: { hex: "#eaeaea", alpha: 1 },
122
+ foreground: { hex: "#ffffff", alpha: 0 },
123
+ warmGlow: "rgba(255, 255, 255, 0.1)",
124
+ noiseOpacity: 0.6,
125
+ },
126
+ typography: {
127
+ ...DEFAULT_TYPOGRAPHY,
128
+ fontSans: `"IBM Plex Sans", ${SYSTEM_SANS}`,
129
+ fontMono: `"IBM Plex Mono", ${SYSTEM_MONO}`,
130
+ fontUrl:
131
+ "https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap",
132
+ },
133
+ layout: {
134
+ ...DEFAULT_LAYOUT,
135
+ radius: "0",
136
+ },
137
+ };
138
+
139
+ export const cyberpunkTheme: DashboardTheme = {
140
+ name: "cyberpunk",
141
+ label: "Cyberpunk",
142
+ description: "Neon green on black — matrix terminal",
143
+ palette: {
144
+ background: { hex: "#040608", alpha: 1 },
145
+ midground: { hex: "#9bffcf", alpha: 1 },
146
+ foreground: { hex: "#ffffff", alpha: 0 },
147
+ warmGlow: "rgba(0, 255, 136, 0.22)",
148
+ noiseOpacity: 1.2,
149
+ },
150
+ typography: {
151
+ ...DEFAULT_TYPOGRAPHY,
152
+ fontSans: `"Share Tech Mono", "JetBrains Mono", ${SYSTEM_MONO}`,
153
+ fontMono: `"Share Tech Mono", "JetBrains Mono", ${SYSTEM_MONO}`,
154
+ fontUrl:
155
+ "https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=JetBrains+Mono:wght@400;700&display=swap",
156
+ },
157
+ layout: {
158
+ ...DEFAULT_LAYOUT,
159
+ radius: "0",
160
+ },
161
+ colorOverrides: {
162
+ success: "#00ff88",
163
+ warning: "#ffd700",
164
+ destructive: "#ff0055",
165
+ },
166
+ };
167
+
168
+ export const roseTheme: DashboardTheme = {
169
+ name: "rose",
170
+ label: "Rosé",
171
+ description: "Soft pink and warm ivory — easy on the eyes",
172
+ palette: {
173
+ background: { hex: "#1a0f15", alpha: 1 },
174
+ midground: { hex: "#ffd4e1", alpha: 1 },
175
+ foreground: { hex: "#ffffff", alpha: 0 },
176
+ warmGlow: "rgba(249, 168, 212, 0.3)",
177
+ noiseOpacity: 0.9,
178
+ },
179
+ typography: {
180
+ ...DEFAULT_TYPOGRAPHY,
181
+ fontSans: `"Fraunces", Georgia, serif`,
182
+ fontMono: `"DM Mono", ${SYSTEM_MONO}`,
183
+ fontUrl:
184
+ "https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=DM+Mono:wght@400;500&display=swap",
185
+ },
186
+ layout: {
187
+ ...DEFAULT_LAYOUT,
188
+ radius: "1rem",
189
+ },
190
+ };
191
+
192
+ /**
193
+ * AMOLED Black — pure #000000 background for OLED screens.
194
+ * Full iOS-style glassmorphism: every surface is frosted glass with
195
+ * backdrop-filter blur, semi-transparent backgrounds, cyan glow accents,
196
+ * pill-shaped nav items, and thin cyan scrollbars.
197
+ */
198
+ export const amoledTheme: DashboardTheme = {
199
+ name: "amoled",
200
+ label: "AMOLED Black",
201
+ description: "Pure black OLED glass — iOS frosted panels, cyan accents, zero grain",
202
+ palette: {
203
+ background: { hex: "#000000", alpha: 1 },
204
+ midground: { hex: "#00e5ff", alpha: 1 },
205
+ foreground: { hex: "#ffffff", alpha: 0.04 },
206
+ warmGlow: "rgba(0, 229, 255, 0.14)",
207
+ noiseOpacity: 0,
208
+ },
209
+ typography: {
210
+ ...DEFAULT_TYPOGRAPHY,
211
+ fontSans: `"Inter", ${SYSTEM_SANS}`,
212
+ fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`,
213
+ fontUrl:
214
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap",
215
+ letterSpacing: "-0.01em",
216
+ },
217
+ layout: {
218
+ radius: "1.25rem",
219
+ density: "comfortable",
220
+ },
221
+ componentStyles: {
222
+ sidebar: {
223
+ background: "rgba(0, 0, 0, 0.72)",
224
+ },
225
+ header: {
226
+ background: "rgba(0, 0, 0, 0.80)",
227
+ },
228
+ card: {
229
+ background: "rgba(6, 6, 6, 0.70)",
230
+ },
231
+ },
232
+ colorOverrides: {
233
+ primary: "#00e5ff",
234
+ primaryForeground: "#000000",
235
+ accent: "#00e5ff",
236
+ accentForeground: "#000000",
237
+ border: "rgba(0, 229, 255, 0.08)",
238
+ input: "rgba(255, 255, 255, 0.05)",
239
+ muted: "rgba(0, 0, 0, 0.50)",
240
+ mutedForeground: "#6b7280",
241
+ card: "rgba(6, 6, 6, 0.70)",
242
+ popover: "rgba(4, 4, 4, 0.88)",
243
+ success: "#00ff88",
244
+ warning: "#ffcc00",
245
+ destructive: "#ff3b5c",
246
+ ring: "#00e5ff",
247
+ },
248
+ customCSS: `
249
+ /* ═══════════════════════════════════════════════════════════════════
250
+ iOS-AMOLED Glass — frosted glass on every surface
251
+ ═══════════════════════════════════════════════════════════════════ */
252
+
253
+ /* ── Sidebar: deep frosted glass panel ─────────────────────────────── */
254
+ #app-sidebar {
255
+ backdrop-filter: blur(32px) saturate(200%) brightness(0.88) !important;
256
+ -webkit-backdrop-filter: blur(32px) saturate(200%) brightness(0.88) !important;
257
+ border-right: 1px solid rgba(0, 229, 255, 0.07) !important;
258
+ box-shadow: 4px 0 48px rgba(0,0,0,0.65), inset -1px 0 0 rgba(0,229,255,0.04) !important;
259
+ }
260
+
261
+ /* ── Mobile top bar: glass bar ──────────────────────────────────────── */
262
+ header {
263
+ backdrop-filter: blur(24px) saturate(180%) !important;
264
+ -webkit-backdrop-filter: blur(24px) saturate(180%) !important;
265
+ border-bottom: 1px solid rgba(0, 229, 255, 0.07) !important;
266
+ }
267
+
268
+ /* ── Sidebar nav items: iOS pill shape ──────────────────────────────── */
269
+ #app-sidebar nav a,
270
+ #app-sidebar nav li > button:not([size="icon"]) {
271
+ border-radius: 0.875rem !important;
272
+ margin: 1px 8px !important;
273
+ padding-left: 12px !important;
274
+ padding-right: 12px !important;
275
+ transition: background 150ms ease, box-shadow 150ms ease !important;
276
+ }
277
+ #app-sidebar nav a[aria-current="page"] {
278
+ background: rgba(0, 229, 255, 0.10) !important;
279
+ box-shadow: inset 0 0 0 1px rgba(0, 229, 255, 0.22), 0 0 16px rgba(0,229,255,0.05) !important;
280
+ }
281
+ #app-sidebar nav a:not([aria-current="page"]):hover {
282
+ background: rgba(255, 255, 255, 0.04) !important;
283
+ }
284
+
285
+ /* ── Modals / dialogs / popovers: deepest glass ─────────────────────── */
286
+ [role="dialog"],
287
+ [data-radix-popper-content-wrapper] > *,
288
+ [data-state][data-side] {
289
+ backdrop-filter: blur(40px) saturate(200%) !important;
290
+ -webkit-backdrop-filter: blur(40px) saturate(200%) !important;
291
+ background: rgba(4, 4, 4, 0.88) !important;
292
+ border: 1px solid rgba(0, 229, 255, 0.09) !important;
293
+ border-radius: var(--theme-radius) !important;
294
+ box-shadow: 0 32px 80px rgba(0,0,0,0.88), 0 0 0 1px rgba(255,255,255,0.025) !important;
295
+ }
296
+
297
+ /* ── Inputs: glass with cyan focus glow ─────────────────────────────── */
298
+ input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
299
+ textarea,
300
+ select {
301
+ background: rgba(255, 255, 255, 0.04) !important;
302
+ border-color: rgba(0, 229, 255, 0.14) !important;
303
+ border-radius: var(--theme-radius) !important;
304
+ transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease !important;
305
+ }
306
+ input:not([type="checkbox"]):not([type="radio"]):focus-visible,
307
+ textarea:focus-visible,
308
+ select:focus {
309
+ border-color: rgba(0, 229, 255, 0.55) !important;
310
+ box-shadow: 0 0 0 3px rgba(0, 229, 255, 0.10), 0 0 20px rgba(0,229,255,0.06) !important;
311
+ outline: none !important;
312
+ background: rgba(0, 229, 255, 0.03) !important;
313
+ }
314
+
315
+ /* ── Search inputs: full pill ────────────────────────────────────────── */
316
+ input[type="search"],
317
+ input[placeholder*="Search"],
318
+ input[placeholder*="search"],
319
+ input[placeholder*="Filter"],
320
+ input[placeholder*="filter"] {
321
+ border-radius: 9999px !important;
322
+ }
323
+
324
+ /* ── Buttons: rounded everywhere ─────────────────────────────────────── */
325
+ button:not([role="switch"]):not([class*="rounded-full"]),
326
+ [role="button"]:not([role="switch"]) {
327
+ border-radius: calc(var(--theme-radius) * 0.75) !important;
328
+ }
329
+
330
+ /* ── Cards: subtle glass depth ───────────────────────────────────────── */
331
+ .card,
332
+ [class*="rounded-lg"],
333
+ [class*="rounded-xl"],
334
+ [class*="rounded-2xl"] {
335
+ backdrop-filter: blur(8px) !important;
336
+ -webkit-backdrop-filter: blur(8px) !important;
337
+ }
338
+
339
+ /* ── Thin cyan scrollbars ─────────────────────────────────────────────── */
340
+ * {
341
+ scrollbar-width: thin;
342
+ scrollbar-color: rgba(0, 229, 255, 0.16) transparent;
343
+ }
344
+ ::-webkit-scrollbar { width: 3px; height: 3px; }
345
+ ::-webkit-scrollbar-track { background: transparent; }
346
+ ::-webkit-scrollbar-thumb {
347
+ background: rgba(0, 229, 255, 0.16);
348
+ border-radius: 99px;
349
+ }
350
+ ::-webkit-scrollbar-thumb:hover { background: rgba(0, 229, 255, 0.35); }
351
+
352
+ /* ── Text selection: cyan highlight ──────────────────────────────────── */
353
+ ::selection {
354
+ background: rgba(0, 229, 255, 0.22);
355
+ color: #ffffff;
356
+ }
357
+
358
+ /* ── Kill grain completely ───────────────────────────────────────────── */
359
+ .grain::after { display: none !important; }
360
+ `,
361
+ };
362
+
363
+ /**
364
+ * Cloud — maximum rounding, soft lavender-white palette.
365
+ * The "bubbly" feel: every element has pill-shaped corners.
366
+ */
367
+ export const cloudTheme: DashboardTheme = {
368
+ name: "cloud",
369
+ label: "Cloud",
370
+ description: "Soft, rounded, airy — lavender haze with pillowy corners",
371
+ palette: {
372
+ background: { hex: "#0f0d1a", alpha: 1 },
373
+ midground: { hex: "#e8e0ff", alpha: 1 },
374
+ foreground: { hex: "#ffffff", alpha: 0.02 },
375
+ warmGlow: "rgba(180, 160, 255, 0.28)",
376
+ noiseOpacity: 0.5,
377
+ },
378
+ typography: {
379
+ ...DEFAULT_TYPOGRAPHY,
380
+ fontSans: `"Nunito", "Outfit", ${SYSTEM_SANS}`,
381
+ fontMono: `"Fira Code", ${SYSTEM_MONO}`,
382
+ fontUrl:
383
+ "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap",
384
+ lineHeight: "1.65",
385
+ letterSpacing: "0.01em",
386
+ },
387
+ layout: {
388
+ radius: "1.5rem",
389
+ density: "spacious",
390
+ },
391
+ colorOverrides: {
392
+ primary: "#a78bfa",
393
+ primaryForeground: "#0f0d1a",
394
+ accent: "#c4b5fd",
395
+ accentForeground: "#0f0d1a",
396
+ border: "#2a2040",
397
+ card: "#16112a",
398
+ success: "#86efac",
399
+ warning: "#fde68a",
400
+ destructive: "#f87171",
401
+ ring: "#a78bfa",
402
+ },
403
+ };
404
+
405
+ /**
406
+ * Nord — the classic Arctic color palette. Rounded, calm, professional.
407
+ */
408
+ export const nordTheme: DashboardTheme = {
409
+ name: "nord",
410
+ label: "Nord",
411
+ description: "Arctic blue — calm, clean, Scandinavian minimalism",
412
+ palette: {
413
+ background: { hex: "#2e3440", alpha: 1 },
414
+ midground: { hex: "#d8dee9", alpha: 1 },
415
+ foreground: { hex: "#eceff4", alpha: 0.03 },
416
+ warmGlow: "rgba(136, 192, 208, 0.2)",
417
+ noiseOpacity: 0.4,
418
+ },
419
+ typography: {
420
+ ...DEFAULT_TYPOGRAPHY,
421
+ fontSans: `"Inter", ${SYSTEM_SANS}`,
422
+ fontMono: `"JetBrains Mono", ${SYSTEM_MONO}`,
423
+ fontUrl:
424
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap",
425
+ letterSpacing: "-0.005em",
426
+ },
427
+ layout: {
428
+ radius: "0.75rem",
429
+ density: "comfortable",
430
+ },
431
+ colorOverrides: {
432
+ primary: "#88c0d0",
433
+ primaryForeground: "#2e3440",
434
+ accent: "#81a1c1",
435
+ accentForeground: "#eceff4",
436
+ border: "#3b4252",
437
+ card: "#3b4252",
438
+ success: "#a3be8c",
439
+ warning: "#ebcb8b",
440
+ destructive: "#bf616a",
441
+ ring: "#88c0d0",
442
+ },
443
+ };
444
+
445
+ /**
446
+ * Same look as ``defaultTheme`` but with a larger root font size, looser
447
+ * line-height, and ``spacious`` density so every rem-based size in the
448
+ * dashboard scales up. For users who find the default 15px UI too dense.
449
+ */
450
+ export const defaultLargeTheme: DashboardTheme = {
451
+ name: "default-large",
452
+ label: "NasTech Teal (Large)",
453
+ description: "NasTech Teal with bigger fonts and roomier spacing",
454
+ palette: defaultTheme.palette,
455
+ typography: {
456
+ ...DEFAULT_TYPOGRAPHY,
457
+ baseSize: "18px",
458
+ lineHeight: "1.65",
459
+ },
460
+ layout: {
461
+ ...DEFAULT_LAYOUT,
462
+ density: "spacious",
463
+ },
464
+ };
465
+
466
+ export const BUILTIN_THEMES: Record<string, DashboardTheme> = {
467
+ default: defaultTheme,
468
+ "default-large": defaultLargeTheme,
469
+ midnight: midnightTheme,
470
+ ember: emberTheme,
471
+ mono: monoTheme,
472
+ cyberpunk: cyberpunkTheme,
473
+ rose: roseTheme,
474
+ amoled: amoledTheme,
475
+ cloud: cloudTheme,
476
+ nord: nordTheme,
477
+ };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Dashboard theme model.
3
+ *
4
+ * Themes customise three orthogonal layers:
5
+ *
6
+ * 1. `palette` — the 3-layer color triplet (background/midground/
7
+ * foreground) + warm-glow + noise opacity. The
8
+ * design-system cascade in `src/index.css` derives
9
+ * every shadcn-compat token (card, muted, border,
10
+ * primary, etc.) from this triplet via `color-mix()`.
11
+ * 2. `typography` — font families, base font size, line height,
12
+ * letter spacing. An optional `fontUrl` is injected
13
+ * as `<link rel="stylesheet">` so self-hosted and
14
+ * Google/Bunny/etc-hosted fonts both work.
15
+ * 3. `layout` — corner radius and density (spacing multiplier).
16
+ *
17
+ * Plus an optional `colorOverrides` escape hatch for themes that want to
18
+ * pin specific shadcn tokens to exact values (e.g. a pastel theme that
19
+ * needs a softer `destructive` red than the derived default).
20
+ */
21
+
22
+ /** A color layer: hex base + alpha (0–1). */
23
+ export interface ThemeLayer {
24
+ alpha: number;
25
+ hex: string;
26
+ }
27
+
28
+ export interface ThemePalette {
29
+ /** Deepest canvas color (typically near-black). */
30
+ background: ThemeLayer;
31
+ /** Primary text + accent. Most UI chrome reads this. */
32
+ midground: ThemeLayer;
33
+ /** Top-layer highlight. In LENS_0 this is white @ alpha 0 — invisible by
34
+ * default but still drives `--color-ring`-style accents. */
35
+ foreground: ThemeLayer;
36
+ /** Warm vignette color for <Backdrop />, as an rgba() string. */
37
+ warmGlow: string;
38
+ /** Scalar multiplier (0–1.2) on the noise overlay. Lower for softer themes
39
+ * like Mono and Rosé, higher for grittier themes like Cyberpunk. */
40
+ noiseOpacity: number;
41
+ }
42
+
43
+ export interface ThemeTypography {
44
+ /** CSS font-family stack for sans-serif body copy. */
45
+ fontSans: string;
46
+ /** CSS font-family stack for monospace / code blocks. */
47
+ fontMono: string;
48
+ /** Optional display/heading font stack. Falls back to `fontSans`. */
49
+ fontDisplay?: string;
50
+ /** Optional external stylesheet URL (e.g. Google Fonts, Bunny Fonts,
51
+ * self-hosted .woff2 @font-face sheet). Injected as a <link> in <head>
52
+ * on theme switch. Same URL is never injected twice. */
53
+ fontUrl?: string;
54
+ /** Root font size (controls rem scale). Example: `"14px"`, `"16px"`. */
55
+ baseSize: string;
56
+ /** Default line-height. Example: `"1.5"`, `"1.65"`. */
57
+ lineHeight: string;
58
+ /** Default letter-spacing. Example: `"0"`, `"0.01em"`, `"-0.01em"`. */
59
+ letterSpacing: string;
60
+ }
61
+
62
+ export type ThemeDensity = "compact" | "comfortable" | "spacious";
63
+
64
+ export interface ThemeLayout {
65
+ /** Corner-radius token. Example: `"0"`, `"0.25rem"`, `"0.5rem"`,
66
+ * `"1rem"`. Maps to `--radius` and cascades into every component. */
67
+ radius: string;
68
+ /** Spacing multiplier. `compact` = 0.85, `comfortable` = 1.0 (default),
69
+ * `spacious` = 1.2. Applied via the `--spacing-mul` CSS var. */
70
+ density: ThemeDensity;
71
+ }
72
+
73
+ /** Overall layout variant the shell renders. `standard` = default single-
74
+ * column page layout. `cockpit` = reserves a left sidebar rail for a
75
+ * plugin slot (intended for HUD-style themes with persistent status panels).
76
+ * `tiled` = relaxes the main content max-width so pages can use the full
77
+ * viewport width. Themes set this; plugins react via CSS vars /
78
+ * `[data-layout-variant="..."]` selectors. */
79
+ export type ThemeLayoutVariant = "standard" | "cockpit" | "tiled";
80
+
81
+ /** Named hero/background assets a theme can populate. Each value is
82
+ * emitted as a CSS var (`--theme-asset-<name>`). The default shell
83
+ * consumes `bg` in `<Backdrop />` when present; other slots are
84
+ * plugin-facing — a cockpit sidebar plugin reads `--theme-asset-hero`
85
+ * to render its hero render without coupling to the theme name. */
86
+ export interface ThemeAssets {
87
+ /** Full-viewport background image URL, injected under the noise layer. */
88
+ bg?: string;
89
+ /** Hero render (Gundam, mascot, wallpaper) — for plugin sidebars/overlays. */
90
+ hero?: string;
91
+ /** Logo mark — header slot consumers use this. */
92
+ logo?: string;
93
+ /** Faction/brand crest — header-left decoration. */
94
+ crest?: string;
95
+ /** Secondary sidebar illustration. */
96
+ sidebar?: string;
97
+ /** Alternate header artwork. */
98
+ header?: string;
99
+ /** User-defined named assets. Keyed by [a-zA-Z0-9_-] only.
100
+ * Emitted as `--theme-asset-custom-<key>`. */
101
+ custom?: Record<string, string>;
102
+ }
103
+
104
+ /** Component-style override buckets. Each bucket's entries become CSS
105
+ * vars (`--component-<bucket>-<kebab-property>`) that shell components
106
+ * (Card, Backdrop, App header/footer, etc.) read. Values are plain CSS
107
+ * strings — we don't parse them, so themes can use `clip-path`,
108
+ * `border-image`, `background`, `box-shadow`, and anything else CSS
109
+ * accepts. */
110
+ export interface ThemeComponentStyles {
111
+ card?: Record<string, string>;
112
+ header?: Record<string, string>;
113
+ footer?: Record<string, string>;
114
+ sidebar?: Record<string, string>;
115
+ tab?: Record<string, string>;
116
+ progress?: Record<string, string>;
117
+ badge?: Record<string, string>;
118
+ backdrop?: Record<string, string>;
119
+ page?: Record<string, string>;
120
+ }
121
+
122
+ /** Optional hex overrides keyed by shadcn-compat token name (without the
123
+ * `--color-` prefix). Any key set here wins over the DS cascade. */
124
+ export interface ThemeColorOverrides {
125
+ card?: string;
126
+ cardForeground?: string;
127
+ popover?: string;
128
+ popoverForeground?: string;
129
+ primary?: string;
130
+ primaryForeground?: string;
131
+ secondary?: string;
132
+ secondaryForeground?: string;
133
+ muted?: string;
134
+ mutedForeground?: string;
135
+ accent?: string;
136
+ accentForeground?: string;
137
+ destructive?: string;
138
+ destructiveForeground?: string;
139
+ success?: string;
140
+ warning?: string;
141
+ border?: string;
142
+ input?: string;
143
+ ring?: string;
144
+ }
145
+
146
+ export interface DashboardTheme {
147
+ description: string;
148
+ label: string;
149
+ name: string;
150
+ palette: ThemePalette;
151
+ typography: ThemeTypography;
152
+ layout: ThemeLayout;
153
+ /** Overall shell layout. Defaults to `"standard"` when absent. */
154
+ layoutVariant?: ThemeLayoutVariant;
155
+ /** Named + custom asset URLs exposed as CSS vars on theme apply. */
156
+ assets?: ThemeAssets;
157
+ /** Raw CSS injected as a scoped `<style>` tag on theme apply, cleaned up
158
+ * on theme switch. Intended for selector-level chrome that's too
159
+ * expressive for componentStyles alone (e.g. `::before` pseudo-elements,
160
+ * complex animations, media queries). */
161
+ customCSS?: string;
162
+ /** Per-component CSS-var overrides. See `ThemeComponentStyles`. */
163
+ componentStyles?: ThemeComponentStyles;
164
+ colorOverrides?: ThemeColorOverrides;
165
+ }
166
+
167
+ /**
168
+ * Wire response shape for `GET /api/dashboard/themes`.
169
+ *
170
+ * The `themes` list is intentionally partial — built-in themes are fully
171
+ * defined in `presets.ts`; user themes carry their full definition so the
172
+ * client can apply them without a second round-trip.
173
+ */
174
+ export interface ThemeListEntry {
175
+ description: string;
176
+ label: string;
177
+ name: string;
178
+ /** Full theme definition. Present for user-defined themes loaded from
179
+ * `~/.nastech/dashboard-themes/*.yaml`; undefined for built-ins (the
180
+ * client already has those in `BUILTIN_THEMES`). */
181
+ definition?: DashboardTheme;
182
+ }
183
+
184
+ export interface ThemeListResponse {
185
+ active: string;
186
+ themes: ThemeListEntry[];
187
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Path aliases */
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ },
24
+
25
+ /* Linting */
26
+ "strict": true,
27
+ "noUnusedLocals": true,
28
+ "noUnusedParameters": true,
29
+ "erasableSyntaxOnly": true,
30
+ "noFallthroughCasesInSwitch": true,
31
+ "noUncheckedSideEffectImports": true
32
+ },
33
+ "include": ["src"]
34
+ }