Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/runtime-targets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"catalog_digest": "7bffd60e57b37c64831cc9e9df582d9d2f124df7f3556a26d1f7e147f1f51f64",
"catalog_digest": "aa3d2e236b45106db2c2121a2f22d2962a9aaa3c981a55ad298412b361f65ba2",
"targets": [
{
"name": "claude",
Expand Down
2 changes: 1 addition & 1 deletion registry/capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -7810,7 +7810,7 @@
"posture": "repository-local",
"source_identity": "stack:skills/micro-branch"
},
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local canvas (Twombly or a built-in board), jump back to any take, and pick one into trunk. Use when iterating on a UI and versions should never overwrite each other.",
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local canvas (Twombly or a built-in board), jump back to any take, and pick one into trunk. Use when iterating on a UI and versions should never overwrite each other. Includes a Design Studio preset that opens each frame on one prototype view.",
"role": "workflow",
"runtimes": {
"publish_targets": [
Expand Down
20 changes: 16 additions & 4 deletions skills/design/micro-branch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ description: >-
one into trunk. Use when someone says "try a few versions", "give me three takes",
"micro-branch this", "put the versions on a canvas", "go back to take 2", "build
on take 3", "pick that one", or wants design iterations that never overwrite
each other. Works in any git repo with a static build or plain HTML.
each other. Works in any git repo with a static build or plain HTML, and has a
Design Studio preset that opens each frame on one prototype view.
---

# micro-branch
Expand Down Expand Up @@ -48,7 +49,9 @@ machine.
1. Check the repo: `git status` is clean and trunk has at least one commit. If the
folder is not a repo, ask before running `git init`.
2. Work out how a take becomes static files. Read `references/setup-recipes.md`
for Vite, Next.js, Astro, plain HTML and other stacks.
for Vite, Next.js, Astro, plain HTML and other stacks. A folder with
`studio.config.json` is a Design Studio install: use `init --preset design-studio`
and read `references/design-studio.md`.
3. Initialise. Defaults: canvas `twombly`, frame `1280x800`, at most 4 takes per round.

```sh
Expand All @@ -58,19 +61,26 @@ $MB init --name "<project name>" --build "<build command>" --out <output dir> \
```

Use `--frame-size 390x844` for mobile screens. Use `--canvas board` when Node is
missing or Twombly fails; the board needs only Python.
missing or Twombly fails; the board needs only Python. When Twombly rejects remote
fonts or images, `--localize-fonts` and `--localize <host>` copy them into each
frame.

## The loop

1. **Start a round** for one question the person cares about:
`$MB round new <slug> --question "How should view-all open?"`
`$MB round new <slug> --question "How should view-all open?"`.
Add `--entry "index.html?variant=b"` when the round's frames open somewhere other
than the repo's entry, or `--view <prototype>/<path>` in Design Studio
(`$MB views` lists them).
2. **Cut takes**, one idea each, usually 3:
`$MB take new r1 <slug> --note "one line on the idea"`.
Edit only inside the `worktree` path it returns. Never edit trunk during a round.
Subagents can work in parallel, one take worktree each.
3. **Publish each take**: `$MB publish r1/t2`. It commits the take, runs the build,
snapshots the output and places a frame on the canvas. A failed build publishes
nothing and marks the take `broken`; read the log path in the hint and fix it.
`prepared` in the reply lists what the snapshot changed (copied assets, scrubbed
strings). Mention it if anything was skipped.
4. **Show the canvas**: `$MB canvas` prints the URL. Tell the person which label is
which idea in one line each.
5. **Read feedback**: `$MB feedback` returns what the person is looking at and their
Expand Down Expand Up @@ -111,4 +121,6 @@ missing or Twombly fails; the board needs only Python.
to fix a frame that fails them.
- `references/setup-recipes.md`: build, output and setup values per stack, and what
to do when a project has no static build.
- `references/design-studio.md`: the Design Studio preset, picking a view, and what
changes in its snapshots.
- `references/troubleshooting.md`: common errors and their fixes.
2 changes: 1 addition & 1 deletion skills/design/micro-branch/capability.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"posture": "repository-local",
"source_identity": "stack:skills/micro-branch"
},
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local canvas (Twombly or a built-in board), jump back to any take, and pick one into trunk. Use when iterating on a UI and versions should never overwrite each other.",
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local canvas (Twombly or a built-in board), jump back to any take, and pick one into trunk. Use when iterating on a UI and versions should never overwrite each other. Includes a Design Studio preset that opens each frame on one prototype view.",
"role": "workflow",
"runtimes": {
"publish_targets": [
Expand Down
78 changes: 78 additions & 0 deletions skills/design/micro-branch/references/design-studio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Design Studio

Design Studio is an open-source prototype sandbox: React views, Markdown documents,
diagrams and canvases in one folder, with a browser preview. The `design-studio`
preset makes each take's frame open on one view, on its own, without the studio
around it.

## Set up

Run init from the Design Studio checkout, after its dependencies are installed
(`pnpm install`, once):

```sh
$MB init --preset design-studio [--localize-fonts] [--localize <host>,<host>]
```

| Setting | Preset value | Why |
|---|---|---|
| `--build` | `node src/platform/node/build/build-manifest.js --deploy && ./node_modules/.bin/vite build` | The manifest and Vite only, about 10 seconds. `pnpm build` adds release checks and typechecking, which reject work in progress |
| `--out` | `dist` | |
| `--setup` | Links `node_modules` and each `src/systems/*/node_modules` from the main checkout | Instant. A fresh `pnpm install` in each take takes about a minute |
| `--root-paths` | on | The build loads `/assets/...`. Twombly serves each frame at its own root, and the board serves it at `r<N>-t<M>.localhost` |
| `--scrub` | `excalidraw-public-config` | See "What changes in the snapshot" |

Trunk is the branch checked out when init runs. In a Design Studio folder that is
often a feature branch, not `main`. Pass `--trunk` to choose.

## Pick the view

Each round opens its frames on one view, a `.tsx` file in a prototype folder:

```sh
$MB views # every view on trunk, as <prototype>/<path>
$MB round new card --question "How should the card read?" --view "<prototype>/<path>.tsx"
```

`--view` takes the full `<prototype>/<path>`, the path alone, or the end of the
path, as long as only one view matches. A take can use a different view:
`take new r1 <slug> --view ...`. A view that a take creates is not on trunk yet, so
`take new` warns instead of failing, and `views --take r1/t2` lists the take's files.

At publish, the script reads the take's build manifest and points the frame at
Design Studio's preview address (`index.html?studio-preview=1&...`). The studio
uses that address to embed views itself. It renders one view with no navigation
or chrome.

## What changes in the snapshot

Snapshots are copies. The take's own files never change.

- **Two Excalidraw strings.** Design Studio's canvas module bundles Excalidraw,
which ships the public Firebase client key of its own collaboration server and a
browser-storage key named `excalidraw-oai-api-key`. Twombly's secret check flags
both. The `excalidraw-public-config` rule rewrites exactly those two strings to
placeholders, in files that mention Excalidraw. Frames use neither. `publish`
lists each rewrite under `prepared.scrubbed`.
- **Remote assets.** Frames can't reach the network. If a design system loads
Google Fonts, or a prototype shows images from a media host, name the hosts at
init: `--localize-fonts` for Google Fonts, `--localize <host>` for the rest. Each
asset is downloaded once over https and cached in
`<git-common-dir>/micro-branch/cache/remote/`. The script copies it into the
frame's `mb-assets/` folder and rewrites the references.
`prepared.localized.skipped` lists anything that failed to download.

## Limits

- A view is live in its frame, so motion and interaction work. Anything that calls
a server needs mock data, like any frame.
- Archived prototypes are left out of the build by `--deploy`. Unarchive one to
explore it.
- Takes share the main checkout's dependencies. A take that adds or upgrades a
package needs its own install: delete the take's linked `node_modules` and run
`pnpm install` inside the take.
- The preview address is part of Design Studio's internals. If an upgrade changes
it, frames show "Invalid preview address". Fix `design_studio_entry()` in
`scripts/mb.py` through a Stack change.
- The Design Studio folder's own `AGENTS.md` still applies inside each take. Build
prototypes the way it says.
12 changes: 10 additions & 2 deletions skills/design/micro-branch/references/setup-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ output lands (`--out`), and what each new worktree needs before it can build
before choosing.

Frames are served from a subfolder on the board and as standalone folders in
Twombly, so **asset paths must be relative**. A blank frame or 404s for `/assets/...`
means the build wrote absolute paths.
Twombly, so **asset paths should be relative**. A blank frame or 404s for
`/assets/...` on the board means the build wrote root paths. If the build can't
change, `init --root-paths` makes the board serve each frame at the root of its own
address (`r1-t2.localhost`), which Chrome, Edge and Firefox resolve locally.

| Stack | `--build` | `--out` | Notes |
|---|---|---|---|
Expand All @@ -19,6 +21,7 @@ means the build wrote absolute paths.
| Create React App | `PUBLIC_URL=. npm run build` | `build` | |
| Storybook | `npx storybook build -o storybook-static` | `storybook-static` | Each frame shows the whole Storybook |
| Monorepo package | `cd apps/web && npm run build -- --base=./` | `apps/web/dist` | Paths are relative to the take's root |
| Design Studio | `init --preset design-studio` | `dist` | Fills in build, setup and root paths. See `design-studio.md` |

Swap `npm run` for `pnpm`, `yarn` or `bun` to match the lockfile.

Expand All @@ -37,6 +40,11 @@ Every take is a separate worktree, so it starts without `node_modules`.
Install dependencies in the main checkout first when using the symlink. If a take
adds a package, replace its symlink with a real install inside that worktree.

A `.gitignore` line like `node_modules/` matches folders, not links, so a linked
`node_modules` would land in the take's commits. After setup, the script adds every
link setup made to the repo's local `.git/info/exclude` (never committed) and lists
them under `setup.ignored_links`.

## Environment

`--setup` and `--build` run through the shell inside the take's worktree with
Expand Down
13 changes: 10 additions & 3 deletions skills/design/micro-branch/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ Every failure prints `"ok": false` with `error` and `hint`. Match the error here
| `round ... is picked` / `closed` | Start a new round, or `round next <take>` to build on one of its takes |
| `build failed ... nothing was published` | Read the log path in `hint`. Run the build by hand in the take's worktree, fix, publish again |
| `build output '<dir>' does not exist` | The build wrote somewhere else. `init --out <dir>` |
| `'index.html' is missing from the build output` | `init --entry <file>` |
| `'index.html' is missing from the build output` | `init --entry <file>`, or per round with `round new --entry` |
| `--view needs the design-studio preset` | Use `--entry` outside Design Studio. In a Design Studio folder, `init --preset design-studio` |
| `no view matches ...` / `matches N views` | `views` lists every view as `<prototype>/<path>`; use that form. At publish, archived prototypes are missing from the build |
| `this repo has no studio.config.json` | The preset only fits a Design Studio checkout. Run init there, or drop `--preset` |
| `Design Studio's dependencies are not installed` | Run `pnpm install` in the main checkout once |
| `not a host name` | `--localize` takes bare hosts, comma-separated: `images.example.com,cdn.example.com` |
| `setup failed in <worktree>` | Run the setup command by hand there. The worktree stays, so publish after fixing |
| `trunk checkout ... has uncommitted changes` | Ask the person whether to commit or stash their trunk changes. Never discard them |
| `does not merge cleanly into <trunk>` | Trunk moved since the round started. In the take's worktree: `git merge <trunk>`, resolve, `publish`, then `pick` again. Trunk was left unchanged |
| `has no changes against <trunk>` | The take is identical to trunk; nothing to pick |
| `twombly <command> failed` | Check `node --version` (18 or newer) and that npm can reach the registry for the first download. If it still fails, `init --canvas board` and carry on |
| `twombly <command> failed` | Check `node --version` (22 or newer) and that npm can reach the registry for the first download. If it still fails, `init --canvas board` and carry on |
| `micro-branch never shares a canvas` | Expected. The person clicks Share in Twombly |
| `no single take matches` | Use the full id from `status`, e.g. `r2-type/t3-serif` |
| Board frame is blank or assets 404 | The build uses absolute asset paths. See the relative-path notes in `setup-recipes.md` |
| Board frame is blank or assets 404 | The build uses root paths. Make them relative (`setup-recipes.md`) or `init --root-paths` |
| `prepared.localized.skipped` is not empty | Those assets failed to download and the frame still points at the network. Check the host and that the person is online; the cache keeps what did download |
| A take's commit contains a `node_modules` link | Made before the exclude fix. Remove it in the take with `git rm --cached node_modules`, then publish |
| A take's folder was deleted by hand | `open <take>` recreates it from the branch or the archive tag |
| The canvas shows an old version of a take | `publish` the take again; the snapshot is replaced |

Expand Down
4 changes: 2 additions & 2 deletions skills/design/micro-branch/references/twombly.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ Twombly checks every frame before placing it. `publish` then fails with

| Code | Cause | Fix |
|---|---|---|
| `EXTERNAL_REQUEST` | The page loads something from another host: Google Fonts, Typekit, a CDN script or stylesheet, a remote image, analytics | Download the asset into the build output and load it with a relative path. For fonts, copy the `.woff2` files and write a local `@font-face`, or inline them as `data:` URLs |
| `EXTERNAL_REQUEST` | The page loads something from another host: Google Fonts, Typekit, a CDN script or stylesheet, a remote image, analytics | For fonts and images, let the script copy them into each snapshot: `init --localize-fonts` for Google Fonts, `init --localize <host>` for other hosts. Otherwise download the asset into the build output and load it with a relative path. Scripts and analytics should come out of the page |
| `MISSING_FILE` | A file the page asks for is not in the output | Use relative asset paths (see `setup-recipes.md`) and check the build copies the file |
| `NO_ENTRY` | No entry file in the output | `init --entry <file>` or fix `--out` |
| `PROJECT_FILE`, `SERVER_CODE`, `ENV_FILE`, `KEY_FILE`, `CREDENTIAL_FILE`, `DATABASE_FILE` | The output folder holds source or config, not just the built site | Point `--out` at the real build output. Common offenders are already left out of snapshots |
| `SECRET_FOUND` | Something that looks like a key or token is in the files | Remove it. Frames use mock data only. Realistic fake tokens can trip this too; make them obviously fake |
| `SECRET_FOUND` | Something that looks like a key or token is in the files | Remove it. Frames use mock data only. Realistic fake tokens can trip this too; make them obviously fake. In Design Studio, the bundled Excalidraw strings are handled by the `excalidraw-public-config` scrub rule (`design-studio.md`) |
| `BLANK_RENDER`, `UNCAUGHT_ERROR`, `LOAD_TIMEOUT` | The page renders nothing, throws, or never finishes loading | Open the snapshot from the board and fix the error. Pages that need a backend need mock data |
| `FILE_TOO_LARGE`, `TOO_MANY_FILES`, `FRAME_TOO_LARGE` | Over 25 MB per file, 5,000 files or 100 MB per frame | Trim the output, compress media |

Expand Down
Loading
Loading