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,15 +1,22 @@
1
1
  import * as webjsx from 'webjsx';
2
- import { Topbar, Crumb, Status, Side, AppShell, Panel, Heading, Lede, Chip, Kpi, Table, Receipt, Changelog, Row, RowLink } from 'ds/components.js';
2
+ import { Topbar, Crumb, Status, Side, AppShell, Panel, Heading, Lede, Chip, Kpi, BarChart, Table, Receipt, Changelog, Row } from 'ds/components.js';
3
3
  import { mountKit } from 'ds/bootstrap.js';
4
4
  const h = webjsx.createElement;
5
5
 
6
6
  const root = document.getElementById('root');
7
7
 
8
8
  const kpis = [
9
- ['24,891', 'requests · 24h'],
10
- ['184ms', 'avg latency · p50'],
11
- ['0.42%', 'error rate · 5xx+4xx'],
12
- ['94.7%', 'cache hit · edge']
9
+ ['24,891', 'requests · 24h', { delta: '+12.4%', tone: 'up', spark: [8, 11, 9, 14, 16, 15, 19, 22, 20, 24] }],
10
+ ['184ms', 'avg latency · p50', { delta: '-6.1%', tone: 'up', spark: [220, 210, 205, 198, 190, 188, 184, 186, 182, 184] }],
11
+ ['0.42%', 'error rate · 5xx+4xx', { delta: '+0.08%', tone: 'down', spark: [0.2, 0.25, 0.3, 0.28, 0.35, 0.3, 0.38, 0.4, 0.36, 0.42] }],
12
+ ['94.7%', 'cache hit · edge', { delta: '+1.2%', tone: 'up', spark: [90, 91, 92, 91, 93, 92, 94, 93, 95, 94.7] }]
13
+ ];
14
+
15
+ const channelBreakdown = [
16
+ { label: 'edge cache', value: 412, display: '412 rps' },
17
+ { label: 'origin fetch', value: 187, display: '187 rps' },
18
+ { label: 'feed api', value: 1200, display: '1.2k rps' },
19
+ { label: 'upload api', value: 24, display: '24 rps' }
13
20
  ];
14
21
 
15
22
  const tableHeaders = ['endpoint', 'rps', 'p95', 'errors', 'status'];
@@ -36,57 +43,139 @@ const changelog = [
36
43
  { date: '2026-05-07', ver: 'v0.4.10', msg: 'migrate session store · add p99 to /metrics' }
37
44
  ];
38
45
 
46
+ // No 01/02/03 index: the feed is already ordered by the `meta` age column, so a
47
+ // counter would carry no information and would read as a ranking that isn't one.
48
+ // The rail tone is the real signal — it says what KIND of event this was, which
49
+ // is what an operator scans for.
39
50
  const events = [
40
- { code: '01', title: 'deploy succeeded', sub: 'v0.4.12 · all regions', meta: '2m' },
41
- { code: '02', title: 'cache flushed', sub: 'edge-cache · eu-west-1', meta: '14m' },
42
- { code: '03', title: 'p95 spike', sub: '/api/upload · 1.4s', meta: '38m' },
43
- { code: '04', title: 'cron ran', sub: 'reindex-search · ok', meta: '1h' },
44
- { code: '05', title: 'config reloaded', sub: 'feature flags', meta: '3h' }
51
+ { title: 'deploy succeeded', sub: 'v0.4.12 · all regions', meta: '2m', rail: 'green' },
52
+ { title: 'cache flushed', sub: 'edge-cache · eu-west-1', meta: '14m' },
53
+ { title: 'p95 spike', sub: '/api/upload · 1.4s', meta: '38m', rail: 'flame' },
54
+ { title: 'cron ran', sub: 'reindex-search · ok', meta: '1h', rail: 'green' },
55
+ { title: 'config reloaded', sub: 'feature flags', meta: '3h' }
45
56
  ];
46
57
 
58
+ // Every data panel below reads its state from here rather than assuming the
59
+ // happy path. The sidebar "feed state" group flips it, so each state is a real
60
+ // reachable surface in the kit, not dead code behind a flag nobody sets.
61
+ const state = { feed: 'ready' };
62
+ const FEED_STATES = ['ready', 'loading', 'empty', 'error'];
63
+
64
+ // Loading placeholder for the events feed. Reuses the .ds-event-row-skeleton
65
+ // primitive (app-shell/files.css) — the row shape it was cut for is the same
66
+ // icon/title/meta rhythm Row() renders, so no new skeleton CSS is needed.
67
+ function EventsSkeleton() {
68
+ return h('div', {},
69
+ ...[0, 1, 2, 3, 4].map((i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
70
+ h('span', { class: 'ds-skel ds-skel-icon' }),
71
+ h('span', { class: 'ds-skel ds-skel-title' }),
72
+ h('span', { class: 'ds-skel ds-skel-meta' })
73
+ ))
74
+ );
75
+ }
76
+
77
+ function EventsEmpty() {
78
+ return h('div', { class: 'ds-empty-state' },
79
+ h('div', { class: 'ds-empty-state-glyph' }, '[ ]'),
80
+ h('p', { class: 'ds-empty-state-msg' }, 'no events in the last 24h'),
81
+ h('p', { class: 'ds-empty-state-hint' }, 'deploys, cache flushes and cron runs land here as they happen. a quiet feed means production is quiet.')
82
+ );
83
+ }
84
+
85
+ function EventsError() {
86
+ return h('div', { class: 'ds-alert ds-alert-error' },
87
+ h('span', { class: 'ds-alert-icon' }, '!'),
88
+ h('div', { class: 'ds-alert-content' },
89
+ h('div', { class: 'ds-alert-title' }, 'event stream disconnected'),
90
+ h('div', { class: 'ds-alert-message' }, 'the eu-west-1 collector stopped answering 38s ago, so this feed is stale. metrics above still come from the edge and are current.'),
91
+ h('div', { class: 'ds-alert-retry' },
92
+ h('button', { class: 'btn', onclick: () => { state.feed = 'ready'; kit.render(); } }, 'reconnect')
93
+ )
94
+ )
95
+ );
96
+ }
97
+
98
+ function EventsPanel() {
99
+ if (state.feed === 'loading') return EventsSkeleton();
100
+ if (state.feed === 'error') return EventsError();
101
+ if (state.feed === 'empty') return EventsEmpty();
102
+ return h('div', {}, ...events.map((e, i) => Row({ key: 'ev' + i, title: e.title, sub: e.sub, meta: e.meta, rail: e.rail })));
103
+ }
104
+
105
+ const feedCountOf = () => (state.feed === 'ready' ? events.length : 0);
106
+
47
107
  function App() {
108
+ const feedCount = feedCountOf();
48
109
  return AppShell({
49
- topbar: Topbar({ brand: '247420', leaf: 'dashboard', items: [['index', '../../'], ['docs', '../docs/'], ['source ', 'https://github.com/AnEntrypoint/design']] }),
110
+ topbar: Topbar({ brand: '247420', leaf: 'dashboard', items: [['index', '../../'], ['docs', '../docs/'], ['source ->', 'https://github.com/AnEntrypoint/design']] }),
50
111
  crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'dashboard' }),
51
112
  side: Side({
52
113
  sections: [
114
+ // Every entry anchors to the panel it names. These were four
115
+ // inert rows styled exactly like working nav; the counts were
116
+ // also invented, so they now read off the data each panel draws.
53
117
  { group: 'views', items: [
54
- { glyph: '', label: 'overview', count: '·', key: 'o' },
55
- { glyph: '', label: 'requests', count: 5, key: 'r' },
56
- { glyph: '', label: 'errors', count: 2, key: 'e' },
57
- { glyph: '', label: 'cron', count: 7, key: 'c' }
118
+ { glyph: '*', label: 'overview', count: kpis.length, key: 'o', href: '#p-metrics' },
119
+ { glyph: '-', label: 'endpoints', count: tableRows.length, key: 'r', href: '#p-endpoints' },
120
+ { glyph: '-', label: 'events', count: feedCountOf(), key: 'e', href: '#p-events' },
121
+ { glyph: '-', label: 'changelog', count: changelog.length, key: 'c', href: '#p-changelog' }
58
122
  ] },
123
+ // Environment is a READOUT, not navigation — there is no
124
+ // per-environment view in this kit to switch to. It anchors to
125
+ // the environment receipt, which is the panel that actually
126
+ // carries this information, rather than promising a filter it
127
+ // cannot perform.
59
128
  { group: 'env', items: [
60
- { glyph: '', label: 'production', count: 'eu', key: 'p', color: 'var(--panel-accent)' },
61
- { glyph: '', label: 'staging', count: 'us', key: 's', color: 'var(--mascot,#e0a200)' }
62
- ] }
129
+ { glyph: h('span', { class: 'ds-dot ds-dot-on' }), label: 'production', count: 'eu', key: 'p', color: 'var(--panel-accent)', href: '#p-environment' },
130
+ { glyph: h('span', { class: 'ds-dot ds-dot-off' }), label: 'staging', count: 'us', key: 's', color: 'var(--mascot)', href: '#p-environment' }
131
+ ] },
132
+ // Reachable state switcher — the events panel is the kit's
133
+ // reference data surface, so every state it can be in is one
134
+ // click away rather than only existing on a real outage.
135
+ { group: 'feed state', items: FEED_STATES.map((s) => ({
136
+ glyph: h('span', { class: state.feed === s ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }),
137
+ label: s, key: 'fs-' + s, active: state.feed === s, href: '#' + s,
138
+ onClick: (e) => { e.preventDefault(); state.feed = s; kit.render(); }
139
+ })) }
63
140
  ]
64
141
  }),
65
142
  main: [
66
- h('div', { class: 'ds-section', style: 'padding:8px' },
143
+ h('div', { class: 'ds-app-surface ds-section-pad' },
67
144
  Heading({ level: 1, children: 'dashboard' }),
68
145
  Lede({ children: 'kpis, tables, receipts, changelog — every content primitive in one operations surface.' }),
69
- Panel({ title: 'live metrics', count: kpis.length, style: 'margin:8px 0', children: Kpi({ items: kpis }) }),
70
- Panel({ title: 'top endpoints', count: tableRows.length, style: 'margin:8px 0', children: Table({ headers: tableHeaders, rows: tableRows }) }),
71
- h('div', { style: 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:8px 0' },
72
- Panel({ title: 'environment', children: Receipt({ rows: receipt }) }),
73
- Panel({ title: 'recent events', count: events.length, children: events.map((e, i) =>
74
- Row({ key: 'ev' + i, code: e.code, title: e.title, sub: e.sub, meta: e.meta })
75
- ) })
146
+ // Reading order: headline counters lead (the glance), then the
147
+ // paired analysis, then the three reference panels. Each tier is
148
+ // separated by the .ds-panel-gap/.ds-panel-duo outer rhythm,
149
+ // which is deliberately wider than any panel's inner gap.
150
+ Panel({ id: 'p-metrics', title: 'live metrics', count: kpis.length, class: 'ds-panel-gap', children: Kpi({ items: kpis }) }),
151
+ h('div', { class: 'ds-panel-duo' },
152
+ Panel({ title: 'traffic by channel', count: channelBreakdown.length, class: 'ds-panel-flush', children: BarChart({ items: channelBreakdown }) }),
153
+ Panel({ id: 'p-endpoints', title: 'top endpoints', count: tableRows.length, class: 'ds-panel-flush', children: h('div', { class: 'ds-scroll-x' }, Table({ headers: tableHeaders, rows: tableRows })) })
154
+ ),
155
+ // Three equal reference panels. A real 3-track grid, not
156
+ // percentage flex-basis: with basis+gap the three tracks
157
+ // overflow 100% and the last one wraps to its own row.
158
+ // .ds-panel-flush drops each panel's own bottom margin so the
159
+ // grid gap is the single source of separation in the row.
160
+ h('div', { class: 'ds-panel-trio' },
161
+ Panel({ id: 'p-environment', title: 'environment', class: 'ds-panel-flush', children: Receipt({ rows: receipt }) }),
162
+ Panel({ id: 'p-events', title: 'recent events', count: feedCount, class: 'ds-panel-flush', children: EventsPanel() }),
163
+ Panel({ id: 'p-changelog', title: 'changelog', count: changelog.length, class: 'ds-panel-flush', children: Changelog({ entries: changelog }) })
76
164
  ),
77
- Panel({ title: 'changelog', count: changelog.length, style: 'margin:8px 0', children: Changelog({ entries: changelog }) }),
78
- Panel({ title: 'about this kit', style: 'margin:8px 0', children: h('div', { class: 'ds-pattern-notes' },
79
- h('p', {}, '· ', Chip({ tone: 'accent', children: 'Kpi' }), ' for headline counters with delta + meta.'),
80
- h('p', {}, '· ', Chip({ tone: 'accent', children: 'Table' }), ' for tabular metrics, ', Chip({ tone: 'accent', children: 'Row' }), ' for event lists.'),
81
- h('p', {}, '· ', Chip({ tone: 'accent', children: 'Receipt' }), ' for kv environment manifest, ', Chip({ tone: 'accent', children: 'Changelog' }), ' for release log.')
165
+ Panel({ title: 'about this kit', class: 'ds-panel-gap', children: h('div', { class: 'ds-pattern-notes' },
166
+ h('p', {}, '· ', Chip({ tone: 'accent', children: 'Kpi' }), ' for headline counters with trend delta + sparkline.'),
167
+ h('p', {}, '· ', Chip({ tone: 'accent', children: 'BarChart' }), ' for a category breakdown, ', Chip({ tone: 'accent', children: 'Table' }), ' for tabular metrics, ', Chip({ tone: 'accent', children: 'Row' }), ' for event lists.'),
168
+ h('p', {}, '· ', Chip({ tone: 'accent', children: 'Receipt' }), ' for kv environment manifest, ', Chip({ tone: 'accent', children: 'Changelog' }), ' for release log.'),
169
+ h('p', {}, '· ', Chip({ tone: 'accent', children: 'ds-panel-trio' }), '/', Chip({ tone: 'accent', children: 'ds-panel-duo' }), ' for multi-panel rows — real grid tracks that step down on container width, not the viewport.'),
170
+ h('p', {}, '· ', Chip({ tone: 'accent', children: 'ds-panel-flush' }), ' on any panel inside a gap-owning row, so the container is the single source of separation.')
82
171
  ) })
83
172
  )
84
173
  ],
85
174
  status: Status({
86
- left: ['dashboard', ' ' + kpis.length + ' kpis', ' ' + tableRows.length + ' endpoints'],
87
- right: ['247420 / mmxxvi', ' live']
175
+ left: ['dashboard', '- ' + kpis.length + ' kpis', '- ' + tableRows.length + ' endpoints', '- feed ' + state.feed],
176
+ right: ['247420 / mmxxvi', '- live']
88
177
  })
89
178
  });
90
179
  }
91
180
 
92
- mountKit({ root, view: App, screen: '08 Dashboard' });
181
+ const kit = mountKit({ root, view: App, screen: '08 Dashboard' });
@@ -11,6 +11,7 @@
11
11
  <link rel="icon" type="image/svg+xml" href="../../favicon.svg">
12
12
  <link rel="stylesheet" href="../../colors_and_type.css">
13
13
  <link rel="stylesheet" href="../../app-shell.css">
14
+ <link rel="stylesheet" href="../../editor-primitives.css">
14
15
  <script type="importmap">
15
16
  { "imports": {
16
17
  "webjsx": "../../vendor/webjsx/index.js",
@@ -34,13 +34,17 @@
34
34
  </head>
35
35
  <body data-screen-label="03 Docs">
36
36
  <div class="app">
37
+ <!-- Matches what AppShell() emits for the JS-rendered kits. This page hand-
38
+ rolls the shell markup statically, so it does not inherit the skip link
39
+ and had no way past the sidebar nav for a keyboard user. -->
40
+ <a href="#app-main" class="skip-link">skip to main content</a>
37
41
  <header class="app-topbar">
38
42
  <span class="brand">247420<span class="slash"> / </span>gm<span class="slash"> / </span>docs</span>
39
43
  <nav>
40
- <a href="../homepage/">← all projects</a>
41
- <a href="#">readme</a>
42
- <a href="#" class="active">docs</a>
43
- <a href="#">source ↗</a>
44
+ <a href="../homepage/"><- all projects</a>
45
+ <a href="https://github.com/AnEntrypoint/gm#readme">readme</a>
46
+ <a class="active" aria-current="page">docs</a>
47
+ <a href="https://github.com/AnEntrypoint/gm">source -></a>
44
48
  </nav>
45
49
  </header>
46
50
  <div class="app-crumb">
@@ -50,36 +54,42 @@
50
54
  <span class="leaf">introduction</span>
51
55
  <span class="crumb-right"><span class="chip dim">v0.4.1</span></span>
52
56
  </div>
57
+ <button class="app-side-toggle" type="button" aria-label="toggle navigation" aria-expanded="false" aria-controls="app-side-shell" onclick="document.querySelector('.app-body').classList.toggle('side-open')">
58
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
59
+ </button>
53
60
  <div class="app-body">
61
+ <div class="app-side-scrim" aria-hidden="true" onclick="document.querySelector('.app-body').classList.remove('side-open')"></div>
62
+ <div class="app-side-shell" id="app-side-shell">
54
63
  <aside class="app-side">
55
64
  <div class="group">start</div>
56
- <a href="#" class="active"><span class="glyph">§</span><span>introduction</span></a>
57
- <a href="#"><span class="glyph">§</span><span>install</span></a>
58
- <a href="#"><span class="glyph">§</span><span>quickstart</span></a>
65
+ <a href="#introduction" class="active"><span class="glyph">//</span><span>introduction</span></a>
66
+ <a href="#install"><span class="glyph">//</span><span>install</span></a>
67
+ <a href="#quickstart"><span class="glyph">//</span><span>quickstart</span></a>
59
68
  <div class="group">concepts</div>
60
- <a href="#"><span class="glyph">›</span><span>states & transitions</span></a>
61
- <a href="#"><span class="glyph">›</span><span>the loop</span></a>
62
- <a href="#"><span class="glyph">›</span><span>tools</span></a>
69
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>states & transitions</span></a>
70
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>the loop</span></a>
71
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>tools</span></a>
63
72
  <div class="group">reference</div>
64
- <a href="#"><span class="glyph">›</span><span>executenodejs</span></a>
65
- <a href="#"><span class="glyph">›</span><span>executedeno</span></a>
66
- <a href="#"><span class="glyph">›</span><span>astgrep_*</span></a>
67
- <a href="#"><span class="glyph">›</span><span>batch_execute</span></a>
73
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>executenodejs</span></a>
74
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>executedeno</span></a>
75
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>astgrep_*</span></a>
76
+ <a class="is-unwritten" title="not written yet"><span class="glyph">&gt;</span><span>batch_execute</span></a>
68
77
  <div class="group">misc</div>
69
- <a href="#"><span class="glyph">§</span><span>philosophy</span></a>
70
- <a href="#"><span class="glyph">↗</span><span>changelog</span></a>
71
- <a href="#"><span class="glyph">↗</span><span>source</span></a>
78
+ <a href="#philosophy"><span class="glyph">//</span><span>philosophy</span></a>
79
+ <a href="https://github.com/AnEntrypoint/gm/blob/main/CHANGELOG.md"><span class="glyph">-&gt;</span><span>changelog</span></a>
80
+ <a href="https://github.com/AnEntrypoint/gm"><span class="glyph">-&gt;</span><span>source</span></a>
72
81
  </aside>
73
- <main class="app-main narrow">
82
+ </div>
83
+ <main class="app-main narrow" id="app-main" tabindex="0">
74
84
  <div class="ds-prose">
75
- <h1>introduction</h1>
85
+ <h1 id="introduction">introduction</h1>
76
86
  <p class="lede">a state machine for coding agents. it thinks, so you don't have to (as much).</p>
77
87
 
78
88
  <p>gm is what happens when you stop trying to make an llm "smart" and start giving it a scaffolding to be useful inside of. it's a small, deterministic runtime that says: <em>here is where you are, here are the transitions available, pick one.</em> that's the whole idea.</p>
79
89
 
80
90
  <p>the rest of this document explains how to install it, how to wire it up, and how to stay out of its way.</p>
81
91
 
82
- <h2>install</h2>
92
+ <h2 id="install">install</h2>
83
93
  <p>one command. you've probably seen this kind of thing before.</p>
84
94
  <pre><span class="c"># via claude code plugin marketplace</span>
85
95
  <span class="k">claude</span> plugin marketplace add AnEntrypoint/gm-cc
@@ -87,37 +97,37 @@
87
97
  </div>
88
98
 
89
99
  <div class="panel panel-inline-wide">
90
- <div class="panel-head"><span>※ note</span></div>
100
+ <div class="panel-head"><span>[!] note</span></div>
91
101
  <div class="ds-pattern-notes">
92
- if you're running <code>gm</code> without claude code, see <a href="#">reference / executenodejs</a>. you'll want bun 1.1+.
102
+ if you're running <code>gm</code> without claude code, see the <code>executenodejs</code> reference (not written yet). you'll want bun 1.1+.
93
103
  </div>
94
104
  </div>
95
105
 
96
106
  <div class="ds-prose">
97
- <h2>quickstart</h2>
107
+ <h2 id="quickstart">quickstart</h2>
98
108
  <h3>the 30-second version</h3>
99
109
  <p>start the repl, ask it to do something, watch it work. press <code>Ctrl</code>+<code>C</code> if it goes somewhere you didn't mean.</p>
100
110
  <pre><span class="k">gm</span> start
101
- <span class="c">→ state: idle</span>
102
- <span class="c">→ tools available: 19</span>
111
+ <span class="c">-> state: idle</span>
112
+ <span class="c">-> tools available: 19</span>
103
113
  &gt; fix the failing test in src/router.ts
104
- <span class="c">→ state: reading</span>
105
- <span class="c">→ state: editing</span>
106
- <span class="c">→ state: verifying</span>
107
- <span class="c">→ state: done</span></pre>
114
+ <span class="c">-> state: reading</span>
115
+ <span class="c">-> state: editing</span>
116
+ <span class="c">-> state: verifying</span>
117
+ <span class="c">-> state: done</span></pre>
108
118
 
109
- <h2>philosophy</h2>
119
+ <h2 id="philosophy">philosophy</h2>
110
120
  <p>there is no philosophy. read the source.</p>
111
121
  </div>
112
122
  </main>
113
123
  </div>
114
124
  <footer class="app-status">
115
125
  <span class="item">main</span>
116
- <span class="item">• markdown</span>
117
- <span class="item">• ln 1</span>
126
+ <span class="item">- markdown</span>
127
+ <span class="item">- ln 1</span>
118
128
  <span class="spread"></span>
119
129
  <span class="item">gm · v0.4.1</span>
120
- <span class="item">• docs</span>
130
+ <span class="item">- docs</span>
121
131
  </footer>
122
132
  </div>
123
133
  </body></html>
@@ -1,56 +1,111 @@
1
1
  import * as webjsx from 'webjsx';
2
- import { Topbar, Crumb, Status, AppShell, Panel, Heading, Lede, Chip, RowLink } from 'ds/components.js';
2
+ import { Topbar, Crumb, Status, AppShell, Panel, Heading, Lede, RowLink } from 'ds/components.js';
3
3
  import { mountKit } from 'ds/bootstrap.js';
4
4
  const h = webjsx.createElement;
5
5
 
6
6
  const root = document.getElementById('root');
7
7
 
8
8
  const suggestions = [
9
- { code: '', title: 'index', sub: 'design system home', href: '../../', meta: 'open ' },
10
- { code: '', title: 'kits', sub: 'every ui kit in the portfolio', href: '../../#kits', meta: 'jump ' },
11
- { code: '§', title: 'previews', sub: 'every primitive isolated', href: '../../preview/buttons.html', meta: 'browse ' },
12
- { code: 'md',title: 'readme', sub: 'overview, manifesto, conventions', href: 'https://github.com/AnEntrypoint/design/blob/main/README.md', meta: 'readme ' },
13
- { code: '', title: 'github', sub: 'source repo · 247420/anentrypoint', href: 'https://github.com/AnEntrypoint/design', meta: 'source ' }
9
+ { code: '*', title: 'index', sub: 'design system home', href: '../../', meta: 'open ->' },
10
+ { code: '-', title: 'kits', sub: 'every ui kit in the portfolio', href: '../../#kits', meta: 'jump ->' },
11
+ { code: '//', title: 'previews', sub: 'every primitive isolated', href: '../../preview/buttons.html', meta: 'browse ->' },
12
+ { code: 'md',title: 'readme', sub: 'overview, manifesto, conventions', href: 'https://github.com/AnEntrypoint/design/blob/main/README.md', meta: 'readme ->' },
13
+ { code: '->', title: 'github', sub: 'source repo · 247420/anentrypoint', href: 'https://github.com/AnEntrypoint/design', meta: 'source ->' }
14
14
  ];
15
15
 
16
16
  const path = (typeof location !== 'undefined' && location.search) ? new URLSearchParams(location.search).get('p') : null;
17
17
 
18
+ // The suggestions panel is this page's one data surface — on a real site it is
19
+ // fed by a "did you mean" lookup against the sitemap, which can be slow, come
20
+ // back empty, or fail. The page is already the error state for the ROUTE; these
21
+ // are the states of the recovery list itself, which is the part that has to
22
+ // keep working when the rest of the page has already failed.
23
+ const state = { phase: 'ready' };
24
+ const PHASES = ['ready', 'loading', 'empty', 'error'];
25
+
26
+ // Row shimmer. Reuses .ds-event-row-skeleton + .ds-skel* (app-shell/files.css)
27
+ // — RowLink is the same code / title / meta rhythm.
28
+ function SuggestSkeleton() {
29
+ return h('div', {},
30
+ ...Array.from({ length: 5 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
31
+ h('span', { class: 'ds-skel ds-skel-rank' }),
32
+ h('span', { class: 'ds-skel ds-skel-title' }),
33
+ h('span', { class: 'ds-skel ds-skel-meta' })
34
+ ))
35
+ );
36
+ }
37
+
38
+ function SuggestEmpty() {
39
+ return h('div', { class: 'ds-empty-state' },
40
+ h('div', { class: 'ds-empty-state-glyph' }, '( )'),
41
+ h('p', { class: 'ds-empty-state-msg' }, 'nothing on this site looks like that url'),
42
+ h('p', { class: 'ds-empty-state-hint' }, 'the sitemap has no page with a similar name. the index and search links above still work — search is the better bet when the path was a guess.')
43
+ );
44
+ }
45
+
46
+ function SuggestError() {
47
+ return h('div', { class: 'ds-alert ds-alert-error' },
48
+ h('span', { class: 'ds-alert-icon' }, '!'),
49
+ h('div', { class: 'ds-alert-content' },
50
+ h('div', { class: 'ds-alert-title' }, 'could not look up alternatives'),
51
+ h('div', { class: 'ds-alert-message' }, 'the sitemap this page checks for near-matches is itself missing, so there is nothing to suggest. the back-to-index link above does not depend on it and still works.'),
52
+ h('div', { class: 'ds-alert-retry' },
53
+ h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; kit.render(); } }, 'try lookup again')
54
+ )
55
+ )
56
+ );
57
+ }
58
+
59
+ function SuggestBody() {
60
+ if (state.phase === 'loading') return SuggestSkeleton();
61
+ if (state.phase === 'error') return SuggestError();
62
+ if (state.phase === 'empty') return SuggestEmpty();
63
+ return h('div', {}, ...suggestions.map((s, i) => RowLink({ key: 's' + i, code: s.code, title: s.title, sub: s.sub, meta: s.meta, href: s.href })));
64
+ }
65
+
18
66
  function App() {
19
67
  return AppShell({
20
68
  narrow: true,
21
69
  topbar: Topbar({ brand: '247420', leaf: '404', items: [['index', '../../']] }),
22
70
  crumb: Crumb({ trail: ['247420', 'kits'], leaf: '404', right: path ? 'requested: ' + path : '' }),
23
71
  main: [
24
- h('div', { class: 'ds-section', style: 'padding:8px' },
25
- Panel({ style: 'margin:8px 0', children: h('div', { style: 'padding:36px 22px;text-align:center;display:flex;flex-direction:column;gap:14px;align-items:center' },
26
- h('div', { style: 'font-family:var(--ff-mono);font-size:120px;line-height:1;color:var(--panel-text-3);letter-spacing:-0.03em' }, '404'),
27
- Heading({ level: 1, style: 'margin:0', children: 'route not found' }),
72
+ h('div', { class: 'ds-section ds-section-pad' },
73
+ Panel({ class: 'ds-panel-gap', children: h('div', { class: 'ds-err-hero' },
74
+ h('div', { class: 'ds-err-numeral' }, '404'),
75
+ Heading({ level: 1, class: 'ds-m0', children: 'route not found' }),
28
76
  Lede({ children: 'we looked. we asked the cat. there is nothing at this url. probably emerging.' }),
29
- path ? h('div', { style: 'display:inline-flex;gap:6px;align-items:center;font-family:var(--ff-mono);font-size:13px;color:var(--panel-text-2);background:var(--panel-1);padding:6px 12px;border-radius:8px' },
30
- h('span', { style: 'color:var(--panel-text-3)' }, 'requested'),
77
+ path ? h('div', { class: 'ds-err-path' },
78
+ h('span', { class: 'ds-text-3' }, 'requested'),
31
79
  h('span', {}, path)
32
80
  ) : null,
33
- h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:6px' },
34
- h('a', { href: '../../', class: 'btn btn-primary' }, '← back to index'),
81
+ // Three real recoveries only. The suggestions state-cycler
82
+ // used to sit here as a fourth peer button, which put a
83
+ // demo control in the row a lost user actually reads and
84
+ // gave it a label ("suggestions: ready") that names no
85
+ // action. It moved to the panel head it governs.
86
+ h('div', { class: 'ds-err-actions' },
87
+ h('a', { href: '../../', class: 'btn btn-primary' }, '<- back to index'),
35
88
  h('a', { href: '../search/', class: 'btn' }, 'search instead'),
36
89
  h('button', { class: 'btn', onclick: () => history.back() }, 'go back')
37
- ),
38
- h('div', { style: 'display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-top:4px' },
39
- Chip({ tone: 'dim', children: '◌ empty' }),
40
- Chip({ tone: 'dim', children: '· status 404' }),
41
- Chip({ tone: 'dim', children: '· no body' })
42
90
  )
43
91
  ) }),
44
- Panel({ title: 'try one of these', count: suggestions.length, style: 'margin:8px 0',
45
- children: suggestions.map((s, i) => RowLink({ key: 's' + i, code: s.code, title: s.title, sub: s.sub, meta: s.meta, href: s.href }))
92
+ Panel({ title: 'try one of these', class: 'ds-panel-gap',
93
+ // The cycler lives on the panel it drives, so it reads as
94
+ // a demo affordance for this list rather than as a fourth
95
+ // recovery action in the hero.
96
+ right: h('button', { class: 'btn', onclick: () => {
97
+ state.phase = PHASES[(PHASES.indexOf(state.phase) + 1) % PHASES.length];
98
+ kit.render();
99
+ } }, state.phase === 'ready' ? suggestions.length + ' routes' : state.phase),
100
+ children: SuggestBody()
46
101
  })
47
102
  )
48
103
  ],
49
104
  status: Status({
50
- left: ['error', ' 404', ' ' + (path || 'unknown')],
105
+ left: ['error', '- 404', '- ' + (path || 'unknown'), '- suggest ' + state.phase],
51
106
  right: ['247420 / mmxxvi']
52
107
  })
53
108
  });
54
109
  }
55
110
 
56
- mountKit({ root, view: App, screen: '13 404' });
111
+ const kit = mountKit({ root, view: App, screen: '13 404' });
@@ -22,7 +22,7 @@ each FileRow takes `{ name, type, size, modified, onOpen, onAction }`. `type` is
22
22
 
23
23
  | type | rail color | glyph |
24
24
  | --------- | ---------- | ----- |
25
- | dir | green | |
25
+ | dir | green | |
26
26
  | image | sky | ◰ |
27
27
  | video | purple | ▰ |
28
28
  | audio | mascot | ◎ |
@@ -1,6 +1,6 @@
1
1
  import * as webjsx from 'webjsx';
2
2
  import {
3
- Topbar, Crumb, AppShell, Status, Btn,
3
+ Topbar, Crumb, AppShell, Status, Btn, Icon,
4
4
  FileGrid, FileToolbar, DropZone, UploadProgress, BreadcrumbPath,
5
5
  ConfirmDialog, PromptDialog, FileViewer,
6
6
  FilePreviewMedia, FilePreviewCode, FilePreviewText
@@ -41,9 +41,32 @@ const state = {
41
41
  viewer: null,
42
42
  confirm: null,
43
43
  prompt: null,
44
- promptValue: ''
44
+ promptValue: '',
45
+ // Which state the listing renders in. FileGrid already owns `loading`
46
+ // (cold-load shimmer) and the empty copy; `error` is a directory-level
47
+ // failure that has to sit above the grid because there is no listing at
48
+ // all to decorate. Driven by the toolbar buttons below so every state is
49
+ // reachable rather than only reproducible against a real broken mount.
50
+ phase: 'ready'
45
51
  };
46
52
 
53
+ const PHASES = ['ready', 'loading', 'empty', 'error'];
54
+
55
+ // Directory-level failure. Names the problem AND the recovery: a bare "could
56
+ // not load" tells the user nothing they can act on.
57
+ function DirError() {
58
+ return h('div', { class: 'ds-alert ds-alert-error' },
59
+ h('span', { class: 'ds-alert-icon' }, '!'),
60
+ h('div', { class: 'ds-alert-content' },
61
+ h('div', { class: 'ds-alert-title' }, 'cannot read demo/tigers'),
62
+ h('div', { class: 'ds-alert-message' }, 'the mount answered but refused the listing -- your account has write access to this path and not read. ask an owner for read, or open a folder you created.'),
63
+ h('div', { class: 'ds-alert-retry' },
64
+ h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; render(); } }, 'retry listing')
65
+ )
66
+ )
67
+ );
68
+ }
69
+
47
70
  const root = document.getElementById('root');
48
71
 
49
72
  function previewBody(file) {
@@ -135,7 +158,10 @@ function pickFiles() {
135
158
  }
136
159
 
137
160
  function App() {
138
- const main = h('div', { class: 'ds-file-stage' },
161
+ // ds-files-stack owns the full-height scroll geometry; ds-app-surface adds
162
+ // the Operate typescale so the page title sits at the app ceiling instead
163
+ // of the 64px display size, which outweighs the file list it labels.
164
+ const main = h('div', { class: 'ds-files-stack ds-app-surface' },
139
165
  h('h1', {}, 'file browser'),
140
166
  h('p', { class: 'lede' },
141
167
  'static demo of the 247420 file-browser primitives. drop files to fake-upload, click rows to preview, ',
@@ -148,7 +174,7 @@ function App() {
148
174
  }),
149
175
  FileToolbar({
150
176
  left: [
151
- Btn({ onClick: pickFiles, children: ' upload' }),
177
+ Btn({ onClick: pickFiles, 'aria-label': 'upload', children: [Icon('upload'), ' upload'] }),
152
178
  Btn({ onClick: () => {
153
179
  state.prompt = {
154
180
  title: 'new folder',
@@ -167,8 +193,15 @@ function App() {
167
193
  }, children: '+ folder' })
168
194
  ],
169
195
  right: [
196
+ // State switcher — keeps loading/empty/error one click away so
197
+ // they are living reference surfaces, not dead code.
198
+ ...PHASES.map((p) => h('button', {
199
+ key: 'ph-' + p,
200
+ class: state.phase === p ? 'btn btn-primary' : 'btn',
201
+ onclick: () => { state.phase = p; render(); }
202
+ }, p)),
170
203
  h('span', { class: 'meta ds-meta-mono' },
171
- String(state.files.length).padStart(2, '0') + ' items'
204
+ String(state.phase === 'ready' ? state.files.length : 0).padStart(2, '0') + ' items'
172
205
  )
173
206
  ]
174
207
  }),
@@ -181,11 +214,13 @@ function App() {
181
214
  onPick: pickFiles
182
215
  }),
183
216
  UploadProgress({ items: state.uploads }),
184
- FileGrid({
185
- files: state.files,
217
+ state.phase === 'error' ? DirError() : FileGrid({
218
+ files: state.phase === 'ready' ? state.files : [],
219
+ loading: state.phase === 'loading',
186
220
  onOpen: openViewer,
187
221
  onAction: rowAction,
188
- emptyText: 'nothing to showupload something or pick a folder.'
222
+ emptyText: 'this folder is empty drop files on the zone above, or use + folder to start a tree here.',
223
+ emptyAction: Btn({ onClick: pickFiles, children: 'upload a file' })
189
224
  })
190
225
  );
191
226
 
@@ -198,12 +233,12 @@ function App() {
198
233
  ['design', '../../'],
199
234
  ['home', '../homepage/'],
200
235
  ['docs', '../docs/'],
201
- ['source ', 'https://github.com/AnEntrypoint/Design']
236
+ ['source ->', 'https://github.com/AnEntrypoint/Design']
202
237
  ]
203
238
  }),
204
239
  crumb: Crumb({ trail: ['247420', 'ui kits'], leaf: 'file browser' }),
205
240
  main,
206
- status: Status({ left: ['main', ' ' + state.files.length + ' items'], right: ['live', 'demo only'] })
241
+ status: Status({ left: ['main', '- ' + (state.phase === 'ready' ? state.files.length : 0) + ' items', '- ' + state.phase], right: ['live', 'demo only'] })
207
242
  }),
208
243
  state.viewer ? FileViewer({
209
244
  file: state.viewer,
@@ -18,6 +18,7 @@
18
18
  <meta name="robots" content="index, follow">
19
19
  <link rel="stylesheet" href="../../colors_and_type.css">
20
20
  <link rel="stylesheet" href="../../app-shell.css">
21
+ <link rel="stylesheet" href="../../chat.css">
21
22
 
22
23
  <script type="importmap">
23
24
  { "imports": {