Skip to content

iPhone: the planning style as a scale, the levers under the plan, and what a change moved (plan pass 5) - #61

Merged
frahlg merged 15 commits into
mainfrom
claude/ios-plan-levers
Oct 14, 2026
Merged

frahlg merged 15 commits into
mainfrom
claude/ios-plan-levers

Conversation

@frahlg

@frahlg frahlg commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Plan pass, change 5 of 5: the two levers under the plan, the planning style as a scale with five stops, and what a change moved. It targets main and carries changes 2–4 (#58, #59, #60) unchanged under its own commits, so the stack lands as one squash; main is merged in at 70e43e7.

What changes

The planning style is one control, not a list. Five stops, from very careful at the left to very bold at the right, ← FÖRSIKTIG and DJÄRV → at the ends. A finger moves the thumb from stop to stop with a .selection tick at each; under the scale the style's name and its help in one line follow the thumb: "Balanserad · Planerar med lite marginal." One command goes when the finger lets go, and only if the stop changed: never one per stop. The status line walks the receipt, SKICKAT ▸ BEKRÄFTAT.

It rests until the box has spoken in this session, while a manual mode runs, and while a change is on its way. Before the box has named its style it has no thumb: it never shows a guess. VoiceOver reads it as one adjustable element with the style's whole help, "Planeringsstil, Djärv. Planerar nära prognosen.", and each swipe up or down steps one style and sends it. With Reduce Motion the thumb jumps rather than springs.

Where it sits. At the foot of the plan view, under two plain rows: BIL 80 % till 07:00 vardagar ›, which opens the car view, and LÄGE Plan ⌃⌄, which opens the mode sheet for the manual modes and selling to the grid. On Now, the glass mode button opens the same sheet in place of its menu, sized to its content, with the scale where the style picker was. The manual modes stay a short list of rows there. The root view presents it: a sheet held by the button in the bar did not open a second time after it closed itself.

What a change moved. When the box's next plan comes after a change of the person's own, only the quarters that moved cross-fade, in 0.2 s, on Now's strip and on the dial; the rest hold still. One line says what moved, under the dial's story and under the scale in the sheet, until the box replans of its own accord:

Ny plan 22:16: lagrar 23:00 i stället för 22:45.

Slider or arc

Fredrik asked for a slider or a short arc and for both to be shown. This ships the straight scale; the arc is behind a debug flag, -FTWStyleControl arc, and in the pictures and the second clip below. Fredrik picks after trying both in TestFlight.

The straight scale, because:

  • it reads the way the words do, careful on the left and bold on the right, and the thumb's place says the style before the words do;
  • it fits a list row in the sheet and a row of the plan view at their full width, so the five stops are 70 to 80 pt apart on the iPhone SE, well clear of a finger;
  • the arc in the dial's ticks sits under the dial and looks like a second, smaller dial: two round things that mean different things.

How

  • StyleScale (new): the shared logic and both looks. ModeModel.stop(at:) and fraction(of:) map a place to a stop; Style.brief is the help in one line.
  • PlanModel.expectChange() keeps the plan on screen when a change is sent (ModeModel.willSend), and take() sets PlanModel.move when the next revision comes within two minutes. The box may send the plan before its receipt, so this cannot wait for applied.
  • PlanShare.before and moved; PlanGlance.moved(from:to:atMs:) says what changed; PlanStrip.moved(from:) and PlanDial.moved(from:) list the quarters.
  • MovedFade draws the old drawing over the new on the ground's colour, masked to the moved quarters (QuarterColumns, QuarterWedges), and fades it out. It plays once, as the plan arrives; a view opened later shows the new plan at once.
  • The demo now lets the style move its plan (careful buys in more cheap hours and gives power in fewer), so a change shows.
  • CLAUDE.md: one row in the design references for a control with a few stops.

Two fixes the new sheet needed:

  • Now's mode button used to open a menu. As a sheet held by the button in the bar it did not open a second time, so the root view presents it (HomeSheet.mode).
  • A sheet opened again just after a mode was applied saw that change and closed itself. It now closes only for a change made while it is open.

Evidence

  • swift test on 70e43e7: FTWShared 226 and FTWKit 369 tests green. New: aChangeShowsWhatThePlanMoved (the plan before a change and the one after, also when the box sends the plan before its receipt), aNewPlanSaysWhatItMoved, onlyTheMovedQuartersChange, onlyTheMovedTicksChange.
  • UI tests on the iPhone SE (3rd generation), iOS 26.5. New: testTheStyleScaleSendsOnceWhenTheFingerLetsGo drags from Balanced to Very bold, waits for CONFIRMED, finds the moved line in the sheet, and drags back. It passed in every run. testTheModeMenuChangesTheModeAndTheLineFollowsTheBox now goes through the sheet.
  • testTheModeSheetFitsWaitsInPlaceAndClosesOnceApplied looked at the tick 0.15 s after a tap, but XCTest's wait for the app to settle took a second, past the demo's 0.9 s answer. It raced on change 4 too and is the known flake on the Pro Max. The test now holds the demo's answer for 2.5 s with a debug flag, -FTWDemoResultDelayMs. On 8c0a0c6 and on 70e43e7 (main merged in), NowFitsTests 12 of 12, PlanStripTests 3 of 3 and PlanDialTests 3 of 3. On 70e43e7 the watch app builds too.

Recording

The plan view while the scale drives itself (-FTWStyleScrub YES): to the levers, Balanserad → Mycket djärv with a tick at each stop, let go, SKICKAT ▸ MOTTAGET ▸ BEKRÄFTAT in the status line, back to the dial as the new plan lands, its moved quarters fade, and "Ny plan 22:16: lagrar 23:00 i stället för 22:45." Then to Försiktig and back to Balanserad.

The style scale driving itself

The same as mp4

The arc, the same way: gif · mp4

Both clips are from before 906206e, which fades the old words out before the new fade in: in them the words overlap for a moment as the thumb moves. The same drive on 70e43e7, with the fix: gif · mp4

Pictures

The levers under the plan, dark and light, and at the largest text.

The levers under the plan

Line or arc: under the plan in dark, and in the sheet in light.

The straight scale and the arc

Now's mode button opens the sheet, dark and light.

The mode sheet from Now

🤖 Generated with Claude Code

frahlg and others added 14 commits October 14, 2026 21:06
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So the plan's saving compares honestly and the plan view shows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For pictures of the plan's unpriced stubs and its style line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan on screen when a change leaves is kept beside the box's next
plan, so the strip, the dial and a sentence can show only the quarters
that moved. The style scale steps by fractions of one turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(plan pass 5)

The style is one control with five stops: a tick per stop, one command
when the finger lets go, the receipt in the status line. It sits under
the plan with the car's goal and the mode, and in the mode sheet that
Now's button now opens. After a change, only the quarters that moved
cross-fade on the strip and the dial, and one line says what moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sheet held by the mode button in the bar did not open a second time
after it closed itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opened again just after a mode was applied, it saw that change and
closed itself. The tests now scroll the sheet in short, slow drags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The demo answered after 0.9 s, and XCTest's wait for the app to settle
after the tap took a second, so the look raced the answer. A debug flag,
-FTWDemoResultDelayMs, lets this test hold the answer for 2.5 s.

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

chatgpt-codex-connector Bot commented Oct 14, 2026 •

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 ✅ Completed 2026-10-14T20:33:12.315355Z 906206e 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.

@frahlg
frahlg changed the base branch from claude/ios-plan-known to main October 14, 2026 20:31
# Conflicts:
#	appleApp/FTW/UI/PlanView.swift
#	appleApp/FTWKit/Sources/FTWShared/PlanGlance.swift
#	appleApp/FTWKit/Sources/FTWShared/Resources/en.lproj/Plan.strings
#	appleApp/FTWKit/Sources/FTWShared/Resources/sv.lproj/Plan.strings

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 906206ea5b

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

guard !sending else { return }
clearing?.cancel()
change = Change(ask: ask)
willSend?()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear pending plan attribution when a command fails

Calling willSend before the command is accepted records the current plan as awaiting a user-driven revision, but rejected, expired, or failed commands never clear that expectation. If the box publishes an unrelated plan revision within the next two minutes—for example, a scheduled replan while a command is refused—PlanModel.take attributes it to the failed action and shows a misleading “New plan” explanation and cross-fade. Clear the expectation for non-applied outcomes, while retaining support for plans that genuinely arrive before an applied receipt.

Useful? React with 👍 / 👎.

Comment on lines +550 to +553
for kind in [Kind.charge, .discharge, .car] {
let a = before.first { $0.kind == kind }, b = after.first { $0.kind == kind }
switch (a, b) {
case let (a?, b?) where abs(a.startMs - b.startMs) >= 60_000:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare later runs when describing what moved

Using only the first event of each kind misses changes whenever the earliest charge, discharge, or car window stays fixed but a later window moves, appears, or disappears. In that case the equal first start times fall through without adding a line, so moved can return nil even while the strip and dial detect and fade the changed quarters; this leaves both the plan view and mode sheet without the promised explanation. Compare the per-kind event sequences and report the first differing run instead.

Useful? React with 👍 / 👎.

@frahlg

frahlg commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

UI tests on 8c0a0c6, iPhone SE (3rd generation), iOS 26.5: 18 of 18 green.

  • NowFitsTests 12 of 12, with the new testTheStyleScaleSendsOnceWhenTheFingerLetsGo and testTheModeSheetFitsWaitsInPlaceAndClosesOnceApplied on the slower demo answer.
  • PlanStripTests 3 of 3, PlanDialTests 3 of 3.

Since then main is merged in (70e43e7): #57 and #49. The four files that conflicted (PlanView.swift, PlanGlance.swift, both Plan.strings) take this branch's side. Main's copies equal change 1, and #49 does not touch them. W1.1's guard on the car's goal (g.side >= 200) and change 4's dashed sun both stand in PlanDialFace. swift test on 70e43e7: FTWShared 226 and FTWKit 369 green. The watch build, the app build and the same UI tests on 70e43e7 are running now.

@frahlg

frahlg commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

On 70e43e7 (main merged in): the watch app and the phone app build, swift test is green (FTWShared 226, FTWKit 369), and the plan UI tests pass on the iPhone SE (3rd generation), iOS 26.5: NowFitsTests 12 of 12, PlanStripTests 3 of 3, PlanDialTests 3 of 3. A clip of the scale on this head is linked in the description.

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