@columenlabs/lumen-ui 0.0.0-stage → 0.2.1

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 (301) 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 +661 -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 +720 -0
  117. package/dist/lumen-widget.js +609 -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/sourced-card.d.ts +10 -0
  173. package/dist/sourced-card.js +69 -0
  174. package/dist/spotlight.d.ts +23 -0
  175. package/dist/spotlight.js +138 -0
  176. package/dist/status-health.d.ts +16 -0
  177. package/dist/status-health.js +41 -0
  178. package/dist/status.d.ts +9 -0
  179. package/dist/status.js +12 -0
  180. package/dist/summary-cards.d.ts +56 -0
  181. package/dist/summary-cards.js +270 -0
  182. package/dist/symbol-sheet-context.d.ts +4 -0
  183. package/dist/symbol-sheet-context.js +5 -0
  184. package/dist/symbol-sheet.d.ts +9 -0
  185. package/dist/symbol-sheet.js +24 -0
  186. package/dist/tasks.d.ts +29 -0
  187. package/dist/tasks.js +278 -0
  188. package/dist/theme.css +606 -0
  189. package/dist/tile.d.ts +37 -0
  190. package/dist/tile.js +23 -0
  191. package/dist/ui/alert-dialog.d.ts +18 -0
  192. package/dist/ui/alert-dialog.js +41 -0
  193. package/dist/ui/alert.d.ts +10 -0
  194. package/dist/ui/alert.js +27 -0
  195. package/dist/ui/avatar.d.ts +11 -0
  196. package/dist/ui/avatar.js +22 -0
  197. package/dist/ui/badge.d.ts +9 -0
  198. package/dist/ui/badge.js +27 -0
  199. package/dist/ui/breadcrumb.d.ts +11 -0
  200. package/dist/ui/breadcrumb.js +27 -0
  201. package/dist/ui/button-group.d.ts +11 -0
  202. package/dist/ui/button-group.js +27 -0
  203. package/dist/ui/button.d.ts +10 -0
  204. package/dist/ui/button.js +38 -0
  205. package/dist/ui/card.d.ts +11 -0
  206. package/dist/ui/card.js +24 -0
  207. package/dist/ui/chart.d.ts +44 -0
  208. package/dist/ui/chart.js +117 -0
  209. package/dist/ui/checkbox.d.ts +4 -0
  210. package/dist/ui/checkbox.js +8 -0
  211. package/dist/ui/collapsible.d.ts +5 -0
  212. package/dist/ui/collapsible.js +12 -0
  213. package/dist/ui/combobox.d.ts +24 -0
  214. package/dist/ui/combobox.js +60 -0
  215. package/dist/ui/command.d.ts +18 -0
  216. package/dist/ui/command.js +34 -0
  217. package/dist/ui/dialog.d.ts +23 -0
  218. package/dist/ui/dialog.js +50 -0
  219. package/dist/ui/dropdown-menu.d.ts +29 -0
  220. package/dist/ui/dropdown-menu.js +50 -0
  221. package/dist/ui/emoji-picker.d.ts +6 -0
  222. package/dist/ui/emoji-picker.js +28 -0
  223. package/dist/ui/empty.d.ts +11 -0
  224. package/dist/ui/empty.js +33 -0
  225. package/dist/ui/field.d.ts +24 -0
  226. package/dist/ui/field.js +65 -0
  227. package/dist/ui/hover-card.d.ts +6 -0
  228. package/dist/ui/hover-card.js +13 -0
  229. package/dist/ui/input-group.d.ts +16 -0
  230. package/dist/ui/input-group.js +56 -0
  231. package/dist/ui/input.d.ts +3 -0
  232. package/dist/ui/input.js +6 -0
  233. package/dist/ui/item.d.ts +23 -0
  234. package/dist/ui/item.js +67 -0
  235. package/dist/ui/kbd.d.ts +3 -0
  236. package/dist/ui/kbd.js +9 -0
  237. package/dist/ui/label.d.ts +4 -0
  238. package/dist/ui/label.js +7 -0
  239. package/dist/ui/popover.d.ts +10 -0
  240. package/dist/ui/popover.js +25 -0
  241. package/dist/ui/progress.d.ts +4 -0
  242. package/dist/ui/progress.js +9 -0
  243. package/dist/ui/resizable.d.ts +7 -0
  244. package/dist/ui/resizable.js +13 -0
  245. package/dist/ui/scroll-area.d.ts +5 -0
  246. package/dist/ui/scroll-area.js +10 -0
  247. package/dist/ui/select.d.ts +15 -0
  248. package/dist/ui/select.js +36 -0
  249. package/dist/ui/separator.d.ts +4 -0
  250. package/dist/ui/separator.js +7 -0
  251. package/dist/ui/sheet.d.ts +14 -0
  252. package/dist/ui/sheet.js +54 -0
  253. package/dist/ui/sidebar.d.ts +69 -0
  254. package/dist/ui/sidebar.js +206 -0
  255. package/dist/ui/skeleton.d.ts +2 -0
  256. package/dist/ui/skeleton.js +6 -0
  257. package/dist/ui/slider.d.ts +4 -0
  258. package/dist/ui/slider.js +9 -0
  259. package/dist/ui/sonner.d.ts +3 -0
  260. package/dist/ui/sonner.js +24 -0
  261. package/dist/ui/spinner.d.ts +2 -0
  262. package/dist/ui/spinner.js +7 -0
  263. package/dist/ui/switch.d.ts +6 -0
  264. package/dist/ui/switch.js +7 -0
  265. package/dist/ui/table.d.ts +10 -0
  266. package/dist/ui/table.js +31 -0
  267. package/dist/ui/tabs.d.ts +7 -0
  268. package/dist/ui/tabs.js +22 -0
  269. package/dist/ui/textarea.d.ts +3 -0
  270. package/dist/ui/textarea.js +6 -0
  271. package/dist/ui/toggle-group.d.ts +10 -0
  272. package/dist/ui/toggle-group.js +22 -0
  273. package/dist/ui/toggle.d.ts +9 -0
  274. package/dist/ui/toggle.js +25 -0
  275. package/dist/ui/tooltip.d.ts +7 -0
  276. package/dist/ui/tooltip.js +16 -0
  277. package/dist/view-edit-cell.d.ts +49 -0
  278. package/dist/view-edit-cell.js +198 -0
  279. package/dist/view-editor.d.ts +52 -0
  280. package/dist/view-editor.js +1081 -0
  281. package/dist/view-formulas.d.ts +61 -0
  282. package/dist/view-formulas.js +247 -0
  283. package/dist/view-icons.d.ts +20 -0
  284. package/dist/view-icons.js +70 -0
  285. package/dist/view-model.d.ts +390 -0
  286. package/dist/view-model.js +316 -0
  287. package/dist/whats-new.d.ts +3 -0
  288. package/dist/whats-new.js +53 -0
  289. package/dist/widget-controls.d.ts +9 -0
  290. package/dist/widget-controls.js +26 -0
  291. package/dist/widget-data.d.ts +4 -0
  292. package/dist/widget-data.js +32 -0
  293. package/dist/widget-settings.d.ts +516 -0
  294. package/dist/widget-settings.js +290 -0
  295. package/dist/widget-thumbs.d.ts +3 -0
  296. package/dist/widget-thumbs.js +29 -0
  297. package/dist/workspace-keys.d.ts +12 -0
  298. package/dist/workspace-keys.js +13 -0
  299. package/dist/workspaces.d.ts +57 -0
  300. package/dist/workspaces.js +473 -0
  301. package/package.json +82 -4
@@ -0,0 +1,105 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // An app's Overview: a service-health page, not a decisions page. A stage map
3
+ // (each stage a chip with its status dot, name and a one-line verdict; arrows between; tap one to
4
+ // focus it) and then each stage's widgets, one LumenLayout card each with its own ↻ and states
5
+ // (on LumenLayout). Apps pass only their stages and data; the
6
+ // widget kinds are fixed here (stat, ring, bars, timeline), drawn with Recharts in shadcn's
7
+ // ChartContainer. An app is the first user (Queue, Runner, Results); an app's
8
+ // Overview moves onto it later (Sources, Pipelines, Storage, Coverage).
9
+ import { ArrowRightIcon, TriangleAlertIcon } from 'lucide-react';
10
+ import { Fragment, useState } from 'react';
11
+ import { Bar, BarChart, Cell, Label, Pie, PieChart, XAxis, YAxis } from 'recharts';
12
+ import { cn } from './lib/utils.js';
13
+ import { LumenLayout } from './lumen-layout.js';
14
+ import { Badge } from './ui/badge.js';
15
+ import { Button } from './ui/button.js';
16
+ import { ChartContainer, ChartTooltip, ChartTooltipContent } from './ui/chart.js';
17
+ const DOT = {
18
+ ok: 'bg-gain',
19
+ warn: 'bg-warning',
20
+ bad: 'bg-destructive',
21
+ idle: 'bg-muted-foreground',
22
+ };
23
+ const FILL = {
24
+ ok: 'var(--gain)',
25
+ warn: 'var(--warning)',
26
+ bad: 'var(--destructive)',
27
+ idle: 'var(--primary)',
28
+ };
29
+ /** A stage's status mark: a dot, or ▲ when it needs a look. */
30
+ export function ToneDot({ tone, className }) {
31
+ if (tone === 'warn' || tone === 'bad') {
32
+ return (_jsx(TriangleAlertIcon, { "aria-hidden": true, className: cn('size-3.5 shrink-0', tone === 'warn' ? 'text-warning' : 'text-destructive') }));
33
+ }
34
+ return (_jsx("span", { "aria-hidden": true, className: cn('inline-block size-2 shrink-0 rounded-full', DOT[tone]) }));
35
+ }
36
+ /** Label and value rows for a widget's detail (schedules and their last tick, latencies). */
37
+ export function OverviewList({ items, }) {
38
+ return (_jsx("dl", { className: "flex min-w-0 flex-col gap-0.5 text-xs", children: items.map((it, i) => (_jsxs("div", { className: cn('flex justify-between gap-2', it.tone === 'warn' && 'text-warning', it.tone === 'bad' && 'text-destructive'), children: [_jsx("dt", { className: "truncate", children: it.label }), _jsx("dd", { className: "num shrink-0 text-muted-foreground", children: it.value })] }, i))) }));
39
+ }
40
+ function Ring({ ring, size }) {
41
+ return (_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-3", children: [ring.rings.map((r) => {
42
+ const v = Math.max(0, Math.min(1, r.value));
43
+ const data = [
44
+ { key: 'value', value: v, fill: FILL[r.tone ?? 'idle'] },
45
+ { key: 'rest', value: 1 - v, fill: 'var(--muted)' },
46
+ ];
47
+ return (_jsx(ChartContainer, { config: { value: { label: r.label } }, className: "aspect-square shrink-0", style: { width: size, height: size }, role: "img", "aria-label": `${r.label} ${r.text}`, children: _jsx(PieChart, { children: _jsxs(Pie, { data: data, dataKey: "value", nameKey: "key", innerRadius: size / 2 - 9, outerRadius: size / 2 - 2, startAngle: 90, endAngle: -270, strokeWidth: 0, isAnimationActive: false, children: [data.map((d) => (_jsx(Cell, { fill: d.fill }, d.key))), _jsx(Label, { content: ({ viewBox }) => {
48
+ if (!viewBox || !('cx' in viewBox))
49
+ return null;
50
+ const many = ring.rings.length > 1;
51
+ return (_jsxs("text", { x: viewBox.cx, y: viewBox.cy, textAnchor: "middle", children: [_jsx("tspan", { x: viewBox.cx, dy: many ? '-0.1em' : '0.35em', className: "fill-foreground font-mono text-[11px]", children: r.text }), many && (_jsx("tspan", { x: viewBox.cx, dy: "1.2em", className: "fill-muted-foreground text-[9px]", children: r.label }))] }));
52
+ } })] }) }) }, r.label));
53
+ }), ring.detail && _jsx("div", { className: "min-w-36 flex-1", children: ring.detail })] }));
54
+ }
55
+ function Bars({ w }) {
56
+ const config = { value: { label: 'Value', color: 'var(--chart-1)' } };
57
+ return (_jsx(ChartContainer, { config: config, className: "aspect-auto h-16 w-full", children: _jsxs(BarChart, { data: w.bars, margin: { top: 2, right: 0, bottom: 0, left: 0 }, children: [_jsx(XAxis, { dataKey: "label", hide: true }), _jsx(YAxis, { hide: true, domain: [0, 'dataMax'] }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { hideIndicator: true, formatter: (v) => (w.format ? w.format(Number(v)) : String(v)) }) }), _jsx(Bar, { dataKey: "value", fill: "var(--color-value)", radius: 2, isAnimationActive: false })] }) }));
58
+ }
59
+ /** Busy blocks over a window: one stacked bar of idle and busy segments, in order. */
60
+ function Timeline({ w }) {
61
+ const span = Math.max(1, w.to - w.from);
62
+ const segments = [];
63
+ let at = w.from;
64
+ const blocks = [...w.blocks]
65
+ .map((b) => ({ start: Math.max(w.from, b.start), end: Math.min(w.to, b.end) }))
66
+ .filter((b) => b.end > b.start)
67
+ .sort((a, b) => a.start - b.start);
68
+ for (const b of blocks) {
69
+ const start = Math.max(at, b.start);
70
+ if (b.end <= start)
71
+ continue;
72
+ if (start > at)
73
+ segments.push({ key: `s${segments.length}`, size: start - at, busy: false });
74
+ segments.push({ key: `s${segments.length}`, size: b.end - start, busy: true });
75
+ at = b.end;
76
+ }
77
+ if (at < w.to)
78
+ segments.push({ key: `s${segments.length}`, size: w.to - at, busy: false });
79
+ const row = Object.fromEntries(segments.map((s) => [s.key, s.size / span]));
80
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(ChartContainer, { config: {}, className: "aspect-auto h-6 w-full", children: _jsxs(BarChart, { data: [row], layout: "vertical", margin: { top: 0, right: 0, bottom: 0, left: 0 }, barCategoryGap: 0, children: [_jsx(XAxis, { type: "number", hide: true, domain: [0, 1] }), _jsx(YAxis, { type: "category", hide: true }), segments.map((s, i) => (_jsx(Bar, { dataKey: s.key, stackId: "t", fill: s.busy ? 'var(--chart-1)' : 'var(--muted)', radius: i === 0 ? [3, 0, 0, 3] : i === segments.length - 1 ? [0, 3, 3, 0] : 0, isAnimationActive: false }, s.key)))] }) }), _jsx("div", { className: "relative h-4 text-[10px] text-muted-foreground", children: w.ticks.map((t) => (_jsx("span", { className: "num absolute -translate-x-1/2 first:translate-x-0 last:-translate-x-full", style: { left: `${((t.at - w.from) / span) * 100}%` }, children: t.label }, t.at))) })] }));
81
+ }
82
+ /** A widget's body: the card's frame, title and ↻ come from LumenLayout. */
83
+ function WidgetBody({ w }) {
84
+ return (_jsxs("div", { "data-widget": w.id, className: "flex min-w-0 flex-col gap-1.5", children: [w.aside && _jsx("span", { className: "num text-xs text-muted-foreground", children: w.aside }), w.kind === 'stat' && (_jsxs(_Fragment, { children: [w.value !== undefined && (_jsx("span", { className: "num truncate text-xl leading-7 font-semibold", children: w.value })), w.detail] })), w.kind === 'ring' && _jsx(Ring, { ring: w, size: w.rings.length > 1 ? 64 : 56 }), w.kind === 'bars' && _jsx(Bars, { w: w }), w.kind === 'timeline' && _jsx(Timeline, { w: w }), w.caption && _jsx("span", { className: "text-xs text-muted-foreground", children: w.caption })] }));
85
+ }
86
+ /** A stage's widgets fill one row of thirds; four go two by two. */
87
+ const widthOf = (count) => (count === 3 ? 2 : 3);
88
+ /** The Overview on LumenLayout: the stage map leads, each widget is a card. */
89
+ export function OverviewPage({ stages, load, updated }) {
90
+ const [focus, setFocus] = useState(null);
91
+ const warnings = stages.filter((s) => s.tone === 'warn' || s.tone === 'bad').length;
92
+ const shown = focus ? stages.filter((s) => s.id === focus) : stages;
93
+ const cards = stages.length
94
+ ? shown.flatMap((s) => s.widgets.map((w) => ({
95
+ id: `${s.id}-${w.id}`,
96
+ title: w.label,
97
+ kind: 'widget',
98
+ w: widthOf(s.widgets.length),
99
+ load: s.load ?? load,
100
+ tools: (w.tone === 'warn' || w.tone === 'bad') && _jsx(ToneDot, { tone: w.tone }),
101
+ children: _jsx(WidgetBody, { w: w }),
102
+ })))
103
+ : [{ id: 'overview', title: 'Overview', kind: 'widget', load, children: null }];
104
+ return (_jsx(LumenLayout, { crumbs: [{ label: 'Overview' }], meta: _jsxs(_Fragment, { children: [warnings > 0 && (_jsxs(Badge, { variant: "outline", className: "border-warning/40 text-warning", children: [_jsx(TriangleAlertIcon, {}), warnings, " ", warnings === 1 ? 'warning' : 'warnings'] })), updated] }), intro: stages.length > 0 && (_jsxs("fieldset", { className: "m-0 flex min-w-0 flex-nowrap items-center gap-1 overflow-x-auto overflow-y-hidden border-0 p-0 sm:gap-1.5", children: [_jsx("legend", { className: "sr-only", children: "Stages" }), stages.map((s, i) => (_jsxs(Fragment, { children: [_jsxs(Button, { variant: "outline", "aria-pressed": focus === s.id, onClick: () => setFocus(focus === s.id ? null : s.id), className: cn('h-9 shrink-0 gap-1.5 px-2.5 sm:h-11 sm:gap-2 sm:px-3', focus === s.id && 'border-primary bg-primary/10', s.tone === 'warn' && focus !== s.id && 'border-warning/60', s.tone === 'bad' && focus !== s.id && 'border-destructive/60'), children: [_jsx(ToneDot, { tone: s.tone }), s.name, _jsx("span", { className: "hidden text-xs font-normal text-muted-foreground sm:inline", children: s.verdict })] }), i < stages.length - 1 && (_jsx(ArrowRightIcon, { "aria-hidden": true, className: "size-3.5 shrink-0 text-muted-foreground sm:size-4" }))] }, s.id)))] })), cards: cards }));
105
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function PageInputs({ summary, label, children, }: {
3
+ /** What is picked, short: `1Y`, `Q2 2026`, `AAPL · 2024–2026`. */
4
+ summary: ReactNode;
5
+ /** The picker's name: its tooltip and accessible name. */
6
+ label?: string;
7
+ /** The inputs, one per line, each with its own label. */
8
+ children: ReactNode;
9
+ }): import("react").JSX.Element;
10
+ /** One input in the picker: its label above it. */
11
+ export declare function PageInput({ label, children }: {
12
+ label: string;
13
+ children: ReactNode;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // A page's load inputs:
3
+ // what decides which rows load (a window, a quarter, an expiry, an explorer view's symbol and
4
+ // range, a calendar week) is one compact picker among the page header's icons (or at the right of
5
+ // the tab row, `LumenTabs`, when only one tab uses it), never a row of its own above the cards. Its button says what is picked; a tap opens the inputs.
6
+ import { ChevronDownIcon, SlidersHorizontalIcon } from 'lucide-react';
7
+ import { Button } from './ui/button.js';
8
+ import { Popover, PopoverContent, PopoverTrigger } from './ui/popover.js';
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
10
+ export function PageInputs({ summary, label = 'Inputs', children, }) {
11
+ return (_jsxs(Popover, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 max-w-48 gap-1.5 px-2", "aria-label": label, children: [_jsx(SlidersHorizontalIcon, { className: "text-muted-foreground" }), _jsx("span", { className: "num truncate", children: summary }), _jsx(ChevronDownIcon, { className: "text-muted-foreground" })] }) }) }), _jsx(TooltipContent, { children: label })] }), _jsx(PopoverContent, { align: "end", className: "flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-3", children: children })] }));
12
+ }
13
+ /** One input in the picker: its label above it. */
14
+ export function PageInput({ label, children }) {
15
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }), _jsx("div", { className: "flex flex-wrap items-center gap-2", children: children })] }));
16
+ }
package/dist/page.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from 'react';
2
+ export { IconButton, type IconButtonProps } from './icon-button.js';
3
+ export interface Crumb {
4
+ label: ReactNode;
5
+ /** Where the crumb links; the last crumb is the page itself and never links. */
6
+ to?: string;
7
+ /** Text for the phone back button and keys when `label` is not plain text. */
8
+ name?: string;
9
+ }
10
+ export interface PageHeaderProps {
11
+ crumbs: Crumb[];
12
+ /** This page's own buttons (section A): `IconButton`s, icon only, name in the tooltip
13
+ * . A divider keeps them apart from the shell's buttons. */
14
+ actions?: ReactNode;
15
+ /** Layout editing (section B's first slot): LumenLayout fills it; pages never do. */
16
+ layout?: ReactNode;
17
+ /** A short status line beside the breadcrumb (under the title on phone). */
18
+ meta?: ReactNode;
19
+ /** This page's status bar items (`StatusItem`), shown after the app's own. */
20
+ status?: ReactNode;
21
+ }
22
+ /** The fixed-height header; `Page` renders it, or a page with several states renders it itself. */
23
+ export declare function PageHeader({ crumbs, actions, layout, meta, status }: PageHeaderProps): import("react").JSX.Element;
24
+ /** The page body: the shell's gutter, section gap and max width. */
25
+ export declare function PageBody({ className, children }: {
26
+ className?: string;
27
+ children: ReactNode;
28
+ }): import("react").JSX.Element;
29
+ export declare function Page({ className, children, ...header }: PageHeaderProps & {
30
+ className?: string;
31
+ children: ReactNode;
32
+ }): import("react").JSX.Element;
package/dist/page.js ADDED
@@ -0,0 +1,45 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The page frame: a fixed-height header (sidebar toggle, breadcrumb, actions)
3
+ // and the body, with the shell's gutter, section gap and max width. Pages never set their own outer
4
+ // padding or header. On phone the actions stay in the bar as icons and a back chevron replaces the trail.
5
+ import { Link, useRouter } from '@tanstack/react-router';
6
+ import { ChevronLeftIcon, EyeIcon, EyeOffIcon, RefreshCwIcon } from 'lucide-react';
7
+ import { Fragment } from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { useAssistantNote } from './assistant-context.js';
10
+ import { cn } from './lib/utils.js';
11
+ import { setPref, usePrefs } from './prefs.js';
12
+ import { useShell } from './shell.js';
13
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from './ui/breadcrumb.js';
14
+ import { Separator } from './ui/separator.js';
15
+ import { SidebarTrigger, useSidebar } from './ui/sidebar.js';
16
+ import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
17
+ import { IconButton } from './icon-button.js';
18
+ import { LumenFinderButton } from './lumen-finder.js';
19
+ export { IconButton } from './icon-button.js';
20
+ const crumbName = (c) => c.name ?? (typeof c.label === 'string' ? c.label : '');
21
+ /** The fixed-height header; `Page` renders it, or a page with several states renders it itself. */
22
+ export function PageHeader({ crumbs, actions, layout, meta, status }) {
23
+ const router = useRouter();
24
+ const { statusSlot, refresh, refreshing, hideAmounts, headerActions } = useShell();
25
+ const hidden = usePrefs().hideAmounts;
26
+ const { isMobile } = useSidebar();
27
+ const parent = [...crumbs.slice(0, -1)].reverse().find((c) => c.to);
28
+ const current = crumbs[crumbs.length - 1];
29
+ // The assistant sees which page this is: its name on the chip, its trail for the model.
30
+ useAssistantNote('page', {
31
+ label: current ? crumbName(current) : '',
32
+ data: { page: crumbs.map(crumbName).filter(Boolean).join(' › ') },
33
+ });
34
+ return (_jsxs(_Fragment, { children: [_jsxs("header", { "data-slot": "shell-header", className: "sticky top-0 z-20 flex h-(--shell-header-height) shrink-0 items-center gap-2 overflow-hidden border-b bg-background px-(--shell-gutter)", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(SidebarTrigger, { className: "-ml-1" }) }), _jsx(TooltipContent, { children: "Toggle sidebar" })] }), _jsx(Separator, { orientation: "vertical", className: "hidden data-vertical:h-4 data-vertical:self-center md:block" }), parent && (_jsx(IconButton, { icon: ChevronLeftIcon, label: `Back to ${crumbName(parent)}`, className: "md:hidden", onClick: () => router.navigate({ to: parent.to }) })), !isMobile && (_jsx(Breadcrumb, { className: "min-w-0", children: _jsx(BreadcrumbList, { className: "flex-nowrap", children: crumbs.map((c, i) => (_jsxs(Fragment, { children: [i > 0 && _jsx(BreadcrumbSeparator, {}), _jsx(BreadcrumbItem, { className: "min-w-0", children: c.to && i < crumbs.length - 1 ? (_jsx(BreadcrumbLink, { asChild: true, children: _jsx(Link, { to: c.to, children: c.label }) })) : i === crumbs.length - 1 ? (_jsx(BreadcrumbPage, { className: "truncate", children: _jsx("h1", { className: "flex min-w-0 items-center gap-2 truncate font-semibold", children: c.label }) })) : (_jsx("span", { className: "truncate", children: c.label })) })] }, `${i}-${crumbName(c)}`))) }) })), isMobile && (_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "flex min-w-0 items-center gap-2 truncate text-sm leading-tight font-semibold", children: current?.label }), meta && (_jsx("div", { className: "flex items-center gap-1.5 truncate text-[11px] leading-tight text-muted-foreground", children: meta }))] })), meta && !isMobile && (_jsx("div", { className: "ml-1 hidden min-w-0 shrink items-center gap-2 truncate border-l pl-3 text-xs text-muted-foreground md:flex", children: meta })), _jsxs("div", { className: cn('ml-auto flex shrink-0 items-center gap-0.5',
35
+ // Phone: a page's text buttons show as icons too; their text stays as the accessible name.
36
+ isMobile &&
37
+ '[&_[data-slot=button]]:size-8 [&_[data-slot=button]]:gap-0 [&_[data-slot=button]]:px-0 [&_[data-slot=button]]:text-[0px]'), children: [actions && (_jsxs(_Fragment, { children: [_jsx("div", { "data-slot": "shell-page-actions", className: "flex items-center gap-0.5", children: actions }), _jsx(Separator, { orientation: "vertical", className: "mx-2 data-vertical:h-5 data-vertical:self-center" })] })), _jsxs("div", { "data-slot": "shell-actions", className: "flex items-center gap-0.5", children: [layout, _jsx(LumenFinderButton, {}), hideAmounts && (_jsx(IconButton, { icon: hidden ? EyeOffIcon : EyeIcon, label: hidden ? 'Show amounts' : 'Hide amounts', "aria-pressed": hidden, onClick: () => setPref('hideAmounts', !hidden) })), refresh && (_jsx(IconButton, { icon: RefreshCwIcon, busy: refreshing, label: "Refresh (skip cache)", disabled: refreshing, onClick: () => void refresh() })), headerActions] })] })] }), status && statusSlot && createPortal(status, statusSlot)] }));
38
+ }
39
+ /** The page body: the shell's gutter, section gap and max width. */
40
+ export function PageBody({ className, children }) {
41
+ return (_jsx("main", { className: cn('mx-auto flex w-full max-w-(--shell-max-width) min-w-0 flex-1 flex-col gap-(--shell-section-gap) p-(--shell-gutter)', className), children: children }));
42
+ }
43
+ export function Page({ className, children, ...header }) {
44
+ return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { ...header }), _jsx(PageBody, { className: className, children: children })] }));
45
+ }
@@ -0,0 +1,10 @@
1
+ export declare function LumenPager({ total, page, size, onPage, onSize, noun, sizes, }: {
2
+ total: number;
3
+ page: number;
4
+ size: number;
5
+ onPage: (page: number) => void;
6
+ onSize: (size: number) => void;
7
+ noun?: string;
8
+ /** Page sizes offered; a server-paged source takes its own. */
9
+ sizes?: readonly number[];
10
+ }): import("react").JSX.Element;
package/dist/pager.js ADDED
@@ -0,0 +1,14 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The compact pager, the same look as LumenView's: the count opens the page sizes,
3
+ // and the chevrons turn the page while there is more than one. Coverage views page their rows with it.
4
+ import { viewPageSizes } from '@columenlabs/lumen-ui-types/views';
5
+ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
6
+ import { IconButton } from './icon-button.js';
7
+ import { Button } from './ui/button.js';
8
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from './ui/dropdown-menu.js';
9
+ export function LumenPager({ total, page, size, onPage, onSize, noun = 'rows', sizes = viewPageSizes, }) {
10
+ const pages = Math.max(1, Math.ceil(total / size));
11
+ return (_jsxs("nav", { "aria-label": "Pages", className: "flex items-center", children: [_jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", className: "num h-7 px-2 text-xs font-normal text-muted-foreground", "aria-label": "Rows per page", children: pages > 1
12
+ ? `${page * size + 1}–${Math.min((page + 1) * size, total)} of ${total}`
13
+ : `${total} ${noun}` }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-40", children: _jsx(DropdownMenuRadioGroup, { value: String(size), onValueChange: (v) => onSize(Number(v)), children: sizes.map((n) => (_jsxs(DropdownMenuRadioItem, { value: String(n), className: "num whitespace-nowrap", children: [n, " per page"] }, n))) }) })] }), pages > 1 && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: ChevronLeftIcon, label: "Previous page", disabled: page === 0, onClick: () => onPage(page - 1) }), _jsx(IconButton, { icon: ChevronRightIcon, label: "Next page", disabled: page >= pages - 1, onClick: () => onPage(page + 1) })] }))] }));
14
+ }
@@ -0,0 +1,71 @@
1
+ export declare const THEMES: readonly ['light', 'dark', 'system'];
2
+ export type Theme = (typeof THEMES)[number];
3
+ export declare const TIME_ZONES: readonly [{
4
+ readonly id: 'America/New_York';
5
+ readonly label: 'Eastern';
6
+ }, {
7
+ readonly id: 'America/Chicago';
8
+ readonly label: 'Central';
9
+ }, {
10
+ readonly id: 'America/Los_Angeles';
11
+ readonly label: 'Pacific';
12
+ }, {
13
+ readonly id: 'UTC';
14
+ readonly label: 'UTC';
15
+ }];
16
+ export type TimeZoneId = (typeof TIME_ZONES)[number]['id'];
17
+ /** The text font; Manrope is the suite's own, the rest are for reading comfort. */
18
+ export declare const FONTS: readonly [{
19
+ readonly id: 'manrope';
20
+ readonly label: 'Manrope';
21
+ }, {
22
+ readonly id: 'inter';
23
+ readonly label: 'Inter';
24
+ }, {
25
+ readonly id: 'plex';
26
+ readonly label: 'IBM Plex Sans';
27
+ }, {
28
+ readonly id: 'system';
29
+ readonly label: 'System';
30
+ }];
31
+ export type FontId = (typeof FONTS)[number]['id'];
32
+ /** Text size: the root size every rem scales from, so spacing grows with the text. */
33
+ export declare const FONT_SIZES: readonly [{
34
+ readonly id: 'small';
35
+ readonly label: 'Small';
36
+ readonly px: 15;
37
+ }, {
38
+ readonly id: 'default';
39
+ readonly label: 'Default';
40
+ readonly px: 16;
41
+ }, {
42
+ readonly id: 'large';
43
+ readonly label: 'Large';
44
+ readonly px: 17;
45
+ }];
46
+ export type FontSizeId = (typeof FONT_SIZES)[number]['id'];
47
+ export interface Prefs {
48
+ theme: Theme;
49
+ glass: boolean;
50
+ font: FontId;
51
+ fontSize: FontSizeId;
52
+ timeZone: TimeZoneId;
53
+ /** Hide amounts: the header's eye masks holding dollars and quantities, e.g. for screen-sharing. */
54
+ hideAmounts: boolean;
55
+ /** Observability details: each card and view shows how long it took to load. */
56
+ observability: boolean;
57
+ }
58
+ /** The assistant panel: never narrower than this, nor wider than this or half the window. */
59
+ export declare const ASSISTANT_WIDTH: {
60
+ readonly min: 320;
61
+ readonly max: 800;
62
+ };
63
+ /** Eastern by default, as an app's zone picker had it (the market's own zone). */
64
+ export declare const DEFAULT_PREFS: Prefs;
65
+ /** The theme on screen: `system` follows the device. */
66
+ export declare const resolvedTheme: (p?: Prefs) => 'light' | 'dark';
67
+ export declare function setPref<K extends keyof Prefs>(key: K, value: Prefs[K]): void;
68
+ export declare const getPrefs: () => Prefs;
69
+ export declare function usePrefs(): Prefs;
70
+ /** `Pacific`, `UTC`: the short name shown next to times. */
71
+ export declare const timeZoneLabel: (id: TimeZoneId) => "America/Chicago" | "America/Los_Angeles" | "America/New_York" | "Central" | "Eastern" | "Pacific" | "UTC";
package/dist/prefs.js ADDED
@@ -0,0 +1,126 @@
1
+ // User preferences that hold on every app: theme, glass, time zone and
2
+ // Hide amounts, Observability details, font and font
3
+ // size.
4
+ // An app's pages may live on their own hosts, so the choice is kept in one cookie (on the parent
5
+ // domain the app names, `configureLumen`) rather than per-origin localStorage; nothing is stored
6
+ // server-side.
7
+ import { useSyncExternalStore } from 'react';
8
+ import { KIT, onConfigure } from './kit-config.js';
9
+ export const THEMES = ['light', 'dark', 'system'];
10
+ export const TIME_ZONES = [
11
+ { id: 'America/New_York', label: 'Eastern' },
12
+ { id: 'America/Chicago', label: 'Central' },
13
+ { id: 'America/Los_Angeles', label: 'Pacific' },
14
+ { id: 'UTC', label: 'UTC' },
15
+ ];
16
+ /** The text font; Manrope is the suite's own, the rest are for reading comfort. */
17
+ export const FONTS = [
18
+ { id: 'manrope', label: 'Manrope' },
19
+ { id: 'inter', label: 'Inter' },
20
+ { id: 'plex', label: 'IBM Plex Sans' },
21
+ { id: 'system', label: 'System' },
22
+ ];
23
+ /** Text size: the root size every rem scales from, so spacing grows with the text. */
24
+ export const FONT_SIZES = [
25
+ { id: 'small', label: 'Small', px: 15 },
26
+ { id: 'default', label: 'Default', px: 16 },
27
+ { id: 'large', label: 'Large', px: 17 },
28
+ ];
29
+ /** The assistant panel: never narrower than this, nor wider than this or half the window. */
30
+ export const ASSISTANT_WIDTH = { min: 320, max: 800 };
31
+ /** Eastern by default, as an app's zone picker had it (the market's own zone). */
32
+ export const DEFAULT_PREFS = {
33
+ theme: 'light',
34
+ glass: false,
35
+ font: 'manrope',
36
+ fontSize: 'default',
37
+ timeZone: 'America/New_York',
38
+ hideAmounts: false,
39
+ observability: false,
40
+ };
41
+ const YEAR = 60 * 60 * 24 * 365;
42
+ const listeners = new Set();
43
+ /** The app's shared cookie domain (`configureLumen`); host-only when it names none. */
44
+ function cookieDomain() {
45
+ const domain = KIT.cookieDomain?.(location.hostname);
46
+ return domain ? `; domain=${domain}` : '';
47
+ }
48
+ function read() {
49
+ try {
50
+ const raw = document.cookie
51
+ .split('; ')
52
+ .find((c) => c.startsWith(`${KIT.prefsCookie}=`))
53
+ ?.slice(KIT.prefsCookie.length + 1);
54
+ if (!raw)
55
+ return DEFAULT_PREFS;
56
+ const v = JSON.parse(decodeURIComponent(raw));
57
+ return {
58
+ theme: THEMES.includes(v.theme) ? v.theme : DEFAULT_PREFS.theme,
59
+ glass: v.glass === true,
60
+ font: FONTS.find((f) => f.id === v.font)?.id ?? DEFAULT_PREFS.font,
61
+ fontSize: FONT_SIZES.find((f) => f.id === v.fontSize)?.id ?? DEFAULT_PREFS.fontSize,
62
+ timeZone: TIME_ZONES.find((z) => z.id === v.timeZone)?.id ?? DEFAULT_PREFS.timeZone,
63
+ hideAmounts: v.hideAmounts === true,
64
+ observability: v.observability === true,
65
+ };
66
+ }
67
+ catch {
68
+ return DEFAULT_PREFS;
69
+ }
70
+ }
71
+ let state = typeof document === 'undefined' ? DEFAULT_PREFS : read();
72
+ const systemDark = () => typeof matchMedia !== 'undefined' && matchMedia('(prefers-color-scheme: dark)').matches;
73
+ /** The theme on screen: `system` follows the device. */
74
+ export const resolvedTheme = (p = state) => p.theme === 'system' ? (systemDark() ? 'dark' : 'light') : p.theme;
75
+ function apply() {
76
+ const el = document.documentElement;
77
+ el.classList.toggle('dark', resolvedTheme() === 'dark');
78
+ el.dataset.glass = state.glass ? 'on' : 'off';
79
+ el.dataset.font = state.font;
80
+ el.dataset.fontSize = state.fontSize;
81
+ }
82
+ if (typeof document !== 'undefined') {
83
+ apply();
84
+ matchMedia?.('(prefers-color-scheme: dark)').addEventListener('change', () => {
85
+ if (state.theme !== 'system')
86
+ return;
87
+ apply();
88
+ for (const l of listeners)
89
+ l();
90
+ });
91
+ // The app named its own cookie: read the person's preferences from it.
92
+ onConfigure(() => {
93
+ state = read();
94
+ apply();
95
+ for (const l of listeners)
96
+ l();
97
+ });
98
+ // Another app (or tab) changed a preference: pick it up when this tab comes back.
99
+ document.addEventListener('visibilitychange', () => {
100
+ if (document.visibilityState !== 'visible')
101
+ return;
102
+ const next = read();
103
+ if (JSON.stringify(next) === JSON.stringify(state))
104
+ return;
105
+ state = next;
106
+ apply();
107
+ for (const l of listeners)
108
+ l();
109
+ });
110
+ }
111
+ export function setPref(key, value) {
112
+ state = { ...state, [key]: value };
113
+ document.cookie = `${KIT.prefsCookie}=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${YEAR}; samesite=lax; secure${cookieDomain()}`;
114
+ apply();
115
+ for (const l of listeners)
116
+ l();
117
+ }
118
+ export const getPrefs = () => state;
119
+ export function usePrefs() {
120
+ return useSyncExternalStore((cb) => {
121
+ listeners.add(cb);
122
+ return () => listeners.delete(cb);
123
+ }, getPrefs, () => DEFAULT_PREFS);
124
+ }
125
+ /** `Pacific`, `UTC`: the short name shown next to times. */
126
+ export const timeZoneLabel = (id) => TIME_ZONES.find((z) => z.id === id)?.label ?? id;
@@ -0,0 +1,7 @@
1
+ import { QueryClient, type QueryClientConfig } from '@tanstack/react-query';
2
+ type Retry = NonNullable<NonNullable<QueryClientConfig['defaultOptions']>['queries']>['retry'];
3
+ /** The app's one QueryClient; `retry` is the app's own rule for failed requests. */
4
+ export declare function createDeskQueryClient({ retry }?: {
5
+ retry?: Retry;
6
+ }): QueryClient;
7
+ export {};
@@ -0,0 +1,19 @@
1
+ // One data cache rule for every app (switching tabs must not fetch again).
2
+ // A page's queries stay fresh for 5 minutes and stay in memory for 30, so a tab or page opened
3
+ // again shows what it had without a request. Data changes reach the cache another way: a save
4
+ // invalidates its keys, a running job polls (`refetchInterval`) until it ends, an ended Task
5
+ // invalidates everything (tasks.tsx), and the header's Refresh refetches the page.
6
+ import { QueryClient } from '@tanstack/react-query';
7
+ /** The app's one QueryClient; `retry` is the app's own rule for failed requests. */
8
+ export function createDeskQueryClient({ retry } = {}) {
9
+ return new QueryClient({
10
+ defaultOptions: {
11
+ queries: {
12
+ retry: retry ?? 1,
13
+ refetchOnWindowFocus: false,
14
+ staleTime: 5 * 60_000,
15
+ gcTime: 30 * 60_000,
16
+ },
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,4 @@
1
+ /** Headers an app's API client adds to each request; empty unless a refresh is running. */
2
+ export declare function refreshHeaders(): Record<string, string>;
3
+ /** Runs `reload` with a fresh token set, so the requests it makes skip the server cache. */
4
+ export declare function withRefreshToken(reload: () => Promise<unknown>): Promise<void>;
@@ -0,0 +1,19 @@
1
+ // The header's Refresh: reloads the page's data and asks the app's server to skip
2
+ // its cache. While a refresh runs, `refreshHeaders()` carries one token per click; an app's API client
3
+ // adds it to every request and the server drops its cached reads once per token.
4
+ import { REFRESH_HEADER } from '@columenlabs/lumen-ui-types';
5
+ let token = null;
6
+ /** Headers an app's API client adds to each request; empty unless a refresh is running. */
7
+ export function refreshHeaders() {
8
+ return token ? { [REFRESH_HEADER]: token } : {};
9
+ }
10
+ /** Runs `reload` with a fresh token set, so the requests it makes skip the server cache. */
11
+ export async function withRefreshToken(reload) {
12
+ token = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13
+ try {
14
+ await reload();
15
+ }
16
+ finally {
17
+ token = null;
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ export interface RenameFieldProps {
2
+ /** The name shown now: the person's own, or `original`. */
3
+ value: string;
4
+ /** The source's own name (the broker's, the bank's), what Reset goes back to. */
5
+ original: string;
6
+ /** Where `original` comes from, for the tooltip: "At the broker", "At the bank". */
7
+ originLabel: string;
8
+ /** null resets to `original`. */
9
+ onSave: (name: string | null) => void;
10
+ }
11
+ export declare function RenameField({ value, original, originLabel, onSave }: RenameFieldProps): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // A name renamed in place (for a broker link accounts; shared so
3
+ // An app's bank accounts rename the same way): Enter or leaving the field saves, Escape undoes,
4
+ // empty or ↺ goes back to the source's own name, shown as the placeholder.
5
+ import { PencilIcon, RotateCcwIcon } from 'lucide-react';
6
+ import { useState } from 'react';
7
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './ui/input-group.js';
8
+ export function RenameField({ value, original, originLabel, onSave }) {
9
+ const [typed, setTyped] = useState(value);
10
+ const save = () => {
11
+ const name = typed.trim();
12
+ if (name === value)
13
+ return;
14
+ if (!name && value === original)
15
+ return setTyped(value);
16
+ onSave(name || null);
17
+ };
18
+ return (_jsxs(InputGroup, { className: "-ml-2 h-7 w-fit max-w-full border-transparent hover:border-input dark:bg-transparent", children: [_jsx(InputGroupInput, { value: typed, placeholder: original, "aria-label": `Rename ${value}`, title: value === original ? 'Rename' : `${originLabel}: ${original}`, onChange: (e) => setTyped(e.target.value), onBlur: save, onKeyDown: (e) => {
19
+ if (e.key === 'Enter')
20
+ e.currentTarget.blur();
21
+ if (e.key === 'Escape') {
22
+ setTyped(value);
23
+ requestAnimationFrame(() => e.currentTarget?.blur());
24
+ }
25
+ }, className: "field-sizing-content min-w-0 px-2 text-sm" }), _jsxs(InputGroupAddon, { align: "inline-end", children: [_jsx(PencilIcon, { className: "size-3" }), value !== original && (_jsx(InputGroupButton, { size: "icon-xs", "aria-label": `Reset to ${original}`, title: `Reset to ${original}`, onClick: () => onSave(null), children: _jsx(RotateCcwIcon, {}) }))] })] }));
26
+ }
@@ -0,0 +1,9 @@
1
+ export interface ResetButtonProps {
2
+ /** Whether anything differs from the default; nothing shows when it doesn't. */
3
+ changed: boolean;
4
+ /** The tooltip and accessible name: "Reset to 15 – 45", "Reset all". */
5
+ label: string;
6
+ onReset: () => void;
7
+ size?: 'icon-xs' | 'icon-sm';
8
+ }
9
+ export declare function ResetButton({ changed, label, onReset, size }: ResetButtonProps): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Puts a setting back to its default: a ↺ that shows only when something
3
+ // differs from the default, its tooltip naming what it goes back to.
4
+ import { RotateCcwIcon } from 'lucide-react';
5
+ import { IconButton } from './icon-button.js';
6
+ export function ResetButton({ changed, label, onReset, size = 'icon-xs' }) {
7
+ if (!changed)
8
+ return null;
9
+ return _jsx(IconButton, { icon: RotateCcwIcon, label: label, size: size, onClick: onReset });
10
+ }
@@ -0,0 +1,42 @@
1
+ import { type ErrorComponentProps } from '@tanstack/react-router';
2
+ /** A dynamic import that failed to load (Chrome, Firefox, Safari wording), not a page's own bug. */
3
+ export declare function isStaleBuildError(error: unknown): boolean;
4
+ /**
5
+ * Reloads onto the new build unless it already did in the last 10 s (a file that is truly missing
6
+ * then shows its error instead of reloading forever); says whether a reload is under way.
7
+ */
8
+ export declare function reloadForNewBuild(): boolean;
9
+ /** The bit of a TanStack router this needs: each route's component, lazy ones with `preload`. */
10
+ type LazyRoutes = {
11
+ routesById: Record<string, {
12
+ options: {
13
+ component?: unknown;
14
+ };
15
+ }>;
16
+ };
17
+ /**
18
+ * Keeps an open tab working across deploys: every page's files load up front (when a router is
19
+ * given), and a file a deploy removed reloads the tab onto the new build. The event is never
20
+ * cancelled: cancelling it makes the import resolve to nothing, which the page then hits as a bug.
21
+ */
22
+ export declare function watchNewBuilds(router?: LazyRoutes): void;
23
+ /**
24
+ * Sends a page error to the Worker's log; never throws, never retries. `where` names the card or
25
+ * panel that caught it (LumenBoundary); left out, the whole page did.
26
+ */
27
+ export declare function reportError(error: unknown, outcome: 'reload' | 'retry' | 'shown', where?: string): void;
28
+ /**
29
+ * One centred message with its buttons, the same on every app: the whole page's (RouteError) or
30
+ * one card's or panel's (LumenBoundary, `what` "card" or "panel", smaller).
31
+ */
32
+ export declare function ErrorPage({ error, onRetry, what, }: {
33
+ error: unknown;
34
+ onRetry: () => void;
35
+ what?: 'page' | 'card' | 'panel';
36
+ }): import("react").JSX.Element;
37
+ /**
38
+ * The router's error page: "Updating" while the tab reloads onto a new build; any other error is
39
+ * tried once more on its own, then shown with Try again and Reload. Each one is logged.
40
+ */
41
+ export declare function RouteError({ error, reset }: ErrorComponentProps): import("react").JSX.Element;
42
+ export {};