@columenlabs/lumen-ui 0.1.86 → 0.1.87
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.
- package/dist/assistant/lumen-assistant.js +1 -1
- package/dist/expand-list.js +1 -1
- package/dist/fact-list.d.ts +19 -0
- package/dist/fact-list.js +13 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lumen-drawer.js +1 -1
- package/dist/people/person-drawer.js +16 -4
- package/dist/people/sections.js +2 -2
- package/dist/people/shared.js +7 -5
- package/dist/schedules.js +1 -1
- package/dist/theme.css +10 -0
- package/package.json +2 -2
|
@@ -302,7 +302,7 @@ function Draft({ draft, onSettle, busy, row, }) {
|
|
|
302
302
|
const [open, setOpen] = useState(!row && draft.status === 'open');
|
|
303
303
|
const lines = argLines(draft.args);
|
|
304
304
|
const actions = draft.status === 'open' && (_jsxs("div", { className: "flex shrink-0 justify-end gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: !!pending || busy, onClick: () => void go(draft, 'dismiss'), children: "Dismiss" }), _jsxs(Button, { size: "sm", disabled: !!pending || busy, onClick: () => void go(draft, 'save'), children: [pending === 'save' ? _jsx(Spinner, {}) : _jsx(CheckIcon, {}), "Save"] })] }));
|
|
305
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "assistant-draft", className: cn('flex flex-col text-sm', row ? 'border-b px-1 py-1.5 last:border-b-0' : 'gap-2 rounded-lg border bg-card p-3'), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": `${open ? 'Hide' : 'Show'} ${draft.title}`, className: "flex min-w-0 flex-1 items-center gap-1.5 text-left", children: [_jsx(ChevronRightIcon, { className: cn('size-3.5 shrink-0 text-muted-foreground transition-transform', open && 'rotate-90') }), _jsx("span", { className: "shrink-0 font-medium", children: draft.title }), !open && (_jsx("span", { className: "min-w-0 truncate text-muted-foreground text-xs", children: argSummary(draft.args) }))] }) }), _jsx(DraftStatus, { draft: draft }), row && draft.status === 'open' && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: XIcon, label: `Dismiss ${draft.title}`, size: "icon-xs", disabled: !!pending || busy, onClick: () => void go(draft, 'dismiss') }), _jsx(IconButton, { icon: pending === 'save' ? Spinner : CheckIcon, label: `Save ${draft.title}`, size: "icon-xs", variant: "outline", disabled: !!pending || busy, onClick: () => void go(draft, 'save') })] }))] }), _jsxs(CollapsibleContent, { className: cn('flex flex-col gap-2', row && 'pt-1.5 pl-5'), children: [_jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs", children: lines.slice(0, 12).map(([k, v]) => (_jsxs("div", { className: "contents", children: [_jsx("dt", { className: "text-muted-foreground", children: k }), _jsx("dd", { className: "font-code break-words", children: v })] }, k))) }), !row && actions] }), draft.error && _jsx("p", { className: "text-loss text-xs", children: draft.error })] }));
|
|
305
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "assistant-draft", className: cn('flex flex-col text-sm', row ? 'border-b px-1 py-1.5 last:border-b-0' : 'gap-2 rounded-lg border bg-card p-3'), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": `${open ? 'Hide' : 'Show'} ${draft.title}`, className: "flex min-w-0 flex-1 items-center gap-1.5 text-left", children: [_jsx(ChevronRightIcon, { className: cn('size-3.5 shrink-0 text-muted-foreground transition-transform', open && 'rotate-90') }), _jsx("span", { className: "shrink-0 font-medium", children: draft.title }), !open && (_jsx("span", { className: "min-w-0 truncate text-muted-foreground text-xs", children: argSummary(draft.args) }))] }) }), _jsx(DraftStatus, { draft: draft }), row && draft.status === 'open' && (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: XIcon, label: `Dismiss ${draft.title}`, size: "icon-xs", disabled: !!pending || busy, onClick: () => void go(draft, 'dismiss') }), _jsx(IconButton, { icon: pending === 'save' ? Spinner : CheckIcon, label: `Save ${draft.title}`, size: "icon-xs", variant: "outline", disabled: !!pending || busy, onClick: () => void go(draft, 'save') })] }))] }), _jsxs(CollapsibleContent, { className: cn('flex flex-col gap-2', row && 'pt-1.5 pl-5'), children: [_jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs", children: lines.slice(0, 12).map(([k, v]) => (_jsxs("div", { className: "contents", children: [_jsx("dt", { className: "whitespace-nowrap text-muted-foreground", children: k }), _jsx("dd", { className: "font-code break-words", children: v })] }, k))) }), !row && actions] }), draft.error && _jsx("p", { className: "text-loss text-xs", children: draft.error })] }));
|
|
306
306
|
}
|
|
307
307
|
/** Two or more cards in a row: one stack with a count, Save all and Dismiss all, a line each. */
|
|
308
308
|
function DraftStack({ drafts, onSettle, busy, }) {
|
package/dist/expand-list.js
CHANGED
|
@@ -29,5 +29,5 @@ export function LumenExpandRow({ id, name, meta, badge, open: openProp, onOpenCh
|
|
|
29
29
|
else
|
|
30
30
|
setOwn(o);
|
|
31
31
|
};
|
|
32
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: change, className: "border-b last:border-b-0", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex w-full items-center gap-3 px-1 py-3 text-left hover:bg-muted/50", children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block font-medium", children: name }), meta && _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: meta })] }), badge, _jsx(ChevronRightIcon, { className: cn('size-4 shrink-0 text-muted-foreground transition-transform', open && 'rotate-90') })] }) }), _jsx(CollapsibleContent, { className: "grid grid-cols-1 gap-5 px-1 pt-1 pb-5", children: children })] }));
|
|
32
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: change, className: "border-b last:border-b-0", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex w-full items-center gap-3 px-1 py-3 text-left hover:bg-muted/50", children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", title: typeof name === 'string' ? name : undefined, children: name }), meta && (_jsx("span", { className: "block truncate text-xs text-muted-foreground", title: typeof meta === 'string' ? meta : undefined, children: meta }))] }), badge, _jsx(ChevronRightIcon, { className: cn('size-4 shrink-0 text-muted-foreground transition-transform', open && 'rotate-90') })] }) }), _jsx(CollapsibleContent, { className: "grid grid-cols-1 gap-5 px-1 pt-1 pb-5", children: children })] }));
|
|
33
33
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface Fact {
|
|
3
|
+
label: string;
|
|
4
|
+
value: ReactNode;
|
|
5
|
+
/** The full value on hover, when the value is not plain text. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** A sentence that may take more lines ("What it inspects"); short values stay on one. */
|
|
8
|
+
wrap?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface FactListProps {
|
|
11
|
+
facts: readonly (Fact | false | null | undefined)[];
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Label and value pairs in a drawer or card ("Last seen today 07:18 PM"). The labels take the
|
|
16
|
+
* width of the longest one and the values the rest. Each sits on one line: a value that runs out
|
|
17
|
+
* of room ends in an ellipsis, the whole of it on hover, so a time or an email never breaks.
|
|
18
|
+
*/
|
|
19
|
+
export declare function FactList({ facts, className }: FactListProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from 'react';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Label and value pairs in a drawer or card ("Last seen today 07:18 PM"). The labels take the
|
|
6
|
+
* width of the longest one and the values the rest. Each sits on one line: a value that runs out
|
|
7
|
+
* of room ends in an ellipsis, the whole of it on hover, so a time or an email never breaks.
|
|
8
|
+
*/
|
|
9
|
+
export function FactList({ facts, className }) {
|
|
10
|
+
return (_jsx("dl", { className: cn('grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-sm', className), children: facts.map((f) => f && (_jsxs(Fragment, { children: [_jsx("dt", { className: "whitespace-nowrap text-muted-foreground", children: f.label }), _jsx("dd", { className: cn('min-w-0', f.wrap ? 'break-words' : 'truncate'), title: f.wrap
|
|
11
|
+
? undefined
|
|
12
|
+
: (f.title ?? (typeof f.value === 'string' ? f.value : undefined)), children: f.value })] }, f.label))) }));
|
|
13
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export { KIT, type KitConfig, configureLumen } from './kit-config.js';
|
|
|
51
51
|
export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, type KeyTestResult, TestBadge, errorText, useClock, } from './integrations.js';
|
|
52
52
|
export { LumenChatgptSignin, type LumenChatgptSigninProps } from './chatgpt-signin.js';
|
|
53
53
|
export { LumenExpandList, LumenExpandRow } from './expand-list.js';
|
|
54
|
+
export { type Fact, FactList, type FactListProps } from './fact-list.js';
|
|
54
55
|
export { LumenPager } from './pager.js';
|
|
55
56
|
export { createDeskQueryClient } from './query-client.js';
|
|
56
57
|
export { type ClientPoint, type ClientPointKind, type LumenTelemetryOptions, apiRoute, reportClient, reportClientError, reportFailedFetch, stackFingerprint, useLumenTelemetry, } from './telemetry.js';
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,7 @@ export { KIT, configureLumen } from './kit-config.js';
|
|
|
58
58
|
export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, TestBadge, errorText, useClock, } from './integrations.js';
|
|
59
59
|
export { LumenChatgptSignin } from './chatgpt-signin.js';
|
|
60
60
|
export { LumenExpandList, LumenExpandRow } from './expand-list.js';
|
|
61
|
+
export { FactList } from './fact-list.js';
|
|
61
62
|
export { LumenPager } from './pager.js';
|
|
62
63
|
export { createDeskQueryClient } from './query-client.js';
|
|
63
64
|
export { apiRoute, reportClient, reportClientError, reportFailedFetch, stackFingerprint, useLumenTelemetry, } from './telemetry.js';
|
package/dist/lumen-drawer.js
CHANGED
|
@@ -16,7 +16,7 @@ import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTi
|
|
|
16
16
|
*/
|
|
17
17
|
export function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', }) {
|
|
18
18
|
const isMobile = useIsMobile();
|
|
19
|
-
const head = (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(SheetTitle, { className: "truncate", children: title }), _jsx(SheetDescription, { className: description ? 'truncate text-muted-foreground text-xs' : 'sr-only', children: description ?? 'Details' })] }));
|
|
19
|
+
const head = (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx(SheetTitle, { className: "truncate", title: typeof title === 'string' ? title : undefined, children: title }), _jsx(SheetDescription, { className: description ? 'truncate text-muted-foreground text-xs' : 'sr-only', title: typeof description === 'string' ? description : undefined, children: description ?? 'Details' })] }));
|
|
20
20
|
const body = _jsx("div", { className: "flex flex-col px-4 pb-4", children: children });
|
|
21
21
|
// Every drawer ends on a footer, styled like a dialog's: the body scrolls above it and always
|
|
22
22
|
// ends on a clear edge. Without actions of its own it offers Close, except on a phone, where the
|
|
@@ -6,6 +6,7 @@ import { AppWindowIcon, BanIcon, HistoryIcon, KeyRoundIcon, MailIcon, ShieldChec
|
|
|
6
6
|
import { useState } from 'react';
|
|
7
7
|
import { LumenConfirm } from '../confirm.js';
|
|
8
8
|
import { DrawerSection } from '../drawer-section.js';
|
|
9
|
+
import { FactList } from '../fact-list.js';
|
|
9
10
|
import { InfoLabel, InfoTip } from '../info-tip.js';
|
|
10
11
|
import { errorText } from '../integrations.js';
|
|
11
12
|
import { LumenDrawer } from '../lumen-drawer.js';
|
|
@@ -35,9 +36,20 @@ export function PersonDrawer({ adapter, view, person, onClose, }) {
|
|
|
35
36
|
void act(() => adapter.setAccess(p.id, next), `${scope.name}: ${levelLabel(scope, level)} for ${who}`, () => adapter.setAccess(p.id, before));
|
|
36
37
|
};
|
|
37
38
|
return (_jsx(LumenDrawer, { open: !!p, onClose: onClose, title: who, description: p ? `${p.email} · ${STATUS_LABEL[p.status]} · invited ${when(p.invitedAt)}` : undefined, footer: p && pending ? (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => act(() => adapter.revoke(p.id), `Revoked the invite for ${p.email}`).then((r) => r && onClose()), children: [_jsx(XIcon, {}), " Revoke"] }), _jsxs(Button, { size: "sm", onClick: () => act(() => adapter.resend(p.id), `New invite sent to ${p.email}`), children: [_jsx(MailIcon, {}), " Resend invite"] })] })) : undefined, children: p && (_jsxs(_Fragment, { children: [_jsx(AccessSection, { adapter: adapter, view: view, person: p, onLevel: setLevel }), _jsxs(DrawerSection, { icon: ShieldCheckIcon, title: "Role", defaultOpen: true, children: [_jsxs("div", { className: "flex items-center justify-between gap-3 py-1 text-sm", children: [_jsx(InfoLabel, { info: "Admins hold the top level on every scope and manage People. One active admin is always kept.", children: "Role" }), _jsx(LumenSelector, { trigger: "chip", size: "sm", label: `Role of ${who}`, disabled: !!guard && p.role === 'admin', value: p.role, options: ROLES, onChange: (role) => role !== p.role &&
|
|
38
|
-
void act(() => adapter.update(p.id, { role }), `${who} is now ${role === 'admin' ? 'an admin' : 'a member'}`, () => adapter.update(p.id, { role: p.role })) })] }), guard && p.role === 'admin' && _jsx(NoteLine, { icon: ShieldCheckIcon, title: guard })] }), _jsx(DrawerSection, { icon: KeyRoundIcon, title: "Sign-in", defaultOpen: true, children:
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
void act(() => adapter.update(p.id, { role }), `${who} is now ${role === 'admin' ? 'an admin' : 'a member'}`, () => adapter.update(p.id, { role: p.role })) })] }), guard && p.role === 'admin' && _jsx(NoteLine, { icon: ShieldCheckIcon, title: guard })] }), _jsx(DrawerSection, { icon: KeyRoundIcon, title: "Sign-in", defaultOpen: true, children: _jsx(FactList, { facts: [
|
|
40
|
+
{ label: 'Last seen', value: when(p.lastSeen) },
|
|
41
|
+
{ label: 'Invited by', value: p.invitedBy ?? '—' },
|
|
42
|
+
pending && {
|
|
43
|
+
label: 'Invite',
|
|
44
|
+
value: p.status === 'expired'
|
|
45
|
+
? `expired ${when(p.expiresAt)}`
|
|
46
|
+
: `expires ${fromNow(p.expiresAt)}`,
|
|
47
|
+
},
|
|
48
|
+
view.sync && {
|
|
49
|
+
label: 'Sign-in provider',
|
|
50
|
+
value: `${view.sync.ok ? 'Synced' : 'Out of step'} · ${when(view.sync.at)}`,
|
|
51
|
+
},
|
|
52
|
+
] }) }), adapter.audit && _jsx(ActivitySection, { adapter: adapter, person: p }), _jsx(DangerSection, { adapter: adapter, person: p, guard: guard, onGone: onClose })] })) }));
|
|
41
53
|
}
|
|
42
54
|
/** Each app's level, then the person's grants on constructs inside it. */
|
|
43
55
|
function AccessSection({ adapter, view, person: p, onLevel, }) {
|
|
@@ -73,7 +85,7 @@ function ActivitySection({ adapter, person }) {
|
|
|
73
85
|
queryFn: () => adapter.audit?.({ target: person.id, limit: 5 }) ?? Promise.resolve(null),
|
|
74
86
|
});
|
|
75
87
|
const events = q.data?.events ?? [];
|
|
76
|
-
return (_jsx(DrawerSection, { icon: HistoryIcon, title: "Recent activity", count: events.length || null, children: q.isError ? (_jsx("p", { className: "text-destructive text-sm", children: errorText(q.error) })) : events.length ? (_jsx("ul", { className: "
|
|
88
|
+
return (_jsx(DrawerSection, { icon: HistoryIcon, title: "Recent activity", count: events.length || null, children: q.isError ? (_jsx("p", { className: "text-destructive text-sm", children: errorText(q.error) })) : events.length ? (_jsx("ul", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1.5 text-sm", children: events.map((e) => (_jsxs("li", { className: "col-span-2 grid grid-cols-subgrid items-center", children: [_jsx("time", { dateTime: new Date(e.at).toISOString(), className: "num text-muted-foreground", children: when(e.at) }), _jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsxs("span", { className: "truncate", title: `${e.actor} · ${e.detail}`, children: [e.actor, " \u00B7 ", e.detail] }), e.refused && _jsx(Badge, { variant: "destructive", children: "Refused" })] })] }, e.id))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: q.isPending ? 'Loading…' : 'Nothing yet.' })) }));
|
|
77
89
|
}
|
|
78
90
|
/** Disable or Enable, and Remove with what each part of the app does with their data first. */
|
|
79
91
|
function DangerSection({ adapter, person: p, guard, onGone, }) {
|
package/dist/people/sections.js
CHANGED
|
@@ -212,7 +212,7 @@ export function PermissionsSection({ frame }) {
|
|
|
212
212
|
title: `Levels for ${scope.name}`,
|
|
213
213
|
icon: ListOrderedIcon,
|
|
214
214
|
link: false,
|
|
215
|
-
children: (_jsxs("ol", { className: "
|
|
215
|
+
children: (_jsxs("ol", { className: "grid grid-cols-[auto_auto_minmax(0,1fr)] gap-x-3 gap-y-2 text-sm", children: [levels.map((l, i) => (_jsxs("li", { className: "col-span-3 grid grid-cols-subgrid", children: [_jsx("span", { className: "num text-muted-foreground", children: i + 1 }), _jsx("span", { className: "whitespace-nowrap font-medium", children: l.label }), _jsx("span", { className: "text-muted-foreground", children: l.description ?? '' })] }, l.id))), parent && (_jsxs("li", { className: "col-span-3 pt-1 text-muted-foreground text-xs", children: ["No grant of its own: the level on ", parent.name, ", by rank."] }))] })),
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
id: 'roles',
|
|
@@ -220,7 +220,7 @@ export function PermissionsSection({ frame }) {
|
|
|
220
220
|
title: 'Roles',
|
|
221
221
|
icon: ShieldCheckIcon,
|
|
222
222
|
link: false,
|
|
223
|
-
children: (_jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm", children: [_jsx("dt", { className: "font-medium", children: "Admin" }), _jsx("dd", { className: "text-muted-foreground", children: "The top level on every scope; manages People. One is always kept." }), _jsx("dt", { className: "font-medium", children: "Member" }), _jsx("dd", { className: "text-muted-foreground", children: "Only the levels set per scope. The default for invites." })] })),
|
|
223
|
+
children: (_jsxs("dl", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm", children: [_jsx("dt", { className: "whitespace-nowrap font-medium", children: "Admin" }), _jsx("dd", { className: "text-muted-foreground", children: "The top level on every scope; manages People. One is always kept." }), _jsx("dt", { className: "whitespace-nowrap font-medium", children: "Member" }), _jsx("dd", { className: "text-muted-foreground", children: "Only the levels set per scope. The default for invites." })] })),
|
|
224
224
|
},
|
|
225
225
|
] }));
|
|
226
226
|
}
|
package/dist/people/shared.js
CHANGED
|
@@ -22,6 +22,8 @@ export const statusTone = (p) => p.status === 'active'
|
|
|
22
22
|
? 'amber'
|
|
23
23
|
: 'muted';
|
|
24
24
|
const DAY = 86_400_000;
|
|
25
|
+
/** Non-breaking spaces, so `today 07:18 PM` and `in 7 d` never split across lines in any text. */
|
|
26
|
+
const glue = (s) => s.replace(/ /g, '\u00a0');
|
|
25
27
|
const sameDay = (a, b) => a.toDateString() === b.toDateString();
|
|
26
28
|
/** `today 09:12`, `yesterday 18:42`, `24 Oct`, `24 Oct 2025`; a dash for never. */
|
|
27
29
|
export function when(ts, now = Date.now()) {
|
|
@@ -31,14 +33,14 @@ export function when(ts, now = Date.now()) {
|
|
|
31
33
|
const n = new Date(now);
|
|
32
34
|
const time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
33
35
|
if (sameDay(d, n))
|
|
34
|
-
return `today ${time}
|
|
36
|
+
return glue(`today ${time}`);
|
|
35
37
|
if (sameDay(d, new Date(now - DAY)))
|
|
36
|
-
return `yesterday ${time}
|
|
37
|
-
return d.toLocaleDateString(undefined, {
|
|
38
|
+
return glue(`yesterday ${time}`);
|
|
39
|
+
return glue(d.toLocaleDateString(undefined, {
|
|
38
40
|
day: 'numeric',
|
|
39
41
|
month: 'short',
|
|
40
42
|
...(d.getFullYear() === n.getFullYear() ? {} : { year: 'numeric' }),
|
|
41
|
-
});
|
|
43
|
+
}));
|
|
42
44
|
}
|
|
43
45
|
/** `in 5 d`, `in 3 h`, `2 d ago`: how far a time is from now. */
|
|
44
46
|
export function fromNow(ts, now = Date.now()) {
|
|
@@ -51,7 +53,7 @@ export function fromNow(ts, now = Date.now()) {
|
|
|
51
53
|
: abs >= 3_600_000
|
|
52
54
|
? `${Math.round(abs / 3_600_000)} h`
|
|
53
55
|
: `${Math.max(1, Math.round(abs / 60_000))} min`;
|
|
54
|
-
return ms >= 0 ? `in ${n}` : `${n} ago
|
|
56
|
+
return glue(ms >= 0 ? `in ${n}` : `${n} ago`);
|
|
55
57
|
}
|
|
56
58
|
/** The top scopes (apps), in order; constructs inside one have a `parent`. */
|
|
57
59
|
export const topScopes = (scopes) => scopes.filter((s) => !s.parent);
|
package/dist/schedules.js
CHANGED
|
@@ -149,7 +149,7 @@ function Editor({ s, onSave, onRun, onRuns, recent, briefs, onClose, }) {
|
|
|
149
149
|
['Cost', s.about.cost],
|
|
150
150
|
]
|
|
151
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: () => {
|
|
152
|
+
.map(([k, v]) => (_jsxs(Fragment, { children: [_jsx("dt", { className: "whitespace-nowrap 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
153
|
assistant.loadChat(last.chat);
|
|
154
154
|
assistant.setOpen(true);
|
|
155
155
|
onClose();
|
package/dist/theme.css
CHANGED
|
@@ -465,6 +465,16 @@
|
|
|
465
465
|
font-family: var(--num-font);
|
|
466
466
|
font-variant-numeric: tabular-nums;
|
|
467
467
|
}
|
|
468
|
+
|
|
469
|
+
/* Short values never wrap: a number, a time, a key. `07:18 PM` stays on one line; where room
|
|
470
|
+
runs out the holder truncates it (`truncate` and a `title`), never breaks it. A `.num` that
|
|
471
|
+
clamps on purpose (`line-clamp-*`) keeps its lines. */
|
|
472
|
+
span.num:not([class*="line-clamp"]),
|
|
473
|
+
dd.num,
|
|
474
|
+
time,
|
|
475
|
+
kbd {
|
|
476
|
+
white-space: nowrap;
|
|
477
|
+
}
|
|
468
478
|
}
|
|
469
479
|
|
|
470
480
|
/* Font and size: the size is the root every rem scales from. Unlayered, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.87",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.87",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|