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/chat.css +38 -7
- package/colors_and_type.css +9 -3
- package/dist/247420.css +47 -10
- package/dist/247420.js +15 -15
- package/package.json +1 -1
- package/src/components/agent-chat.js +71 -10
- package/src/components/sessions.js +7 -0
- package/src/kits/os/shell.js +47 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
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('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
|
package/src/kits/os/shell.js
CHANGED
|
@@ -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
|
-
|
|
153
|
-
|
|
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.
|
|
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
|
}
|