anentrypoint-design 0.0.437 → 0.0.439
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/247420.css +78 -23
- package/dist/247420.js +23 -23
- package/dist/colors_and_type.css +86 -42
- package/dist/preview/buttons.html +5 -2
- package/dist/preview/colors-core.html +4 -1
- package/dist/preview/colors-lore.html +4 -1
- package/dist/preview/colors-semantic.html +4 -1
- package/dist/preview/dateline.html +4 -1
- package/dist/preview/dropzone.html +4 -1
- package/dist/preview/file-grid.html +4 -1
- package/dist/preview/file-row.html +4 -1
- package/dist/preview/file-toolbar.html +4 -1
- package/dist/preview/file-viewer.html +7 -1
- package/dist/preview/header.html +5 -2
- package/dist/preview/icons-unicode.html +26 -20
- package/dist/preview/index-row.html +4 -1
- package/dist/preview/inputs.html +4 -1
- package/dist/preview/manifesto.html +4 -1
- package/dist/preview/rules.html +4 -1
- package/dist/preview/spacing.html +17 -12
- package/dist/preview/stamps-lore.html +4 -1
- package/dist/preview/stamps.html +4 -1
- package/dist/preview/theme-ink.html +4 -1
- package/dist/preview/type-display.html +4 -1
- package/dist/preview/type-mono.html +4 -1
- package/dist/preview/type-prose.html +5 -2
- package/dist/preview/type-scale.html +40 -15
- package/dist/preview/wordmarks.html +18 -22
- package/dist/src/components/content.js +2 -1
- package/dist/src/components.js +21 -5
- package/dist/src/kits/os/theme.css +38 -5
- package/dist/ui_kits/aicat/app.js +1 -1
- package/dist/ui_kits/gallery/app.js +27 -7
- package/dist/ui_kits/signin/app.js +51 -10
- package/dist/ui_kits/system_primer/app.js +189 -3
- package/dist/ui_kits/terminal/app.js +56 -10
- package/package.json +1 -1
- package/src/components/content/cli.js +7 -1
- package/src/components/content/panel.js +7 -2
- package/src/components/shell/app-shell.js +7 -2
- package/src/components/shell/icons.js +4 -1
- package/src/css/app-shell/kits-appended.css +58 -21
- package/src/css/app-shell/panel-row.css +14 -0
- package/src/css/app-shell/responsive2-workspace.css +5 -1
- package/src/css/app-shell/topbar.css +1 -1
- package/src/kits/os/files-app.js +9 -2
- package/src/kits/os/icons.js +5 -0
- package/src/kits/os/launcher.css +7 -2
- package/src/kits/os/launcher.js +27 -3
- package/src/kits/os/monitor-app.js +8 -2
- package/src/kits/os/shell.js +55 -12
- package/src/kits/os/theme.css +20 -2
- package/src/kits/os/wm.js +41 -11
- package/types/components.d.ts +2 -0
- package/dist/slides/deck-stage-overlay.js +0 -63
- package/dist/slides/deck-stage-state.js +0 -81
- package/dist/slides/deck-stage-style.js +0 -117
- package/dist/slides/deck-stage.js +0 -159
- package/dist/src/kits/os/freddie/helpers.js +0 -59
- package/dist/src/kits/os/validator-app.js +0 -55
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as webjsx from 'webjsx';
|
|
2
|
-
import { Topbar, Crumb, Status, Side, AppShell, Panel, PageHeader, Chip, Btn, ThemeToggle } from 'ds/components.js';
|
|
2
|
+
import { Topbar, Crumb, Status, Side, AppShell, Panel, PageHeader, Chip, Btn, ThemeToggle, Slider, Progress, InputOTP, HoverCard, DatePicker, formatDate, DateRangePicker, Carousel, Menubar, AspectRatio, LiveCursorOverlay, RemoteSelectionRings, RecentEditHighlightFlash, AgentPresenceChip, PresenceBar, ContextMeter, ContextTreemap, ContextXRayPanel } from 'ds/components.js';
|
|
3
3
|
import { mountKit } from 'ds/bootstrap.js';
|
|
4
4
|
const h = webjsx.createElement;
|
|
5
5
|
|
|
@@ -94,6 +94,50 @@ function TypeScalePanel() {
|
|
|
94
94
|
// click, on the page whose whole job is showing how controls behave.
|
|
95
95
|
const primState = { pressed: null };
|
|
96
96
|
|
|
97
|
+
// Same interactive-demonstration standard as primState above, for the 2026
|
|
98
|
+
// restyle's new primitives (Calendar/DatePicker/Slider/InputOTP/HoverCard):
|
|
99
|
+
// each control is genuinely wired to state and re-renders on change, not a
|
|
100
|
+
// static screenshot of the component.
|
|
101
|
+
const restyleState = {
|
|
102
|
+
sliderValue: 40,
|
|
103
|
+
otpValue: '',
|
|
104
|
+
otpComplete: null,
|
|
105
|
+
datePickerValue: null,
|
|
106
|
+
datePickerOpen: false,
|
|
107
|
+
hoverCardOpen: false,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// The remaining backfilled primitives. Same live-wired standard: every control
|
|
111
|
+
// that looks interactive owns real state and re-renders on change. The three
|
|
112
|
+
// collab-ui overlays and the context-pane trio are DATA-driven rather than
|
|
113
|
+
// input-driven, so their specimens supply representative fixture data — the
|
|
114
|
+
// honest demonstration for a component whose input is a remote peer's cursor
|
|
115
|
+
// position or a token accounting breakdown, neither of which a primer page can
|
|
116
|
+
// manufacture by being clicked.
|
|
117
|
+
const moreState = {
|
|
118
|
+
rangeValue: { from: null, to: null },
|
|
119
|
+
rangeOpen: false,
|
|
120
|
+
menuOpenIndex: null,
|
|
121
|
+
menuPicked: null,
|
|
122
|
+
xrayOpenId: null,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const COLLAB_USERS = [
|
|
126
|
+
{ userId: 'u1', label: 'ana', color: 'var(--purple)', status: 'active' },
|
|
127
|
+
{ userId: 'u2', label: 'blake', color: 'var(--green)', status: 'idle' },
|
|
128
|
+
{ userId: 'u3', label: 'agent', color: 'var(--mascot)', status: 'active' },
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
// `value` (not `tokens`) and a `tone` class suffix are the real field names
|
|
132
|
+
// ContextMeter/ContextTreemap/ContextXRayPanel all read — a `tokens` key renders
|
|
133
|
+
// zero-width bars and an empty treemap rather than erroring, so it has to match.
|
|
134
|
+
const CONTEXT_SEGMENTS = [
|
|
135
|
+
{ id: 'sys', label: 'system', value: 1840, tone: 'system' },
|
|
136
|
+
{ id: 'files', label: 'files', value: 7320, tone: 'files' },
|
|
137
|
+
{ id: 'chat', label: 'chat', value: 4210, tone: 'chat' },
|
|
138
|
+
{ id: 'tools', label: 'tool defs', value: 1130, tone: 'other' },
|
|
139
|
+
];
|
|
140
|
+
|
|
97
141
|
function PrimitivesPanel() {
|
|
98
142
|
return Panel({ id: 'primitives', title: 'primitives', class: 'ds-panel-gap', children:
|
|
99
143
|
h('div', { class: 'ds-prim-panel' },
|
|
@@ -120,6 +164,144 @@ function PrimitivesPanel() {
|
|
|
120
164
|
});
|
|
121
165
|
}
|
|
122
166
|
|
|
167
|
+
// New primitives added in the 2026 webjsx-toolkit-look restyle. Live-wired
|
|
168
|
+
// (not static screenshots), same standard as PrimitivesPanel above.
|
|
169
|
+
function RestylePanel() {
|
|
170
|
+
return Panel({ id: 'restyle', title: 'restyle 2026 — new primitives', class: 'ds-panel-gap', children:
|
|
171
|
+
h('div', { class: 'ds-prim-panel' },
|
|
172
|
+
h('div', { class: 'ds-prim-row' },
|
|
173
|
+
h('span', { class: 'ds-prim-label' }, 'slider'),
|
|
174
|
+
Slider({
|
|
175
|
+
value: restyleState.sliderValue, min: 0, max: 100,
|
|
176
|
+
onChange: (v) => { restyleState.sliderValue = v; kit.render(); },
|
|
177
|
+
label: 'demo slider',
|
|
178
|
+
}),
|
|
179
|
+
h('span', { class: 'ds-prim-label' }, String(restyleState.sliderValue))
|
|
180
|
+
),
|
|
181
|
+
h('div', { class: 'ds-prim-row' },
|
|
182
|
+
h('span', { class: 'ds-prim-label' }, 'progress'),
|
|
183
|
+
Progress({ value: restyleState.sliderValue, max: 100, label: 'demo progress' })
|
|
184
|
+
),
|
|
185
|
+
h('div', { class: 'ds-prim-row' },
|
|
186
|
+
h('span', { class: 'ds-prim-label' }, 'otp input'),
|
|
187
|
+
InputOTP({
|
|
188
|
+
length: 6, value: restyleState.otpValue,
|
|
189
|
+
onChange: (v) => { restyleState.otpValue = v; kit.render(); },
|
|
190
|
+
onComplete: (v) => { restyleState.otpComplete = v; kit.render(); },
|
|
191
|
+
label: 'demo code',
|
|
192
|
+
}),
|
|
193
|
+
h('span', { class: 'ds-prim-label' },
|
|
194
|
+
restyleState.otpComplete ? 'complete: ' + restyleState.otpComplete : 'type 6 digits')
|
|
195
|
+
),
|
|
196
|
+
h('div', { class: 'ds-prim-row' },
|
|
197
|
+
h('span', { class: 'ds-prim-label' }, 'hover card'),
|
|
198
|
+
HoverCard({
|
|
199
|
+
trigger: Btn({ children: 'hover me' }),
|
|
200
|
+
content: h('div', {}, 'HoverCard content — composes Popover with a delayed hover trigger.'),
|
|
201
|
+
open: restyleState.hoverCardOpen,
|
|
202
|
+
onOpenChange: (v) => { restyleState.hoverCardOpen = v; kit.render(); },
|
|
203
|
+
})
|
|
204
|
+
),
|
|
205
|
+
h('div', { class: 'ds-prim-row' },
|
|
206
|
+
h('span', { class: 'ds-prim-label' }, 'date picker'),
|
|
207
|
+
DatePicker({
|
|
208
|
+
value: restyleState.datePickerValue,
|
|
209
|
+
onChange: (v) => { restyleState.datePickerValue = v; kit.render(); },
|
|
210
|
+
open: restyleState.datePickerOpen,
|
|
211
|
+
onOpenChange: (v) => { restyleState.datePickerOpen = v; kit.render(); },
|
|
212
|
+
placeholder: 'pick a date',
|
|
213
|
+
}),
|
|
214
|
+
h('span', { class: 'ds-prim-label' },
|
|
215
|
+
restyleState.datePickerValue ? formatDate(restyleState.datePickerValue) : 'none selected')
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The rest of the backfilled surface: range picking, the two overlay
|
|
222
|
+
// compositions, the aspect-ratio wrapper, the collab-ui overlays and the
|
|
223
|
+
// context-pane trio. Split from RestylePanel rather than appended to it to keep
|
|
224
|
+
// each function a single readable specimen group.
|
|
225
|
+
function BackfillPanel() {
|
|
226
|
+
const totalTokens = CONTEXT_SEGMENTS.reduce((n, s) => n + s.value, 0);
|
|
227
|
+
return Panel({ id: 'backfill', title: 'backfill — range, overlays, collab, context', class: 'ds-panel-gap', children:
|
|
228
|
+
h('div', { class: 'ds-prim-panel' },
|
|
229
|
+
h('div', { class: 'ds-prim-row' },
|
|
230
|
+
h('span', { class: 'ds-prim-label' }, 'date range'),
|
|
231
|
+
DateRangePicker({
|
|
232
|
+
value: moreState.rangeValue,
|
|
233
|
+
onChange: (v) => { moreState.rangeValue = v; kit.render(); },
|
|
234
|
+
open: moreState.rangeOpen,
|
|
235
|
+
onOpenChange: (v) => { moreState.rangeOpen = v; kit.render(); },
|
|
236
|
+
placeholder: 'pick a range',
|
|
237
|
+
name: 'primer-drp',
|
|
238
|
+
}),
|
|
239
|
+
h('span', { class: 'ds-prim-label' },
|
|
240
|
+
moreState.rangeValue.from
|
|
241
|
+
? formatDate(moreState.rangeValue.from) + ' -> ' + (moreState.rangeValue.to ? formatDate(moreState.rangeValue.to) : '...')
|
|
242
|
+
: 'none selected')
|
|
243
|
+
),
|
|
244
|
+
h('div', { class: 'ds-prim-row' },
|
|
245
|
+
h('span', { class: 'ds-prim-label' }, 'menubar'),
|
|
246
|
+
Menubar({
|
|
247
|
+
openIndex: moreState.menuOpenIndex,
|
|
248
|
+
onOpenIndexChange: (i) => { moreState.menuOpenIndex = i; kit.render(); },
|
|
249
|
+
ariaLabel: 'primer demo menubar',
|
|
250
|
+
menus: [
|
|
251
|
+
{ id: 'file', label: 'file', items: [{ id: 'new', label: 'new' }, { id: 'open', label: 'open' }], onSelect: (id) => { moreState.menuPicked = 'file/' + id; kit.render(); } },
|
|
252
|
+
{ id: 'edit', label: 'edit', items: [{ id: 'copy', label: 'copy' }, { separator: true }, { id: 'del', label: 'delete', danger: true }], onSelect: (id) => { moreState.menuPicked = 'edit/' + id; kit.render(); } },
|
|
253
|
+
],
|
|
254
|
+
}),
|
|
255
|
+
h('span', { class: 'ds-prim-label' },
|
|
256
|
+
moreState.menuPicked ? 'chose: ' + moreState.menuPicked : 'nothing chosen yet')
|
|
257
|
+
),
|
|
258
|
+
h('div', { class: 'ds-prim-row' },
|
|
259
|
+
h('span', { class: 'ds-prim-label' }, 'carousel'),
|
|
260
|
+
Carousel({
|
|
261
|
+
label: 'primer demo carousel',
|
|
262
|
+
items: ['one', 'two', 'three', 'four'],
|
|
263
|
+
renderItem: (item) => h('div', { class: 'ds-prim-label' }, item),
|
|
264
|
+
})
|
|
265
|
+
),
|
|
266
|
+
h('div', { class: 'ds-prim-row' },
|
|
267
|
+
h('span', { class: 'ds-prim-label' }, 'aspect ratio'),
|
|
268
|
+
AspectRatio({ ratio: '16 / 9', children: h('div', { class: 'ds-prim-label' }, '16 / 9') })
|
|
269
|
+
),
|
|
270
|
+
h('div', { class: 'ds-prim-row' },
|
|
271
|
+
h('span', { class: 'ds-prim-label' }, 'presence'),
|
|
272
|
+
PresenceBar({ users: COLLAB_USERS }),
|
|
273
|
+
AgentPresenceChip({ userId: 'u9', label: 'solo agent', color: 'var(--purple-2)', status: 'active' })
|
|
274
|
+
),
|
|
275
|
+
h('div', { class: 'ds-prim-row' },
|
|
276
|
+
h('span', { class: 'ds-prim-label' }, 'collab overlays'),
|
|
277
|
+
// LiveCursorOverlay takes flat x/y, but the ring and flash
|
|
278
|
+
// overlays read a NESTED `rect` ({top,left,width,height}) --
|
|
279
|
+
// passing flat coords there throws on `s.rect.left`, taking the
|
|
280
|
+
// whole kit's mount down rather than just blanking one specimen.
|
|
281
|
+
LiveCursorOverlay({ cursors: [{ userId: 'u1', label: 'ana', color: 'var(--purple)', x: 40, y: 18 }, { userId: 'u3', label: 'agent', color: 'var(--mascot)', x: 120, y: 44 }] }),
|
|
282
|
+
RemoteSelectionRings({ selections: [{ userId: 'u2', color: 'var(--green)', rect: { left: 20, top: 12, width: 90, height: 18 } }] }),
|
|
283
|
+
RecentEditHighlightFlash({ edits: [{ timestamp: 1, color: 'var(--purple)', rect: { left: 12, top: 60, width: 70, height: 16 } }] })
|
|
284
|
+
),
|
|
285
|
+
h('div', { class: 'ds-prim-row' },
|
|
286
|
+
h('span', { class: 'ds-prim-label' }, 'context meter'),
|
|
287
|
+
ContextMeter({ used: totalTokens, total: 32000, segments: CONTEXT_SEGMENTS })
|
|
288
|
+
),
|
|
289
|
+
h('div', { class: 'ds-prim-row' },
|
|
290
|
+
h('span', { class: 'ds-prim-label' }, 'context treemap'),
|
|
291
|
+
ContextTreemap({ items: CONTEXT_SEGMENTS, width: 280, height: 160 })
|
|
292
|
+
),
|
|
293
|
+
h('div', { class: 'ds-prim-row' },
|
|
294
|
+
h('span', { class: 'ds-prim-label' }, 'context x-ray'),
|
|
295
|
+
ContextXRayPanel({
|
|
296
|
+
segments: CONTEXT_SEGMENTS,
|
|
297
|
+
openId: moreState.xrayOpenId,
|
|
298
|
+
onOpenIdChange: (id) => { moreState.xrayOpenId = id; kit.render(); },
|
|
299
|
+
})
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
123
305
|
function App() {
|
|
124
306
|
return AppShell({
|
|
125
307
|
topbar: Topbar({
|
|
@@ -137,7 +319,9 @@ function App() {
|
|
|
137
319
|
{ glyph: '-', label: 'palette', key: 'p', href: '#palette' },
|
|
138
320
|
{ glyph: '-', label: 'semantic', key: 's', href: '#semantic' },
|
|
139
321
|
{ glyph: '-', label: 'type scale', key: 't', href: '#type-scale' },
|
|
140
|
-
{ glyph: '-', label: 'primitives', key: 'r', href: '#primitives' }
|
|
322
|
+
{ glyph: '-', label: 'primitives', key: 'r', href: '#primitives' },
|
|
323
|
+
{ glyph: '-', label: 'restyle 2026', key: 'x', href: '#restyle' },
|
|
324
|
+
{ glyph: '-', label: 'backfill', key: 'b', href: '#backfill' }
|
|
141
325
|
] }
|
|
142
326
|
]
|
|
143
327
|
}),
|
|
@@ -156,7 +340,9 @@ function App() {
|
|
|
156
340
|
PaletteGrid(),
|
|
157
341
|
SemanticGrid(),
|
|
158
342
|
TypeScalePanel(),
|
|
159
|
-
PrimitivesPanel()
|
|
343
|
+
PrimitivesPanel(),
|
|
344
|
+
RestylePanel(),
|
|
345
|
+
BackfillPanel()
|
|
160
346
|
)
|
|
161
347
|
],
|
|
162
348
|
status: Status({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as webjsx from 'webjsx';
|
|
2
2
|
import { Topbar, Crumb, Status, Side, AppShell, Panel, Heading, Lede, Chip, ThemeToggle } from 'ds/components.js';
|
|
3
3
|
import { mountKit } from 'ds/bootstrap.js';
|
|
4
|
+
import { run as runCommand, complete as completeLine } from 'ds/shell.js';
|
|
4
5
|
const h = webjsx.createElement;
|
|
5
6
|
|
|
6
7
|
const root = document.getElementById('root');
|
|
@@ -8,10 +9,15 @@ const root = document.getElementById('root');
|
|
|
8
9
|
// Live terminal — instant output, no fake reveal animation. This is the
|
|
9
10
|
// usable surface; anything typed appears immediately, anything emitted by
|
|
10
11
|
// the (stubbed) backend appears immediately.
|
|
12
|
+
// Seeded by really running the commands through the interpreter at load, not
|
|
13
|
+
// by hand-writing their output. A canned transcript would go stale the moment
|
|
14
|
+
// the filesystem or a command changed, and it would show a shell that cannot
|
|
15
|
+
// be told apart from one that executes nothing -- which is exactly what this
|
|
16
|
+
// kit used to be.
|
|
11
17
|
const liveTranscript = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ kind: '
|
|
18
|
+
// No leading '#' in the text: Line() renders the cmt prompt mark itself,
|
|
19
|
+
// so writing one here produces a doubled '# #' on screen.
|
|
20
|
+
{ kind: 'cmt', text: 'live shell — really executes. type `help` for commands.' },
|
|
15
21
|
];
|
|
16
22
|
const live = { input: '', cwd: '~/dev/design', phase: 'ready' };
|
|
17
23
|
const PHASES = ['ready', 'loading', 'empty', 'error'];
|
|
@@ -76,6 +82,28 @@ const demo = { visible: reduced ? demoScript.slice() : [], looping: !reduced };
|
|
|
76
82
|
const history = [];
|
|
77
83
|
let historyIdx = -1;
|
|
78
84
|
|
|
85
|
+
// The interpreter's session state. cwd is an array of path segments owned here
|
|
86
|
+
// and mutated in place by `cd`, so the prompt and the shell never disagree
|
|
87
|
+
// about where the session is.
|
|
88
|
+
const shellCwd = [];
|
|
89
|
+
const shellPath = () => '~/' + shellCwd.join('/');
|
|
90
|
+
const shellCtx = {
|
|
91
|
+
cwd: shellCwd,
|
|
92
|
+
clear: () => clearScrollback(),
|
|
93
|
+
// `theme dark` drives the same data-theme attribute ThemeToggle writes, so
|
|
94
|
+
// the command and the toggle stay one mechanism rather than two.
|
|
95
|
+
setTheme: (t) => document.documentElement.setAttribute('data-theme', t),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Run a line for real and append both the command and its output, so the
|
|
99
|
+
// opening scrollback is produced by the same path a typed command takes.
|
|
100
|
+
function seed(line) {
|
|
101
|
+
liveTranscript.push({ kind: 'cmd', text: line });
|
|
102
|
+
for (const out of runCommand(line, shellCtx)) liveTranscript.push(out);
|
|
103
|
+
}
|
|
104
|
+
seed('whoami');
|
|
105
|
+
seed('ls');
|
|
106
|
+
|
|
79
107
|
// Empties the scrollback and drops the shell into its own empty state, which is
|
|
80
108
|
// the honest reading of a cleared shell (the empty panel explains what lands
|
|
81
109
|
// there). Both the sidebar item and Cmd/Ctrl+K call this.
|
|
@@ -149,7 +177,10 @@ function App() {
|
|
|
149
177
|
// rather than documenting a keystroke and doing nothing when
|
|
150
178
|
// clicked. Both are also bound to the keys they advertise.
|
|
151
179
|
{ group: 'shortcuts', items: [
|
|
152
|
-
|
|
180
|
+
// The handler binds metaKey OR ctrlKey, so the label names
|
|
181
|
+
// both. The Command symbol is also the one glyph the mono
|
|
182
|
+
// stack has no coverage for -- it rendered as tofu here.
|
|
183
|
+
{ glyph: '·', label: 'clear (ctrl/cmd k)', key: 'c',
|
|
153
184
|
onClick: (e) => { e.preventDefault(); clearScrollback(); } },
|
|
154
185
|
{ glyph: '·', label: 'history (up)', key: 'h',
|
|
155
186
|
onClick: (e) => { e.preventDefault(); recallHistory(); } }
|
|
@@ -162,7 +193,7 @@ function App() {
|
|
|
162
193
|
h('div', {}, Heading({ level: 1, children: 'terminal' })),
|
|
163
194
|
ThemeToggle()
|
|
164
195
|
),
|
|
165
|
-
Lede({ children: '
|
|
196
|
+
Lede({ children: 'a working shell — ls, cd, cat, echo, theme and more run against a real in-memory tree, with tab completion and history. below it, a decorative demo loop plays the .cli row primitives. respects prefers-reduced-motion.' }),
|
|
166
197
|
|
|
167
198
|
// Live terminal — usable, no reveal delays.
|
|
168
199
|
Panel({
|
|
@@ -179,17 +210,31 @@ function App() {
|
|
|
179
210
|
h('span', { class: 'prompt' }, '$'),
|
|
180
211
|
h('input', {
|
|
181
212
|
value: live.input,
|
|
182
|
-
placeholder: '
|
|
213
|
+
placeholder: 'try `help`, `ls`, `cat readme.md`…',
|
|
183
214
|
class: 'ds-term-input',
|
|
184
215
|
oninput: (e) => { live.input = e.target.value; },
|
|
185
216
|
onkeydown: (e) => {
|
|
186
217
|
if (e.key === 'Enter' && live.input.trim()) {
|
|
187
|
-
|
|
188
|
-
liveTranscript.push({ kind: '
|
|
189
|
-
|
|
218
|
+
const line = live.input;
|
|
219
|
+
liveTranscript.push({ kind: 'cmd', text: line });
|
|
220
|
+
// Real execution: the interpreter in
|
|
221
|
+
// ds/shell.js owns the filesystem and the
|
|
222
|
+
// command table, so an unknown command
|
|
223
|
+
// reports a genuine error instead of the
|
|
224
|
+
// old '(stub) ran: ...' echo that claimed
|
|
225
|
+
// success for anything typed.
|
|
226
|
+
for (const out of runCommand(line, shellCtx)) liveTranscript.push(out);
|
|
227
|
+
live.cwd = shellPath();
|
|
228
|
+
history.push(line);
|
|
190
229
|
historyIdx = -1;
|
|
191
230
|
live.input = '';
|
|
192
231
|
kit.render();
|
|
232
|
+
} else if (e.key === 'Tab') {
|
|
233
|
+
// Completion has to pre-empt the browser's
|
|
234
|
+
// focus move, so preventDefault comes first.
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
live.input = completeLine(live.input, shellCwd);
|
|
237
|
+
kit.render();
|
|
193
238
|
} else if (e.key === 'ArrowUp') {
|
|
194
239
|
// The sidebar advertises 'history (up)';
|
|
195
240
|
// the key it names has to do it too.
|
|
@@ -216,7 +261,8 @@ function App() {
|
|
|
216
261
|
Panel({ title: 'about this kit', class: 'ds-panel-gap', children: h('div', { class: 'ds-pattern-notes' },
|
|
217
262
|
h('p', {}, '· ', Chip({ tone: 'accent', children: '.cli' }), ' rows pair ', h('code', {}, '.prompt'), ' + ', h('code', {}, '.cmd'), '.'),
|
|
218
263
|
h('p', {}, '· six line kinds: ', Chip({ tone: 'dim', children: 'cmt' }), ' ', Chip({ tone: 'dim', children: 'cmd' }), ' ', Chip({ tone: 'dim', children: 'out' }), ' ', Chip({ tone: 'accent', children: 'ok' }), ' ', Chip({ tone: '', children: 'warn' }), ' ', Chip({ tone: 'dim', children: 'log' }), '.'),
|
|
219
|
-
h('p', {}, '· live panel is instant
|
|
264
|
+
h('p', {}, '· live panel is instant and really executes — ', h('code', {}, 'help'), ' lists the commands, tab completes, up walks history.'),
|
|
265
|
+
h('p', {}, '· demo panel reveals lines on a loop for the showcase only — never fake-animate output a user is waiting on.')
|
|
220
266
|
) })
|
|
221
267
|
)
|
|
222
268
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.439",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -5,13 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
7
7
|
import { Panel } from './panel.js';
|
|
8
|
+
import { Btn } from '../shell.js';
|
|
8
9
|
const h = webjsx.createElement;
|
|
9
10
|
|
|
10
11
|
export function Install({ cmd, copied, onCopy }) {
|
|
11
12
|
return h('div', { class: 'cli' },
|
|
12
13
|
h('span', { class: 'prompt' }, '$'),
|
|
13
14
|
h('span', { class: 'cmd' }, cmd),
|
|
14
|
-
|
|
15
|
+
Btn({
|
|
16
|
+
class: 'copy', size: 'sm',
|
|
17
|
+
onClick: () => onCopy && onCopy(cmd),
|
|
18
|
+
'aria-label': copied ? 'copied to clipboard' : 'copy install command',
|
|
19
|
+
children: h('span', { 'aria-live': 'polite' }, copied ? 'copied' : 'copy')
|
|
20
|
+
})
|
|
15
21
|
);
|
|
16
22
|
}
|
|
17
23
|
|
|
@@ -7,11 +7,16 @@ import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
|
7
7
|
import { RowLink } from './row.js';
|
|
8
8
|
const h = webjsx.createElement;
|
|
9
9
|
|
|
10
|
-
export function Panel({ title, count, right, style = '', class: className = '', children, kind, id }) {
|
|
10
|
+
export function Panel({ title, count, right, style = '', class: className = '', children, kind, id, headingLevel = 2 }) {
|
|
11
11
|
const cls = 'panel' + (kind ? ' panel-' + kind : '') + (className ? ' ' + className : '');
|
|
12
|
+
// title renders as a real heading (h2 by default; `headingLevel` lets a
|
|
13
|
+
// caller nest a Panel under an existing h2/h3 without skipping a level) so
|
|
14
|
+
// a screen-reader heading-jump can actually find each section, not just
|
|
15
|
+
// the page's single top-level h1.
|
|
16
|
+
const headingTag = 'h' + headingLevel;
|
|
12
17
|
return h('div', { class: cls, style, ...(id ? { id } : {}) },
|
|
13
18
|
title != null ? h('div', { class: 'panel-head' },
|
|
14
|
-
h('
|
|
19
|
+
h(headingTag, { class: 'panel-title' }, title),
|
|
15
20
|
right != null ? right : (count != null ? h('span', {}, String(count)) : null)
|
|
16
21
|
) : null,
|
|
17
22
|
h('div', { class: 'panel-body' }, ...(Array.isArray(children) ? children : [children]))
|
|
@@ -93,7 +93,7 @@ export function Side({ sections = [] } = {}) {
|
|
|
93
93
|
return h('div', { class: 'app-side-group', key: sec.group, role: 'group', 'aria-labelledby': groupId },
|
|
94
94
|
h('h2', { class: 'group', id: groupId }, sec.group),
|
|
95
95
|
...sec.items.map((item, i) => {
|
|
96
|
-
const { glyph, label, href, active, count, color, onClick } = item;
|
|
96
|
+
const { glyph, label, href, active, count, color, onClick, ariaLabel } = item;
|
|
97
97
|
const countLabel = (count != null && count !== 0 && count !== '0') ? ` (${count})` : '';
|
|
98
98
|
// An item with neither href nor onClick is not a control, and
|
|
99
99
|
// must not look like one. href used to default to '#', so a
|
|
@@ -118,7 +118,12 @@ export function Side({ sections = [] } = {}) {
|
|
|
118
118
|
...(isControl ? { href: href != null ? href : '#' } : {}),
|
|
119
119
|
class: active ? 'active' : '',
|
|
120
120
|
'aria-current': active ? 'page' : null,
|
|
121
|
-
|
|
121
|
+
// ariaLabel lets a caller pass the untruncated source string
|
|
122
|
+
// as the accessible name when `label` itself is a shortened
|
|
123
|
+
// display string (e.g. a preset prompt cut to fit the row) —
|
|
124
|
+
// otherwise screen-reader users lose the exact same
|
|
125
|
+
// distinguishing text sighted users lose to the ellipsis.
|
|
126
|
+
'aria-label': (ariaLabel != null ? ariaLabel : label) + countLabel,
|
|
122
127
|
onclick: onClick,
|
|
123
128
|
onkeydown: isControl ? onSideLinkKeyDown : null
|
|
124
129
|
},
|
|
@@ -89,7 +89,10 @@ export const ICON_PATHS = {
|
|
|
89
89
|
clipboard: '<rect x="8" y="4" width="8" height="4" rx="1"/><path d="M8 6H6a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-2"/>',
|
|
90
90
|
// Live-cursor pointer arrow — collab.js's LiveCursorOverlay renders one
|
|
91
91
|
// per remote collaborator, filled with that collaborator's color.
|
|
92
|
-
cursor: '<path d="M5 3l14 8-6.5 1.5L11 20z"/>'
|
|
92
|
+
cursor: '<path d="M5 3l14 8-6.5 1.5L11 20z"/>',
|
|
93
|
+
// Password-visibility toggle — eye / eye-off pair.
|
|
94
|
+
eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>',
|
|
95
|
+
'eye-off': '<path d="M2 12s3.5-7 10-7c1.7 0 3.2.4 4.5 1.1M22 12s-3.5 7-10 7c-1.7 0-3.2-.4-4.5-1.1"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/><path d="m4 4 16 16"/>'
|
|
93
96
|
};
|
|
94
97
|
|
|
95
98
|
// The single SVG attribute contract (viewBox/stroke/linecap…) shared by both
|
|
@@ -289,23 +289,12 @@
|
|
|
289
289
|
.ds-gallery-tile:hover { transform: translateY(-2px); box-shadow: 0 2px 8px color-mix(in oklab, var(--fg) 10%, transparent); }
|
|
290
290
|
.ds-gallery-tile:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
|
|
291
291
|
.ds-gallery-tile--tight { min-height: clamp(96px, 14vw, 120px); }
|
|
292
|
-
.ds-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
.ds-lightbox-card {
|
|
300
|
-
/* 28px ceiling has no tier (--space-4 is 24, --space-5 is 32); it is the
|
|
301
|
-
lightbox card inset tuned against the min(520px,100%) card width. */
|
|
302
|
-
background: var(--panel-0); border-radius: var(--r-2); padding: clamp(var(--space-3), 4vw, 28px);
|
|
303
|
-
width: min(520px, 100%); min-width: 0;
|
|
304
|
-
max-height: calc(100vh - 24px);
|
|
305
|
-
max-height: calc(100dvh - 24px);
|
|
306
|
-
overflow: auto;
|
|
307
|
-
display: flex; flex-direction: column; gap: 14px; /* off-scale, matches the card inset above */
|
|
308
|
-
}
|
|
292
|
+
/* The lightbox scrim/card chrome itself is now Dialog's (.ds-ep-dialog /
|
|
293
|
+
.ds-ep-dialog-backdrop in editor-primitives.css) — Dialog already ships
|
|
294
|
+
role="dialog", aria-modal, Tab-trap and focus-restore-on-close, which the
|
|
295
|
+
former hand-rolled .ds-lightbox/.ds-lightbox-card overlay had none of. Only
|
|
296
|
+
the gallery-specific content rules (head row, tag, carousel sizing, tile
|
|
297
|
+
preview) remain here. */
|
|
309
298
|
.ds-lightbox-preview {
|
|
310
299
|
/* 36px ceiling has no tier (--space-5 is 32, --space-6 is 48). */
|
|
311
300
|
padding: clamp(var(--space-3), 6vw, 36px); border-radius: var(--r-1); text-align: center;
|
|
@@ -511,11 +500,24 @@
|
|
|
511
500
|
.ds-auth-otp-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
|
|
512
501
|
.ds-auth-field { display: flex; flex-direction: column; gap: var(--space-1); }
|
|
513
502
|
/* .ds-auth-field-label joins the kit small-label voice above, no overrides. */
|
|
503
|
+
.ds-auth-field-input-row { position: relative; display: flex; align-items: center; }
|
|
504
|
+
/* Reserves room for the absolutely-positioned toggle: its --space-1 inset plus
|
|
505
|
+
the --ds-icon-btn-sm 26px box, so the input text never sits under it. */
|
|
506
|
+
.ds-auth-field-input-row .input { flex: 1 1 auto; padding-right: calc(26px + var(--space-2)); }
|
|
507
|
+
.ds-auth-pw-toggle { position: absolute; right: var(--space-1); }
|
|
514
508
|
.ds-auth-row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
|
|
515
509
|
.ds-auth-remember { display: flex; align-items: center; gap: var(--space-1-75); cursor: pointer; }
|
|
516
510
|
.ds-auth-remember-text { color: var(--panel-text-2); font-size: var(--fs-tiny); }
|
|
517
511
|
.ds-auth-forgot { font-size: var(--fs-tiny); }
|
|
518
|
-
|
|
512
|
+
/* Error and status share layout but not color: a color-only distinction
|
|
513
|
+
between role="alert" (something is wrong) and role="status" (informational)
|
|
514
|
+
fails WCAG 1.4.1, so each also carries a distinct ASCII marker via ::before —
|
|
515
|
+
never rely on the reader noticing the role attribute alone. */
|
|
516
|
+
.ds-auth-error, .ds-auth-status { padding: var(--space-2) var(--space-2-5); background: var(--panel-1); border-radius: var(--r-0); font-size: var(--fs-tiny); overflow-wrap: anywhere; display: flex; gap: var(--space-1-75); align-items: flex-start; }
|
|
517
|
+
.ds-auth-error { color: var(--danger); }
|
|
518
|
+
.ds-auth-error::before { content: '[!]'; flex: none; }
|
|
519
|
+
.ds-auth-status { color: var(--panel-text-2); }
|
|
520
|
+
.ds-auth-status::before { content: '[i]'; flex: none; }
|
|
519
521
|
.ds-auth-providers { display: flex; gap: var(--space-2); flex-wrap: wrap; }
|
|
520
522
|
.ds-auth-provider-btn { flex: 1 1 100px; min-width: 0; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2-5); }
|
|
521
523
|
.ds-auth-provider-btn--loading { opacity: 0.7; pointer-events: none; }
|
|
@@ -541,8 +543,12 @@
|
|
|
541
543
|
.ds-gal-swatch-meta { display: flex; justify-content: space-between; gap: var(--space-1-75); flex-wrap: wrap; font-family: var(--ff-mono); font-size: var(--fs-nano); min-width: 0; }
|
|
542
544
|
.ds-gal-swatch-name { color: var(--panel-text); }
|
|
543
545
|
.ds-gal-swatch-hint { color: var(--panel-text-3); overflow-wrap: anywhere; }
|
|
544
|
-
.ds-lightbox-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
|
|
546
|
+
.ds-lightbox-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-2-75); }
|
|
545
547
|
/* .ds-lightbox-tag joins the kit small-label voice above, no overrides. */
|
|
548
|
+
/* Dialog's .ds-ep-dialog-body has no default child gap (unlike the former
|
|
549
|
+
hand-rolled .ds-lightbox-card, which was `display:flex; gap:14px`), so the
|
|
550
|
+
carousel block gets its own top gutter to keep head/carousel/hint spaced. */
|
|
551
|
+
.ds-lightbox-card--carousel { margin-bottom: var(--space-2-75); }
|
|
546
552
|
|
|
547
553
|
/* --- error_404 kit --- */
|
|
548
554
|
.ds-err-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; margin-top: var(--space-1-75); }
|
|
@@ -600,6 +606,20 @@
|
|
|
600
606
|
.ds-term-input-row { margin-top: var(--space-1-75); }
|
|
601
607
|
.ds-term-input { flex: 1; min-width: 0; background: transparent; border: 0; outline: 0; font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--paper); caret-color: var(--acid); }
|
|
602
608
|
.ds-term-input::placeholder { color: var(--paper-3-dark); }
|
|
609
|
+
/* Adding type="text" (for the a11y fix below) makes the global
|
|
610
|
+
input[type="text"] rule in states-interactions.css match this element for
|
|
611
|
+
the first time, at equal-or-higher specificity than this file's load
|
|
612
|
+
order would otherwise win. Re-assert the borderless prompt-line chrome so
|
|
613
|
+
the shared form-control skin (bg, border, radius, fixed height) can't leak
|
|
614
|
+
onto what is a terminal prompt, not a form field. */
|
|
615
|
+
.ds-term-input[type='text'] { background: transparent; border: 0; border-radius: 0; box-shadow: none; height: auto; }
|
|
616
|
+
/* The row-level `outline: 0` above kills the browser default, so the control
|
|
617
|
+
needs its own :focus-visible ring back — otherwise a keyboard user gets no
|
|
618
|
+
visual confirmation focus landed on the one control they type into. */
|
|
619
|
+
.ds-term-input:focus-visible {
|
|
620
|
+
outline: var(--focus-w) solid var(--focus-color);
|
|
621
|
+
outline-offset: var(--focus-offset);
|
|
622
|
+
}
|
|
603
623
|
/* The input itself is a prompt line, not a form control, so it is NOT floored
|
|
604
624
|
to 44px (that would break the terminal's line rhythm). Instead the whole
|
|
605
625
|
prompt ROW becomes the tap target, and the font goes to 16px so focusing it
|
|
@@ -622,8 +642,25 @@
|
|
|
622
642
|
.ds-chat-layout > .chat { min-width: 0; }
|
|
623
643
|
.ds-chat-detail { display: none; }
|
|
624
644
|
@media (min-width: 1100px) {
|
|
625
|
-
|
|
626
|
-
|
|
645
|
+
/* Two compounding grid quirks, not one:
|
|
646
|
+
1. align-items:start sizes the row to its tallest item's natural content
|
|
647
|
+
height instead of stretching items to fill it — align-items must stay
|
|
648
|
+
the grid default (stretch).
|
|
649
|
+
2. Even with stretch, an implicit row with no explicit sizing (the
|
|
650
|
+
default here) ALSO sizes itself to content first, then stretches
|
|
651
|
+
items to that content-derived height — which can exceed the row's
|
|
652
|
+
actual allotment from this grid's own flex parent (.chat-kit-page,
|
|
653
|
+
flex:1/min-height:0). .chat then renders taller than .ds-chat-layout's
|
|
654
|
+
own box, so it overflows past the grid container instead of being
|
|
655
|
+
clipped by .chat-thread's internal scroll — visually burying the
|
|
656
|
+
pattern-notes panel that follows this grid in the DOM. Explicit
|
|
657
|
+
`grid-template-rows: minmax(0, 1fr)` makes the row take exactly the
|
|
658
|
+
container's height (from the flex chain above) as the ceiling, so
|
|
659
|
+
.chat's min-height:0 has a real, smaller-than-content value to clip
|
|
660
|
+
against. .ds-chat-detail opts out via align-self so its own sticky
|
|
661
|
+
positioning still works without forcing the row back to content-sized. */
|
|
662
|
+
.ds-chat-layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; grid-template-rows: minmax(0, 1fr); gap: var(--space-4); }
|
|
663
|
+
.ds-chat-detail { display: block; position: sticky; top: 0; align-self: start; }
|
|
627
664
|
}
|
|
628
665
|
|
|
629
666
|
/* Avatar — generic identity disc primitive (content.js Avatar). Sized
|
|
@@ -53,8 +53,22 @@
|
|
|
53
53
|
font-weight: 500; color: var(--fg-3);
|
|
54
54
|
font-family: var(--ff-mono); letter-spacing: 0;
|
|
55
55
|
}
|
|
56
|
+
/* Panel titles render as a real heading (h2 by default) for AT heading-jump
|
|
57
|
+
navigation, but must read identically to the old plain span — reset the
|
|
58
|
+
UA heading margin/size/weight so it inherits .panel-head's own type rules
|
|
59
|
+
instead of the browser's h2 stylesheet. */
|
|
60
|
+
.panel-title {
|
|
61
|
+
margin: 0; font: inherit; font-size: inherit; font-weight: inherit;
|
|
62
|
+
letter-spacing: inherit; text-transform: inherit; color: inherit;
|
|
63
|
+
}
|
|
56
64
|
.panel-body { padding: var(--space-2) var(--space-3) var(--space-3); }
|
|
57
65
|
.panel-body > * { margin: 0; }
|
|
66
|
+
/* One-line explanatory caption under a panel's title — e.g. distinguishing
|
|
67
|
+
two visually-similar panels (lore palette vs semantic tokens) in words
|
|
68
|
+
instead of leaving the reader to infer it from the title alone. */
|
|
69
|
+
.ds-panel-caption {
|
|
70
|
+
font-size: var(--fs-sm); color: var(--fg-3); margin: 0 0 var(--space-2);
|
|
71
|
+
}
|
|
58
72
|
/* Inner sibling rhythm scales with density like the outer panel rhythm, so a
|
|
59
73
|
compact/spacious surface stays proportional instead of only its outer
|
|
60
74
|
intervals moving while the panel interiors hold still. Kept strictly below
|
|
@@ -126,7 +126,11 @@
|
|
|
126
126
|
/* Embedded shell (freddie WM window) fills its container, not the viewport. */
|
|
127
127
|
.fd-root .app { height: 100%; overflow: visible; }
|
|
128
128
|
.app-body { min-height: 0; flex: 1 1 auto; }
|
|
129
|
-
.app-
|
|
129
|
+
/* scrollbar-gutter mirrors .app-main's own (topbar.css) — without it the
|
|
130
|
+
thumb draws flush against the sidebar's nav text with zero breathing
|
|
131
|
+
room, reading as a mark stuck to the content rather than a scrollbar
|
|
132
|
+
anchored to the shell's own edge. */
|
|
133
|
+
.app-side-shell { overflow-y: auto; overflow-x: hidden; min-height: 0; scrollbar-gutter: stable; }
|
|
130
134
|
.app-main { overflow-y: auto; min-height: 0; }
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -164,7 +164,7 @@ body.canvas-host { background: transparent !important; }
|
|
|
164
164
|
|
|
165
165
|
.app-side {
|
|
166
166
|
display: flex; flex-direction: column; gap: var(--space-4);
|
|
167
|
-
padding: var(--space-5) var(--space-3);
|
|
167
|
+
padding: var(--space-5) var(--space-2) var(--space-5) var(--space-3);
|
|
168
168
|
font-size: var(--fs-sm);
|
|
169
169
|
}
|
|
170
170
|
/* Sidebar section wrapper emitted by Side() (src/components/shell/app-shell.js).
|
package/src/kits/os/files-app.js
CHANGED
|
@@ -11,6 +11,13 @@ export function renderFilesApp(opts = {}) {
|
|
|
11
11
|
|
|
12
12
|
let preview = null;
|
|
13
13
|
let selected = null;
|
|
14
|
+
function renderError(err) {
|
|
15
|
+
node.innerHTML = '';
|
|
16
|
+
const errRow = document.createElement('div');
|
|
17
|
+
errRow.className = 'row-error';
|
|
18
|
+
errRow.textContent = 'could not load files: ' + (err && err.message ? err.message : String(err));
|
|
19
|
+
node.appendChild(errRow);
|
|
20
|
+
}
|
|
14
21
|
async function refresh() {
|
|
15
22
|
const items = await list();
|
|
16
23
|
preview = null;
|
|
@@ -40,8 +47,8 @@ export function renderFilesApp(opts = {}) {
|
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
let timer = null;
|
|
43
|
-
refresh().catch(
|
|
44
|
-
if (pollMs > 0) timer = setInterval(() => refresh().catch(
|
|
50
|
+
refresh().catch(renderError);
|
|
51
|
+
if (pollMs > 0) timer = setInterval(() => refresh().catch(renderError), pollMs);
|
|
45
52
|
|
|
46
53
|
return {
|
|
47
54
|
node,
|