Port the UI language v2 draft onto the canvas #237
No reviewers
Labels
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!237
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "design/ui-language-v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ports the UI language v2 draft (Inspector with the Stacks touches) from Figma onto
design/Ponente.free, as the canvas's new region right of the traced frames.What lands on the canvas (from x 9000; the 45 traced frames are untouched):
That makes 79 frames. Text stays editable, colours are resolved per frame (so dark frames are dark), and shadows, blurs and smooth corners carry over.
How (
design/figma, README inside):use_figmahas no network and its return value lands in the agent's context. Soextract.jspacks the frame tree into a PNG image fill,download_assetshands back its original bytes, andport.pydecodes the tree, converts it to FREE and posts it to Lunacy. FREE has no letter spacing, variables or instance overrides, so lines run a touch wide, colours are literal and components arrive flattened.Docs:
docs/frontend.mddescribes the region and its rules. It is a proposal whose behaviour changes (inspector instead of the dialog, saving on leave, Undo instead of confirmations, the Done deck) still wait on sign-off. It is edited in Figma and ported, never edited on the canvas, and its new copy reachesLocaleServiceonly when built.Checked:
extract.jsandport.pyreproduce the port's output exactly..freekeeps every original entry and all 49 old top-level layers.tools/verify.shpasses (584 tests).The
.freegrows from 0.8 MB to 1.7 MB.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
This PR adds the v2 canvas region, the extractor and port scripts, a README, and the
docs/frontend.mdupdate. I did not open the binary.free, so I judged the scripts and docs by reading them. I found nothing blocking.design/figmais added to theCovers:line, soDocumentationTestsshould still pass. The "edited in Figma, never on the canvas" rule and the exception for new copy are stated with reasons.port.py: a per-side stroke with a gradient paint is dropped silently (strokesreturns[]).port.py:layout()raisesKeyErrorif a batch is missing, such as a skippedScreens · Statespage. The README does say to pass every batch, so this fails loudly rather than corrupting anything.🧠 claude-sonnet-5-5 · ⏱ 0m 9s · 2 turns · tokens: 37.0k in / 1.1k out (+0 cache-read)
📜 Full session transcript
💬 Verdict: comment — notes only, nothing blocking.
Solid, well-documented tooling port.
extract.js/port.pymirror the provendesign/trace/build.pyconventions (FREE colour hex, gradient fill format, per-side borders as thin rects,getLayersInAreareplace), the PNG round-trip is correct (uncompressed deflate + Adler/CRC are right;unpackdecodes all five row filters in case Figma re-encodes), and the gradientat()inverse-matrix math checks out. Text behavior codes (0 auto-width / 1 auto-height / 2 fixed) are consistent withbuild.py's precedent, and the widen-away-from-anchor trick for Inter's wider metrics is a reasonable workaround for FREE's missing letter spacing. Docs are updated in the same PR and the v2 region's "proposal, not spec" framing is correctly carried intofrontend.md; no changelog line is right for a maintainer-only change.Two approximations beyond those the README lists, worth a line there before the next port run: image fills become a flat
D9D4CCcolour (matching the tracer's no-images rule), and mask nodes are dropped outright (convertreturns[]), so whatever they clipped renders unclipped. Also, small nit:design/figma/README.mdsays "the 2026-10-03 port" while the day is 2026-10-02 — intended?⏱ 6m 47s · 10 steps · $0.0489 · tokens: 168.2k in / 956 out (+169.6k cache-read) · Go: 5h 0% · wk 7% · mo 86%
📜 Full session transcript
@ -0,0 +115,4 @@return []w, h, (t, r, b, l) = n['w'], n['h'], n['sw']sides = (('top', t, [0, 0, w, t]), ('right', r, [w - r, 0, r, h]), ('bottom', b, [0, h - b, w, b]), ('left', l, [0, 0, l, h]))return [{'_t': 'RECT', 'name': 'border-' + s, 'pos': g[:2], 'size': g[2:], 'fill': f[0]['color']} for s, wd, g in sides if wd > 0]Per-side stroke rects carry positions in the node's local space (
[0, 0, w, t],[w - r, 0, r, h], …). That is correct for the FRAME branch ofconvert()(they join the frame's ownlayers, which is frame-local), but in the shape branch andvector()they come back as siblings of the shape in the parent's coordinate space — without adding the shape's ownp/matrix they all land at the parent's origin. Comparedesign/trace/build.pybox_layer, whichrel()s the absolute geo against the frame origin for exactly this reason. If the v2 draft has a RECTANGLE with unequal side weights (a bottom-border row separator) anywhere but at its parent's origin, the border draws at the top-left of its containing frame; if no such node exists the path is dormant — but since the scripts are the committed reproducible artifact and the region is re-ported wholesale next time, the offset (node pos + geo, rotation aside) should be added in the non-frame branch.On the date in the README: 2026-10-03 is the local date (CEST) the port ran, just after midnight; in UTC it was still the 2nd, which is the day both reviews saw. Leaving it as the local date.
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
I found nothing blocking. I read the scripts and docs and did not open the binary
.free.The new commit adds
beside()inport.py. It moves per-side border rects from the shape's own space into the parent's space, usingplace()with the node's offset or matrix. For shapes and vectors, that is the right space for those rects. Frames keep theirs inside the frame, which is also correct.My earlier notes are still open, and all are minor:
layout()raisesKeyErrorif a batch page is missing.🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 25.6k in / 611 out (+12.2k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Re-review: the two points from the last pass are addressed. The per-side stroke rects in
port.pynow go throughbeside(), which re-places them from node-local into the parent's coordinate space via the node's own pos/matrix in both the shape and vector branches (the FRAME branch correctly keeps them frame-local), so a row separator or bordered rect no longer risks landing at its container's origin. The README now documents the two remaining approximations (image fills as a flatD9D4CC, mask layers dropped with what they clipped rendered whole), both noting the draft uses neither. Re-checkedport.pyend to end (PNG unpack with all row filters, gradient inverse-matrix math, curve extrema, text widen/behavior handling, layout and the replace-at-x≥8500 flow) and thedocs/frontend.mdchanges: still sound, and the v2 region's proposal-not-spec framing is correctly carried into the docs. One non-blocking nit: the README still says "the 2026-10-03 port" while the date is 2026-10-02 — likely intended as the actual port day, but worth a glance.⏱ 1m 36s · 5 steps · $0.0094 · tokens: 38.6k in / 831 out (+65.7k cache-read) · Go: 5h 0% · wk 7% · mo 86%
📜 Full session transcript