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.
- package/dist/247420.js +62 -12
- package/dist/app-shell.css +40 -594
- package/dist/colors_and_type.css +607 -47
- package/dist/index.html +227 -277
- package/dist/preview/buttons.html +39 -28
- package/dist/preview/colors-core.html +18 -8
- package/dist/preview/colors-lore.html +26 -17
- package/dist/preview/colors-semantic.html +24 -18
- package/dist/preview/dateline.html +6 -5
- package/dist/preview/dropzone.html +4 -3
- package/dist/preview/file-grid.html +10 -9
- package/dist/preview/file-row.html +14 -13
- package/dist/preview/file-toolbar.html +4 -3
- package/dist/preview/file-viewer.html +4 -3
- package/dist/preview/header.html +26 -24
- package/dist/preview/icons-unicode.html +8 -5
- package/dist/preview/index-row.html +17 -9
- package/dist/preview/inputs.html +4 -3
- package/dist/preview/manifesto.html +34 -17
- package/dist/preview/motion-default.js +77 -57
- package/dist/preview/rules.html +4 -3
- package/dist/preview/spacing.html +13 -12
- package/dist/preview/stamps-lore.html +5 -4
- package/dist/preview/stamps.html +4 -3
- package/dist/preview/theme-ink.html +7 -6
- package/dist/preview/type-display.html +14 -5
- package/dist/preview/type-mono.html +6 -5
- package/dist/preview/type-prose.html +12 -10
- package/dist/preview/type-scale.html +4 -3
- package/dist/preview/wordmarks.html +10 -7
- package/dist/site/content/globals/site.yaml +5 -3
- package/dist/site/content/pages/freddie.yaml +2 -2
- package/dist/site/content/pages/home.yaml +55 -55
- package/dist/site/theme.mjs +96 -348
- package/dist/slides/index.html +13 -13
- package/dist/src/bootstrap.js +10 -1
- package/dist/src/components/chat.js +43 -195
- package/dist/src/components/community.js +18 -166
- package/dist/src/components/content.js +30 -210
- package/dist/src/components/files-modals.js +14 -106
- package/dist/src/components/files.js +15 -116
- package/dist/src/components/freddie/helpers.js +53 -8
- package/dist/src/components/freddie.js +35 -25
- package/dist/src/components/shell.js +16 -114
- package/dist/src/components/theme-toggle.js +69 -25
- package/dist/src/components.js +92 -13
- package/dist/src/deck-stage.js +6 -4
- package/dist/src/highlight.js +51 -7
- package/dist/src/index.js +58 -12
- package/dist/src/kits/os/about-app.js +2 -3
- package/dist/src/kits/os/app-panes.css +46 -70
- package/dist/src/kits/os/browser-app.js +28 -1
- package/dist/src/kits/os/files-app.js +7 -0
- package/dist/src/kits/os/freddie/pages-chat.js +90 -75
- package/dist/src/kits/os/freddie/pages-core.js +9 -9
- package/dist/src/kits/os/freddie/pages-os.js +8 -8
- package/dist/src/kits/os/freddie/pages-tools.js +14 -14
- package/dist/src/kits/os/freddie/routes.js +21 -19
- package/dist/src/kits/os/freddie-dashboard.css +42 -21
- package/dist/src/kits/os/freddie-dashboard.js +6 -6
- package/dist/src/kits/os/icons.js +38 -16
- package/dist/src/kits/os/index.js +11 -0
- package/dist/src/kits/os/launcher.css +26 -10
- package/dist/src/kits/os/launcher.js +3 -0
- package/dist/src/kits/os/shell.js +72 -108
- package/dist/src/kits/os/terminal-app.js +1 -1
- package/dist/src/kits/os/theme.css +1330 -150
- package/dist/src/kits/os/wm.css +68 -15
- package/dist/src/kits/os/wm.js +93 -17
- package/dist/src/markdown.js +121 -12
- package/dist/src/motion.js +18 -2
- package/dist/src/page-html.js +62 -134
- package/dist/src/theme.js +106 -13
- package/dist/src/web-components/ds-chat.js +80 -9
- package/dist/ui_kits/aicat/app.js +54 -6
- package/dist/ui_kits/aicat/index.html +2 -0
- package/dist/ui_kits/blog/index.html +33 -17
- package/dist/ui_kits/chat/app.js +90 -22
- package/dist/ui_kits/chat/index.html +2 -0
- package/dist/ui_kits/community/app.js +86 -24
- package/dist/ui_kits/community/index.html +1 -0
- package/dist/ui_kits/dashboard/app.js +123 -34
- package/dist/ui_kits/dashboard/index.html +1 -0
- package/dist/ui_kits/docs/index.html +43 -33
- package/dist/ui_kits/error_404/app.js +78 -23
- package/dist/ui_kits/file_browser/README.md +1 -1
- package/dist/ui_kits/file_browser/app.js +45 -10
- package/dist/ui_kits/file_browser/index.html +1 -0
- package/dist/ui_kits/gallery/app.js +130 -51
- package/dist/ui_kits/homepage/app.js +156 -138
- package/dist/ui_kits/project_page/app.js +142 -125
- package/dist/ui_kits/project_page/index.html +2 -1
- package/dist/ui_kits/search/app.js +52 -15
- package/dist/ui_kits/settings/app.js +166 -39
- package/dist/ui_kits/signin/app.js +175 -49
- package/dist/ui_kits/signin/index.html +1 -0
- package/dist/ui_kits/slide_deck/app.js +83 -49
- package/dist/ui_kits/system_primer/app.js +57 -40
- package/dist/ui_kits/terminal/app.js +160 -43
- package/package.json +1 -1
- package/src/page-html/client-script.js +40 -11
- 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
|
|
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
|
-
{
|
|
41
|
-
{
|
|
42
|
-
{
|
|
43
|
-
{
|
|
44
|
-
{
|
|
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
|
|
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: '
|
|
55
|
-
{ glyph: '
|
|
56
|
-
{ glyph: '
|
|
57
|
-
{ glyph: '
|
|
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: '
|
|
61
|
-
{ glyph: '
|
|
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'
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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: '
|
|
78
|
-
|
|
79
|
-
h('p', {}, '· ', Chip({ tone: 'accent', children: '
|
|
80
|
-
h('p', {}, '· ', Chip({ tone: 'accent', children: '
|
|
81
|
-
h('p', {}, '· ', Chip({ tone: 'accent', children: '
|
|
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', '
|
|
87
|
-
right: ['247420 / mmxxvi', '
|
|
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/"
|
|
41
|
-
<a href="#">readme</a>
|
|
42
|
-
<a
|
|
43
|
-
<a href="
|
|
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"
|
|
57
|
-
<a href="#"><span class="glyph"
|
|
58
|
-
<a href="#"><span class="glyph"
|
|
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
|
|
61
|
-
<a
|
|
62
|
-
<a
|
|
69
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>states & transitions</span></a>
|
|
70
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>the loop</span></a>
|
|
71
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>tools</span></a>
|
|
63
72
|
<div class="group">reference</div>
|
|
64
|
-
<a
|
|
65
|
-
<a
|
|
66
|
-
<a
|
|
67
|
-
<a
|
|
73
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>executenodejs</span></a>
|
|
74
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>executedeno</span></a>
|
|
75
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>astgrep_*</span></a>
|
|
76
|
+
<a class="is-unwritten" title="not written yet"><span class="glyph">></span><span>batch_execute</span></a>
|
|
68
77
|
<div class="group">misc</div>
|
|
69
|
-
<a href="#"><span class="glyph"
|
|
70
|
-
<a href="
|
|
71
|
-
<a href="
|
|
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">-></span><span>changelog</span></a>
|
|
80
|
+
<a href="https://github.com/AnEntrypoint/gm"><span class="glyph">-></span><span>source</span></a>
|
|
72
81
|
</aside>
|
|
73
|
-
|
|
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
|
|
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 <
|
|
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"
|
|
102
|
-
<span class="c"
|
|
111
|
+
<span class="c">-> state: idle</span>
|
|
112
|
+
<span class="c">-> tools available: 19</span>
|
|
103
113
|
> fix the failing test in src/router.ts
|
|
104
|
-
<span class="c"
|
|
105
|
-
<span class="c"
|
|
106
|
-
<span class="c"
|
|
107
|
-
<span class="c"
|
|
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"
|
|
117
|
-
<span class="item"
|
|
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"
|
|
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,
|
|
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: '
|
|
10
|
-
{ code: '
|
|
11
|
-
{ code: '
|
|
12
|
-
{ code: 'md',title: 'readme', sub: 'overview, manifesto, conventions', href: 'https://github.com/AnEntrypoint/design/blob/main/README.md', meta: 'readme
|
|
13
|
-
{ code: '
|
|
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
|
|
25
|
-
Panel({
|
|
26
|
-
h('div', {
|
|
27
|
-
Heading({ level: 1,
|
|
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', {
|
|
30
|
-
h('span', {
|
|
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
|
-
|
|
34
|
-
|
|
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',
|
|
45
|
-
|
|
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', '
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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
|
|
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', '
|
|
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,
|