Skip to content

Release 1.1 (gated first public build) + availability decoupling (FR-700-23, FR-710-24) - #49

Merged
kipp-ing merged 18 commits into
mainfrom
impl-fr-700-23-710-24
Jul 29, 2026
Merged

kipp-ing merged 18 commits into
mainfrom
impl-fr-700-23-710-24

Conversation

@kipp-ing

@kipp-ing kipp-ing commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Implements the two FRs from the 2026-07-26 spec amendment (#46): Home Assistant no longer shows the frame offline whenever an in-app modal covers the slideshow, and a new free-tier frame_status diagnostic sensor carries the UI-visibility signal instead.

What changed

FR-700-23 / SC-700-15 — availability is app-level connectivity only:

  • Teardown decisions in SlideshowView/TVSlideshowView onDisappear now go through SlideshowSurfaceLifecycle.decision(for:isModalPresented:), keyed on the presenting layer's modal state (never lifecycle inference). A modal cover keeps the broker session AND the keep-awake hold (the FR-400-01 half of the same root cause); genuine exits and backgrounding tear down exactly as before (FR-400-02/03).
  • All six covering surfaces count: SlideshowView's four sheets plus the RootView-level incoming-link and album-reselect sheets (externallyCovered), per the FR's "any other sheet/full-screen surface" clause — the latter two were caught by an adversarial review pass.
  • iOS coordinator ownership moved to an identity-scoped HACoordinatorLease (@State): its deinit stops the coordinator when a view generation is destroyed under an open sheet, so a live transport can never leak and fight its successor for the frame's MQTT client id.
  • tvOS: while the settings fullScreenCover removes TVSlideshowView, TVRootView watches scenePhase so backgrounding under the cover still releases keep-awake and stops HA.

FR-710-24 / SC-710-08 — frame_status (running|inactive):

  • New HAEntity.frameStatus, sensor component, entity_category: diagnostic, retained, availability-bound, no command topic; free-tier telemetry (published in telemetry-only mode, FR-1100-03a).
  • Driven exclusively by HAControlCoordinator.setSurfaceVisible(_:) — the explicit signal from the presenting layer. Idempotent; records while disconnected; announce/reconnect republish the current visibility (a reconnect under a modal announces inactive). Orthogonal to phase/playback/availability by construction and by test.

Process

Two independent implementation agents produced competing entries in isolated worktrees (TDD, red-first); the winning entry closed two lifecycle gaps the other missed. A separate adversarial verification pass then attacked ten hypotheses against the merged diff: it confirmed the RootView-sheet gap (fixed here), refuted the rest, and identified two pre-existing issues, filed as #47 and #48.

Verification

  • Host: all 12 packages green; HAControlKit 141 tests (+17 new, @covers-tagged FR-700-23/SC-700-15/FR-710-24/SC-710-08; coverage.py --check clean)
  • Full sim suite (iPad Pro 11" M4, iOS 26.0, -test-timeouts-enabled YES): 165 passed / 0 failed / 61 skipped — all skips are the opt-in German-screenshot-sweep (56), ASC-screenshot (1), device-rig (3), live-smoke (1) categories
  • tvOS app target builds clean
  • Device-day item: observe the fix live on the Framepad (HA stays online while Settings is open; frame_status flips) — the defect's onDisappear trigger only reproduces on iOS 17 hardware

Spec bookkeeping: Phase 8 (T042–T046) added to specs/710-ha-full-control/tasks.md and ticked; data-model.md gained the entity line.

https://claude.ai/code/session_01XWbnBdWdcjnCH5smD6X1Ai


Added 2026-07-29 — release 1.1, the gated first public build

The branch now also carries everything needed to ship the purchase gate as the first version the
public ever sees (FR-1100-17). Version bumped to 1.1 (9), all ten target configurations.

App Store Connect — the store side is done

All four in-app purchases now exist and are READY_TO_SUBMIT: localized (en-US + de-DE),
priced, Family Sharing on the unlock and off the tips, review screenshots attached, available in
all 175 territories. The App Review notes were stale too — still "Photo Frame for Immich", no
mention of the IAPs — and now carry the current text from docs/app-store-listing.md.

Creating them surfaced a real bug: ASC rejects a product id containing a hyphen, and every id
was derived from the bundle id ing.kipp.Immich-Slideshow. They are now ing.kipp.ownframe.*.
Nothing was migrated — the hyphenated ids never existed in ASC, so no purchase can reference
them. A test now pins the character set, not just the literals.

Two things the repo had wrong about the release

  • FR-1100-17 is held by a switch, not a state. v1.0 build 8 reads READY_FOR_SALE /
    READY_FOR_DISTRIBUTION, review completed 2026-07-14, releaseType: AFTER_APPROVAL. What
    keeps it off the store is app availability — 175/175 territories false since 2026-07-22.
    Turning availability on before 1.1 ships would publish the ungated build instantly.
  • EU trader status is missing: 27 territories carry TRADER_STATUS_NOT_PROVIDED, so the app
    cannot be sold in the EU at all, Germany included. Web-UI only, multi-day verification.
    (Started 2026-07-29.)

Still open for Jan: between approval (~07-15) and the pull (07-22) the app may have been publicly
downloadable. App Analytics will say; if anyone installed, FR-1100-13 binds for them.

Fixes

  • Storage copy says "this iPad" on iPhone #53 — copy no longer names the device it runs on ("this iPad" on an iPhone). Guarded
    catalogue-wide rather than per-string.
  • Unlock screen title truncates to "The Supporte…" on narrow phones #52 — the unlock title truncated to "The Supporte…" at 375 pt, on the screen where the
    purchase decision is made. Verified in German, the worst case.
  • StoreKitClientTests fail on iOS 26.5 simulators (SKTestSession serves 0 products) — release-gating suite unverifiable there #54 — on iOS 26.4/26.5 simulators SKTestSession never binds: storefront reads back
    empty even right after being assigned. Not timing, not the host app racing StoreKit, not the
    fixture. setUp now skips on exactly that signal and still fails hard on every other cause,
    with an assertion pinning the discriminator so the skip cannot silently become unsafe.
    26.0 is unaffected (7/7) — the defect is per-runtime-build, not the 26 line.
  • App icon — the new screen artwork could not build at all: Icon Composer wrote the file
    under its collision-mangled name while icon.json referred to the plain one, and actool failed
    the whole app target with a misleading attempt to insert nil object. Renamed to
    Image 13.png; verified composed in the iPadOS 26 dock.

Gate (re-run for these changes)

  • 12 host packages green (882 tests).
  • Full simulator suite, iPad Pro 11" M4 / iOS 26.0: 172 passed, 1 failed, 61 skipped. The failure
    was a SpringBoard launch crash in WelcomeICloudUITests; it passes in isolation.
  • StoreKitClientTests 7/7 in that run and again on 18.6.

docs/testing.md now records the destination trap that nearly hid this: XcodeBuildMCP's
simulatorName re-resolves and overrides a pinned simulatorId when several runtimes share a
device name. Confirm the runtime from the result bundle, not the selector.

kipp-ing added 17 commits July 26, 2026 17:51
…710-24)

Phase 8 covers both new requirements — availability decoupled from in-app
modal presentation (also un-breaking the FR-400-01 idle-timer regression)
and the frame_status diagnostic sensor — since spec 700 has no tasks.md.

Claude-Session: https://claude.ai/code/session_01XWbnBdWdcjnCH5smD6X1Ai
…nsor (FR-700-23, FR-710-24)

Presenting any in-app modal over the slideshow (Settings, album browser,
sources, connection-error editor, and the host-level incoming-link and
album-reselect sheets) no longer publishes HA offline, disconnects the
broker, or re-arms the idle timer: the teardown decision now reads the
presenting layer's modal state (SlideshowSurfaceLifecycle) instead of
being inferred from onDisappear, and the coordinator is owned by an
identity-scoped HACoordinatorLease whose deinit backstops teardown when
a generation dies under an open sheet. Backgrounding and genuine exits
tear down exactly as before (FR-400-02/03); on tvOS the presenting layer
watches scenePhase while the settings cover removes the slideshow view.

New free-tier diagnostic sensor frame_status (running|inactive), driven
by an explicit UI-visibility signal (HAControlCoordinator
.setSurfaceVisible): retained, availability-bound, no command topic,
published in telemetry-only mode, orthogonal to phase/playback.
Reconnects and pre-start seeding announce the current visibility.

Verified: 12 packages green on the host (HAControlKit 141, +17 new
tests, all @covers-tagged); full sim suite 165/0/61 (all skips are the
opt-in screenshot/device-rig/live-smoke categories); tvOS target builds.
An adversarial review pass confirmed one gap (the two RootView sheets,
fixed here) and filed #47/#48 for two pre-existing follow-ups.

Claude-Session: https://claude.ai/code/session_01XWbnBdWdcjnCH5smD6X1Ai
First session against iOS 27 on real hardware. Records:

- Xcode 26.6 / SDK 26.5 drives an iOS 27 device fully (build, install,
  console launch, XCUITest); only LLDB attach needs the Xcode 27 beta.
- Submission is unaffected — the 2026-04-28 mandate wants the iOS 26 SDK,
  which 26.5 already satisfies. Do not hold the gated release for iOS 27.
- StoreKitClientTests 7/7 green on 27.0; app runs without crashing.
- 7 UI failures, 1 of them pre-existing. The other 6 were controlled for
  screen geometry (new iPhone 13 mini sim on 26.5 — all 6 pass) and for
  timing (deterministic across two device runs).
- Open confound recorded: all 26.5 baselines are simulators, all 27.0 data
  is real hardware, so sim-vs-device is not yet separated from the OS.
- Trap: device test-runner env vars need the TEST_RUNNER_ prefix, else the
  test silently skips.

Also documents the full paired-device table and notes that Framepad caps at
iOS 17 forever, so iOS 27 is a customer risk rather than a rig risk.

Refs #50, #51

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
Corrections and research from re-examining the two findings of 2026-07-27.

#51 — the spec was right, the task was not. FR-1000-06 mandates KVS sync;
T005 listed "entitlements (iCloud KVS + CloudKit private DB)" and was marked
done, but it was never implemented. OwnFrame.entitlements carries only the app
group and OwnFrameTV has no CODE_SIGN_ENTITLEMENTS at all (the issue caught
only the iOS half). Consequence: NSUbiquitousKeyValueStore degrades to a silent
local no-op, so FR-1000-06 is inert on every real device and has only ever run
against injected fakes. Annotated T005, split the work out as T026 (KVS first,
CloudKit separately), and recorded the researched KVS facts in research.md —
entitlement key name, the silent-no-op behavior, signing/profile match, the
same-bundle-id shared store, and the 1 MB / 1024 keys / 64-byte-key limits.

Also corrected docs/privacy-policy.md, which described the Apple TV iCloud
sync as a working feature. It is a public document and the app cannot do this;
the section now states plainly that it is not available yet.

#50 — iOS 27 demoted from "leading hypothesis" to unlikely. The change that
would produce the symptom (Liquid Glass bar minimization reflowing the safe
area on scroll) is gated on linking the iOS 27 SDK, and we build against
SDK 26.5. The iOS 27 UIKit changes that apply regardless of SDK cannot move
form fields.

Tested the software-keyboard hypothesis (sim has a hardware keyboard attached,
FramePhone does not) and refuted it: all six tests pass 6/6 on a fresh iPhone
13 mini / 26.5 sim, and a throwaway probe showed the software keyboard appears
in headless xcodebuild runs with ConnectHardwareKeyboard unset AND with it
set to 1 — so there is no keyboard delta between sim and device.

Two traps recorded while establishing that: PlistBuddy writes to
com.apple.iphonesimulator.plist are silently discarded by cfprefsd (use
defaults -dict-add), and the pref does not affect software-keyboard
presentation in headless runs at all, so a control built on it is worthless
without a probe.

Leading hypothesis is now device-vs-simulator; the recorded failure mode is in
any case fully explained by test fragility alone (two of the six never type —
they are pure fixed-swipe sweeps). Next control is the suite on a real iOS
26.5 device. All external claims carry source links.

Refs #50, #51

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
Ran the six failing tests on "jk in da house" (iPhone 16 Pro, iOS 26.5.2,
97DCFF2E-012F-57E8-914F-CC15B7924FB7): 6 executed, 0 failures, 109 s, full
build from clean DerivedData. Real hardware on 26.5 behaves like the simulator
on 26.5, so device-vs-simulator is refuted.

With geometry controlled by the 13 mini simulator and runtime controlled by
this run, iOS 27 is the only factor present in every failing run and absent
from every passing one. This reverses the demotion committed in 25faa70.

That earlier inference over-generalized: the SDK-gating fact is correct and
still stands, but it rules out bar minimization as the *mechanism*, not iOS 27
as a whole. Something in iOS 27 does change layout for a binary linked against
SDK 26.5; identifying it is now the open question.

Strict single-variable isolation (13 mini hardware on 26.5) is no longer
obtainable — FramePhone is on 27.0 and downgrading is impractical — but no
other variable survives.

The question that actually matters is still open: the tests fail because fixed
swipe counts cannot adapt, while a human scrolling by hand adapts trivially, so
users on 27.0 may see a perfectly usable app. Next step recorded: read the
failure-time screenshots from the 27.0 run before assuming product breakage.

Refs #50

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
…own write

Hardens the XCUITest scroll harness after the iOS 27 investigation (#50), and
fixes a pre-existing bug the work uncovered.

New OwnFrameUITests/ScrollHarness.swift replaces the fixed-swipe helper that
had been copy-pasted into six files. Two defects in the old shape:

- a default swipeUp() is an inertial flick whose coast depends on system scroll
  deceleration, which is not stable across OS releases. Steps are now
  low-velocity swipes: short travel, little coasting. It stays a swipe on
  purpose — press(forDuration:thenDragTo:) dwells on whatever is under the
  start point and a SwiftUI Toggle tracks that touch.
- the loop counted swipes and checked the target only at the top of each
  iteration, so it could neither stop on arrival nor tell "not there yet" from
  "scrolled past it". Every step now re-checks, and the end of the content is
  detected by fingerprint. End-detection requires TWO consecutive unchanged
  reads: one also happens mid-animation, and scrolling before a rotation
  settled was read as "end of content" (caught by SettingsUITests landscape).

Also prefers hittability over existence — "present but not hittable" was the
literal message in four of the six failures — and the locked-row sweep now
records "MQTT seen at any step" instead of "present at the end", which is what
made it depend on where the sweep happened to stop.

Separately: --uitest-reset-publish-options reset inside
HAPublishOptionsStoreFactory.make(), which is called by the app entry, by the
HA coordinator on every broker start, and by SlideshowSettingsView.init on
every SwiftUI re-initialisation. A reset launch therefore wiped the preference
moments after the user toggled it, and the next launch read the default back.
testImagePublishTogglePersistsAcrossRelaunch was green only when no re-render
landed in that window. The reset belongs to the launch, so it is now a static
let that runs once per process. Same for FrameNameStoreFactory. Both stores
expose their key so the seam clears the key rather than the persistent domain.

Measured: FramePhone/27.0 goes 0/6 -> 4/6. Full iOS suite on a 26.5 sim is
156 passed / 61 skipped, with SlideshowChromeUITests failing as it already did
on main and 7 StoreKitClientTests failing on that simulator for reasons under
separate triage (nothing here touches StoreKit).

The two remaining 27.0 failures are deliberately NOT worked around — see
docs/testing.md. Both look like genuine iOS 27 behaviour and each needs one
manual check on the device to classify.

Refs #50

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
… escalate on stall

Follow-up to 47b6630, fixing three ways that change reached further than it
needed to. Each was caught by a test that had been green.

1. Contract. scrollToElement was upgraded from "exists" to "hittable", which is
   a stronger promise than any caller asked for. It made
   SettingsUITests/testBottomSettingsSectionReachableInBothOrientations fail:
   landscape cannot always bring the MQTT row to a hittable position, and the
   test only ever asserted reachability. The generic helpers go back to
   scrollUntilExists; hittability stays where it belongs, at the tap sites,
   which is where it fixed the iOS 27 failures in the first place. The
   substantive improvement — convergence instead of a fixed swipe budget — is
   unaffected.

2. Gesture target. XCUIApplication's own frame can be reported in a rotated
   coordinate space, so app.swipeUp() travels along the wrong axis in landscape
   and the content does not move. Gestures now go to the scrollable container
   (collection view / table / scroll view), whose frame is orientation-correct.

3. Stall handling. A .slow flick can be too short to shift a shallow landscape
   viewport, and the harness read "did not move" as "end of content" — stopping
   one section above MQTT. A stalled step now escalates to .default velocity
   before concluding the end, so fine-grained stepping is kept without the
   silent truncation this file exists to remove. Replaces the earlier
   two-consecutive-stalls counter, which escalation subsumes.

Also: the album card is now matched with a typed .buttons query and tapped with
a brief press rather than a zero-duration tap. Jan confirmed manually that a
finger navigates on 27.0, so the remaining failure there is XCUITest synthesis,
not the app — attempt recorded, still unverified on device.

Measured: 35/35 across every migrated file on the 26.5 sim (31 in the broad set
plus SettingsUITests 4/4). The FramePhone/27.0 re-run is still outstanding — the
device locked and needs a physical unlock to recover.

Refs #50

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
…in recorded as expected failure

Final re-run of the six iOS 27 failures on FramePhone (27.0), against the
hardened harness: 6 executed, 0 failures, 120.6 s, ** TEST SUCCEEDED **.

Five were genuine harness fixes — both BrokerSetupUITests, both
PurchaseGateUITests, and SourceOnboardingUITests…InLandscape. The onboarding
one was fixed by 986a21c's container-targeted gesture: app.swipeUp() travels
along the wrong axis when XCUIApplication's frame is reported rotated, which is
why the failure frame after 60 scroll steps had been byte-identical to the
first. So the wrong-axis alternative recorded for it was the right one, and the
manual device check it was waiting on is no longer needed.

The sixth is not a fix and is not pretended to be one. On 27.0 no synthesized
tap activates the album card — element tap, coordinate tap and a brief press
were all tried — while a finger navigates (Jan confirmed on device). That tap
is now wrapped in XCTExpectFailure(strict: true), gated at runtime on
majorVersion >= 27 because the app links the 26.5 SDK and has no iOS 27 symbol
to compile against. The case still guards 17–26 unchanged, still runs its
remaining assertions on 27, and fails loudly if the drill-in ever starts
working — which is the signal to delete the block.

No user-visible defect came out of the six. docs/testing.md carries the
corrected account (five/one, not four/two) and device-testing.md no longer
advertises the confound as open.

Refs #50

Claude-Session: https://claude.ai/code/session_01JKeUmQnB8bL72HGXSpubz5
App Store Connect rejects a product id containing anything outside
alphanumerics, underscores and periods. Every id in ProductCatalog was
derived from the bundle id `ing.kipp.Immich-Slideshow`, whose hyphen is
legal for a bundle id and illegal here — creation 409s with
ENTITY_ERROR.ATTRIBUTE.INVALID. Found on 2026-07-29 when the four IAPs
were created for the first time.

Ids are now `ing.kipp.ownframe.*` (Jan's call; matches the app name and
cannot hit the hyphen rule again). Nothing was migrated: the hyphenated
ids never existed in ASC, so no purchase can reference them.

The four products now exist in ASC with these ids, en-US + de-DE
localizations, Family Sharing on the unlock and off the tips, and prices.

A new test pins the character set, not just the literals, so the bundle
id and the product ids can never be conflated again.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
The Storage footer told an iPhone user their photos are kept on "this
iPad", and the iCloud-album onboarding row said the same. Both now say
"this device" in English and "diesem Gerät" in German.

The guard is a catalogue-wide test rather than a fixture for the one
reported string: any key or translation naming iPad/iPhone/Apple TV
fails it. UnlockScreenView's deliberate "iPad, iPhone, and Apple TV"
enumeration lives in PurchaseKit's own catalogue, so it is out of scope
by construction.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
"The Supporter Unlock" rendered as "The Supporte…" at 375 pt — the
product's own name cut off on the screen where the purchase decision is
made. The close button shares the header HStack, so a one-line
.largeTitle had nowhere to go.

It now wraps to two lines and only shrinks as a backstop. Verified on
the reported geometry (iPhone 13 mini, 26.5) in German, which is the
worst case: "Die Supporter-Freischaltung" wraps cleanly and renders in
full. PurchaseGateUITests 7/7 green on the same simulator.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
…ect (#54)

On iOS 26.x simulators SKTestSession never binds: `storefront` reads back
empty even right after assignment, and every product query returns 0.
Three hypotheses tested and refuted — not timing (5 s of polling changes
nothing), not the host app touching StoreKit before setUp, not the
fixture or the ids (the same bundle is 7/7 on 18.6). It is the runtime.

setUp now skips on that exact signal (no products AND empty storefront)
and still fails hard on every other cause, so a real adapter regression
cannot hide behind it. The healthy path asserts the storefront is
non-empty, which pins the discriminator the skip depends on — if that
assertion ever fails, the skip has become unsafe.

Deliberately narrower than the blanket XCTSkipIf removed on 2026-07-21,
which hid two genuine setup bugs.

Verified both directions: 7/7 on iPad Pro 11" M4 (18.6), 7 skipped /
0 failed on iPhone 13 mini (26.5). docs/testing.md now names 18.6 as the
release-gate destination and warns that a green 26.x run is not a green
purchase gate.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
App Store Connect requires a review screenshot per in-app purchase, in
the primary locale (en-US), and they are the same screens this sweep
already navigates to. SCREENSHOT_LOCALE=en re-runs any case in English
instead of duplicating the navigation; the default stays German.

Used to capture the unlock screen and tip jar for the four IAPs, which
are now READY_TO_SUBMIT in ASC.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
…rong

FR-1100-17 is not held by an unreleased build. Audited via the ASC API:
v1.0 build 8 reads READY_FOR_SALE / READY_FOR_DISTRIBUTION with review
completed 2026-07-14 and releaseType AFTER_APPROVAL. What keeps it off
the store is app availability — all 175 territories false since
2026-07-22. It is a switch, not a state, and turning it on before 1.1
ships would publish the ungated build instantly.

That also opens a question the spec assumed away: between approval
(~07-15) and the pull (07-22) the app may have been publicly
downloadable. App Analytics will say; if anyone installed, FR-1100-13
binds for them.

New blocker: 27 EU territories carry TRADER_STATUS_NOT_PROVIDED, so the
app cannot be sold in the EU at all — including Germany, which the whole
de-DE listing was written for. Web-UI only, multi-day verification.

Store setup itself is done: four IAPs created, localized, priced,
Family-Sharing flagged, review screenshots attached, available in 175
territories. All four READY_TO_SUBMIT.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
Jan's in-progress Icon Composer edit swaps the frame's screen photo from
the Iceland geyser (Image 12, now hidden) to the mountain/meadow
illustration, same 947x929 rounded frame, scale 0.78.

It could not build: the layer referenced "Image.png", which does not
exist on disk. Icon Composer had written the file under its
collision-mangled name "1__#$!@%!#__Image.png" — it collides with the
existing image.png on a case-insensitive filesystem — while icon.json
kept the unmangled name. actool failed the whole app target with
"attempt to insert nil object", not just the icon.

Renamed to "Image 13.png" (the convention the other layers use) rather
than teaching icon.json the mangled name: those characters are
shell-hostile and the collision that produced them would persist.

Verified: build succeeds and the composed 152pt icon renders the iPad
frame, the new artwork, and the play-button glass layer correctly.

Also corrects yesterday's #54 scope in docs/testing.md — 26.0 runs the
StoreKit suite 7/7, so only 26.4 and 26.5 are affected — and records the
trap that produced the wrong reading: XcodeBuildMCP's `simulatorName`
re-resolves and overrides a pinned `simulatorId` when several runtimes
share a device name.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
Design/AppIcon already tracks the icon's design sources (the SVGs).
image.png is the raw 1254x1254 square the new screen artwork was cut
from; Image 13.png in the .icon bundle is that same art cropped to the
frame's screen aperture with the corner radius applied.

Byte-identical to the copy Icon Composer keeps as a hidden layer, so the
provenance is unambiguous.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
The gated build. v1.0 build 8 is approved but has never been available
to the public — 175/175 territories are off — so 1.1 is the first
version anyone can install, which is the whole point of FR-1100-17.

All ten target configurations move together.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
@kipp-ing kipp-ing changed the title Availability decoupled from in-app UI presentation + frame_status sensor (FR-700-23, FR-710-24) Release 1.1 (gated first public build) + availability decoupling (FR-700-23, FR-710-24) Jul 29, 2026
…ndout

The .afdesign grew 33.1 MB -> 37.8 MB. Tracking it stays deliberate (888f14f),
so it is committed rather than dropped; noted that each future edit adds ~38 MB
to history.

docs/release-1.1-handout.md consolidates what only Jan can do before shipping
1.1, ordered by calendar cost. Sourced from a live App Store Connect API audit
on 2026-07-29 plus the FINAL DEVICE DAY list in docs/manual-verification.md.

Claude-Session: https://claude.ai/code/session_016E2qREcZH5HyWE342eGVdk
@kipp-ing
kipp-ing merged commit dc4e885 into main Jul 29, 2026
1 check passed
@kipp-ing
kipp-ing deleted the impl-fr-700-23-710-24 branch July 29, 2026 16:54
kipp-ing added a commit that referenced this pull request Sep 13, 2026
Release 1.1 (gated first public build) + availability decoupling (FR-700-23, FR-710-24)
kipp-ing added a commit that referenced this pull request Sep 13, 2026
Release 1.1 (gated first public build) + availability decoupling (FR-700-23, FR-710-24)
kipp-ing added a commit that referenced this pull request Sep 25, 2026
…ity and soak automation

hitl.md automation assessment, steps 1-2 and 5. Device-only, opt-in (skipped in suite runs).
- AirplaneMode: flips airplane mode through Control Center and proves offline with a probe of a
  PUBLIC host (the demo server is a LAN address from inside and the runner has no Local Network
  permission, so probing it would make every offline check pass vacuously).
- Resilience (device-accept.sh resilience): offline cold launch resumes from the cache (#80) and
  2 min offline then recovery. Both GREEN on iPad jk (26.6.1), cable-connected.
- Share Sheet round trip (Safari -> Share -> OwnFrame -> Done -> cold app prefilled): written,
  first run could not find OwnFrame in the scrolling app row; now scrolls it. Not yet green.
- PR #49 availability (device-accept.sh availability + check-availability.py): lines up
  RIGMARK markers with a timestamped broker recording. Not yet run to the end (runner blocked
  by iOS's UI Automation passcode re-prompt). The rig part confirmed free telemetry on the
  unentitled jk: 7 sensors, 0 controllable entities.
- soak.sh + DeviceSoakUITests: 24 h offline-entitled and 4 h free-tier soaks. Not yet run.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
kipp-ing added a commit that referenced this pull request Sep 25, 2026
…n on iPad jk

- availability: 8/8 on the live broker (online through Settings, frame_status inactive ->
  running, offline on backgrounding, online on return). XCUIDevice.press(.home) did not
  background the app the way a person does (no teardown fired, while a devicectl launch of
  Safari published offline within 7 s), so the test backgrounds by switching to Safari.
- Share Sheet: Safari -> Share -> OwnFrame -> German confirmation -> Done -> cold app prefilled.
  Scrolls the app row by frame (isHittable throws for scrolled-out cells) and waits for the row
  to settle before tapping.
- device-accept.sh: SKIP_RIG=1 reuses a configured frame.
- hitl.md: what these runs ticked off and what is still open.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
kipp-ing added a commit that referenced this pull request Sep 26, 2026
Release 1.1 (gated first public build) + availability decoupling (FR-700-23, FR-710-24)
kipp-ing added a commit that referenced this pull request Sep 26, 2026
…ity and soak automation

hitl.md automation assessment, steps 1-2 and 5. Device-only, opt-in (skipped in suite runs).
- AirplaneMode: flips airplane mode through Control Center and proves offline with a probe of a
  PUBLIC host (the demo server is a LAN address from inside and the runner has no Local Network
  permission, so probing it would make every offline check pass vacuously).
- Resilience (device-accept.sh resilience): offline cold launch resumes from the cache (#80) and
  2 min offline then recovery. Both GREEN on iPad jk (26.6.1), cable-connected.
- Share Sheet round trip (Safari -> Share -> OwnFrame -> Done -> cold app prefilled): written,
  first run could not find OwnFrame in the scrolling app row; now scrolls it. Not yet green.
- PR #49 availability (device-accept.sh availability + check-availability.py): lines up
  RIGMARK markers with a timestamped broker recording. Not yet run to the end (runner blocked
  by iOS's UI Automation passcode re-prompt). The rig part confirmed free telemetry on the
  unentitled jk: 7 sensors, 0 controllable entities.
- soak.sh + DeviceSoakUITests: 24 h offline-entitled and 4 h free-tier soaks. Not yet run.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
kipp-ing added a commit that referenced this pull request Sep 26, 2026
…n on iPad jk

- availability: 8/8 on the live broker (online through Settings, frame_status inactive ->
  running, offline on backgrounding, online on return). XCUIDevice.press(.home) did not
  background the app the way a person does (no teardown fired, while a devicectl launch of
  Safari published offline within 7 s), so the test backgrounds by switching to Safari.
- Share Sheet: Safari -> Share -> OwnFrame -> German confirmation -> Done -> cold app prefilled.
  Scrolls the app row by frame (isHittable throws for scrolled-out cells) and waits for the row
  to settle before tapping.
- device-accept.sh: SKIP_RIG=1 reuses a configured frame.
- hitl.md: what these runs ticked off and what is still open.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
kipp-ing added a commit that referenced this pull request Sep 26, 2026
…Framepad (iOS 17)

A dropped synthesized keystroke on Framepad saved broker host hme.kippings.de; TLS
rejected it and the rig still reported green. set() now verifies the field value
(bullet count for secure fields) and retypes up to 3 times, else fails. On the next
run it retyped the host once, and the PR #49 check passed 8/8 on iOS 17.7.11.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
kipp-ing added a commit that referenced this pull request Sep 27, 2026
…reen on FramePhone (iOS 27)

The rig only knew the iPad's unentitled layout. On an iPhone it now drags the Form's own
list in short steps (flicks miss the sheet and overshoot), closes the keyboard before
scrolling, and expands the collapsed MQTT group an entitled frame shows. The availability
checker tolerates 2 s of device-vs-Mac clock skew (FramePhone ran ~0.1 s ahead). On
FramePhone: rig green, PR #49 8/8, and HA registered all 23 entities as
<domain>.ownframe_8852_<entity>, named "OwnFrame Battery" etc.

Claude-Session: https://claude.ai/code/session_01HEQMx22jwoMuXxKxMuCGEV
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