@columenlabs/lumen-ui 0.0.0-stage → 0.2.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 (297) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +12 -0
  3. package/README.md +4 -2
  4. package/dist/add-column.d.ts +23 -0
  5. package/dist/add-column.js +62 -0
  6. package/dist/api-fetch.d.ts +9 -0
  7. package/dist/api-fetch.js +60 -0
  8. package/dist/app-switcher.d.ts +21 -0
  9. package/dist/app-switcher.js +12 -0
  10. package/dist/app-views.d.ts +18 -0
  11. package/dist/app-views.js +41 -0
  12. package/dist/assistant-context.d.ts +72 -0
  13. package/dist/assistant-context.js +30 -0
  14. package/dist/assistant-panel.d.ts +58 -0
  15. package/dist/assistant-panel.js +244 -0
  16. package/dist/assistant-screen.d.ts +124 -0
  17. package/dist/assistant-screen.js +252 -0
  18. package/dist/card-switch.d.ts +16 -0
  19. package/dist/card-switch.js +16 -0
  20. package/dist/color-dot.d.ts +14 -0
  21. package/dist/color-dot.js +19 -0
  22. package/dist/confirm.d.ts +18 -0
  23. package/dist/confirm.js +5 -0
  24. package/dist/connections.d.ts +45 -0
  25. package/dist/connections.js +275 -0
  26. package/dist/coverage-calendar-map.d.ts +27 -0
  27. package/dist/coverage-calendar-map.js +84 -0
  28. package/dist/coverage-cells.d.ts +55 -0
  29. package/dist/coverage-cells.js +85 -0
  30. package/dist/coverage-map.d.ts +112 -0
  31. package/dist/coverage-map.js +428 -0
  32. package/dist/details-table.d.ts +20 -0
  33. package/dist/details-table.js +13 -0
  34. package/dist/drawer-section.d.ts +27 -0
  35. package/dist/drawer-section.js +23 -0
  36. package/dist/floating-head.d.ts +2 -0
  37. package/dist/floating-head.js +57 -0
  38. package/dist/fold.d.ts +16 -0
  39. package/dist/fold.js +70 -0
  40. package/dist/form-issues.d.ts +32 -0
  41. package/dist/form-issues.js +60 -0
  42. package/dist/formula-input.d.ts +17 -0
  43. package/dist/formula-input.js +104 -0
  44. package/dist/frame-script.d.ts +7 -0
  45. package/dist/frame-script.js +103 -0
  46. package/dist/freshness.d.ts +24 -0
  47. package/dist/freshness.js +99 -0
  48. package/dist/group-button.d.ts +33 -0
  49. package/dist/group-button.js +67 -0
  50. package/dist/growth-chart.d.ts +77 -0
  51. package/dist/growth-chart.js +195 -0
  52. package/dist/hooks/use-keyboard-fit.d.ts +15 -0
  53. package/dist/hooks/use-keyboard-fit.js +29 -0
  54. package/dist/hooks/use-mobile.d.ts +1 -0
  55. package/dist/hooks/use-mobile.js +15 -0
  56. package/dist/icon-button.d.ts +21 -0
  57. package/dist/icon-button.js +22 -0
  58. package/dist/icon-picker.d.ts +14 -0
  59. package/dist/icon-picker.js +36 -0
  60. package/dist/index.d.ts +83 -0
  61. package/dist/index.js +90 -0
  62. package/dist/integrations.d.ts +57 -0
  63. package/dist/integrations.js +73 -0
  64. package/dist/joins.d.ts +128 -0
  65. package/dist/joins.js +767 -0
  66. package/dist/kit-config.d.ts +13 -0
  67. package/dist/kit-config.js +15 -0
  68. package/dist/lib/utils.d.ts +2 -0
  69. package/dist/lib/utils.js +5 -0
  70. package/dist/lumen-boundary.d.ts +7 -0
  71. package/dist/lumen-boundary.js +10 -0
  72. package/dist/lumen-code-editor-impl.d.ts +2 -0
  73. package/dist/lumen-code-editor-impl.js +16 -0
  74. package/dist/lumen-code-editor.d.ts +11 -0
  75. package/dist/lumen-code-editor.js +11 -0
  76. package/dist/lumen-code.d.ts +22 -0
  77. package/dist/lumen-code.js +75 -0
  78. package/dist/lumen-drawer.d.ts +30 -0
  79. package/dist/lumen-drawer.js +27 -0
  80. package/dist/lumen-echart.d.ts +58 -0
  81. package/dist/lumen-echart.js +129 -0
  82. package/dist/lumen-filter.d.ts +75 -0
  83. package/dist/lumen-filter.js +546 -0
  84. package/dist/lumen-finder.d.ts +62 -0
  85. package/dist/lumen-finder.js +275 -0
  86. package/dist/lumen-form.d.ts +34 -0
  87. package/dist/lumen-form.js +81 -0
  88. package/dist/lumen-geo-map.d.ts +9 -0
  89. package/dist/lumen-geo-map.js +135 -0
  90. package/dist/lumen-grid.d.ts +49 -0
  91. package/dist/lumen-grid.js +203 -0
  92. package/dist/lumen-layout.d.ts +150 -0
  93. package/dist/lumen-layout.js +658 -0
  94. package/dist/lumen-links.d.ts +96 -0
  95. package/dist/lumen-links.js +213 -0
  96. package/dist/lumen-page-state.d.ts +20 -0
  97. package/dist/lumen-page-state.js +15 -0
  98. package/dist/lumen-selector.d.ts +139 -0
  99. package/dist/lumen-selector.js +308 -0
  100. package/dist/lumen-source.d.ts +137 -0
  101. package/dist/lumen-source.js +327 -0
  102. package/dist/lumen-tab-drawer.d.ts +27 -0
  103. package/dist/lumen-tab-drawer.js +26 -0
  104. package/dist/lumen-tabs.d.ts +21 -0
  105. package/dist/lumen-tabs.js +6 -0
  106. package/dist/lumen-toast.d.ts +11 -0
  107. package/dist/lumen-toast.js +16 -0
  108. package/dist/lumen-trend.d.ts +18 -0
  109. package/dist/lumen-trend.js +157 -0
  110. package/dist/lumen-view-card.d.ts +35 -0
  111. package/dist/lumen-view-card.js +50 -0
  112. package/dist/lumen-view.d.ts +172 -0
  113. package/dist/lumen-view.js +645 -0
  114. package/dist/lumen-widget-charts.d.ts +57 -0
  115. package/dist/lumen-widget-charts.js +69 -0
  116. package/dist/lumen-widget.d.ts +614 -0
  117. package/dist/lumen-widget.js +594 -0
  118. package/dist/market/compare-chart.d.ts +34 -0
  119. package/dist/market/compare-chart.js +292 -0
  120. package/dist/market/indicator-picker.d.ts +29 -0
  121. package/dist/market/indicator-picker.js +125 -0
  122. package/dist/market/indicators.d.ts +63 -0
  123. package/dist/market/indicators.js +314 -0
  124. package/dist/market/live.d.ts +120 -0
  125. package/dist/market/live.js +444 -0
  126. package/dist/market/lumen-chart.d.ts +88 -0
  127. package/dist/market/lumen-chart.js +645 -0
  128. package/dist/market/market-widget.d.ts +45 -0
  129. package/dist/market/market-widget.js +346 -0
  130. package/dist/market/option-chain.d.ts +19 -0
  131. package/dist/market/option-chain.js +53 -0
  132. package/dist/market/price-chart.d.ts +128 -0
  133. package/dist/market/price-chart.js +619 -0
  134. package/dist/mcp-settings.d.ts +20 -0
  135. package/dist/mcp-settings.js +65 -0
  136. package/dist/nav.d.ts +77 -0
  137. package/dist/nav.js +73 -0
  138. package/dist/note-line.d.ts +13 -0
  139. package/dist/note-line.js +5 -0
  140. package/dist/notifications.d.ts +9 -0
  141. package/dist/notifications.js +166 -0
  142. package/dist/origin-legend.d.ts +14 -0
  143. package/dist/origin-legend.js +38 -0
  144. package/dist/overview.d.ts +84 -0
  145. package/dist/overview.js +105 -0
  146. package/dist/page-inputs.d.ts +14 -0
  147. package/dist/page-inputs.js +16 -0
  148. package/dist/page.d.ts +32 -0
  149. package/dist/page.js +45 -0
  150. package/dist/pager.d.ts +10 -0
  151. package/dist/pager.js +14 -0
  152. package/dist/prefs.d.ts +71 -0
  153. package/dist/prefs.js +126 -0
  154. package/dist/query-client.d.ts +7 -0
  155. package/dist/query-client.js +19 -0
  156. package/dist/refresh.d.ts +4 -0
  157. package/dist/refresh.js +19 -0
  158. package/dist/rename-field.d.ts +11 -0
  159. package/dist/rename-field.js +26 -0
  160. package/dist/reset-button.d.ts +9 -0
  161. package/dist/reset-button.js +10 -0
  162. package/dist/route-error.d.ts +42 -0
  163. package/dist/route-error.js +229 -0
  164. package/dist/schedules.d.ts +88 -0
  165. package/dist/schedules.js +221 -0
  166. package/dist/settings.d.ts +51 -0
  167. package/dist/settings.js +181 -0
  168. package/dist/shell.d.ts +99 -0
  169. package/dist/shell.js +137 -0
  170. package/dist/source-health.d.ts +12 -0
  171. package/dist/source-health.js +58 -0
  172. package/dist/spotlight.d.ts +23 -0
  173. package/dist/spotlight.js +138 -0
  174. package/dist/status-health.d.ts +16 -0
  175. package/dist/status-health.js +41 -0
  176. package/dist/status.d.ts +9 -0
  177. package/dist/status.js +12 -0
  178. package/dist/summary-cards.d.ts +56 -0
  179. package/dist/summary-cards.js +270 -0
  180. package/dist/symbol-sheet-context.d.ts +4 -0
  181. package/dist/symbol-sheet-context.js +5 -0
  182. package/dist/symbol-sheet.d.ts +9 -0
  183. package/dist/symbol-sheet.js +24 -0
  184. package/dist/tasks.d.ts +29 -0
  185. package/dist/tasks.js +278 -0
  186. package/dist/theme.css +606 -0
  187. package/dist/tile.d.ts +37 -0
  188. package/dist/tile.js +23 -0
  189. package/dist/ui/alert-dialog.d.ts +18 -0
  190. package/dist/ui/alert-dialog.js +41 -0
  191. package/dist/ui/alert.d.ts +10 -0
  192. package/dist/ui/alert.js +27 -0
  193. package/dist/ui/avatar.d.ts +11 -0
  194. package/dist/ui/avatar.js +22 -0
  195. package/dist/ui/badge.d.ts +9 -0
  196. package/dist/ui/badge.js +27 -0
  197. package/dist/ui/breadcrumb.d.ts +11 -0
  198. package/dist/ui/breadcrumb.js +27 -0
  199. package/dist/ui/button-group.d.ts +11 -0
  200. package/dist/ui/button-group.js +27 -0
  201. package/dist/ui/button.d.ts +10 -0
  202. package/dist/ui/button.js +38 -0
  203. package/dist/ui/card.d.ts +11 -0
  204. package/dist/ui/card.js +24 -0
  205. package/dist/ui/chart.d.ts +44 -0
  206. package/dist/ui/chart.js +117 -0
  207. package/dist/ui/checkbox.d.ts +4 -0
  208. package/dist/ui/checkbox.js +8 -0
  209. package/dist/ui/collapsible.d.ts +5 -0
  210. package/dist/ui/collapsible.js +12 -0
  211. package/dist/ui/combobox.d.ts +24 -0
  212. package/dist/ui/combobox.js +60 -0
  213. package/dist/ui/command.d.ts +18 -0
  214. package/dist/ui/command.js +34 -0
  215. package/dist/ui/dialog.d.ts +23 -0
  216. package/dist/ui/dialog.js +50 -0
  217. package/dist/ui/dropdown-menu.d.ts +29 -0
  218. package/dist/ui/dropdown-menu.js +50 -0
  219. package/dist/ui/emoji-picker.d.ts +6 -0
  220. package/dist/ui/emoji-picker.js +28 -0
  221. package/dist/ui/empty.d.ts +11 -0
  222. package/dist/ui/empty.js +33 -0
  223. package/dist/ui/field.d.ts +24 -0
  224. package/dist/ui/field.js +65 -0
  225. package/dist/ui/hover-card.d.ts +6 -0
  226. package/dist/ui/hover-card.js +13 -0
  227. package/dist/ui/input-group.d.ts +16 -0
  228. package/dist/ui/input-group.js +56 -0
  229. package/dist/ui/input.d.ts +3 -0
  230. package/dist/ui/input.js +6 -0
  231. package/dist/ui/item.d.ts +23 -0
  232. package/dist/ui/item.js +67 -0
  233. package/dist/ui/kbd.d.ts +3 -0
  234. package/dist/ui/kbd.js +9 -0
  235. package/dist/ui/label.d.ts +4 -0
  236. package/dist/ui/label.js +7 -0
  237. package/dist/ui/popover.d.ts +10 -0
  238. package/dist/ui/popover.js +25 -0
  239. package/dist/ui/progress.d.ts +4 -0
  240. package/dist/ui/progress.js +9 -0
  241. package/dist/ui/resizable.d.ts +7 -0
  242. package/dist/ui/resizable.js +13 -0
  243. package/dist/ui/scroll-area.d.ts +5 -0
  244. package/dist/ui/scroll-area.js +10 -0
  245. package/dist/ui/select.d.ts +15 -0
  246. package/dist/ui/select.js +36 -0
  247. package/dist/ui/separator.d.ts +4 -0
  248. package/dist/ui/separator.js +7 -0
  249. package/dist/ui/sheet.d.ts +14 -0
  250. package/dist/ui/sheet.js +54 -0
  251. package/dist/ui/sidebar.d.ts +69 -0
  252. package/dist/ui/sidebar.js +206 -0
  253. package/dist/ui/skeleton.d.ts +2 -0
  254. package/dist/ui/skeleton.js +6 -0
  255. package/dist/ui/slider.d.ts +4 -0
  256. package/dist/ui/slider.js +9 -0
  257. package/dist/ui/sonner.d.ts +3 -0
  258. package/dist/ui/sonner.js +24 -0
  259. package/dist/ui/spinner.d.ts +2 -0
  260. package/dist/ui/spinner.js +7 -0
  261. package/dist/ui/switch.d.ts +6 -0
  262. package/dist/ui/switch.js +7 -0
  263. package/dist/ui/table.d.ts +10 -0
  264. package/dist/ui/table.js +31 -0
  265. package/dist/ui/tabs.d.ts +7 -0
  266. package/dist/ui/tabs.js +22 -0
  267. package/dist/ui/textarea.d.ts +3 -0
  268. package/dist/ui/textarea.js +6 -0
  269. package/dist/ui/toggle-group.d.ts +10 -0
  270. package/dist/ui/toggle-group.js +22 -0
  271. package/dist/ui/toggle.d.ts +9 -0
  272. package/dist/ui/toggle.js +25 -0
  273. package/dist/ui/tooltip.d.ts +7 -0
  274. package/dist/ui/tooltip.js +16 -0
  275. package/dist/view-edit-cell.d.ts +49 -0
  276. package/dist/view-edit-cell.js +198 -0
  277. package/dist/view-editor.d.ts +52 -0
  278. package/dist/view-editor.js +940 -0
  279. package/dist/view-formulas.d.ts +61 -0
  280. package/dist/view-formulas.js +247 -0
  281. package/dist/view-icons.d.ts +20 -0
  282. package/dist/view-icons.js +70 -0
  283. package/dist/view-model.d.ts +390 -0
  284. package/dist/view-model.js +316 -0
  285. package/dist/whats-new.d.ts +3 -0
  286. package/dist/whats-new.js +53 -0
  287. package/dist/widget-controls.d.ts +9 -0
  288. package/dist/widget-controls.js +26 -0
  289. package/dist/widget-settings.d.ts +436 -0
  290. package/dist/widget-settings.js +290 -0
  291. package/dist/widget-thumbs.d.ts +3 -0
  292. package/dist/widget-thumbs.js +29 -0
  293. package/dist/workspace-keys.d.ts +12 -0
  294. package/dist/workspace-keys.js +13 -0
  295. package/dist/workspaces.d.ts +57 -0
  296. package/dist/workspaces.js +473 -0
  297. package/package.json +82 -4
@@ -0,0 +1,229 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The router's error page for every app (Logs, and Feed before it, showed a
3
+ // bare "Something went wrong / Show error" that looked like a crash).
4
+ //
5
+ // The usual cause is a tab still running the build before a deploy: its next page asks for files
6
+ // the server no longer has. Vite then fires `vite:preloadError`; cancelling that event (as this did
7
+ // before) makes the import resolve to `undefined`, so the page threw "Cannot read properties of
8
+ // undefined (reading 'LogsPage')", which nothing recognised as a new build and the error page
9
+ // showed until the reload landed. Now the event is never cancelled, the import fails as what it
10
+ // is, and the page shows "Updating" while the tab reloads onto the new build.
11
+ //
12
+ // Any other error is tried once more on its own; if it fails again the page says so with Try again
13
+ // and Reload. Every error is sent to the Worker (`POST /api/client-error`), which writes it to
14
+ // Workers Logs, so it can be read there.
15
+ import { useRouter } from '@tanstack/react-router';
16
+ import { ChevronRight, RefreshCw, RotateCcw, TriangleAlert } from 'lucide-react';
17
+ import { useEffect, useState } from 'react';
18
+ import { toast } from 'sonner';
19
+ import { Button } from './ui/button.js';
20
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible.js';
21
+ import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from './ui/empty.js';
22
+ import { Spinner } from './ui/spinner.js';
23
+ const KEY = 'lumen_new_build_reload';
24
+ const GAP_MS = 10_000;
25
+ /** A dynamic import that failed to load (Chrome, Firefox, Safari wording), not a page's own bug. */
26
+ export function isStaleBuildError(error) {
27
+ const message = error instanceof Error ? error.message : '';
28
+ return (message.startsWith('Failed to fetch dynamically imported module') ||
29
+ message.startsWith('error loading dynamically imported module') ||
30
+ message.startsWith('Importing a module script failed') ||
31
+ message.includes('Unable to preload CSS') ||
32
+ // Safari, when a missing file comes back as a page instead of a script.
33
+ message.includes('is not a valid JavaScript MIME type'));
34
+ }
35
+ /** A reload this tab has already started; anything failing meanwhile waits for it. */
36
+ let reloadStarted = false;
37
+ /** Whether this tab has not reloaded for a new build in the last 10 s. */
38
+ function mayReload() {
39
+ if (reloadStarted)
40
+ return true;
41
+ try {
42
+ return Date.now() - Number(sessionStorage.getItem(KEY) ?? 0) >= GAP_MS;
43
+ }
44
+ catch {
45
+ return true;
46
+ }
47
+ }
48
+ /**
49
+ * Reloads onto the new build unless it already did in the last 10 s (a file that is truly missing
50
+ * then shows its error instead of reloading forever); says whether a reload is under way.
51
+ */
52
+ export function reloadForNewBuild() {
53
+ if (reloadStarted)
54
+ return true;
55
+ if (!mayReload())
56
+ return false;
57
+ try {
58
+ sessionStorage.setItem(KEY, String(Date.now()));
59
+ }
60
+ catch {
61
+ // No sessionStorage (private mode): reload anyway.
62
+ }
63
+ reloadStarted = true;
64
+ window.location.reload();
65
+ return true;
66
+ }
67
+ /**
68
+ * Fetches every lazy page of this build once the tab is idle, so a later click never asks the
69
+ * server for files a deploy has since replaced. Without it each deploy (many a day) made the next
70
+ * click on a page not yet opened reload the whole tab, which cut the assistant answer short.
71
+ */
72
+ /** How long after load the other pages' files start loading. */
73
+ const PRELOAD_AFTER_MS = 8_000;
74
+ function preloadPages(router) {
75
+ const run = () => {
76
+ for (const route of Object.values(router.routesById)) {
77
+ const preload = route.options.component
78
+ ?.preload;
79
+ preload?.().catch(() => { });
80
+ }
81
+ };
82
+ // Not at load: the page's own first render and data come first. At load a phone was still
83
+ // building the page, and every other page's files were fetched and run beside it (2026-10-22).
84
+ const idle = () => setTimeout(() => 'requestIdleCallback' in window ? window.requestIdleCallback(run) : setTimeout(run, 1000), PRELOAD_AFTER_MS);
85
+ if (document.readyState === 'complete')
86
+ idle();
87
+ else
88
+ window.addEventListener('load', idle, { once: true });
89
+ }
90
+ /**
91
+ * Keeps an open tab working across deploys: every page's files load up front (when a router is
92
+ * given), and a file a deploy removed reloads the tab onto the new build. The event is never
93
+ * cancelled: cancelling it makes the import resolve to nothing, which the page then hits as a bug.
94
+ */
95
+ export function watchNewBuilds(router) {
96
+ window.addEventListener('vite:preloadError', () => {
97
+ reloadForNewBuild();
98
+ });
99
+ if (router)
100
+ preloadPages(router);
101
+ watchDeploys();
102
+ }
103
+ /** How often a tab in view asks whether a deploy has replaced its build. */
104
+ const CHECK_MS = 10 * 60_000;
105
+ /** This tab's entry script, the hashed file every build names afresh. */
106
+ const entryOf = (html) => html.match(/<script[^>]*type="module"[^>]*src="([^"]+)"/)?.[1] ?? null;
107
+ /** Whether the app's page now names another entry script than the one this tab runs. */
108
+ async function newBuildOut() {
109
+ const mine = document
110
+ .querySelector('script[type="module"][src]')
111
+ ?.getAttribute('src');
112
+ if (!mine)
113
+ return false;
114
+ try {
115
+ const res = await fetch(import.meta.env.BASE_URL ?? '/', { cache: 'no-store' });
116
+ if (!res.ok || !res.headers.get('content-type')?.includes('text/html'))
117
+ return false;
118
+ const live = entryOf(await res.text());
119
+ return !!live && live !== mine;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ /**
126
+ * A tab left open across deploys keeps running its old build until a file it asks for is gone,
127
+ * so it can be a day behind and talk to a Worker that expects the new one (* The assistant on a phone tab from yesterday said the app had no workspaces). The tab asks whether a
128
+ * deploy has replaced its build when it comes back into view and every 10 minutes while in view,
129
+ * and offers Reload once per new build. It never reloads on its own: that would cut work short.
130
+ */
131
+ function watchDeploys() {
132
+ let offered = false;
133
+ let busy = false;
134
+ const check = async () => {
135
+ if (offered || busy || document.visibilityState !== 'visible')
136
+ return;
137
+ busy = true;
138
+ try {
139
+ if (!(await newBuildOut()))
140
+ return;
141
+ offered = true;
142
+ toast('A new version is ready', {
143
+ duration: Number.POSITIVE_INFINITY,
144
+ action: { label: 'Reload', onClick: () => window.location.reload() },
145
+ });
146
+ }
147
+ finally {
148
+ busy = false;
149
+ }
150
+ };
151
+ document.addEventListener('visibilitychange', () => void check());
152
+ setInterval(() => void check(), CHECK_MS);
153
+ }
154
+ /** When each error was last sent from each place, so the same one is sent once. */
155
+ const sent = new Map();
156
+ /** Last error tried again on its own, so a page that keeps failing is tried once, not forever. */
157
+ let retried = null;
158
+ /**
159
+ * Sends a page error to the Worker's log; never throws, never retries. `where` names the card or
160
+ * panel that caught it (LumenBoundary); left out, the whole page did.
161
+ */
162
+ export function reportError(error, outcome, where) {
163
+ const err = error instanceof Error ? error : new Error(String(error));
164
+ // React renders a failing part twice before giving up: one line per error and place.
165
+ const key = `${where ?? 'page'} ${outcome} ${err.message}`;
166
+ if (Date.now() - (sent.get(key) ?? 0) < GAP_MS)
167
+ return;
168
+ sent.set(key, Date.now());
169
+ const body = JSON.stringify({
170
+ message: err.message.slice(0, 500),
171
+ stack: (err.stack ?? '').slice(0, 2000),
172
+ path: window.location.pathname + window.location.search,
173
+ build: import.meta.url.split('/').pop() ?? '',
174
+ outcome,
175
+ where: where ?? 'page',
176
+ });
177
+ try {
178
+ fetch('/api/client-error', {
179
+ method: 'POST',
180
+ headers: { 'content-type': 'application/json' },
181
+ body,
182
+ keepalive: true,
183
+ }).catch(() => { });
184
+ }
185
+ catch {
186
+ // Reporting must never break the page.
187
+ }
188
+ }
189
+ /**
190
+ * One centred message with its buttons, the same on every app: the whole page's (RouteError) or
191
+ * one card's or panel's (LumenBoundary, `what` "card" or "panel", smaller).
192
+ */
193
+ export function ErrorPage({ error, onRetry, what = 'page', }) {
194
+ const message = error instanceof Error ? error.message : String(error);
195
+ const stack = error instanceof Error ? error.stack : undefined;
196
+ return (_jsxs(Empty, { className: what === 'page' ? 'min-h-[50vh]' : 'gap-3 p-4 md:p-6', children: [_jsxs(EmptyHeader, { children: [_jsx(EmptyMedia, { variant: "icon", children: _jsx(TriangleAlert, {}) }), _jsxs(EmptyTitle, { children: ["This ", what, " hit an error"] }), _jsx(EmptyDescription, { children: "Try again, or reload the app. It has been logged." })] }), _jsxs(EmptyContent, { children: [_jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { size: "sm", onClick: onRetry, children: [_jsx(RotateCcw, {}), "Try again"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => window.location.reload(), children: [_jsx(RefreshCw, {}), "Reload"] })] }), _jsxs(Collapsible, { className: "w-full text-left", children: [_jsxs(CollapsibleTrigger, { className: "group flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(ChevronRight, { className: "size-3 transition-transform group-data-[state=open]:rotate-90" }), "Details"] }), _jsx(CollapsibleContent, { children: _jsx("pre", { className: "mt-2 max-h-48 overflow-auto rounded-md bg-muted p-2 text-left text-xs whitespace-pre-wrap", children: stack ?? message }) })] })] })] }));
197
+ }
198
+ /**
199
+ * The router's error page: "Updating" while the tab reloads onto a new build; any other error is
200
+ * tried once more on its own, then shown with Try again and Reload. Each one is logged.
201
+ */
202
+ export function RouteError({ error, reset }) {
203
+ const router = useRouter();
204
+ const [mode] = useState(() => {
205
+ if (isStaleBuildError(error) && mayReload())
206
+ return 'reload';
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ if (!retried || retried.message !== message || Date.now() - retried.at > GAP_MS)
209
+ return 'retry';
210
+ return 'shown';
211
+ });
212
+ const retry = () => {
213
+ reset();
214
+ router.invalidate();
215
+ };
216
+ // biome-ignore lint/correctness/useExhaustiveDependencies: once per error page.
217
+ useEffect(() => {
218
+ reportError(error, mode);
219
+ if (mode === 'reload')
220
+ reloadForNewBuild();
221
+ if (mode === 'retry') {
222
+ retried = { message: error instanceof Error ? error.message : String(error), at: Date.now() };
223
+ retry();
224
+ }
225
+ }, []);
226
+ if (mode === 'shown')
227
+ return _jsx(ErrorPage, { error: error, onRetry: retry });
228
+ return (_jsx(Empty, { className: "min-h-[50vh]", children: _jsxs(EmptyHeader, { children: [_jsx(EmptyMedia, { variant: "icon", children: _jsx(Spinner, {}) }), _jsx(EmptyDescription, { children: mode === 'reload' ? 'Updating to the new version' : 'Loading' })] }) }));
229
+ }
@@ -0,0 +1,88 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ScheduleRun {
3
+ at: string;
4
+ ok: boolean;
5
+ detail: string;
6
+ }
7
+ /** One schedule as an app's API returns it. */
8
+ export interface ScheduleItem {
9
+ id: string;
10
+ name: string;
11
+ on: boolean;
12
+ /** Minutes between runs. */
13
+ every: number;
14
+ /** `HH:MM`: the window's start (`window`) or the time of day (`daily`). */
15
+ from: string;
16
+ /** `HH:MM`: the window's end (`window`). */
17
+ to: string;
18
+ /** Days it runs, 0 = Sunday (`window`). */
19
+ days: number[];
20
+ tz: string;
21
+ /** `window`: a window and days; `outside`: whenever a window is closed; `daily`: once a day; `reports`: on report days. */
22
+ kind: 'window' | 'outside' | 'daily' | 'reports';
23
+ /** The Every choices in minutes (none: it runs once a day). */
24
+ choices: number[];
25
+ /** Fixed words for when it runs, for kinds whose times can't be changed. */
26
+ when?: string;
27
+ /** What it does: the data it inspects, processing, enrichment, output and cost. */
28
+ about: {
29
+ inspects: string;
30
+ processes: string;
31
+ enriches?: string;
32
+ outputs: string;
33
+ cost?: string;
34
+ };
35
+ last: ScheduleRun | null;
36
+ next: string | null;
37
+ /** Why it can't run now (a service not connected). */
38
+ waiting: string | null;
39
+ /** A the assistant brief: what it asks, its spend cap, Notify and its last run's chat. */
40
+ brief?: {
41
+ /** `outlook`: the market outlook, whose instructions are fixed; `brief`: the person's own. */
42
+ kind: 'outlook' | 'brief';
43
+ instructions: string;
44
+ cap_usd: number;
45
+ notify: boolean;
46
+ last: {
47
+ chat: string;
48
+ usd: number;
49
+ } | null;
50
+ };
51
+ }
52
+ export type ScheduleChange = Partial<Pick<ScheduleItem, 'on' | 'every' | 'from' | 'to' | 'days' | 'tz'>>;
53
+ export interface BriefChange {
54
+ instructions?: string;
55
+ cap_usd?: number;
56
+ notify?: boolean;
57
+ }
58
+ /** How an app saves, adds and removes the assistant briefs (each change returns once the list is fresh). */
59
+ export interface BriefActions {
60
+ save: (id: string, change: BriefChange) => Promise<void>;
61
+ /** Adds a brief; returns its schedule's id. */
62
+ create: (brief: {
63
+ name: string;
64
+ instructions: string;
65
+ }) => Promise<string>;
66
+ remove: (id: string) => Promise<void>;
67
+ }
68
+ type Save = (id: string, change: ScheduleChange) => Promise<void>;
69
+ /** Opens the app's Logs filtered to one schedule's runs. */
70
+ type Runs = (id: string) => void;
71
+ /** The app's view of one schedule's newest runs (from its Logs). */
72
+ type Recent = (id: string) => ReactNode;
73
+ /** The Settings ▸ Schedules section's body. */
74
+ export declare function SchedulesSection({ schedules, error, onSave, onRun, onRuns, recent, briefs, }: {
75
+ /** Undefined while loading. */
76
+ schedules?: ScheduleItem[];
77
+ error?: string | null;
78
+ onSave: Save;
79
+ /** Runs it now; returns the run. */
80
+ onRun: (id: string) => Promise<ScheduleRun | null>;
81
+ /** Opens Logs filtered to a schedule's runs; adds Runs buttons. */
82
+ onRuns?: Runs;
83
+ /** Draws a schedule's newest runs in its editor (else its last run). */
84
+ recent?: Recent;
85
+ /** The assistant briefs: their own fields in the editor, and + Brief. */
86
+ briefs?: BriefActions;
87
+ }): import("react").JSX.Element;
88
+ export {};
@@ -0,0 +1,221 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AlertTriangleIcon, CheckIcon, ListIcon, MessageSquareIcon, PlayIcon, PlusIcon, RefreshCwIcon, Trash2Icon, } from 'lucide-react';
3
+ import { Fragment, useState } from 'react';
4
+ import { useAssistant, useAssistantName } from './assistant-context.js';
5
+ // Settings ▸ Schedules: the work an app runs for the person on its own, one
6
+ // section for every app. An app passes its schedules (each says how it is shaped: a window, the
7
+ // rest of the time, once a day or on report days) and how to save and run one; the shell draws the
8
+ // list and the editor sheet. An app without schedules adds no section. The assistant briefs are schedules too: ✦ rows with their instructions, spend cap and Notify switch, + Brief
9
+ // to add one, and their last run's chat opens in the assistant.
10
+ import { LumenConfirm } from './confirm.js';
11
+ import { useIsMobile } from './hooks/use-mobile.js';
12
+ import { LumenSelector } from './lumen-selector.js';
13
+ import { IconButton } from './page.js';
14
+ import { usePrefs } from './prefs.js';
15
+ import { useShell } from './shell.js';
16
+ import { Badge } from './ui/badge.js';
17
+ import { Button } from './ui/button.js';
18
+ import { Input } from './ui/input.js';
19
+ import { Label } from './ui/label.js';
20
+ import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from './ui/sheet.js';
21
+ import { Skeleton } from './ui/skeleton.js';
22
+ import { Switch } from './ui/switch.js';
23
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table.js';
24
+ import { Textarea } from './ui/textarea.js';
25
+ import { ToggleGroup, ToggleGroupItem } from './ui/toggle-group.js';
26
+ const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
27
+ const ZONES = [
28
+ 'America/Los_Angeles',
29
+ 'America/Denver',
30
+ 'America/Chicago',
31
+ 'America/New_York',
32
+ 'Europe/London',
33
+ 'Asia/Kolkata',
34
+ 'UTC',
35
+ ];
36
+ const everyText = (m) => (m >= 1440 ? '1 day' : m >= 60 ? `${m / 60} h` : `${m} min`);
37
+ /** `13:00` → `1:00 PM`. */
38
+ const clockText = (hhmm) => {
39
+ const [h = 0, m = 0] = hhmm.split(':').map(Number);
40
+ return `${h % 12 || 12}:${String(m).padStart(2, '0')} ${h < 12 ? 'AM' : 'PM'}`;
41
+ };
42
+ const daysText = (days) => {
43
+ const d = [...days].sort();
44
+ if (d.length === 7)
45
+ return 'Every day';
46
+ if (d.join() === '1,2,3,4,5')
47
+ return 'Weekdays';
48
+ return d.map((x) => DAYS[x]).join(', ');
49
+ };
50
+ const zoneText = (tz) => tz.split('/').pop()?.replace('_', ' ') ?? tz;
51
+ /** When it runs, in words. */
52
+ function when(s) {
53
+ if (s.when)
54
+ return s.when;
55
+ if (s.kind === 'window')
56
+ return `${daysText(s.days)} ${clockText(s.from)}–${clockText(s.to)} ${zoneText(s.tz)}`;
57
+ return `Daily at ${clockText(s.from)} ${zoneText(s.tz)}`;
58
+ }
59
+ function useTime() {
60
+ const { timeZone } = usePrefs();
61
+ return (iso) => new Date(iso).toLocaleString('en-US', {
62
+ timeZone,
63
+ weekday: 'short',
64
+ hour: 'numeric',
65
+ minute: '2-digit',
66
+ });
67
+ }
68
+ function Last({ s, compact = false }) {
69
+ const time = useTime();
70
+ if (!s.last)
71
+ return _jsx("span", { className: "text-muted-foreground", children: "Not yet" });
72
+ return (_jsxs("span", { className: `flex min-w-0 items-center gap-1.5 ${s.last.ok ? '' : 'text-loss'}`, children: [s.last.ok ? (_jsx(CheckIcon, { className: "size-3.5 shrink-0 text-gain" })) : (_jsx(AlertTriangleIcon, { className: "size-3.5 shrink-0" })), _jsx("span", { className: "truncate", title: s.last.detail, children: compact ? time(s.last.at) : `${time(s.last.at)} · ${s.last.detail}` })] }));
73
+ }
74
+ function OnSwitch({ s, onSave }) {
75
+ const [pending, setPending] = useState(false);
76
+ return (_jsx(Switch, { "aria-label": `${s.name} on`, checked: s.on, disabled: pending, onClick: (e) => e.stopPropagation(), onCheckedChange: (on) => {
77
+ setPending(true);
78
+ void onSave(s.id, { on }).finally(() => setPending(false));
79
+ } }));
80
+ }
81
+ function Row({ label, children }) {
82
+ return (_jsxs("div", { className: "grid grid-cols-1 gap-1.5", children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: label }), children] }));
83
+ }
84
+ /** At (a daily schedule), Days and Time zone: the schedule editor's and New brief's. */
85
+ function TimeFields({ kind, draft, set, }) {
86
+ const zones = !draft.tz || ZONES.includes(draft.tz) ? ZONES : [draft.tz, ...ZONES];
87
+ return (_jsxs(_Fragment, { children: [kind === 'daily' ? (_jsx(Row, { label: "At", children: _jsx(Input, { type: "time", "aria-label": "At", className: "num w-32", value: draft.from, onChange: (e) => set({ from: e.target.value }) }) })) : null, _jsx(Row, { label: "Days", children: _jsx(ToggleGroup, { type: "multiple", variant: "outline", size: "sm", spacing: 1, className: "w-full", value: (draft.days ?? []).map(String), onValueChange: (v) => v.length && set({ days: v.map(Number) }), children: [1, 2, 3, 4, 5, 6, 0].map((d) => (_jsx(ToggleGroupItem, { value: String(d), className: "flex-1 px-0", children: DAYS[d] }, d))) }) }), _jsx(Row, { label: "Time zone", children: _jsx(LumenSelector, { label: "Time zone", className: "w-full", options: zones.map((z) => ({ value: z, label: z.replace('_', ' ') })), value: draft.tz, onChange: (tz) => set({ tz }) }) })] }));
88
+ }
89
+ /** One schedule's settings, with Run now. */
90
+ function Editor({ s, onSave, onRun, onRuns, recent, briefs, onClose, }) {
91
+ const assistantName = useAssistantName();
92
+ const time = useTime();
93
+ const assistant = useAssistant();
94
+ const { closeSettings } = useShell();
95
+ const [brief, setBrief] = useState({
96
+ instructions: s.brief?.instructions,
97
+ cap_usd: s.brief?.cap_usd,
98
+ notify: s.brief?.notify,
99
+ });
100
+ const [confirm, setConfirm] = useState(false);
101
+ const own = s.brief?.kind === 'brief';
102
+ const last = s.brief?.last;
103
+ const [draft, setDraft] = useState({
104
+ on: s.on,
105
+ every: s.every,
106
+ from: s.from,
107
+ to: s.to,
108
+ days: s.days,
109
+ tz: s.tz,
110
+ });
111
+ const [busy, setBusy] = useState(null);
112
+ const [error, setError] = useState(null);
113
+ const [ran, setRan] = useState(null);
114
+ const set = (c) => setDraft((d) => ({ ...d, ...c }));
115
+ const act = async (what) => {
116
+ setBusy(what);
117
+ setError(null);
118
+ try {
119
+ if (what === 'save') {
120
+ if (s.brief && briefs) {
121
+ const changed = {
122
+ ...(own && brief.instructions?.trim() !== s.brief.instructions
123
+ ? { instructions: brief.instructions?.trim() }
124
+ : {}),
125
+ ...(brief.cap_usd !== s.brief.cap_usd ? { cap_usd: brief.cap_usd } : {}),
126
+ ...(brief.notify !== s.brief.notify ? { notify: brief.notify } : {}),
127
+ };
128
+ if (Object.keys(changed).length)
129
+ await briefs.save(s.id, changed);
130
+ }
131
+ await onSave(s.id, draft);
132
+ onClose();
133
+ }
134
+ else
135
+ setRan(await onRun(s.id));
136
+ }
137
+ catch (err) {
138
+ setError(err.message);
139
+ }
140
+ finally {
141
+ setBusy(null);
142
+ }
143
+ };
144
+ return (_jsxs(_Fragment, { children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: s.name }), _jsx(SheetDescription, { children: when(s) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-5 overflow-y-auto px-4", children: [_jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-xs", children: [
145
+ ['Inspects', s.about.inspects],
146
+ ['Processes', s.about.processes],
147
+ ['Enriches', s.about.enriches],
148
+ ['Outputs', s.about.outputs],
149
+ ['Cost', s.about.cost],
150
+ ]
151
+ .filter(([, v]) => v)
152
+ .map(([k, v]) => (_jsxs(Fragment, { children: [_jsx("dt", { className: "text-muted-foreground", children: k }), _jsx("dd", { children: v })] }, k))) }), s.brief ? (_jsx(Row, { label: "Instructions", children: own ? (_jsx(Textarea, { "aria-label": "Instructions", rows: 5, maxLength: 4000, value: brief.instructions ?? '', onChange: (e) => setBrief((b) => ({ ...b, instructions: e.target.value })) })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: s.brief.instructions })) })) : null, _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { htmlFor: "sched-on", children: "On" }), _jsx(Switch, { id: "sched-on", checked: draft.on, onCheckedChange: (on) => set({ on }) })] }), s.choices.length ? (_jsx(Row, { label: "Every", children: _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", value: String(draft.every), onValueChange: (v) => v && set({ every: Number(v) }), children: s.choices.map((m) => (_jsx(ToggleGroupItem, { value: String(m), className: "num", children: everyText(m) }, m))) }) })) : null, s.kind === 'window' ? (_jsx(_Fragment, { children: _jsx(Row, { label: "Window", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { type: "time", "aria-label": "From", className: "num w-32", value: draft.from, onChange: (e) => set({ from: e.target.value }) }), _jsx("span", { className: "text-muted-foreground", children: "to" }), _jsx(Input, { type: "time", "aria-label": "To", className: "num w-32", value: draft.to, onChange: (e) => set({ to: e.target.value }) })] }) }) })) : null, s.kind === 'window' || s.kind === 'daily' ? (_jsx(TimeFields, { kind: s.kind, draft: draft, set: set })) : null, s.brief ? (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Spend cap a run", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "$" }), _jsx(Input, { type: "number", "aria-label": "Spend cap a run, USD", className: "num w-24", min: 0.05, max: 5, step: 0.05, value: brief.cap_usd ?? '', onChange: (e) => setBrief((b) => ({ ...b, cap_usd: Number(e.target.value) })) })] }) }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { htmlFor: "brief-notify", children: "Notify when ready" }), _jsx(Switch, { id: "brief-notify", checked: brief.notify ?? false, onCheckedChange: (notify) => setBrief((b) => ({ ...b, notify })) })] })] })) : null, s.waiting ? (_jsx("p", { className: "text-sm text-warning", children: s.waiting })) : s.next ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Next run ", time(s.next)] })) : null, recent && !ran ? (_jsx(Row, { label: "Last runs", children: recent(s.id) })) : (_jsx(Row, { label: "Last run", children: _jsx("span", { className: "text-sm", children: _jsx(Last, { s: ran ? { ...s, last: ran } : s }) }) })), last && assistant ? (_jsxs(Button, { variant: "outline", size: "sm", className: "justify-self-start", onClick: () => {
153
+ assistant.loadChat(last.chat);
154
+ assistant.setOpen(true);
155
+ onClose();
156
+ closeSettings();
157
+ }, children: [_jsx(MessageSquareIcon, {}), "Open last run in ", assistantName, " \u00B7 ", _jsxs("span", { className: "num", children: ["$", last.usd.toFixed(2)] })] })) : null, error ? (_jsx("p", { role: "alert", className: "text-xs text-loss", children: error })) : null] }), _jsxs(SheetFooter, { className: "flex-row justify-end", children: [own && briefs ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: Trash2Icon, label: "Delete brief", variant: "destructive", disabled: busy !== null, onClick: () => setConfirm(true) }), _jsx(LumenConfirm, { open: confirm, onOpenChange: setConfirm, title: `Delete ${s.name.replace(/^✦ /, '')}?`, description: `Its schedule stops. Chats from its runs stay in ${assistantName}.`, onConfirm: () => {
158
+ setBusy('save');
159
+ void briefs
160
+ .remove(s.id)
161
+ .then(onClose)
162
+ .catch((err) => setError(err.message))
163
+ .finally(() => setBusy(null));
164
+ } })] })) : null, onRuns ? (_jsx(IconButton, { icon: ListIcon, label: "All runs", variant: "outline", className: "mr-auto", onClick: () => onRuns(s.id) })) : null, _jsx(IconButton, { icon: busy === 'run' ? RefreshCwIcon : PlayIcon, busy: busy === 'run', label: busy === 'run' ? 'Running…' : 'Run now', variant: "outline", disabled: busy !== null || Boolean(s.waiting), onClick: () => void act('run') }), _jsxs(Button, { size: "sm", disabled: busy !== null, onClick: () => void act('save'), children: [_jsx(CheckIcon, {}), "Save"] })] })] }));
165
+ }
166
+ /** The Settings ▸ Schedules section's body. */
167
+ export function SchedulesSection({ schedules, error, onSave, onRun, onRuns, recent, briefs, }) {
168
+ const isMobile = useIsMobile();
169
+ const { closeSettings } = useShell();
170
+ const [open, setOpen] = useState(null);
171
+ const [adding, setAdding] = useState(false);
172
+ // Logs is a page: close the editor and Settings on the way there.
173
+ const toRuns = onRuns
174
+ ? (id) => {
175
+ setOpen(null);
176
+ closeSettings();
177
+ onRuns(id);
178
+ }
179
+ : undefined;
180
+ const editing = schedules?.find((s) => s.id === open);
181
+ const { timeZone } = usePrefs();
182
+ if (error)
183
+ return _jsx("p", { className: "text-sm text-loss", children: error });
184
+ if (!schedules)
185
+ return _jsx(Skeleton, { className: "h-48 w-full" });
186
+ return (_jsxs(_Fragment, { children: [isMobile ? (_jsx("div", { className: "grid grid-cols-1 divide-y rounded-lg border", children: schedules.map((s) => (_jsxs("button", { type: "button", className: "grid grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 p-3 text-left hover:bg-muted/50", onClick: () => setOpen(s.id), children: [_jsx("span", { className: "font-medium", children: s.name }), _jsx(OnSwitch, { s: s, onSave: onSave }), _jsxs("span", { className: "col-span-2 text-xs text-muted-foreground", children: [s.kind === 'daily' ? '' : `Every ${everyText(s.every)} · `, when(s)] }), _jsx("span", { className: "col-span-2 text-xs", children: s.waiting ? _jsx("span", { className: "text-warning", children: s.waiting }) : _jsx(Last, { s: s }) })] }, s.id))) })) : (_jsx("div", { className: "overflow-x-auto overflow-y-hidden rounded-lg border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-12", children: "On" }), _jsx(TableHead, { children: "Schedule" }), _jsx(TableHead, { children: "When" }), _jsx(TableHead, { children: "Last run" }), onRuns ? _jsx(TableHead, { className: "w-0" }) : null] }) }), _jsx(TableBody, { children: schedules.map((s) => (_jsxs(TableRow, { className: "cursor-pointer", onClick: () => setOpen(s.id), children: [_jsx(TableCell, { children: _jsx(OnSwitch, { s: s, onSave: onSave }) }), _jsx(TableCell, { className: "font-medium", children: s.name }), _jsxs(TableCell, { className: "max-w-36 truncate text-muted-foreground", children: [s.kind === 'daily' ? '' : `Every ${everyText(s.every)} · `, when(s)] }), _jsx(TableCell, { className: "max-w-48", children: s.waiting ? (_jsx(Badge, { variant: "outline", className: "text-warning", title: s.waiting, children: "Not connected" })) : (_jsx(Last, { s: s, compact: true })) }), onRuns ? (_jsx(TableCell, { children: _jsx(IconButton, { icon: ListIcon, label: `${s.name} runs`, onClick: (e) => {
187
+ e.stopPropagation();
188
+ toRuns?.(s.id);
189
+ } }) })) : null] }, s.id))) })] }) })), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Times shown in ", timeZone.replace('_', ' '), ". Each run is a line in Logs."] }), briefs ? (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setAdding(true), children: [_jsx(PlusIcon, {}), "Brief"] })) : null] }), briefs ? (_jsx(Sheet, { open: adding, onOpenChange: setAdding, children: _jsx(SheetContent, { side: isMobile ? 'bottom' : 'right', className: "gap-4", children: _jsx(NewBrief, { create: async (b, time) => {
190
+ const id = await briefs.create(b);
191
+ await onSave(id, time);
192
+ return id;
193
+ }, onDone: () => setAdding(false) }) }) })) : null, _jsx(Sheet, { open: Boolean(editing), onOpenChange: (o) => !o && setOpen(null), children: _jsx(SheetContent, { side: isMobile ? 'bottom' : 'right', className: "gap-4", children: editing ? (_jsx(Editor, { s: editing, onSave: onSave, onRun: onRun, onRuns: toRuns, recent: recent, briefs: briefs, onClose: () => setOpen(null) }, editing.id)) : null }) })] }));
194
+ }
195
+ /** + Brief: a name, what the assistant should work out, and when (daily at 8:00 AM Pacific on weekdays to start). */
196
+ function NewBrief({ create, onDone, }) {
197
+ const assistantName = useAssistantName();
198
+ const [name, setName] = useState('');
199
+ const [instructions, setInstructions] = useState('');
200
+ const [time, setTime] = useState({
201
+ from: '08:00',
202
+ days: [1, 2, 3, 4, 5],
203
+ tz: 'America/Los_Angeles',
204
+ });
205
+ const [busy, setBusy] = useState(false);
206
+ const [error, setError] = useState(null);
207
+ const add = async () => {
208
+ setBusy(true);
209
+ setError(null);
210
+ try {
211
+ onDone(await create({ name: name.trim(), instructions: instructions.trim() }, time));
212
+ }
213
+ catch (err) {
214
+ setError(err.message);
215
+ }
216
+ finally {
217
+ setBusy(false);
218
+ }
219
+ };
220
+ return (_jsxs(_Fragment, { children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: "New brief" }), _jsxs(SheetDescription, { children: [assistantName, " works it out on a schedule, reads only, and sends it when it is ready."] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-5 overflow-y-auto px-4", children: [_jsx(Row, { label: "Name", children: _jsx(Input, { "aria-label": "Name", maxLength: 60, placeholder: "Options brief", value: name, onChange: (e) => setName(e.target.value) }) }), _jsx(Row, { label: "Instructions", children: _jsx(Textarea, { "aria-label": "Instructions", rows: 6, maxLength: 4000, placeholder: `What ${assistantName} should look at and report, e.g. my short options expiring this week with their delta and P&L.`, value: instructions, onChange: (e) => setInstructions(e.target.value) }) }), _jsx(TimeFields, { kind: "daily", draft: time, set: (c) => setTime((t) => ({ ...t, ...c })) }), error ? (_jsx("p", { role: "alert", className: "text-xs text-loss", children: error })) : null] }), _jsx(SheetFooter, { className: "flex-row justify-end", children: _jsxs(Button, { size: "sm", disabled: busy || !name.trim() || !instructions.trim(), onClick: () => void add(), children: [_jsx(PlusIcon, {}), "Add brief"] }) })] }));
221
+ }
@@ -0,0 +1,51 @@
1
+ import { SunIcon } from 'lucide-react';
2
+ import { type ReactNode } from 'react';
3
+ /** A section an app adds to Settings, listed under the app's name. */
4
+ export interface SettingsSectionDef {
5
+ id: string;
6
+ title: string;
7
+ icon: typeof SunIcon;
8
+ description?: ReactNode;
9
+ content: ReactNode;
10
+ /** Listed indented under the section before it (the assistant ▸ Model providers). */
11
+ sub?: boolean;
12
+ /** What the section's footer holds (a status line, Revert and Save). The footer is pinned under
13
+ * every section, empty when it has nothing; `SettingsFooter` fills it too. */
14
+ footer?: ReactNode;
15
+ }
16
+ /** Renders its children in the section header, right of the title. */
17
+ export declare function SettingsActions({ children }: {
18
+ children: ReactNode;
19
+ }): import("react").ReactPortal | null;
20
+ /** Renders its children in the open section's footer, from anywhere in the section's content. */
21
+ export declare function SettingsFooter({ children }: {
22
+ children: ReactNode;
23
+ }): import("react").ReactPortal | null;
24
+ /** One setting: title and description on the left, its control on the right. */
25
+ export declare function SettingRow({ label, description, htmlFor, children, }: {
26
+ label: string;
27
+ description?: ReactNode;
28
+ htmlFor?: string;
29
+ children: ReactNode;
30
+ }): import("react").JSX.Element | null;
31
+ /** Light, dark or system: in Settings with labels, in the profile menu as icons with tooltips. */
32
+ export declare function ThemeToggle({ compact }: {
33
+ compact?: boolean;
34
+ }): import("react").JSX.Element;
35
+ /** Every Settings section in the order the dialog lists them: General, the product's, the app's. */
36
+ export declare function allSettings(profile: ReactNode | undefined, shared?: SettingsSectionDef[], sections?: SettingsSectionDef[]): SettingsSectionDef[];
37
+ export declare function LumenSettings({ open, section, onOpen, onClose, appLabel, profile, sections, shared, sharedLabel, }: {
38
+ open: boolean;
39
+ /** The section shown; unknown ids show Profile. */
40
+ section: string;
41
+ onOpen: (section: string) => void;
42
+ onClose: () => void;
43
+ /** The app's name, heading its own sections. */
44
+ appLabel: string;
45
+ /** Profile's content, the first General section; none without it. */
46
+ profile?: ReactNode;
47
+ sections?: SettingsSectionDef[];
48
+ /** Sections the product adds to every app (the assistant), listed under `sharedLabel` after General. */
49
+ shared?: SettingsSectionDef[];
50
+ sharedLabel?: string;
51
+ }): import("react").JSX.Element;