anentrypoint-design 0.0.414 → 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 (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,199 +1,47 @@
1
1
  // Chat surface — matches upstream signatures (parts, typing, reactions,
2
2
  // receipts, aicat). Pure factories — props in, vnode out.
3
3
  // Includes ChatMessage, ChatComposer, Chat, AICat, AICatPortrait.
4
-
5
- import * as webjsx from '../../vendor/webjsx/index.js';
6
- import { renderMarkdown, ensureReady as ensureMarkdownReady } from '../markdown.js';
7
- import { highlightAllUnder, ensurePrism } from '../highlight.js';
8
- import { register } from '../debug.js';
9
-
10
- const h = webjsx.createElement;
11
- let _stats = { messages: 0, lastKindCounts: {} };
12
-
13
- export function fmtBytes(n) {
14
- if (n == null) return '';
15
- if (n < 1024) return n + ' B';
16
- if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
17
- if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + ' MB';
18
- return (n / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
19
- }
20
-
21
- // Inline-only markdown subset; safe for chat bubbles.
22
- export function renderInline(text) {
23
- if (text == null) return [];
24
- const out = [];
25
- const re = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g;
26
- let last = 0; let m; let i = 0;
27
- const push = (n) => out.push(n);
28
- while ((m = re.exec(text)) !== null) {
29
- if (m.index > last) push(h('span', { key: 's' + i + 'a' }, text.slice(last, m.index)));
30
- if (m[2] != null) push(h('strong', { key: 's' + i }, m[2]));
31
- else if (m[3] != null) push(h('em', { key: 's' + i }, m[3]));
32
- else if (m[4] != null) push(h('code', { key: 's' + i, class: 'chat-tick' }, m[4]));
33
- else if (m[5] != null) push(h('a', { key: 's' + i, href: m[6], target: '_blank', rel: 'noopener' }, m[5]));
34
- last = m.index + m[0].length; i += 1;
35
- }
36
- if (last < text.length) push(h('span', { key: 's' + i + 'a' }, text.slice(last)));
37
- return out;
38
- }
39
-
40
- const FILE_GLYPHS = { pdf: '▤', zip: '▦', tar: '▦', gz: '▦', mp4: '▶', mp3: '♪', wav: '♪', csv: '⊞', json: '{}', md: '§', txt: '§', default: '◫' };
41
- function fileGlyph(name) {
42
- const ext = String(name || '').split('.').pop().toLowerCase();
43
- return FILE_GLYPHS[ext] || FILE_GLYPHS.default;
44
- }
45
-
46
- function MdNode(p) {
47
- const refSink = (el) => {
48
- if (!el) return;
49
- if (el.dataset.mdSrc === p.text) return;
50
- el.dataset.mdSrc = p.text || '';
51
- ensureMarkdownReady().then(() => renderMarkdown(p.text || '')).then((html) => { el.innerHTML = html; });
52
- };
53
- return h('div', { class: 'chat-bubble chat-md', ref: refSink });
54
- }
55
-
56
- function CodeNode(p) {
57
- const refSink = (el) => {
58
- if (!el) return;
59
- if (el.dataset.codeKey === (p.lang || '') + '|' + (p.code || '').length) return;
60
- el.dataset.codeKey = (p.lang || '') + '|' + (p.code || '').length;
61
- ensurePrism().then(() => highlightAllUnder(el));
62
- };
63
- return h('div', { class: 'chat-bubble chat-code', ref: refSink },
64
- h('div', { class: 'chat-code-head' },
65
- h('span', { class: 'lang' }, p.lang || 'code'),
66
- p.filename ? h('span', { class: 'name' }, p.filename) : null
67
- ),
68
- h('pre', {}, h('code', { class: p.lang ? 'lang-' + p.lang + ' language-' + p.lang : '' }, p.code || ''))
69
- );
70
- }
71
-
72
- const PART_RENDERERS = {
73
- text: (p) => h('div', { class: 'chat-bubble' }, ...renderInline(p.text || '')),
74
- md: (p) => MdNode(p),
75
- code: (p) => CodeNode(p),
76
- image: (p) => h('a', { class: 'chat-image', href: p.href || p.src, target: '_blank', rel: 'noopener' },
77
- h('img', { src: p.src, alt: p.alt || '', loading: 'lazy' }),
78
- p.caption ? h('span', { class: 'cap' }, p.caption) : null),
79
- pdf: (p) => h('div', { class: 'chat-pdf' },
80
- h('div', { class: 'chat-pdf-head' },
81
- h('span', { class: 'glyph' }, '▤'),
82
- h('span', { class: 'name' }, p.name || 'document.pdf'),
83
- p.size != null ? h('span', { class: 'size' }, fmtBytes(p.size)) : null,
84
- h('a', { class: 'open', href: p.src, target: '_blank', rel: 'noopener' }, 'open ↗')
85
- ),
86
- h('embed', { src: p.src, type: 'application/pdf' })),
87
- file: (p) => h('a', { class: 'chat-file', href: p.src, target: '_blank', rel: 'noopener', download: p.name || true },
88
- h('span', { class: 'glyph' }, fileGlyph(p.name)),
89
- h('span', { class: 'meta' },
90
- h('span', { class: 'name' }, p.name || 'attachment'),
91
- h('span', { class: 'size' }, [p.kindLabel || (p.name || '').split('.').pop().toUpperCase(), p.size != null ? fmtBytes(p.size) : null].filter(Boolean).join(' · '))
92
- ),
93
- h('span', { class: 'go' }, '↓')),
94
- link: (p) => h('a', { class: 'chat-link', href: p.href, target: '_blank', rel: 'noopener' },
95
- p.thumb ? h('img', { class: 'thumb', src: p.thumb, alt: '' }) : null,
96
- h('span', { class: 'meta' },
97
- h('span', { class: 'host' }, p.host || (() => { try { return new URL(p.href).host; } catch { return ''; } })()),
98
- h('span', { class: 'title' }, p.title || p.href),
99
- p.desc ? h('span', { class: 'desc' }, p.desc) : null
100
- ))
4
+ //
5
+ // This module is a barrel: every component lives in a single-responsibility
6
+ // submodule under ./chat/, and the public export surface here is unchanged
7
+ // no consumer import needs to move.
8
+
9
+ import { fmtFileSize } from './files.js';
10
+ import { safeUrl as sharedSafeUrl, renderInline as sharedRenderInline, injectCodeCopy as sharedInjectCodeCopy } from './chat-message-parts.js';
11
+ import { hasSelectionInside, makeThreadAutoScroll } from './chat/thread-scroll.js';
12
+ import { ChatMessage } from './chat/message.js';
13
+ import { ChatComposer } from './chat/composer.js';
14
+ import { flashComposerNote } from './chat/composer-affordances.js';
15
+ import { Chat, AICat, AICatPortrait, ChatSuggestions, AICAT_FACE } from './chat/threads.js';
16
+ // Imported for its side effect: registers the 'chat' snapshot into the single
17
+ // window.__debug registry at module load, exactly as this file did before the
18
+ // split. Nothing here consumes the symbol directly.
19
+ import './chat/stats.js';
20
+
21
+ // ONE byte format across the kit: fmtFileSize (files.js) is canonical; the old
22
+ // divergent fmtBytes ('0.0 KB' for zero, no B tier) is gone — this alias keeps
23
+ // existing imports working while rendering the same string as the Files grid.
24
+ export const fmtBytes = fmtFileSize;
25
+
26
+ // safeUrl / renderInline now live in chat-message-parts.js (the shared
27
+ // message-part renderer both this file and agent-chat.js render parts
28
+ // through) re-exported here under their original names so every existing
29
+ // consumer of chat.js's public API (components.js barrel, any host importing
30
+ // directly from './components/chat.js') keeps working unchanged.
31
+ export const safeUrl = sharedSafeUrl;
32
+ export const renderInline = sharedRenderInline;
33
+
34
+ // injectCodeCopy / MdNode / CodeNode / ToolCallNode / ThinkingNode /
35
+ // PART_RENDERERS all now live in chat-message-parts.js — the one dispatch
36
+ // table this file, agent-chat.js, and any future chat surface render message
37
+ // parts through. injectCodeCopy stays exported here (re-exported, same
38
+ // signature) since it was part of this file's public surface before the
39
+ // extraction, even though nothing in-repo imports it directly today.
40
+ export const injectCodeCopy = sharedInjectCodeCopy;
41
+
42
+ export {
43
+ hasSelectionInside, makeThreadAutoScroll,
44
+ ChatMessage,
45
+ ChatComposer, flashComposerNote,
46
+ Chat, AICat, AICatPortrait, ChatSuggestions, AICAT_FACE,
101
47
  };
102
-
103
- function renderPart(p, key) {
104
- const fn = PART_RENDERERS[p.kind] || PART_RENDERERS.text;
105
- const node = fn(p);
106
- if (node && typeof node === 'object') node.props = { ...(node.props || {}), key: 'p' + key };
107
- _stats.lastKindCounts[p.kind] = (_stats.lastKindCounts[p.kind] || 0) + 1;
108
- return node;
109
- }
110
-
111
- export function ChatMessage({ who = 'them', avatar, text, parts, time, typing, key, aicat, reactions, receipt, name }) {
112
- _stats.messages += 1;
113
- const cls = 'chat-msg ' + who + (aicat && who === 'them' ? ' aicat' : '');
114
- const av = h('span', { class: 'chat-avatar' }, avatar || (who === 'you' ? 'u' : '?'));
115
- let bodyNodes;
116
- if (typing) bodyNodes = [h('span', { class: 'chat-typing', key: 'typ' }, h('span'), h('span'), h('span'))];
117
- else if (parts && parts.length) bodyNodes = parts.map((p, i) => renderPart(p, i));
118
- else bodyNodes = [h('div', { class: 'chat-bubble', key: 't' }, ...renderInline(text || ''))];
119
- const reactionRow = reactions && reactions.length
120
- ? h('div', { class: 'chat-reactions' },
121
- ...reactions.map((r, i) => h('span', { class: 'rxn' + (r.you ? ' you' : ''), key: 'r' + i },
122
- h('span', { class: 'e' }, r.emoji), h('span', { class: 'n' }, String(r.count)))))
123
- : null;
124
- const tickNode = who === 'you' && receipt
125
- ? h('span', { class: 'tick' + (receipt === 'read' ? ' read' : '') }, receipt === 'read' ? '✓✓' : '✓')
126
- : null;
127
- const metaItems = [];
128
- if (name && who === 'them') metaItems.push(h('span', { class: 'who', key: 'w' }, name));
129
- if (time) metaItems.push(h('span', { class: 't', key: 'ti' }, time));
130
- if (tickNode) metaItems.push(tickNode);
131
- const meta = metaItems.length ? h('div', { class: 'chat-meta' }, ...metaItems) : null;
132
- const stack = h('div', { class: 'chat-stack' }, ...bodyNodes, reactionRow, meta);
133
- return h('div', { key, class: cls }, who === 'you' ? stack : av, who === 'you' ? av : stack);
134
- }
135
-
136
- export function ChatComposer({ value, onInput, onSend, placeholder = 'message…', disabled }) {
137
- const send = () => {
138
- const v = (value || '').trim();
139
- if (!v || disabled) return;
140
- if (onSend) onSend(v);
141
- };
142
- return h('div', { class: 'chat-composer' },
143
- h('textarea', { value: value || '', placeholder, rows: 1,
144
- oninput: (e) => onInput && onInput(e.target.value),
145
- onkeydown: (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } } }),
146
- h('button', { class: 'send', disabled: disabled || !(value && value.trim()), onclick: send }, '↑')
147
- );
148
- }
149
-
150
- export function Chat({ title = 'chat', sub, messages = [], composer, header } = {}) {
151
- return h('div', { class: 'chat' },
152
- header || h('div', { class: 'chat-head' },
153
- h('span', { class: 'dot' }),
154
- h('span', {}, title),
155
- sub ? h('span', { class: 'sub' }, ' · ' + sub) : null,
156
- h('span', { class: 'spread' }),
157
- h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' msgs')
158
- ),
159
- h('div', { class: 'chat-thread' },
160
- ...messages.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
161
- ),
162
- composer || null
163
- );
164
- }
165
-
166
- export const AICAT_FACE = ` /\\_/\\\n( o.o )\n > ^ <`;
167
-
168
- export function AICatPortrait({ name = 'aicat', status = 'idle', face } = {}) {
169
- return h('div', { class: 'aicat-portrait' },
170
- h('pre', { class: 'aicat-face' }, face || AICAT_FACE),
171
- h('div', { class: 'aicat-meta' },
172
- h('span', { class: 'name' }, name),
173
- h('span', { class: 'status' }, h('span', { class: 'dot' }, '● '), status)
174
- )
175
- );
176
- }
177
-
178
- export function AICat({ name = 'aicat', messages = [], thinking, composer, status = 'online · purring' } = {}) {
179
- const annotated = messages.map((m) =>
180
- m.who === 'them' ? { ...m, aicat: true, avatar: m.avatar || '=^.^=' } : m);
181
- const all = thinking
182
- ? [...annotated, { who: 'them', aicat: true, avatar: '=^.^=', typing: true, key: '_thinking' }]
183
- : annotated;
184
- return h('div', { class: 'chat' },
185
- h('div', { class: 'chat-head' },
186
- h('span', { class: 'dot' }),
187
- h('span', {}, name),
188
- h('span', { class: 'sub' }, ' · ' + status),
189
- h('span', { class: 'spread' }),
190
- h('span', { class: 'sub' }, String(messages.length).padStart(2, '0') + ' turns')
191
- ),
192
- h('div', { class: 'chat-thread' },
193
- ...all.map((m, i) => ChatMessage({ ...m, key: m.key != null ? m.key : i }))
194
- ),
195
- composer || null
196
- );
197
- }
198
-
199
- register('chat', () => ({ messages: _stats.messages, lastKindCounts: { ..._stats.lastKindCounts } }));
@@ -1,167 +1,19 @@
1
1
  // Community surface — matches upstream signatures.
2
-
3
- import * as webjsx from '../../vendor/webjsx/index.js';
4
- const h = webjsx.createElement;
5
-
6
- export function ServerIcon({ id, name, icon, active, badge, onClick } = {}) {
7
- const initials = (name || '?').slice(0, 2).toUpperCase();
8
- return h('div', { class: 'cm-server-icon' + (active ? ' active' : ''), onclick: onClick, title: name, 'data-id': id },
9
- h('span', { class: 'cm-server-pill' }),
10
- icon ? h('img', { src: icon, alt: name }) : h('span', {}, initials),
11
- badge ? h('span', { class: 'cm-server-badge' }, badge > 99 ? '99+' : String(badge)) : null
12
- );
13
- }
14
-
15
- export function ServerRail({ servers = [], activeId, onSelect, onAdd } = {}) {
16
- return h('div', { class: 'cm-server-rail' },
17
- h('a', { class: 'cm-server-back', href: '../', title: 'Back' }, '◰'),
18
- h('div', { class: 'cm-server-sep' }),
19
- ...servers.map(s => ServerIcon({ ...s, active: s.id === activeId, onClick: () => onSelect && onSelect(s.id) })),
20
- onAdd ? h('button', { class: 'cm-server-add', onclick: onAdd, title: 'Add server' }, '+') : null
21
- );
22
- }
23
-
24
- export function ChannelItem({ id, name, type = 'text', active, voiceActive, onClick, onContext } = {}) {
25
- const icon = type === 'voice' ? '🔊' : type === 'forum' ? '◻' : '#';
26
- return h('div', {
27
- class: 'cm-channel-item' + (active ? ' active' : '') + (voiceActive ? ' voice-active' : ''),
28
- 'data-id': id,
29
- onclick: onClick,
30
- oncontextmenu: (e) => { e.preventDefault(); onContext && onContext(id, e.clientX, e.clientY); }
31
- },
32
- h('span', { class: 'cm-ch-icon' }, icon),
33
- h('span', { class: 'cm-ch-name' }, name)
34
- );
35
- }
36
-
37
- export function ChannelCategory({ id, name, channels = [], collapsed, activeId, onToggle, onAddChannel, onChannelClick, onChannelContext } = {}) {
38
- return h('div', { class: 'cm-channel-category' },
39
- h('div', {
40
- class: 'cm-category-header' + (collapsed ? ' collapsed' : ''),
41
- onclick: () => onToggle && onToggle(id)
42
- },
43
- h('svg', { class: 'cm-cat-arrow', viewBox: '0 0 24 24' }, h('path', { d: 'M7 10l5 5 5-5z' })),
44
- h('span', { class: 'cm-cat-name' }, name),
45
- onAddChannel ? h('button', { class: 'cm-cat-add', onclick: (e) => { e.stopPropagation(); onAddChannel(id); }, title: 'Add channel' }, '+') : null
46
- ),
47
- collapsed ? null : h('div', { class: 'cm-cat-channels' },
48
- ...channels.map(c => ChannelItem({
49
- ...c,
50
- active: c.id === activeId,
51
- onClick: () => onChannelClick && onChannelClick(c),
52
- onContext: onChannelContext
53
- }))
54
- )
55
- );
56
- }
57
-
58
- export function VoiceUser({ identity, speaking, color } = {}) {
59
- const initial = (identity || '?').slice(0, 1).toUpperCase();
60
- return h('div', { class: 'cm-voice-user' + (speaking ? ' speaking' : '') },
61
- h('div', { class: 'cm-voice-user-avatar', style: color ? `background:${color}` : '' }, initial),
62
- h('span', { class: 'cm-voice-user-name' }, identity)
63
- );
64
- }
65
-
66
- export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen, onSettings } = {}) {
67
- const initial = (name || '?').slice(0, 1).toUpperCase();
68
- return h('div', { class: 'cm-user-panel' },
69
- h('div', { class: 'cm-user-avatar', style: color ? `background:${color}` : '' },
70
- h('span', { class: 'cm-user-status-dot' }),
71
- initial
72
- ),
73
- h('div', { class: 'cm-user-info' },
74
- h('div', { class: 'cm-user-name' }, name || 'You'),
75
- tag ? h('div', { class: 'cm-user-tag' }, tag) : null
76
- ),
77
- h('div', { class: 'cm-user-controls' },
78
- h('button', { class: 'cm-user-btn' + (muted ? ' muted' : ''), onclick: onMute, title: muted ? 'Unmute' : 'Mute' }, muted ? '🔇' : '🎤'),
79
- h('button', { class: 'cm-user-btn' + (deafened ? ' deafened' : ''), onclick: onDeafen, title: deafened ? 'Undeafen' : 'Deafen' }, deafened ? '🔕' : '🎧'),
80
- h('button', { class: 'cm-user-btn', onclick: onSettings, title: 'Settings' }, '⚙')
81
- )
82
- );
83
- }
84
-
85
- export function ChannelSidebar({ serverName, channels = [], categories = [], activeId, collapsedCats, onChannelClick, onCategoryToggle, onAddChannel, onChannelContext, userPanelProps } = {}) {
86
- const collapsed = collapsedCats || new Set();
87
- const uncategorized = channels.filter(c => !c.categoryId || !categories.find(cat => cat.id === c.categoryId));
88
- const sorted = [...categories].sort((a, b) => (a.position || 0) - (b.position || 0));
89
- return h('div', { class: 'cm-channel-sidebar' },
90
- h('div', { class: 'cm-server-header' },
91
- h('span', { class: 'cm-server-header-name' }, serverName || 'Server'),
92
- ),
93
- h('div', { class: 'cm-channel-list' },
94
- ...sorted.map(cat => ChannelCategory({
95
- id: cat.id,
96
- name: cat.name,
97
- channels: channels.filter(c => c.categoryId === cat.id).sort((a, b) => (a.position || 0) - (b.position || 0)),
98
- collapsed: collapsed.has && collapsed.has(cat.id),
99
- activeId,
100
- onToggle: onCategoryToggle,
101
- onAddChannel,
102
- onChannelClick,
103
- onChannelContext
104
- })),
105
- uncategorized.length ? ChannelCategory({
106
- id: 'uncategorized',
107
- name: 'CHANNELS',
108
- channels: uncategorized,
109
- activeId,
110
- onChannelClick,
111
- onChannelContext
112
- }) : null
113
- ),
114
- userPanelProps ? UserPanel(userPanelProps) : null
115
- );
116
- }
117
-
118
- export function MemberItem({ identity, name, color, status = 'online' } = {}) {
119
- const initial = (name || identity || '?').slice(0, 1).toUpperCase();
120
- return h('div', { class: 'cm-member-item' },
121
- h('div', { class: 'cm-member-avatar', style: color ? `background:${color}` : '' },
122
- h('span', { class: 'cm-member-status' + (status === 'online' ? ' online' : '') }),
123
- initial
124
- ),
125
- h('span', { class: 'cm-member-name' }, name || identity)
126
- );
127
- }
128
-
129
- export function MemberList({ categories = [], open } = {}) {
130
- return h('div', { class: 'cm-member-list' + (open ? ' open' : '') },
131
- ...categories.flatMap(cat => [
132
- h('div', { class: 'cm-member-category', key: cat.label }, `${cat.label} — ${cat.members.length}`),
133
- ...cat.members.map((m, i) => MemberItem({ ...m, key: m.identity || i }))
134
- ])
135
- );
136
- }
137
-
138
- export function ChatHeader({ icon = '#', name, topic, toolbar = [] } = {}) {
139
- return h('div', { class: 'cm-chat-header' },
140
- h('span', { class: 'cm-chat-header-icon' }, icon),
141
- h('span', { class: 'cm-chat-header-name' }, name),
142
- topic ? h('span', { class: 'cm-chat-header-topic' }, topic) : null,
143
- h('div', { class: 'cm-chat-header-toolbar' }, ...toolbar)
144
- );
145
- }
146
-
147
- export function VoiceStrip({ channelName, status, muted, deafened, onMute, onDeafen, onLeave, open } = {}) {
148
- return h('div', { class: 'cm-voice-strip' + (open ? ' open' : '') },
149
- h('div', { class: 'cm-vs-label' },
150
- h('span', { class: 'cm-vs-channel' }, '🔊 ' + (channelName || 'voice')),
151
- h('span', { class: 'cm-vs-status' }, status || 'connected')
152
- ),
153
- h('button', { class: 'cm-vs-btn', onclick: onMute, title: 'Mute' }, muted ? '🔇' : '🎤'),
154
- h('button', { class: 'cm-vs-btn', onclick: onDeafen, title: 'Deafen' }, deafened ? '🔕' : '🎧'),
155
- h('button', { class: 'cm-vs-btn danger', onclick: onLeave, title: 'Leave' }, '✕')
156
- );
157
- }
158
-
159
- export function CommunityShell({ serverRailProps, sidebarProps, children, memberListProps, voiceStripProps } = {}) {
160
- return h('div', { class: 'cm-shell' },
161
- serverRailProps ? ServerRail(serverRailProps) : null,
162
- sidebarProps ? ChannelSidebar(sidebarProps) : null,
163
- h('div', { class: 'cm-main' }, ...(Array.isArray(children) ? children : [children])),
164
- memberListProps ? MemberList(memberListProps) : null,
165
- voiceStripProps ? VoiceStrip(voiceStripProps) : null
166
- );
167
- }
2
+ //
3
+ // This module is a barrel: every component lives in a single-responsibility
4
+ // submodule under ./community/, and the public export surface here is unchanged
5
+ // — no consumer import needs to move.
6
+
7
+ import { ServerIcon, ServerRail, ChannelItem, ChannelCategory, ChannelSidebar } from './community/navigation.js';
8
+ import { VoiceUser, UserPanel, MemberItem, MemberList, VoiceStrip } from './community/presence.js';
9
+ import { ChatHeader, MobileHeader, ReplyBar, Banner } from './community/chrome.js';
10
+ import { ThreadPanel, ForumView, PageView } from './community/views.js';
11
+ import { CommunityShell } from './community/shell.js';
12
+
13
+ export {
14
+ ServerIcon, ServerRail, ChannelItem, ChannelCategory, ChannelSidebar,
15
+ VoiceUser, UserPanel, MemberItem, MemberList, VoiceStrip,
16
+ ChatHeader, MobileHeader, ReplyBar, Banner,
17
+ ThreadPanel, ForumView, PageView,
18
+ CommunityShell,
19
+ };