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`)
- [A clickable option stays clickable: its script exports, a real press is judged, and its source is readable beside it](features/clickable-option-export.md): Make from a brief that asks for behaviour (a row opens, a filter narrows) and read the option html (inline `<script>` kept under `script-src 'unsafe-inline'`), the make JSON's `critique.behaviour` (`present`, `score`, `reason`, `defects`, `clicked`, `widths`) and the judge call (frames `click-<width>-before`/`-after`, the prompt asking for behaviour); press a page whose control is wired to nothing and it is judged `clicked: false` and does not pass; a drawn `App.jsx` option keeps its readable source in `<option>.source/` and is not asked about behaviour; the Studio's frame origin serves the clickable page. Nonpaid proof via `done-done run click` (`src/make.mjs`, `src/render-check.mjs`, `src/judge.mjs`, `src/critic.mjs`, `src/source.mjs`, `src/cli.mjs`, `src/polish.mjs`, `src/view.mjs`, `journeys/click.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`).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# A clickable option stays clickable: its script exports, a real press is judged, and its source is readable beside it

Sources: `src/make.mjs` (`staticExport` with `script`, the live policy, `source_files`), `src/draft.mjs`, `src/render-check.mjs` (`clicks()`, `PRESS`, `PRESS_AT`), `src/judge.mjs` (`BEHAVIOUR_VERDICT`, `BEHAVIOUR_SIDE`), `src/critic.mjs` (the press, `critique.behaviour`, the pass bar), `src/source.mjs` (`<option>.source/`), `src/cli.mjs` (`make`, `judge`), `src/polish.mjs`, `src/view.mjs` (the frame origin Play opens), `journeys/click.mjs`.

## User outcome

An option made from a brief that asks for behaviour (a message row opening, a filter narrowing a list, a tab switching) is exported with its own inline script under a content security policy that adds `script-src 'unsafe-inline'` to `default-src 'none'`: the page really answers a click, loads nothing and sends nothing, and answers a click inside the sandboxed frame origin that Play and "Open live" open (`sandbox allow-scripts`, `connect-src 'none'`). Before the independent judge sees the option, the host presses the first control a person can press — a `button`, `[role=button]`, `[role=tab]`, `[role=menuitem]`, `input[type=submit|button]`, never a link — with a real mouse event (`Input.dispatchMouseEvent`) at 390 and at 1280, and keeps a short frame sequence across the press (11 frames from `PRESS_AT` over about 1.2 s, each with its own real timestamp), so the page is seen changing over time rather than only before and after. The judge gets those frames and answers `critique.behaviour = { present, score, reason, defects, clicked, widths }`. `clicked` says whether a press changed the page; `widths` names each control it pressed (`label`, `tag`) and how many it found, with `after`, the text a person reads once the page has redrawn; `idle` says the page also changes on its own, so a change it shows is not credited to the press. A control that changes nothing is a defect and the option does not pass (the bar stays 9). A page drawn from `App.jsx` has no script left, so it is never asked about behaviour: only a live `index.html` option is. A drawn option's source is written readable beside the export in `<option>.source/` (`App.jsx` plus the files it imports); an `index.html` option is its own readable source, so it is not written twice, and the page's in-band `first-draft-source` comment still travels with it for `refine`.

## Handles it drives

- `first-draft-axi make "<brief asking for behaviour>" --out <file.html>`: the option html keeps its `<script>`; the make JSON has `files[].critique.behaviour` and the judge call carries `click-<width>-before` and `click-<width>-after` frames.
- `render-check` `clicks(html)`: `{ clicked, widths: [{ width, controls, idle, clicked: { label, tag, changed, after, sequence: [{ at_ms, mime, dataUrl }] } | null }], frames }` (`frames` holds the before/after stills taken from the sequence).
- `first-draft-axi judge <option.html> "<brief>" --plain <baseline.html>`: the same verdict on a retained page. A page whose control is wired to nothing gets `clicked: false`, the control count and no frames, and `passed: false`.
- `make --engine`: a drawn `App.jsx` option writes `<option>.source/App.jsx`, and the make JSON's `files[].source_dir` names the folder; `critique.behaviour.present` is `false`.
- `first-draft-axi view <option.html>`: the frame origin (`/o/<n>`) serves the option script under `sandbox allow-scripts`; `#/o/<n>/play` is what Play and sign-off's "Open live" open.
- Nonpaid journey: `done-done run click` (`journeys/click.mjs`, local model and judge stand-ins); it encodes each width's sequence into a small `frames/click-<width>.mp4` with `ffmpeg` (at most 1280 wide, about a second and a half), and refuses before any work with a clear message when `ffmpeg` is not on `PATH`.

## Evidence kind

The click clips `frames/click-390.mp4` and `frames/click-1280.mp4` (the press as it happened, with the frame timestamps in the log), the click log `click-log.json` (the controls pressed per width, `clicked`, `idle`, the frame `at_ms` list, the clips, and `critique.behaviour`), the JPEG stills `click-390-before`, `click-390-after`, `click-1280-before` and `click-1280-after` under `frames/` — the board requires a clip for a click, since stills of mostly text do not prove one, the make JSON carrying `critique.behaviour`, the refused dead-control judge JSON, and the drawn option's `source_dir`. Live runs add browser screenshots of the framed page. Stand-in scores prove the protocol, not design quality.
3 changes: 3 additions & 0 deletions .done-done.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ journeys:
motion:
command: node journeys/motion.mjs
timeout: 15m
click:
command: node journeys/click.mjs
timeout: 15m

# 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:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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.
- **Click it, don't squint at it.** An option whose brief asks for behaviour (a row opens, a filter narrows, a tab switches) is exported with its own inline script, so it really answers a click in Play. The host presses one of its controls with a real mouse event at 390 and 1280 before the judge sees it, and the judge scores `critique.behaviour` from the page before and after that press, naming each control that does nothing. A drawn option carries its App.jsx source beside it in `<option>.source/`.
- **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 Down
4 changes: 3 additions & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,9 @@ 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 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.
Generated files carry their own content security policy; an option keeps only inline script (the motion its brief asked for, and the controls that answer a click), 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.

A live option's controls are judged by a real press, not by a still: before the independent judge sees the option, the host presses the first control a person can press at 390 and at 1280 with a mouse event in a headless browser, and keeps the page before and after it. The press is kept as a short frame sequence with its own timestamps (11 frames over about 1.2 s), which a run encodes into a small clip (`click-<width>.mp4`) beside the stills. When that press changes the page, the judge is asked for `critique.behaviour = { present, score, reason, defects, clicked, widths }` from the before and after frames; a control that changes nothing is named as a defect and the option does not pass. A page drawn from `App.jsx` has no script left, so it is never asked about behaviour, and its readable source is written beside the export in `<option>.source/` (`App.jsx` and the files it imports); an `index.html` option's page is its own readable source.

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
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ <h3>Hand it to your coding agent.</h3>
</section>
</main>

<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>
<footer><span>first-draft <span class="version">0.8.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
Loading
Loading