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,154 +1,171 @@
1
1
  import * as webjsx from 'webjsx';
2
- const h = webjsx.createElement;
2
+ import { mountKit } from 'ds/bootstrap.js';
3
+ import {
4
+ AppShell, Topbar, Crumb, Side, Status, Chip,
5
+ Heading, Lede, Section, Install, Receipt, Changelog
6
+ } from 'ds/components.js';
3
7
 
4
- const state = { copied: false, tab: 'readme' };
8
+ const h = webjsx.createElement;
5
9
  const root = document.getElementById('root');
10
+ // `phase` drives the changelog — this page's one remote-fed data surface.
11
+ // Toggled from the sidebar so its loading / empty / error readings are
12
+ // reachable here rather than only against a live registry.
13
+ const state = { copied: false, tab: 'readme', phase: 'ready' };
6
14
 
15
+ // `tab` items switch the readme/docs view; `anchor` items scroll to a section
16
+ // on this page; `href` items leave. Every entry now goes somewhere — the
17
+ // reference and links groups were previously seven inert rows styled exactly
18
+ // like the four working ones.
7
19
  const sideSections = [
8
20
  { group: 'project', items: [
9
- ['', 'overview', 'readme', true],
10
- ['§', 'readme', 'readme', false],
11
- ['§', 'docs', 'docs', false],
12
- ['§', 'changelog', 'changelog', false]
13
- ]},
21
+ { glyph: '//', label: 'readme', tab: 'readme' },
22
+ { glyph: '//', label: 'docs', tab: 'docs' },
23
+ { glyph: '-', label: 'install', anchor: 'install' },
24
+ { glyph: '-', label: 'receipt', anchor: 'receipt' },
25
+ { glyph: '-', label: 'changelog', anchor: 'changelog' }
26
+ ] },
14
27
  { group: 'reference', items: [
15
- ['', 'executenodejs', 'ref', false],
16
- ['', 'executedeno', 'ref', false],
17
- ['', 'astgrep_*', 'ref', false],
18
- ['', 'batch_execute', 'ref', false]
19
- ]},
28
+ { glyph: '>', label: 'executenodejs', href: '#executenodejs' },
29
+ { glyph: '>', label: 'executedeno', href: '#executedeno' },
30
+ { glyph: '>', label: 'astgrep_*', href: '#astgrep' },
31
+ { glyph: '>', label: 'batch_execute', href: '#batch_execute' }
32
+ ] },
20
33
  { group: 'links', items: [
21
- ['', 'source', 'ext', false],
22
- ['', 'npm', 'ext', false],
23
- ['', 'releases', 'ext', false]
24
- ]}
34
+ { glyph: '->', label: 'source', href: 'https://github.com/AnEntrypoint' },
35
+ { glyph: '->', label: 'npm', href: 'https://www.npmjs.com/package/@anentrypoint/mcp-gm' },
36
+ { glyph: '->', label: 'releases', href: 'https://github.com/AnEntrypoint/releases' }
37
+ ] }
25
38
  ];
26
39
 
27
- function Topbar() {
28
- return h('header', { class: 'app-topbar' },
29
- h('span', { class: 'brand' }, '247420', h('span', { class: 'slash' }, ' / '), 'gm'),
30
- h('nav', {},
31
- h('a', { href: '../homepage/' }, '← all projects'),
32
- h('a', { href: '#', class: state.tab==='readme'?'active':'' , onclick:(e)=>{e.preventDefault();state.tab='readme';render();}}, 'readme'),
33
- h('a', { href: '#', class: state.tab==='docs'?'active':'' , onclick:(e)=>{e.preventDefault();state.tab='docs';render();}}, 'docs'),
34
- h('a', { href: '#' }, 'source ↗')
35
- )
36
- );
37
- }
38
-
39
- function Crumb() {
40
- return h('div', { class: 'app-crumb' },
41
- h('span', {}, '247420'), h('span', { class: 'sep' }, '›'),
42
- h('span', {}, 'gm'), h('span', { class: 'sep' }, '›'),
43
- h('span', { class: 'leaf' }, state.tab),
44
- h('span', { style: 'margin-left:auto;display:flex;gap:10px;align-items:center' },
45
- h('span', { class: 'chip accent' }, '● live'),
46
- h('span', { class: 'chip dim' }, 'v0.4.1')
47
- )
48
- );
49
- }
50
-
51
- function Side() {
52
- return h('aside', { class: 'app-side' }, ...sideSections.flatMap(sec => [
53
- h('div', { class: 'group', key: sec.group }, sec.group),
54
- ...sec.items.map(([glyph, label, kind, active], i) =>
55
- h('a', { key: sec.group + i, href: '#', class: active ? 'active' : '' },
56
- h('span', { class: 'glyph' }, glyph),
57
- h('span', {}, label)
58
- )
59
- )
60
- ]));
61
- }
62
-
63
- function Overview() {
64
- return [
65
- h('h1', {}, 'gm'),
66
- h('p', { class: 'lede' }, 'state machine for coding agents. it thinks, so you don\'t have to (as much).'),
40
+ const receiptRows = [
41
+ ['status', 'live · ships tuesdays'],
42
+ ['stars', '3,124'],
43
+ ['license', 'MIT'],
44
+ ['lang', 'typescript · deno'],
45
+ ['size', '2.1mb'],
46
+ ['deps', '0 runtime'],
47
+ ['authors', 'the collective'],
48
+ ['first commit', '2024.09.03']
49
+ ];
67
50
 
68
- h('h3', {}, 'install'),
69
- Install({ cmd: 'npx -y @anentrypoint/mcp-gm' }),
51
+ const changelog = [
52
+ { date: '2026.04.20', ver: 'v0.4.1', msg: 'ship it. fixed the thing everyone complained about.' },
53
+ { date: '2026.03.22', ver: 'v0.4.0', msg: 'new state machine runtime. broke everything on purpose. read the postmortem.' },
54
+ { date: '2026.02.09', ver: 'v0.3.7', msg: 'astgrep_search is now astgrep_enhanced_search. you will adapt.' },
55
+ { date: '2025.12.11', ver: 'v0.3.0', msg: 'first public release. gm, world.' }
56
+ ];
70
57
 
71
- h('h3', {}, 'receipt'),
72
- Receipt({ rows: [
73
- ['status','live · ships tuesdays'],
74
- ['stars','3,124'],
75
- ['license','MIT'],
76
- ['lang','typescript · deno'],
77
- ['size','2.1mb'],
78
- ['deps','0 runtime'],
79
- ['authors','the collective'],
80
- ['first commit','2024.09.03']
81
- ]}),
58
+ const PHASES = ['ready', 'loading', 'empty', 'error'];
82
59
 
83
- h('h3', {}, 'changelog'),
84
- Changelog({ entries: [
85
- { date:'2026.04.20', ver:'v0.4.1', msg:'ship it. fixed the thing everyone complained about.' },
86
- { date:'2026.03.22', ver:'v0.4.0', msg:'new state machine runtime. broke everything on purpose. read the postmortem.' },
87
- { date:'2026.02.09', ver:'v0.3.7', msg:'astgrep_search is now astgrep_enhanced_search. you will adapt.' },
88
- { date:'2025.12.11', ver:'v0.3.0', msg:'first public release. gm, world.' }
89
- ]})
90
- ];
60
+ // Release-row shimmer. Reuses .ds-event-row-skeleton + .ds-skel*
61
+ // (app-shell/files.css) a Changelog entry is the same date / message /
62
+ // version rhythm the primitive was cut for.
63
+ function ChangelogSkeleton() {
64
+ return h('div', {},
65
+ ...Array.from({ length: 4 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
66
+ h('span', { class: 'ds-skel ds-skel-rank' }),
67
+ h('span', { class: 'ds-skel ds-skel-title' }),
68
+ h('span', { class: 'ds-skel ds-skel-meta' })
69
+ ))
70
+ );
91
71
  }
92
72
 
93
- function Install({ cmd }) {
94
- return h('div', { class: 'cli' },
95
- h('span', { class: 'prompt' }, '$'),
96
- h('span', { class: 'cmd' }, cmd),
97
- h('span', {
98
- class: 'copy',
99
- onclick: () => { navigator.clipboard?.writeText(cmd); state.copied = true; render(); setTimeout(() => { state.copied = false; render(); }, 1200); }
100
- }, state.copied ? 'copied' : 'copy')
73
+ function ChangelogEmpty() {
74
+ return h('div', { class: 'ds-empty-state' },
75
+ h('div', { class: 'ds-empty-state-glyph' }, '[ ]'),
76
+ h('p', { class: 'ds-empty-state-msg' }, 'no releases tagged yet'),
77
+ h('p', { class: 'ds-empty-state-hint' }, 'every tagged build shows up here with its date and notes. tag a commit and the first entry appears on the next publish.')
101
78
  );
102
79
  }
103
80
 
104
- function Receipt({ rows }) {
105
- return h('table', { class: 'kv' },
106
- h('tbody', {}, ...rows.map(([k, v], i) =>
107
- h('tr', { key: i },
108
- h('td', {}, k),
109
- h('td', {}, v)
81
+ function ChangelogError() {
82
+ return h('div', { class: 'ds-alert ds-alert-error' },
83
+ h('span', { class: 'ds-alert-icon' }, '!'),
84
+ h('div', { class: 'ds-alert-content' },
85
+ h('div', { class: 'ds-alert-title' }, 'release feed rate-limited'),
86
+ h('div', { class: 'ds-alert-message' }, 'the registry capped this page at 60 requests an hour and the window resets in about 4 minutes. the install command and receipt above are cached and still accurate.'),
87
+ h('div', { class: 'ds-alert-retry' },
88
+ h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; kit.render(); } }, 'retry now')
110
89
  )
111
- ))
90
+ )
112
91
  );
113
92
  }
114
93
 
115
- function Changelog({ entries }) {
116
- return h('div', { class: 'panel', style: 'max-width:900px' },
117
- h('div', { class: 'panel-body' }, ...entries.map((e, i) =>
118
- h('div', { key: i, class: 'row', style: 'grid-template-columns:100px 70px 1fr' },
119
- h('span', { class: 'code' }, e.date),
120
- h('span', { style: 'color:var(--panel-accent);font-family:var(--ff-mono);font-size:14px' }, e.ver),
121
- h('span', { class: 'title' }, e.msg)
122
- )
123
- ))
124
- );
94
+ function ChangelogBody() {
95
+ if (state.phase === 'loading') return ChangelogSkeleton();
96
+ if (state.phase === 'error') return ChangelogError();
97
+ if (state.phase === 'empty') return ChangelogEmpty();
98
+ return Changelog({ entries: changelog });
125
99
  }
126
100
 
127
- function Status() {
128
- return h('footer', { class: 'app-status' },
129
- h('span', { class: 'item' }, 'main'),
130
- h('span', { class: 'item' }, '• typescript'),
131
- h('span', { class: 'item' }, '• 0 errors'),
132
- h('span', { class: 'item' }, '• 0 warnings'),
133
- h('span', { class: 'spread' }),
134
- h('span', { class: 'item' }, 'v0.4.1'),
135
- h('span', { class: 'item' }, '• MIT')
136
- );
101
+ function copyInstall(cmd) {
102
+ navigator.clipboard?.writeText(cmd);
103
+ state.copied = true; kit.render();
104
+ setTimeout(() => { state.copied = false; kit.render(); }, 1200);
137
105
  }
138
106
 
139
107
  function App() {
140
- return h('div', { class: 'app' },
141
- Topbar(),
142
- Crumb(),
143
- h('div', { class: 'app-body' },
144
- Side(),
145
- h('main', { class: 'app-main narrow' }, ...Overview())
146
- ),
147
- Status()
148
- );
108
+ return AppShell({
109
+ topbar: Topbar({
110
+ brand: '247420', leaf: 'gm',
111
+ items: [
112
+ ['<- all projects', '../homepage/'],
113
+ ['readme', '#readme'],
114
+ ['docs', '#docs'],
115
+ ['source ->', 'https://github.com/AnEntrypoint']
116
+ ],
117
+ active: state.tab,
118
+ onNav: (label) => { if (label === 'readme' || label === 'docs') { state.tab = label; kit.render(); } }
119
+ }),
120
+ crumb: Crumb({
121
+ trail: ['247420', 'gm'], leaf: state.tab,
122
+ right: [Chip({ tone: 'dim', children: 'shipping' }), Chip({ tone: 'dim', children: 'v0.4.1' })]
123
+ }),
124
+ side: Side({
125
+ sections: [
126
+ // Only the `tab` items can be "here", so only they take the
127
+ // active highlight. Previously `overview` was hardcoded active
128
+ // forever AND the release-feed phase row took the same acid
129
+ // fill, so the page showed two "you are here" markers at once
130
+ // and neither meant anything.
131
+ ...sideSections.map((sec) => ({
132
+ group: sec.group,
133
+ items: sec.items.map((it, i) => ({
134
+ key: sec.group + i, glyph: it.glyph, label: it.label,
135
+ active: !!it.tab && state.tab === it.tab,
136
+ href: it.href || '#' + (it.tab || it.anchor || it.label),
137
+ onClick: it.tab
138
+ ? (e) => { e.preventDefault(); state.tab = it.tab; kit.render(); }
139
+ : null
140
+ }))
141
+ })),
142
+ // Demo switcher for the changelog's states. Marked with a
143
+ // glyph, never the active fill — it is not a location.
144
+ { group: 'release feed', items: PHASES.map((p) => ({
145
+ glyph: p === state.phase ? '*' : '-',
146
+ label: p, key: 'ph-' + p, href: '#feed-' + p,
147
+ onClick: (e) => { e.preventDefault(); state.phase = p; kit.render(); }
148
+ })) }
149
+ ]
150
+ }),
151
+ main: [
152
+ h('div', { class: 'ds-section ds-section-pad' },
153
+ Heading({ level: 1, children: 'gm' }),
154
+ Lede({ children: 'state machine for coding agents. it thinks, so you don\'t have to (as much).' }),
155
+ Section({ id: 'install', title: 'install',
156
+ children: Install({ cmd: 'npx -y @anentrypoint/mcp-gm', copied: state.copied, onCopy: copyInstall }) }),
157
+ Section({ id: 'receipt', title: 'receipt', children: Receipt({ rows: receiptRows }) }),
158
+ Section({ id: 'changelog', title: 'changelog', children: ChangelogBody() })
159
+ )
160
+ ],
161
+ // Describes this page, not a compiler. The previous left side read
162
+ // "typescript · 0 errors · 0 warnings" — an editor status bar borrowed
163
+ // onto a package readme, reporting on a build that is not running here.
164
+ status: Status({
165
+ left: ['gm', '- ' + state.tab, '- releases ' + state.phase],
166
+ right: ['v0.4.1', 'MIT']
167
+ })
168
+ });
149
169
  }
150
170
 
151
- function render() {
152
- webjsx.applyDiff(root, App());
153
- }
154
- render();
171
+ const kit = mountKit({ root, view: App, screen: '14 Project page' });
@@ -35,7 +35,8 @@
35
35
  "webjsx": "../../vendor/webjsx/index.js",
36
36
  "webjsx/": "../../vendor/webjsx/",
37
37
  "webjsx/jsx-runtime": "../../vendor/webjsx/jsx-runtime.js",
38
- "webjsx-router": "../../vendor/webjsx-router.js"
38
+ "webjsx-router": "../../vendor/webjsx-router.js",
39
+ "ds/": "../../src/"
39
40
  } }
40
41
  </script>
41
42
  </head>
@@ -29,7 +29,36 @@ const corpus = [
29
29
 
30
30
  const kinds = ['all', 'kit', 'preview', 'doc', 'api'];
31
31
 
32
- const state = { q: 'panel', kind: 'all' };
32
+ // `phase` drives which state the results panel renders. It is a real toggle in
33
+ // the sidebar rather than a flag only a live backend could set — an index kit
34
+ // whose loading and error surfaces exist only in dead code has not shipped them.
35
+ const state = { q: 'panel', kind: 'all', phase: 'ready' };
36
+ const PHASES = ['ready', 'loading', 'error'];
37
+
38
+ // Ranked-result loading placeholder. Reuses .ds-event-row-skeleton + .ds-skel*
39
+ // (app-shell/files.css) because a RowLink is the same code/title/meta rhythm.
40
+ function ResultsSkeleton() {
41
+ return h('div', {},
42
+ ...[0, 1, 2, 3, 4, 5].map((i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
43
+ h('span', { class: 'ds-skel ds-skel-icon' }),
44
+ h('span', { class: 'ds-skel ds-skel-title' }),
45
+ h('span', { class: 'ds-skel ds-skel-meta' })
46
+ ))
47
+ );
48
+ }
49
+
50
+ function ResultsError() {
51
+ return h('div', { class: 'ds-alert ds-alert-error' },
52
+ h('span', { class: 'ds-alert-icon' }, '!'),
53
+ h('div', { class: 'ds-alert-content' },
54
+ h('div', { class: 'ds-alert-title' }, 'index out of date'),
55
+ h('div', { class: 'ds-alert-message' }, 'the search index last rebuilt 9 days ago and rejected this query. results would be wrong rather than missing, so nothing is shown. rebuilding takes about 20s.'),
56
+ h('div', { class: 'ds-alert-retry' },
57
+ h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; kit.render(); } }, 'rebuild index')
58
+ )
59
+ )
60
+ );
61
+ }
33
62
 
34
63
  function score(item, q) {
35
64
  const t = (item.title + ' ' + item.sub).toLowerCase();
@@ -55,22 +84,28 @@ function App() {
55
84
  return AppShell({
56
85
  topbar: Topbar({
57
86
  brand: '247420', leaf: 'search',
58
- items: [['index', '../../'], ['source ', 'https://github.com/AnEntrypoint/design']],
87
+ items: [['index', '../../'], ['source ->', 'https://github.com/AnEntrypoint/design']],
59
88
  search: h('input', {
60
- class: 'input', value: state.q, placeholder: 'search kits, previews, docs, api…',
61
- style: 'width:280px',
89
+ class: 'input ds-topbar-search', value: state.q, placeholder: 'search kits, previews, docs, api…',
62
90
  oninput: (e) => { state.q = e.target.value; kit.render(); }
63
91
  })
64
92
  }),
65
- crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'search', right: rows.length + ' result' + (rows.length === 1 ? '' : 's') }),
93
+ crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'search', right: state.phase === 'ready' ? rows.length + ' result' + (rows.length === 1 ? '' : 's') : state.phase }),
66
94
  side: Side({
67
95
  sections: [
68
96
  { group: 'kind', items: kinds.map((k) => ({
69
- glyph: state.kind === k ? '' : '', label: k,
97
+ glyph: h('span', { class: state.kind === k ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }), label: k,
70
98
  count: k === 'all' ? corpus.length : corpus.filter((c) => c.kind === k).length,
71
99
  href: '#' + k, active: state.kind === k, key: k,
72
100
  onClick: (e) => { e.preventDefault(); state.kind = k; kit.render(); }
73
101
  })) },
102
+ // Reachable state switcher — the results panel is this kit's
103
+ // data surface, so loading and error are one click away.
104
+ { group: 'index state', items: PHASES.map((p) => ({
105
+ glyph: h('span', { class: state.phase === p ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }),
106
+ label: p, key: 'ph-' + p, active: state.phase === p, href: '#' + p,
107
+ onClick: (e) => { e.preventDefault(); state.phase = p; kit.render(); }
108
+ })) },
74
109
  { group: 'recent', items: [
75
110
  { glyph: '·', label: 'panel', key: 'q1', onClick: (e) => { e.preventDefault(); state.q = 'panel'; kit.render(); } },
76
111
  { glyph: '·', label: 'rail', key: 'q2', onClick: (e) => { e.preventDefault(); state.q = 'rail'; kit.render(); } },
@@ -80,17 +115,19 @@ function App() {
80
115
  ]
81
116
  }),
82
117
  main: [
83
- h('div', { class: 'ds-section', style: 'padding:8px' },
118
+ h('div', { class: 'ds-app-surface ds-section-pad' },
84
119
  Heading({ level: 1, children: 'search' }),
85
120
  Lede({ children: 'query bar in the topbar, faceted filters in the sidebar, ranked results in panel rows. same row primitive every other surface uses.' }),
86
- rows.length ? Panel({ title: 'results', count: rows.length, style: 'margin:8px 0', children:
87
- rows.map((r, i) => RowLink({ key: 'r' + r.code + i, code: r.code, title: r.title, sub: r.sub, meta: r.kind + '', href: r.href }))
88
- }) : Panel({ title: 'no results', style: 'margin:8px 0', children: h('div', { style: 'padding:24px;text-align:center;color:var(--panel-text-3)' },
89
- h('div', { style: 'font-size:32px' }, ''),
90
- h('p', { style: 'margin:6px 0' }, 'no matches for ', h('code', {}, '"' + state.q + '"')),
91
- h('p', { style: 'margin:0;font-size:13px' }, 'try a shorter query, or pick a different kind.')
121
+ state.phase === 'loading' ? Panel({ title: 'searching', class: 'ds-panel-gap', children: ResultsSkeleton() })
122
+ : state.phase === 'error' ? Panel({ title: 'results unavailable', class: 'ds-panel-gap', children: ResultsError() })
123
+ : rows.length ? Panel({ title: 'results', count: rows.length, class: 'ds-panel-gap', children:
124
+ rows.map((r, i) => RowLink({ key: 'r' + r.code + i, code: r.code, title: r.title, sub: r.sub, meta: r.kind + ' ->', href: r.href }))
125
+ }) : Panel({ title: 'no results', class: 'ds-panel-gap', children: h('div', { class: 'ds-empty-state' },
126
+ h('div', { class: 'ds-empty-state-glyph' }, '( )'),
127
+ h('p', { class: 'ds-empty-state-msg' }, 'no matches for ', h('code', {}, '"' + state.q + '"')),
128
+ h('p', { class: 'ds-empty-state-hint' }, 'try a shorter query, or pick a different kind.')
92
129
  ) }),
93
- Panel({ title: 'about this kit', style: 'margin:8px 0', children: h('div', { class: 'ds-pattern-notes' },
130
+ Panel({ title: 'about this kit', class: 'ds-panel-gap', children: h('div', { class: 'ds-pattern-notes' },
94
131
  h('p', {}, '· query input lives in the ', Chip({ tone: 'accent', children: 'Topbar' }), ' search slot — same component the index uses.'),
95
132
  h('p', {}, '· filters are ', Chip({ tone: 'accent', children: 'Side' }), ' sections with active states; counts come from the corpus.'),
96
133
  h('p', {}, '· results reuse ', Chip({ tone: 'accent', children: 'RowLink' }), ' — never a bespoke result row.')
@@ -98,7 +135,7 @@ function App() {
98
135
  )
99
136
  ],
100
137
  status: Status({
101
- left: ['search', ' kind=' + state.kind, ' ' + rows.length + ' rows'],
138
+ left: ['search', '- kind=' + state.kind, state.phase === 'ready' ? '- ' + rows.length + ' rows' : '- ' + state.phase],
102
139
  right: ['247420 / mmxxvi']
103
140
  })
104
141
  });