Skip to content

feat(now): the home as the iPhone's ring - #110

Merged
frahlg merged 2 commits into
mainfrom
web-ios-step2-ring
Oct 25, 2026
Merged

frahlg merged 2 commits into
mainfrom
web-ios-step2-ring

Conversation

@frahlg

@frahlg frahlg commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Step 2 of taking the iOS design to the web app (Fredrik, 25 October 2026: "Steg för steg"). Step 1 was the tabs (#108) and the palette (#109).

What changes

Now draws the house as the iOS app does (Ring / Quiet, chosen 24 October 2026):

  • What the home uses sits in the middle of a ring. The sun, the battery, the grid and the car sit in the corners as text on the ground: name, figure, and what it does ("Resting · 62%").
  • Each node owns the quarter of the ring that faces it. Its arc lights from the middle out, longer with more power (ringFill: a fifth at 0.6 kW, half at 1.8 kW, nine tenths at 6 kW).
  • A wave of light runs along a lit arc: clockwise into the home, the other way out of it. It glows only in dark mode.
  • Light means live. Only readings the box confirms live glow or move. A cache or a quiet source (srcState not live) is a still arc in ink. Under reduced motion the light holds still and a chevron shows the direction.
  • A node whose partner across the middle is missing moves onto the axis, as on the phone.
  • Several devices of one kind fold into one node. A battery folds by its signed sum, and a tap on a folded node opens the kind without a name, as the box's hero did (agg-…).
  • Core's warning or alarm marks the node.
  • Taps open the same sheets as before: the car opens the charger sheet, the rest open their live line.

How

  • src/lib/state/ring.ts ports FlowPlan, RingGeometry, ringFill and quantised from NowFlow.swift/Flow.swift. It also folds FlowReadings into nodes.
  • src/lib/state/ring-motion.ts ports Follow, Light and RingMotion. These are plain values stepped once per frame, so tests can run them.
  • src/views/EnergyRing.svelte draws on one canvas, with conic-gradient stops ten to a wavelength as on iOS. The labels are buttons.
    • The frame loop runs only while an arc is live and flowing, or for 2.4 s after a change.
    • It stops while Now is hidden, scrolled away or the page is in the background.
    • A tick that changes nothing asks for no frame.
  • Now.svelte feeds the ring only while it is the view on screen. It replaces the nodes only when what they show changes.
  • FLOW_IDLE_W (42 W) now lives in flow.ts, with a test holding it equal to the box's value. The first chunk no longer loads the box's hero component for that one number: index chunk 243 102 → 225 985 bytes (86.5 → 81.3 kB gzipped) against main.

The box's own page still uses <ftw-energy-flow>. Moving it to the ring is a separate pair of PRs once this one has been lived with.

Not kept from the old hero

  • The kWh-today lines and "self-powered today" on the bubbles. The Today card under the ring already holds today's totals, and the phone shows none on Now.
  • "observe only" under a battery. The node still does not open controls for it.

Evidence

  • Browser, dev simulator, 375 px:
    • live dark;
    • not live after ftwSim.fault({sourceStates: {'inverter.sungrow': 'stale'}}): every arc still and in ink, labels in ink;
    • light theme;
    • desktop width.
  • A tap on Car opens "EV charger" and a tap on Usage opens "Home". No console errors.
  • Tests (vitest, 1 164 passed):
    • new: ring.test.ts, ring-motion.test.ts, EnergyRing.svelte.test.ts;
    • Now's feed tests now read the ring's page: hidden means no change, an unchanged tick touches nothing, a changed reading does;
    • the EV, live-line and control-proof tests now tap ring nodes.
  • svelte-check passes with 0 errors, and vite build succeeds. check:stats and build:stats need the Cloudflare types, so CI runs them.

After review (29edffa)

An agent review found no fault in signs, colours or the frame loop. It did find these, all fixed:

  • Clipped ring at a wide column (large root font): the label box shrank below the 300 px ring. The ring and its glow now have their own room, and the label box spans the ring so labels keep to its corners. Checked in the browser at 20 px root font.
  • A missing house reading showed a live "0 W". FlowReadings.load is now number | null, and the hub shows "—, no reading".
  • A folded node with one device missing was drawn live. It is now not live. A placeholder opens nothing unless it carries a control mark, as in the box's hero.
  • A NaN reading broke its arc. It now counts as no reading.
  • 1 W jitter restarted the loop, and each changed tick made a probe canvas. Now keys the ring by what it draws (ringKey, quantised figures), and colours are read once per theme.
  • A hidden view's width of 0 drew one frame at 360 px. That width is now ignored.

The review also called some tests vacuous; they now prove their claims:

  • The status snapshot must reach the ring (a control mark and a house use only the snapshot carries), and a stale snapshot must not.
  • The loop runs while a live arc flows and rests on a hidden page or tab.
  • A recording 2D context shows a live arc glowing in its colour and a cache in ink at no more than 0.35 strength.
  • Each of these fails on the mutant it names.
  • The old hero's control-mark tests (tests/control-proof-bubbles.test.ts) now run against the ring (EnergyRing.marks.svelte.test.ts).

Left as follow-ups:

  • withLoadpointEv still subtracts the car from the house load. The phone stopped doing that ("Core owns the house load").
  • On a box without field 10, the car node appears only while charging, so the shape changes.
  • No frame cap for low power.

🤖 Generated with Claude Code

Now draws the house as the iOS app does (Ring / Quiet): what the home uses
in the middle of a ring, each other node as text on the ground in its
corner, and the quarter facing it lit from its middle out, longer with
more power. A wave of light runs along a lit arc toward the home or away
from it. Only readings the box confirms live glow or move; a cache or a
quiet source is a still arc in ink. Reduced motion holds the light still
and draws a chevron.

The numbers are the phone's: ring.ts ports FlowPlan/RingGeometry from
NowFlow.swift, ring-motion.ts ports Follow/Light/RingMotion from
Flow.swift, and EnergyRing.svelte draws it on one canvas with the labels
as buttons. Taps open the same sheets as before. The loop runs only while
an arc is live and flowing, or 2.4 s after a change, and only while Now is
on screen.

FLOW_IDLE_W now lives in flow.ts with a test holding it to the box's
value, so the first chunk no longer loads the box's hero component for
one number: 17 kB smaller, 5 kB gzipped, than main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-25T18:09:03.325809Z 3a0b4e0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ftw-webapp 29edffa Commit Preview URL

Branch Preview URL
Oct 25 2026, 06:38 PM

From the review of #110:

- The ring and its glow get their own room: at a wide column (a large
  root font) the label box shrank below the 300 px ring and clipped it.
  The label box now spans the ring there, so labels keep to its corners.
- A missing house reading is "—, no reading", not a live 0 W
  (FlowReadings.load is now number | null).
- A folded node with one device missing is not live, and a placeholder
  opens nothing unless it carries a control mark, as the box's hero did.
- A reading that is not a number counts as none.
- Now keys the ring by what it draws (ringKey: quantised figures), so a
  1 W wobble repaints nothing. Colours are read once per theme with one
  probe canvas, not on every changed tick.
- A hidden view's width of 0 is ignored, so the first frame back is not
  drawn at 360 px.

Tests: the status snapshot must reach the ring and a stale one must not
(a control mark and a house use only the snapshot carries); the loop
runs while a live arc flows and rests on a hidden page or tab; a
recording 2D context shows a live arc glowing in its colour and a cache
drawn in ink at no more than 0.35. The old hero's control-mark tests
now run against the ring. Each new test fails on the mutant it names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 7dbc5bd into main Oct 25, 2026
3 checks 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