@columenlabs/lumen-ui 0.1.26 → 0.1.28

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.
@@ -37,7 +37,8 @@ export interface CoverageData {
37
37
  current?: string;
38
38
  }
39
39
  export type CoverageBlock = 'day' | 'week' | 'month' | 'quarter' | 'year';
40
- export type CoverageSummary = 'complete' | 'first' | 'last' | 'rows';
40
+ /** `rows`: what is in hand; `counts`: in hand / expected (`9,212 / 9,712`). */
41
+ export type CoverageSummary = 'complete' | 'first' | 'last' | 'rows' | 'counts';
41
42
  /** A backfill pick: each row with the months its picked blocks cover. */
42
43
  export type CoveragePick = {
43
44
  row: string;
@@ -139,13 +139,17 @@ const SUMMARY_HEAD = {
139
139
  first: 'First',
140
140
  last: 'Last',
141
141
  rows: '',
142
+ counts: '',
142
143
  };
143
144
  const SUMMARY_WIDTH = {
144
145
  complete: '5rem',
145
146
  first: '6.5rem',
146
147
  last: '6.5rem',
147
148
  rows: '5rem',
149
+ counts: '9rem',
148
150
  };
151
+ /** In hand / expected; a row with nothing expected shows only what it has. */
152
+ const fmtCounts = (have, expected) => expected > 0 ? `${fmtInt(have)} / ${fmtInt(expected)}` : fmtInt(have);
149
153
  /** A grid-shaped skeleton: label column plus empty cells, so nothing jumps when data lands. */
150
154
  function GridSkeleton({ rows = 8 }) {
151
155
  return (_jsx("div", { "aria-busy": "true", "aria-label": "Loading", className: "flex flex-col gap-[5px] py-1", children: Array.from({ length: rows }, (_, i) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-16 shrink-0" }), _jsx(Skeleton, { className: "h-[18px] flex-1" })] }, i))) }));
@@ -192,7 +196,9 @@ const MapRow = memo(function MapRow({ r, cols, marks, picks, total, look, }) {
192
196
  ? (r.first ?? '—')
193
197
  : s === 'last'
194
198
  ? (r.last ?? '—')
195
- : fmtInt(total.have) }, s)))] }));
199
+ : s === 'counts'
200
+ ? fmtCounts(total.have, total.expected)
201
+ : fmtInt(total.have) }, s)))] }));
196
202
  }, (a, b) => a.sig === b.sig && sameNode(a.r.badge, b.r.badge));
197
203
  export function LumenCoverageMap({ source, title, icon = Grid3x3Icon, rows: rowSpec, layout = 'grid', block: initialBlock = 'month', zoom, window = 'all', unit = 'sessions', summary = ['complete', 'last'], select, selectable = () => true, onQueue, drilled = null, onDrill, groupBy = false, hint, pageSize: initialPageSize, pager, tools, filter, filterCount, empty, lazy = false, }) {
198
204
  const { data, load: raw, ref } = useLumenSource(source, { lazy });
@@ -319,7 +325,7 @@ export function LumenCoverageMap({ source, title, icon = Grid3x3Icon, rows: rowS
319
325
  const grid = {
320
326
  gridTemplateColumns: `${mobile ? '2.75rem' : '4rem'} repeat(12, minmax(${mobile ? '14px' : '18px'}, 1fr)) ${sumCols}`,
321
327
  };
322
- body = (_jsxs("div", { className: "grid items-center gap-[3px]", style: grid, onClick: onCellClick, children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Year" }), MON.map((m, i) => (_jsx("span", { className: "text-xs text-muted-foreground", children: mobile ? (i % 3 === 0 ? m : '') : m }, m))), sums.map((s) => (_jsx("span", { className: "pl-3 text-right text-xs text-muted-foreground", children: s === 'rows' ? rowsHead : SUMMARY_HEAD[s] }, s))), years.map((y) => {
328
+ body = (_jsxs("div", { className: "grid items-center gap-[3px]", style: grid, onClick: onCellClick, children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Year" }), MON.map((m, i) => (_jsx("span", { className: "text-xs text-muted-foreground", children: mobile ? (i % 3 === 0 ? m : '') : m }, m))), sums.map((s) => (_jsx("span", { className: "pl-3 text-right text-xs text-muted-foreground", children: s === 'rows' || s === 'counts' ? rowsHead : SUMMARY_HEAD[s] }, s))), years.map((y) => {
323
329
  let have = 0;
324
330
  let expected = 0;
325
331
  return [
@@ -334,7 +340,13 @@ export function LumenCoverageMap({ source, title, icon = Grid3x3Icon, rows: rowS
334
340
  expected += c.expected;
335
341
  return (_jsx(CoverageCell, { level: c.level, label: `${row} · ${blockName(key)}`, tip: `${row}|${key}`, picked: drilled === key, pickable: select === 'drill', disabled: select !== 'drill', className: mobile ? 'h-4' : 'h-5' }, key));
336
342
  }),
337
- ...sums.map((s) => (_jsx("span", { className: cn('num pl-3 text-right text-xs', s !== 'complete' && 'text-muted-foreground'), children: s === 'complete' ? fmtPct(have, expected) : s === 'rows' ? fmtInt(have) : '' }, `${y}-${s}`))),
343
+ ...sums.map((s) => (_jsx("span", { className: cn('num pl-3 text-right text-xs', s !== 'complete' && 'text-muted-foreground'), children: s === 'complete'
344
+ ? fmtPct(have, expected)
345
+ : s === 'rows'
346
+ ? fmtInt(have)
347
+ : s === 'counts'
348
+ ? fmtCounts(have, expected)
349
+ : '' }, `${y}-${s}`))),
338
350
  ];
339
351
  })] }));
340
352
  }
@@ -363,7 +375,7 @@ export function LumenCoverageMap({ source, title, icon = Grid3x3Icon, rows: rowS
363
375
  }
364
376
  const look = { mobile, groupBy, select, sums, drilled, link: rowSpec.link };
365
377
  let group;
366
- body = (_jsxs("div", { className: "grid min-w-min items-center gap-[3px]", style: grid, onClick: onCellClick, children: [_jsx("span", { className: "sticky left-0 z-1 bg-card text-xs text-muted-foreground", children: mobile ? '' : rowSpec.title }), cols.map((k) => (_jsx("span", { className: "num h-4 overflow-visible text-[11px] whitespace-nowrap text-muted-foreground", children: headLabel(k, block, cols.length) }, k))), sums.map((s) => (_jsx("span", { className: "pl-3 text-right text-xs text-muted-foreground", children: s === 'rows' ? rowsHead : SUMMARY_HEAD[s] }, s))), rowList.flatMap((r) => {
378
+ body = (_jsxs("div", { className: "grid min-w-min items-center gap-[3px]", style: grid, onClick: onCellClick, children: [_jsx("span", { className: "sticky left-0 z-1 bg-card text-xs text-muted-foreground", children: mobile ? '' : rowSpec.title }), cols.map((k) => (_jsx("span", { className: "num h-4 overflow-visible text-[11px] whitespace-nowrap text-muted-foreground", children: headLabel(k, block, cols.length) }, k))), sums.map((s) => (_jsx("span", { className: "pl-3 text-right text-xs text-muted-foreground", children: s === 'rows' || s === 'counts' ? rowsHead : SUMMARY_HEAD[s] }, s))), rowList.flatMap((r) => {
367
379
  const out = [];
368
380
  const shut = groupBy && folds.has(foldKey + (r.group ?? ''));
369
381
  if (groupBy && r.group !== group) {
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export { type ChartBand, type ChartMarker, GrowthChart, KEY_DATE_KINDS, KeyDateC
35
35
  export { FoldGroup, NavGroup, type NavItem, type SectionItem, type SectionSubItem, SidebarSection, } from './nav.js';
36
36
  export { type OverviewPageProps, type OverviewStage, type OverviewTone, type OverviewWidget, OverviewList, OverviewPage, ToneDot, } from './overview.js';
37
37
  export { type Crumb, IconButton, type IconButtonProps, Page, PageBody, PageHeader, type PageHeaderProps, } from './page.js';
38
- export { DEFAULT_PREFS, type Prefs, TIME_ZONES, type Theme, type TimeZoneId, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
38
+ export { DEFAULT_PREFS, deviceTimeZone, type Prefs, TIME_ZONES, type Theme, type TimeZoneId, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
39
39
  export { KIT, type KitConfig, configureLumen } from './kit-config.js';
40
40
  export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, type KeyTestResult, TestBadge, errorText, useClock, } from './integrations.js';
41
41
  export { LumenPager } from './pager.js';
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ export { GrowthChart, KEY_DATE_KINDS, KeyDateChips, PALETTE, benchColour, keyDat
42
42
  export { FoldGroup, NavGroup, SidebarSection, } from './nav.js';
43
43
  export { OverviewList, OverviewPage, ToneDot, } from './overview.js';
44
44
  export { IconButton, Page, PageBody, PageHeader, } from './page.js';
45
- export { DEFAULT_PREFS, TIME_ZONES, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
45
+ export { DEFAULT_PREFS, deviceTimeZone, TIME_ZONES, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
46
46
  export { KIT, configureLumen } from './kit-config.js';
47
47
  export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, TestBadge, errorText, useClock, } from './integrations.js';
48
48
  export { LumenPager } from './pager.js';
@@ -1,3 +1,4 @@
1
+ import type { Prefs } from './prefs.js';
1
2
  export interface KitConfig {
2
3
  /** The cookie that keeps each person's display preferences. */
3
4
  prefsCookie: string;
@@ -5,6 +6,8 @@ export interface KitConfig {
5
6
  cookieDomain?: (host: string) => string | undefined;
6
7
  /** What a market widget says where the app has no live feed. */
7
8
  marketOff: string;
9
+ /** The app's own defaults for preferences a person has not set (theme, time zone, ...). */
10
+ defaultPrefs?: Partial<Prefs>;
8
11
  }
9
12
  export declare const KIT: KitConfig;
10
13
  /** Runs `fn` after each `configureLumen` (preferences read again from the app's cookie). */
package/dist/prefs.d.ts CHANGED
@@ -62,6 +62,8 @@ export declare const ASSISTANT_WIDTH: {
62
62
  };
63
63
  /** Eastern by default, as an app's zone picker had it (the market's own zone). */
64
64
  export declare const DEFAULT_PREFS: Prefs;
65
+ /** The device's own zone when the picker lists it (`America/Los_Angeles`), else undefined. */
66
+ export declare function deviceTimeZone(): TimeZoneId | undefined;
65
67
  /** The theme on screen: `system` follows the device. */
66
68
  export declare const resolvedTheme: (p?: Prefs) => 'light' | 'dark';
67
69
  export declare function setPref<K extends keyof Prefs>(key: K, value: Prefs[K]): void;
package/dist/prefs.js CHANGED
@@ -38,6 +38,13 @@ export const DEFAULT_PREFS = {
38
38
  hideAmounts: false,
39
39
  observability: false,
40
40
  };
41
+ /** The kit's defaults with the app's own (`configureLumen` `defaultPrefs`) over them. */
42
+ const defaults = () => ({ ...DEFAULT_PREFS, ...KIT.defaultPrefs });
43
+ /** The device's own zone when the picker lists it (`America/Los_Angeles`), else undefined. */
44
+ export function deviceTimeZone() {
45
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
46
+ return TIME_ZONES.find((z) => z.id === zone)?.id;
47
+ }
41
48
  const YEAR = 60 * 60 * 24 * 365;
42
49
  const listeners = new Set();
43
50
  /** The app's shared cookie domain (`configureLumen`); host-only when it names none. */
@@ -46,26 +53,27 @@ function cookieDomain() {
46
53
  return domain ? `; domain=${domain}` : '';
47
54
  }
48
55
  function read() {
56
+ const d = defaults();
49
57
  try {
50
58
  const raw = document.cookie
51
59
  .split('; ')
52
60
  .find((c) => c.startsWith(`${KIT.prefsCookie}=`))
53
61
  ?.slice(KIT.prefsCookie.length + 1);
54
62
  if (!raw)
55
- return DEFAULT_PREFS;
63
+ return d;
56
64
  const v = JSON.parse(decodeURIComponent(raw));
57
65
  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,
66
+ theme: THEMES.includes(v.theme) ? v.theme : d.theme,
67
+ glass: typeof v.glass === 'boolean' ? v.glass : d.glass,
68
+ font: FONTS.find((f) => f.id === v.font)?.id ?? d.font,
69
+ fontSize: FONT_SIZES.find((f) => f.id === v.fontSize)?.id ?? d.fontSize,
70
+ timeZone: TIME_ZONES.find((z) => z.id === v.timeZone)?.id ?? d.timeZone,
71
+ hideAmounts: typeof v.hideAmounts === 'boolean' ? v.hideAmounts : d.hideAmounts,
72
+ observability: typeof v.observability === 'boolean' ? v.observability : d.observability,
65
73
  };
66
74
  }
67
75
  catch {
68
- return DEFAULT_PREFS;
76
+ return d;
69
77
  }
70
78
  }
71
79
  let state = typeof document === 'undefined' ? DEFAULT_PREFS : read();
package/dist/schedules.js CHANGED
@@ -11,7 +11,7 @@ import { LumenConfirm } from './confirm.js';
11
11
  import { useIsMobile } from './hooks/use-mobile.js';
12
12
  import { LumenSelector } from './lumen-selector.js';
13
13
  import { IconButton } from './page.js';
14
- import { usePrefs } from './prefs.js';
14
+ import { timeZoneLabel, usePrefs } from './prefs.js';
15
15
  import { useShell } from './shell.js';
16
16
  import { Badge } from './ui/badge.js';
17
17
  import { Button } from './ui/button.js';
@@ -183,10 +183,10 @@ export function SchedulesSection({ schedules, error, onSave, onRun, onRuns, rece
183
183
  return _jsx("p", { className: "text-sm text-loss", children: error });
184
184
  if (!schedules)
185
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) => {
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: "min-w-48 whitespace-normal 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
187
  e.stopPropagation();
188
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) => {
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: ["Last runs shown in ", timeZoneLabel(timeZone), ".", onRuns ? ' 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
190
  const id = await briefs.create(b);
191
191
  await onSave(id, time);
192
192
  return id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "@codemirror/lang-javascript": "6.2.5",
42
42
  "@codemirror/lang-json": "6.0.2",
43
43
  "@codemirror/lang-sql": "6.10.0",
44
- "@columenlabs/lumen-ui-types": "0.1.26",
44
+ "@columenlabs/lumen-ui-types": "0.1.28",
45
45
  "@dnd-kit/core": "6.3.1",
46
46
  "@dnd-kit/sortable": "10.0.0",
47
47
  "@dnd-kit/utilities": "3.2.2",