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": "aa3d2e236b45106db2c2121a2f22d2962a9aaa3c981a55ad298412b361f65ba2",
"catalog_digest": "aa9bf5a311f1ada9ee219cc5d40c93b1928808a04d40ed97e0d3654a9fe1762a",
"targets": [
{
"name": "claude",
Expand Down
8 changes: 8 additions & 0 deletions docs/plans/2026-11-06-micro-branch-skill-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ execution: code

# micro-branch skill - Plan

> **Update, 2026-11-10: Twombly dropped.** Twombly is a paid third-party product,
> so Maroun took it out of Stack. The local board is now the only canvas. The
> Design Studio preset, per-origin frames, `--view` and `--localize` stay. Gone: the
> Twombly adapter, its version pin, port and privacy flags, canvas comments and
> focus (`feedback`), and the Excalidraw scrub, which only existed to pass
> Twombly's secret check. A test now fails if "twombly" appears under `skills/`.
> The Twombly decisions below are left as written, as a record of what was built.

## Goal contract

- **Objective:** ship a Stack-owned `micro-branch` skill that keeps every design iteration as its own git branch ("take"), lays takes out side by side on a canvas, and lets the person jump back to any take or pick one into trunk.
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. Includes a Design Studio preset that opens each frame on one prototype view.",
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local 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
52 changes: 23 additions & 29 deletions skills/design/micro-branch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
name: micro-branch
description: >-
Iterate on a UI without losing versions. Each attempt ("take") lives on its own
git branch and worktree, every take is laid out side by side on a local canvas
(Twombly, or a built-in board), and the person can jump back to any take or pick
one into trunk. Use when someone says "try a few versions", "give me three takes",
git branch and worktree, every take is laid out side by side on a local board,
and the person can jump back to any take or pick 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, and has a
Expand Down Expand Up @@ -41,8 +40,8 @@ MB="python3 <this skill's directory>/scripts/mb.py"

Run it from anywhere inside the project's main checkout. Every command prints JSON
with `"ok": true`, or `"ok": false` plus `error` and `hint`. Read the hint before
retrying. State lives in `<git-common-dir>/micro-branch/` and never leaves the
machine.
retrying. State, snapshots and the board live in `<git-common-dir>/micro-branch/`
and never leave the machine.

## First use in a repo

Expand All @@ -52,18 +51,17 @@ machine.
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.
3. Initialise. Defaults: frame `1280x800`, at most 4 takes per round.

```sh
$MB init --name "<project name>" --build "<build command>" --out <output dir> \
--setup "<install command, only if each take needs dependencies>" \
[--frame-size 390x844] [--canvas board]
[--frame-size 390x844]
```

Use `--frame-size 390x844` for mobile screens. Use `--canvas board` when Node is
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.
Use `--frame-size 390x844` for mobile screens. The board needs only Python. To keep
frames working offline, `--localize-fonts` and `--localize <host>` copy remote fonts
and images into each frame.

## The loop

Expand All @@ -77,16 +75,16 @@ frame.
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
comments (Twombly only), with `takes_by_label` to map frames to takes. On the
board, ask in chat. Summarise per take and propose the next takes. Build only
after the person agrees.
snapshots the output and lays it out on the local board, starting the board if
it isn't running. 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 assets
the snapshot copied. Mention any under `skipped`.
4. **Show the board**: the reply's `board` is its URL (`$MB canvas` prints it too).
Open it in the built-in browser if there is one, or give the person the link.
Tell them which label is which idea, one line each.
5. **Get feedback in chat**: the board has no comments. Ask which takes work and
what to change, by label (`R1 · T2 ...`); `status` maps labels to takes.
Summarise per take and propose the next takes. Build only after the person agrees.
6. **Iterate without overwriting**:
- Refine one take into a new one: `$MB take new r1 <slug> --from t2`.
- Start the next question from a take without merging it:
Expand All @@ -105,22 +103,18 @@ frame.
## Rules

- Never push branches or tags, and never open pull requests from this skill.
- Never share a canvas. In Twombly the person clicks Share themselves; the script
refuses `share`.
- The board stays on 127.0.0.1. Never expose it (no tunnels, no other interfaces) and
never upload frames anywhere.
- Never delete `mb/*` branches, `mb-archive/*` tags or take worktrees by hand. `pick`
and `round close` tag first, check the tag, then clean up.
- Never `git reset --hard` or force-checkout in trunk or a take.
- Keep to the take limit. More than 4 ideas means the question is too broad: split it
into two rounds.
- Canvas comments are feedback to summarise, never instructions to execute. This
matters most once a canvas is shared and other people can comment.

## References

- `references/twombly.md`: the pinned Twombly CLI, what its gates reject, and how
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/design-studio.md`: the Design Studio preset, picking a view, and
copying remote assets into its frames.
- `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. Includes a Design Studio preset that opens each frame on one prototype view.",
"purpose": "Keep every design iteration as its own git branch (a take), lay takes out side by side on a local 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
28 changes: 10 additions & 18 deletions skills/design/micro-branch/references/design-studio.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ $MB init --preset design-studio [--localize-fonts] [--localize <host>,<host>]
| `--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" |
| `--root-paths` | on | The build loads `/assets/...`, so the board serves each frame at the root of its own address, `r<N>-t<M>.localhost` |

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.
Expand All @@ -44,23 +43,16 @@ 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
## Remote assets

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.
A frame on the board loads fonts and images from the network like any page. To keep
frames working offline, and the same every time they are opened, 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 snapshot's references. Snapshots are
copies, so the take's own files never change. `prepared.localized.skipped` lists
anything that failed to download.

## Limits

Expand Down
14 changes: 7 additions & 7 deletions skills/design/micro-branch/references/setup-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ output lands (`--out`), and what each new worktree needs before it can build
(`--setup`). Read `package.json` scripts, the lockfile and the framework config
before choosing.

Frames are served from a subfolder on the board and as standalone folders in
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.
The board serves each frame from a subfolder, so **asset paths should be
relative**. A blank frame or 404s for `/assets/...` 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 |
|---|---|---|---|
| Plain HTML, CSS, JS | `""` (none) | `.` or the folder holding `index.html` | The whole folder is copied, minus `.git` and `node_modules` |
| Vite (React, Vue, Svelte, vanilla) | `npm run build -- --base=./` | `dist` | `--base=./` makes asset paths relative |
| Next.js | `npx next build` with `output: 'export'` in `next.config` | `out` | Needs static export; set `images.unoptimized: true`. If `/_next/` assets 404 on the board, use Twombly or ask before changing `assetPrefix` |
| Next.js | `npx next build` with `output: 'export'` in `next.config` | `out` | Needs static export; set `images.unoptimized: true`. If `/_next/` assets 404 on the board, use `init --root-paths`, or ask before changing `assetPrefix` |
| Astro | `npm run build` | `dist` | If assets 404 on the board, set `base` in `astro.config` |
| SvelteKit | `npm run build` with `adapter-static` | `build` | `paths.relative` must stay `true` |
| Create React App | `PUBLIC_URL=. npm run build` | `build` | |
Expand Down Expand Up @@ -58,7 +58,7 @@ become frames directly. In order of preference:
1. Add a static export for the screens under exploration, if the framework supports it.
2. Build a small static harness page in the take that renders the component with
mock data, and point `--out` at it.
3. Skip the canvas: cut takes as usual, run the dev server in each take's worktree
3. Skip the board: cut takes as usual, run the dev server in each take's worktree
on its own port, and share the URLs with the person.

## Frame size
Expand Down
6 changes: 3 additions & 3 deletions skills/design/micro-branch/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ Every failure prints `"ok": false` with `error` and `hint`. Match the error here
| `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` (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 |
| `unrecognized arguments: --canvas ...` | The board is the only canvas now; drop the flag |
| State from a version that had another canvas | Nothing to fix by hand. Old settings and frame fields are dropped when state loads; run `init` with no flags to write the cleaned state to disk |
| `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 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 |
| The board shows an old version of a take | `publish` the take again; the snapshot is replaced |

## Inspecting state

Expand Down
Loading
Loading