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
1 change: 1 addition & 0 deletions .agents/skills/done-done-verify-first-draft/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Harness: A shell runs the CLI and curl for the url. A browser tool opens the url
- [Logo mode: concepts from the brief (wordmark-led, abstract, symbol; distinct base shapes; letter + object dropped), self-critique at 16 and 32 px, brand sheets with kind, idea and palette, checked SVG files and the app icon set](features/logo-mode.md): Run `first-draft-axi logo "<brief>" --out tea.html --options 3` (Chrome and an image-capable model required); deterministic `test/logo.test.mjs`; nonpaid whole-viewer, concept, critique and icon-set capture of a real run via `FD_LOGO_SOURCE=<out path> done-done run logo-recapture` (`src/logo.mjs`, `src/logo-concepts.mjs`, `src/make.mjs`, `src/cli.mjs`, `journeys/logo.mjs`)
- [Authenticated engines are required](features/ai-engine-required.md): Offline no-credential refusal through `test/engine-required.test.mjs` and independent make/judge CLI integrations through `test/critic.test.mjs`; one real normal-HOME `make --options 1 --first-view` must name distinct generator/judge engines and models. No fixture or self-assessment result counts as live proof.
- [The independent critic and local reference library](features/make-critic.md): `npm test` retains one real-CLI make/independent-judge integration and one retained-output `judge` integration in `test/critic.test.mjs`; it no longer covers the former detailed capture, retry, and refusal matrix; nonpaid sealed report via `done-done run critic`; live retained assessment via `first-draft-axi judge <option.html> "<brief>" --plain <baseline.html>` (`src/critic.mjs`, `src/human-side.mjs`, `src/judge.mjs`, `src/reference-library.mjs`, `src/reference-library.json`, `src/reference-capture.mjs`, `src/reference-network.mjs`, `src/cli.mjs`, `src/render-check.mjs`, `journeys/critic.mjs`)
- [Motion kept and judged: an animated option keeps its code, plays, and is scored on its frames](features/motion-kept-and-judged.md): Make from a brief that asks for motion and read the option html (inline `<script>` kept under `script-src 'unsafe-inline'`), `<option>.motion/` (`motion.json` plus 4 JPEG frames at 390 and 4 at 1280) and `critique.motion` (`present`, `score`, `reason`, `defects`; `score: null` for a page that does not move); open it in the Studio to see it play and the Motion panel; `polish --feedback` keeps the motion per round (`round-N.motion/`); nonpaid proof via `done-done run motion` (`src/make.mjs`, `src/draft.mjs`, `src/render-check.mjs`, `src/motion.mjs`, `src/critic.mjs`, `src/polish.mjs`, `src/view.mjs`, `src/view/app.js`, `journeys/motion.mjs`)
- [Make on OpenDesign: incremental generation, first-draft gates](features/make-on-the-full-engine.md): The pinned canonical recipe drives default make, incremental `--engine`/refine/logo/assets and the per-screen redesign writer. Verify imported source/licenses, native CLI tool/render/critic journeys, the blinded same-model comparison and clean packed install; Studio/JSX compatibility remains separate (`src/design-engine.mjs`, `src/engine-host.mjs`, `src/draft.mjs`, `src/make.mjs`, `src/redesign-writer.mjs`, `test/opendesign.test.mjs`).

## Evidence
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Motion kept and judged: an animated option keeps its code, plays, and is scored on its frames

Sources: `src/make.mjs` (`staticExport` with `script`, the live policy, the DRAWN/STATIC contracts), `src/draft.mjs` (`script-kept-inline`, `script-inline-only`, `motion-for-real`), `src/render-check.mjs` (`motion()`, `MOTION_AT`), `src/motion.mjs`, `src/critic.mjs` (`MOTION_VERDICT`, `MOTION_SIDE`), `src/polish.mjs`, `src/cli.mjs` (`make`, `judge`, `polish`), `src/view.mjs` (`/motion/<n>/<frame>`), `src/view/app.js` (the Studio Motion panel), `journeys/motion.mjs`.

## User outcome

An option made from a brief that asks for motion (a typing terminal, a pulse, a marquee) keeps its inline script and CSS animation, and plays in the Studio and in Play inside the option's sandboxed frame origin (`sandbox allow-scripts`, `connect-src 'none'`), loading nothing from the network. Its motion is captured as a frame series, 4 frames at 390 and 4 at 1280 (0.4, 1.4, 2.6 and 4.2 s after load, around the first moving element), and the independent judge returns a motion verdict `critique.motion = { present, score, reason, defects }` from those frames. A page that does not move gets `score: null` (with a defect when the brief asked for motion); a decorative loop is a defect. `polish` keeps the motion: each round has its own frames, and a round that stops moving is not accepted. A script loaded from a URL is still refused, or drawn once and dropped.

## Handles it drives

- `first-draft-axi make "<brief asking for motion>" --out <file.html>`: the option html keeps its `<script>` under a CSP with `script-src 'unsafe-inline'` and `default-src 'none'`; the make JSON has `files[].motion` (`moves`, `frames`, `dir`) and `files[].critique.motion`.
- `render-check` `motion(html)`: the frame series and what moved (`widths[].animations`, `scripted`, `infinite`, `looping`).
- `first-draft-axi judge <option.html> "<brief>" --plain <baseline.html>`: the same verdict on a retained page, frames written beside it.
- `first-draft-axi polish <option.html> "<brief>" --out-dir <dir> --feedback "<one sentence>"`: `round-N.motion/` per round, `critique.motion` in each `round-N.json`.
- The Studio Motion panel (`first-draft-axi view <option.html>`, `#/o/<n>`): the verdict, its defects and both frame rows, under the canvas at phone width.
- Nonpaid journey: `done-done run motion` (`journeys/motion.mjs`, local model and judge stand-ins).

## Evidence kind

`motion.json` plus the JPEG frame series `motion-<width>-<n>.jpg` under `<option>.motion/` (and `round-N.motion/` for polish), with the make, judge or polish JSON that carries `critique.motion`. Live runs add browser screenshots of the Studio while the option plays. Stand-in scores prove the protocol, not design quality.
22 changes: 20 additions & 2 deletions .done-done.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ journeys:
human-side-recapture:
command: node journeys/human-side.mjs
timeout: 8m
motion:
command: node journeys/motion.mjs
timeout: 15m

# This repo's coding agent, over agent in ~/.done-done/config.yaml (model, effort and the other agent settings stay there).
# agent: opencode
# Written requirements for the motion journey: an option's own animation, captured as frames (no mock: it is a new capture, not a new screen).
approved_requirements:
- kind: spec
name: motion frame at 390, first
requirement: "A narrow page with the heading 'Notes, version 1, draw 1' and a grey terminal block that holds only one partly typed command line beginning with '$ npm'."
capture: motion-390-1.jpg
journey: motion
- kind: spec
name: motion frame at 390, last
requirement: "A narrow page with the heading 'Notes, version 1, draw 1' whose grey terminal block holds three lines: '$ npm install -g first-draft-axi', 'added 1 package in 2s' and '$ first-draft-axi make \"a notes app\"'."
capture: motion-390-4.jpg
journey: motion
- kind: spec
name: motion frame at 1280, last
requirement: "A wide page with the heading 'Notes, version 1, draw 1' whose grey terminal block holds the same three lines, with an underscore cursor below the block."
capture: motion-1280-4.jpg
journey: motion
11 changes: 7 additions & 4 deletions ENGINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,13 @@ remain first-draft inputs. Model scores are assessments, not a design-match
or functional certification.

The host still draws every exported screen at 390x844 and 1280x800 in light
and dark. The whole design is a script-free, self-contained static HTML page
under the existing CSP, at most 5 MB. Fonts and permitted media are embedded;
remote files do not load. JSX is drawn once by the compatibility renderer and
snapshotted; browserless generation must deliver script-free HTML. `done`
and dark. The whole design is a self-contained HTML page under the existing
CSP, at most 5 MB. Fonts and permitted media are embedded; remote files do not
load. JSX is drawn once by the compatibility renderer and snapshotted without
script. An HTML page whose script is all inline keeps it, so the motion a brief
asks for plays; its CSP adds `script-src 'unsafe-inline'` and nothing else, so
it still loads and sends nothing. A `<script src>` is refused, or drawn once
and dropped when a browser is present. `done`
submits source to these same gates, and a failed submission returns the exact
findings. Turn limits, bounded retries and the last verified version remain
host-owned. Logo SVG/palette/icon validation and independent logo review stay
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Prefer Claude? Use `--agent byokit:claude --model claude-sonnet-5-5` in step 2 w

- **Explore more than one direction.** Ask for 1–5 options, a named style, or a multi-screen flow.
- **Compare at the right size.** Studio has Desktop, Phone and Both views; Play opens a screen flow.
- **Get the motion you asked for.** A brief that asks for motion (a typing terminal, a pulse, a marquee) gets it for real: the option keeps its CSS animation and inline script and plays in the Studio, sandboxed, loading nothing from the network. A script loaded from a URL is still dropped. Four frames at 390 and four at 1280 are kept in `<option>.motion/` and shown beside the option, and the independent judge scores motion from them, naming each defect. A page that does not move gets no motion score; a looping animation that only decorates is scored as a defect.
- **Point at the change.** Click a spot to leave a comment; numbered pins and plain JSON keep the feedback beside the design.
- **See the market first.** `--competitors` finds 3–5 real products of the brief's kind, captures their public pages through the same robots/terms review as a reference, and shows them beside the options with their sources and every refusal.
- **Keep the original.** Refine into a new option, or send the choices to [sign-off](https://github.com/umeranjum17/sign-off) for someone's pick.
Expand All @@ -66,7 +67,7 @@ first-draft-axi polish notes-1.html "A notes app" \
first-draft-axi polish-feedback notes-polished "Make the sidebar narrower"
```

`polish-feedback <run-folder> "<request>"` adds cumulative feedback while a run is in progress; the next round judges it. The original file is untouched.
`polish-feedback <run-folder> "<request>"` adds cumulative feedback while a run is in progress; the next round judges it. The original file is untouched. An animated option keeps its motion through polish: each round has its own `round-N.motion/` frames, and a round that stops moving is not accepted.

`first-draft-axi init` also installs the [agent skill](skills/first-draft-axi/SKILL.md) for Claude Code, Codex and pi in your repo. Commands return JSON, so your coding agent can drive the same workflow.

Expand Down
2 changes: 1 addition & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ The viewer's grid links to each canvas at `#/o/<n>`; add `/both`, `/phone` or `/

`view` defaults to the machine's tailnet address (`tailscale ip -4`), port 4430. Use `--host` and `--port` to change it; `--host 127.0.0.1` keeps the viewer on this machine. Option frames use the next port, a separate origin.

Generated files carry no scripts and their own content security policy. The viewer adds a sandboxed runtime for screen navigation, but options load and send nothing over the network. Remote assets named by a design are listed as `blocked_remote` and show empty.
Generated files carry their own content security policy; an option keeps only inline script (the motion its brief asked for), never a script from a URL. The viewer adds a sandboxed runtime for screen navigation, but options load and send nothing over the network. Remote assets named by a design are listed as `blocked_remote` and show empty.

Comments live in `notes.comments.json` next to `notes.html`, an array such as `[{"option":1,"device":"phone","x":0.4,"y":0.2,"text":"More space here"}]`. Coordinates are fractions of the frame; `device` (`phone` or `desktop`) is optional, and a comment without it shows on both frames. Comments added in the viewer are appended to this file, which is created if missing; the viewer never rewrites an invalid file, it shows a notice instead. Tap a numbered pin to read it.

Expand Down
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ <h2 id="open-title">A design you can open, at every width.</h2>
<p class="sub">Each option is a self-contained HTML file. The Studio shows it at desktop and phone width beside its brief; Play clicks through its screens and Fullscreen gives it the whole window.</p>
<ul class="facts">
<li><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>Every screen is checked at 390 and 1280, light and dark.</li>
<li><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>Motion you ask for plays: a typing terminal or a pulse keeps its code, and the judge scores it from frames at both widths.</li>
<li><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>Viewing needs no model call, on a laptop or a phone.</li>
<li><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>Comments are saved next to the design as plain JSON.</li>
</ul>
Expand All @@ -208,7 +209,7 @@ <h3>Hand it to your coding agent.</h3>
</section>
</main>

<footer><span>first-draft <span class="version">0.6.0</span> · Open source · MIT · Design engine: <a href="https://github.com/umeranjum17/open-design">OpenDesign</a> · Apache-2.0</span><a href="https://github.com/umeranjum17/first-draft#reference">CLI reference ↗</a></footer>
<footer><span>first-draft <span class="version">0.7.0</span> · Open source · MIT · Design engine: <a href="https://github.com/umeranjum17/open-design">OpenDesign</a> · Apache-2.0</span><a href="https://github.com/umeranjum17/first-draft#reference">CLI reference ↗</a></footer>
</div>
<script>document.addEventListener('click', (e) => { const b = e.target.closest('[data-copy]'); if (b) navigator.clipboard?.writeText(b.dataset.copy).then(() => b.setAttribute('aria-label', 'Copied'), () => {}); });</script>
</body>
Expand Down
25 changes: 17 additions & 8 deletions docs/wrapper-allowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,22 +218,31 @@
"reason": "An owner decision about honesty, not a design restriction (2026-10-24). It replaced the engine rule that labelled all sample content, which produced \"Placeholder quote\" and \"concept design\" copy (bake-off cause 2)."
},
{
"id": "script-drawn-once",
"id": "script-kept-inline",
"source": "contract",
"kind": "delivery",
"what": "Script may run once; the host keeps what the first render shows, without the script.",
"sha256": "17b216e97598b4f5",
"what": "Inline script and CSS animation are kept and play; a script loaded from a URL runs once and the host keeps what that first render shows, without any script.",
"sha256": "09292f0980fef96e",
"verdict": "accepted",
"reason": "Where Chrome is installed the host draws the page once into a static document; the sentence says what survives."
"reason": "Where Chrome is installed the exported page keeps its inline script under a policy that loads and sends nothing; a script from a URL cannot run there, so that page is drawn once into a static document. The sentence says what survives. It replaced script-drawn-once (2026-10-30), which dropped every script and with it the motion a brief asked for."
},
{
"id": "no-script",
"id": "script-inline-only",
"source": "contract",
"kind": "delivery",
"what": "Write no script: the page is shown as a static document.",
"sha256": "63d1c2d956a51103",
"what": "Script is inline only: a script with a src or a javascript: URL is refused; inline script and CSS animation play.",
"sha256": "4ca6767afc035144",
"verdict": "accepted",
"reason": "Without Chrome the page cannot be drawn, so script would not run in the exported document."
"reason": "Without Chrome the page cannot be drawn, so a script from a URL would not run in the exported document; inline script runs under the page's own policy, which loads and sends nothing. It replaced no-script (2026-10-30)."
},
{
"id": "motion-for-real",
"source": "contract",
"kind": "policy",
"what": "Motion the brief asks for is written for real (CSS animation or inline script), not as a still, honouring prefers-reduced-motion.",
"sha256": "a8b9700872e8c2bc",
"verdict": "accepted",
"reason": "An owner decision (2026-10-30): a brief that asks for a terminal animation, a pulse or a marquee gets that motion in the option. Under no-script the model was told to remove exactly what was asked for, and the animation was hand-rolled in the product instead. It restricts nothing the plain call would do."
},
{
"id": "critic-flow-check",
Expand Down
Loading
Loading