anentrypoint-design 0.0.170 → 0.0.172
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/app-shell.css +2 -0
- package/community.css +1 -1
- package/dist/247420.css +94 -1
- package/dist/247420.js +12 -12
- package/package.json +3 -1
- package/src/community-app.js +22 -6
- package/src/components/agent-chat.js +5 -25
- package/src/components/chat.js +49 -73
- package/src/components/community.js +16 -5
- package/src/components/content.js +4 -4
- package/src/components/editor-primitives.js +2 -1
- package/src/components/files-modals.js +3 -3
- package/src/components/files.js +11 -11
- package/src/components/freddie/runtime.js +12 -4
- package/src/components/freddie.js +6 -6
- package/src/components/overlay-primitives.js +10 -3
- package/src/components/shell.js +37 -4
- package/src/components/voice.js +3 -3
- package/src/index.js +1 -0
- package/src/kits/os/freddie/helpers.js +2 -2
- package/src/kits/os/freddie/pages-chat.js +3 -3
- package/src/kits/os/freddie/pages-core.js +4 -4
- package/src/kits/os/freddie/pages-os.js +7 -7
- package/src/kits/os/freddie/pages-tools.js +10 -10
- package/src/kits/os/freddie/routes.js +21 -19
- package/src/kits/os/freddie-dashboard.js +2 -2
- package/src/kits/spoint/host-join-lobby.css +89 -0
- package/src/kits/spoint/host-join-lobby.js +81 -0
- package/src/kits/spoint/index.js +2 -0
- package/src/markdown-cache.js +4 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// .ds-247420 (see editor-primitives.css).
|
|
5
5
|
|
|
6
6
|
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
7
|
+
import { Icon } from './shell.js';
|
|
7
8
|
const h = webjsx.createElement;
|
|
8
9
|
const kids = (c) => c == null ? [] : (Array.isArray(c) ? c : [c]);
|
|
9
10
|
const FOCUSABLE_SEL = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
@@ -65,6 +66,12 @@ function _hideTip() {
|
|
|
65
66
|
if (_tipFloat) { _tipFloat.dispose(); _tipFloat = null; }
|
|
66
67
|
if (_tipEl) { _tipEl.hidden = true; _tipEl.className = 'ds-tooltip'; }
|
|
67
68
|
}
|
|
69
|
+
// One module-scope scroll listener hides the shared bubble on any scroll —
|
|
70
|
+
// registered once, never per-trigger (per-trigger leaked a listener per element).
|
|
71
|
+
if (typeof window !== 'undefined' && !window.__dsTipScrollBound) {
|
|
72
|
+
window.__dsTipScrollBound = true;
|
|
73
|
+
window.addEventListener('scroll', _hideTip, true);
|
|
74
|
+
}
|
|
68
75
|
function _showTip(trigger, label, placement, kind) {
|
|
69
76
|
if (typeof document === 'undefined') return;
|
|
70
77
|
if (!_tipEl || !document.body.contains(_tipEl)) {
|
|
@@ -95,7 +102,6 @@ export function Tooltip({ children, label, placement = 'top', delay = 350, kind
|
|
|
95
102
|
el.addEventListener('focus', show);
|
|
96
103
|
el.addEventListener('blur', _hideTip);
|
|
97
104
|
el.addEventListener('keydown', (e) => { if (e.key === 'Escape') _hideTip(); });
|
|
98
|
-
window.addEventListener('scroll', _hideTip, true);
|
|
99
105
|
useLongPress(el, show, { ms: 500 });
|
|
100
106
|
};
|
|
101
107
|
const prevRef = child.props && child.props.ref;
|
|
@@ -343,7 +349,8 @@ export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose
|
|
|
343
349
|
tabindex: '-1',
|
|
344
350
|
onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
|
|
345
351
|
ref: (el) => {
|
|
346
|
-
if (!el
|
|
352
|
+
if (!el) { if (rootEl && rootEl._ovEmojiCleanup) rootEl._ovEmojiCleanup(); return; }
|
|
353
|
+
if (el._ovEmoji) return; el._ovEmoji = true; rootEl = el;
|
|
347
354
|
const place = () => {
|
|
348
355
|
const r = el.getBoundingClientRect();
|
|
349
356
|
const { left, top } = _clampToViewport(anchorX, anchorY, r.width || 260, r.height || 240);
|
|
@@ -381,7 +388,7 @@ export function BootOverlay({ progress = 0, phase = '', errored = false, visible
|
|
|
381
388
|
return h('div', { class: 'ov-boot' + (errored ? ' is-error' : ''), role: errored ? 'alert' : 'status', 'aria-live': 'polite' },
|
|
382
389
|
h('div', { class: 'ov-boot-inner' },
|
|
383
390
|
errored
|
|
384
|
-
? h('div', { class: 'ov-boot-mark ov-boot-mark-error', 'aria-hidden': 'true' }, '
|
|
391
|
+
? h('div', { class: 'ov-boot-mark ov-boot-mark-error', 'aria-hidden': 'true' }, Icon('warn'))
|
|
385
392
|
: h('div', { class: 'ov-boot-spinner', 'aria-hidden': 'true' }),
|
|
386
393
|
!errored ? h('div', { class: 'ov-boot-bar', role: 'progressbar',
|
|
387
394
|
'aria-valuenow': String(Math.round(pct)), 'aria-valuemin': '0', 'aria-valuemax': '100' },
|
package/src/components/shell.js
CHANGED
|
@@ -95,7 +95,39 @@ const ICON_PATHS = {
|
|
|
95
95
|
megaphone: '<path d="M3 11v2a1 1 0 0 0 1 1h2l5 4V6L6 10H4a1 1 0 0 0-1 1z"/><path d="M15 8a4 4 0 0 1 0 8M18 5a8 8 0 0 1 0 14"/>',
|
|
96
96
|
forum: '<path d="M4 5h13a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H9l-4 3v-3H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"/>',
|
|
97
97
|
page: '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5M8 13h8M8 17h6"/>',
|
|
98
|
-
thread: '<path d="M5 6h14M5 11h14M5 16h8"/><circle cx="17" cy="17" r="3"/>'
|
|
98
|
+
thread: '<path d="M5 6h14M5 11h14M5 16h8"/><circle cx="17" cy="17" r="3"/>',
|
|
99
|
+
// status / control icons (replace decorative text glyphs at the source)
|
|
100
|
+
check: '<path d="M20 6 9 17l-5-5"/>',
|
|
101
|
+
'check-check': '<path d="M18 6 7 17l-3-3"/><path d="m22 10-7.5 7.5L13 16"/>',
|
|
102
|
+
'chevron-right': '<path d="m9 6 6 6-6 6"/>',
|
|
103
|
+
'chevron-down': '<path d="m6 9 6 6 6-6"/>',
|
|
104
|
+
'arrow-down': '<path d="M12 5v14M5 12l7 7 7-7"/>',
|
|
105
|
+
'arrow-right': '<path d="M5 12h14M12 5l7 7-7 7"/>',
|
|
106
|
+
x: '<path d="M18 6 6 18M6 6l12 12"/>',
|
|
107
|
+
play: '<path d="M6 4v16l14-8z"/>',
|
|
108
|
+
pause: '<path d="M8 5v14M16 5v14"/>',
|
|
109
|
+
refresh: '<path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/>',
|
|
110
|
+
circle: '<circle cx="12" cy="12" r="9"/>',
|
|
111
|
+
'circle-dot': '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3" fill="currentColor"/>',
|
|
112
|
+
dot: '<circle cx="12" cy="12" r="4" fill="currentColor"/>',
|
|
113
|
+
square: '<rect x="4" y="4" width="16" height="16" rx="2"/>',
|
|
114
|
+
activity: '<path d="M3 12h4l3 8 4-16 3 8h4"/>',
|
|
115
|
+
info: '<circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/>',
|
|
116
|
+
warn: '<path d="M10.3 4 2.7 17a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 4a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>',
|
|
117
|
+
// file-type icons (replace the FILE_GLYPHS unicode set)
|
|
118
|
+
'file-pdf': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="M9 13h6M9 17h6"/>',
|
|
119
|
+
'file-zip': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="M11 4v3M11 9v3M11 14v3"/>',
|
|
120
|
+
'file-video': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="m10 12 4 2.5L10 17z"/>',
|
|
121
|
+
'file-audio': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="M9 17v-3l4-1v3"/><circle cx="8" cy="17" r="1"/><circle cx="12" cy="16" r="1"/>',
|
|
122
|
+
'file-sheet': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="M8 13h8M8 17h8M12 11v8"/>',
|
|
123
|
+
'file-code': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="m10 12-2 2 2 2M14 12l2 2-2 2"/>',
|
|
124
|
+
'file-text': '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/><path d="M8 13h8M8 17h6"/>',
|
|
125
|
+
file: '<path d="M6 3h8l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z"/><path d="M14 3v5h5"/>',
|
|
126
|
+
pencil: '<path d="M4 20h4L19 9a2 2 0 0 0-3-3L5 17z"/><path d="M14 6l3 3"/>',
|
|
127
|
+
'skip-forward': '<path d="M5 5v14l9-7z"/><path d="M19 5v14"/>',
|
|
128
|
+
'chevron-left': '<path d="m15 6-6 6 6 6"/>',
|
|
129
|
+
trash: '<path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13"/>',
|
|
130
|
+
'external-link': '<path d="M14 4h6v6M20 4l-9 9M19 13v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6"/>'
|
|
99
131
|
};
|
|
100
132
|
export function Icon(name, { size = 16 } = {}) {
|
|
101
133
|
const inner = ICON_PATHS[name];
|
|
@@ -175,7 +207,7 @@ export function Side({ sections = [] } = {}) {
|
|
|
175
207
|
export function Status({ left = [], right = [] } = {}) {
|
|
176
208
|
return h('footer', { class: 'app-status', role: 'contentinfo' },
|
|
177
209
|
...left.map((t, i) => h('span', { key: 'l' + i, class: 'item' }, t)),
|
|
178
|
-
h('span', { class: 'spread', 'aria-hidden': 'true' }),
|
|
210
|
+
h('span', { key: 'spread', class: 'spread', 'aria-hidden': 'true' }),
|
|
179
211
|
...right.map((t, i) => h('span', { key: 'r' + i, class: 'item' }, t))
|
|
180
212
|
);
|
|
181
213
|
}
|
|
@@ -229,9 +261,10 @@ export function Lede({ children }) {
|
|
|
229
261
|
|
|
230
262
|
export function Dot({ tone = 'on' }) {
|
|
231
263
|
const isOn = tone === 'on' || tone === 'live';
|
|
232
|
-
const cls = isOn ? 'ds-dot-on' : 'ds-dot-off';
|
|
264
|
+
const cls = 'ds-dot ' + (isOn ? 'ds-dot-on' : 'ds-dot-off');
|
|
233
265
|
const statusLabel = isOn ? 'on status indicator' : 'off status indicator';
|
|
234
|
-
|
|
266
|
+
// Drawn as a CSS circle (.ds-dot) — no decorative text glyph.
|
|
267
|
+
return h('span', { class: cls, role: 'img', 'aria-label': statusLabel });
|
|
235
268
|
}
|
|
236
269
|
|
|
237
270
|
export function Rail({ tone = 'green' }) {
|
package/src/components/voice.js
CHANGED
|
@@ -33,7 +33,7 @@ export function PttButton({ state = 'idle', mode = 'ptt', onHoldStart, onHoldEnd
|
|
|
33
33
|
ontouchend: (e) => { e.preventDefault(); end(e); }
|
|
34
34
|
},
|
|
35
35
|
h('span', { class: 'vx-ptt-glow', 'aria-hidden': 'true' }),
|
|
36
|
-
h('span', { class: 'vx-ptt-icon', 'aria-hidden': 'true' }, state === 'idle' ? Icon('mic') : '
|
|
36
|
+
h('span', { class: 'vx-ptt-icon', 'aria-hidden': 'true' }, state === 'idle' ? Icon('mic') : h('span', { class: 'ds-dot ds-dot-on', 'aria-hidden': 'true' })),
|
|
37
37
|
h('span', { class: 'vx-ptt-label' }, label)
|
|
38
38
|
);
|
|
39
39
|
}
|
|
@@ -229,11 +229,11 @@ export function AudioQueue({ segments = [], currentSegmentId = null, paused = fa
|
|
|
229
229
|
type: 'button', class: 'vx-queue-btn',
|
|
230
230
|
'aria-label': paused ? 'resume' : 'pause',
|
|
231
231
|
onclick: () => paused ? (onResume && onResume()) : (onPause && onPause())
|
|
232
|
-
}, paused ? '
|
|
232
|
+
}, paused ? Icon('play') : Icon('pause')),
|
|
233
233
|
h('button', {
|
|
234
234
|
type: 'button', class: 'vx-queue-btn',
|
|
235
235
|
'aria-label': 'skip', onclick: () => onSkip && onSkip()
|
|
236
|
-
}, '
|
|
236
|
+
}, Icon('skip-forward'))
|
|
237
237
|
),
|
|
238
238
|
h('div', { class: 'vx-queue-strip' },
|
|
239
239
|
...segments.map(s => h('button', {
|
package/src/index.js
CHANGED
|
@@ -77,6 +77,7 @@ export const applyDiff = webjsx.applyDiff;
|
|
|
77
77
|
// spoint kit paint surfaces (loading screen, HUD, editor chrome).
|
|
78
78
|
export { renderLoadingScreen } from './kits/spoint/loading-screen.js';
|
|
79
79
|
export { renderGameHud } from './kits/spoint/game-hud.js';
|
|
80
|
+
export { renderHostJoinLobby } from './kits/spoint/host-join-lobby.js';
|
|
80
81
|
|
|
81
82
|
// Re-export freddie helpers so consumers can `import { FREDDIE_PAGES } from
|
|
82
83
|
// 'anentrypoint-design'` directly.
|
|
@@ -41,7 +41,7 @@ export function renderChatMessages(container, messages) {
|
|
|
41
41
|
det.className = 'fd-chatlog-tool';
|
|
42
42
|
const sum = document.createElement('summary');
|
|
43
43
|
sum.className = 'fd-chatlog-tool-sum';
|
|
44
|
-
sum.textContent = '
|
|
44
|
+
sum.textContent = '[tool] ' + m.name + (m.argsSummary ? ' ' + m.argsSummary : '');
|
|
45
45
|
det.appendChild(sum);
|
|
46
46
|
const body = document.createElement('pre');
|
|
47
47
|
body.className = 'fd-chatlog-tool-body';
|
|
@@ -51,7 +51,7 @@ export function renderChatMessages(container, messages) {
|
|
|
51
51
|
} else {
|
|
52
52
|
const el = document.createElement('div');
|
|
53
53
|
el.className = 'fd-chatlog-msg fd-chatlog-' + (m.role === 'assistant' ? 'assistant' : 'user');
|
|
54
|
-
el.textContent = (m.role === 'assistant' ? '
|
|
54
|
+
el.textContent = (m.role === 'assistant' ? 'assistant: ' : 'user: ') + (m.content || '');
|
|
55
55
|
container.appendChild(el);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -4,7 +4,7 @@ import * as components from '../../../components.js';
|
|
|
4
4
|
import { getRecentPaths, saveRecentPath, skillLabel, renderChatMessages } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
const h = webjsx.createElement;
|
|
7
|
-
const { Panel, Receipt, Chip } = components;
|
|
7
|
+
const { Panel, Receipt, Chip, Icon } = components;
|
|
8
8
|
|
|
9
9
|
function parseSseEvents(text) {
|
|
10
10
|
const events = [];
|
|
@@ -106,7 +106,7 @@ export function makeChatPage(ctx) {
|
|
|
106
106
|
|
|
107
107
|
const selProv = h('select', { name: 'provider', onchange: (ev) => { chatState.provider = ev.target.value; } },
|
|
108
108
|
h('option', { value: '' }, configuredProviders.length ? '— auto —' : '— no providers configured —'),
|
|
109
|
-
...configuredProviders.map(p => h('option', { value: p.name, selected: chatState.provider === p.name ? 'true' : null }, (p.available ? '
|
|
109
|
+
...configuredProviders.map(p => h('option', { value: p.name, selected: chatState.provider === p.name ? 'true' : null }, (p.available ? '(on) ' : '(off) ') + p.name)));
|
|
110
110
|
|
|
111
111
|
return [
|
|
112
112
|
Panel({
|
|
@@ -137,7 +137,7 @@ export function makeChatPage(ctx) {
|
|
|
137
137
|
['or use a gateway', 'run a gateway server on localhost:4800 for local LLMs'],
|
|
138
138
|
] }) })
|
|
139
139
|
: Panel({ title: 'configured providers', children: h('div', { class: 'fd-chip-wrap' },
|
|
140
|
-
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.
|
|
140
|
+
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.configured ? [p.name, ' ', p.available ? Icon('circle-dot') : Icon('circle')] : p.name }))) }),
|
|
141
141
|
];
|
|
142
142
|
};
|
|
143
143
|
}
|
|
@@ -4,7 +4,7 @@ import * as components from '../../../components.js';
|
|
|
4
4
|
import { pre, form, skillLabel } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
const h = webjsx.createElement;
|
|
7
|
-
const { Panel, Row, Hero, Receipt, Kpi, Table, EmptyState } = components;
|
|
7
|
+
const { Panel, Row, Hero, Receipt, Kpi, Table, EmptyState, Icon } = components;
|
|
8
8
|
|
|
9
9
|
export function makeCorePages(ctx) {
|
|
10
10
|
return {
|
|
@@ -13,7 +13,7 @@ export function makeCorePages(ctx) {
|
|
|
13
13
|
const activeProj = (typeof h0.pi.projects.active === 'function') ? h0.pi.projects.active() : null;
|
|
14
14
|
const rows = list.map(p => Row({
|
|
15
15
|
key: p.name,
|
|
16
|
-
code: p.name === activeProj?.name ? '
|
|
16
|
+
code: p.name === activeProj?.name ? Icon('circle-dot') : Icon('circle'),
|
|
17
17
|
title: p.name + (p.name === activeProj?.name ? ' (active)' : ''),
|
|
18
18
|
meta: p.path,
|
|
19
19
|
onClick: () => { if (p.name !== activeProj?.name) try { h0.pi.projects.setActive(p.name); ctx.rerender(); } catch (e) { alert(e.message); } },
|
|
@@ -26,7 +26,7 @@ export function makeCorePages(ctx) {
|
|
|
26
26
|
submit: 'add',
|
|
27
27
|
onSubmit: (ev) => { try { h0.pi.projects.create({ name: ev.target.elements.name.value, path: ev.target.elements.path.value }); ctx.rerender(); } catch (e) { alert(e.message); } },
|
|
28
28
|
}) }),
|
|
29
|
-
Panel({ title: 'all projects', count: list.length, children: rows.length ? rows : EmptyState({ text: 'no projects', glyph: '
|
|
29
|
+
Panel({ title: 'all projects', count: list.length, children: rows.length ? rows : EmptyState({ text: 'no projects', glyph: Icon('square') }) }),
|
|
30
30
|
Panel({ title: 'how encapsulation works', children: Receipt({ rows: [
|
|
31
31
|
['sessions db', '<project>/sessions.db'],
|
|
32
32
|
['config', '<project>/config.json'],
|
|
@@ -78,7 +78,7 @@ export function makeCorePages(ctx) {
|
|
|
78
78
|
return [
|
|
79
79
|
Kpi({ items: [[list.length, 'sessions']] }),
|
|
80
80
|
Panel({ title: 'recent sessions', count: list.length, children: list.length === 0
|
|
81
|
-
? EmptyState({ text: 'no sessions yet — open chat and send a message', glyph: '
|
|
81
|
+
? EmptyState({ text: 'no sessions yet — open chat and send a message', glyph: Icon('thread') })
|
|
82
82
|
: Table({ headers: ['id', 'title', 'platform', 'model', 'cwd', 'skill', ''], rows }) }),
|
|
83
83
|
];
|
|
84
84
|
},
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as components from '../../../components.js';
|
|
3
3
|
import { pre } from './helpers.js';
|
|
4
4
|
|
|
5
|
-
const { Panel, Kpi, Table, EmptyState } = components;
|
|
5
|
+
const { Panel, Kpi, Table, EmptyState, Icon } = components;
|
|
6
6
|
|
|
7
7
|
export function makeOsPages(ctx) {
|
|
8
8
|
const { osSurfaces, instance } = ctx;
|
|
@@ -13,9 +13,9 @@ export function makeOsPages(ctx) {
|
|
|
13
13
|
return [
|
|
14
14
|
Kpi({ items: [[list.length, 'instances'], [activeId || '—', 'active']] }),
|
|
15
15
|
Panel({ title: 'instances', count: list.length, children: list.length === 0
|
|
16
|
-
? EmptyState({ text: 'no instances', glyph: '
|
|
16
|
+
? EmptyState({ text: 'no instances', glyph: Icon('square') })
|
|
17
17
|
: Table({ headers: ['id', 'active', 'shells', 'windows'],
|
|
18
|
-
rows: list.map(i => [i.id, i.id === activeId ? '
|
|
18
|
+
rows: list.map(i => [i.id, i.id === activeId ? Icon('circle') : '', String((i.shells || []).length), String((i.windows || []).length)]) }) }),
|
|
19
19
|
];
|
|
20
20
|
},
|
|
21
21
|
async ['os-windows']() {
|
|
@@ -24,15 +24,15 @@ export function makeOsPages(ctx) {
|
|
|
24
24
|
return [
|
|
25
25
|
Kpi({ items: [[wins.length, 'windows'], [focused ? (focused.id || focused.title || '?') : '—', 'focused']] }),
|
|
26
26
|
Panel({ title: 'windows', count: wins.length, children: wins.length === 0
|
|
27
|
-
? EmptyState({ text: 'no windows open', glyph: '
|
|
27
|
+
? EmptyState({ text: 'no windows open', glyph: Icon('square') })
|
|
28
28
|
: Table({ headers: ['id', 'title', 'min', 'max', 'pos'],
|
|
29
|
-
rows: wins.map(w => [w.id || '?', w.title || '', w.min ? '
|
|
29
|
+
rows: wins.map(w => [w.id || '?', w.title || '', w.min ? Icon('circle') : '', w.max ? Icon('circle') : '',
|
|
30
30
|
(w.el ? `${w.el.offsetLeft},${w.el.offsetTop} ${w.el.offsetWidth}×${w.el.offsetHeight}` : '')]) }) }),
|
|
31
31
|
];
|
|
32
32
|
},
|
|
33
33
|
async ['os-x']() {
|
|
34
34
|
const x = osSurfaces && osSurfaces.xServer && osSurfaces.xServer();
|
|
35
|
-
if (!x) return [Panel({ title: 'x-server', children: EmptyState({ text: 'x-server not running in this instance', glyph: '
|
|
35
|
+
if (!x) return [Panel({ title: 'x-server', children: EmptyState({ text: 'x-server not running in this instance', glyph: Icon('x') }) })];
|
|
36
36
|
return [
|
|
37
37
|
Kpi({ items: [[x.windows, 'windows'], [x.pixmaps, 'pixmaps'], [x.gcs, 'gcs'], [x.atoms, 'atoms'], [x.cursors, 'cursors']] }),
|
|
38
38
|
Panel({ title: 'display', children: pre(x) }),
|
|
@@ -43,7 +43,7 @@ export function makeOsPages(ctx) {
|
|
|
43
43
|
return [
|
|
44
44
|
Kpi({ items: [[list.length, 'paths'], [instance.id, 'instance']] }),
|
|
45
45
|
Panel({ title: 'paths', count: list.length, children: list.length === 0
|
|
46
|
-
? EmptyState({ text: 'empty fs', glyph: '
|
|
46
|
+
? EmptyState({ text: 'empty fs', glyph: Icon('square') })
|
|
47
47
|
: pre(list.join('\n')) }),
|
|
48
48
|
];
|
|
49
49
|
},
|
|
@@ -4,7 +4,7 @@ import * as components from '../../../components.js';
|
|
|
4
4
|
import { pre, form, skillLabel } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
const h = webjsx.createElement;
|
|
7
|
-
const { Panel, Row, Receipt, Kpi, Table, Section, EmptyState, Chip } = components;
|
|
7
|
+
const { Panel, Row, Receipt, Kpi, Table, Section, EmptyState, Chip, Icon } = components;
|
|
8
8
|
|
|
9
9
|
export function makeToolsPages(ctx) {
|
|
10
10
|
const { rerender } = ctx;
|
|
@@ -18,10 +18,10 @@ export function makeToolsPages(ctx) {
|
|
|
18
18
|
return [
|
|
19
19
|
Kpi({ items: [[list.length, 'sessions'], [tools.length, 'tools']] }),
|
|
20
20
|
Panel({ title: 'sessions by platform', children: Object.keys(byPlatform).length === 0
|
|
21
|
-
? EmptyState({ text: 'no data', glyph: '
|
|
21
|
+
? EmptyState({ text: 'no data', glyph: Icon('activity') })
|
|
22
22
|
: Table({ headers: ['platform', 'count'], rows: Object.entries(byPlatform).sort((a, b) => b[1] - a[1]) }) }),
|
|
23
23
|
Panel({ title: 'sessions by model', children: Object.keys(byModel).length === 0
|
|
24
|
-
? EmptyState({ text: 'no data', glyph: '
|
|
24
|
+
? EmptyState({ text: 'no data', glyph: Icon('circle-dot') })
|
|
25
25
|
: Table({ headers: ['model', 'count'], rows: Object.entries(byModel).sort((a, b) => b[1] - a[1]) }) }),
|
|
26
26
|
Panel({ title: 'tool distribution', children: Table({ headers: ['toolset', 'count', 'tools'],
|
|
27
27
|
rows: Object.entries(byToolset).map(([k, v]) => [k, v.length, v.slice(0, 4).join(', ') + (v.length > 4 ? '…' : '')]) }) }),
|
|
@@ -50,7 +50,7 @@ export function makeToolsPages(ctx) {
|
|
|
50
50
|
},
|
|
51
51
|
}) }),
|
|
52
52
|
Panel({ title: 'provider availability', children: h('div', { class: 'fd-chip-wrap' },
|
|
53
|
-
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name
|
|
53
|
+
...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: [p.name, ' ', p.configured ? (p.available ? Icon('circle-dot') : Icon('circle')) : Icon('dot')] }))
|
|
54
54
|
) }),
|
|
55
55
|
];
|
|
56
56
|
},
|
|
@@ -64,7 +64,7 @@ export function makeToolsPages(ctx) {
|
|
|
64
64
|
onSubmit: async (ev) => { try { await h0.pi.cron.create({ cron: ev.target.elements.cron.value, prompt: ev.target.elements.prompt.value }); rerender(); } catch (e) { alert(e.message); } },
|
|
65
65
|
}) }),
|
|
66
66
|
Panel({ title: 'scheduled jobs', count: list.length, children: list.length === 0
|
|
67
|
-
? EmptyState({ text: 'no cron jobs — add one above', glyph: '
|
|
67
|
+
? EmptyState({ text: 'no cron jobs — add one above', glyph: Icon('circle') })
|
|
68
68
|
: Table({ headers: ['id', 'cron', 'prompt', 'enabled'],
|
|
69
69
|
rows: list.map(j => [j.id, j.cron, (j.prompt || '').slice(0, 40), j.enabled ? 'yes' : 'no']) }) }),
|
|
70
70
|
];
|
|
@@ -74,9 +74,9 @@ export function makeToolsPages(ctx) {
|
|
|
74
74
|
const byCat = list.reduce((a, s) => { (a[s.category || 'other'] = a[s.category || 'other'] || []).push(s); return a; }, {});
|
|
75
75
|
return [
|
|
76
76
|
Kpi({ items: [[list.length, 'skills'], [Object.keys(byCat).length, 'categories']] }),
|
|
77
|
-
list.length === 0 ? EmptyState({ text: 'no skills loaded — add SKILL.md files to ~/.freddie/skills/', glyph: '
|
|
77
|
+
list.length === 0 ? EmptyState({ text: 'no skills loaded — add SKILL.md files to ~/.freddie/skills/', glyph: Icon('square') }) : null,
|
|
78
78
|
...Object.entries(byCat).map(([cat, ss]) => Panel({ title: cat, count: ss.length,
|
|
79
|
-
children: ss.length === 0 ? EmptyState({ text: 'none', glyph: '
|
|
79
|
+
children: ss.length === 0 ? EmptyState({ text: 'none', glyph: Icon('square') })
|
|
80
80
|
: Table({ headers: ['name', 'description'], rows: ss.map(s => [skillLabel(s), (s.description || '').slice(0, 120)]) }) })),
|
|
81
81
|
].filter(Boolean);
|
|
82
82
|
},
|
|
@@ -132,7 +132,7 @@ export function makeToolsPages(ctx) {
|
|
|
132
132
|
return [
|
|
133
133
|
Kpi({ items: [[list.length, 'tools'], [Object.keys(byToolset).length, 'toolsets']] }),
|
|
134
134
|
...Object.entries(byToolset).map(([ts, items]) => Panel({ title: 'toolset · ' + ts, count: items.length,
|
|
135
|
-
children: items.map(t => Row({ key: t.name, code: '
|
|
135
|
+
children: items.map(t => Row({ key: t.name, code: Icon('settings'), title: t.name, sub: (t.description || (t.schema && t.schema.description) || '').slice(0, 80) })) })),
|
|
136
136
|
];
|
|
137
137
|
},
|
|
138
138
|
async batch(h0) {
|
|
@@ -170,8 +170,8 @@ export function makeToolsPages(ctx) {
|
|
|
170
170
|
Kpi({ items: [[platforms.length, 'platforms'], [active.length, 'active']] }),
|
|
171
171
|
Panel({ title: 'platforms', count: platforms.length,
|
|
172
172
|
right: active.length > 0 ? Chip({ tone: 'ok', children: active.length + ' active' }) : Chip({ tone: 'miss', children: 'none active' }),
|
|
173
|
-
children: platforms.length === 0 ? EmptyState({ text: 'no platforms registered', glyph: '
|
|
174
|
-
: platforms.map(p => Row({ key: p.name, code: p.enabled ? '
|
|
173
|
+
children: platforms.length === 0 ? EmptyState({ text: 'no platforms registered', glyph: Icon('arrow-right') })
|
|
174
|
+
: platforms.map(p => Row({ key: p.name, code: p.enabled ? Icon('circle-dot') : Icon('circle'), title: p.name, sub: p.note || '', meta: p.enabled ? 'enabled' : '' })) }),
|
|
175
175
|
Panel({ title: 'start gateway', children: Receipt({ rows: [
|
|
176
176
|
['webhook + api_server', 'freddie gateway --port 3000'],
|
|
177
177
|
['specific platform', 'TELEGRAM_BOT_TOKEN=… freddie gateway'],
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
import { Icon } from '../../../components/shell.js';
|
|
2
|
+
|
|
1
3
|
export const ROUTES = [
|
|
2
|
-
{ path: 'projects', label: 'projects', glyph: '
|
|
3
|
-
{ path: 'home', label: 'home', glyph: '
|
|
4
|
-
{ path: 'chat', label: 'chat', glyph: '
|
|
5
|
-
{ path: 'sessions', label: 'sessions', glyph: '
|
|
6
|
-
{ path: 'agents', label: 'agents', glyph: '
|
|
7
|
-
{ path: 'analytics', label: 'analytics', glyph: '
|
|
8
|
-
{ path: 'models', label: 'models', glyph: '
|
|
9
|
-
{ path: 'logs', label: 'logs', glyph: '
|
|
10
|
-
{ path: 'cron', label: 'cron', glyph: '
|
|
11
|
-
{ path: 'skills', label: 'skills', glyph: '
|
|
12
|
-
{ path: 'config', label: 'config', glyph: '
|
|
13
|
-
{ path: 'env', label: 'keys', glyph: '
|
|
14
|
-
{ path: 'tools', label: 'tools', glyph: '
|
|
15
|
-
{ path: 'batch', label: 'batch', glyph: '
|
|
16
|
-
{ path: 'gateway', label: 'gateway', glyph: '
|
|
4
|
+
{ path: 'projects', label: 'projects', glyph: Icon('square') },
|
|
5
|
+
{ path: 'home', label: 'home', glyph: Icon('page') },
|
|
6
|
+
{ path: 'chat', label: 'chat', glyph: Icon('forum') },
|
|
7
|
+
{ path: 'sessions', label: 'sessions', glyph: Icon('thread') },
|
|
8
|
+
{ path: 'agents', label: 'agents', glyph: Icon('members') },
|
|
9
|
+
{ path: 'analytics', label: 'analytics', glyph: Icon('activity') },
|
|
10
|
+
{ path: 'models', label: 'models', glyph: Icon('circle-dot') },
|
|
11
|
+
{ path: 'logs', label: 'logs', glyph: Icon('menu') },
|
|
12
|
+
{ path: 'cron', label: 'cron', glyph: Icon('circle') },
|
|
13
|
+
{ path: 'skills', label: 'skills', glyph: Icon('check') },
|
|
14
|
+
{ path: 'config', label: 'config', glyph: Icon('settings') },
|
|
15
|
+
{ path: 'env', label: 'keys', glyph: Icon('hash') },
|
|
16
|
+
{ path: 'tools', label: 'tools', glyph: Icon('more-horizontal') },
|
|
17
|
+
{ path: 'batch', label: 'batch', glyph: Icon('square') },
|
|
18
|
+
{ path: 'gateway', label: 'gateway', glyph: Icon('arrow-right') },
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
export const OS_ROUTE_DEFS = [
|
|
20
|
-
{ path: 'os-instances', label: 'instances', glyph: '
|
|
21
|
-
{ path: 'os-windows', label: 'windows', glyph: '
|
|
22
|
-
{ path: 'os-x', label: 'x-server', glyph: '
|
|
23
|
-
{ path: 'os-fs', label: 'fs', glyph: '
|
|
22
|
+
{ path: 'os-instances', label: 'instances', glyph: Icon('square') },
|
|
23
|
+
{ path: 'os-windows', label: 'windows', glyph: Icon('screen') },
|
|
24
|
+
{ path: 'os-x', label: 'x-server', glyph: Icon('x') },
|
|
25
|
+
{ path: 'os-fs', label: 'fs', glyph: Icon('page') },
|
|
24
26
|
];
|
|
@@ -6,7 +6,7 @@ import { makeChatPage } from './freddie/pages-chat.js';
|
|
|
6
6
|
import { makeToolsPages } from './freddie/pages-tools.js';
|
|
7
7
|
import { makeOsPages } from './freddie/pages-os.js';
|
|
8
8
|
|
|
9
|
-
const { AppShell, Topbar, Side, Crumb, Status, Panel, Chip, EmptyState } = components;
|
|
9
|
+
const { AppShell, Topbar, Side, Crumb, Status, Panel, Chip, EmptyState, Icon } = components;
|
|
10
10
|
|
|
11
11
|
function pre(obj) {
|
|
12
12
|
return webjsx.createElement('pre', { class: 'fd-pre' }, typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
|
@@ -66,7 +66,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
|
|
|
66
66
|
topbar: Topbar({ brand: 'assistant', leaf: 'dashboard', items: [], active: '' }),
|
|
67
67
|
crumb: Crumb({ trail: ['assistant', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'ok', children: 'live' }) }),
|
|
68
68
|
side: buildSide(),
|
|
69
|
-
main: state.body || EmptyState({ text: loadingText || 'loading…', glyph: '
|
|
69
|
+
main: state.body || EmptyState({ text: loadingText || 'loading…', glyph: Icon('circle') }),
|
|
70
70
|
status: Status({ left: ['ds-247420 · webjsx · ' + allRoutes.length + ' routes', 'instance=' + instance.id], right: [state.ts] }),
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* Host/join lobby kit styles. Scoped under .ds-247420 at build time.
|
|
2
|
+
Token-only colors (no raw literals) per the token-lint gate. */
|
|
3
|
+
.sp-lobby {
|
|
4
|
+
position: fixed;
|
|
5
|
+
inset: 0;
|
|
6
|
+
z-index: 9500;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
background: color-mix(in srgb, var(--bg) 70%, transparent);
|
|
11
|
+
font-family: var(--ff-mono);
|
|
12
|
+
}
|
|
13
|
+
.sp-lobby-card {
|
|
14
|
+
position: relative;
|
|
15
|
+
width: min(360px, 88vw);
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 12px;
|
|
19
|
+
padding: 24px;
|
|
20
|
+
background: var(--bg-2);
|
|
21
|
+
border: 1px solid var(--bw-rule, var(--rule));
|
|
22
|
+
border-radius: var(--r-2, 10px);
|
|
23
|
+
color: var(--fg);
|
|
24
|
+
}
|
|
25
|
+
.sp-lobby-title {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-family: var(--ff-display);
|
|
28
|
+
font-size: var(--fs-h3);
|
|
29
|
+
}
|
|
30
|
+
.sp-lobby-sub {
|
|
31
|
+
margin: 0;
|
|
32
|
+
color: var(--fg-3);
|
|
33
|
+
font-size: var(--fs-small, 12px);
|
|
34
|
+
}
|
|
35
|
+
.sp-lobby-btn {
|
|
36
|
+
appearance: none;
|
|
37
|
+
border: 1px solid var(--bw-rule, var(--rule));
|
|
38
|
+
background: var(--bg-3);
|
|
39
|
+
color: var(--fg);
|
|
40
|
+
font-family: var(--ff-mono);
|
|
41
|
+
font-size: var(--fs-body);
|
|
42
|
+
padding: 10px 14px;
|
|
43
|
+
border-radius: var(--r-1, 6px);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
.sp-lobby-btn:hover { background: var(--bg-2); }
|
|
47
|
+
.sp-lobby-btn:disabled { opacity: 0.6; cursor: default; }
|
|
48
|
+
.sp-lobby-btn-primary {
|
|
49
|
+
background: var(--accent);
|
|
50
|
+
color: var(--accent-fg);
|
|
51
|
+
border-color: var(--accent);
|
|
52
|
+
}
|
|
53
|
+
.sp-lobby-join { display: flex; gap: 8px; }
|
|
54
|
+
.sp-lobby-input {
|
|
55
|
+
flex: 1;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
background: var(--bg);
|
|
58
|
+
color: var(--fg);
|
|
59
|
+
border: 1px solid var(--bw-rule, var(--rule));
|
|
60
|
+
border-radius: var(--r-1, 6px);
|
|
61
|
+
padding: 8px 10px;
|
|
62
|
+
font: var(--fs-body) var(--ff-mono);
|
|
63
|
+
outline: none;
|
|
64
|
+
}
|
|
65
|
+
.sp-lobby-input:focus { border-color: var(--accent); }
|
|
66
|
+
.sp-lobby-link { font-size: var(--fs-small, 11px); }
|
|
67
|
+
.sp-lobby-code {
|
|
68
|
+
font-family: var(--ff-display);
|
|
69
|
+
font-size: var(--fs-h2);
|
|
70
|
+
letter-spacing: 0.12em;
|
|
71
|
+
text-align: center;
|
|
72
|
+
color: var(--accent);
|
|
73
|
+
padding: 8px;
|
|
74
|
+
background: var(--bg);
|
|
75
|
+
border-radius: var(--r-1, 6px);
|
|
76
|
+
}
|
|
77
|
+
.sp-lobby-err { min-height: 16px; color: var(--danger); font-size: var(--fs-small, 11px); }
|
|
78
|
+
.sp-lobby-close {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 10px;
|
|
81
|
+
right: 12px;
|
|
82
|
+
appearance: none;
|
|
83
|
+
background: none;
|
|
84
|
+
border: none;
|
|
85
|
+
color: var(--fg-3);
|
|
86
|
+
font-size: 16px;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
line-height: 1;
|
|
89
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Host/join lobby paint surface for the spoint multiplayer setup.
|
|
2
|
+
// renderHostJoinLobby({ onHost, onJoin, onClose }) -> { node, showHosting,
|
|
3
|
+
// showError, dispose }. The consumer owns transport/navigation; this module
|
|
4
|
+
// owns layout + classes. Two actions: Host (start a room, then showHosting
|
|
5
|
+
// surfaces the code + copyable join link) and Join (enter a code or paste a
|
|
6
|
+
// join link). onHost() -> Promise|void; onJoin(rawCodeOrLink) -> void.
|
|
7
|
+
|
|
8
|
+
export function renderHostJoinLobby(opts = {}) {
|
|
9
|
+
const { onHost, onJoin, onClose } = opts
|
|
10
|
+
|
|
11
|
+
const node = document.createElement('div')
|
|
12
|
+
node.className = 'sp-lobby ds-247420'
|
|
13
|
+
node.dataset.component = 'host-join-lobby'
|
|
14
|
+
|
|
15
|
+
const card = document.createElement('div')
|
|
16
|
+
card.className = 'sp-lobby-card'
|
|
17
|
+
node.appendChild(card)
|
|
18
|
+
|
|
19
|
+
const mk = (tag, cls, text) => { const el = document.createElement(tag); if (cls) el.className = cls; if (text != null) el.textContent = text; return el }
|
|
20
|
+
|
|
21
|
+
function _idle() {
|
|
22
|
+
card.replaceChildren()
|
|
23
|
+
card.append(
|
|
24
|
+
mk('h2', 'sp-lobby-title', 'Multiplayer'),
|
|
25
|
+
mk('p', 'sp-lobby-sub', 'Host a match others can join, or join with a code.')
|
|
26
|
+
)
|
|
27
|
+
const hostBtn = mk('button', 'sp-lobby-btn sp-lobby-btn-primary', 'Host a Game')
|
|
28
|
+
hostBtn.addEventListener('click', () => { hostBtn.disabled = true; hostBtn.textContent = 'Starting...'; onHost?.() })
|
|
29
|
+
card.appendChild(hostBtn)
|
|
30
|
+
|
|
31
|
+
const joinRow = mk('div', 'sp-lobby-join')
|
|
32
|
+
const input = mk('input', 'sp-lobby-input')
|
|
33
|
+
input.type = 'text'
|
|
34
|
+
input.placeholder = 'Enter room code or link'
|
|
35
|
+
input.autocapitalize = 'characters'
|
|
36
|
+
const joinBtn = mk('button', 'sp-lobby-btn', 'Join')
|
|
37
|
+
const submit = () => { const v = (input.value || '').trim(); if (!v) { showError('Enter a code'); return } onJoin?.(v) }
|
|
38
|
+
joinBtn.addEventListener('click', submit)
|
|
39
|
+
input.addEventListener('keydown', e => { if (e.key === 'Enter') submit() })
|
|
40
|
+
joinRow.append(input, joinBtn)
|
|
41
|
+
card.appendChild(joinRow)
|
|
42
|
+
|
|
43
|
+
const err = mk('div', 'sp-lobby-err'); err.id = 'sp-lobby-err'
|
|
44
|
+
card.appendChild(err)
|
|
45
|
+
|
|
46
|
+
if (onClose) {
|
|
47
|
+
const close = mk('button', 'sp-lobby-close', 'x')
|
|
48
|
+
close.addEventListener('click', () => onClose())
|
|
49
|
+
card.appendChild(close)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function showHosting(code, link) {
|
|
54
|
+
card.replaceChildren()
|
|
55
|
+
card.append(
|
|
56
|
+
mk('h2', 'sp-lobby-title', 'Hosting'),
|
|
57
|
+
mk('p', 'sp-lobby-sub', 'Share this so others can join:'),
|
|
58
|
+
mk('div', 'sp-lobby-code', code)
|
|
59
|
+
)
|
|
60
|
+
const linkField = mk('input', 'sp-lobby-input sp-lobby-link')
|
|
61
|
+
linkField.type = 'text'; linkField.readOnly = true; linkField.value = link
|
|
62
|
+
card.appendChild(linkField)
|
|
63
|
+
const copyBtn = mk('button', 'sp-lobby-btn sp-lobby-btn-primary', 'Copy Join Link')
|
|
64
|
+
copyBtn.addEventListener('click', async () => {
|
|
65
|
+
try { await navigator.clipboard.writeText(link) } catch (_) { linkField.select(); document.execCommand && document.execCommand('copy') }
|
|
66
|
+
copyBtn.textContent = 'Copied'
|
|
67
|
+
setTimeout(() => { copyBtn.textContent = 'Copy Join Link' }, 1600)
|
|
68
|
+
})
|
|
69
|
+
card.appendChild(copyBtn)
|
|
70
|
+
card.appendChild(mk('p', 'sp-lobby-sub', 'Waiting for players...'))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function showError(msg) {
|
|
74
|
+
const err = card.querySelector('#sp-lobby-err')
|
|
75
|
+
if (err) err.textContent = msg
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_idle()
|
|
79
|
+
|
|
80
|
+
return { node, showHosting, showError, dispose: () => node.remove() }
|
|
81
|
+
}
|
package/src/kits/spoint/index.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
export { renderLoadingScreen } from './loading-screen.js';
|
|
7
7
|
export { renderGameHud } from './game-hud.js';
|
|
8
|
+
export { renderHostJoinLobby } from './host-join-lobby.js';
|
|
8
9
|
|
|
9
10
|
export const themeUrl = new URL('./loading-screen.css', import.meta.url).href;
|
|
10
11
|
export const gameHudCssUrl = new URL('./game-hud.css', import.meta.url).href;
|
|
12
|
+
export const lobbyCssUrl = new URL('./host-join-lobby.css', import.meta.url).href;
|
package/src/markdown-cache.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { renderMarkdown, ensureReady as ensureMarkdownReady } from './markdown.js';
|
|
6
6
|
import { highlightAllUnder, ensurePrism } from './highlight.js';
|
|
7
|
+
import { register } from './debug.js';
|
|
7
8
|
|
|
8
9
|
// Simple content-based hash for memoization (FNV-1a 32-bit)
|
|
9
10
|
function simpleHash(str) {
|
|
@@ -153,6 +154,9 @@ export function getCacheStats() {
|
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// Observability: expose markdown/prism cache stats live via window.__debug.
|
|
158
|
+
register('markdown-cache', () => getCacheStats());
|
|
159
|
+
|
|
156
160
|
/**
|
|
157
161
|
* Reset cache state (for testing only).
|
|
158
162
|
*/
|