anentrypoint-design 0.0.413 → 0.0.415
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.js +63 -13
- package/dist/app-shell.css +40 -594
- package/dist/colors_and_type.css +607 -47
- package/dist/index.html +227 -277
- package/dist/preview/buttons.html +39 -28
- package/dist/preview/colors-core.html +18 -8
- package/dist/preview/colors-lore.html +26 -17
- package/dist/preview/colors-semantic.html +24 -18
- package/dist/preview/dateline.html +6 -5
- package/dist/preview/dropzone.html +4 -3
- package/dist/preview/file-grid.html +10 -9
- package/dist/preview/file-row.html +14 -13
- package/dist/preview/file-toolbar.html +4 -3
- package/dist/preview/file-viewer.html +4 -3
- package/dist/preview/header.html +26 -24
- package/dist/preview/icons-unicode.html +8 -5
- package/dist/preview/index-row.html +17 -9
- package/dist/preview/inputs.html +4 -3
- package/dist/preview/manifesto.html +34 -17
- package/dist/preview/motion-default.js +77 -57
- package/dist/preview/rules.html +4 -3
- package/dist/preview/spacing.html +13 -12
- package/dist/preview/stamps-lore.html +5 -4
- package/dist/preview/stamps.html +4 -3
- package/dist/preview/theme-ink.html +7 -6
- package/dist/preview/type-display.html +14 -5
- package/dist/preview/type-mono.html +6 -5
- package/dist/preview/type-prose.html +12 -10
- package/dist/preview/type-scale.html +4 -3
- package/dist/preview/wordmarks.html +10 -7
- package/dist/site/content/globals/site.yaml +5 -3
- package/dist/site/content/pages/freddie.yaml +2 -2
- package/dist/site/content/pages/home.yaml +55 -55
- package/dist/site/theme.mjs +96 -348
- package/dist/slides/index.html +13 -13
- package/dist/src/bootstrap.js +10 -1
- package/dist/src/components/chat.js +43 -195
- package/dist/src/components/community.js +18 -166
- package/dist/src/components/content.js +30 -210
- package/dist/src/components/files-modals.js +14 -106
- package/dist/src/components/files.js +15 -116
- package/dist/src/components/freddie/helpers.js +53 -8
- package/dist/src/components/freddie.js +35 -25
- package/dist/src/components/shell.js +16 -114
- package/dist/src/components/theme-toggle.js +69 -25
- package/dist/src/components.js +92 -13
- package/dist/src/deck-stage.js +6 -4
- package/dist/src/highlight.js +51 -7
- package/dist/src/index.js +58 -12
- package/dist/src/kits/os/about-app.js +2 -3
- package/dist/src/kits/os/app-panes.css +46 -70
- package/dist/src/kits/os/browser-app.js +28 -1
- package/dist/src/kits/os/files-app.js +7 -0
- package/dist/src/kits/os/freddie/pages-chat.js +90 -75
- package/dist/src/kits/os/freddie/pages-core.js +9 -9
- package/dist/src/kits/os/freddie/pages-os.js +8 -8
- package/dist/src/kits/os/freddie/pages-tools.js +14 -14
- package/dist/src/kits/os/freddie/routes.js +21 -19
- package/dist/src/kits/os/freddie-dashboard.css +42 -21
- package/dist/src/kits/os/freddie-dashboard.js +6 -6
- package/dist/src/kits/os/icons.js +38 -16
- package/dist/src/kits/os/index.js +11 -0
- package/dist/src/kits/os/launcher.css +26 -10
- package/dist/src/kits/os/launcher.js +3 -0
- package/dist/src/kits/os/shell.js +72 -108
- package/dist/src/kits/os/terminal-app.js +1 -1
- package/dist/src/kits/os/theme.css +1330 -150
- package/dist/src/kits/os/wm.css +68 -15
- package/dist/src/kits/os/wm.js +93 -17
- package/dist/src/markdown.js +121 -12
- package/dist/src/motion.js +18 -2
- package/dist/src/page-html.js +62 -134
- package/dist/src/theme.js +106 -13
- package/dist/src/web-components/ds-chat.js +80 -9
- package/dist/ui_kits/aicat/app.js +54 -6
- package/dist/ui_kits/aicat/index.html +2 -0
- package/dist/ui_kits/blog/index.html +33 -17
- package/dist/ui_kits/chat/app.js +90 -22
- package/dist/ui_kits/chat/index.html +2 -0
- package/dist/ui_kits/community/app.js +86 -24
- package/dist/ui_kits/community/index.html +1 -0
- package/dist/ui_kits/dashboard/app.js +123 -34
- package/dist/ui_kits/dashboard/index.html +1 -0
- package/dist/ui_kits/docs/index.html +43 -33
- package/dist/ui_kits/error_404/app.js +78 -23
- package/dist/ui_kits/file_browser/README.md +1 -1
- package/dist/ui_kits/file_browser/app.js +45 -10
- package/dist/ui_kits/file_browser/index.html +1 -0
- package/dist/ui_kits/gallery/app.js +130 -51
- package/dist/ui_kits/homepage/app.js +156 -138
- package/dist/ui_kits/project_page/app.js +142 -125
- package/dist/ui_kits/project_page/index.html +2 -1
- package/dist/ui_kits/search/app.js +52 -15
- package/dist/ui_kits/settings/app.js +166 -39
- package/dist/ui_kits/signin/app.js +175 -49
- package/dist/ui_kits/signin/index.html +1 -0
- package/dist/ui_kits/slide_deck/app.js +83 -49
- package/dist/ui_kits/system_primer/app.js +57 -40
- package/dist/ui_kits/terminal/app.js +160 -43
- package/package.json +1 -1
- package/src/page-html/client-script.js +40 -11
- package/src/page-html/page-styles.js +22 -1
- package/src/page-html.js +10 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Core freddie pages: projects, home, sessions, agents, logs.
|
|
2
2
|
import * as webjsx from '../../../../vendor/webjsx/index.js';
|
|
3
3
|
import * as components from '../../../components.js';
|
|
4
|
-
import { pre, form, skillLabel } from '
|
|
4
|
+
import { pre, form, skillLabel } from '../../../components/freddie/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'],
|
|
@@ -45,15 +45,15 @@ 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: '
|
|
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"],
|
|
52
52
|
['pick skill', "software dev, research, planning — shown with descriptions"],
|
|
53
53
|
['pick model', "select a configured provider + model in the chat bar"],
|
|
54
|
-
['list tools', '/tools in chat
|
|
55
|
-
['set api key', 'keys tab
|
|
56
|
-
['add cron', 'cron tab
|
|
54
|
+
['list tools', '/tools in chat -> tools tab'],
|
|
55
|
+
['set api key', 'keys tab -> click chip to set value'],
|
|
56
|
+
['add cron', 'cron tab -> form'],
|
|
57
57
|
] }) }),
|
|
58
58
|
Panel({ title: 'host', children: Receipt({ rows: Object.entries(health).map(([k, v]) => [k, String(v)]) }) }),
|
|
59
59
|
];
|
|
@@ -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
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// OS-overlay freddie pages, only mounted when osSurfaces is provided.
|
|
2
2
|
import * as components from '../../../components.js';
|
|
3
|
-
import { pre } from '
|
|
3
|
+
import { pre } from '../../../components/freddie/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
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Tools-ish freddie pages: analytics, models, cron, skills, config, env, tools, batch, gateway.
|
|
2
2
|
import * as webjsx from '../../../../vendor/webjsx/index.js';
|
|
3
3
|
import * as components from '../../../components.js';
|
|
4
|
-
import { pre, form, skillLabel } from '
|
|
4
|
+
import { pre, form, skillLabel } from '../../../components/freddie/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
|
},
|
|
@@ -91,7 +91,7 @@ export function makeToolsPages(ctx) {
|
|
|
91
91
|
submit: 'save',
|
|
92
92
|
onSubmit: async (ev) => {
|
|
93
93
|
let v = ev.target.elements.value.value;
|
|
94
|
-
try { v = JSON.parse(v); } catch {}
|
|
94
|
+
try { v = JSON.parse(v); } catch { /* swallow: value may be a plain string, not JSON — keep it as-is */ }
|
|
95
95
|
await h0.pi.config.saveValue(ev.target.elements.key.value, v);
|
|
96
96
|
rerender();
|
|
97
97
|
},
|
|
@@ -115,7 +115,7 @@ export function makeToolsPages(ctx) {
|
|
|
115
115
|
if (typeof h0.pi.env.set === 'function') { h0.pi.env.set(k.key, v); rerender(); }
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
-
Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? '
|
|
118
|
+
Chip({ tone: k.set ? 'ok' : 'miss', children: k.key + (k.set ? ' [x]' : ' [ ]') })
|
|
119
119
|
));
|
|
120
120
|
return [
|
|
121
121
|
Kpi({ items: [[setCount, 'set'], [list.length - setCount, 'missing'], [list.length, 'total known']] }),
|
|
@@ -132,14 +132,14 @@ 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) {
|
|
139
139
|
const out = h('div', { id: 'fd-batch-out' });
|
|
140
140
|
const root = ctx.root;
|
|
141
141
|
return [
|
|
142
|
-
Section({ title: '
|
|
142
|
+
Section({ title: 'batch runner', children: [
|
|
143
143
|
Panel({ title: 'run prompts', children: form({
|
|
144
144
|
fields: [{ name: 'prompts', kind: 'textarea', placeholder: 'one prompt per line' }, { name: 'concurrency', type: 'number', value: '4' }],
|
|
145
145
|
submit: 'run',
|
|
@@ -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
|
];
|
|
@@ -3,49 +3,70 @@
|
|
|
3
3
|
Table/Receipt/Hero/Row/EmptyState/Chip) that ship their own classes via
|
|
4
4
|
247420.css. This file holds the freddie-specific chrome that AGENTS.md's
|
|
5
5
|
inline-styles ban pushed out of freddie-dashboard.js and the freddie/* page
|
|
6
|
-
modules.
|
|
6
|
+
modules. Tokens follow the kit --os-* namespace plus the base --space-*/
|
|
7
|
+
--fs-* 8pt scale, with the var(--token, fallback) idiom so the kit's
|
|
8
|
+
standalone theme.css resolves cleanly. */
|
|
7
9
|
|
|
8
10
|
.app-fd .fd-pre {
|
|
9
|
-
font-family: var(--
|
|
10
|
-
font-size: 12px;
|
|
11
|
+
font-family: var(--os-mono, monospace);
|
|
12
|
+
font-size: var(--fs-tiny, 12px);
|
|
11
13
|
white-space: pre-wrap;
|
|
12
14
|
word-break: break-all;
|
|
13
15
|
margin: 0;
|
|
14
16
|
max-height: 320px;
|
|
15
17
|
overflow: auto;
|
|
16
|
-
background: var(--
|
|
17
|
-
padding: 8px
|
|
18
|
-
border-radius: var(--
|
|
18
|
+
background: var(--os-bg-2);
|
|
19
|
+
padding: var(--space-2, 8px) var(--space-3, 16px);
|
|
20
|
+
border-radius: var(--os-radius-sm, 6px);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
.fd-root { height: 100%; overflow: hidden; display: flex; flex-direction: column; }
|
|
22
24
|
|
|
23
|
-
.fd-btn-mini { padding:
|
|
25
|
+
.fd-btn-mini { padding: var(--space-1, 4px) var(--space-2, 8px); font-size: var(--fs-tiny, 12px); }
|
|
24
26
|
|
|
25
27
|
/* chat composer fields */
|
|
26
|
-
.fd-chat-form { display: flex; flex-direction: column; gap: var(--
|
|
27
|
-
.fd-chat-row { display: flex; gap: var(--
|
|
28
|
-
.fd-chat-field { display: flex; flex-direction: column; gap: 4px; min-width: 120px; }
|
|
28
|
+
.fd-chat-form { display: flex; flex-direction: column; gap: var(--space-3, 16px); }
|
|
29
|
+
.fd-chat-row { display: flex; gap: var(--space-3, 16px); flex-wrap: wrap; }
|
|
30
|
+
.fd-chat-field { display: flex; flex-direction: column; gap: var(--space-1, 4px); min-width: 120px; }
|
|
29
31
|
.fd-chat-field-grow { flex: 2; min-width: 140px; }
|
|
30
|
-
.fd-chat-field > label { font-size:
|
|
32
|
+
.fd-chat-field > label { font-family: var(--os-mono, monospace); font-size: var(--fs-micro, 11px); color: var(--os-fg-3); letter-spacing: var(--tr-label, 0.10em); text-transform: uppercase; }
|
|
31
33
|
.fd-chat-field > input { width: 100%; box-sizing: border-box; }
|
|
32
|
-
.fd-chat-submit { display: flex; gap: var(--
|
|
34
|
+
.fd-chat-submit { display: flex; gap: var(--space-3, 16px); align-items: flex-end; }
|
|
33
35
|
.fd-chat-submit textarea { flex: 1; resize: none; min-height: 80px; }
|
|
34
36
|
.fd-chat-submit > button { align-self: flex-end; }
|
|
35
37
|
|
|
36
38
|
/* chatlog (legacy fd-chat-msgs container) */
|
|
37
39
|
.fd-chatlog {
|
|
38
40
|
max-height: 420px; overflow-y: auto;
|
|
39
|
-
background:
|
|
40
|
-
border-radius:
|
|
41
|
+
background: var(--os-bg-2);
|
|
42
|
+
border-radius: var(--os-radius-sm, 6px);
|
|
43
|
+
padding: var(--space-1, 4px); margin-top: var(--space-2, 8px);
|
|
41
44
|
}
|
|
42
|
-
.fd-chatlog-msg { padding:
|
|
43
|
-
.fd-chatlog-assistant { color: var(--
|
|
44
|
-
.fd-chatlog-tool { margin: 4px 0; padding: 4px 8px; background:
|
|
45
|
-
.fd-chatlog-tool-sum { cursor: pointer; color: var(--
|
|
46
|
-
.fd-chatlog-tool-body { margin: 4px 0 0; white-space: pre-wrap; word-break: break-all; max-height: 200px; overflow-y: auto; }
|
|
45
|
+
.fd-chatlog-msg { padding: var(--space-1, 4px) var(--space-3, 16px); background: var(--os-bg-2); white-space: pre-wrap; word-break: break-word; }
|
|
46
|
+
.fd-chatlog-assistant { color: var(--os-accent); }
|
|
47
|
+
.fd-chatlog-tool { margin: var(--space-1, 4px) 0; padding: var(--space-1, 4px) var(--space-2, 8px); background: var(--os-bg-3); border-radius: var(--os-radius-sm, 6px); font-family: var(--os-mono, monospace); font-size: var(--fs-tiny, 12px); }
|
|
48
|
+
.fd-chatlog-tool-sum { cursor: pointer; color: var(--os-red); padding: var(--space-1, 4px) 0; }
|
|
49
|
+
.fd-chatlog-tool-body { margin: var(--space-1, 4px) 0 0; white-space: pre-wrap; word-break: break-all; max-height: 200px; overflow-y: auto; }
|
|
47
50
|
|
|
48
51
|
/* chip wraps (providers, env) */
|
|
49
|
-
.fd-chip-wrap { display: flex; flex-wrap: wrap; gap:
|
|
50
|
-
.fd-chip-wrap-padded { padding: 8px 4px; }
|
|
52
|
+
.fd-chip-wrap { display: flex; flex-wrap: wrap; gap: var(--space-1, 4px); }
|
|
53
|
+
.fd-chip-wrap-padded { padding: var(--space-2, 8px) var(--space-1, 4px); }
|
|
51
54
|
.fd-env-chip { cursor: pointer; }
|
|
55
|
+
|
|
56
|
+
/* dashboard chat tab — config strip above + <ds-chat> thread below
|
|
57
|
+
--------------------------------------------------------------------- */
|
|
58
|
+
.fd-chat-config { display: flex; flex-direction: column; gap: var(--space-2, 8px); padding-bottom: var(--space-3, 12px); border-bottom: 1px solid color-mix(in oklab, var(--fg) 10%, transparent); margin-bottom: var(--space-3, 12px); }
|
|
59
|
+
.fd-chat-config .fd-chat-field { display: flex; flex-direction: column; gap: var(--space-1, 4px); min-width: 120px; }
|
|
60
|
+
.fd-chat-config .fd-chat-field > label { font-family: var(--os-mono, monospace); font-size: 10px; opacity: 0.6; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
61
|
+
.fd-chat-config .fd-chat-field > input,
|
|
62
|
+
.fd-chat-config .fd-chat-field > select { width: 100%; box-sizing: border-box; padding: var(--space-1-75) var(--space-2); background: var(--panel-1, transparent); color: var(--fg, inherit); border: 1px solid color-mix(in oklab, var(--fg) 14%, transparent); border-radius: var(--r-0); font: inherit; font-size: 12px; }
|
|
63
|
+
.fd-chat-config .fd-chat-field > input:focus-visible,
|
|
64
|
+
.fd-chat-config .fd-chat-field > select:focus-visible { outline: 2px solid var(--os-accent, #247420); outline-offset: 0; border-color: color-mix(in oklab, var(--os-accent, #247420) 60%, transparent); }
|
|
65
|
+
.fd-chat-config .fd-chat-row { display: flex; gap: var(--space-2, 8px); flex-wrap: wrap; }
|
|
66
|
+
.fd-chat-config .fd-chat-field-grow { flex: 1 1 140px; min-width: 0; }
|
|
67
|
+
|
|
68
|
+
.app-fd ds-chat.fd-dashboard-chat { flex: 1 1 auto; min-height: 280px; display: flex; flex-direction: column; overflow: hidden; }
|
|
69
|
+
|
|
70
|
+
.fd-chat-actions { display: inline-flex; gap: var(--space-1, 4px); align-items: center; }
|
|
71
|
+
.fd-chat-actions .btn-secondary { background: transparent; color: var(--danger, #c0392b); border: 1px solid color-mix(in oklab, var(--danger, #c0392b) 40%, transparent); cursor: pointer; padding: var(--space-hair) var(--space-2); border-radius: var(--r-0); font: inherit; font-size: 12px; }
|
|
72
|
+
.fd-chat-actions .btn-secondary:hover { background: color-mix(in oklab, var(--danger, #c0392b) 10%, transparent); }
|
|
@@ -6,13 +6,13 @@ 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));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
|
|
15
|
+
export function createFreddieDashboard({ instance, bootHost, osSurfaces, loadingText }) {
|
|
16
16
|
const root = document.createElement('div');
|
|
17
17
|
root.className = 'app-fd ds-247420 fd-root';
|
|
18
18
|
|
|
@@ -42,7 +42,7 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
|
|
|
42
42
|
|
|
43
43
|
function buildSide() {
|
|
44
44
|
const sections = [{
|
|
45
|
-
group: '
|
|
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,10 +63,10 @@ export function createFreddieDashboard({ instance, bootHost, osSurfaces }) {
|
|
|
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: '
|
|
67
|
-
crumb: Crumb({ trail: ['
|
|
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
|
-
main: state.body || EmptyState({ text: '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
|
}
|
|
@@ -1,17 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { ICON_PATHS } from '../../components/shell.js';
|
|
2
|
+
|
|
3
|
+
// os-window-manager app icons. Concepts here (terminal/browser/canvas/monitor/
|
|
4
|
+
// apps/xdisplay/tools/freddie) are OS-app glyphs with no equivalent in the
|
|
5
|
+
// shared ICON_PATHS UI-icon set, so they keep their own path data — but every
|
|
6
|
+
// entry renders through iconMarkup()'s attribute contract (viewBox 0 0 24 24,
|
|
7
|
+
// stroke=currentColor, shared --ds-icon-stroke var) instead of a second
|
|
8
|
+
// hardcoded stroke-width, so this module can't drift from the system's
|
|
9
|
+
// visual weight. Where a concept already exists in ICON_PATHS (close/files/
|
|
10
|
+
// validator/about/apps/home/chat), reuse that entry's path data directly.
|
|
11
|
+
const OS_PATHS = {
|
|
12
|
+
terminal: '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/>',
|
|
13
|
+
browser: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/>',
|
|
14
|
+
canvas: '<rect x="3" y="3" width="18" height="14" rx="2"/><path d="M3 17l6-5 4 3 5-4 3 2"/>',
|
|
15
|
+
files: ICON_PATHS.folder,
|
|
16
|
+
monitor: '<path d="M3 12l4-8 4 14 4-10 4 8 2-3"/>',
|
|
17
|
+
validator: ICON_PATHS.check,
|
|
18
|
+
about: ICON_PATHS.info,
|
|
19
|
+
apps: ICON_PATHS.grid,
|
|
20
|
+
plus: '<path d="M12 5v14M5 12h14"/>',
|
|
21
|
+
home: ICON_PATHS.menu,
|
|
22
|
+
xdisplay: '<rect x="3" y="4" width="18" height="13" rx="1.5"/><path d="M8 21h8M12 17v4M9 9l6 4M15 9l-6 4"/>',
|
|
23
|
+
close: ICON_PATHS.x,
|
|
24
|
+
chat: ICON_PATHS.forum,
|
|
25
|
+
tools: '<path d="M14 7l3-3 3 3-3 3-3-3zM7 14l3 3-7 7-3-3 7-7zM5 7l3-3M14 14l6 6"/>',
|
|
26
|
+
freddie: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2M8 12a4 4 0 008 0"/>',
|
|
17
27
|
};
|
|
28
|
+
|
|
29
|
+
// iconMarkup() only resolves names already registered in the shared
|
|
30
|
+
// ICON_PATHS table; this module's names are private to the os kit, so each
|
|
31
|
+
// entry is rendered against the identical attr contract iconMarkup() uses
|
|
32
|
+
// (viewBox/stroke/linecap/--ds-icon-stroke) rather than forking a second
|
|
33
|
+
// stroke-width constant.
|
|
34
|
+
export const icons = Object.fromEntries(
|
|
35
|
+
Object.entries(OS_PATHS).map(([name, inner]) => [
|
|
36
|
+
name,
|
|
37
|
+
`<svg class="ds-icon ds-icon-${name}" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="var(--ds-icon-stroke, 1.6)" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${inner}</svg>`
|
|
38
|
+
])
|
|
39
|
+
);
|
|
@@ -2,4 +2,15 @@ export { icons } from './icons.js';
|
|
|
2
2
|
export { createDesktopShell } from './shell.js';
|
|
3
3
|
export { renderWindow } from './wm.js';
|
|
4
4
|
export { renderDock } from './launcher.js';
|
|
5
|
+
|
|
6
|
+
// App modules — surfaced through the kit entry point so consumers can use a
|
|
7
|
+
// single import site. Each module also remains importable from its own path
|
|
8
|
+
// for tree-shaking and back-compat.
|
|
9
|
+
export { renderAboutApp } from './about-app.js';
|
|
10
|
+
export { renderBrowserPane } from './browser-app.js';
|
|
11
|
+
export { renderFilesApp } from './files-app.js';
|
|
12
|
+
export { renderMonitorApp } from './monitor-app.js';
|
|
13
|
+
export { renderTerminal } from './terminal-app.js';
|
|
14
|
+
export { createFreddieDashboard } from './freddie-dashboard.js';
|
|
15
|
+
|
|
5
16
|
export const themeUrl = new URL('./theme.css', import.meta.url).href;
|
|
@@ -6,24 +6,26 @@
|
|
|
6
6
|
.launcher-dock {
|
|
7
7
|
position: fixed;
|
|
8
8
|
left: 0; top: 0; bottom: 0;
|
|
9
|
-
width: 56px;
|
|
9
|
+
width: calc(var(--os-rail-w, 56px) + env(safe-area-inset-left, 0px));
|
|
10
10
|
background: var(--os-bg-2);
|
|
11
11
|
border: none;
|
|
12
12
|
display: flex;
|
|
13
13
|
flex-direction: column;
|
|
14
14
|
align-items: center;
|
|
15
|
-
padding: 8px 0;
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
padding: var(--space-2, 8px) 0;
|
|
16
|
+
padding-left: env(safe-area-inset-left, 0px);
|
|
17
|
+
gap: var(--space-2, 8px);
|
|
18
|
+
z-index: var(--z-dock);
|
|
18
19
|
pointer-events: auto;
|
|
19
20
|
font: 11px var(--os-mono);
|
|
20
21
|
color: var(--os-fg);
|
|
22
|
+
overflow: hidden;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.launcher-btn {
|
|
24
26
|
width: 40px; height: 40px;
|
|
25
27
|
background: transparent;
|
|
26
|
-
color:
|
|
28
|
+
color: var(--os-fg-2);
|
|
27
29
|
cursor: pointer;
|
|
28
30
|
border-radius: var(--os-radius-sm);
|
|
29
31
|
display: flex;
|
|
@@ -31,31 +33,45 @@
|
|
|
31
33
|
justify-content: center;
|
|
32
34
|
padding: 0;
|
|
33
35
|
font: inherit;
|
|
36
|
+
text-transform: lowercase;
|
|
34
37
|
transition: background 80ms ease, color 80ms ease, box-shadow 80ms ease;
|
|
35
38
|
}
|
|
36
39
|
.launcher-btn:hover { background: var(--panel-hover, var(--os-bg-1)); color: var(--os-fg); }
|
|
40
|
+
.launcher-btn:active { background: var(--panel-select, var(--os-accent-soft)); }
|
|
37
41
|
.launcher-btn.active {
|
|
38
42
|
background: var(--panel-select, var(--os-accent-soft));
|
|
39
43
|
color: var(--os-fg);
|
|
40
44
|
box-shadow: inset 4px 0 0 var(--os-accent);
|
|
41
45
|
}
|
|
42
|
-
.launcher-add { font-size: 20px; }
|
|
46
|
+
.launcher-add { font-size: 20px; color: var(--os-fg-2); }
|
|
47
|
+
.launcher-add:hover { color: var(--os-accent); }
|
|
43
48
|
|
|
44
49
|
.launcher-instances {
|
|
45
50
|
display: flex;
|
|
46
51
|
flex-direction: column;
|
|
47
52
|
align-items: center;
|
|
48
53
|
gap: 8px;
|
|
54
|
+
flex: 1 1 auto;
|
|
55
|
+
min-height: 0;
|
|
56
|
+
overflow-y: auto;
|
|
57
|
+
overflow-x: hidden;
|
|
58
|
+
width: 100%;
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
.launcher-row {
|
|
52
62
|
display: flex;
|
|
53
63
|
flex-direction: column;
|
|
54
64
|
align-items: center;
|
|
55
|
-
gap:
|
|
65
|
+
gap: var(--space-1, 4px);
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
.launcher-close { font-size: 11px; opacity: 0.7; width: 20px; height: 20px; }
|
|
59
|
-
.launcher-close:hover { opacity: 1; }
|
|
68
|
+
.launcher-close { font-size: 11px; opacity: 0.7; width: 20px; height: 20px; color: var(--os-fg-3); }
|
|
69
|
+
.launcher-close:hover { opacity: 1; color: var(--os-red); }
|
|
70
|
+
|
|
71
|
+
.launcher-dock + .wm-root { left: calc(var(--os-rail-w, 56px) + env(safe-area-inset-left, 0px)) !important; }
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
/* Touch tap-target floor for dock controls. */
|
|
74
|
+
@media (pointer: coarse) {
|
|
75
|
+
.launcher-btn { width: 44px; height: 44px; }
|
|
76
|
+
.launcher-close { width: 44px; height: 44px; font-size: 13px; }
|
|
77
|
+
}
|
|
@@ -14,6 +14,7 @@ export function renderDock(opts = {}) {
|
|
|
14
14
|
addBtn.className = 'launcher-btn launcher-add';
|
|
15
15
|
addBtn.textContent = '+';
|
|
16
16
|
addBtn.title = 'new instance';
|
|
17
|
+
addBtn.setAttribute('aria-label', 'new instance');
|
|
17
18
|
addBtn.addEventListener('click', () => callbacks.onNewInstance && callbacks.onNewInstance());
|
|
18
19
|
el.appendChild(addBtn);
|
|
19
20
|
|
|
@@ -42,6 +43,7 @@ export function renderDock(opts = {}) {
|
|
|
42
43
|
selBtn.className = 'launcher-btn';
|
|
43
44
|
selBtn.textContent = inst.label || inst.id;
|
|
44
45
|
selBtn.title = 'instance ' + inst.id;
|
|
46
|
+
selBtn.setAttribute('aria-label', 'instance ' + inst.id);
|
|
45
47
|
selBtn.dataset.role = 'select';
|
|
46
48
|
selBtn.dataset.instanceId = inst.id;
|
|
47
49
|
if (inst.active || inst.id === activeId) selBtn.classList.add('active');
|
|
@@ -51,6 +53,7 @@ export function renderDock(opts = {}) {
|
|
|
51
53
|
closeBtn.className = 'launcher-btn launcher-close';
|
|
52
54
|
closeBtn.textContent = 'x';
|
|
53
55
|
closeBtn.title = 'close ' + inst.id;
|
|
56
|
+
closeBtn.setAttribute('aria-label', 'close ' + inst.id);
|
|
54
57
|
closeBtn.dataset.role = 'close';
|
|
55
58
|
closeBtn.dataset.instanceId = inst.id;
|
|
56
59
|
closeBtn.addEventListener('click', e => {
|