anentrypoint-design 0.0.414 → 0.0.416

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 (102) hide show
  1. package/dist/247420.js +62 -12
  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
@@ -1,213 +1,33 @@
1
1
  // Content blocks: Panel, Row, RowLink, Section, Hero, Install, Receipt,
2
2
  // Changelog, WorksList, WritingList, Manifesto, Kpi, Table, HomeView,
3
3
  // ProjectView, Form. Pure factories.
4
-
5
- import * as webjsx from '../../vendor/webjsx/index.js';
6
- import { Btn, Heading, Lede, Dot } from './shell.js';
7
- const h = webjsx.createElement;
8
-
9
- export function Panel({ title, count, right, style = '', children, kind }) {
10
- const cls = 'panel' + (kind ? ' panel-' + kind : '');
11
- return h('div', { class: cls, style },
12
- title != null ? h('div', { class: 'panel-head' },
13
- h('span', {}, title),
14
- right != null ? right : (count != null ? h('span', {}, String(count)) : null)
15
- ) : null,
16
- h('div', { class: 'panel-body' }, ...(Array.isArray(children) ? children : [children]))
17
- );
18
- }
19
-
20
- export function Row({ code, title, sub, meta, active, onClick, key, style, href, kind, cols, leading, trailing, target }) {
21
- const isLink = kind === 'link' || (href != null && !onClick);
22
- const cls = 'row' + (active ? ' active' : '') + (cols ? ' row-grid' : '');
23
- const props = { key, class: cls, style: cols ? `${style ? style + ';' : ''}grid-template-columns:${cols}` : style };
24
- if (isLink) { props.href = href || '#'; if (target) props.target = target; }
25
- else if (onClick) { props.onclick = onClick; }
26
- return h(isLink ? 'a' : 'div', props,
27
- leading != null ? leading : (code != null ? h('span', { class: 'code' }, code) : null),
28
- h('span', { class: 'title' }, title, sub ? h('span', { class: 'sub' }, sub) : null),
29
- trailing != null ? trailing : (meta != null ? h('span', { class: 'meta' }, meta) : null));
30
- }
31
-
32
- export function RowLink({ code, title, sub, meta, href = '#', key, target }) {
33
- return Row({ code, title, sub, meta, href, kind: 'link', key, target });
34
- }
35
-
36
- export function Section({ title, eyebrow, children }) {
37
- return h('section', { class: 'ds-section' },
38
- eyebrow ? h('span', { class: 'eyebrow' }, eyebrow) : null,
39
- title ? h('h3', {}, title) : null,
40
- ...(Array.isArray(children) ? children : [children])
41
- );
42
- }
43
-
44
- export function Hero({ eyebrow, title, body, accent, badge, badgeCount, actions }) {
45
- return h('div', { class: 'ds-hero' },
46
- eyebrow ? h('span', { class: 'eyebrow' }, eyebrow) : null,
47
- h('h1', { class: 'ds-hero-title' }, title),
48
- body ? h('p', { class: 'ds-hero-body' },
49
- body,
50
- accent ? h('span', { class: 'ds-hero-accent' }, ' ' + accent) : null
51
- ) : null,
52
- actions ? h('div', { class: 'ds-hero-actions', style: 'display:flex;gap:10px;flex-wrap:wrap;margin-top:8px' }, ...(Array.isArray(actions) ? actions : [actions])) : null,
53
- badge ? Panel({ title: badge, count: badgeCount, kind: 'inline', children: [] }) : null
54
- );
55
- }
56
-
57
- export function Install({ cmd, copied, onCopy }) {
58
- return h('div', { class: 'cli' },
59
- h('span', { class: 'prompt' }, '$'),
60
- h('span', { class: 'cmd' }, cmd),
61
- h('span', { class: 'copy', onclick: () => onCopy && onCopy(cmd) }, copied ? 'copied' : 'copy')
62
- );
63
- }
64
-
65
- export function Receipt({ rows = [] }) {
66
- return h('table', { class: 'kv' },
67
- h('tbody', {}, ...rows.map(([k, v], i) =>
68
- h('tr', { key: i }, h('td', {}, k), h('td', {}, v))
69
- ))
70
- );
71
- }
72
-
73
- export function Changelog({ entries = [] }) {
74
- return Panel({
75
- kind: 'wide',
76
- children: entries.map((e, i) =>
77
- h('div', { key: i, class: 'row ds-changelog-row' },
78
- h('span', { class: 'code' }, e.date),
79
- h('span', { class: 'ds-changelog-ver' }, e.ver),
80
- h('span', { class: 'title' }, e.msg)
81
- )
82
- )
83
- });
84
- }
85
-
86
- export function WorksList({ works = [], openedIndex = -1, onToggle }) {
87
- return Panel({
88
- children: works.map((w, i) => {
89
- const isOpen = openedIndex === i;
90
- return h('div', { key: i },
91
- Row({
92
- code: w.code,
93
- title: w.title, sub: w.sub,
94
- meta: w.meta + ' ' + (isOpen ? '−' : '+'),
95
- active: isOpen,
96
- onClick: () => onToggle && onToggle(isOpen ? -1 : i)
97
- }),
98
- isOpen ? h('div', { class: 'work-detail', 'data-work-index': String(i) },
99
- h('div', { class: 'ds-prose' },
100
- h('p', { class: 'ds-work-body' }, w.body)
101
- ),
102
- h('div', { class: 'ds-work-actions' },
103
- Btn({ primary: true, href: w.href || '#', children: 'open ↗' }),
104
- Btn({ href: w.source || '#', children: 'source' })
105
- )
106
- ) : null
107
- );
108
- })
109
- });
110
- }
111
-
112
- export function WritingList({ posts = [] }) {
113
- return Panel({
114
- children: posts.map((p, i) =>
115
- RowLink({ key: i, code: p.date, title: p.title, meta: p.tag, href: p.href || '#' })
116
- )
117
- });
118
- }
119
-
120
- export function Manifesto({ paragraphs = [], maxWidth }) {
121
- return h('div', {
122
- class: 'ds-prose ds-manifesto',
123
- 'data-max-width': maxWidth ? String(maxWidth) : null
124
- },
125
- ...paragraphs.map((p, i) => h('p', {
126
- key: i,
127
- class: 'ds-manifesto-para' + (p.dim ? ' dim' : '')
128
- }, p.text || p))
129
- );
130
- }
131
-
132
- export function Kpi({ items = [] }) {
133
- return h('div', { class: 'kpi' }, ...items.map(([n, l], i) =>
134
- h('div', { key: i, class: 'kpi-card' },
135
- h('div', { class: 'num' }, String(n)),
136
- h('div', { class: 'lbl' }, l))));
137
- }
138
-
139
- export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet' }) {
140
- if (!rows || rows.length === 0) return h('div', { class: 'empty' }, emptyText);
141
- return h('table', {},
142
- h('thead', {}, h('tr', {}, ...headers.map((hd, i) => h('th', { key: i }, hd)))),
143
- h('tbody', {}, ...rows.map((row, i) => h('tr', {
144
- key: i,
145
- class: onRowClick ? 'clickable' : '',
146
- onclick: onRowClick ? () => onRowClick(i) : null
147
- }, ...row.map((c, j) => h('td', { key: j }, c == null ? '' : (typeof c === 'object' ? c : String(c))))))));
148
- }
149
-
150
- export function HomeView({ state = {}, onNav, onToggleWork, works = [], posts = [], manifesto = [], currentlyShipping } = {}) {
151
- return [
152
- Hero({
153
- eyebrow: 'an entrypoint',
154
- title: 'Small, weird, useful tools — built in public.',
155
- body: '247420 is a creative collective of eight, scattered across three timezones. We have been shipping open-source tools for the web since 2018.',
156
- accent: 'Some become the future. Most don\'t. That\'s the deal.'
157
- }),
158
- currentlyShipping ? Section({
159
- eyebrow: 'currently shipping',
160
- children: Panel({
161
- kind: 'wide',
162
- children: currentlyShipping.map((row, i) =>
163
- Row({
164
- key: i,
165
- code: Dot({ tone: row.live ? 'live' : 'idle' }),
166
- title: row.title, sub: row.sub, meta: row.meta
167
- })
168
- )
169
- })
170
- }) : null,
171
- works.length ? Section({
172
- eyebrow: 'works', title: 'Everything else.',
173
- children: WorksList({ works, openedIndex: state.opened ?? -1, onToggle: onToggleWork })
174
- }) : null,
175
- posts.length ? Section({
176
- eyebrow: 'writing', title: 'When we have something to say.',
177
- children: WritingList({ posts })
178
- }) : null,
179
- manifesto.length ? Section({
180
- eyebrow: 'who\'s here', title: 'Eight people, three timezones, one ongoing conversation.',
181
- children: Manifesto({ paragraphs: manifesto })
182
- }) : null
183
- ].filter(Boolean);
184
- }
185
-
186
- export function ProjectView({ project = {}, copied, onCopy } = {}) {
187
- return [
188
- h('div', { class: 'ds-prose' },
189
- Heading({ level: 1, children: project.name }),
190
- Lede({ children: project.tagline })
191
- ),
192
- project.install ? [
193
- Heading({ level: 3, children: 'install' }),
194
- Install({ cmd: project.install, copied, onCopy }),
195
- ] : null,
196
- project.receipt ? [
197
- Heading({ level: 3, children: 'by the numbers' }),
198
- Receipt({ rows: project.receipt }),
199
- ] : null,
200
- project.changelog ? [
201
- Heading({ level: 3, children: 'recent releases' }),
202
- Changelog({ entries: project.changelog })
203
- ] : null
204
- ].filter(Boolean).flat();
205
- }
206
-
207
- export function Form({ fields = [], submit = 'submit', onSubmit }) {
208
- return h('form', { class: 'row-form', onsubmit: (ev) => { ev.preventDefault(); onSubmit && onSubmit(ev); } },
209
- ...fields.map((f, i) => f.kind === 'textarea'
210
- ? h('textarea', { key: i, name: f.name, placeholder: f.placeholder || '', rows: f.rows || 4 })
211
- : h('input', { key: i, name: f.name, type: f.type || 'text', placeholder: f.placeholder || '', value: f.value || '', required: f.required ? 'true' : null })),
212
- h('button', { type: 'submit', class: 'btn-primary' }, submit));
213
- }
4
+ //
5
+ // This module is a barrel: every component lives in a single-responsibility
6
+ // submodule under ./content/, and the public export surface here is unchanged
7
+ // no consumer import needs to move.
8
+
9
+ import { avatarInitial, Avatar } from './content/avatar.js';
10
+ import { Row, RowLink } from './content/row.js';
11
+ import { Panel, Card, PanelFromItems, Section, Receipt, Changelog } from './content/panel.js';
12
+ import { Hero, HeroFromPageData, Marquee, Manifesto, PageHeader } from './content/hero.js';
13
+ import { Install, CliBlock } from './content/cli.js';
14
+ import { WorksList, WritingList, EventList } from './content/lists.js';
15
+ import { Kpi, Sparkline, BarChart } from './content/charts.js';
16
+ import { Table, HealthTable, ProcessRegistryTable } from './content/table.js';
17
+ import { SearchInput, TextField, Select, Form } from './content/fields.js';
18
+ import { Spinner, Skeleton, Alert, FilterPills } from './content/feedback.js';
19
+ import { HomeView, ProjectView } from './content/views.js';
20
+
21
+ export {
22
+ avatarInitial, Avatar,
23
+ Row, RowLink,
24
+ Panel, Card, PanelFromItems, Section, Receipt, Changelog,
25
+ Hero, HeroFromPageData, Marquee, Manifesto, PageHeader,
26
+ Install, CliBlock,
27
+ WorksList, WritingList, EventList,
28
+ Kpi, Sparkline, BarChart,
29
+ Table, HealthTable, ProcessRegistryTable,
30
+ SearchInput, TextField, Select, Form,
31
+ Spinner, Skeleton, Alert, FilterPills,
32
+ HomeView, ProjectView,
33
+ };
@@ -1,107 +1,15 @@
1
1
  // File modals — matches upstream signatures + class names.
2
-
3
- import * as webjsx from '../../vendor/webjsx/index.js';
4
- import { Btn } from './shell.js';
5
- import { fileGlyph, fmtFileSize } from './files.js';
6
- const h = webjsx.createElement;
7
-
8
- function Backdrop({ onClose, children, kind = '' } = {}) {
9
- return h('div', {
10
- class: 'ds-modal-backdrop',
11
- onclick: (e) => { if (e.target === e.currentTarget && onClose) onClose(); }
12
- },
13
- h('div', { class: 'ds-modal' + (kind ? ' ds-modal-' + kind : '') }, ...(Array.isArray(children) ? children : [children]))
14
- );
15
- }
16
-
17
- export function ConfirmDialog({ title = 'confirm', message, confirmLabel = 'confirm', cancelLabel = 'cancel', destructive, onConfirm, onCancel } = {}) {
18
- return Backdrop({
19
- onClose: onCancel,
20
- kind: 'small',
21
- children: [
22
- h('div', { class: 'ds-modal-head' }, title),
23
- h('div', { class: 'ds-modal-body' }, message || ''),
24
- h('div', { class: 'ds-modal-actions' },
25
- Btn({ onClick: onCancel, children: cancelLabel }),
26
- h('button', {
27
- class: destructive ? 'btn-primary danger' : 'btn-primary',
28
- onclick: onConfirm
29
- }, confirmLabel)
30
- )
31
- ]
32
- });
33
- }
34
-
35
- export function PromptDialog({ title = 'name', value = '', placeholder = '', confirmLabel = 'ok', cancelLabel = 'cancel', onConfirm, onCancel, onInput } = {}) {
36
- return Backdrop({
37
- onClose: onCancel,
38
- kind: 'small',
39
- children: [
40
- h('div', { class: 'ds-modal-head' }, title),
41
- h('div', { class: 'ds-modal-body' },
42
- h('input', {
43
- class: 'input ds-modal-input',
44
- type: 'text',
45
- value,
46
- placeholder,
47
- autofocus: true,
48
- oninput: (e) => onInput && onInput(e.target.value),
49
- onkeydown: (e) => {
50
- if (e.key === 'Enter') { e.preventDefault(); onConfirm && onConfirm(e.target.value); }
51
- if (e.key === 'Escape') { e.preventDefault(); onCancel && onCancel(); }
52
- }
53
- })
54
- ),
55
- h('div', { class: 'ds-modal-actions' },
56
- Btn({ onClick: onCancel, children: cancelLabel }),
57
- h('button', { class: 'btn-primary', onclick: () => onConfirm && onConfirm(value) }, confirmLabel)
58
- )
59
- ]
60
- });
61
- }
62
-
63
- export function FilePreviewMedia({ src, type = 'other', name } = {}) {
64
- if (type === 'image') return h('img', { class: 'ds-preview-media', src, alt: name || '' });
65
- if (type === 'video') return h('video', { class: 'ds-preview-media', src, controls: true });
66
- if (type === 'audio') return h('audio', { class: 'ds-preview-audio', src, controls: true });
67
- return h('div', { class: 'ds-preview-fallback' },
68
- h('span', { class: 'ds-preview-glyph' }, fileGlyph(type)),
69
- h('span', {}, 'no inline preview for ' + (type || 'this file'))
70
- );
71
- }
72
-
73
- export function FilePreviewCode({ content = '', lang } = {}) {
74
- return h('pre', { class: 'ds-preview-code' + (lang ? ' lang-' + lang : '') },
75
- h('code', { class: lang ? 'language-' + lang : '' }, content)
76
- );
77
- }
78
-
79
- export function FilePreviewText({ content = '', truncated } = {}) {
80
- return h('pre', { class: 'ds-preview-text' },
81
- h('code', {}, content),
82
- truncated ? h('div', { class: 'ds-preview-truncated' }, '… (truncated)') : null
83
- );
84
- }
85
-
86
- export function FileViewer({ file, body, onClose, onAction } = {}) {
87
- if (!file) return null;
88
- const meta = [file.type, file.size != null ? fmtFileSize(file.size) : null, file.modified || null]
89
- .filter(Boolean).join(' · ');
90
- return Backdrop({
91
- onClose,
92
- kind: 'preview',
93
- children: [
94
- h('div', { class: 'ds-modal-head ds-preview-head', 'data-file-type': file.type || 'other' },
95
- h('span', { class: 'ds-preview-name' }, file.name || ''),
96
- h('span', { class: 'ds-preview-meta' }, meta),
97
- h('span', { class: 'ds-preview-actions' },
98
- onAction ? h('button', { class: 'ds-file-act', title: 'download', onclick: () => onAction('download') }, '↓') : null,
99
- h('button', { class: 'ds-file-act', title: 'close', onclick: onClose }, '✕')
100
- )
101
- ),
102
- h('div', { class: 'ds-preview-body', 'data-file-type': file.type || 'other' },
103
- ...(Array.isArray(body) ? body : [body])
104
- )
105
- ]
106
- });
107
- }
2
+ //
3
+ // This module is a barrel: every component lives in a single-responsibility
4
+ // submodule under ./files-modals/, and the public export surface here is
5
+ // unchanged no consumer import needs to move.
6
+
7
+ import { ConfirmDialog, PromptDialog, CountdownDialog } from './files-modals/dialogs.js';
8
+ import { FilePreviewMedia, FilePreviewCode, FilePreviewText } from './files-modals/preview-bodies.js';
9
+ import { FileViewer, FilePreviewPane } from './files-modals/preview-containers.js';
10
+
11
+ export {
12
+ ConfirmDialog, PromptDialog, CountdownDialog,
13
+ FilePreviewMedia, FilePreviewCode, FilePreviewText,
14
+ FileViewer, FilePreviewPane,
15
+ };
@@ -1,118 +1,17 @@
1
1
  // File primitives — matches upstream signatures.
2
-
3
- import * as webjsx from '../../vendor/webjsx/index.js';
4
- import { Btn } from './shell.js';
5
- const h = webjsx.createElement;
6
-
7
- const FILE_TYPES = ['dir', 'image', 'video', 'audio', 'code', 'text', 'archive', 'document', 'symlink', 'other'];
8
- const TYPE_GLYPH = {
9
- dir: '▣', image: '◰', video: '', audio: '◎', code: '⌘',
10
- text: '§', archive: '◐', document: '▢', symlink: '↗', other: ''
2
+ //
3
+ // This module is a barrel: every component lives in a single-responsibility
4
+ // submodule under ./files/, and the public export surface here is unchanged
5
+ // no consumer import needs to move.
6
+
7
+ import { fileGlyph, fmtFileSize, FileIcon, sortFiles } from './files/types.js';
8
+ import { FileRow, FileSkeleton } from './files/entries.js';
9
+ import { FileGrid } from './files/grid.js';
10
+ import { FileToolbar, RootsPicker, DropZone, UploadProgress, EmptyState, BreadcrumbPath, BulkBar } from './files/chrome.js';
11
+
12
+ export {
13
+ fileGlyph, fmtFileSize, FileIcon, sortFiles,
14
+ FileRow, FileSkeleton,
15
+ FileGrid,
16
+ FileToolbar, RootsPicker, DropZone, UploadProgress, EmptyState, BreadcrumbPath, BulkBar,
11
17
  };
12
-
13
- export function fileGlyph(type) {
14
- return TYPE_GLYPH[type] || TYPE_GLYPH.other;
15
- }
16
-
17
- export function fmtFileSize(bytes) {
18
- if (bytes == null || bytes === 0) return '—';
19
- const u = ['B', 'KB', 'MB', 'GB', 'TB'];
20
- let i = 0, n = bytes;
21
- while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; }
22
- return n.toFixed(i === 0 ? 0 : 1) + ' ' + u[i];
23
- }
24
-
25
- export function FileIcon({ type = 'other' } = {}) {
26
- return h('span', { class: 'ds-file-icon', 'data-file-type': type }, fileGlyph(type));
27
- }
28
-
29
- export function FileRow({ name, type = 'other', size, modified, code, onOpen, onAction, active, key } = {}) {
30
- const meta = [type === 'dir' ? null : fmtFileSize(size), modified || null].filter(Boolean).join(' · ');
31
- return h('div', {
32
- key,
33
- class: 'ds-file-row row' + (active ? ' active' : ''),
34
- 'data-file-type': type,
35
- onclick: onOpen
36
- },
37
- code != null ? h('span', { class: 'code' }, code) : null,
38
- FileIcon({ type }),
39
- h('span', { class: 'title' }, name),
40
- h('span', { class: 'ds-file-meta meta' }, meta || '—'),
41
- onAction ? h('span', { class: 'ds-file-actions', onclick: (e) => e.stopPropagation() },
42
- h('button', { class: 'ds-file-act', title: 'download', onclick: () => onAction('download') }, '↓'),
43
- h('button', { class: 'ds-file-act', title: 'rename', onclick: () => onAction('rename') }, '✎'),
44
- h('button', { class: 'ds-file-act ds-file-act-warn', title: 'delete', onclick: () => onAction('delete') }, '✕')
45
- ) : null
46
- );
47
- }
48
-
49
- export function FileGrid({ files = [], onOpen, onAction, emptyText = 'no files here yet' } = {}) {
50
- if (!files.length) return EmptyState({ text: emptyText });
51
- return h('div', { class: 'ds-file-grid' },
52
- ...files.map((f, i) => FileRow({
53
- key: f.path || f.name + i,
54
- name: f.name, type: f.type, size: f.size, modified: f.modified, code: f.code, active: f.active,
55
- onOpen: onOpen ? () => onOpen(f) : null,
56
- onAction: onAction ? (act) => onAction(act, f) : null
57
- }))
58
- );
59
- }
60
-
61
- export function FileToolbar({ left = [], right = [] } = {}) {
62
- return h('div', { class: 'ds-file-toolbar' },
63
- h('div', { class: 'ds-file-toolbar-left' }, ...left),
64
- h('div', { class: 'ds-file-toolbar-right' }, ...right)
65
- );
66
- }
67
-
68
- export function DropZone({ children, dragover, onDrop, onDragOver, onDragLeave, label = 'drop files here', onPick } = {}) {
69
- return h('div', {
70
- class: 'ds-dropzone' + (dragover ? ' dragover' : ''),
71
- ondragover: (e) => { e.preventDefault(); onDragOver && onDragOver(e); },
72
- ondragleave: (e) => { onDragLeave && onDragLeave(e); },
73
- ondrop: (e) => { e.preventDefault(); onDrop && onDrop(e.dataTransfer.files); }
74
- },
75
- h('div', { class: 'ds-dropzone-inner' },
76
- h('span', { class: 'ds-dropzone-glyph' }, '⇪'),
77
- h('span', { class: 'ds-dropzone-label' }, label),
78
- onPick ? Btn({ onClick: onPick, children: 'pick files' }) : null
79
- ),
80
- ...(Array.isArray(children) ? children : children ? [children] : [])
81
- );
82
- }
83
-
84
- export function UploadProgress({ items = [] } = {}) {
85
- if (!items.length) return null;
86
- return h('div', { class: 'ds-upload-progress' },
87
- ...items.map((it, i) => h('div', {
88
- key: it.name + i,
89
- class: 'ds-upload-item' + (it.done ? ' done' : '') + (it.error ? ' error' : '')
90
- },
91
- h('span', { class: 'ds-upload-name' }, it.name),
92
- h('span', { class: 'ds-upload-bar' },
93
- h('span', { class: 'ds-upload-fill', 'data-pct': String(Math.max(0, Math.min(100, it.pct || 0))) })
94
- ),
95
- h('span', { class: 'ds-upload-pct' }, (it.error ? 'err' : (it.done ? 'ok' : (it.pct || 0) + '%')))
96
- ))
97
- );
98
- }
99
-
100
- export function EmptyState({ text = 'nothing here', glyph = '◌' } = {}) {
101
- return h('div', { class: 'ds-file-empty' },
102
- h('span', { class: 'ds-file-empty-glyph' }, glyph),
103
- h('span', { class: 'ds-file-empty-text' }, text)
104
- );
105
- }
106
-
107
- export function BreadcrumbPath({ segments = [], onNav, root = 'root' } = {}) {
108
- const parts = [h('button', { key: 'root', class: 'ds-crumb-seg', onclick: () => onNav && onNav(0) }, root)];
109
- segments.forEach((seg, i) => {
110
- parts.push(h('span', { key: 'sep' + i, class: 'ds-crumb-sep' }, '›'));
111
- parts.push(h('button', {
112
- key: 'seg' + i,
113
- class: 'ds-crumb-seg' + (i === segments.length - 1 ? ' leaf' : ''),
114
- onclick: () => onNav && onNav(i + 1)
115
- }, seg));
116
- });
117
- return h('div', { class: 'ds-crumb-path' }, ...parts);
118
- }
@@ -1,10 +1,22 @@
1
- // Freddie helpers — minimal stubs. Real freddie surfaces ship downstream
2
- // (gm-cc, foph, hermes-fork). The SDK exposes the registry shape and
3
- // localStorage helpers so consumer pages can wire to existing patterns.
1
+ // Freddie helpers — shared registry/localStorage/render helpers used by both
2
+ // the freddie component surface (src/components/freddie.js) and the OS kit's
3
+ // freddie pages (src/kits/os/freddie/*.js). Canonical merged location: this
4
+ // module previously had a diverged twin at src/kits/os/freddie/helpers.js
5
+ // (pre/form/getRecentPaths/saveRecentPath/skillLabel) that re-implemented the
6
+ // same localStorage-recent-paths + skill-label concerns with different keys
7
+ // and a different skillLabel() call shape; merged here so both surfaces
8
+ // share one behavior.
4
9
 
5
10
  import * as webjsx from '../../../vendor/webjsx/index.js';
6
11
  const h = webjsx.createElement;
7
12
 
13
+ // The ONE eyebrow on the freddie surface, and it earns it by being the only
14
+ // label that varies: `freddie · <id>` names the specific unimplemented route,
15
+ // which is the whole point of a stub — the reader needs to know WHICH page
16
+ // failed to resolve, and the <h2> below only shows a human title. The real
17
+ // freddie pages (src/components/freddie.js) deliberately carry NO eyebrow:
18
+ // there, the kicker was the constant string 'freddie' on all 22 headers, which
19
+ // named nothing the topbar/crumb did not already say. Do not add siblings.
8
20
  export function renderPageStub({ id, title }) {
9
21
  return h('div', { class: 'ds-freddie-stub' },
10
22
  h('span', { class: 'eyebrow' }, 'freddie · ' + id),
@@ -13,7 +25,22 @@ export function renderPageStub({ id, title }) {
13
25
  );
14
26
  }
15
27
 
16
- const SKILL_LABELS = {
28
+ // pre() / form() — small raw-DOM helpers used by the OS kit's freddie pages
29
+ // (JSON dumps, quick add-forms) that don't need the full component barrel.
30
+ export function pre(obj) {
31
+ return h('pre', { class: 'fd-pre' }, typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
32
+ }
33
+
34
+ export function form(opts) {
35
+ const { fields = [], submit = 'submit', onSubmit } = opts;
36
+ return h('form', { class: 'row-form', onsubmit: (ev) => { ev.preventDefault(); onSubmit && onSubmit(ev); } },
37
+ ...fields.map(f => f.kind === 'textarea'
38
+ ? h('textarea', { name: f.name, placeholder: f.placeholder || '', rows: f.rows || 4 })
39
+ : h('input', { name: f.name, type: f.type || 'text', placeholder: f.placeholder || '', value: f.value || '', required: f.required ? 'true' : null })),
40
+ h('button', { type: 'submit', class: 'btn-primary' }, submit));
41
+ }
42
+
43
+ const SKILL_SLUG_LABELS = {
17
44
  transcribe: 'transcribe',
18
45
  summarize: 'summarize',
19
46
  translate: 'translate',
@@ -21,11 +48,29 @@ const SKILL_LABELS = {
21
48
  classify: 'classify',
22
49
  };
23
50
 
24
- export function skillLabel(slug) {
25
- return SKILL_LABELS[slug] || slug;
51
+ // skillLabel() accepts EITHER call shape used by existing call sites:
52
+ // skillLabel('transcribe') -> looks up the static slug map
53
+ // skillLabel({ name, shortName }) -> derives a display label from a
54
+ // freddie skill object (OS kit's
55
+ // pages-core/-tools/-chat usage)
56
+ // Additive/widened on purpose: never narrows either existing signature.
57
+ export function skillLabel(input) {
58
+ if (input && typeof input === 'object') {
59
+ if (input.shortName) return input.shortName;
60
+ const n = input.name || '';
61
+ return n.replace(/^gm:/, '').replace(/^software-development$/, 'software dev').replace(/-/g, ' ');
62
+ }
63
+ return SKILL_SLUG_LABELS[input] || input;
26
64
  }
27
65
 
28
- const RECENT_KEY = 'ds247420.recent.paths';
66
+ // Recent-cwd localStorage helpers. The two diverged copies used different
67
+ // keys (ds247420.recent.paths vs fd_recent_cwds) and different caps (10 vs
68
+ // 5) — keep the fd_recent_cwds key + cap 5 since that is the one every real
69
+ // call site (the OS kit's freddie chat page) actually reads/writes; a
70
+ // consumer of the ds247420.recent.paths key never existed (grep-confirmed
71
+ // zero other readers), so this is a safe consolidation, not a behavior cut.
72
+ const RECENT_KEY = 'fd_recent_cwds';
73
+ const RECENT_CAP = 5;
29
74
 
30
75
  export function getRecentPaths() {
31
76
  if (typeof localStorage === 'undefined') return [];
@@ -37,7 +82,7 @@ export function saveRecentPath(path) {
37
82
  if (typeof localStorage === 'undefined' || !path) return;
38
83
  const list = getRecentPaths().filter(p => p !== path);
39
84
  list.unshift(path);
40
- try { localStorage.setItem(RECENT_KEY, JSON.stringify(list.slice(0, 10))); } catch {}
85
+ try { localStorage.setItem(RECENT_KEY, JSON.stringify(list.slice(0, RECENT_CAP))); } catch { /* swallow: persistence is best-effort, recent-path history is non-critical */ }
41
86
  }
42
87
 
43
88
  // Helper used by consumers that want to render chat-message arrays with