@milesroxas/create-design-hub 0.0.0-stage → 0.1.0

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -2
  3. package/index.mjs +154 -0
  4. package/lib.mjs +65 -0
  5. package/package.json +37 -3
  6. package/setup/.fallowrc.jsonc +6 -0
  7. package/setup/app.html +861 -0
  8. package/setup/lib.mjs +147 -0
  9. package/setup/lib.test.mjs +96 -0
  10. package/setup/server.mjs +444 -0
  11. package/setup/server.test.mjs +99 -0
  12. package/setup/slug.mjs +15 -0
  13. package/template/.claude/launch.json +18 -0
  14. package/template/.claude/settings.json +35 -0
  15. package/template/.design-hub/checks.json +9 -0
  16. package/template/.env.example +28 -0
  17. package/template/.impeccable/config.json +16 -0
  18. package/template/AGENTS.md +133 -0
  19. package/template/CLAUDE.md +1 -0
  20. package/template/DESIGN.md +126 -0
  21. package/template/Design Hub.command +11 -0
  22. package/template/PRODUCT.md +65 -0
  23. package/template/README.md +20 -0
  24. package/template/_gitattributes +2 -0
  25. package/template/_gitignore +41 -0
  26. package/template/api/review.ts +5 -0
  27. package/template/docs/.obsidian/app.json +6 -0
  28. package/template/docs/.obsidian/appearance.json +1 -0
  29. package/template/docs/.obsidian/core-plugins.json +32 -0
  30. package/template/docs/README.md +19 -0
  31. package/template/docs/sprints/sample/guide.md +69 -0
  32. package/template/kit/AppShell.tsx +149 -0
  33. package/template/kit/SiteShell.tsx +75 -0
  34. package/template/kit/app-shell.css +70 -0
  35. package/template/kit/icons.ts +11 -0
  36. package/template/kit/shell.ts +59 -0
  37. package/template/kit/site-shell.css +27 -0
  38. package/template/kit/tokens.css +249 -0
  39. package/template/package.json +38 -0
  40. package/template/plans/README.md +11 -0
  41. package/template/prototypes/sample-list/README.md +51 -0
  42. package/template/prototypes/sample-list/fixtures.json +87 -0
  43. package/template/prototypes/sample-list/index.tsx +122 -0
  44. package/template/prototypes/sample-list/styles.css +25 -0
  45. package/template/proxy.ts +4 -0
  46. package/template/sketches/paper/README.md +3 -0
  47. package/template/tsconfig.json +10 -0
  48. package/template/vercel.json +49 -0
@@ -0,0 +1,99 @@
1
+ // The server's guard rails, against the real process: it runs commands on this
2
+ // machine, so only its own page may reach the actions.
3
+ import assert from 'node:assert/strict';
4
+ import { spawn } from 'node:child_process';
5
+ import { request } from 'node:http';
6
+ import { after, before, test } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ let server;
10
+ let base;
11
+ let token;
12
+
13
+ before(async () => {
14
+ server = spawn(process.execPath, [fileURLToPath(new URL('server.mjs', import.meta.url)), '--no-open']);
15
+ base = await new Promise((resolve, reject) => {
16
+ let out = '';
17
+ server.stdout.on('data', (d) => {
18
+ out += d;
19
+ const url = out.match(/http:\/\/127\.0\.0\.1:\d+\//)?.[0];
20
+ if (url) resolve(url);
21
+ });
22
+ server.on('exit', () => reject(new Error(`server exited: ${out}`)));
23
+ });
24
+ token = (await (await fetch(base)).text()).match(/const TOKEN = '([0-9a-f]{32})'/)?.[1];
25
+ });
26
+
27
+ after(() => server.kill('SIGTERM'));
28
+
29
+ const post = (path, body = {}, headers = { 'x-setup-token': token }) =>
30
+ fetch(new URL(path, base), {
31
+ method: 'POST',
32
+ headers: { 'content-type': 'application/json', ...headers },
33
+ body: JSON.stringify(body),
34
+ });
35
+
36
+ test('the page carries a fresh token and shares the naming rule', async () => {
37
+ assert.match(token, /^[0-9a-f]{32}$/);
38
+ const slug = await fetch(new URL('slug.mjs', base));
39
+ assert.match(slug.headers.get('content-type'), /javascript/);
40
+ assert.match(await slug.text(), /export function slugify/);
41
+ });
42
+
43
+ test('another host name is refused, so a rebound DNS name reaches nothing', async () => {
44
+ const { port } = new URL(base);
45
+ const status = await new Promise((resolve, reject) => {
46
+ request({ host: '127.0.0.1', port, path: '/', headers: { host: `evil.example:${port}` } }, (res) => {
47
+ res.resume();
48
+ resolve(res.statusCode);
49
+ })
50
+ .on('error', reject)
51
+ .end();
52
+ });
53
+ assert.equal(status, 403);
54
+ });
55
+
56
+ test('actions need the token and a POST', async () => {
57
+ assert.equal((await post('/api/state', {}, {})).status, 403);
58
+ assert.equal((await post('/api/state', {}, { 'x-setup-token': 'nope' })).status, 403);
59
+ assert.equal((await fetch(new URL('/api/state', base))).status, 403);
60
+ assert.equal((await post('/api/nothing')).status, 404);
61
+ });
62
+
63
+ test('state describes this machine', async () => {
64
+ const state = await (await post('/api/state')).json();
65
+ assert.equal(state.mode, 'create');
66
+ assert.match(state.defaults.parent, /\/Design$/);
67
+ assert.equal(typeof state.tools.git, 'boolean');
68
+ });
69
+
70
+ test('the name check uses the same rule as create', async () => {
71
+ const empty = await (await post('/api/path', { name: '---', parent: '/tmp' })).json();
72
+ assert.deepEqual(empty, { ok: false, error: 'Use at least one letter or number in the name.' });
73
+ const relative = await (await post('/api/path', { name: 'x', parent: 'Design' })).json();
74
+ assert.equal(relative.ok, false);
75
+ });
76
+
77
+ test('project actions refuse a folder this server did not create', async () => {
78
+ for (const action of ['start', 'stop', 'reveal', 'claude']) {
79
+ const res = await (await post(`/api/${action}`, { path: '/etc' })).json();
80
+ assert.deepEqual(res, { ok: false, message: 'Unknown project.' }, action);
81
+ }
82
+ });
83
+
84
+ test('a bad create request is answered by field, before anything runs', async () => {
85
+ const res = await post('/api/create', {
86
+ name: 'x',
87
+ parent: '/tmp',
88
+ visibility: 'everyone',
89
+ owner: '-bad',
90
+ });
91
+ const events = (await res.text())
92
+ .trim()
93
+ .split('\n')
94
+ .map((line) => JSON.parse(line));
95
+ assert.equal(events.length, 1);
96
+ assert.equal(events[0].step, 'invalid');
97
+ assert.ok(events[0].errors.visibility);
98
+ assert.ok(events[0].errors.owner);
99
+ });
package/setup/slug.mjs ADDED
@@ -0,0 +1,15 @@
1
+ // The one rule for turning a project name into a folder and repo name. No
2
+ // imports, because the page loads this file too: the path it previews is the
3
+ // path the server creates.
4
+
5
+ /** The folder name for a project name: what create.mjs accepts as a repo name. */
6
+ export function slugify(name) {
7
+ return String(name ?? '')
8
+ .normalize('NFKD')
9
+ .replace(/[\u0300-\u036f]/g, '')
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9._-]+/g, '-')
12
+ .replace(/-{2,}/g, '-')
13
+ .replace(/^[-.]+|[-.]+$/g, '')
14
+ .slice(0, 80);
15
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": "0.0.1",
3
+ "configurations": [
4
+ {
5
+ "name": "prototypes",
6
+ "runtimeExecutable": "pnpm",
7
+ "runtimeArgs": [
8
+ "dev"
9
+ ],
10
+ "port": 3101
11
+ },
12
+ {
13
+ "name": "prototypes-attach",
14
+ "url": "http://localhost:3101",
15
+ "port": 3101
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": "Write|Edit|MultiEdit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" && node_modules/.bin/design-hub hook md-writing",
10
+ "timeout": 10,
11
+ "statusMessage": "Checking Markdown edit"
12
+ },
13
+ {
14
+ "type": "command",
15
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" && node_modules/.bin/design-hub hook fidelity-guard",
16
+ "timeout": 15,
17
+ "statusMessage": "Fidelity guard"
18
+ }
19
+ ]
20
+ }
21
+ ],
22
+ "UserPromptSubmit": [
23
+ {
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "cd \"$CLAUDE_PROJECT_DIR\" && node_modules/.bin/design-hub hook skill-hint",
28
+ "timeout": 5,
29
+ "statusMessage": "Skill hint"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "jev": { "model": "jev-1.14.0", "timeoutMs": 2000 },
3
+ "fidelity": { "warnAt": 0.7, "blockAt": 0.9 },
4
+ "states": { "blockAt": 0.8 },
5
+ "contacts": { "privateAt": 0.5, "allow": [] },
6
+ "skillHint": { "gateAt": 0.3, "fitsAt": 0.3 },
7
+ "triage": { "at": 0.5 },
8
+ "visual": { "warnAt": 0.7 }
9
+ }
@@ -0,0 +1,28 @@
1
+ # Written by `pnpm bootstrap`: the dev server's port, checked free on this
2
+ # machine at the time. Never commit .env.
3
+ PORT=
4
+
5
+ # Password gate for the deployed prototypes (proxy.ts). Set both in the Vercel
6
+ # project's Environment Variables for Production and Preview; `pnpm bootstrap
7
+ # --vercel <project>` does it. Never commit values.
8
+ # GATE_PASSWORD: what a reviewer types. Rotating it signs every visitor out.
9
+ GATE_PASSWORD=
10
+ # GATE_SECRET: 32+ random bytes, base64 or hex (openssl rand -base64 32). Signs the
11
+ # session cookie so a password guess cannot be turned into a forged cookie.
12
+ GATE_SECRET=
13
+
14
+ # Optional: TypeSafe's Jev checks (the fidelity guard, the pre-flight, comment
15
+ # triage, the skill hint). Set by `pnpm bootstrap --typesafe-key <key>`. Only this
16
+ # file turns it on, never a key in the shell. Absent, every Jev question is skipped.
17
+ TYPESAFE_API_KEY=
18
+
19
+ # Optional: the review store (comments and people) on the deployed site.
20
+ # `pnpm bootstrap --vercel <project> --review` adds Upstash Redis through the
21
+ # Vercel Marketplace and copies these two here, so `pnpm comments` and
22
+ # `pnpm people` reach the same store. Absent, they use the local dev store.
23
+ KV_REST_API_URL=
24
+ KV_REST_API_TOKEN=
25
+
26
+ # Optional: `pnpm fixtures-pull` reads a Webflow CMS collection with a site
27
+ # token that has the cms:read scope.
28
+ WEBFLOW_TOKEN=
@@ -0,0 +1,16 @@
1
+ {
2
+ "buildPath": "prototypes",
3
+ "detector": {
4
+ "extensions": [],
5
+ "designSystem": {
6
+ "enabled": true
7
+ },
8
+ "ignoreRules": [],
9
+ "ignoreFiles": [],
10
+ "ignoreValues": []
11
+ },
12
+ "hook": {
13
+ "enabled": true,
14
+ "quiet": true
15
+ }
16
+ }
@@ -0,0 +1,133 @@
1
+ # Agent guide
2
+
3
+ The contract for every coding agent in this project (Claude, Codex, Cursor, others). `CLAUDE.md` points here.
4
+
5
+ This project holds prototypes, the materials they are drawn with, and the documents around them. The hub that lists and serves them, the review panel, the password gate and the `design-hub` command are the `@milesroxas/design-hub` package in `node_modules`: never edit it, never read it to do prototype work. Everything an agent touches is in this folder.
6
+
7
+ ## Why it works this way
8
+
9
+ Code here is for sketching, not shipping. Following Karri Saarinen's [Design is more than code](https://linear.app/now/design-is-more-than-code), a prototype answers a question that was written down first, in words. Speed comes from removing production constraints, never from skipping the problem.
10
+
11
+ 1. **Problem first.** `README.md` states the problem and the one question before any code exists. Restate it when you present the prototype.
12
+ 2. **Two places to stand.** Pick one per prototype: the disposable shell (`AppShell` for a product, `SiteShell` for a website), the project's chrome from `kit/shell.ts` and `DESIGN.md`, Shell; or the low-gravity space (`Space`), one thing alone on a quiet canvas with the picker and live dials.
13
+ 3. **Low gravity.** A prototype imports only `@kit/*` and its own folder. Never another prototype. No tests, no stories, no abstractions for later. The fidelity ceiling is in the `miles-prototype` skill, and the fidelity guard hook says so when a write goes over it.
14
+ 4. **Fork, don't flex.** If a kit piece needs to behave differently, copy it into the prototype folder and change the copy. Never add a prop or a variant to `kit/` for one prototype.
15
+ 5. **Promote on second use.** When a second prototype copies the same thing, move it into `kit/` and point both at it.
16
+ 6. **Real materials, worst content.** Real tokens, fonts and icons. The content comes from `fixtures.json`: `real` pulled from the CMS or copied from the comp, `missing` written by hand, and `longest`, `empty` and `max` derived by `@kit/fixtures`.
17
+ 7. **Done means decided.** When the user picks, write the decision in the prototype's README: what was chosen, its values, what was rejected and why. Then delete the folder, or set `status: archived` if the user wants it kept.
18
+ 8. **The floor never moves.** Accessibility and performance are not polish passes. The `miles-prototype` skill carries the rules.
19
+
20
+ ## Stack (fixed)
21
+
22
+ | Need | Use | Not |
23
+ | --- | --- | --- |
24
+ | UI | React 19 function components, TypeScript strict | class components |
25
+ | Styling | Plain CSS beside the component, `kit/tokens.css` variables | Tailwind, CSS-in-JS, CSS modules, UI libraries |
26
+ | Motion | CSS transitions, `@starting-style`, keyframes, WAAPI first; `motion/react` only when it earns it | GSAP, react-spring |
27
+ | Live values, controls | DialKit through `@kit/dials` | custom panels, testing aids inside the product chrome |
28
+ | Data | `fixtures.json` in the prototype folder | fetching, a server, localStorage as a store |
29
+ | State, routing | `useState`, a module store with `useSyncExternalStore`, `@kit/hooks` `useHashRoute` | state libraries, React Router |
30
+ | Tests | none in a prototype | Vitest, Jest, Playwright, Storybook |
31
+
32
+ Adding a dependency needs the user's OK. Ask, with the reason and the bundle cost.
33
+
34
+ ## Commands
35
+
36
+ Run from the project root. Every command prints one JSON object; `--help` prints its schema; `ok: false` comes with `fix`. Read the JSON; never parse prose.
37
+
38
+ ```bash
39
+ pnpm dev # http://localhost:<PORT>, review panel and all
40
+ pnpm new <slug> --feature <feature> # a space prototype with the picker and dials
41
+ pnpm new <slug> --app --sprint --feature <feature> # the feature's one primary prototype, in the shell
42
+ pnpm new <slug> --app --alternate --feature <feature> # another take on the primary
43
+ pnpm guide <feature> # the feature's walkthrough, docs/sprints/<feature>/guide.md
44
+ pnpm check # TypeScript and Biome; must pass before you report done
45
+ pnpm fix # Biome format and safe fixes
46
+ pnpm preflight # every check a share link needs (miles-deploy)
47
+ pnpm comments pull # review comments into docs/sprints/<feature>/comments.md (miles-review)
48
+ pnpm fixtures-pull <slug> --collection <id> --map key=field # real fixtures from Webflow (miles-get-hostile)
49
+ pnpm commands # every command, with its flags, as JSON
50
+ ```
51
+
52
+ The port is Conductor's `$CONDUCTOR_PORT` in a workspace, else the `PORT` in `.env` that `pnpm bootstrap` picked. A taken port is refused. Never pass another port: a second port silently serves the wrong project.
53
+
54
+ ## Layout
55
+
56
+ ```
57
+ prototypes/<slug>/ one prototype: README.md, index.tsx, styles.css, fixtures.json
58
+ kit/ the project's materials: tokens.css, shell.ts (brand, nav), icons.ts, AppShell, SiteShell
59
+ docs/sprints/<feature>/ design-doc.md, guide.md, comments.md (generated), port.md, assets/
60
+ DESIGN.md PRODUCT.md the design system's named rules; who the product is for
61
+ .design-hub/checks.json the Jev thresholds (design-hub calibrate --write rewrites it)
62
+ api/review.ts proxy.ts vercel.json the deploy entries; one line each, never edited
63
+ ```
64
+
65
+ `prototypes/sample-list/` is the reference for every convention; delete it once the project has a feature.
66
+
67
+ ## A prototype
68
+
69
+ README frontmatter, all fields required (`feature` may be empty for a one-off): `title`, `summary`, `context` (space | app), `status` (exploring | decided | archived), `kind` (sprint | alternate | exploration), `feature`, `created`. `pnpm new` writes it.
70
+
71
+ The README's `## States` table lists every state the prototype holds and how to reach it. The fidelity guard asks whether a write adds a state that is not there, and `miles-port` refuses a folder without the table.
72
+
73
+ - Sub-pages are hash routes (`/sample-list/#/items/42`) via `useHashRoute`. The path belongs to the hub, and `/guide/` is taken.
74
+ - Persist with `useStoredState(key, initial)`, keys prefixed with the slug. A preference, never a store.
75
+ - Only the open prototype's code and CSS load. Kit class names are shared: never reuse one for something else.
76
+
77
+ ## A feature
78
+
79
+ One slug in two places: `docs/sprints/<feature>/` and the `feature:` line of every prototype that belongs to it. The hub serves `docs/sprints/<feature>/guide.md` at `/guide/<feature>/`; inside any prototype of the feature, **Open the walkthrough** is the last row of Controls. Nothing is wired by hand.
80
+
81
+ ## Kit
82
+
83
+ `@kit/<name>` is `kit/<name>` when the project has that file, else the package's copy. Copying a package file into `kit/` under the same name replaces it everywhere.
84
+
85
+ | Import | What |
86
+ | --- | --- |
87
+ | `@kit/shell` | `BRAND`, `PRODUCT` (nav, workspace, user), `SITE` (nav, footer): the one place the chrome is written. |
88
+ | `@kit/AppShell`, `@kit/SiteShell` | The disposable shells. Mount the dialog, sheet and toast hosts and the View dials. `PageHead({ title, desc, back, actions })`. |
89
+ | `@kit/Space` | `<Space title lede width>`: the low-gravity canvas. |
90
+ | `@kit/ui` | `Button`, `Chevron`, `Segmented`, `Badge`, `Checkbox`, `useMenu`, `Select`, `openDialog`, `confirmDialog`, `openSheet`, `toast()`, `notInPrototype(what)`. Card, table and empty-state classes in `ui.css`. |
91
+ | `@kit/fixtures` | `fixtureSets({ real, missing }, { max, text })` returns `{ real, longest, empty, max, missing }`; `SET_NAMES` for the dial. |
92
+ | `@kit/Variants` | `<Variants options={{ Quiet, Editorial }}>`: the picker. Keys 1-N, arrows, R; `?v=N`. |
93
+ | `@kit/dials` | `useDials(name, config, { hints })` for values JS reads; `<DialVars name config target?>` writes CSS variables without re-rendering. Every dial carries a hint. |
94
+ | `@kit/Icon`, `@kit/icons` | `<Icon name size label?>`. Add a glyph in `kit/icons.ts`. |
95
+ | `@kit/prefs`, `@kit/hooks`, `@kit/format`, `@kit/motion` | Theme and motion preferences; `useHashRoute`, `useStoredState`; `fmtDate`, `plural`; `ease.*` and `spring.*`. |
96
+ | `@kit/tokens.css` | The design's tokens, light and `.dark-mode`. Loaded globally. |
97
+
98
+ URL switches on any prototype: `?v=2`, `?theme=dark|light|system`, `?motion=reduce`, `?lapse`, `?a11y=0`.
99
+
100
+ ## Code
101
+
102
+ - TypeScript strict. `any` is allowed when it saves time; never `// @ts-ignore`.
103
+ - Comments: none by default. One line when the reason is not obvious. Rationale, values and rejected options go in the README.
104
+ - Colors, radii, shadows, type and motion come from tokens. A raw value is a decision: name it in the README or add a token and note it in `DESIGN.md`.
105
+ - Copy follows `DESIGN.md` and the product voice: plain, specific, no em dashes.
106
+ - **The Scope Toast Rule.** Anything a control would do that the prototype does not build says so in the prototype's own voice: `notInPrototype('Model history')`, or `toast(message, { kind: 'scope' })`. A product toast with a green check for something that did not happen is a bug.
107
+
108
+ ## Definition of done
109
+
110
+ 1. `pnpm check` passes.
111
+ 2. The page loads with no console errors and no `[a11y]` warnings.
112
+ 3. The main path works by keyboard alone.
113
+ 4. `?motion=reduce` and `?theme=dark` look right, when the prototype has motion or should support dark.
114
+ 5. README is current: problem, question, flows or directions, states, fixtures, and the decision once there is one.
115
+
116
+ Say what you could not verify. Do not commit unless asked.
117
+
118
+ ## Skills
119
+
120
+ The `miles-*` skills in `.claude/skills/` come from the `@milesroxas/design-hub` package (`pnpm skills` refreshes them; never edit the copies). Load one when its trigger matches.
121
+
122
+ | Skill | Load when |
123
+ | --- | --- |
124
+ | `miles-prototype` | Writing or changing any prototype code: the fidelity ceiling, DialKit, motion, accessibility, performance. |
125
+ | `miles-new-feature` | Starting a feature or a prototype: the folder, the README, `docs/sprints/<feature>/`. |
126
+ | `miles-get-hostile` | Writing `fixtures.json` or deciding what an edge case looks like. |
127
+ | `miles-paper-to-prototype` | Every Paper MCP read. Maps Paper output onto the kit's tokens and components. |
128
+ | `miles-deploy` | Deploying, sharing a link, the password, checking the work before either. |
129
+ | `miles-review` | Comments from the deployed prototypes, and who may comment. |
130
+ | `miles-port` | Turning an approved prototype into production. |
131
+ | `miles-conductor` | Conductor workspaces and port collisions. |
132
+
133
+ Existing skills these lean on: `emil-prototype`, `emil-animations`, `emil-touch-and-accessibility`, `emil-performance`, `emil-writing-skills` for every Markdown file, `impeccable` for a design pass. If a skill disagrees with this file, this file wins.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: Project name
3
+ description: The product design system this project builds on, recorded from the design (Paper) and, where one exists, the live product
4
+ colors:
5
+ brand: "#2b57cc"
6
+ brand-hover: "#2246a6"
7
+ ink: "#101828"
8
+ ink-secondary: "#344054"
9
+ ink-tertiary: "#475467"
10
+ ink-quaternary: "#667085"
11
+ surface: "#ffffff"
12
+ surface-secondary: "#f9fafb"
13
+ surface-tertiary: "#f2f4f7"
14
+ line: "#d0d5dd"
15
+ line-secondary: "#eaecf0"
16
+ error: "#d92d20"
17
+ warning: "#dc6803"
18
+ success: "#079455"
19
+ typography:
20
+ display:
21
+ fontFamily: "Inter, system-ui, sans-serif"
22
+ fontSize: "24px"
23
+ fontWeight: 600
24
+ lineHeight: "32px"
25
+ headline:
26
+ fontFamily: "Inter, system-ui, sans-serif"
27
+ fontSize: "18px"
28
+ fontWeight: 600
29
+ lineHeight: "28px"
30
+ body:
31
+ fontFamily: "Inter, system-ui, sans-serif"
32
+ fontSize: "14px"
33
+ fontWeight: 400
34
+ lineHeight: "20px"
35
+ label:
36
+ fontFamily: "Inter, system-ui, sans-serif"
37
+ fontSize: "14px"
38
+ fontWeight: 600
39
+ lineHeight: "20px"
40
+ caption:
41
+ fontFamily: "Inter, system-ui, sans-serif"
42
+ fontSize: "12px"
43
+ fontWeight: 400
44
+ lineHeight: "18px"
45
+ rounded:
46
+ xs: "4px"
47
+ sm: "6px"
48
+ md: "8px"
49
+ lg: "10px"
50
+ xl: "12px"
51
+ full: "9999px"
52
+ spacing:
53
+ 2xs: "4px"
54
+ xs: "8px"
55
+ sm: "12px"
56
+ md: "16px"
57
+ lg: "20px"
58
+ xl: "24px"
59
+ 2xl: "32px"
60
+ ---
61
+
62
+ # Design System: Project name
63
+
64
+ This file is the template's. Replace every value and every sentence below with the project's own the first time `miles-paper-to-prototype` reads the design, and keep it current after: `kit/tokens.css` holds the same values as CSS variables, and the two must agree. The frontmatter is what Impeccable reads (`/impeccable doctor` checks it); the prose is what a person or an agent reads before touching the product.
65
+
66
+ ## Overview
67
+
68
+ One paragraph on what the product is for and how the interface serves that. Name the creative north star in a phrase, then the two or three characteristics that follow from it (one typeface and why; where saturated colour is allowed; how edges are drawn). Say whether light and dark are equal citizens.
69
+
70
+ ## Shell
71
+
72
+ Every project has a shell: a product's sidebar, top bar and content, or a website's header and footer, with a pre-footer when the design treats one as a global. Record the answer `miles-design-hub` collected so it is built once and never asked again:
73
+
74
+ - **Kind:** product or website.
75
+ - **Paper frames:** the link to each frame that holds the shell (header, sidebar, footer, pre-footer). Empty when the design has no shell and prototypes stand in the Space.
76
+ - **Where it is built:** `kit/shell.ts` (brand, navigation, people) and `AppShell.tsx` or `SiteShell.tsx`. A prototype never rebuilds the shell inside its own folder.
77
+
78
+ ## Colors
79
+
80
+ Group by role: primary, neutral, status, any utility family. For each colour, its name, hex, token, what it is for and its dark value. Then the named rules, each a bold name and a paragraph that says the rule and why it holds.
81
+
82
+ ### Named Rules
83
+
84
+ **The Labelled Hue Rule.** Colour never carries meaning alone. A status gets a dot or a word, a chart series a legend entry, a tier its name. Why: a reviewer in grayscale, a printout and a screen reader all lose the hue.
85
+
86
+ **The Hover Rule.** A hovered row or menu item lifts on `--bg-primary_hover`, never on a brand tint. Why: a brand tint on hover reads as selection, and the two states must stay distinct.
87
+
88
+ ## Typography
89
+
90
+ The faces, the weights, and the hierarchy as a ladder: display, headline, title, body, label, caption, each with size, line height, weight and where it appears. Then the named rules.
91
+
92
+ ### Named Rules
93
+
94
+ **The Weight Ladder Rule.** Hierarchy climbs 400 to 500 to 600 before it climbs in size. Two sizes apart with the same weight is a mistake. Why: weight separates levels at the same measure; size alone makes every level a new line length.
95
+
96
+ ## Layout
97
+
98
+ The shell's dimensions, the content measure, the breakpoints, the spacing steps observed and which to prefer.
99
+
100
+ ## Elevation & Depth
101
+
102
+ How edges are drawn (hairline first, shadow second), the shadow vocabulary by name, and what dark mode does instead.
103
+
104
+ ### Named Rules
105
+
106
+ **The Hairline First Rule.** An edge is a 1px border in `--border-primary` or `--border-secondary`; a shadow may reinforce it but never replaces it. Why: dark mode draws no shadows, and the border has to be enough on its own.
107
+
108
+ **The Hit Area Rule.** Nothing clickable is smaller than 40px on a side on desktop, 44px on touch. A small glyph gets padding or a pseudo-element, never a smaller target. Why: a target that is hard to hit is a defect the reviewer blames on the design.
109
+
110
+ ## Shapes
111
+
112
+ Radii by element, border placement, icon size and set.
113
+
114
+ ## Components
115
+
116
+ Buttons, badges, cards, inputs, navigation, tables: the shape, the states, the motion, in that order. Rules decided in a prototype land here as named rules the day they are decided, with the date and the prototype that decided them.
117
+
118
+ ## Do's and Don'ts
119
+
120
+ ### Do:
121
+ - **Do** take every value from `kit/tokens.css` and keep this file in step with it.
122
+ - **Do** design every screen in light and dark and preview both before calling it done.
123
+
124
+ ### Don't:
125
+ - **Don't** use raw hex in a prototype. A raw value is a decision: name it in the README or add a token and note it here.
126
+ - **Don't** restyle the shell inside a prototype. Change `kit/shell.ts` and say so here.
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ # Double-click to open this project's Design Hub in the browser. Keep the
3
+ # window open while you work; closing it stops the hub.
4
+ cd "$(dirname "$0")" || exit 1
5
+ export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
6
+ if [ ! -x node_modules/.bin/design-hub ]; then
7
+ echo "Installing the project first (one time)."
8
+ command -v pnpm >/dev/null 2>&1 && pnpm install || npx --yes pnpm@12 install
9
+ fi
10
+ [ -f .env ] && grep -q '^PORT=[0-9]' .env || node_modules/.bin/design-hub bootstrap --no-install >/dev/null
11
+ exec node_modules/.bin/design-hub open
@@ -0,0 +1,65 @@
1
+ # Product
2
+
3
+ <!-- impeccable:product-schema 1 -->
4
+
5
+ This file is the template's. Fill every section the first time `miles-design-hub` sets the project up, from the kickoff and the design; an empty section is a question Claude will answer by guessing.
6
+
7
+ ## Platform
8
+
9
+ web
10
+
11
+ ## Stack
12
+
13
+ Disposable prototypes in `prototypes/`, one folder per prototype, with the design's tokens in `kit/tokens.css`. The comps are Paper frames (`DESIGN.md`, Shell, names the ones that hold the shell). The live product is built elsewhere and is not in this repo; `miles-port` carries an approved prototype into it.
14
+
15
+ ## Users
16
+
17
+ Who uses the product, in what role, how often, and to what end. Then the secondary readers: who sees the output without opening the product.
18
+
19
+ ## Product Purpose
20
+
21
+ What the product does, in two or three sentences a new engineer could repeat. What success looks like for a user, and what the repeat loop is.
22
+
23
+ ## Positioning
24
+
25
+ What the product claims that its alternatives do not, in the product's own words.
26
+
27
+ ## Operating Context
28
+
29
+ - The shell: the navigation items in order, and what sits at the foot.
30
+ - The object model: the nouns, what holds what.
31
+ - The journey, in order.
32
+ - Dense or sparse, which units, which appearance modes.
33
+ - Work in progress, with the feature slug under `docs/sprints/`.
34
+
35
+ ## Capabilities and Constraints
36
+
37
+ - Terminology as the product writes it: the abbreviations, the series names, the tiers.
38
+ - UI copy conventions: case, numbers, missing values, units.
39
+ - Undecided product facts. Do not invent these; name who decides.
40
+ - Known product gaps to design around, not hide.
41
+
42
+ ## Brand Commitments
43
+
44
+ - Name and company.
45
+ - Typeface, as shipped. Binding.
46
+ - Brand colours, by token.
47
+ - Voice: plain, specific, sentence case, no hype.
48
+
49
+ ## Evidence on Hand
50
+
51
+ - The live product and who has access to what.
52
+ - Reference screenshots and where they are.
53
+ - Research and kickoff notes under `docs/research/`.
54
+ - What does not exist (testimonials, pricing, real figures), so nothing is presented as real that is not.
55
+
56
+ ## Product Principles
57
+
58
+ 1. Lead with the answer.
59
+ 2. Every number traces to its inputs.
60
+ 3. Preserve the product's system; add controls only where the journey needs them.
61
+ 4. Warn before side effects.
62
+
63
+ ## Accessibility & Inclusion
64
+
65
+ WCAG 2.2 AA. Colour never carries meaning alone. Everything works by keyboard. State the product's own additions here (print, grayscale, screen reader paths).
@@ -0,0 +1,20 @@
1
+ # Design work
2
+
3
+ Prototypes for one product or website, built on [Design Hub](https://github.com/milesroxas/design-hub). One repo holds the prototypes, the design rules, the walkthroughs reviewers read, and their comments.
4
+
5
+ ```bash
6
+ pnpm bootstrap # once per clone: a free port into .env, install
7
+ pnpm dev # http://localhost:<PORT>
8
+ ```
9
+
10
+ Or double-click `Design Hub.command`.
11
+
12
+ | Path | What goes there |
13
+ | --- | --- |
14
+ | `prototypes/<slug>/` | One prototype: its README, code, styles and fixtures. `sample-list/` is the reference; delete it once the project has a feature. |
15
+ | `kit/` | The project's materials: `tokens.css`, `shell.ts` (brand and navigation), `icons.ts`, the disposable `AppShell` and `SiteShell`. |
16
+ | `docs/sprints/<feature>/` | A feature's design doc, walkthrough, pulled comments and port report. |
17
+ | `DESIGN.md`, `PRODUCT.md` | The recorded design system and who the product is for. |
18
+ | `.claude/` | Claude Code hooks and the `miles-*` skills (`pnpm skills` refreshes them from the package). |
19
+
20
+ The rules for people and agents are in [AGENTS.md](AGENTS.md). Deploying, the password gate and review comments: the `miles-deploy` and `miles-review` skills, or `pnpm commands` for every command.
@@ -0,0 +1,2 @@
1
+ # Generated by `pnpm elyx` from kit/tokens.css; reviews skip it.
2
+ design-system/** linguist-generated=true
@@ -0,0 +1,41 @@
1
+ # macOS
2
+ .DS_Store
3
+ ._*
4
+
5
+ # Editors
6
+ .vscode/
7
+ .idea/
8
+
9
+ # Dependencies and builds
10
+ node_modules/
11
+ dist/
12
+ .vercel/
13
+
14
+ # Environment and secrets
15
+ .env
16
+ .env.*
17
+ !.env.example
18
+
19
+ # Logs
20
+ *.log
21
+
22
+ # Local state: the Jev log and its labels, the dev review store. checks.json is the project's thresholds and stays.
23
+ .design-hub/*
24
+ !.design-hub/checks.json
25
+
26
+ # Obsidian per-machine state (docs/ is an Obsidian vault)
27
+ docs/.obsidian/workspace*.json
28
+
29
+ # Impeccable per-developer overrides and generated caches
30
+ .impeccable/config.local.json
31
+ .impeccable/live/
32
+ .impeccable/hook.cache.json
33
+
34
+ # Claude Code worktrees
35
+ .claude/worktrees/
36
+
37
+ # Conductor per-developer settings
38
+ .conductor/settings.local.toml
39
+
40
+ # Elyx add-on: fetched dependencies
41
+ design-system/elyx_deps/
@@ -0,0 +1,5 @@
1
+ // The review API on Vercel. The project's thresholds travel with it.
2
+ import { reviewFunction } from '@milesroxas/design-hub/vercel';
3
+ import checks from '../.design-hub/checks.json' with { type: 'json' };
4
+
5
+ export default reviewFunction(checks);
@@ -0,0 +1,6 @@
1
+ {
2
+ "useMarkdownLinks": true,
3
+ "newLinkFormat": "relative",
4
+ "attachmentFolderPath": "assets",
5
+ "promptDelete": false
6
+ }