Skip to content

Make the iPhone app ready for TestFlight and the App Store - #13

Closed
frahlg wants to merge 5 commits into
claude/ios-m1-foundationfrom
claude/ios-m1-store-ready-4df952
Closed

frahlg wants to merge 5 commits into
claude/ios-m1-foundationfrom
claude/ios-m1-store-ready-4df952

Conversation

@frahlg

@frahlg frahlg commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Problem

#8 gives the iPhone app pairing, Now and widgets, but not what TestFlight and the App Store need from the app itself. The app store permission in #10 asks that the license and an offer of the exact version's source be reachable in the app. App Review needs a way in without a box. And the app lacks a privacy manifest, an export answer, a launch screen and an iOS 26 icon.

This branch makes the app ready for TestFlight and the App Store in every country. A parallel session builds the release pipeline (Actions, RELEASING.md, store text and the privacy policy).

Change

1. Source & licenses (FTW/About/, FTWKit/Identity/BuildSource.swift, LicenseText.swift)

  • A Copy Files phase copies LICENSE, NOTICE and LICENSING.md from the repository root into FTW.app/Licenses/. These are the root files themselves, not copies kept in the app folder.
  • A script phase writes the commit to Licenses/REVISION:
    • FTW_SOURCE_REVISION if set. CI passes GITHUB_SHA. Anything but 40 lower-case hex digits fails the build.
    • Else git rev-parse HEAD.
    • A tree with changes, or no git at all, is a local build. The screen says "Local build" and links to the repository.
  • The screen shows version, build and commit. It links to tree/<commit> and blob/<commit>/LICENSE, and it shows the three texts, joined for a phone's width. A test checks that joining keeps every word of every file in order. Relative links in LICENSING.md open at the same commit.
  • It shows the copyright line and the no-warranty notice, as AGPL §0 asks of an interactive program.
  • Third-party code: none, and the screen says so. A test fails if Package.swift or the project gains a package.
  • Origin.licenseURL, which pointed at main, is gone. BuildSource builds both links.
  • The words are in English and Swedish. A test reads the views and fails on any sentence with no Swedish.

2. Remove this phone (FTW/About/RemovePhoneView.swift, FTWKit/Identity/PhoneRemoval.swift)

  • protocol/registry.yaml has no op for a phone to remove itself, so none is named. Core does have DELETE /api/app-link/devices/{id} over the passthrough, but it refuses to remove the last owner over a session, which is the usual household. The box's own page always works.
  • The screen does what the phone can:
    • It deletes the key, the pairing, the sealed readings and the widgets' records, then reloads the widgets. This is AppModel.leave(), which AppModelTests already covers.
    • It names the phone as the box does (Phone plus 8 characters of its key, Vault.deviceIDOnBox) and gives the three steps on the box.
  • The passkey stays in the Passwords app, and the screen says so. Signalling it as unknown (ASCredentialUpdater) could delete a passkey that the web app on another device still uses.
  • 5.1.1(v): it does not apply. Pairing creates no account with Sourceful: no user name and no record on any server of ours. Apple's guidance defines deletion as removing the account from the developer's records (5.1.1 account deletion, guidelines, updated June 8, 2026). The app offers removal anyway, and the review notes should say so.

3. Demo in Release. No code change was needed. FTWDemo links in every configuration, and the pairing screen offers "Try the live demo" under "Interactive demo" on a fresh install. Inside, the band reads "Live demo · simulated home" with Exit demo. Checked with a Release simulator build (release-pair-*.png).

4. Launch screen. UILaunchScreen fills with LaunchBackground, the app's surface colour (#F4F4F2 light, #0D0D0D dark). The launch screen and the first frame match.

5. Privacy manifests (FTW/PrivacyInfo.xcprivacy, FTWWidgets/PrivacyInfo.xcprivacy)

  • No tracking, no tracking domains, nothing collected.
  • Each executable needs its own manifest (required reasons). Reasons were checked against the Release binaries with nm -u:
API App Widget Why
File timestamp, C617.1 yes yes CacheReader.latestSiteID sorts App Group files by contentModificationDate. C617.1 covers the app group container.
UserDefaults, CA92.1 yes no Only Debug builds read it today, for -FTWDemo, -FTWTab and -FTWAbout. It is declared so that adding @AppStorage cannot trip ITMS-91053.
  • No disk space, boot time or keyboard APIs.
  • App Privacy answer: "No, we do not collect data from this app". Apple counts data as collected when it is kept off the device in readable form beyond serving the request (App Privacy Details). Readings and commands are end to end encrypted to the household's box, and the relay keeps no record of who connected (ftw-webapp relay/README.md). Its access log deletes client addresses.
  • One gap: Caddy's own error log (relay/deploy/Caddyfile, ERROR level, 3 × 10 MB) can hold a client address beside the request URI when a proxy error happens. That is diagnostics nobody uses, but it is readable and kept. Either strip addresses from that log too, which makes the answer exact, or declare "Other Diagnostic Data", not linked to the user and not used for tracking.

6. Export compliance: ITSAppUsesNonExemptEncryption = NO

  • Apple: NO means only exempt encryption, which Apple frames as exempt from documentation upload (key, article). App Store Connect asks for the algorithm type (overview, outcomes). The answer is "None of the algorithms mentioned above". X25519, ChaChaPoly, SHA-256, HKDF and AES-GCM all run in CryptoKit, which ships in iOS. Noise IK is a published protocol made from those primitives, and the app implements no algorithm. Apple's outcome for encryption limited to the OS: no documents. No Apple page names CryptoKit as OS encryption in so many words; that is our reading.
  • US EAR:
    • The source is public, so the encryption source code is not subject to the EAR (15 CFR 742.15(b)). Neither is object code built from it (note to 734.3(b)(2)–(3), 734.7).
    • Since the 2021 rule (86 FR 16482), only "non-standard cryptography" needs an email to BIS and NSA, and FTW uses none.
    • The fallback route is mass market under 740.17(b)(1). Its annual report (740.17(e)(3)) now covers only components and executable software, not an end-user app.
    • So there is no filing, as long as every store build comes from a public commit. That is what the source revision phase records.
  • France: answering "None" brings no upload request from Apple. French law still asks for a free declaration to ANSSI a month before supplying a cryptology tool for confidentiality in France (Décret 2007-663, art. 3–5; ANSSI procedures; online since 31 March 2026). Decision for Fredrik: file it before France goes live, or leave France out of the first release.
  • No lawyer has reviewed this.

7. Icon. FTW/AppIcon.icon is an Icon Composer document made from ftw-webapp/public/icons/icon.svg: the amber bolt (#FFAC41, the mark's oklch(0.82 0.16 65)) as a glass layer on #0D0D0D, with no gradient, as the mark asks. iOS draws the light, dark, tinted and clear looks from it. The archive holds 1024 px renditions for any, dark and tinted. The 1024 PNG and the ten macOS sizes are gone. The SVG says it is a placeholder mark until the real FTW icon exists.

8. Accessibility

  • The new screens use system fonts and lists. The mark in the header grows with the text, and the header and the footer links stack at accessibility sizes. Headings in the license texts carry the header trait.
  • The flow diagram and the price chart live in FTW/UI/, which the car session owns and is rewriting. At the largest size on this base, the diagram's nodes overlap and the demo band takes four lines (demo-now-largest-text.png). The car branch already caps the diagram and the freshness band. The rest goes to that session as concrete changes, not edits here: cap DemoBand, give the price bars VoiceOver labels and values in place of a fixed English sentence, and add accessibility actions to the tappable flow nodes.

Privacy policy and support (5.1.1(i)). The foot of Now and of pairing links the privacy policy beside Source & licenses. Source & licenses has a Privacy and support section with the privacy policy and the support page. Both pages come from srcfl/ftw-web#18: https://ftw.energy/privacy and /support, or /sv/privacy and /sv/support when the app speaks Swedish. ftw.energy does not serve that site yet; #18 says how to add the domain.

Entry point. One line each, AboutLink(app: app): at the end of NowView.body and after content in PairView. The footer stays on one line while it fits and stacks when it does not.

Evidence

  • swift test: 108 passed, 1 live test skipped (DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer). The new AboutTests has 13 tests.
  • Simulator builds, Debug and Release: 0 warnings. FTW_SOURCE_REVISION=dev fails the build with the error; a full SHA is written as given.
  • xcodebuild archive -configuration Release -destination generic/platform=iOS CODE_SIGNING_ALLOWED=NO succeeded with 0 warnings. The archive holds Licenses/{LICENSE,NOTICE,LICENSING.md,REVISION} (the commit, e76660d), both manifests, ITSAppUsesNonExemptEncryption = false and UILaunchScreen.
  • Removal, end to end in the simulator: I paired from a QR picture with a random box key. Afterwards the Keychain held 5 items for the app. Remove this phone → Remove: 0 items, an empty App Group, and the app back on Connect FTW.
  • Screenshots: light, dark, largest text and Swedish, of Source & licenses (with the privacy section), the license texts, Remove this phone and its question, the Release pairing screen with the demo, and Now's footer. They are in the session scratchpad under m1/store/, on the shared laptop.

Not in this PR

  • The car session should add the two AboutLink lines on its branch, and the accessibility changes above.
  • README Status is not updated here.
  • The commits carry no DCO sign-off. Fredrik signs off his own work.

🤖 Generated with Claude Code

frahlg and others added 5 commits October 12, 2026 18:41
… phone

The app store permission in LICENSE asks that the license and an offer of
the exact version's source be reachable in the app. A Copy Files phase
puts LICENSE, NOTICE and LICENSING.md from the repository root into the
bundle, and a script phase records the commit: FTW_SOURCE_REVISION from
CI, else git, with a tree that has changes marked as a local build.

Source & licenses shows version, build and commit, links to the tree and
the license at that commit, the three texts joined for a phone's width,
and that there is no third-party code. A test fails when the package or
the project gains a dependency.

Remove this phone clears the key, the pairing, the sealed readings and
the widgets' records (AppModel.leave) and says how to remove the phone on
the box. The registry has no op for a phone to remove itself.

Both are reached from the foot of Now and of pairing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t frame

The icon is an Icon Composer document made from FTW's mark in
ftw-webapp/public/icons/icon.svg: the amber bolt as a glass layer on the
mark's near-black. iOS draws the light, dark, tinted and clear looks from
it. The 1024 image and the macOS sizes left over from #5 are gone.

The launch screen fills with the app's surface colour, light and dark, so
nothing changes colour between the launch screen and the first frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both the app and the widget extension carry a privacy manifest: no
tracking, no tracking domains, nothing collected. Readings and commands
travel end to end encrypted between the phone and the household's box;
the relay forwards ciphertext and stores nothing.

Required reasons, checked against the Release binaries:
- File timestamp, C617.1, in both. FTWShared's CacheReader picks the
  newest home by the modification date of its App Group files.
- UserDefaults, CA92.1, in the app. Only Debug builds read it today, for
  the screenshot launch arguments.

ITSAppUsesNonExemptEncryption is NO. Every primitive comes from CryptoKit
in iOS; the app implements no algorithm of its own. The pull request
gives the reasoning and sources, and what France needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The close button is the neutral glass one; as a confirmation action it
  turned amber.
- The removal question points at the button that asked it, not at the
  top of the list.
- The mark in the header grows with the text, and the header stacks at
  accessibility sizes.
- A title in capitals over a short line, such as the license's name over
  its version, reads as one heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App Review's 5.1.1(i) asks that the privacy policy be easy to find in the
app. The foot of Now and of pairing links it beside Source & licenses,
and Source & licenses has a Privacy and support section. Both pages are
srcfl/ftw-web#18's, at ftw.energy, in Swedish when the app is.

The footer's links stay on one line while they fit and stack when they
do not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Oct 12, 2026
#13 sets ITSAppUsesNonExemptEncryption to NO and adds privacy manifests
with no collected data. RELEASING.md now gives the matching App Store
Connect answers ("None of the algorithms mentioned above", "Data Not
Collected"), #13's reading of US and French encryption rules, and the
choice about the relay's error log. The App Review notes say where the
privacy link is and that there is no account to delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg

frahlg commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Included in #14, which lands the whole iPhone M1 app as one PR. The branch stays.

@frahlg frahlg closed this Oct 12, 2026
frahlg added a commit that referenced this pull request Oct 12, 2026
…#14)

* Bring in appleApp and the Apple workflow from #5, drop iosApp

Taken as they stand from claude/native-swift-ftw-app-d7j9w4 (dbfce65):
appleApp/, .github/workflows/apple.yml, .gitignore and the KMP change
that removes the iOS targets. iosApp/ and shared/src/iosMain go, as in
#5. protocol/registry.yaml stays as it is on main.

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

* Cut the Swift app down to the iPhone M1 foundation

iPhone only, iOS 26. Pairing, Now with the price card, and the demo stay.
History, Plan, Box admin, escrow, spoken box codes, the macOS target and
menu bar, swift-crypto, the Linux job and PlainWebSocket go. The charger
sheet goes too; the car view replaces it next.

- One Swift package, three modules: FTWKit (the app's logic), FTWShared
  (Sendable values for widgets and later a notification extension) and
  FTWDemo (the simulated box, out of the product module).
- FTWShared holds the App Group cache: prices-<site> and car-<site>,
  each with carrier, srcState and when it was read, written with
  completeUntilFirstUserAuthentication. A reader takes any number of
  sources, so a cloud copy can be one more source later. The price and
  freshness formatters moved here, in English and Swedish.
- The car record keeps soc_source and when the level was read.
- Contract.swift follows main's registry: planner.prefs.set and the
  modes without priority and weighted. ContractTests now also checks
  modes, frozen field ids, the shared enums and the demo box's names.
- The session closes when the app goes to the background and reopens on
  return without a passkey. A relay 4429 shows "FTW is busy".
- A WidgetKit extension with one price widget that reads the cache. The
  App Group is written once, in FTW.xcconfig; both entitlements and
  Info.plists read it. No DEVELOPMENT_TEAM; simulator builds sign ad hoc
  so the entitlements travel with the app.

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

* Sign with Sourceful's team, open pairing links, pair from a picture

- DEVELOPMENT_TEAM L66M368PZ6 in FTW.xcconfig, for the app and the
  widget, with automatic signing. Simulator builds still sign ad hoc and
  need no Apple account. The App IDs, App Group and Associated Domains
  still have to be registered for the team; the README lists the steps.
- A pairing QR scanned with the iOS camera arrives as a universal link
  to https://app.ftw.energy/p#…. The app takes it through onOpenURL and
  onContinueUserActivity, the same offer path as any pairing link.
- In the simulator, which has no camera, a picture of the QR from Photos
  pairs. PairingImage reads the link with CoreImage, with tests.
- FTW Cloud is now an opt-in, readable state cache. The cache comments
  and CLAUDE.md say so; M1 does not depend on it.
- Passkeys no longer uses the presentation anchor iOS 26 deprecated.

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

* Price widgets: the price now, the cheapest two hours next, and a grey end

Four families read the App Group cache and nothing else: small (also
the StandBy widget), medium with a twelve-hour curve, and the lock
screen's rectangle and inline line. Prices show on the lock screen.

- PriceGlance (FTWShared) builds the timeline: one entry now and one at
  every price boundary the box sent, at the box's resolution. Where the
  prices stop, the widget turns grey and says "No prices after Tue
  24:00 · open FTW". It never stretches or repeats old prices.
- The total says what it holds. The wire carries spot and total but not
  the settings between them, so a line through the two tells a grid fee
  from VAT.
- Freshness stays two fields: every entry is carrier .cache with the
  srcState the app saw when it fetched.
- SiteModel logs each write with os.Logger and gains
  refreshSharedPrices. The app asks for a BGAppRefreshTask just after
  13:05, then every half hour until the box has tomorrow. No silent
  push, no server.
- Swedish and English live in FTWShared; a test checks that every
  widget sentence has Swedish.

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

* Follow car commands to their outcome and read the box's clock

The car view's rules, in FTWKit so they are tested:

- Where the level comes from: from the car, from the car but older than
  five minutes ("last reported HH:MM"), set from this phone, FTW's
  estimate, or FTW's assumption of 20 % at plug-in.
- A receipt for every command: sent, received (cmd.ack), done
  (cmd.result), or why not. Session.command gains onAck for this.
- The ready time on the household clock, worked out the way Core does,
  so 07:00 stays 07:00 across 25 October and a box that kept the UTC
  minute shows the 06:00 it really uses. Goals go out as both
  time_of_day_min and time_of_day_min_utc.
- Whether the car makes its goal, from the plan's own shortfall.
- "Is FTW in control?" from Core's control feedback, never green from
  readings that are not live.
- English and Swedish words for all of it.

LoadpointsModel drops boost, solar-only and battery size from the daily
path. Charge now holds the charger's full current with no time limit and
no level gate. The demo box plugs in and out, has a car that reports its
level, a plan with a shortfall, and a charger that takes a moment.

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

* Draw the curve's bars from zero and keep the last hour label whole

A bar mark with an x interval and a single y lies on its side, so the
curve showed short dashes at each price. Rectangles from zero to the
price fix that. Hour labels fall every third hour, clear of the right
edge, on the 24-hour clock the rest of the widget uses. The average
line takes the muted tone instead of the system tint.

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

* Add an app store distribution permission to the license

App stores attach their own terms to every copy, and the GNU AGPL forbids
further restrictions on recipients. Sourceful owns all code here today, so
it may publish through the App Store as it is. That stops being true once
someone else's code comes in under plain AGPL.

LICENSE gains a second section 7 permission, next to the Energyplan one.
It allows conveying the app through a store such as the App Store,
TestFlight or Google Play despite the store's terms. Every other AGPL
condition stays: source for the exact version outside the store, license
and source offer reachable in the app, no added restrictions, and
installation information except the distributor's own signing keys.

CONTRIBUTING now says each contribution grants both permissions, so the
permission covers future code as well. LICENSING.md and the README say the
same in plain words.

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

* Fit the lock screen, drop UIKit, and time the timeline

On the lock screen the inline line shares its width with the date, so
it now shows the price and the start of the cheapest stretch ("273 öre
· billigast 02:45"), and "Inga priser · öppna FTW" when there is none.
The rectangle's lower lines shrink a little before they would be cut.

Colours resolve through a SwiftUI ShapeStyle instead of UIColor, and the
background task reads the scene phase instead of UIApplication.

The timeline sorts the slots and reads what the total holds once, not
once per entry: 121 entries took 37-40 ms in a Debug build in the
simulator, now 14-17 ms, file read included. An OSSignposter interval
wraps it for Instruments, and the log line carries the time.

The price figure changes with a numeric content transition.

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

* Add the car view and cut Now to what is looked at daily

Now keeps four things: "Is FTW in control?" in one calm line, the power
flow, the car as one row that opens the car view, and the price card.
The headline sentence, today's totals, savings and the fuse go. The
line is green only when Core's control feedback says so over a live
stream; a cache is grey and dated.

The car view, in FTW/Car:

- How full the car is, and who says so. Plugged in and unread, FTW
  assumes 20 % and says so with a badge. 20/40/60/80 and ±5 send
  loadpoint.soc.set at once; quick taps fold into one send.
- The goal, on the household clock, with the next deadline, whether
  the plan makes it, and when it charges. "Use 80 % by 07:00 on
  weekdays" when there is none; an editor for the rest.
- Charge now, Pause and Back to the plan, each with a receipt: sent,
  received, done, or why not.
- In the demo, a card to plug the car in and out.

Every word is in English and Swedish. The tab bar with one tab gives way
to a navigation stack. The bands above every screen stop growing past
xxxLarge text, and the flow diagram past xLarge.

The demo's loopback could deliver a frame before the one sent ahead of
it: each frame had its own timer, and live timers with the same delay
need not fire in order. An api.chunk before its api.head read as "your
box didn't answer". Each timer now delivers the oldest frame.

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

* Write the cache off the main thread, skip unchanged prices, follow the rendering mode

- The App Group write runs off the main actor (a @Concurrent function),
  in the order it was asked for. A sign-out asked for meanwhile wins:
  writes and clears share a lock and a per-directory clear count, so a
  late write never puts the home back.
- The same prices again are neither written nor reloaded, so the widget
  timeline is rebuilt only for new prices. The background task waits
  for the write to land before iOS can suspend it.
- The curve follows widgetRenderingMode: tinted, clear and StandBy's
  one-colour mode keep the current and cheap slots strong and fade the
  rest. The cheapest line and the symbol take the accent.
- A bolt symbol heads every family, as in Apple's own widgets.

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

* Archive the iPhone app and send it to TestFlight from CI

A tag ios-v1.2.3, or a manual run, archives the app with Xcode 27 on
GitHub's xcode-27 image, signs it with Sourceful's team through an App
Store Connect API key and uploads it with method app-store-connect and
destination upload. The version comes from the tag, the build number
from the run. Without the ASC_* secrets, and on every pull request, it
archives unsigned and says the upload was skipped.

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

* Show Source & licenses, pinned to the build's commit, and remove this phone

The app store permission in LICENSE asks that the license and an offer of
the exact version's source be reachable in the app. A Copy Files phase
puts LICENSE, NOTICE and LICENSING.md from the repository root into the
bundle, and a script phase records the commit: FTW_SOURCE_REVISION from
CI, else git, with a tree that has changes marked as a local build.

Source & licenses shows version, build and commit, links to the tree and
the license at that commit, the three texts joined for a phone's width,
and that there is no third-party code. A test fails when the package or
the project gains a dependency.

Remove this phone clears the key, the pairing, the sealed readings and
the widgets' records (AppModel.leave) and says how to remove the phone on
the box. The registry has no op for a phone to remove itself.

Both are reached from the foot of Now and of pairing.

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

* Give the app an iOS 26 icon and a launch screen that matches its first frame

The icon is an Icon Composer document made from FTW's mark in
ftw-webapp/public/icons/icon.svg: the amber bolt as a glass layer on the
mark's near-black. iOS draws the light, dark, tinted and clear looks from
it. The 1024 image and the macOS sizes left over from #5 are gone.

The launch screen fills with the app's surface colour, light and dark, so
nothing changes colour between the launch screen and the first frame.

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

* Declare privacy and export compliance for the App Store

Both the app and the widget extension carry a privacy manifest: no
tracking, no tracking domains, nothing collected. Readings and commands
travel end to end encrypted between the phone and the household's box;
the relay forwards ciphertext and stores nothing.

Required reasons, checked against the Release binaries:
- File timestamp, C617.1, in both. FTWShared's CacheReader picks the
  newest home by the modification date of its App Group files.
- UserDefaults, CA92.1, in the app. Only Debug builds read it today, for
  the screenshot launch arguments.

ITSAppUsesNonExemptEncryption is NO. Every primitive comes from CryptoKit
in iOS; the app implements no algorithm of its own. The pull request
gives the reasoning and sources, and what France needs.

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

* Keep the release key in a GitHub environment

The upload needs an Admin API key, because only Account Holder and Admin
may use cloud-managed distribution certificates. A repository secret
would let any branch's workflow read it, so the job takes its secrets
from the app-store environment, which only main and ios-v* tags may
use. Pull requests run without the environment.

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

* Write down the App Store steps and the store texts

RELEASING.md lists, in order, what someone in team L66M368PZ6 does in
App Store Connect and on GitHub before the first upload: app record and
name, EU trader status, App Privacy, export, age rating, price, countries,
the API key, TestFlight and the review. store/ holds the product page in
English and Swedish, the App Review notes for the demo, and check.sh,
which holds each text to Apple's limits. scripts/store-screenshots.sh
takes the 6.9" pictures from the demo in its own simulator.

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

* Make the car view and Now fast, native and timer-free

Nothing asks the box on a timer any more. The chargers are read when a
screen that shows them appears, after a command, when the stream says
the car started or stopped drawing, and when the stream comes back. The
plan's shortfall is read when the car view opens and when the car's plan
changes. "Is FTW in control?" now comes from lane 0 alone (the dispatch
block and each source's state), so Now no longer polls /api/status.

A cold start paints the car from the App Group cache the app writes for
its widgets, dated and marked as not live, before the box answers. While
nothing at all is known the screens show their shape, not a spinner.
LaunchClock logs process start to first frame and first content.

A tapped level shows as the chosen chip and the receipt; the number only
moves once the box confirms it. Goals send time_of_day_min and today's
time_of_day_min_utc, built from nothing read; tests cover a Core with
srcfl/ftw#1586 and one without.

The car view is a system List with system buttons, haptics on commands
and outcomes, symbol effects and numeric transitions. The Now car row
zooms into it. Now is split into small views that redraw only what a
frame changed. Theme colours move to an asset catalog, so no UIKit is
left outside the camera. CLAUDE.md gains "Feel and speed".

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

* Leave the pairing screen out of the store pictures

In the simulator it shows a button a phone does not have. Wait 12 s
so the charging picture shows the car charging, not starting.

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

* Tidy Source & licenses after a look in the simulator

- The close button is the neutral glass one; as a confirmation action it
  turned amber.
- The removal question points at the button that asked it, not at the
  top of the list.
- The mark in the header grows with the text, and the header stacks at
  accessibility sizes.
- A title in capitals over a short line, such as the license's name over
  its version, reads as one heading.

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

* Put the shortest way to an iPhone first, and add an upload fallback

RELEASING.md now starts with installing from Xcode on a connected iPhone
(Developer Mode, an Apple Account in Xcode, xcodebuild and devicectl),
then internal TestFlight, which needs no Beta App Review: the agreement,
the app record, an Admin key in plain repository secrets, a tag, and a
tester group. External TestFlight and the App Store follow.

The workflow accepts tags like ios-v0.1.0-1: text after a dash only
tells tags apart. If Xcode's upload fails, it exports the .ipa and sends
it with `altool --upload-package`, which Apple's "Upload builds" help
still lists.

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

* Link the privacy policy and support from inside the app

App Review's 5.1.1(i) asks that the privacy policy be easy to find in the
app. The foot of Now and of pairing links it beside Source & licenses,
and Source & licenses has a Privacy and support section. Both pages are
srcfl/ftw-web#18's, at ftw.energy, in Swedish when the app is.

The footer's links stay on one line while they fit and stack when they
do not.

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

* Match the release guide to the store-ready branch's answers

#13 sets ITSAppUsesNonExemptEncryption to NO and adds privacy manifests
with no collected data. RELEASING.md now gives the matching App Store
Connect answers ("None of the algorithms mentioned above", "Data Not
Collected"), #13's reading of US and French encryption rules, and the
choice about the relay's error log. The App Review notes say where the
privacy link is and that there is no account to delete.

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

* Leave France out of the first release

Fredrik decided that France waits until the ANSSI encryption
declaration is filed. RELEASING.md now removes France under Pricing and
Availability, keeps China out for want of an ICP filing, and adds a
step for the declaration, with ANSSI's forms page, before France is
added back.

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

* Reflow the license header so the app shows it as one paragraph

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

* Link the privacy, support and marketing pages on ftw.sourceful.energy

The website lives at ftw.sourceful.energy (srcfl/ftw-web README).
ftw.energy has no A record, so the About links and the store URLs
pointed nowhere. The pages from srcfl/ftw-web#18 answer 200 there in
both languages.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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