Skip to content

Progress line: pick its colour; goal sources stay blocked - #81

Merged
freethinkel merged 2 commits into
mainfrom
issue-61-progress-line-goals
Oct 20, 2026
Merged

freethinkel merged 2 commits into
mainfrom
issue-61-progress-line-goals

Conversation

@freethinkel

@freethinkel freethinkel commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Refs #61

What changed

  • Colour. Selecting a progress line shows a colour picker in the frames section. A pick redraws all 101 frames in that colour (fill at full alpha, track at 20% alpha) and counts as one undo step (progressLineColored → recolorProgressLineFx → committed). The picker reads the current colour back off the 100% frame. cf 5 stores 5/6/5 bits, so the value comes back rounded: #ff5c00 reads #ff5d00.
  • Telling a progress line apart. progressLineOf in factory.ts matches an image widget on battery (0x24) with 101 frames whose frame is bar-shaped (one side at least 4× the other). The shape check is needed because Analog__370__Hemisphere's stock '0%'..'100%' text has the same meta and frame count, and recolouring it would turn the text into bars. This is marked as a ponytail: heuristic.
  • Edge cases. The colour is read from the cached bitmap, because a resize leaves data stale. A rotated line gets no picker: the rotation is baked into its pixels, so a redraw would lay it flat. Picking the colour the line already has does nothing. Frames for a line that was deleted, resized or rotated during the redraw are dropped. The "regenerate from a font" button stays next to the picker.
  • Sources: still battery only. The corpus evidence below doesn't support binding the line to goals.

Corpus evidence (100 stock faces + tests/__fixtures__)

I counted every widget struct by data-source id (meta byte 9) and parent tag:

source where it appears
0x25, 0x26, 0x49, 0x6c, 0x6f, 0x46, 0x82 (goal %/slot ids) 0x80/0x81 rings only (plus 0x83/0x89 Pro 3 widgets with one frame)
0x6a 0x81 rings and 0x60 digit strips
0x24 battery 0x30 picker with 101 frames (Hemisphere, text), 0x38 with 7/20/26 frames, digit strips, rings
0x3c heart rate (gauge) 0x38 with 11 frames
  • No goal-percent id drives a multi-frame 0x30 picker or a 0x38 gauge anywhere in the corpus. The only proven consumer is the arc ring, which takes min/max from its 0x5a/0x5b spec. Pointing a 101-frame picker at 0x26 would rely on two unverified assumptions: that the firmware hands a picker the same 0..100 the ring gets, and that it doesn't hand it raw steps (which value % 101 would turn into noise).
  • Fewer than 101 frames: the corpus does have bucketed gauges. They use a separate widget tag, 0x38, a struct with no bind/fmt children: Analog__336__Energetic (battery, 7 frames), Multifunction__363__Vintage (battery, 20 frames, accent), Digital__285__Widgets (battery, 26 frames) and Multifunction__348__Tumbler (0x3c, 11 frames). The editor keeps 0x38 as a raw layer. A static file can't show how the firmware maps 0..100 onto N frames (floor(v·N/101), floor(v·(N−1)/100), rounding…). Each face's embedded preview shows only one designer-chosen state, so that needs a watch. 0x30 with fewer than 101 frames on battery doesn't occur, so frames[value % count] is the only rule the editor can claim for 0x30.

What would unblock goals:

  1. A stock or community face that binds a goal id to a 0x30/0x38 picker.
  2. A test on a watch: a 0x38 (or 0x30) widget on 0x26 with numbered frames, checked at known step counts.

Minute/second/hour-driven fills are not touched (#63).

Test

tests/doc-factory.test.ts: a vertical #ff5c00 line is found by progressLineOf with vertical: true, and its colour reads back as #ff5d00. Hemisphere's 101-frame text is not taken for a progress line.

pnpm format, pnpm lint (only existing warnings), pnpm check (0 errors) and pnpm test. On this machine (load average ~256) a few browser tests and wf-roundtrip timed out in the full run. All of them pass when run on their own (wf-roundtrip with --testTimeout=300000). None of them touch code changed here.

Draft reply to the reporter

Thanks for the request! The progress line has shipped (Add → Progress line / Vertical progress line). With this update you can also set its colour from the inspector.

For now it can only show battery. The watch's file format has no native bar widget, so the line is a set of 101 pre-drawn frames, and the watch picks one by value. I went through all 100 stock faces from Nothing: every one of them displays the steps/calories/activity goals with the progress ring and never with a frame set. So I can't yet promise that a bar bound to a goal would show the right value on the watch. Battery is the one value a stock face proves works this way. If you'd like a goal bar today, a Progress Ring on that goal is what the watch is known to draw correctly. If someone with a watch is willing to try a test face, I can build one that checks how goals feed a frame set, and add goal bars once it's confirmed.

🤖 Generated with Claude Code

https://claude.ai/code/session_012tYrCAS1CWaaNaC12cP5X6

freethinkel and others added 2 commits October 20, 2026 13:22
The inspector shows a colour picker for a selected progress line and
redraws its 101 frames in the picked colour (one undo step). The colour
is read back off the full frame. Hemisphere's stock 101-frame text is
told apart by the bar shape.

The stock corpus gives no evidence for goal sources: every goal-percent
id drives a 0x80/0x81 ring only, never a 0x30 picker or a 0x38
bucketed gauge, so the line stays on battery.

Refs #61

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tYrCAS1CWaaNaC12cP5X6
- Read the colour through the cache bitmap: a resize leaves `data`
  stale, and decoding it at the new size threw on every edit. Memoised
  per bitmap, so a drag no longer decodes a frame per tick.
- A turned line is not offered for recolouring: the turn is baked into
  its pixels and a redraw would lay it flat.
- The commit re-checks the line: frames drawn for a line that was
  deleted, resized or turned meanwhile are dropped.
- Picking the colour the line already has (after cf 5 rounding) is a
  no-op instead of an undo step and 101 new assets.
- The "regenerate from a font" button is back next to the picker.

Refs #61

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012tYrCAS1CWaaNaC12cP5X6
@freethinkel
freethinkel merged commit 9eb1ac7 into main Oct 20, 2026
1 check passed
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