anentrypoint-design 0.0.320 → 0.0.322

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.320",
3
+ "version": "0.0.322",
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",
@@ -14,6 +14,7 @@ import * as webjsx from '../../vendor/webjsx/index.js';
14
14
  import { ChatComposer, ChatMessage, makeThreadAutoScroll } from './chat.js';
15
15
  import { Select } from './content.js';
16
16
  import { Btn, Icon } from './shell.js';
17
+ import { BreadcrumbPath } from './files.js';
17
18
  import { initializeCachesEagerly } from '../markdown-cache.js';
18
19
 
19
20
  const h = webjsx.createElement;
@@ -116,18 +117,74 @@ function AgentControls({ agents, selectedAgent, models, selectedModel, busy, sta
116
117
  // `error`/`checking` give inline validation feedback while typing/blur (the host
117
118
  // debounces its /api/stat probe and sets these): a plain-language line renders
118
119
  // under the input (aria-describedby) and save stays disabled while either is set.
119
- function CwdBar({ cwd, editing, draft, onEdit, onSave, onCancel, onClear, onDraft, error, checking }) {
120
+ //
121
+ // Practicality upgrade: setting cwd by typing an exact absolute path from memory
122
+ // was the only path (a fresh user has no way to discover what's even browsable,
123
+ // and a regular user re-types/re-remembers the same handful of paths every
124
+ // session). Three additive affordances, all optional/host-driven so a host that
125
+ // doesn't wire them keeps the old text-only behavior:
126
+ // - `roots` (fsAllowRoots-equivalent): one-click starting points, always
127
+ // visible while editing, not buried in a separate Files-tab-only picker.
128
+ // - `recent` (host's own small MRU list, e.g. localStorage-backed): one-click
129
+ // chips for the last few cwds actually used, so switching between a
130
+ // regular handful of working directories needs no typing at all.
131
+ // - `browse` (host-driven inline directory listing): clicking "browse" asks
132
+ // the host (via onBrowse) to list a directory's subdirectories (reusing
133
+ // the same confined listing endpoint the Files tab already uses), and
134
+ // renders them as a breadcrumb + clickable dir list right in the composer
135
+ // — no round-trip through the Files tab and back required anymore.
136
+ function CwdBar({ cwd, editing, draft, onEdit, onSave, onCancel, onClear, onDraft, error, checking,
137
+ roots, recent, browse, onBrowseCrumb, onBrowseEnter, onBrowsePick, onBrowseToggle }) {
120
138
  if (editing) {
121
139
  const hint = checking ? 'checking…' : (error || null);
140
+ const rootsRow = (roots && roots.length)
141
+ ? h('div', { key: 'roots', class: 'agentchat-cwd-roots', role: 'group', 'aria-label': 'accessible folders' },
142
+ ...roots.map((r, i) => h('button', {
143
+ key: 'root' + i, type: 'button', class: 'agentchat-cwd-chip',
144
+ onclick: () => onDraft && onDraft(r.path || r),
145
+ }, r.label || r.path || r)))
146
+ : null;
147
+ const recentRow = (recent && recent.length)
148
+ ? h('div', { key: 'recent', class: 'agentchat-cwd-recent', role: 'group', 'aria-label': 'recently used folders' },
149
+ h('span', { key: 'rlbl', class: 'agentchat-cwd-recent-label' }, 'recent:'),
150
+ ...recent.map((r, i) => h('button', {
151
+ key: 'rec' + i, type: 'button', class: 'agentchat-cwd-chip',
152
+ title: r, onclick: () => onDraft && onDraft(r),
153
+ }, r.split(/[/\\]/).filter(Boolean).slice(-1)[0] || r)))
154
+ : null;
155
+ const browseToggle = onBrowseToggle
156
+ ? h('button', { key: 'browsetoggle', type: 'button', class: 'agentchat-cwd-btn agentchat-cwd-browse-toggle',
157
+ 'aria-expanded': browse ? 'true' : 'false',
158
+ onclick: () => onBrowseToggle() }, browse ? 'hide browser' : 'browse…')
159
+ : null;
160
+ const browsePanel = (browse && browse.entries)
161
+ ? h('div', { key: 'browsepanel', class: 'agentchat-cwd-browse', role: 'group', 'aria-label': 'browse folders' },
162
+ browse.segments ? BreadcrumbPath({ segments: browse.segments, root: browse.rootLabel || 'root', onNav: (i) => onBrowseCrumb && onBrowseCrumb(i) }) : null,
163
+ h('div', { key: 'browselist', class: 'agentchat-cwd-browse-list', role: 'listbox', 'aria-label': 'subdirectories' },
164
+ browse.loading
165
+ ? h('div', { key: 'browseloading', class: 'agentchat-cwd-browse-loading', role: 'status' }, 'loading…')
166
+ : (browse.entries.length
167
+ ? browse.entries.map((e, i) => h('button', {
168
+ key: 'be' + i, type: 'button', class: 'agentchat-cwd-browse-item', role: 'option',
169
+ onclick: () => onBrowseEnter && onBrowseEnter(e.path || e.name),
170
+ }, e.name || e.path))
171
+ : h('div', { key: 'browseempty', class: 'agentchat-cwd-browse-empty' }, 'no subfolders here'))),
172
+ h('button', { key: 'browseuse', type: 'button', class: 'agentchat-cwd-btn', onclick: () => onBrowsePick && onBrowsePick(browse.current) }, 'use this folder'))
173
+ : null;
122
174
  return h('div', { class: 'agentchat-cwd agentchat-cwd-editing', role: 'group', 'aria-label': 'Set working directory' },
123
- h('input', { class: 'agentchat-cwd-input', type: 'text', value: draft ?? cwd ?? '',
124
- placeholder: 'absolute path (blank = server default)',
125
- 'aria-describedby': hint ? 'agentchat-cwd-hint' : null,
126
- 'aria-invalid': error ? 'true' : null,
127
- 'aria-busy': checking ? 'true' : null,
128
- oninput: (e) => onDraft && onDraft(e.target.value) }),
129
- Btn({ key: 'cancel', onClick: () => onCancel && onCancel(), children: 'cancel' }),
130
- Btn({ key: 'save', variant: 'primary', disabled: !!(error || checking), onClick: () => onSave && onSave(), children: 'save' }),
175
+ h('div', { key: 'row1', class: 'agentchat-cwd-row' },
176
+ h('input', { class: 'agentchat-cwd-input', type: 'text', value: draft ?? cwd ?? '',
177
+ placeholder: 'absolute path (blank = server default)',
178
+ 'aria-describedby': hint ? 'agentchat-cwd-hint' : null,
179
+ 'aria-invalid': error ? 'true' : null,
180
+ 'aria-busy': checking ? 'true' : null,
181
+ oninput: (e) => onDraft && onDraft(e.target.value) }),
182
+ browseToggle,
183
+ Btn({ key: 'cancel', onClick: () => onCancel && onCancel(), children: 'cancel' }),
184
+ Btn({ key: 'save', variant: 'primary', disabled: !!(error || checking), onClick: () => onSave && onSave(), children: 'save' })),
185
+ rootsRow,
186
+ recentRow,
187
+ browsePanel,
131
188
  hint ? h('span', { key: 'hint', id: 'agentchat-cwd-hint', role: 'status', 'aria-live': 'polite',
132
189
  class: 'agentchat-cwd-hint' + (error ? ' is-error' : ' is-checking') }, hint) : null);
133
190
  }
@@ -151,9 +208,11 @@ export function AgentChat(props = {}) {
151
208
  agents = [], selectedAgent = '', models = [], selectedModel = '', modelsLoading = false, agentsLoading = false,
152
209
  messages = [], busy = false, draft = '', status, banners = [],
153
210
  cwd = '', cwdEditing = false, cwdDraft, cwdError, cwdChecking = false,
211
+ cwdRoots, cwdRecent, cwdBrowse,
154
212
  agentName, placeholder,
155
213
  onSelectAgent, onSelectModel, onSend, onStop, onNewChat, onInput,
156
214
  onCwdEdit, onCwdSave, onCwdCancel, onCwdClear, onCwdDraft,
215
+ onCwdBrowseToggle, onCwdBrowseCrumb, onCwdBrowseEnter, onCwdBrowsePick,
157
216
  canSend = true,
158
217
  suggestions = [], onSuggestionClick,
159
218
  onCopyMessage, onRetryMessage, onEditMessage,
@@ -398,7 +457,9 @@ export function AgentChat(props = {}) {
398
457
  AgentControls({ agents, selectedAgent, models, selectedModel, busy, status, modelsLoading, agentsLoading,
399
458
  onSelectAgent, onSelectModel, onNewChat, onStop, exportActions }),
400
459
  CwdBar({ cwd, editing: cwdEditing, draft: cwdDraft, error: cwdError, checking: cwdChecking,
401
- onEdit: onCwdEdit, onSave: onCwdSave, onCancel: onCwdCancel, onClear: onCwdClear, onDraft: onCwdDraft }),
460
+ roots: cwdRoots, recent: cwdRecent, browse: cwdBrowse,
461
+ onEdit: onCwdEdit, onSave: onCwdSave, onCancel: onCwdCancel, onClear: onCwdClear, onDraft: onCwdDraft,
462
+ onBrowseToggle: onCwdBrowseToggle, onBrowseCrumb: onCwdBrowseCrumb, onBrowseEnter: onCwdBrowseEnter, onBrowsePick: onCwdBrowsePick }),
402
463
  ...(banners || []).filter(Boolean),
403
464
  h('div', { class: 'agentchat-head' },
404
465
  h('h1', { class: 'agentchat-title' }, name + (selectedModel ? ' · ' + selectedModel : '')),
@@ -155,6 +155,13 @@ export function SessionMeta({ items = [] } = {}) {
155
155
  'aria-label': 'copy ' + (it.title || it.label || 'value'),
156
156
  onclick: () => it.onCopy(it.value),
157
157
  }, 'copy') : null,
158
+ // Generic secondary action (e.g. a directory row's "use as chat cwd")
159
+ // - kept distinct from onCopy since a fact can want a non-copy action,
160
+ // or (rare) both.
161
+ it.onAction ? h('button', {
162
+ key: 'a', type: 'button', class: 'ds-session-meta-action',
163
+ onclick: () => it.onAction(it.value),
164
+ }, it.actionLabel || 'use') : null,
158
165
  ].filter(Boolean))));
159
166
  }
160
167
 
@@ -74,6 +74,8 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
74
74
  brandEl.textContent = brand;
75
75
 
76
76
  const appsBtn = makeBtn(icons.apps, 'apps', 'apps');
77
+ appsBtn.setAttribute('aria-haspopup', 'menu');
78
+ appsBtn.setAttribute('aria-expanded', 'false');
77
79
  const newInstBtn = onNewInstance ? makeBtn(icons.plus, 'instance', 'add') : null;
78
80
 
79
81
  const instSwitch = document.createElement('div');
@@ -94,17 +96,29 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
94
96
 
95
97
  const appsMenu = document.createElement('div');
96
98
  appsMenu.className = 'os-menu';
99
+ appsMenu.setAttribute('role', 'menu');
100
+ appsMenu.setAttribute('aria-label', 'Apps');
97
101
 
98
102
  const sideRail = document.createElement('div');
99
103
  sideRail.className = 'os-side-rail';
104
+ sideRail.setAttribute('role', 'navigation');
105
+ sideRail.setAttribute('aria-label', 'App launcher rail');
100
106
 
101
107
  const drawer = document.createElement('div');
102
108
  drawer.className = 'os-drawer';
103
109
  drawer.setAttribute('aria-hidden', 'true');
110
+ // Full-screen overlay that traps the user's attention while open — the
111
+ // dialog role + aria-modal + aria-labelledby give a screen reader the
112
+ // same "you are now in a dialog named X" announcement a sighted user
113
+ // gets from the visual takeover.
114
+ drawer.setAttribute('role', 'dialog');
115
+ drawer.setAttribute('aria-modal', 'true');
116
+ drawer.setAttribute('aria-labelledby', 'os-drawer-title');
104
117
  const drawerHeader = document.createElement('div');
105
118
  drawerHeader.className = 'os-drawer-head';
106
119
  const drawerTitle = document.createElement('span');
107
120
  drawerTitle.className = 'os-drawer-title';
121
+ drawerTitle.id = 'os-drawer-title';
108
122
  drawerTitle.textContent = 'apps';
109
123
  const drawerClose = document.createElement('button');
110
124
  drawerClose.className = 'os-drawer-close';
@@ -121,6 +135,7 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
121
135
  for (const app of apps) {
122
136
  const iconSvg = app.icon || icons[app.id] || '';
123
137
  const menuBtn = makeBtn(iconSvg, app.name);
138
+ menuBtn.setAttribute('role', 'menuitem');
124
139
  menuBtn.addEventListener('click', () => { closeMenu(); openApp(app.id); });
125
140
  appsMenu.appendChild(menuBtn);
126
141
 
@@ -143,16 +158,40 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
143
158
 
144
159
  const taskbar = document.createElement('div');
145
160
  taskbar.className = 'os-taskbar';
161
+ // Taskbar contents are rebuilt on a 500ms poll whenever windows open/
162
+ // close/gain focus (refreshTaskbar below); aria-live announces those
163
+ // additions/removals to a screen reader, which otherwise gets no signal
164
+ // that the running-window list changed. "polite" so it never interrupts.
165
+ taskbar.setAttribute('role', 'toolbar');
166
+ taskbar.setAttribute('aria-label', 'Open windows');
167
+ taskbar.setAttribute('aria-live', 'polite');
168
+ taskbar.setAttribute('aria-relevant', 'additions removals');
146
169
 
147
170
  osRoot.append(menubar, appsMenu, taskbar);
148
171
  document.body.append(sideRail, drawer);
149
172
 
150
- function openMenu() { appsMenu.classList.add('open'); }
151
- function closeMenu() { appsMenu.classList.remove('open'); }
152
- function openDrawer() { drawer.classList.add('open'); drawer.setAttribute('aria-hidden', 'false'); }
153
- function closeDrawer() { drawer.classList.remove('open'); drawer.setAttribute('aria-hidden', 'true'); }
173
+ function openMenu() { appsMenu.classList.add('open'); appsBtn.setAttribute('aria-expanded', 'true'); }
174
+ function closeMenu() { appsMenu.classList.remove('open'); appsBtn.setAttribute('aria-expanded', 'false'); }
175
+ // Focus management: opening the drawer moves keyboard focus onto its
176
+ // close button (the first reachable control inside the now-visible
177
+ // dialog) so Tab starts inside it, not lost on a now-hidden ancestor;
178
+ // closing restores focus to whichever element opened it (homeBtn is the
179
+ // only trigger today) so the user's keyboard position isn't lost.
180
+ let drawerReturnFocus = null;
181
+ function openDrawer() {
182
+ drawerReturnFocus = document.activeElement;
183
+ drawer.classList.add('open');
184
+ drawer.setAttribute('aria-hidden', 'false');
185
+ drawerClose.focus();
186
+ }
187
+ function closeDrawer() {
188
+ drawer.classList.remove('open');
189
+ drawer.setAttribute('aria-hidden', 'true');
190
+ if (drawerReturnFocus && typeof drawerReturnFocus.focus === 'function') drawerReturnFocus.focus();
191
+ drawerReturnFocus = null;
192
+ }
154
193
 
155
- appsBtn.addEventListener('click', e => { e.stopPropagation(); appsMenu.classList.toggle('open'); });
194
+ appsBtn.addEventListener('click', e => { e.stopPropagation(); appsMenu.classList.contains('open') ? closeMenu() : openMenu(); });
156
195
  homeBtn.addEventListener('click', e => { e.stopPropagation(); drawer.classList.contains('open') ? closeDrawer() : openDrawer(); });
157
196
  drawerClose.addEventListener('click', closeDrawer);
158
197
  drawer.addEventListener('click', e => { if (e.target === drawer) closeDrawer(); });
@@ -207,6 +246,9 @@ export function createDesktopShell({ root = document.body, wm, registry, brand =
207
246
  t.type = 'button';
208
247
  t.textContent = w.title;
209
248
  t.dataset.winId = w.id;
249
+ // aria-current announces which window is the active one; a
250
+ // sighted user reads this from the .focused visual state alone.
251
+ if (w.focused) t.setAttribute('aria-current', 'true');
210
252
  t.addEventListener('click', () => wm.focus(w.id));
211
253
  taskbar.appendChild(t);
212
254
  }