anentrypoint-design 0.0.145 → 0.0.147

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.
@@ -303,11 +303,12 @@ export function CommandPalette({ open, items = [], onSelect, onClose } = {}) {
303
303
  h('div', { class: 'ov-cmd-panel', role: 'dialog', 'aria-label': 'Command palette', onkeydown: onKey },
304
304
  h('input', {
305
305
  type: 'text', class: 'ov-cmd-input', placeholder: 'Type a command…',
306
- 'aria-label': 'Filter commands',
306
+ 'aria-label': 'command search',
307
+ 'aria-controls': 'ov-cmd-list',
307
308
  oninput: (e) => { filterText = e.target.value; active = 0; renderInner(); },
308
309
  ref: (el) => { if (!el || el._ovCmdIn) return; el._ovCmdIn = true; inputEl = el; queueMicrotask(() => el.focus()); },
309
310
  }),
310
- h('div', { class: 'ov-cmd-list', role: 'listbox',
311
+ h('div', { class: 'ov-cmd-list', id: 'ov-cmd-list', role: 'listbox',
311
312
  ref: (el) => { if (!el) return; listEl = el; queueMicrotask(renderInner); } })
312
313
  )
313
314
  );
@@ -462,3 +463,91 @@ export function SettingsPopover({ title = 'Settings', open, anchorX = 0, anchorY
462
463
  }))
463
464
  );
464
465
  }
466
+
467
+ // AuthModal — centered login dialog: extension / generate / import (nsec) modes.
468
+ export function AuthModal({ mode = 'extension', error = '', busy = false, open = false, onModeChange, onConnectExtension, onGenerate, onImport, onClose } = {}) {
469
+ if (!open) return null;
470
+ const close = () => onClose && onClose();
471
+ const modes = [
472
+ { id: 'extension', label: 'Extension' },
473
+ { id: 'generate', label: 'Generate' },
474
+ { id: 'import', label: 'Import key' },
475
+ ];
476
+ let nsec = '';
477
+ const body = () => {
478
+ if (mode === 'generate') {
479
+ return [
480
+ h('p', { class: 'ov-auth-hint' }, 'Create a fresh Nostr identity. Back up the key after.'),
481
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
482
+ onclick: () => onGenerate && onGenerate() }, busy ? 'Working…' : 'Generate new key'),
483
+ ];
484
+ }
485
+ if (mode === 'import') {
486
+ return [
487
+ h('p', { class: 'ov-auth-hint' }, 'Paste an existing nsec / hex secret key.'),
488
+ h('input', {
489
+ type: 'password', class: 'ov-auth-input', placeholder: 'nsec1…',
490
+ 'aria-label': 'secret key', disabled: busy ? true : null,
491
+ oninput: (e) => { nsec = e.target.value; },
492
+ onkeydown: (e) => { if (e.key === 'Enter') { e.preventDefault(); onImport && onImport(nsec); } },
493
+ }),
494
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
495
+ onclick: () => onImport && onImport(nsec) }, busy ? 'Working…' : 'Import'),
496
+ ];
497
+ }
498
+ return [
499
+ h('p', { class: 'ov-auth-hint' }, 'Connect a NIP-07 browser extension (Alby, nos2x…).'),
500
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
501
+ onclick: () => onConnectExtension && onConnectExtension() }, busy ? 'Connecting…' : 'Connect extension'),
502
+ ];
503
+ };
504
+ return h('div', {
505
+ class: 'ov-auth-backdrop', role: 'presentation',
506
+ ref: (el) => {
507
+ if (!el || el._ovAuth) return; el._ovAuth = true;
508
+ el.addEventListener('mousedown', (e) => {
509
+ const panel = el.querySelector('.ov-auth-panel');
510
+ if (panel && !panel.contains(e.target)) close();
511
+ });
512
+ },
513
+ },
514
+ h('div', {
515
+ class: 'ov-auth-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Sign in',
516
+ onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
517
+ },
518
+ h('div', { class: 'ov-auth-head' },
519
+ h('h2', { class: 'ov-auth-title' }, 'Sign in'),
520
+ h('button', { type: 'button', class: 'ov-auth-x', 'aria-label': 'close', onclick: close }, '×')
521
+ ),
522
+ h('div', { class: 'ov-auth-tabs', role: 'tablist' },
523
+ ...modes.map(m => h('button', {
524
+ type: 'button', role: 'tab', key: 'am-' + m.id,
525
+ class: 'ov-auth-tab' + (m.id === mode ? ' is-active' : ''),
526
+ 'aria-selected': m.id === mode ? 'true' : 'false',
527
+ onclick: () => onModeChange && onModeChange(m.id),
528
+ }, m.label))
529
+ ),
530
+ h('div', { class: 'ov-auth-body' }, ...body()),
531
+ error ? h('div', { class: 'ov-auth-error', role: 'alert' }, String(error)) : null
532
+ )
533
+ );
534
+ }
535
+
536
+ // VideoLightbox — fullscreen video player overlay with backdrop dismiss.
537
+ export function VideoLightbox({ src, label = '', open = false, onClose } = {}) {
538
+ if (!open || !src) return null;
539
+ const close = () => onClose && onClose();
540
+ return h('div', {
541
+ class: 'ov-lightbox-backdrop', role: 'dialog', 'aria-modal': 'true', 'aria-label': label || 'Video',
542
+ tabindex: '-1',
543
+ onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
544
+ ref: (el) => { if (el && !el._ovLb) { el._ovLb = true; queueMicrotask(() => el.focus()); } },
545
+ onmousedown: (e) => { if (e.target === e.currentTarget) close(); },
546
+ },
547
+ h('button', { type: 'button', class: 'ov-lightbox-x', 'aria-label': 'close', onclick: close }, '×'),
548
+ h('div', { class: 'ov-lightbox-stage' },
549
+ h('video', { class: 'ov-lightbox-video', src, controls: true, autoplay: true, playsinline: true }),
550
+ label ? h('div', { class: 'ov-lightbox-label' }, label) : null
551
+ )
552
+ );
553
+ }
@@ -43,8 +43,26 @@ export function Btn({ href = '#', variant = 'default', children, onClick, 'aria-
43
43
  }, children);
44
44
  }
45
45
 
46
- export function Glyph({ children, color }) {
47
- return h('span', { class: 'glyph', style: color ? `color:${color}` : '' }, children);
46
+ export function IconButton({ icon, onClick, title, size = 'base', variant = 'ghost', disabled = false }) {
47
+ const cls = 'ds-icon-btn ds-icon-btn-' + variant + ' ds-icon-btn-' + size + (disabled ? ' is-disabled' : '');
48
+ return h('button', {
49
+ type: 'button',
50
+ class: cls,
51
+ title,
52
+ 'aria-label': title,
53
+ disabled: disabled ? true : null,
54
+ onclick: (e) => { if (disabled) { e.preventDefault(); return; } if (onClick) onClick(e); }
55
+ }, Glyph({ children: icon, size }));
56
+ }
57
+
58
+ export function Badge({ children, variant = 'default', tone = 'neutral' }) {
59
+ return h('span', { class: 'ds-badge ds-badge-' + variant + ' tone-' + tone }, children);
60
+ }
61
+
62
+ export function Glyph({ children, color, size = 'base' }) {
63
+ const fontSize = size === 'sm' ? '11px' : (size === 'lg' ? '16px' : '13px');
64
+ const style = `font-size:${fontSize}` + (color ? `;color:${color}` : '');
65
+ return h('span', { class: 'glyph', style }, children);
48
66
  }
49
67
 
50
68
  export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search } = {}) {
@@ -113,23 +131,40 @@ export function Status({ left = [], right = [] } = {}) {
113
131
  );
114
132
  }
115
133
 
134
+ // Toggle the mobile sidebar drawer. Pure-DOM because AppShell is stateless
135
+ // chrome; the class lives on .app-body and is read by the ≤900px media query.
136
+ function toggleSide(open) {
137
+ const body = document.querySelector('.app-body');
138
+ if (!body) return;
139
+ const next = open != null ? open : !body.classList.contains('side-open');
140
+ body.classList.toggle('side-open', next);
141
+ const btn = document.querySelector('.app-side-toggle');
142
+ if (btn) btn.setAttribute('aria-expanded', next ? 'true' : 'false');
143
+ }
144
+
116
145
  export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
117
146
  const hasSide = Boolean(side);
118
147
  const sideNode = hasSide ? side : h('aside', { class: 'app-side', 'aria-hidden': 'true' });
119
148
  return h('div', { class: 'app' },
120
149
  h('a', { href: '#app-main', class: 'skip-link' }, 'skip to main content'),
150
+ hasSide ? h('button', {
151
+ class: 'app-side-toggle', type: 'button',
152
+ 'aria-label': 'toggle navigation', 'aria-expanded': 'false', 'aria-controls': 'app-main',
153
+ onclick: () => toggleSide(),
154
+ }, '☰') : null,
121
155
  topbar || null,
122
156
  crumb || null,
123
157
  h('div', { class: 'app-body' + (hasSide ? '' : ' no-side') },
124
- h('div', { class: 'app-side-shell' }, sideNode),
158
+ h('div', { class: 'app-side-scrim', 'aria-hidden': 'true', onclick: () => toggleSide(false) }),
159
+ h('div', { class: 'app-side-shell', onclick: (e) => { if (e.target.closest('a')) toggleSide(false); } }, sideNode),
125
160
  h('main', { class: 'app-main' + (narrow ? ' narrow' : ''), id: 'app-main' }, ...(Array.isArray(main) ? main : [main]))
126
161
  ),
127
162
  status || null
128
163
  );
129
164
  }
130
165
 
131
- export function Heading({ level = 1, children, style = '' }) {
132
- return h('h' + level, { style }, children);
166
+ export function Heading({ level = 1, children, style = '', 'aria-level': ariaLevel }) {
167
+ return h('h' + level, { style, 'aria-level': ariaLevel != null ? String(ariaLevel) : null }, children);
133
168
  }
134
169
 
135
170
  export function Lede({ children }) {
@@ -192,6 +192,31 @@ export function VoiceSettingsModal({ open = false, mode = 'ptt', inputId, output
192
192
  );
193
193
  }
194
194
 
195
+ export function VoiceControls({ muted = false, deafened = false, cameraOn = false, screenShareOn = false, onMic, onDeafen, onCamera, onScreenShare, onSettings, onLeave } = {}) {
196
+ const btn = (cls, on, label, glyph, handler) => h('button', {
197
+ type: 'button',
198
+ class: 'vx-vc-btn ' + cls + (on ? ' vx-vc-on' : '') + (handler ? '' : ' vx-vc-disabled'),
199
+ 'aria-pressed': on ? 'true' : 'false',
200
+ 'aria-label': label,
201
+ title: label,
202
+ disabled: handler ? null : true,
203
+ onclick: handler ? (e) => handler(e) : null
204
+ },
205
+ h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, glyph)
206
+ );
207
+ return h('div', { class: 'vx-vc', role: 'toolbar', 'aria-label': 'voice controls' },
208
+ btn('vx-vc-mic', !muted, muted ? 'Unmute' : 'Mute', muted ? '🔇' : '🎙', onMic),
209
+ btn('vx-vc-deafen', !deafened, deafened ? 'Undeafen' : 'Deafen', deafened ? '🔕' : '🔊', onDeafen),
210
+ btn('vx-vc-camera', cameraOn, cameraOn ? 'Stop camera' : 'Start camera', '📷', onCamera),
211
+ btn('vx-vc-screen', screenShareOn, screenShareOn ? 'Stop sharing' : 'Share screen', '🖥', onScreenShare),
212
+ btn('vx-vc-settings', false, 'Voice settings', '⚙', onSettings),
213
+ h('button', {
214
+ type: 'button', class: 'vx-vc-btn vx-vc-leave', 'aria-label': 'Leave voice', title: 'Leave voice',
215
+ onclick: onLeave ? (e) => onLeave(e) : null
216
+ }, h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, '📞'))
217
+ );
218
+ }
219
+
195
220
  export function AudioQueue({ segments = [], currentSegmentId = null, paused = false, onReplay, onSkip, onResume, onPause } = {}) {
196
221
  if (!segments || !segments.length) {
197
222
  return h('div', { class: 'vx-queue vx-queue-empty' },
package/src/components.js CHANGED
@@ -4,13 +4,13 @@ import * as webjsx from '../vendor/webjsx/index.js';
4
4
  export const h = webjsx.createElement;
5
5
 
6
6
  export {
7
- Brand, Chip, Btn, Glyph,
7
+ Brand, Chip, Btn, Glyph, IconButton, Badge,
8
8
  Topbar, Crumb, Side, Status, AppShell,
9
9
  Heading, Lede, Dot, Rail
10
10
  } from './components/shell.js';
11
11
 
12
12
  export {
13
- Panel, Row, RowLink,
13
+ Panel, Card, Row, RowLink,
14
14
  Hero, Install, Receipt, Changelog,
15
15
  WorksList, WritingList, Manifesto, Section, PageHeader,
16
16
  Kpi, Table, SearchInput, TextField, Select, EventList,
@@ -41,11 +41,12 @@ export {
41
41
  VoiceUser, UserPanel, ChannelSidebar,
42
42
  MemberItem, MemberList,
43
43
  ChatHeader, VoiceStrip, CommunityShell,
44
- MobileHeader, ReplyBar, Banner
44
+ MobileHeader, ReplyBar, Banner,
45
+ ThreadPanel, ForumView, PageView
45
46
  } from './components/community.js';
46
47
 
47
48
  export {
48
- PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue
49
+ PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue, VoiceControls
49
50
  } from './components/voice.js';
50
51
 
51
52
  export { ThemeToggle } from './components/theme-toggle.js';
@@ -75,7 +76,8 @@ export {
75
76
 
76
77
  export {
77
78
  Tooltip, Popover, Dropdown, useLongPress, useFloating,
78
- CommandPalette, EmojiPicker, BootOverlay, SettingsPopover
79
+ CommandPalette, EmojiPicker, BootOverlay, SettingsPopover,
80
+ AuthModal, VideoLightbox
79
81
  } from './components/overlay-primitives.js';
80
82
 
81
83
  export {
@@ -134,7 +134,7 @@ export function makeChatPage(ctx) {
134
134
  ? Panel({ title: 'no providers configured', children: Receipt({ rows: [
135
135
  ['set API key', 'go to keys tab, click a provider chip to set its key'],
136
136
  ['then reload', 'refresh this page to see providers here'],
137
- ['or use acptoapi', 'run acptoapi server on localhost:4800 for local LLMs'],
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
140
  ...providers.map(p => Chip({ tone: p.configured ? (p.available ? 'ok' : 'warn') : 'miss', children: p.name + (p.configured ? (p.available ? ' ●' : ' ○') : '') }))) }),
@@ -45,7 +45,7 @@ export function makeCorePages(ctx) {
45
45
  const skills = h0.pi.skills.size;
46
46
  const health = (typeof h0.pi.health === 'function') ? h0.pi.health() : { ok: true };
47
47
  return [
48
- Hero({ title: 'freddie', body: 'open js agent harness — pi-mono · xstate · floosie · anentrypoint-design.', accent: h0.version || 'web' }),
48
+ Hero({ title: 'assistant', body: 'open js agent harness — in-page agent runtime.', accent: h0.version || 'web' }),
49
49
  Kpi({ items: [[sessions.length, 'sessions'], [tools, 'tools'], [skills, 'skills']] }),
50
50
  Panel({ title: 'quick start', children: Receipt({ rows: [
51
51
  ['open chat', "click 'chat' in sidebar — set a working directory and pick a skill"],
@@ -42,7 +42,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
42
42
 
43
43
  function buildSide() {
44
44
  const sections = [{
45
- group: 'FREDDIE',
45
+ group: 'ASSISTANT',
46
46
  items: ROUTES.map(r => ({
47
47
  glyph: r.glyph, label: r.label, href: '#fd-' + r.path,
48
48
  active: state.active === r.path,
@@ -63,8 +63,8 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces, loading
63
63
  function view() {
64
64
  const route = allRoutes.find(r => r.path === state.active) || ROUTES[1];
65
65
  return AppShell({
66
- topbar: Topbar({ brand: 'freddie', leaf: 'dashboard', items: [], active: '' }),
67
- crumb: Crumb({ trail: ['freddie', instance.id], leaf: route.path, right: state.error ? Chip({ tone: 'miss', children: 'error' }) : Chip({ tone: 'ok', children: 'live' }) }),
66
+ topbar: Topbar({ brand: 'assistant', leaf: 'dashboard', items: [], active: '' }),
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
69
  main: state.body || EmptyState({ text: loadingText || 'loading…', glyph: '◌' }),
70
70
  status: Status({ left: ['ds-247420 · webjsx · ' + allRoutes.length + ' routes', 'instance=' + instance.id], right: [state.ts] }),
@@ -39,6 +39,8 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
39
39
 
40
40
  const menubar = document.createElement('div');
41
41
  menubar.className = 'os-menubar';
42
+ menubar.setAttribute('role', 'menubar');
43
+ menubar.setAttribute('aria-label', 'Desktop menu bar');
42
44
 
43
45
  const homeBtn = makeBtn(icons.home, '', 'home');
44
46
  homeBtn.title = 'apps';