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.
Files changed (103) hide show
  1. package/dist/247420.js +63 -13
  2. package/dist/app-shell.css +40 -594
  3. package/dist/colors_and_type.css +607 -47
  4. package/dist/index.html +227 -277
  5. package/dist/preview/buttons.html +39 -28
  6. package/dist/preview/colors-core.html +18 -8
  7. package/dist/preview/colors-lore.html +26 -17
  8. package/dist/preview/colors-semantic.html +24 -18
  9. package/dist/preview/dateline.html +6 -5
  10. package/dist/preview/dropzone.html +4 -3
  11. package/dist/preview/file-grid.html +10 -9
  12. package/dist/preview/file-row.html +14 -13
  13. package/dist/preview/file-toolbar.html +4 -3
  14. package/dist/preview/file-viewer.html +4 -3
  15. package/dist/preview/header.html +26 -24
  16. package/dist/preview/icons-unicode.html +8 -5
  17. package/dist/preview/index-row.html +17 -9
  18. package/dist/preview/inputs.html +4 -3
  19. package/dist/preview/manifesto.html +34 -17
  20. package/dist/preview/motion-default.js +77 -57
  21. package/dist/preview/rules.html +4 -3
  22. package/dist/preview/spacing.html +13 -12
  23. package/dist/preview/stamps-lore.html +5 -4
  24. package/dist/preview/stamps.html +4 -3
  25. package/dist/preview/theme-ink.html +7 -6
  26. package/dist/preview/type-display.html +14 -5
  27. package/dist/preview/type-mono.html +6 -5
  28. package/dist/preview/type-prose.html +12 -10
  29. package/dist/preview/type-scale.html +4 -3
  30. package/dist/preview/wordmarks.html +10 -7
  31. package/dist/site/content/globals/site.yaml +5 -3
  32. package/dist/site/content/pages/freddie.yaml +2 -2
  33. package/dist/site/content/pages/home.yaml +55 -55
  34. package/dist/site/theme.mjs +96 -348
  35. package/dist/slides/index.html +13 -13
  36. package/dist/src/bootstrap.js +10 -1
  37. package/dist/src/components/chat.js +43 -195
  38. package/dist/src/components/community.js +18 -166
  39. package/dist/src/components/content.js +30 -210
  40. package/dist/src/components/files-modals.js +14 -106
  41. package/dist/src/components/files.js +15 -116
  42. package/dist/src/components/freddie/helpers.js +53 -8
  43. package/dist/src/components/freddie.js +35 -25
  44. package/dist/src/components/shell.js +16 -114
  45. package/dist/src/components/theme-toggle.js +69 -25
  46. package/dist/src/components.js +92 -13
  47. package/dist/src/deck-stage.js +6 -4
  48. package/dist/src/highlight.js +51 -7
  49. package/dist/src/index.js +58 -12
  50. package/dist/src/kits/os/about-app.js +2 -3
  51. package/dist/src/kits/os/app-panes.css +46 -70
  52. package/dist/src/kits/os/browser-app.js +28 -1
  53. package/dist/src/kits/os/files-app.js +7 -0
  54. package/dist/src/kits/os/freddie/pages-chat.js +90 -75
  55. package/dist/src/kits/os/freddie/pages-core.js +9 -9
  56. package/dist/src/kits/os/freddie/pages-os.js +8 -8
  57. package/dist/src/kits/os/freddie/pages-tools.js +14 -14
  58. package/dist/src/kits/os/freddie/routes.js +21 -19
  59. package/dist/src/kits/os/freddie-dashboard.css +42 -21
  60. package/dist/src/kits/os/freddie-dashboard.js +6 -6
  61. package/dist/src/kits/os/icons.js +38 -16
  62. package/dist/src/kits/os/index.js +11 -0
  63. package/dist/src/kits/os/launcher.css +26 -10
  64. package/dist/src/kits/os/launcher.js +3 -0
  65. package/dist/src/kits/os/shell.js +72 -108
  66. package/dist/src/kits/os/terminal-app.js +1 -1
  67. package/dist/src/kits/os/theme.css +1330 -150
  68. package/dist/src/kits/os/wm.css +68 -15
  69. package/dist/src/kits/os/wm.js +93 -17
  70. package/dist/src/markdown.js +121 -12
  71. package/dist/src/motion.js +18 -2
  72. package/dist/src/page-html.js +62 -134
  73. package/dist/src/theme.js +106 -13
  74. package/dist/src/web-components/ds-chat.js +80 -9
  75. package/dist/ui_kits/aicat/app.js +54 -6
  76. package/dist/ui_kits/aicat/index.html +2 -0
  77. package/dist/ui_kits/blog/index.html +33 -17
  78. package/dist/ui_kits/chat/app.js +90 -22
  79. package/dist/ui_kits/chat/index.html +2 -0
  80. package/dist/ui_kits/community/app.js +86 -24
  81. package/dist/ui_kits/community/index.html +1 -0
  82. package/dist/ui_kits/dashboard/app.js +123 -34
  83. package/dist/ui_kits/dashboard/index.html +1 -0
  84. package/dist/ui_kits/docs/index.html +43 -33
  85. package/dist/ui_kits/error_404/app.js +78 -23
  86. package/dist/ui_kits/file_browser/README.md +1 -1
  87. package/dist/ui_kits/file_browser/app.js +45 -10
  88. package/dist/ui_kits/file_browser/index.html +1 -0
  89. package/dist/ui_kits/gallery/app.js +130 -51
  90. package/dist/ui_kits/homepage/app.js +156 -138
  91. package/dist/ui_kits/project_page/app.js +142 -125
  92. package/dist/ui_kits/project_page/index.html +2 -1
  93. package/dist/ui_kits/search/app.js +52 -15
  94. package/dist/ui_kits/settings/app.js +166 -39
  95. package/dist/ui_kits/signin/app.js +175 -49
  96. package/dist/ui_kits/signin/index.html +1 -0
  97. package/dist/ui_kits/slide_deck/app.js +83 -49
  98. package/dist/ui_kits/system_primer/app.js +57 -40
  99. package/dist/ui_kits/terminal/app.js +160 -43
  100. package/package.json +1 -1
  101. package/src/page-html/client-script.js +40 -11
  102. package/src/page-html/page-styles.js +22 -1
  103. 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 './helpers.js';
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: '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"],
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 tools tab'],
55
- ['set api key', 'keys tab click chip to set value'],
56
- ['add cron', 'cron tab form'],
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 './helpers.js';
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 ? '' : '', String((i.shells || []).length), String((i.windows || []).length)]) }) }),
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 ? '' : '', w.max ? '' : '',
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 './helpers.js';
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 + (p.configured ? (p.available ? '' : '') : ' ·') }))
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: '' }) : null,
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: '', title: t.name, sub: (t.description || (t.schema && t.schema.description) || '').slice(0, 80) })) })),
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: '// batch runner', children: [
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 ? '' : '', title: p.name, sub: p.note || '', meta: p.enabled ? '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(--ff-mono, JetBrains Mono, monospace);
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(--panel-2, #DDD3BC);
17
- padding: 8px 10px;
18
- border-radius: var(--r-1, 6px);
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: 2px 10px; font-size: 0.8em; }
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(--tool-gutter, 16px); }
27
- .fd-chat-row { display: flex; gap: var(--tool-gutter, 16px); flex-wrap: wrap; }
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: 0.75em; opacity: 0.7; letter-spacing: 0.05em; }
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(--tool-gutter, 16px); align-items: flex-end; }
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: rgba(0,0,0,0.12);
40
- border-radius: 4px; padding: 4px; margin-top: 8px;
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: 6px 10px; border-bottom: 1px solid rgba(128,128,128,0.15); white-space: pre-wrap; word-break: break-word; }
43
- .fd-chatlog-assistant { color: var(--color-accent, #7c9); }
44
- .fd-chatlog-tool { margin: 4px 0; padding: 4px 8px; background: rgba(0,0,0,0.18); border-radius: 4px; font-family: monospace; font-size: 0.85em; }
45
- .fd-chatlog-tool-sum { cursor: pointer; color: var(--color-warn, #fc9); padding: 2px 0; }
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: 6px; }
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: '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,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: '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
- 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
- export const icons = {
2
- terminal: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/></svg>',
3
- browser: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/></svg>',
4
- canvas: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="14" rx="2"/><path d="M3 17l6-5 4 3 5-4 3 2"/></svg>',
5
- files: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6a2 2 0 012-2h4l2 2h8a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>',
6
- monitor: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12l4-8 4 14 4-10 4 8 2-3"/></svg>',
7
- validator: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l4 4L19 6"/></svg>',
8
- about: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v.01M11 12h1v5h1"/></svg>',
9
- apps: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>',
10
- plus: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>',
11
- home: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>',
12
- xdisplay: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="13" rx="1.5"/><path d="M8 21h8M12 17v4M9 9l6 4M15 9l-6 4"/></svg>',
13
- close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l12 12M18 6l-12 12"/></svg>',
14
- chat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16v11H8l-4 4z"/><path d="M8 9h8M8 12h6"/></svg>',
15
- tools: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 7l3-3 3 3-3 3-3-3zM7 14l3 3-7 7-3-3 7-7zM5 7l3-3M14 14l6 6"/></svg>',
16
- freddie: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2M8 12a4 4 0 008 0"/></svg>',
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
- gap: 8px;
17
- z-index: 10000;
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: inherit;
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: 2px;
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
- .launcher-dock + .wm-root { left: 56px !important; }
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 => {