Skip to content

Let a progress ring be resized, gapped and given a bitmap - #25

Merged
freethinkel merged 3 commits into
mainfrom
fmc(ring-props)
Aug 11, 2026
Merged

Let a progress ring be resized, gapped and given a bitmap#25
freethinkel merged 3 commits into
mainfrom
fmc(ring-props)

Conversation

@freethinkel

Copy link
Copy Markdown
Owner

Closes the resize/replace half of #22, and answers the rounded-corner half with corpus evidence.

What was broken

A ring the editor creates is procedural — 0x5a, no frames — and every sizing path assumes pixels:

  • geometry.svelte reads w/h off the first frame, so a bare ring showed no size fields at all;
  • hasArt() gated the canvas corner handles, so it couldn't be dragged either;
  • nothing offered it a bitmap, so "replace the image in the progress ring" had no image to replace;
  • the arc spec (sweep, stroke, gauge range) was a read-only hint string.

Separately, a ring with art resized its bitmap but not meta.w/h — and that is the circle
drawSector pivots the filled sector around, so the arc tore the moment the ring was resized.

What's here

  • Inspectorsize (bare rings: spec.radius + meta.w/h, growing around its own centre),
    stroke, start/end, and gauge min–max in the source section next to the metric it reads.
  • Gapstart/end are what Can not resize Progress Ring #22's second point asks for: 20 → 340 leaves a 40° notch at
    the top for an icon, and the fill still spans the whole sweep.
  • Canvas — corner drags and arrow keys resize a bare ring through ringResized: one factor on
    radius and stroke together, so the drop lands where the preview's ctx.scale showed it.
  • Ring artringImageAdded gives a bare ring a bitmap; the tag stays 0x5a (the corpus has
    0x5a rings with an image), and meta.w/h take the bitmap's size.
  • Fix — a ring's meta.w/h now scale with its art on resize.
  • Aligned the geometry section's label column while I was in there: x/y, w/h, align and the
    pivot row sized to their text, so every row started its inputs somewhere different.

Rounded caps — not possible, and I checked properly

100 corpus faces, 79 arc widgets. Every one is 0x80/0x81 with exactly two children (struct +
spec) — no style node exists. The spec is a fixed 19/17 bytes with everything decoded but a 3-byte
trailer, and that trailer disagrees between visually identical sibling rings in 8 faces. 17 rings
carry no bitmap (so firmware strokes them); the 4 with a partial sweep show both ends in their baked
preview, at three different trailer values — all flat radial caps. Details in
#22 (comment).

Verification

pnpm check 0 errors, pnpm lint clean, 164 tests pass including three new ones in
tests/editor-ring.browser.test.ts (spec-driven resize, meta.w/h following the art, giving a bare
ring a bitmap). Also driven by hand in a real browser: add ring → drag a corner (200 → 260, stroke
10 → 13) → gap at 20/340 → drop a ring PNG on it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QLHPEnYV5UMkne4WBTCk5s

freethinkel and others added 3 commits August 9, 2026 23:51
A ring created in the editor is procedural (0x5a, no frames), so it fell through
every path that assumes pixels: no w/h in the inspector, no corner handles, and
nothing to click to give it art. Its geometry is the arc spec, so edit that
instead — size (radius + meta.w/h, growing around its own centre), stroke, and
the start/end sweep, which is what leaves a gap for an icon to sit in. The gauge
range moves to the source section, where the metric it reads already lives.

Corner drags and arrow keys route to ringResized for a ring with no art: one
factor, applied to radius and stroke together so the drop lands where the
preview's ctx.scale showed it.

A ring with art had the opposite bug: resizing rescaled the bitmap but left
meta.w/h alone, and that is the circle drawSector pivots the filled sector
around — so the arc tore as soon as the ring was resized. It now follows the
same factors.

Rounded caps stay out: the arc spec is 17/19 fixed bytes with only a 3-byte
trailer undecoded, that trailer disagrees between visually identical sibling
rings, and all four firmware-drawn partial-sweep rings in the corpus bake flat
radial ends. See github.com//issues/22.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLHPEnYV5UMkne4WBTCk5s
Pre-existing on main — the button's attributes sit on one over-long line and
`oxfmt --check` has been failing on it, which is why CI is red here too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLHPEnYV5UMkne4WBTCk5s
Review of the ring work turned up ten defects, most of them the same mistake in
different places: the rule for what a ring's size IS was written out three
times. It now lives once, in edits.ts's scaleRing, and the standalone resize,
the group resize and the inspector's size field all go through it.

The bugs that rule fixes:

* meta.w/h compounded. resizeImageFx scaled it by the factors rescaleFrames
  reports, but those are measured against the PINNED ORIGINAL bitmap, so the
  second resize multiplied an already-scaled meta by a total factor — halving a
  284px ring twice landed its circle on 36 instead of 71, and the sector tore
  around a centre the art no longer had. The meta factor is now taken against
  the asset's current size.
* A circle can't scale per axis. ringResized collapsed the drag's two factors
  with max(), which the corner-drag anchor was never computed for, and which
  froze every shrink (max(0.9, 1) === 1) — arrow-key shrink did nothing at all.
  The canvas now scales a bare ring uniformly, like a shift-drag, and the model
  reads the axis that actually moved.
* Nothing capped the radius, so a drag could cross drawProceduralArc's
  full-bleed threshold, which draws from the screen centre and ignores x/y —
  the ring teleported mid-drag and lost its position. That threshold is now a
  named constant the editor stays below.
* resizeGroupFx never applied any of this, so a ring inside a resized group
  tore exactly the way a standalone one used to, and a bare one didn't resize.
* addRingImageFx left spec.radius at its old value, exporting a diameter the
  widget no longer had, and its failData reached no error store — an
  undecodable file failed silently.
* A resize that rounded to no change still rebuilt the document, eating an undo.
* meta.auto (0x8000, a group's "size me from my content" sentinel) was
  overwritten as if it were a diameter.

Also: the size field fires ringResized instead of rebuilding the layer in the
view, so typing a size and dragging to it now agree (stroke scales either way),
and the ring-art picker uses the shared Button over a hidden input, like the
tree panel, instead of its own copy of the button chrome.

Four regression tests, each checked to fail against the old code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLHPEnYV5UMkne4WBTCk5s
@freethinkel
freethinkel merged commit 71d93a4 into main Aug 11, 2026
2 checks passed
@freethinkel
freethinkel deleted the fmc(ring-props) branch August 11, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant