@columenlabs/lumen-ui 0.1.128 → 0.1.130
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/app-switcher.js +2 -1
- package/dist/fetch-within.d.ts +8 -0
- package/dist/fetch-within.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/market/live.js +3 -2
- package/dist/notifications.js +2 -1
- package/dist/people/lumen-people.js +1 -1
- package/dist/route-error.js +2 -1
- package/dist/tasks.js +3 -2
- package/dist/whats-new.js +2 -1
- package/package.json +2 -2
package/dist/app-switcher.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
8
8
|
import { useQuery } from '@tanstack/react-query';
|
|
9
9
|
import { ArrowUpRightIcon, ChevronsUpDownIcon } from 'lucide-react';
|
|
10
10
|
import { Fragment, useState } from 'react';
|
|
11
|
+
import { fetchWithin } from './fetch-within.js';
|
|
11
12
|
import { cn } from './lib/utils.js';
|
|
12
13
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from './ui/dropdown-menu.js';
|
|
13
14
|
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from './ui/sidebar.js';
|
|
@@ -80,7 +81,7 @@ export function LumenSuiteSwitcher({ source, current, product, menuLabel, }) {
|
|
|
80
81
|
queryKey: ['lumen-suite', source],
|
|
81
82
|
enabled: !!source,
|
|
82
83
|
queryFn: async () => {
|
|
83
|
-
const res = await
|
|
84
|
+
const res = await fetchWithin(source, { credentials: 'include' });
|
|
84
85
|
if (!res.ok)
|
|
85
86
|
throw new Error(`suite list ${res.status}`);
|
|
86
87
|
return res.json();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** How long a call to the app's own server may take before it gives up (ms). */
|
|
2
|
+
export declare const FETCH_MS = 15000;
|
|
3
|
+
/**
|
|
4
|
+
* `fetch` that never waits forever: it gives up after `ms` with a `TimeoutError` (the caller's own
|
|
5
|
+
* `signal`, if any, still aborts it as before), so a hung connection becomes an error state or a
|
|
6
|
+
* fallback instead of a spinner that never ends.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchWithin(url: string, init?: RequestInit, ms?: number): Promise<Response>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** How long a call to the app's own server may take before it gives up (ms). */
|
|
2
|
+
export const FETCH_MS = 15_000;
|
|
3
|
+
/**
|
|
4
|
+
* `fetch` that never waits forever: it gives up after `ms` with a `TimeoutError` (the caller's own
|
|
5
|
+
* `signal`, if any, still aborts it as before), so a hung connection becomes an error state or a
|
|
6
|
+
* fallback instead of a spinner that never ends.
|
|
7
|
+
*/
|
|
8
|
+
export function fetchWithin(url, init = {}, ms = FETCH_MS) {
|
|
9
|
+
const timeout = AbortSignal.timeout(ms);
|
|
10
|
+
const signal = init.signal && typeof AbortSignal.any === 'function'
|
|
11
|
+
? AbortSignal.any([init.signal, timeout])
|
|
12
|
+
: (init.signal ?? timeout);
|
|
13
|
+
return fetch(url, { ...init, signal });
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export { type McpServer, McpSettings } from './mcp-settings.js';
|
|
|
76
76
|
export { lumenToast } from './lumen-toast.js';
|
|
77
77
|
export { LumenShell, type LumenShellProps, type ShellUser, StatusItem, firstName, initials, maskAmount, useAmountsHidden, useApp, useShell, } from './shell.js';
|
|
78
78
|
export { API_TIMEOUT_MS, apiJson } from './api-fetch.js';
|
|
79
|
+
export { FETCH_MS, fetchWithin } from './fetch-within.js';
|
|
79
80
|
export { savedAnswers, type SavedAnswersOptions, type SavedAnswersPersister, } from './saved-answers.js';
|
|
80
81
|
export { cn } from './lib/utils.js';
|
|
81
82
|
export { type HealthProbe, StatusHealth } from './status-health.js';
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,7 @@ export { McpSettings } from './mcp-settings.js';
|
|
|
83
83
|
export { lumenToast } from './lumen-toast.js';
|
|
84
84
|
export { LumenShell, StatusItem, firstName, initials, maskAmount, useAmountsHidden, useApp, useShell, } from './shell.js';
|
|
85
85
|
export { API_TIMEOUT_MS, apiJson } from './api-fetch.js';
|
|
86
|
+
export { FETCH_MS, fetchWithin } from './fetch-within.js';
|
|
86
87
|
export { savedAnswers, } from './saved-answers.js';
|
|
87
88
|
export { cn } from './lib/utils.js';
|
|
88
89
|
export { StatusHealth } from './status-health.js';
|
package/dist/market/live.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { EXTENDED_LABEL, } from '@columenlabs/lumen-ui-types/market';
|
|
12
12
|
import { createContext, createElement, useContext, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
|
|
13
|
+
import { fetchWithin } from '../fetch-within.js';
|
|
13
14
|
const HIDDEN_CLOSE_MS = 30_000;
|
|
14
15
|
const CLOSED_CHECK_MS = 60_000;
|
|
15
16
|
/** Folds changed bars into a series (same minute replaced, new ones appended in order). */
|
|
@@ -63,7 +64,7 @@ class MarketHub {
|
|
|
63
64
|
const k = keys.join(',');
|
|
64
65
|
let p = this.days.get(k);
|
|
65
66
|
if (!p) {
|
|
66
|
-
p =
|
|
67
|
+
p = fetchWithin(`${this.api}/daily?symbols=${encodeURIComponent(k)}`, {
|
|
67
68
|
credentials: 'same-origin',
|
|
68
69
|
}).then(async (r) => {
|
|
69
70
|
if (!r.ok)
|
|
@@ -77,7 +78,7 @@ class MarketHub {
|
|
|
77
78
|
}
|
|
78
79
|
/** Whether the app serves market data at all (a 404 means no feed here), read once. */
|
|
79
80
|
available() {
|
|
80
|
-
this.probe ??=
|
|
81
|
+
this.probe ??= fetchWithin(`${this.api}/session`, { credentials: 'same-origin' })
|
|
81
82
|
.then((r) => r.status !== 404)
|
|
82
83
|
.catch(() => true);
|
|
83
84
|
return this.probe;
|
package/dist/notifications.js
CHANGED
|
@@ -7,6 +7,7 @@ import { useState } from 'react';
|
|
|
7
7
|
// own paid ntfy.sh token) and webhooks, then which event goes to which target. The app passes its
|
|
8
8
|
// API base (`/api/notifications`), its product's name and the address email comes from.
|
|
9
9
|
import { LumenConfirm } from './confirm.js';
|
|
10
|
+
import { fetchWithin } from './fetch-within.js';
|
|
10
11
|
import { ErrorLine, IntegrationRow, KeyTestLine, TestBadge, } from './integrations.js';
|
|
11
12
|
import { IconButton } from './page.js';
|
|
12
13
|
import { Badge } from './ui/badge.js';
|
|
@@ -18,7 +19,7 @@ import { Label } from './ui/label.js';
|
|
|
18
19
|
const NTFY_IOS = 'https://apps.apple.com/us/app/ntfy/id1625396347';
|
|
19
20
|
const NTFY_ANDROID = 'https://play.google.com/store/apps/details?id=io.heckel.ntfy';
|
|
20
21
|
async function call(url, method = 'GET', body) {
|
|
21
|
-
const res = await
|
|
22
|
+
const res = await fetchWithin(url, {
|
|
22
23
|
method,
|
|
23
24
|
headers: body === undefined ? {} : { 'content-type': 'application/json' },
|
|
24
25
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
@@ -168,7 +168,7 @@ function PeopleList({ frame, fields, onInvite, }) {
|
|
|
168
168
|
const active = people.filter((p) => p.status === 'active').length;
|
|
169
169
|
const disabled = people.filter((p) => p.status === 'disabled').length;
|
|
170
170
|
const onlyMe = people.length <= 1;
|
|
171
|
-
return (_jsxs(_Fragment, { children: [_jsx(LumenLayout, { crumbs: frame.crumbs, actions: frame.invite, intro: _jsxs("div", { className: "flex flex-col gap-3", children: [frame.tabs, _jsxs(TileRow, { fill: true, "aria-label": "People at a glance", children: [_jsx(Tile, { label: "People", value: people.length, caption: `${active} active${disabled ? ` · ${disabled} disabled` : ''}` }), _jsx(Tile, { label: "Admins", value: admins.length, caption: "
|
|
171
|
+
return (_jsxs(_Fragment, { children: [_jsx(LumenLayout, { crumbs: frame.crumbs, actions: frame.invite, intro: _jsxs("div", { className: "flex flex-col gap-3", children: [frame.tabs, _jsxs(TileRow, { fill: true, "aria-label": "People at a glance", children: [_jsx(Tile, { label: "People", value: people.length, caption: `${active} active${disabled ? ` · ${disabled} disabled` : ''}` }), _jsx(Tile, { label: "Admins", value: admins.length, caption: "Can\u2019t remove the last admin" }), _jsx(Tile, { label: "Invites pending", value: invited, caption: expired ? `${expired} expired` : 'none expired' }), view.sync ? (_jsx(Tile, { label: "Sign-in sync", value: view.sync.ok ? 'Synced' : 'Out of step', caption: view.sync.at ? when(view.sync.at) : view.sync.detail, valueClassName: view.sync.ok ? undefined : 'text-loss' })) : (_jsx(Tile, { label: "Email", value: view.mail.configured ? 'Working' : 'Not set up', caption: view.mail.from ?? 'links only' }))] }), frame.mailNote, picked.size > 0 && (_jsx(BulkBar, { frame: frame, ids: [...picked], onClear: () => setPicked(new Set()) }))] }), domain: domain, ...layout, rows: people, load: load, state: onlyMe ? _jsx(FirstRun, { mail: view.mail.configured, onInvite: onInvite }) : undefined, selection: { keys: picked, onChange: setPicked }, rowTitle: (p) => p.name || p.email, onOpen: (p) => setOpenId(p.id) }), _jsx(LumenBoundary, { where: "people-person", what: "panel", children: _jsx(PersonDrawer, { adapter: adapter, view: view, person: open, onClose: () => setOpenId(null) }) })] }));
|
|
172
172
|
}
|
|
173
173
|
/** One change over the ticked people. Apply clears the selection; Undo puts the change back. */
|
|
174
174
|
function BulkBar({ frame, ids, onClear, }) {
|
package/dist/route-error.js
CHANGED
|
@@ -16,6 +16,7 @@ import { useRouter } from '@tanstack/react-router';
|
|
|
16
16
|
import { ChevronRight, RefreshCw, RotateCcw, TriangleAlert } from 'lucide-react';
|
|
17
17
|
import { useEffect, useState } from 'react';
|
|
18
18
|
import { toast } from 'sonner';
|
|
19
|
+
import { fetchWithin } from './fetch-within.js';
|
|
19
20
|
import { reportClientError } from './telemetry.js';
|
|
20
21
|
import { Button } from './ui/button.js';
|
|
21
22
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible.js';
|
|
@@ -113,7 +114,7 @@ async function newBuildOut() {
|
|
|
113
114
|
if (!mine)
|
|
114
115
|
return false;
|
|
115
116
|
try {
|
|
116
|
-
const res = await
|
|
117
|
+
const res = await fetchWithin(import.meta.env.BASE_URL ?? '/', { cache: 'no-store' }, 5000);
|
|
117
118
|
if (!res.ok || !res.headers.get('content-type')?.includes('text/html'))
|
|
118
119
|
return false;
|
|
119
120
|
const live = entryOf(await res.text());
|
package/dist/tasks.js
CHANGED
|
@@ -15,6 +15,7 @@ import { toast } from 'sonner';
|
|
|
15
15
|
// otherwise it reads again on focus. The product mounts `TasksProvider` and `TasksButton` in the
|
|
16
16
|
// shell's slots; where the API answers 404 nothing shows.
|
|
17
17
|
import { LumenConfirm } from './confirm.js';
|
|
18
|
+
import { fetchWithin } from './fetch-within.js';
|
|
18
19
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
19
20
|
import { cn } from './lib/utils.js';
|
|
20
21
|
import { IconButton } from './page.js';
|
|
@@ -80,7 +81,7 @@ function TasksLive({ api, apps, children, }) {
|
|
|
80
81
|
// Reads already on the wire share one request.
|
|
81
82
|
const inflight = useRef(null);
|
|
82
83
|
const read = useCallback(async () => {
|
|
83
|
-
const res = await
|
|
84
|
+
const res = await fetchWithin(api, { headers: { accept: 'application/json' } }).catch(() => null);
|
|
84
85
|
if (!res)
|
|
85
86
|
return;
|
|
86
87
|
if (res.status === 404) {
|
|
@@ -121,7 +122,7 @@ function TasksLive({ api, apps, children, }) {
|
|
|
121
122
|
void refresh();
|
|
122
123
|
}, [refresh]);
|
|
123
124
|
const post = useCallback(async (path) => {
|
|
124
|
-
await
|
|
125
|
+
await fetchWithin(`${api}${path}`, {
|
|
125
126
|
method: 'POST',
|
|
126
127
|
headers: { 'content-type': 'application/json' },
|
|
127
128
|
body: '{}',
|
package/dist/whats-new.js
CHANGED
|
@@ -12,6 +12,7 @@ import { SparklesIcon } from 'lucide-react';
|
|
|
12
12
|
import { useState } from 'react';
|
|
13
13
|
import Markdown from 'react-markdown';
|
|
14
14
|
import { DrawerSection } from './drawer-section.js';
|
|
15
|
+
import { fetchWithin } from './fetch-within.js';
|
|
15
16
|
import { LumenDrawer } from './lumen-drawer.js';
|
|
16
17
|
import { usePrefs } from './prefs.js';
|
|
17
18
|
import { Badge } from './ui/badge.js';
|
|
@@ -34,7 +35,7 @@ async function page(api, before) {
|
|
|
34
35
|
const q = new URLSearchParams({ limit: String(PAGE) });
|
|
35
36
|
if (before)
|
|
36
37
|
q.set('before', before);
|
|
37
|
-
const r = await
|
|
38
|
+
const r = await fetchWithin(`${api}?${q}`, { credentials: 'include' });
|
|
38
39
|
if (r.status === 404)
|
|
39
40
|
return null;
|
|
40
41
|
if (!r.ok)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.130",
|
|
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.130",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|