@milesroxas/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.
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/client.d.ts +6 -0
- package/configs/biome/biome.json +63 -0
- package/configs/tsconfig.project.json +17 -0
- package/core/bytes.test.ts +14 -0
- package/core/bytes.ts +14 -0
- package/core/catalog.test.ts +44 -0
- package/core/catalog.ts +43 -0
- package/core/checks.test.ts +23 -0
- package/core/checks.ts +72 -0
- package/core/contacts.test.ts +31 -0
- package/core/contacts.ts +38 -0
- package/core/doc.ts +4 -0
- package/core/feature.ts +6 -0
- package/core/fixtures.test.ts +33 -0
- package/core/fixtures.ts +69 -0
- package/core/frontmatter.ts +15 -0
- package/core/html.ts +3 -0
- package/core/image.test.ts +22 -0
- package/core/image.ts +37 -0
- package/core/jev.test.ts +66 -0
- package/core/jev.ts +77 -0
- package/core/review/model.ts +189 -0
- package/core/review/policy.ts +26 -0
- package/core/review/service.test.ts +163 -0
- package/core/review/service.ts +309 -0
- package/core/review/store.ts +51 -0
- package/core/review/triage.test.ts +41 -0
- package/core/review/triage.ts +58 -0
- package/core/states.test.ts +36 -0
- package/core/states.ts +34 -0
- package/dist/core/bytes.d.ts +5 -0
- package/dist/core/bytes.js +9 -0
- package/dist/core/catalog.d.ts +17 -0
- package/dist/core/catalog.js +24 -0
- package/dist/core/checks.d.ts +41 -0
- package/dist/core/checks.js +53 -0
- package/dist/core/contacts.d.ts +9 -0
- package/dist/core/contacts.js +34 -0
- package/dist/core/doc.d.ts +11 -0
- package/dist/core/doc.js +1 -0
- package/dist/core/feature.d.ts +3 -0
- package/dist/core/feature.js +4 -0
- package/dist/core/fixtures.d.ts +19 -0
- package/dist/core/fixtures.js +52 -0
- package/dist/core/frontmatter.d.ts +4 -0
- package/dist/core/frontmatter.js +15 -0
- package/dist/core/html.d.ts +2 -0
- package/dist/core/html.js +2 -0
- package/dist/core/image.d.ts +6 -0
- package/dist/core/image.js +34 -0
- package/dist/core/jev.d.ts +54 -0
- package/dist/core/jev.js +52 -0
- package/dist/core/review/model.d.ts +85 -0
- package/dist/core/review/model.js +112 -0
- package/dist/core/review/policy.d.ts +3 -0
- package/dist/core/review/policy.js +19 -0
- package/dist/core/review/service.d.ts +79 -0
- package/dist/core/review/service.js +245 -0
- package/dist/core/review/store.d.ts +20 -0
- package/dist/core/review/store.js +37 -0
- package/dist/core/review/triage.d.ts +6 -0
- package/dist/core/review/triage.js +45 -0
- package/dist/core/states.d.ts +6 -0
- package/dist/core/states.js +29 -0
- package/dist/scripts/bootstrap.d.ts +51 -0
- package/dist/scripts/bootstrap.js +278 -0
- package/dist/scripts/calibrate.d.ts +127 -0
- package/dist/scripts/calibrate.js +164 -0
- package/dist/scripts/check.d.ts +25 -0
- package/dist/scripts/check.js +30 -0
- package/dist/scripts/cli.d.ts +2 -0
- package/dist/scripts/cli.js +73 -0
- package/dist/scripts/commands.d.ts +25 -0
- package/dist/scripts/commands.js +49 -0
- package/dist/scripts/comments.d.ts +63 -0
- package/dist/scripts/comments.js +152 -0
- package/dist/scripts/dev.d.ts +46 -0
- package/dist/scripts/dev.js +84 -0
- package/dist/scripts/elyx.d.ts +61 -0
- package/dist/scripts/elyx.js +195 -0
- package/dist/scripts/fixtures-pull.d.ts +60 -0
- package/dist/scripts/fixtures-pull.js +121 -0
- package/dist/scripts/guard.d.ts +58 -0
- package/dist/scripts/guard.js +201 -0
- package/dist/scripts/hook.d.ts +18 -0
- package/dist/scripts/hook.js +46 -0
- package/dist/scripts/lib/bin.d.ts +4 -0
- package/dist/scripts/lib/bin.js +20 -0
- package/dist/scripts/lib/catalog.d.ts +7 -0
- package/dist/scripts/lib/catalog.js +18 -0
- package/dist/scripts/lib/cli.d.ts +75 -0
- package/dist/scripts/lib/cli.js +88 -0
- package/dist/scripts/lib/css-tokens.d.ts +13 -0
- package/dist/scripts/lib/css-tokens.js +67 -0
- package/dist/scripts/lib/judge.d.ts +24 -0
- package/dist/scripts/lib/judge.js +60 -0
- package/dist/scripts/lib/ports.d.ts +35 -0
- package/dist/scripts/lib/ports.js +113 -0
- package/dist/scripts/lib/review.d.ts +7 -0
- package/dist/scripts/lib/review.js +22 -0
- package/dist/scripts/lib/rules.d.ts +12 -0
- package/dist/scripts/lib/rules.js +20 -0
- package/dist/scripts/lib/vercel.d.ts +17 -0
- package/dist/scripts/lib/vercel.js +53 -0
- package/dist/scripts/new.d.ts +47 -0
- package/dist/scripts/new.js +113 -0
- package/dist/scripts/people.d.ts +59 -0
- package/dist/scripts/people.js +73 -0
- package/dist/scripts/preflight.d.ts +35 -0
- package/dist/scripts/preflight.js +251 -0
- package/dist/scripts/release-check.d.ts +19 -0
- package/dist/scripts/release-check.js +39 -0
- package/dist/scripts/skill-hint.d.ts +32 -0
- package/dist/scripts/skill-hint.js +103 -0
- package/dist/scripts/skills.d.ts +13 -0
- package/dist/scripts/skills.js +34 -0
- package/dist/scripts/visual-match.d.ts +46 -0
- package/dist/scripts/visual-match.js +93 -0
- package/dist/server/api.d.ts +22 -0
- package/dist/server/api.js +143 -0
- package/dist/server/checks.d.ts +4 -0
- package/dist/server/checks.js +22 -0
- package/dist/server/dev.d.ts +2 -0
- package/dist/server/dev.js +54 -0
- package/dist/server/env.d.ts +8 -0
- package/dist/server/env.js +57 -0
- package/dist/server/fake-upstash.d.ts +1 -0
- package/dist/server/fake-upstash.js +37 -0
- package/dist/server/file-kv.d.ts +2 -0
- package/dist/server/file-kv.js +24 -0
- package/dist/server/local.d.ts +4 -0
- package/dist/server/local.js +25 -0
- package/dist/server/paths.d.ts +22 -0
- package/dist/server/paths.js +81 -0
- package/dist/server/session.d.ts +22 -0
- package/dist/server/session.js +110 -0
- package/dist/server/upstash.d.ts +7 -0
- package/dist/server/upstash.js +41 -0
- package/dist/vercel/index.d.ts +5 -0
- package/dist/vercel/index.js +12 -0
- package/dist/vercel/proxy.d.ts +11 -0
- package/dist/vercel/proxy.js +238 -0
- package/dist/vite/kit.d.ts +4 -0
- package/dist/vite/kit.js +35 -0
- package/dist/vite/markdown.d.ts +2 -0
- package/dist/vite/markdown.js +212 -0
- package/dist/vite/plugin.d.ts +15 -0
- package/dist/vite/plugin.js +84 -0
- package/dist/vite/registry.d.ts +7 -0
- package/dist/vite/registry.js +70 -0
- package/kit/Icon.tsx +39 -0
- package/kit/Space.tsx +25 -0
- package/kit/Variants.tsx +134 -0
- package/kit/ViewTools.tsx +34 -0
- package/kit/base.css +27 -0
- package/kit/dials.css +98 -0
- package/kit/dials.tsx +322 -0
- package/kit/fixtures.ts +3 -0
- package/kit/format.ts +13 -0
- package/kit/harness.css +99 -0
- package/kit/hooks.ts +35 -0
- package/kit/icons.ts +329 -0
- package/kit/motion.ts +18 -0
- package/kit/prefs.ts +111 -0
- package/kit/space.css +12 -0
- package/kit/ui/Button.tsx +135 -0
- package/kit/ui/Menu.tsx +386 -0
- package/kit/ui/Overlay.tsx +300 -0
- package/kit/ui/Toast.tsx +187 -0
- package/kit/ui/index.ts +13 -0
- package/kit/ui/layer.ts +51 -0
- package/kit/ui/ui.css +238 -0
- package/package.json +87 -3
- package/shell/Guide.tsx +160 -0
- package/shell/Index.tsx +301 -0
- package/shell/Notice.tsx +27 -0
- package/shell/Prototype.tsx +31 -0
- package/shell/a11y.ts +50 -0
- package/shell/doc.d.ts +7 -0
- package/shell/doc.ts +3 -0
- package/shell/guide.css +133 -0
- package/shell/index.html +17 -0
- package/shell/main.tsx +53 -0
- package/shell/public/favicon.svg +1 -0
- package/shell/registry.ts +21 -0
- package/shell/registry.virtual.d.ts +10 -0
- package/shell/review/Review.tsx +989 -0
- package/shell/review/anchor.ts +132 -0
- package/shell/review/client.ts +165 -0
- package/shell/review/mount.tsx +12 -0
- package/shell/review/review.css +296 -0
- package/shell/shell.css +160 -0
- package/shell/system.css +238 -0
- package/skills/miles-conductor/SKILL.md +37 -0
- package/skills/miles-deploy/SKILL.md +67 -0
- package/skills/miles-design-hub/SKILL.md +62 -0
- package/skills/miles-get-hostile/SKILL.md +61 -0
- package/skills/miles-new-feature/SKILL.md +40 -0
- package/skills/miles-paper-to-prototype/SKILL.md +46 -0
- package/skills/miles-port/SKILL.md +59 -0
- package/skills/miles-port/references/astro.md +35 -0
- package/skills/miles-port/references/next.md +36 -0
- package/skills/miles-prototype/SKILL.md +83 -0
- package/skills/miles-prototype/references/accessibility.md +48 -0
- package/skills/miles-prototype/references/motion.md +59 -0
- package/skills/miles-prototype/references/performance.md +45 -0
- package/skills/miles-review/SKILL.md +39 -0
- package/templates/app/README.md +41 -0
- package/templates/app/index.tsx +32 -0
- package/templates/app/styles.css +1 -0
- package/templates/conductor/settings.toml +29 -0
- package/templates/guide.md +38 -0
- package/templates/space/README.md +37 -0
- package/templates/space/index.tsx +43 -0
- package/templates/space/styles.css +13 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miles-new-feature
|
|
3
|
+
description: "Start a feature or a prototype: the folder, the README with its question and states, docs/sprints/<feature>/ with the design doc and the walkthrough. Load when the user names a new feature, page, flow or exploration, or asks for a new prototype. Triggers on: new feature, new prototype, start a prototype, scaffold, pnpm new, sprint, alternate, exploration, design doc, walkthrough, guide."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# New feature
|
|
7
|
+
|
|
8
|
+
One feature is one slug used in two places: `docs/sprints/<slug>/` and the `feature:` line of every prototype that belongs to it. This skill makes both, in an order that puts the question before the code.
|
|
9
|
+
|
|
10
|
+
## Procedure
|
|
11
|
+
|
|
12
|
+
1. **Name the slug.** Kebab-case, the feature's name in the product's words (`bulk-export`, not `feature-2`). It never changes once a prototype carries it, because the index, the walkthrough route and the port report all key on it.
|
|
13
|
+
2. **Which kind?** Walk this; the first match wins.
|
|
14
|
+
- The feature's main prototype, the one its walkthrough opens → `--sprint`. One per feature; `pnpm new` refuses a second.
|
|
15
|
+
- Another full take on the same flow, to compare against the primary → `--alternate`. Refused until the primary exists.
|
|
16
|
+
- A side question about one part (a menu's motion, a loading state) → exploration, the default.
|
|
17
|
+
3. **Where does it stand?** `--app` when the question is about the feature in context (the shell from `DESIGN.md`, Shell); nothing when it is one thing alone in the Space.
|
|
18
|
+
4. **Scaffold.** From the project root:
|
|
19
|
+
```bash
|
|
20
|
+
pnpm new <slug> --app --sprint --feature <feature> --title "Title"
|
|
21
|
+
pnpm guide <feature> # once per feature, for the sprint
|
|
22
|
+
```
|
|
23
|
+
The scaffolder fills the frontmatter and refuses a bad slug, a second sprint, or an alternate without a primary. Never create the folder by hand: a folder without the README's frontmatter is invisible to the index.
|
|
24
|
+
5. **Write the README before any code**, top to bottom:
|
|
25
|
+
- **Problem.** What is wrong or unknown today, and for whom. Two sentences.
|
|
26
|
+
- **Question.** The one decision the prototype exists to make. One sentence, a question mark. If there are two questions, there are two prototypes.
|
|
27
|
+
- **Flows** (app) or **Directions** (space). A table: each flow with where it starts and its edge cases; each direction with its name and the axis it varies on.
|
|
28
|
+
- **States.** Every state the prototype will hold and how to reach it. Start with the five fixture sets (`miles-get-hostile`); add the feature's own (selected, editing, a run in progress). The guard and `miles-port` read this table.
|
|
29
|
+
- **Fixtures.** Where `real` comes from and the design's maximum count.
|
|
30
|
+
- **Decision.** Empty.
|
|
31
|
+
6. **Design doc.** `docs/sprints/<slug>/design-doc.md` holds what the prototype tests: the context, the proposal, the open decisions. Write it when the feature has more than one flow; a single exploration needs only its README.
|
|
32
|
+
7. **Then build**, under `miles-prototype`.
|
|
33
|
+
|
|
34
|
+
## The walkthrough
|
|
35
|
+
|
|
36
|
+
`docs/sprints/<feature>/guide.md` is for the reviewer who has the password and not the repo. It is written from the States table: one section per workflow, then "Every state, and how to reach it" copied from the README, then what the prototype does not do, then what the review must settle. Screenshots go in `assets/guide/` beside it, named in order (`01-list.jpg`); a missing file fails the build. Write it before the share link goes out (`miles-deploy` checks it exists for a sprint).
|
|
37
|
+
|
|
38
|
+
## When done means decided
|
|
39
|
+
|
|
40
|
+
When the user picks: the Decision section gets what was chosen, its values, what was rejected and why; then the folder is deleted, or `status: archived` when the user wants it kept. A rule the decision made lands in `DESIGN.md` as a named rule the same day.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miles-paper-to-prototype
|
|
3
|
+
description: "Turn a Paper Desktop comp into prototype conventions: tokens into kit/tokens.css, the shell into kit/shell.ts, components onto @kit/ui, content into fixtures. Load on every Paper MCP read, not only the first. Triggers on: Paper, Paper Desktop, paper.design, comp, frame, artboard, get_jsx, get_computed_styles, get_tokens, from the design, match the comp, the design says."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Paper to prototype
|
|
7
|
+
|
|
8
|
+
Paper is the source of the design; the prototype is built from its values, never from its code. This skill loads on every Paper read, because the drift it prevents compounds: a size read off a screenshot is a few pixels out, and three components later the page is a different design.
|
|
9
|
+
|
|
10
|
+
## Hard rules
|
|
11
|
+
|
|
12
|
+
1. **Values come from the tools, never from a screenshot.** Sizes, spacing, colours and type come from `get_computed_styles`, `get_tokens` and `get_font_family_info`; structure from `get_jsx` and `get_tree_summary`. `get_screenshot` is for seeing the whole, never for measuring. Why: screenshot values drift by a few pixels and the drift compounds across components.
|
|
13
|
+
2. **Never paste Paper JSX.** It is a reference for structure and values. The prototype is written in `@kit/ui` and the feature's own CSS on `kit/tokens.css` variables. Why: pasted JSX carries inline values and Paper's class names, which is a second design system beside the kit's.
|
|
14
|
+
3. **Tokens land once.** A value the design uses in two places becomes a token in `kit/tokens.css` under the kit's existing name, and `DESIGN.md`'s frontmatter gets the same value. A value used once stays a raw value in the feature's CSS, named in the README.
|
|
15
|
+
4. **The comp decides, the kit draws.** Where the comp and `@kit/ui` disagree on a control, the comp wins inside the feature folder (fork the control there); the kit changes only when the shell or a second feature needs it too.
|
|
16
|
+
|
|
17
|
+
## Procedure
|
|
18
|
+
|
|
19
|
+
1. **Open the file.** `get_basic_info` for the artboards and their sizes; `get_selection` when the user is pointing at something. Omit `fileId` to read the file the user has open.
|
|
20
|
+
2. **Fonts first.** `get_font_family_info` before any type is styled. Write the family to `--font` in `tokens.css`; add the `@font-face` there if the design names a file. Do this once per project.
|
|
21
|
+
3. **Tokens.** `get_tokens` for the project's palette, spacing and radii. Map each onto the kit's names (the table below). Write `tokens.css` light and dark, and `DESIGN.md` frontmatter, in one change. A design with no dark values gets the kit's dark derived by the same steps (neutral surfaces step down, brand lifts one step). When the repo has `design-system/` (the Elyx add-on), run `pnpm elyx` in the same change and never edit that folder: it is generated from `tokens.css`, and an edit there is lost on the next run.
|
|
22
|
+
4. **The shell.** Read `DESIGN.md`, Shell. If it names frames, `get_computed_styles` on each and fill `kit/shell.ts` (brand, nav, footer) and the shell CSS in `kit/`. If it is empty, prototypes stand in the Space and the shell is not built.
|
|
23
|
+
5. **The feature frame.** `get_tree_summary` for the structure, `get_computed_styles` on each component node for its values. Build top down in `@kit/ui` and the feature's CSS. Content on the frame goes to `fixtures.json` `real`, word for word (`miles-get-hostile`).
|
|
24
|
+
6. **Compare.** `get_screenshot` of the frame beside the prototype at the same width. Fix what differs by measuring the node again, never by eye.
|
|
25
|
+
7. **Visual match, only with TypeSafe on.** Export the frame as PNG and screenshot the prototype in the same state, both between 280 and 1568 px a side, then `pnpm visual-match <slug> --comp <png> --shot <png>`. It answers three questions: an element missing, an element added, a changed order. Treat a finding as a place to look, then measure the node; never act on it for a size or a colour. Why: Jev reads images in an experimental mode and is weak on numbers, so it can point at a difference but never measure one.
|
|
26
|
+
|
|
27
|
+
## Token map
|
|
28
|
+
|
|
29
|
+
Kit names stay; values change. The common Paper names and where they land:
|
|
30
|
+
|
|
31
|
+
| In the design | In the kit |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| primary, accent, brand | `--brand-*` and the `--bg-brand-*`, `--button-primary-*`, `--border-brand`, `--fg-brand-*` tokens |
|
|
34
|
+
| text, foreground, ink | `--text-primary`, `--text-secondary`, `--text-tertiary`, `--text-quaternary` |
|
|
35
|
+
| background, surface, canvas | `--bg-primary`, `--bg-secondary`, `--bg-tertiary` |
|
|
36
|
+
| border, stroke, divider | `--border-primary`, `--border-secondary` |
|
|
37
|
+
| radius | `--radius-xs` to `--radius-2xl` |
|
|
38
|
+
| shadow, elevation | `--shadow-xs` to `--shadow-xl` |
|
|
39
|
+
| font family, weight, size | `--font`, and the hierarchy in `DESIGN.md`, Typography |
|
|
40
|
+
| easing, duration | `--ease-*`, `--dur-*`; a curve the design names and the kit lacks is added once with a comment |
|
|
41
|
+
|
|
42
|
+
A design value with no kit name is added to `tokens.css` under a kit-shaped name (`--bg-brand-subtle`), noted in `DESIGN.md`, and never written raw in two places.
|
|
43
|
+
|
|
44
|
+
## Done
|
|
45
|
+
|
|
46
|
+
The feature's values are in `tokens.css` and `DESIGN.md` and nowhere else; the shell, when there is one, is in `kit/shell.ts`; the prototype's CSS uses only `var(--…)` and the raw values its README names; `pnpm check` passes.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miles-port
|
|
3
|
+
description: "Turn an approved prototype into production: in Webflow (variables, CMS, conditional visibility, interactions) or in another codebase (its tokens, components, tests or stories). The prototype is a spec, never code to paste. Load when the user asks to build, ship, port, hand off or implement an approved prototype. Triggers on: port, hand off, handoff, implement, build it for real, into Webflow, into the codebase, production, engineers, state map, port report."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Port
|
|
7
|
+
|
|
8
|
+
An approved prototype is a spec. It is never pasted: pasted prototype code carries the kit, DialKit and the fidelity-ceiling shortcuts into production, which is the rebuild this hub exists to prevent. The port reads the feature folder, writes a state map, builds in the target's own materials, and writes a report the user reads before anything goes live.
|
|
9
|
+
|
|
10
|
+
## Input: the feature folder is complete, or the port does not start
|
|
11
|
+
|
|
12
|
+
Read `prototypes/<slug>/`, never the running prototype. Refuse to start, naming what is missing, unless all five hold:
|
|
13
|
+
|
|
14
|
+
1. The README's `## States` table lists every state (a dial option, a route, a click) the prototype holds.
|
|
15
|
+
2. Final dial values are committed as defaults in the code (`useDials` and `DialVars` configs) and in the README's Decision. A live dial value never written down does not exist to the port.
|
|
16
|
+
3. `fixtures.json` holds `real` and `missing`, and the component's `max` is in the code, so `@kit/fixtures` can derive the rest.
|
|
17
|
+
4. The CSS references tokens by name. A raw value is allowed only where the README names it.
|
|
18
|
+
5. No open `bug` comment on the feature. Run `pnpm comments pull` and read `docs/sprints/<feature>/comments.md`; an open bug is fixed first (`miles-review`), or the user says in so many words that it ships as is, and the report lists it. Why: a bug a reviewer already found and nobody fixed is the cheapest bug the port can stop.
|
|
19
|
+
|
|
20
|
+
## Branch: where is it going?
|
|
21
|
+
|
|
22
|
+
Ask one question first: Webflow, or a codebase (the path to its repo)? Then walk one branch. Both follow three rules:
|
|
23
|
+
|
|
24
|
+
- **The target's conventions win.** Where a token or component has no match in the target, list it in the port report. Never invent one in the target.
|
|
25
|
+
- **Dials become fixed values.** DialKit never ships. Each dial's committed default is the value; the README's Decision is the source.
|
|
26
|
+
- **The state map comes before any build.** One row per README state: the state, the mechanism that carries it in the target, and where. A state with no mechanism is a bug found before build instead of after launch; it goes in the report as unmapped and the user decides.
|
|
27
|
+
|
|
28
|
+
### Webflow
|
|
29
|
+
|
|
30
|
+
1. **Tools.** Call `webflow_guide_tool` once. Check whether the Designer tools are connected (`designer_tool`, `data_variable_tool`, `data_style_tool`, `data_element_tool`). If they are, layout, classes and variables are built over MCP; if not, step 4 writes the build spec for a person in the Designer.
|
|
31
|
+
2. **Tokens → variables.** Each token the feature uses becomes a Webflow variable with the same name (`bg-primary`, `radius-md`). Existing variables with the same meaning are reused, named in the report.
|
|
32
|
+
3. **States → mechanisms.** Walk each README state:
|
|
33
|
+
- empty, missing → conditional visibility on a CMS field
|
|
34
|
+
- a visual variant (compact, selected) → a combo class
|
|
35
|
+
- motion, open and close → an interaction, with the committed durations and curves
|
|
36
|
+
- anything else → unmapped, in the report
|
|
37
|
+
4. **CMS.** Collection fields from the shape of `real` (one field per rendered key, typed from the value). Then the `real` items as drafts through the CMS tool's create-item action. Never the `_live` variants, never a site publish: publishing a client site is the user's call. Hostile sets stay in the prototype; draft test items in a client CMS get published by accident.
|
|
38
|
+
5. **Layout, classes, interactions.** Over MCP when the Designer tools are connected, otherwise from the build spec in the report, by a person.
|
|
39
|
+
|
|
40
|
+
### Codebase
|
|
41
|
+
|
|
42
|
+
1. **Read the target first.** Framework, styling, component library, tokens, and its `AGENTS.md` or `CLAUDE.md`. Its rules bind the port.
|
|
43
|
+
2. **Map tokens and components by name** onto the target's. Unmatched ones go in the report, never into the target as new ones.
|
|
44
|
+
3. **Fixtures travel.** Each fixture set becomes a test fixture or a story in the target's own convention, and each README state a test or a story. Why: the hostile sets are where QA catches the edge cases, so they go with the feature.
|
|
45
|
+
4. **A new branch**, never the target's main. Name it after the feature slug.
|
|
46
|
+
|
|
47
|
+
Read the reference for the target's stack before step 2, when there is one: `references/next.md` for Next.js, `references/astro.md` for Astro. The target's own `AGENTS.md` still wins over both. For any other stack, the target's docs are the reference.
|
|
48
|
+
|
|
49
|
+
## Output: the port report
|
|
50
|
+
|
|
51
|
+
`docs/sprints/<feature>/port.md`, written before the user is told the port is done:
|
|
52
|
+
|
|
53
|
+
- **State map.** The table from the branch step, every README state, with its mechanism or `unmapped`.
|
|
54
|
+
- **Mapped.** Tokens and components with their target names.
|
|
55
|
+
- **Not mapped.** Tokens with no target, states with no mechanism, and what the user has to decide for each.
|
|
56
|
+
- **Webflow build spec** (Webflow branch): the structure, classes, variables and interactions a person builds in the Designer, in build order.
|
|
57
|
+
- **Where it is.** The branch, the collection, the draft items.
|
|
58
|
+
|
|
59
|
+
The report is the review surface. Nothing goes live until the user has read it.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Porting into Astro
|
|
2
|
+
|
|
3
|
+
Read the target first; where it already decides something below, it wins. This file decides only what the target leaves open.
|
|
4
|
+
|
|
5
|
+
## Where each piece goes
|
|
6
|
+
|
|
7
|
+
| Prototype | Astro |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `kit/tokens.css` values | The target's tokens: CSS variables in its global stylesheet, or its Tailwind theme, under the target's names. Never a second token file. |
|
|
10
|
+
| A hash route (`#/items/42`) | A page: `src/pages/items/[id].astro` with `getStaticPaths`, or an on-demand route if the target renders on request. |
|
|
11
|
+
| `fixtures.json` `real` | A content collection (`src/content.config.ts`) when the content is the site's own; the CMS loader when it comes from a CMS. The fixture shape is the collection's schema. |
|
|
12
|
+
| The hostile sets | Test fixtures for the component tests, plus one collection entry per hostile case in a draft or test collection that never builds into production. |
|
|
13
|
+
| A dial's committed default | A prop default or a constant. |
|
|
14
|
+
| `notInPrototype()` and scope toasts | Deleted. Each one is a feature the port report lists as not built. |
|
|
15
|
+
|
|
16
|
+
## Astro component or island
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Does the state change after the page loads (a click, a toggle, an animation on interaction)?
|
|
20
|
+
├── No → an .astro component. Every static state (empty, missing, longest) renders on the server.
|
|
21
|
+
└── Yes → an island in the target's UI framework, hydrated with the lightest directive that works:
|
|
22
|
+
client:visible below the fold, client:idle above it, client:load only for what the first interaction needs.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Why the lightest directive: each island ships its framework and code to the browser, and the prototype hydrated everything because it was one React app.
|
|
26
|
+
|
|
27
|
+
## States
|
|
28
|
+
|
|
29
|
+
- **Empty and missing** branch in the `.astro` template on the collection's data; the README's copy for each state ships as written.
|
|
30
|
+
- **Motion** keeps the README's committed durations and curves as CSS. Page-to-page motion is the target's View Transitions setup when it has one; never add it for one feature.
|
|
31
|
+
- **Errors and loading** exist only for on-demand routes and islands that fetch. Each one is new and listed in the report.
|
|
32
|
+
|
|
33
|
+
## Tests
|
|
34
|
+
|
|
35
|
+
Each README state becomes one test with its fixture set, in the target's runner (Vitest with the Astro container API for `.astro` components, the framework's own for islands). Never add a runner the target does not have; list the gap in the report.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Porting into Next.js (App Router)
|
|
2
|
+
|
|
3
|
+
Read the target first; where it already decides something below, it wins. This file decides only what the target leaves open.
|
|
4
|
+
|
|
5
|
+
## Where each piece goes
|
|
6
|
+
|
|
7
|
+
| Prototype | Next.js |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `kit/tokens.css` values | The target's tokens. CSS variables in `app/globals.css`, or `@theme` in Tailwind v4, under the target's names. Never a second token file. |
|
|
10
|
+
| A hash route (`#/items/42`) | A segment: `app/items/[id]/page.tsx`. A hash route was the prototype's shortcut, never the product's URL. |
|
|
11
|
+
| `fixtures.json` `real` | The target's data layer: a fetch in a Server Component, a CMS query, or a route handler. The fixture shape is the props contract the data must meet. |
|
|
12
|
+
| The hostile sets | Test fixtures beside the component: `<Component>.fixtures.ts`, used by its tests and stories. |
|
|
13
|
+
| A dial's committed default | A constant or a prop default. Never `useState` for a value the README decided. |
|
|
14
|
+
| `useStoredState` | Only if the README says the preference persists in the product; then the target's own persistence. Otherwise drop it. |
|
|
15
|
+
| `notInPrototype()` and scope toasts | Deleted. Each one is a feature the port report lists as not built. |
|
|
16
|
+
|
|
17
|
+
## Server or client
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Does the component hold state, handle an event, or animate on interaction?
|
|
21
|
+
├── No → Server Component (the default). It renders the README's static states from props.
|
|
22
|
+
└── Yes → the smallest leaf that needs it gets 'use client'; its parent stays on the server.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Why the smallest leaf: `'use client'` pulls everything the file imports into the browser bundle, and the prototype's fidelity shortcuts make that bundle large.
|
|
26
|
+
|
|
27
|
+
## States
|
|
28
|
+
|
|
29
|
+
- **Empty and missing** come from the data: the Server Component branches on an empty array or a null field. The README's copy for each state is the copy that ships.
|
|
30
|
+
- **Loading** is `loading.tsx` or a `<Suspense>` boundary with a skeleton the same size as the loaded row, so the layout never moves. A prototype that had no loading state gets one here, and the report says so.
|
|
31
|
+
- **Errors** are `error.tsx` at the segment. The prototype never had error handling (it had no backend), so each error state is new and listed in the report.
|
|
32
|
+
- **Motion** keeps the README's committed durations and curves as CSS. `motion` only where the prototype used it, imported in the client leaf.
|
|
33
|
+
|
|
34
|
+
## Tests and stories
|
|
35
|
+
|
|
36
|
+
Each README state becomes one test or one story with its fixture set: `real`, `longest`, `empty`, `max`, `missing`, plus the feature's own. Use the target's runner (Vitest, Jest, Playwright) and Storybook if it has one. Never add a runner the target does not have; list the gap in the report.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miles-prototype
|
|
3
|
+
description: "The rules for writing or changing any prototype code under prototypes/: low gravity, the fidelity ceiling, DialKit patterns, the scope toast, and the binding motion, accessibility and performance rules in references/. Load before the first line of prototype code in a session and again whenever a change touches kit/. Triggers on: prototype, prototyping, build the state, add a dial, DialKit, variant, fixture set, fidelity, too much code, kit, AppShell, SiteShell, Space, motion, animation, accessibility, performance."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prototype
|
|
7
|
+
|
|
8
|
+
A prototype here answers one written question with the least code that can answer it, in the project's real materials, with every state reachable from a dial. This skill holds the ceiling. `AGENTS.md` holds the stack and the layout; `prototypes/sample-list/` is the worked example and wins over any description of it here.
|
|
9
|
+
|
|
10
|
+
## Before the first line
|
|
11
|
+
|
|
12
|
+
1. Read the feature's `README.md`. If it has no Problem, Question and States, stop and write them with `miles-new-feature`. Code written before the question is code that answers a different question.
|
|
13
|
+
2. Decide where it stands: `AppShell` or `SiteShell` when the question is about the feature in context, `Space` when it is about one thing alone. Never both in one prototype.
|
|
14
|
+
3. Read `prototypes/sample-list/index.tsx` once per session. It is the reference for every pattern below.
|
|
15
|
+
|
|
16
|
+
## The fidelity ceiling
|
|
17
|
+
|
|
18
|
+
The guard (the `design-hub hook fidelity-guard` hook in `.claude/settings.json`) names these rules when a write goes over them; the headings are the names it uses. It speaks at two levels, with thresholds in `.design-hub/checks.json`:
|
|
19
|
+
|
|
20
|
+
- **A warning** arrives as context. Fix it, or say in one sentence why this write earns it.
|
|
21
|
+
- **A block** stops the turn until you answer it: an import that leaves the feature folder, or a Jev judgment over `fidelity.blockAt`. Undo the write, or tell the user why it must stay and wait for their answer. Never write around a block.
|
|
22
|
+
|
|
23
|
+
A finding with a log id (`log 3f9a…`) was Jev's. When the user says it was wrong, run `pnpm calibrate --no <id>`; when right, `--yes <id>`. Why: the thresholds move only with labels, so an unlabelled false alarm keeps firing.
|
|
24
|
+
|
|
25
|
+
### Low gravity
|
|
26
|
+
|
|
27
|
+
A prototype imports only `@kit/*` and files in its own folder. Never another prototype, never the hub's own code in `node_modules/@milesroxas/design-hub`. Why: the moment two prototypes share a file, deleting one breaks the other, and a prototype that cannot be deleted is production.
|
|
28
|
+
|
|
29
|
+
### Fork, don't flex
|
|
30
|
+
|
|
31
|
+
A kit piece that needs to behave differently is copied into the feature folder and changed there. Never add a prop, a variant or a branch to `kit/` for one prototype. Promote on second use: when a second prototype copies the same thing, move it into `kit/` and point both at it, in one change that says so. A kit piece that lives in the package (`@kit/ui`, `@kit/dials`) is forked the same way: copy the file into `kit/` under the same name and every import takes the copy.
|
|
32
|
+
|
|
33
|
+
### No new dependency without the user
|
|
34
|
+
|
|
35
|
+
Adding a package needs the user's OK first, with the reason and the gzipped cost. Why: every prototype pays for every kit import, and a library brought in for one state stays for all of them.
|
|
36
|
+
|
|
37
|
+
### Fixtures, never a backend
|
|
38
|
+
|
|
39
|
+
Data is `fixtures.json` in the feature folder, bundled at build (`miles-get-hostile`). Never `fetch`, never a server module, never IndexedDB, never `localStorage` as a store (`useStoredState` is for a prototype preference, such as which fold is open). Latency, when it is part of the question, is a timer on a dial. Why: a prototype with a backend has a second thing that can be wrong, and a reviewer cannot tell which.
|
|
40
|
+
|
|
41
|
+
### No abstraction for later
|
|
42
|
+
|
|
43
|
+
No custom hook, context, generic component or utility module used in one place. Inline it. Why: an abstraction is a bet on a second use, and the second use is the one that moves it into `kit/`, not the first.
|
|
44
|
+
|
|
45
|
+
### No handling a fixture cannot reach
|
|
46
|
+
|
|
47
|
+
No error state, loading state or validation for a case none of `real`, `longest`, `empty`, `max`, `missing` exercises. If the case matters, add it to `fixtures.json` and the README's States first; then build the handling and put it on the dial. Why: handling nobody can reach is code nobody reviews.
|
|
48
|
+
|
|
49
|
+
### Every state is in the README
|
|
50
|
+
|
|
51
|
+
A user-visible state the README's `## States` table does not list does not exist. Add the row before the code, with how to reach it (a dial, a route, a click). Why: `miles-port` builds from that table, and a state missing there is a bug found after launch instead of before.
|
|
52
|
+
|
|
53
|
+
### Scope, said in the prototype's voice
|
|
54
|
+
|
|
55
|
+
Anything a control would do that the prototype does not build says so: `notInPrototype('Model history')` or `toast(message, { kind: 'scope' })`. Never a product toast with a green check for something that did not happen. Why: a reviewer who sees a success they did not get stops trusting the ones they did.
|
|
56
|
+
|
|
57
|
+
## DialKit patterns
|
|
58
|
+
|
|
59
|
+
Walk this for every number and every switch:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
Is it a value CSS consumes (duration, distance, scale, colour)?
|
|
63
|
+
├── Yes → <DialVars name config target={ref}> and read it as var(--key) with calc(var(--dur) * 1ms)
|
|
64
|
+
│ Why: a slider tick re-renders nothing; React state per tick stutters at 60fps.
|
|
65
|
+
└── No
|
|
66
|
+
├── Does JS branch on it (which fixture set, which role, a count)?
|
|
67
|
+
│ └── Yes → useDials(name, config, { hints }); one panel per group a reviewer reaches for
|
|
68
|
+
└── Does code also set it (a reset, every toggle at once)?
|
|
69
|
+
└── Yes → useDialsController
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- Every dial carries a hint: one sentence, the unit named. A reviewer never guesses what a slider does.
|
|
73
|
+
- Panels register in the order a reviewer reaches for them: Review (fixtures, role, scenarios) first, then the feature's own numbers, then Motion. View is mounted by the shell.
|
|
74
|
+
- Slider defaults are the comp's values. The decision is the number the user settles on; copy it into the README's Decision with the rejected range.
|
|
75
|
+
- Never a control inside the product chrome. A sidebar item or a sheet that switches fixtures is a testing aid wearing the product's clothes.
|
|
76
|
+
|
|
77
|
+
## Motion, accessibility, performance
|
|
78
|
+
|
|
79
|
+
The references beside this skill (`references/motion.md`, `references/accessibility.md`, `references/performance.md`) are binding; the short rules: transform and opacity only, ease-out in and ease-in out, nothing over 300ms, nothing animates on a keyboard action, reduced motion keeps the fades, every control has a name, 40px targets, no state per frame.
|
|
80
|
+
|
|
81
|
+
## Done
|
|
82
|
+
|
|
83
|
+
Before reporting any change: `pnpm check` passes, the page loads with no console errors and no `[a11y]` lines, the main path works by keyboard, `?theme=dark` and `?motion=reduce` look right, and the README's States and Decision are current. Say what you could not verify.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
The floor for every prototype, WCAG 2.2 AA. A prototype that only works with a mouse answers the wrong question.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- Native elements first: `<button>` for actions, `<a href>` for navigation, `<label>` for every input, `<fieldset>`/`<legend>` for groups, `<table>` for tabular data, `<dialog>` or the kit dialog for modals.
|
|
8
|
+
- One `<main>` and one `<h1>` per page; headings in order. `Space` and `AppShell` already provide the landmarks.
|
|
9
|
+
- Every control has an accessible name. Icon-only buttons take `aria-label`; `<Icon label="…">` only when the icon itself carries meaning.
|
|
10
|
+
- `aria-*` only where HTML has no equivalent, and then follow the [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/) pattern exactly (menu button, listbox, dialog, tabs, combobox).
|
|
11
|
+
|
|
12
|
+
## Keyboard
|
|
13
|
+
|
|
14
|
+
- Everything a pointer can do, the keyboard can do, in a sensible tab order. No positive `tabindex`.
|
|
15
|
+
- Focus is always visible: the kit's focus ring comes from `--focus-ring`; never remove it without a replacement.
|
|
16
|
+
- Esc closes the topmost menu, popover, sheet or dialog, and focus returns to what opened it.
|
|
17
|
+
- Dialogs and sheets trap focus while open and make the page behind inert.
|
|
18
|
+
- Menus: arrows move, Home/End jump, typeahead finds, Enter/Space choose, Tab leaves.
|
|
19
|
+
- A keyboard open never animates (see `motion.md`).
|
|
20
|
+
|
|
21
|
+
## Perception
|
|
22
|
+
|
|
23
|
+
- Text and icons meet AA contrast against their background in light and dark. Tokens do this; one-off colors must be checked.
|
|
24
|
+
- Color is never the only signal: pair it with text, an icon or a pattern.
|
|
25
|
+
- Status that changes without a page load (a run finishing, a toast, a save) goes through a live region: `role="status"` for polite, `role="alert"` only for errors that need action.
|
|
26
|
+
- Respect `[data-motion="reduce"]` (see `motion.md`).
|
|
27
|
+
- Works at 200% zoom and at 320px wide without horizontal scrolling of the page.
|
|
28
|
+
- `forced-colors` keeps working: focus rings use a transparent outline under the shadow (kit base does this), borders stay borders.
|
|
29
|
+
|
|
30
|
+
## Pointer and touch
|
|
31
|
+
|
|
32
|
+
- Targets at least 24×24px, 40px or more for primary touch targets. Extend small hit areas with padding or a pseudo-element.
|
|
33
|
+
- Hover-only affordances need a focus and touch equivalent.
|
|
34
|
+
- Inputs use a 16px font on touch so iOS does not zoom.
|
|
35
|
+
|
|
36
|
+
## Forms
|
|
37
|
+
|
|
38
|
+
- Labels are visible; placeholders are examples, not labels.
|
|
39
|
+
- Errors sit next to the field, name the fix, and link with `aria-describedby`; `aria-invalid` on the field.
|
|
40
|
+
- Disabled actions are rare; prefer an enabled action that explains what is missing. If something is absent for a role (a Viewer), hide it, do not disable it.
|
|
41
|
+
|
|
42
|
+
## How to check
|
|
43
|
+
|
|
44
|
+
1. Watch the console: the shell runs axe in dev and logs `[a11y]` warnings with the failing selectors. Zero is the bar.
|
|
45
|
+
2. `pnpm check`: Biome's a11y rules catch missing labels, wrong roles and click handlers without keys.
|
|
46
|
+
3. Unplug the mouse: tab through the main path, open and close every popup, confirm focus lands somewhere sensible.
|
|
47
|
+
4. Load with `?motion=reduce` and `?theme=dark`.
|
|
48
|
+
5. For a new interaction pattern, a VoiceOver pass (⌘F5) on the main path.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Motion
|
|
2
|
+
|
|
3
|
+
Binding for every prototype. Curves and durations live in `kit/tokens.css` (`--ease-*`, `--dur-*`) and, for JS, `@kit/motion`.
|
|
4
|
+
|
|
5
|
+
## Decide before you animate
|
|
6
|
+
|
|
7
|
+
1. **How often is it seen?** A hundred times a day (keyboard shortcuts, command palette, list navigation): no animation. Tens of times (hover, tabs): barely any. Occasional (menus, dialogs, toasts, sheets): standard. Rare (first run, a celebration): room for delight.
|
|
8
|
+
2. **What is it for?** Spatial consistency, a state change, feedback, or softening a jump. "It looks nice" is not a purpose for something seen often.
|
|
9
|
+
3. **Which curve?** Entering or leaving: ease-out. Moving on screen: ease-in-out. Color and fill: `--ease-hover`. Constant motion: linear. Never `ease-in` on UI.
|
|
10
|
+
4. **How long?** Press 100-160ms. Tooltip 125-200ms. Menu, select 150-250ms. Dialog, drawer 200-320ms. UI stays under 300ms; exits are faster than entrances.
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
13
|
+
|
|
14
|
+
- Animate `transform` and `opacity`. Anything else (height, width, top, padding, filter on large areas) needs a reason written in the README.
|
|
15
|
+
- Name exact properties: `transition: transform 180ms var(--ease-out)`, never `transition: all`.
|
|
16
|
+
- Keyboard-initiated opens, closes and arrow moves are instant. Pointer-initiated ones may animate.
|
|
17
|
+
- Press feedback on anything pressable: `:active { transform: scale(0.97) }`, 0.95 to 0.98.
|
|
18
|
+
- Never enter from `scale(0)`. Start at 0.95 or higher with `opacity: 0`.
|
|
19
|
+
- Popovers and menus grow from their trigger (`transform-origin` at the trigger edge). Modals scale from center.
|
|
20
|
+
- Hover motion only under `@media (hover: hover) and (pointer: fine)`.
|
|
21
|
+
- Stagger lists by 30-80ms, never block input while it plays.
|
|
22
|
+
- Blur under 20px, and only to bridge a crossfade.
|
|
23
|
+
|
|
24
|
+
## CSS first
|
|
25
|
+
|
|
26
|
+
| Situation | Use |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| State that can flip mid-animation (open/close, toasts, toggles) | `transition`: it retargets from where it is |
|
|
29
|
+
| Element entering the DOM | `@starting-style` with a transition |
|
|
30
|
+
| Element leaving the DOM | keep it mounted with a closing state until `transitionend`, or `motion`'s `AnimatePresence` |
|
|
31
|
+
| Loops, spinners, one-shot sequences nobody interrupts | `@keyframes` |
|
|
32
|
+
| Values computed at runtime (measured heights, pointer positions) | WAAPI `element.animate()` |
|
|
33
|
+
| Crossfading a whole view | View Transitions (`document.startViewTransition`) with a fallback |
|
|
34
|
+
|
|
35
|
+
## When `motion` earns its place
|
|
36
|
+
|
|
37
|
+
Reach for `motion/react` only for: springs that must keep velocity when interrupted, drag and gestures with momentum, layout animations (FLIP) between changing positions, exit animations in a list (`AnimatePresence`), shared-element transitions. Then:
|
|
38
|
+
|
|
39
|
+
- Import from `motion/react`. Use `ease.*` and `spring.*` from `@kit/motion` so JS matches the CSS tokens.
|
|
40
|
+
- Animate a `transform` string (`animate={{ transform: 'translateY(8px)' }}`) when the main thread may be busy; the `x`/`y` shorthands run on the main thread.
|
|
41
|
+
- Springs: `{ type: 'spring', duration: 0.3, bounce: 0 }` by default; bounce 0.1-0.3 only for drag release and playful moments.
|
|
42
|
+
- Drive per-frame values with motion values, not React state.
|
|
43
|
+
|
|
44
|
+
## Reduced motion
|
|
45
|
+
|
|
46
|
+
`[data-motion="reduce"]` on `<html>` is the one switch. The shell sets it from the OS setting, `?motion=reduce`, or the Space dial. Write reduced-motion CSS against it, not the media query:
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
.menu { transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }
|
|
50
|
+
:root[data-motion="reduce"] .menu { transition: opacity 120ms linear; transform: none; }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In JS, `useReducedMotion()` from `@kit/prefs`. Keep fades and color changes, drop movement and scale.
|
|
54
|
+
|
|
55
|
+
## Tuning and review
|
|
56
|
+
|
|
57
|
+
- Every number you are unsure of goes on a dial (`DialVars` for CSS, `useDials` for JS). The decision is the number the user settles on; copy it from DialKit into the README.
|
|
58
|
+
- Watch at 0.25x: the Space speed dial, or `?lapse` for frame-by-frame takes, scrubbing and a jank report.
|
|
59
|
+
- Test the interruption: open, then close mid-open. It should reverse from where it is.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
A prototype that stutters gets judged on the stutter. The floor below keeps the question about the design.
|
|
4
|
+
|
|
5
|
+
## Already handled by the shell
|
|
6
|
+
|
|
7
|
+
- Each prototype is its own lazy chunk; the index loads none of them.
|
|
8
|
+
- Dev-only tools (axe, Lapse) never ship in a build and load only when used.
|
|
9
|
+
- `motion` and DialKit load only in prototypes that import them.
|
|
10
|
+
|
|
11
|
+
## Rendering
|
|
12
|
+
|
|
13
|
+
- Animate `transform` and `opacity`; they skip layout and paint. See `motion.md`.
|
|
14
|
+
- CSS animations and transitions run off the main thread; prefer them to JS-driven motion.
|
|
15
|
+
- No React state per animation frame or per slider tick. Use `DialVars` (CSS variables), motion values, or a ref.
|
|
16
|
+
- Setting a CSS variable on a parent restyles every child. For per-frame values on one element, set `transform` on that element.
|
|
17
|
+
- Read layout, then write it: never interleave `getBoundingClientRect()` or `offsetWidth` with style writes in a loop.
|
|
18
|
+
- `will-change` only on elements that are about to move, and remove it after.
|
|
19
|
+
- Keep `backdrop-filter` and `filter: blur()` to small areas; both are expensive, worst in Safari.
|
|
20
|
+
|
|
21
|
+
## React
|
|
22
|
+
|
|
23
|
+
- State lives where it is used. Lift it only as far as needed.
|
|
24
|
+
- A module store read through `useSyncExternalStore` with a narrow selector beats context for values that change often; context re-renders every consumer.
|
|
25
|
+
- `useDeferredValue` for filtering or searching over big lists.
|
|
26
|
+
- Don't add `useMemo` and `useCallback` by reflex; add them when a profile shows the cost.
|
|
27
|
+
- Stable `key`s from data, never array indexes for lists that reorder.
|
|
28
|
+
|
|
29
|
+
## Content
|
|
30
|
+
|
|
31
|
+
- More than ~200 rows: `content-visibility: auto` with `contain-intrinsic-size`, or window the list.
|
|
32
|
+
- Images and media carry `width` and `height` (no layout shift), `loading="lazy"` below the fold, `decoding="async"`.
|
|
33
|
+
- Fonts come from tokens (`--font`, `--font-voice`). Add a web font only in `tokens.css`, once, as the design names it.
|
|
34
|
+
- Data is local: seed files in the prototype folder. Simulate latency with timers when latency is part of the question.
|
|
35
|
+
|
|
36
|
+
## Dependencies
|
|
37
|
+
|
|
38
|
+
- Each new dependency needs the user's OK (see `AGENTS.md`). Say what it costs gzipped and why CSS or the kit can't do it.
|
|
39
|
+
- Nothing heavy goes in `kit/`: every prototype that imports the kit pays for it.
|
|
40
|
+
|
|
41
|
+
## How to check
|
|
42
|
+
|
|
43
|
+
1. Chrome Performance panel with 4x CPU throttling on the main interaction: no long tasks over 50ms during an animation.
|
|
44
|
+
2. `?lapse`, record a take of the interaction: its report lists dropped frames, layout shifts and which properties animated on layout or paint.
|
|
45
|
+
3. React DevTools Profiler, "Highlight updates": a slider or a tick should light up only what it changes.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miles-review
|
|
3
|
+
description: "Work from review comments on the deployed prototypes: pull them into the repo, sort them by state and kind, act on each, resolve them, and manage who may comment. Load when the user mentions comments, feedback, review notes, a reviewer, an invite link, or asks what the client said. Triggers on: comments, feedback, review notes, client notes, what did they say, reviewer, invite, people, revoke, resolve, triage, pnpm comments, pnpm people."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Review
|
|
7
|
+
|
|
8
|
+
Each comment is pinned to a prototype, its state (the URL and every dial value) and, usually, an element. The repo gets them as `docs/sprints/<feature>/comments.md`, grouped by README state, then by kind. That file is generated: read it, never edit it, and close comments in the store.
|
|
9
|
+
|
|
10
|
+
## Working from comments
|
|
11
|
+
|
|
12
|
+
1. **Pull**, from the project root: `pnpm comments pull`. The JSON's `store` says which store it read: `deployed` when `.env` holds the store's credentials, `local` otherwise. Say which to the user, because local comments are only yours.
|
|
13
|
+
2. **Sort what is unsorted.** With TypeSafe on, `pnpm comments triage` first. Without it, sort by reading, and set the kind and state in the panel so the next pull agrees.
|
|
14
|
+
3. **Walk each open thread.** The first match wins:
|
|
15
|
+
```
|
|
16
|
+
Does its state match no row of the README's States table?
|
|
17
|
+
└── Yes → the table is missing a state. Add the row (miles-prototype) before acting on the comment.
|
|
18
|
+
kind?
|
|
19
|
+
├── bug → reproduce it in that state: the line's URL, or Show state in the panel, which restores the dials.
|
|
20
|
+
│ Fix it under miles-prototype. Resolve after the fix is deployed.
|
|
21
|
+
├── copy → change fixtures.json or the copy. Resolve after it is deployed.
|
|
22
|
+
└── taste → change nothing. List it for the user with what each option costs.
|
|
23
|
+
Build an --alternate only when the user chooses to explore it.
|
|
24
|
+
```
|
|
25
|
+
Why taste waits: one reviewer's preference, built at once, overwrites a decision the team made and the next reviewer argues the other way. The user decides taste; bugs and copy are not taste.
|
|
26
|
+
4. **Resolve** with `pnpm comments resolve <id>`, or in the panel, only once the change is live. Why: a reviewer who opens a resolved comment and still sees the old state stops trusting the list.
|
|
27
|
+
5. **Report** to the user: what was fixed, by comment id; the taste calls waiting for them; any comment with no state.
|
|
28
|
+
|
|
29
|
+
## People
|
|
30
|
+
|
|
31
|
+
- **Editors** resolve any thread, sort comments and invite people. **Reviewers** comment, reply, and resolve or delete their own. Give a client's stakeholders the reviewer role; editors are the studio.
|
|
32
|
+
- **Team** is the company: `Studio`, or the client's name. It shows beside every name, so a reviewer reads who said what.
|
|
33
|
+
- The first editor comes from `pnpm people invite --name <n> --team <t> --role editor --site <url>`; after that, editors invite from the People tab.
|
|
34
|
+
- A link works for 14 days. `pnpm people reissue <id>` replaces it and signs that person out everywhere; `revoke` ends their access on their next page load.
|
|
35
|
+
- Never paste an invite link into a commit, a document, a ticket or a comment. Why: anyone holding it signs in as that person.
|
|
36
|
+
|
|
37
|
+
## The words in comments
|
|
38
|
+
|
|
39
|
+
`comments.md` holds reviewers' names and their words. Commit it only to a private repository. Never quote a comment in public copy, and never send one outside the project.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: {{title}}
|
|
3
|
+
summary: One line on what this explores.
|
|
4
|
+
context: app
|
|
5
|
+
status: exploring
|
|
6
|
+
kind: {{kind}}
|
|
7
|
+
feature: {{feature}}
|
|
8
|
+
created: {{created}}
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# {{title}}
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
What is wrong or unknown today, and for whom. Write this before any code.
|
|
16
|
+
|
|
17
|
+
## Question
|
|
18
|
+
|
|
19
|
+
The one decision this prototype exists to make.
|
|
20
|
+
|
|
21
|
+
## Flows
|
|
22
|
+
|
|
23
|
+
| Flow | Starts from | Edge cases |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| | | |
|
|
26
|
+
|
|
27
|
+
## States
|
|
28
|
+
|
|
29
|
+
Every state the prototype holds, one row each. The fidelity guard and `miles-port` read this table: a state that is not here does not exist to them.
|
|
30
|
+
|
|
31
|
+
| State | How to reach it |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| | |
|
|
34
|
+
|
|
35
|
+
## Fixtures
|
|
36
|
+
|
|
37
|
+
`fixtures.json` holds `real` and `missing`; `@kit/fixtures` derives `longest`, `empty` and `max`. Say where `real` came from (which CMS collection, or the comp) and the design's maximum count.
|
|
38
|
+
|
|
39
|
+
## Decision
|
|
40
|
+
|
|
41
|
+
Empty until picked. What was chosen, its values, and what was rejected and why. Then delete the folder.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AppShell, PageHead } from '@kit/AppShell';
|
|
2
|
+
import { useHashRoute } from '@kit/hooks';
|
|
3
|
+
import { Button, toast } from '@kit/ui';
|
|
4
|
+
import './styles.css';
|
|
5
|
+
|
|
6
|
+
export default function Prototype() {
|
|
7
|
+
const { path } = useHashRoute('#/items');
|
|
8
|
+
return (
|
|
9
|
+
<AppShell active={path}>
|
|
10
|
+
<div className="page">
|
|
11
|
+
<PageHead
|
|
12
|
+
title="{{title}}"
|
|
13
|
+
desc="Replace with the question from README.md."
|
|
14
|
+
actions={
|
|
15
|
+
<Button
|
|
16
|
+
kind="primary"
|
|
17
|
+
label="Start"
|
|
18
|
+
icon="plus"
|
|
19
|
+
onClick={() => toast('Wire this to the flow under test.', { kind: 'scope' })}
|
|
20
|
+
/>
|
|
21
|
+
}
|
|
22
|
+
/>
|
|
23
|
+
<div className="card card-pad flow-body">
|
|
24
|
+
<h2 className="card-title">The page under test</h2>
|
|
25
|
+
<p className="card-sub">
|
|
26
|
+
Build the flow here with @kit/ui. Sub-pages are hash routes, read with useHashRoute.
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</AppShell>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.flow-body { display: flex; flex-direction: column; gap: 4px; }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Conductor shared repository settings: https://www.conductor.build/docs/reference/settings
|
|
2
|
+
# Written by `design-hub bootstrap --conductor`. Personal overrides go in
|
|
3
|
+
# .conductor/settings.local.toml (gitignored).
|
|
4
|
+
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"
|
|
5
|
+
|
|
6
|
+
# Each workspace installs the project and serves it on its own
|
|
7
|
+
# $CONDUCTOR_PORT; the dev server reads that port before .env's PORT. No
|
|
8
|
+
# secrets are copied in: the GATE_* values are for Vercel only.
|
|
9
|
+
[scripts]
|
|
10
|
+
setup = """
|
|
11
|
+
git -C "$CONDUCTOR_ROOT_PATH" fetch --prune origin && git -C "$CONDUCTOR_ROOT_PATH" pull --ff-only || true
|
|
12
|
+
pnpm install && pnpm bootstrap --no-install
|
|
13
|
+
"""
|
|
14
|
+
run_mode = "concurrent"
|
|
15
|
+
|
|
16
|
+
# strictPort refuses a taken port instead of drifting away from the Open
|
|
17
|
+
# button's URL; bootstrap names the process that holds it.
|
|
18
|
+
[scripts.run.prototypes]
|
|
19
|
+
command = "pnpm dev"
|
|
20
|
+
default = true
|
|
21
|
+
icon = "play"
|
|
22
|
+
available_in = ["local"]
|
|
23
|
+
|
|
24
|
+
[prompts]
|
|
25
|
+
general = """Land work on `{{branch}}`: `git fetch origin && git rebase origin/{{branch}}`, run `pnpm check` if prototypes/ or kit/ changed, then push or open a PR as the project owner prefers. Prototype rules: AGENTS.md."""
|
|
26
|
+
|
|
27
|
+
[[preview_urls]]
|
|
28
|
+
name = "Prototypes"
|
|
29
|
+
url = "http://localhost:$CONDUCTOR_PORT"
|