Repository navigation
Clickable options are judged on a real press and keep their source (0.8.0) - #110
Merged
umeranjum17 merged 2 commits intoOct 30, 2026
Merged
Conversation
…keep their source (0.8.0)
Sign-off found the export script-free, so a design option could not be
clicked, and 0.7.0 already kept a live option's inline script: measured
with a clickable inbox option, the export keeps the script under
`script-src 'unsafe-inline'` and `default-src 'none'`, and a real mouse
press in Chrome changes the page. The gaps were judging and source.
- render-check gains clicks(): at 390 and 1280 it presses the first
control a person can press (button, role=button/tab/menuitem, submit)
with Input.dispatchMouseEvent, and keeps the page before and after the
press, the control's label, what a person reads after it, and whether
the page also changed on its own (idle).
- The judge is asked for `behaviour { score, reason, defects }` with
those frames, only for a live page that carries a control; a press that
changes nothing is a defect and the option does not pass (bar stays 9).
A page drawn from App.jsx has no script left, so it is never asked.
- A drawn option writes its engine source readable beside it in
`<option>.source/`, and make/judge JSON name it in `files[].source_dir`;
the in-band comment still travels with the page for refine.
- journeys/click.mjs drives the whole path through the real CLI with
local stand-ins: export, press, judge verdict, a dead control refused,
the drawn source sidecar, and a real click on the framed page the view
serves.
Version 0.8.0 so the release workflow publishes on merge; the workflow
itself is unchanged.
The board refuses a click proved by stills of mostly text, so the press is now kept as a short sequence and encoded into a video. - render-check's clicks() keeps 11 CDP frames across the press (PRESS_AT, about 1.2 s) with each frame's own real timestamp, and takes the before/after stills from that sequence; a control that changes nothing keeps no frames. - The judge still reads only the before/after frames: the behaviour log says how many frames span the press and over how long, never the bytes. - journeys/click.mjs encodes each width into frames/click-<width>.mp4 with ffmpeg (concat with the real frame durations, at most 1280 wide, about a second and a half), refuses before any work with a clear message when ffmpeg is absent, and records the frame timestamps and the clips in click-log.json.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sign-off found first-draft's export script-free, so a design option could not be clicked, and the captain wants design picks as live interactive demos. I measured before building, so this PR only closes the gaps measurement proved.
What already worked on 0.7.0 (measured, not assumed)
I wrote a clickable inbox option (a row opens its message, Unread narrows the list), ran it through the shipped export path (
staticExport(html, { script: true })), and drove a real CDP mouse press on the result in headless Chrome:<script>verbatim;default-src 'none'; form-action 'none'; script-src 'unsafe-inline'; ...— script runs, nothing loads or is sent;sandbox allow-scripts, so a click works inside the sandboxed frame;<script src=...>is still refused (unchanged).So the export, network and sandbox parts of the ask were already true. The two real gaps were judging and source.
What this adds
1. The judge scores behaviour by a real click (
src/render-check.mjsclicks(),src/judge.mjs,src/critic.mjs). Before the independent judge sees an option, the host presses the first control a person can press —button,[role=button],[role=tab],[role=menuitem],input[type=submit|button]; never a link, because a link is navigation — at 390 and at 1280 withInput.dispatchMouseEvent(a real trusted mouse event, notelement.click()), and keeps the page before and after that press. The judge is asked forbehaviour { score, reason, defects }and is sentclick-390-before,click-390-after,click-1280-before,click-1280-after. The log it also gets says which control was pressed (label,tag), how many controls exist,after(the text a person reads once the page redrew), andidle(the page changes on its own, so a change is not credited to the press).critique.behaviour = { present, score, reason, defects, clicked, widths }.App.jsxhas no script left, so it is never asked about behaviour (present: falsewith the reason). Only a liveindex.htmloption is judged on its press — the drawn path's dead buttons are a known limitation of the snapshot, not a new failure.2. The exported option keeps its source sidecar (
src/source.mjs,src/make.mjs,src/cli.mjs,src/polish.mjs).<option>.source/and it sits beside the option file (inbox.html→inbox.source/), the same convention as<option>.motion/. It holds the files the engine wrote:App.jsxplus the files it imports, as plain readable text — not the base64 blob that was the only copy of a drawn option's source before.makeJSON names it infiles[].source_dir, and eachpolishround writes its own<round-N>.source/.index.htmloption is its own readable source (its script and CSS sit in the page), so it is not duplicated. The in-bandfirst-draft-sourcecomment stays: it is howrefineseeds the next round.../, absolute) is refused.For sign-off (separate task in that repo)
<view-url>#/o/<n>/play— exactly whatpostalready passes as each option's--link. It must not read the reference/source link. That route serves the option from the frame origin (<view-port+1>/o/<n>) withsandbox allow-scripts,script-src 'unsafe-inline'andconnect-src 'none', i.e. the clickable page.<option>.source/beside the option (drawn options only); sandboxsandbox allow-scriptsin a separate origin withdefault-src 'none'and no network; andcritique.behaviourin the make/judge JSON with the frames under the same evidence conventions as<option>.motion/.Proof
proof:the environment ships thedone-done-verify-first-draftverify skill; this change adds the feature it now drives,features/clickable-option-export.mdand theclickjourney (done-done run click). Evidence folder:/home/umer/.treehouse/firstmate-cff959/3/firstmate/data/interactive-prototype-export-options-kee-23/run4/(click-log.json,make.json,refused-dead-control.json,drawn-source-sidecar.json,steps.json, andframes/click-390-before|after.jpg,frames/click-1280-before|after.jpg).node journeys/click.mjs(nonpaid, real CLI, local model and judge stand-ins) at this head: a clickable inbox option exports with its inline script underscript-src 'unsafe-inline'anddefault-src 'none'; the host's real press changes the page at 390 and 1280 and the click log shows the narrowed list (only the unread row left); the judge is sent the before/after frames and answers with a behaviour verdict; a control wired to nothing is judgedclicked: falseand refused; a drawnApp.jsxoption writesApp.jsxreadable in<option>.source/and is not asked about behaviour; and a real click on the page the view serves (sandbox allow-scripts,connect-src 'none') still changes it. Scores are synthetic stand-in scores: they prove the protocol, not design quality.npm test: 30/30 pass, including the newtest/click.test.mjs(verdict schema + sidecar paths, no browser) and the existing motion, critic, make, polish and view integrations.Scope
No board restyle, no engine or draft prompt rewrite, no new design option, nothing in the sign-off repo. Version bumped to 0.8.0 so the existing release workflow publishes on merge; the workflow file is untouched. No
done-done runwas executed at this head (its judge spends model calls); the journey above is the same drivedone-done run clickperforms.Checks
outcome: A real mouse press on the Unread button in the exported page is recorded as clicked: true at 390 wide and at 1280 wide, and the clip of each press shows the inbox list narrowing to the unread row once the page redraws.
repro: n/a
proof: done-done-verify-first-draft (features/clickable-option-export.md); done-done run 20261030T112801Z-5fc65295-2ca128 (journey click) at head 5fc6529
evidence: 20261030T112801Z-5fc65295-2ca128/frames/click-390.mp4, 20261030T112801Z-5fc65295-2ca128/frames/click-1280.mp4, 20261030T112801Z-5fc65295-2ca128/click-log.json