Repository navigation
Conversation
FTWKit is a Swift package that builds and tests on Linux and Apple platforms. CryptoKit supplies the primitives; swift-crypto stands in on Linux only. The Cacophony vectors and the interop vectors that srcfl/ftw generates from the web app's code pass byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
Enrollment parsing, the box code, the PRF derivations, the vault and the recovery copy follow the web app byte for byte. Vectors produced by running the web app's own identity code prove it: a copy the web app sealed opens here under the same passkey, and the escrow ids and write keys match. The escrow client pads, signs and versions its requests the way the service expects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
RelayCarrier, NoiseCarrier and Session follow the web app's carrier and session rules: rotating rendezvous handles with bounded epoch correction, a silent box ending the handshake and a retry, requests that always settle, one API call on the wire at a time, and the three command deadlines. A SimulatedBox speaks the same frames for the demo and the tests; the tests drive the whole stack through fake sockets on a manual clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
A live run against a box built from srcfl/ftw, over the production relay, found that the box drops a hist.query or price.get whose times carry a fraction: it decodes them into int64. Times now go out whole, and the simulated box refuses fractions the same way so the unit tests hold it. LiveBoxTests pairs with a freshly minted code and exercises the stream, the API passthrough, the plan, history, the E_USE_CMD refusal and a mode command. It runs only when FTW_LIVE_URL is set; on Linux, whose Foundation has no WebSocket client, it reaches the relay through a local bridge named by FTW_LIVE_RELAY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
SiteModel keeps freshness honest the web app's way: a carrier is claimed only once it delivers a reading, ages come from box uptime, and a quiet stream stops reading as live. The cached snapshot is sealed on disk under a key kept beside the vault, so a cold start paints before any passkey prompt. Feature models cover the plan and modes, prices, energy by day, history tiles with a cache, the charger sheet with its commands and goal, sharing, notification rules, restart, pairing, recovery and the demo. Every sentence is ported from the web app and pinned by tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
The native app is now pure Swift on Apple platforms. FTWKit moves to appleApp/FTWKit so the Xcode project can sit beside it, and the app in appleApp/FTW covers every screen the web app has: pairing (camera, a picture of the QR on a Mac, passkey recovery, the demo), Now (the sentence, the energy flow, price, the plan brief, today, the fuse, the live line and the charger sheet), Plan, History with daily energy, and Box (access, notifications, restart, the sealed copy, sign out). The Keychain holds the store, passkeys run through AuthenticationServices with PRF on the web app's relying party and salt, and a Mac gets a menu bar glance. Three pieces of logic moved into FTWKit with tests so both apps say the same thing: the freshness band's wording, the price card's cheapest two hours and tones, and the sealed copy switch. CI now builds the app, unsigned, for the iOS Simulator and for macOS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
Xcode 26 refuses to resume a continuation with the platform's passkey credential, which is not Sendable. The delegate now copies the credential id and the PRF output into plain values on the main actor and passes those. The Linux test client stops mixing an Int literal into a UInt8 array, which Swift 6.1 does not accept. iOS and macOS are pure Swift now, so iosApp/, shared/src/iosMain and the iOS framework targets in shared/build.gradle.kts go. Android keeps the KMP core unchanged. README and CLAUDE.md describe the Apple app, its scope as chosen by Fredrik on 2026-09-25, what is proven and what still needs a device, and keep the Android gate as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
frahlg
force-pushed
the
claude/native-swift-ftw-app-d7j9w4
branch
from
September 25, 2026 06:54
4cd5ac2 to
2a24e9a
Compare
Debug builds take -FTWDemo YES and -FTWTab <now|plan|history|box>, so appleApp/scripts/demo-screenshots.sh can open every screen of the simulated home and save a picture of each, dark and light. CI runs it after the build and keeps the pictures as an artifact. It is evidence for whoever reviews the screens, not a gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t
This was referenced Oct 12, 2026
Member
Author
|
Included in #14, which lands the whole iPhone M1 app as one PR. The branch stays. |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A pure Swift FTW client for iPhone, iPad and Mac. It replaces
iosApp/and the Kotlin Multiplatform dependency on Apple platforms. Android keeps the KMP core unchanged.Scope set by Fredrik on 2026-09-25: Swift for iOS and macOS, replace iosApp directly, cover the whole web app, and derive the wrap key from the passkey the way the web app does (HKDF), so one passkey opens a home in both. Native pairings made before this change scan the QR again.
Layout
appleApp/FTWKit: Swift package with everything that is not a pixel. CBOR, frames, Noise IK, transport, relay and Noise carriers, the session, pairing, vault, PRF derivations, recovery blob, escrow, the simulated box for the demo, and the state every screen reads. Builds and tests on Linux and on Apple platforms (CryptoKit there, swift-crypto on Linux only).appleApp/FTW: the SwiftUI app. Pair, Now (sentence, energy flow, price card, plan brief, today, fuse, live line, charger sheet), Plan, History with daily energy, Box (identity, access and viewer invites, notification rules, restart, sealed copy, sign out). A Mac also gets a menu bar glance.appleApp/FTW.xcodeproj: hand written, objectVersion 77 with a synchronizedFTW/folder and FTWKit as a local package. Bundle idenergy.ftw.app, iOS 18 and macOS 15.iosApp/,shared/src/iosMainand the iOS framework targets inshared/build.gradle.ktsare removed. README and CLAUDE.md describe the new split.Evidence
swift testinappleApp/FTWKit: 95 tests green on Linux (Swift 6.3) and on macOS in CI (Xcode 26.3, CryptoKit)../gradlew :shared:jvmTestafter removing the iOS targets: 41 passed, 0 failed, 4 skipped (the live box tests, which needFTW_LIVE_BOX).wss://relay.ftw.energy: paired, streaming after 1.2 s,/api/statusover the passthrough, plan, history tiles,site.mode.setapplied. That run found a real bug (times sent with a fraction) that is fixed here.State at handoff
Work moves to Fredrik's Mac from here. This session stops pushing.
Passkeys.swift(a non Sendable credential crossing a continuation). That is fixed in 2a24e9a. The run for the latest head had not finished at handoff, so the app compiling end to end is not yet confirmed. Expect possibly a few more strict concurrency errors in Xcode.Continue on a Mac
git fetch && git checkout claude/native-swift-ftw-app-d7j9w4cd appleApp/FTWKit && swift testappleApp/FTW.xcodeproj, pick your team under Signing & Capabilities, build the FTW scheme for an iPhone simulator and for My Mac. Fix whatever Xcode flags.appleApp/scripts/demo-screenshots.shphotographs every tab of the demo after a Debug build..well-known/apple-app-site-associationto app.ftw.energy (srcfl/ftw-webapp) with<TEAM_ID>.energy.ftw.appunderwebcredentialsandapplinks.Open
🤖 Generated with Claude Code
https://claude.ai/code/session_015czRWjvgq7kpxB3pdJXm4t