Port the UI language v2 draft onto the canvas #237

Merged
Grandiras merged 2 commits from design/ui-language-v2 into main 2026-10-02 22:38:05 +00:00
Owner

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):

  • Desktop: every screen in light and dark, including board, board + inspector, Home, calendar month and timeline, search, the four settings tabs, Members, Profile, sign in, request access and request status
  • States: loading, reconnecting, no columns, deleted board, not found and access denied, each in light and dark
  • Phone: Home, board, holding a card, both card sheets, account sheet, calendar, search and board settings (with Columns and Access), in light and dark
  • Overview, the five foundation sheets and one sheet per component page

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_figma has no network and its return value lands in the agent's context. So extract.js packs the frame tree into a PNG image fill, download_assets hands back its original bytes, and port.py decodes 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.md describes 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 reaches LocaleService only when built.

Checked:

  • Exported frames from Lunacy and compared them with Figma: desktop, phone, icons and component sheets match closely.
  • The committed extract.js and port.py reproduce the port's output exactly.
  • The saved .free keeps every original entry and all 49 old top-level layers.
  • tools/verify.sh passes (584 tests).

The .free grows from 0.8 MB to 1.7 MB.

🤖 Generated with Claude Code

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): - Desktop: every screen in light and dark, including board, board + inspector, Home, calendar month and timeline, search, the four settings tabs, Members, Profile, sign in, request access and request status - States: loading, reconnecting, no columns, deleted board, not found and access denied, each in light and dark - Phone: Home, board, holding a card, both card sheets, account sheet, calendar, search and board settings (with Columns and Access), in light and dark - Overview, the five foundation sheets and one sheet per component page 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_figma` has no network and its return value lands in the agent's context. So `extract.js` packs the frame tree into a PNG image fill, `download_assets` hands back its original bytes, and `port.py` decodes 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.md` describes 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 reaches `LocaleService` only when built. **Checked:** - Exported frames from Lunacy and compared them with Figma: desktop, phone, icons and component sheets match closely. - The committed `extract.js` and `port.py` reproduce the port's output exactly. - The saved `.free` keeps every original entry and all 49 old top-level layers. - `tools/verify.sh` passes (584 tests). The `.free` grows from 0.8 MB to 1.7 MB. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Port the UI language v2 draft onto the canvas
All checks were successful
CI / build (pull_request) Successful in 46s
b39a90e8cc
The Figma draft of the proposed redesign now sits on design/Ponente.free,
right of the traced frames (from x 9000): 79 frames, every desktop, state
and phone screen in light and dark, the foundations, and one sheet per
component page. The traced frames are untouched.

design/figma makes the port a rerun. extract.js walks frames inside Figma
with each colour resolved for its own mode and packs the tree into a PNG
image fill, since use_figma has no network; port.py decodes it, converts
it to FREE (editable text, paths, shadows, blurs) and posts it to Lunacy.

docs/frontend.md says what the region is: a proposal whose behaviour
changes wait on sign-off, edited in Figma and ported, never edited here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-237/run-2026-10-02T22-26-45-231Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/665); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-237/run-665.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-02 22:27:21 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

This PR adds the v2 canvas region, the extractor and port scripts, a README, and the docs/frontend.md update. I did not open the binary .free, so I judged the scripts and docs by reading them. I found nothing blocking.

  • Docs: design/figma is added to the Covers: line, so DocumentationTests should still pass. The "edited in Figma, never on the canvas" rule and the exception for new copy are stated with reasons.
  • Scripts: the extractor and port code reads as sound. The PNG pack/unpack round-trips, including all PNG row filters. Colour resolution per frame and the matrix handling look right.
  • Minor, port.py: a per-side stroke with a gradient paint is dropped silently (strokes returns []).
  • Minor, port.py: layout() raises KeyError if a batch is missing, such as a skipped Screens · States page. The README does say to pass every batch, so this fails loudly rather than corrupting anything.
  • Minor, README: it dates the port "2026-10-03", but today is 2026-10-02. It is probably a typo.

🧠 claude-sonnet-5-5 · ⏱ 0m 9s · 2 turns · tokens: 37.0k in / 1.1k out (+0 cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. This PR adds the v2 canvas region, the extractor and port scripts, a README, and the `docs/frontend.md` update. I did not open the binary `.free`, so I judged the scripts and docs by reading them. I found nothing blocking. - **Docs:** `design/figma` is added to the `Covers:` line, so `DocumentationTests` should still pass. The "edited in Figma, never on the canvas" rule and the exception for new copy are stated with reasons. - **Scripts:** the extractor and port code reads as sound. The PNG pack/unpack round-trips, including all PNG row filters. Colour resolution per frame and the matrix handling look right. - **Minor, `port.py`:** a per-side stroke with a gradient paint is dropped silently (`strokes` returns `[]`). - **Minor, `port.py`:** `layout()` raises `KeyError` if a batch is missing, such as a skipped `Screens · States` page. The README does say to pass every batch, so this fails loudly rather than corrupting anything. - **Minor, README:** it dates the port "2026-10-03", but today is 2026-10-02. It is probably a typo. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 9s · 2 turns · tokens: 37.0k in / 1.1k out (+0 cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-237/run-665.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:b39a90e8cc1bc52ae3902e96ac63c131cc1edeb3 -->
opencode-reviewer left a comment

💬 Verdict: comment — notes only, nothing blocking.

Solid, well-documented tooling port. extract.js/port.py mirror the proven design/trace/build.py conventions (FREE colour hex, gradient fill format, per-side borders as thin rects, getLayersInArea replace), the PNG round-trip is correct (uncompressed deflate + Adler/CRC are right; unpack decodes all five row filters in case Figma re-encodes), and the gradient at() inverse-matrix math checks out. Text behavior codes (0 auto-width / 1 auto-height / 2 fixed) are consistent with build.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 into frontend.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 D9D4CC colour (matching the tracer's no-images rule), and mask nodes are dropped outright (convert returns []), so whatever they clipped renders unclipped. Also, small nit: design/figma/README.md says "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

💬 **Verdict: comment** — notes only, nothing blocking. Solid, well-documented tooling port. `extract.js`/`port.py` mirror the proven `design/trace/build.py` conventions (FREE colour hex, gradient fill format, per-side borders as thin rects, `getLayersInArea` replace), the PNG round-trip is correct (uncompressed deflate + Adler/CRC are right; `unpack` decodes all five row filters in case Figma re-encodes), and the gradient `at()` inverse-matrix math checks out. Text behavior codes (0 auto-width / 1 auto-height / 2 fixed) are consistent with `build.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 into `frontend.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 `D9D4CC` colour (matching the tracer's no-images rule), and mask nodes are dropped outright (`convert` returns `[]`), so whatever they clipped renders unclipped. Also, small nit: `design/figma/README.md` says "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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-237/run-2026-10-02T22-26-45-231Z.md) <!-- opencode-reviewed-head:b39a90e8cc1bc52ae3902e96ac63c131cc1edeb3 --> <!-- opencode-verdict:comment -->
@ -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]
Collaborator

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 of convert() (they join the frame's own layers, which is frame-local), but in the shape branch and vector() they come back as siblings of the shape in the parent's coordinate space — without adding the shape's own p/matrix they all land at the parent's origin. Compare design/trace/build.py box_layer, which rel()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.

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 of `convert()` (they join the frame's own `layers`, which is frame-local), but in the shape branch and `vector()` they come back as siblings of the shape in the **parent's** coordinate space — without adding the shape's own `p`/matrix they all land at the parent's origin. Compare `design/trace/build.py` `box_layer`, which `rel()`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.
Author
Owner

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.

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.
Place a shape's side borders at the shape, and list the last approximations
All checks were successful
CI / build (pull_request) Successful in 41s
c9bb58e526
A per-side stroke comes back from strokes() in its node's own space. Inside
a frame that is right, but beside a shape or path it landed at the parent's
origin. beside() moves it through the shape's offset or matrix. The draft
has no such shape, so the ported frames are unchanged.

The README now says image fills become a flat colour and masks are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-237/run-2026-10-02T22-35-47-823Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 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.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/666); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-237/run-666.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ 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() in port.py. It moves per-side border rects from the shape's own space into the parent's space, using place() 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:

  • A per-side stroke with a gradient paint is silently dropped.
  • layout() raises KeyError if a batch page is missing.
  • The README still dates the port "2026-10-03", while today is 2026-10-02. It is probably a typo.

🧠 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. I found nothing blocking. I read the scripts and docs and did not open the binary `.free`. The new commit adds `beside()` in `port.py`. It moves per-side border rects from the shape's own space into the parent's space, using `place()` 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: - A per-side stroke with a gradient paint is silently dropped. - `layout()` raises `KeyError` if a batch page is missing. - The README still dates the port "2026-10-03", while today is 2026-10-02. It is probably a typo. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 25.6k in / 611 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-237/run-666.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:c9bb58e526f74c41b99f356acef098c22b286ab9 -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Re-review: the two points from the last pass are addressed. The per-side stroke rects in port.py now go through beside(), 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 flat D9D4CC, mask layers dropped with what they clipped rendered whole), both noting the draft uses neither. Re-checked port.py end 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 the docs/frontend.md changes: 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

✅ **Verdict: approve** — no blocking issues found. Re-review: the two points from the last pass are addressed. The per-side stroke rects in `port.py` now go through `beside()`, 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 flat `D9D4CC`, mask layers dropped with what they clipped rendered whole), both noting the draft uses neither. Re-checked `port.py` end 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 the `docs/frontend.md` changes: 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](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-237/run-2026-10-02T22-35-47-823Z.md) <!-- opencode-reviewed-head:c9bb58e526f74c41b99f356acef098c22b286ab9 --> <!-- opencode-verdict:approve -->
Grandiras deleted branch design/ui-language-v2 2026-10-02 22:38:05 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!237
No description provided.