From 532a0253d985fc3c3380266844041dca24662fcd Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 4 Nov 2026 13:49:44 -0600 Subject: [PATCH 1/6] Finite for iOS: a native client on the existing wire, signed by its own key A SwiftUI app for iOS 26 (ios/), organized like the web shell: the launcher, chats with streaming, Stop and same-id retry, History, Your Apps in web sheets, and Settings. The phone pairs through the existing /cli approval with a secp256k1 key of its own, kept in Keychain, and signs every request with NIP-98. Apps and brains open through POST /api/f/{name}/session, the platform route in its own PR. It talks to a real platform only: no fixture server and no unsigned mode. Development runs against `cargo xtask dev`; unit tests run the app's HTTP and live-socket code against a stub of the platform's answers, including a lost acknowledgement, a refusal, a rate limit and a dropped stream. A 4xx refusal of a post is final: the post leaves the outbox and its words return to the composer. The app keeps no copy of limits the server enforces. Renaming an agent stays on the web, where its job is rewritten with its name. The eval guard reads the app's Swift: WKWebView's evaluateJavaScript and callAsyncJavaScript are refused like DevTools' evaluation. Co-Authored-By: Claude Opus 5.5 --- docs/MODEL.md | 1 + docs/ios.md | 173 ++++++ ios/.gitignore | 2 + ios/Fragment.xcodeproj/project.pbxproj | 536 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/swiftpm/Package.resolved | 15 + .../xcshareddata/xcschemes/Fragment.xcscheme | 113 ++++ ios/Info.plist | 46 ++ ios/README.md | 157 +++++ ios/Sources/AccountConnection.swift | 185 ++++++ ios/Sources/AppAppearanceView.swift | 131 +++++ ios/Sources/AppCreationSheet.swift | 28 + .../AppIcon.icon/Assets/FiniteHand.svg | 1 + ios/Sources/AppIcon.icon/icon.json | 38 ++ ios/Sources/AppSettingsAPI.swift | 137 +++++ ios/Sources/AppSettingsView.swift | 104 ++++ ios/Sources/AppSharingView.swift | 123 ++++ ios/Sources/Assets.xcassets/Contents.json | 1 + .../DesktopWallpaper.imageset/Contents.json | 22 + .../wallpaper-dark.jpg | Bin 0 -> 221144 bytes .../DesktopWallpaper.imageset/wallpaper.jpg | Bin 0 -> 358745 bytes ios/Sources/AuthenticatedImage.swift | 31 + ios/Sources/ChatDraftStore.swift | 64 +++ ios/Sources/ChatMedia.swift | 118 ++++ ios/Sources/ChatSession.swift | 211 +++++++ ios/Sources/ChatView.swift | 233 ++++++++ ios/Sources/DeviceIdentity.swift | 109 ++++ ios/Sources/FragmentAPI.swift | 187 ++++++ ios/Sources/FragmentApp.swift | 261 +++++++++ ios/Sources/FragmentWebSheet.swift | 129 +++++ ios/Sources/HomeView.swift | 208 +++++++ ios/Sources/MarkdownText.swift | 82 +++ ios/Sources/Models.swift | 194 +++++++ ios/Sources/SettingsView.swift | 187 ++++++ ios/Sources/ShellViews.swift | 450 +++++++++++++++ ios/Sources/ThirdPartyNotices.txt | 48 ++ ios/Tests/AppSettingsTests.swift | 77 +++ ios/Tests/DeviceIdentityTests.swift | 75 +++ ios/Tests/MarkdownTests.swift | 18 + ios/Tests/SearchTests.swift | 29 + ios/Tests/ShellTests.swift | 140 +++++ ios/Tests/Stub.swift | 149 +++++ ios/Tests/TimelineTests.swift | 67 +++ ios/Tests/TransportTests.swift | 159 ++++++ ios/UITests/FragmentUITests.swift | 30 + ios/project.yml | 103 ++++ xtask/src/eval_sinks.rs | 33 +- 47 files changed, 5208 insertions(+), 4 deletions(-) create mode 100644 docs/ios.md create mode 100644 ios/.gitignore create mode 100644 ios/Fragment.xcodeproj/project.pbxproj create mode 100644 ios/Fragment.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Fragment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 ios/Fragment.xcodeproj/xcshareddata/xcschemes/Fragment.xcscheme create mode 100644 ios/Info.plist create mode 100644 ios/README.md create mode 100644 ios/Sources/AccountConnection.swift create mode 100644 ios/Sources/AppAppearanceView.swift create mode 100644 ios/Sources/AppCreationSheet.swift create mode 100644 ios/Sources/AppIcon.icon/Assets/FiniteHand.svg create mode 100644 ios/Sources/AppIcon.icon/icon.json create mode 100644 ios/Sources/AppSettingsAPI.swift create mode 100644 ios/Sources/AppSettingsView.swift create mode 100644 ios/Sources/AppSharingView.swift create mode 100644 ios/Sources/Assets.xcassets/Contents.json create mode 100644 ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/Contents.json create mode 100644 ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper-dark.jpg create mode 100644 ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper.jpg create mode 100644 ios/Sources/AuthenticatedImage.swift create mode 100644 ios/Sources/ChatDraftStore.swift create mode 100644 ios/Sources/ChatMedia.swift create mode 100644 ios/Sources/ChatSession.swift create mode 100644 ios/Sources/ChatView.swift create mode 100644 ios/Sources/DeviceIdentity.swift create mode 100644 ios/Sources/FragmentAPI.swift create mode 100644 ios/Sources/FragmentApp.swift create mode 100644 ios/Sources/FragmentWebSheet.swift create mode 100644 ios/Sources/HomeView.swift create mode 100644 ios/Sources/MarkdownText.swift create mode 100644 ios/Sources/Models.swift create mode 100644 ios/Sources/SettingsView.swift create mode 100644 ios/Sources/ShellViews.swift create mode 100644 ios/Sources/ThirdPartyNotices.txt create mode 100644 ios/Tests/AppSettingsTests.swift create mode 100644 ios/Tests/DeviceIdentityTests.swift create mode 100644 ios/Tests/MarkdownTests.swift create mode 100644 ios/Tests/SearchTests.swift create mode 100644 ios/Tests/ShellTests.swift create mode 100644 ios/Tests/Stub.swift create mode 100644 ios/Tests/TimelineTests.swift create mode 100644 ios/Tests/TransportTests.swift create mode 100644 ios/UITests/FragmentUITests.swift create mode 100644 ios/project.yml diff --git a/docs/MODEL.md b/docs/MODEL.md index e70568905..9f5e185b9 100644 --- a/docs/MODEL.md +++ b/docs/MODEL.md @@ -106,6 +106,7 @@ Every change is checked against this table. | A person's session archive | its conversations' files, `sessions/.json`, in a fragment of theirs of kind `sessions` (templates/sessions/README.md) | its indexes (`index/.json`), a projection rewritten by each import; its search, a projection of `sessions/` each import writes (a source a file, its revision the file's sha256) and reconciles against the indexes; the store an import read is never written | | A fragment's preview card | its own storage (`fragment_core::card::Cards`) | status's `page`; errors also an `events` row | | What members are doing: each one's newest listed record | the channel's records | the fragment's `listed`, and each member's list row (docs/api.md, Listed records) | +| Native iPhone drafts and pending sends | protected, backup-excluded Application Support files, scoped to platform and person (`ios/Sources/ChatDraftStore.swift`) | bounded to 32 saved chats, 16 pending sends each; a send is saved before HTTP, retried only with the same id and body, and dropped back to the composer once refused; device keys live only in Keychain | | A person's own view of their fragments (archived, docked, read up to) | their list, the `Principal` cell | none | | A person's export | their list, the `Principal` cell, and its archive in R2 | the archive is a copy as read, never updated, deleted after seven days (docs/api.md, Export) | diff --git a/docs/ios.md b/docs/ios.md new file mode 100644 index 000000000..4021e5d7a --- /dev/null +++ b/docs/ios.md @@ -0,0 +1,173 @@ +# Native iOS client and pairing + +Status: **native CLI-key pairing and scoped web sessions implemented; hosted rollout and TestFlight remain**. +The client is in `ios/`, SwiftUI on iOS 26+, with URLSession HTTP/WebSockets. +The app shares wire shapes with Fragment, not its implementation, and talks to +a real platform only: a deployment, or `cargo xtask dev` in development. + +## Problem and acceptance + +Make Finite.Computer feel at home on iPhone: open your agent, resume a chat, send a +message, see its reply stream, and open an ordinary app without leaving the +conversation. Follow the later, simpler Finite iOS design and the existing +Fragment mobile colours. The signed-in home is a native fullscreen version +of Fragment's Command-K launcher (Paul, 2026-10-14): one input searches chats, +apps and message text; the first action and Return start a new chat with the +complete input. Search hints alone are capped at eight words and 256 bytes; +older search responses never replace newer results. Empty input stays quiet. +Paul, 2026-11-04: use web organization with native iOS presentation. Open on +its search launcher over the web desktop wallpaper. A left sidebar groups chats +by agent (multiple agents supported), with History, Your Apps and Settings. Chat has +sidebar left, title, search/new chat and Your Apps right; chat actions live in the +agent hero menus in the sidebar and chat-row context menus, with no chat ellipsis; Your Apps uses a native list over the shared photo background +and Add an app opens the built-in prompt sheet, without a separate suggestion empty state. +Your Apps has Manage Apps at top right, with all apps (including hidden and archived) +searchable in a native sheet. Per-app settings mirror web sharing and permissions, +personal show/hide/archive, naming, icons and preview generation. +Use system typography and system glass controls, a single glass composer with +aligned 44-point controls and no recipient caption, and actual fragment icons +in Your Apps. The installed hand icon is a layered Icon Composer asset; product copy is Finite.Computer, +Finite for short, and messages name their actual agent. App viewers are native large sheets with a visible drag handle and swipe-down +dismissal, without a Done button; chat state is retained. Native history, settings categories, attachments, voice +memos, reply/copy/share and group addressing follow the existing wire contracts. +No Rust FFI, embedded chat webpage, model key or parallel chat protocol. + +Acceptance: real HTTP +and WebSockets, a working composer above the keyboard, whole-text drafts +replaced once by committed replies, Stop, retry with the original id, +reconnect/catch-up without duplicates or regressing either channel's cursor, +earlier history independent of live catch-up, selectable native Markdown, +light/dark, and ordinary fragments in dismissible WKWebView sheets. Build +with Swift 6 strict concurrency and warnings as errors. See `ios/README.md` +for commands and evaluation. Unit tests on a stub of the platform's answers +lose a committed post's acknowledgement, refuse a post and drop a stream to +test recovery. + +## Existing contract; what is missing + +The live service uses these routes already (`docs/api.md` is authoritative): + +| Need | Existing API | +| --- | --- | +| Person, owned agent | `GET /api/identities/me`, `GET /api/fragments`, agent's `GET /api/f/{name}/status` for its npub | +| New chat | `POST /api/fragments {label, template:"chat", title}` then `PUT /api/f/{chat}/members/{agent npub} {role:"editor"}`; keep a successful create across a failed join | +| Messages, Stop, prompt answer | `POST /api/f/{chat}/channels/chat {id, body}`, as `docs/chat-records.md` defines | +| Stream/catch-up | `GET /f/{chat}/__live`, subscribe to `chat` and `work` with `last` initially, `after` on reconnect; follow `subscribed.more` until caught up | +| Earlier messages | `GET /api/f/{chat}/channels/chat?before=&limit=50`; never advance live cursors from this page | +| List invalidation | `GET /api/fragments/watch`; refetch the authenticated list on `changed` | +| Launcher search | `GET /api/search?q=` → fragments by title/label and messages by text, archived included; a message result opens its conversation | +| Read marker | `PUT /api/fragments/{chat}/seen`, after the visible chat catches up | +| App URL | `GET /api/f/{name}/status` → `urls.canonical` | +| App management | Personal `PUT /api/fragments/{name}/dock` and `/archived`; app `/status`, `/visibility`, `/members`, `/invites`, `/rotate`, `/icon`, `/icon/make`, and `/card` use the existing web contracts | + +## Connect an account + +The native client reuses `fragment login`; no second WorkOS integration or device +token protocol. The phone generates a fresh secp256k1 key, saves it to Keychain, +and signs `POST /cli/begin`. The person types the returned code on the platform’s +`/cli` page, signs in through the existing WorkOS flow if needed, checks the request, +and approves. The phone polls signed `GET /api/identities/me` every two seconds +while foregrounded, up to the code’s ten-minute expiry. That existing person and +email become the connected account. An npub is an identifier; possession of the +approved private key authenticates requests. The CLI’s private key is never copied. + +`DeviceIdentity.swift` uses P256K 0.21.1 (Bitcoin Core’s libsecp256k1) for BIP-340. +NIP-98 binds the actual HTTP URL, method, body hash, and clock. A WebSocket’s tag +names the HTTP upgrade URL, not `wss:`. Every HTTP path (history, attachments, +icons and previews included) and live socket signs through `FragmentAPI`. +Credentials go only to the selected platform origin. Signed sessions are ephemeral, +hold no browser cookies, and refuse redirects. Release accepts HTTPS origins; +Debug also permits loopback and local development hostnames over HTTP. + +Keychain stores the origin, private key and approved account together, using +`kSecAttrAccessibleWhenUnlockedThisDeviceOnly`, without synchronization. No key in +UserDefaults, logs, URLs, a fragment page, or its JavaScript. A 401 is checked +against the identity endpoint and returns the app to connection when authorization +has ended. Disconnect revokes this phone’s key through the existing key API and +removes its local account data; it leaves other devices and browser sessions alone. +A failed/offline revocation is shown as an error, not reported as a successful logout. +The separate Remove from this iPhone action clears local credentials and drafts +after confirmation, explicitly leaving server revocation for the website. This +also lets a person leave an unreachable server without getting stuck. + +## What an agent's runtime offers + +Each runtime offers what it supports, and we don't build parity across +runtimes (AGENTS.md). The app offers nothing per agent beyond its chats today +(no Profile, Screen or agent skills), which is right for every runtime, so it +reads no runtime. Should it show one of those, the platform would report what +each agent offers, an add-only `offers` on the agent's status, rather than the +app copying the shell's table (`cell/shell/agent-runtimes.js`). Renaming an +agent rewrites its job (SOUL.md) as well as its title, a rule the shell and the +CLI hold, so the app leaves it to the web until one route does both. + +## Apps and brains in web sheets + +A signed `POST /api/f/{name}/session` reuses the registry’s existing single-use +redemption and top-level fragment session. It returns a one-fragment `__signin` +URL, good for 60 seconds, that the sheet’s nonpersistent WKWebView opens. Only +that fragment’s cookie enters the webview. Native authorization headers and private +keys never do. A stranger’s app asks for consent before learning the person’s +identity; owned/shared apps use their existing membership. General access and +membership permissions still belong to each fragment. + +Each approved native key holds one internal parent session, expiring after 30 days, +which cannot authenticate as a browser’s platform session. The registry checks the +bound key when redeeming and on every derived session lookup; revoking the key +invalidates existing web cookies and pending redemptions. Existing per-parent +redemption and per-fragment session bounds apply. Expired parents are replaced on +the next authenticated mint. The key itself follows normal CLI-key revocation, +not a separate mobile expiration rule. Schema step: `0009_key_sessions.sql`. + +The viewer follows same-origin navigation and sends unrelated clicked web links +to the system browser. It validates the redemption against the fragment’s canonical +origin, reports main-frame HTTP errors, and supports swipe dismissal. Add is a +native prompt sheet, matching the web shell’s built-in Add hand; there is no real +server fragment named `add-app`. + +## Local data and reliability + +Real-account drafts and pending sends are protected, atomic files in Application +Support, scoped by platform and person and excluded from backup. Bounds: 32 saved +chats, one MiB per file, 16 pending sends per chat, eight attachments per message. +The complete post and its original id are saved before HTTP. After termination, +ambiguous sends appear for explicit retry with that same id/body; they are never +silently resent with a new id. The channel takes the same id and body again as +the same post (`docs/chat-records.md`). A refusal (4xx, but 408 and 429) is final: +the post leaves the outbox at once and its words return to the composer, with the +server's message. Draft edits are debounced and flushed on leaving a chat or +backgrounding. Empty drafts are removed. + +The app keeps no copy of a limit the server enforces: it shows the server's +refusal. Its own bounds are its storage's (above) and what it sends over a +phone's network: an attachment of at most 25 MiB, and an icon fitted inside +256×256 on the phone, as the server would fit it. + +JSON text subscriptions match the Worker’s live protocol. Connected accounts follow the signed list watcher, discard +stale list reloads, and mark visible, caught-up chats read. HTTP/WebSocket +interruption recovery, independent cursors, Stop and history remain unchanged. Push delivery, approval-card controls, imported-chat continuation, and +full Markdown tables are not implemented. Notification permission alone does not +deliver background replies. + +## Validation and rollout + +`cargo xtask check` checks guards, schema lock, host tests and host/wasm Clippy. +`cargo xtask e2e --only signin,identities,native,restart` covers native-key web redemption, +unknown/revoked/agent keys, unsafe returns, one-use and wrong-fragment refusal, +consent, revocation isolation, and session survival across graceful restart/crash. +The existing sign-in lanes also exercise actual WorkOS/CLI browser approval. +Native tests cover NIP-98 URL/method/body/time binding and Schnorr verification, +credential origin boundaries, Keychain persistence/deletion, account-scoped drafts, +original-id outbox recovery, and the existing transport and UI flows. + +For local commands, see `ios/README.md`. The app always opens on Connect until a +key is approved. Debug accepts loopback and `.localhost` HTTP origins, and +`--pair-server ` fills in Connect for `cargo xtask dev`. + +Hosted use needs the server changes deployed for private app/brain web sheets; +existing signed native API calls reuse the already deployed CLI authentication. +Deploy to preview, approve a phone key there, and verify an existing chat, stream, +Stop, private app/brain, membership removal and key revocation before production. +Run the same flows on a physical iPhone before TestFlight. Signing/team identifiers, +privacy disclosures and App Store submission remain release work. No deployment, +production account enrollment, or paid model call is authorized by this document. diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 000000000..078b7fb95 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,2 @@ +xcuserdata/ +*.xcuserstate diff --git a/ios/Fragment.xcodeproj/project.pbxproj b/ios/Fragment.xcodeproj/project.pbxproj new file mode 100644 index 000000000..42fe30b06 --- /dev/null +++ b/ios/Fragment.xcodeproj/project.pbxproj @@ -0,0 +1,536 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 08013A491653D5236024FE71 /* AppAppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84A48915AD6415232B1F460 /* AppAppearanceView.swift */; }; + 0891E14C0D3DD0876D0C138F /* ChatSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB5B600A80E7BE0157A6E77 /* ChatSession.swift */; }; + 1FA3D11FABC00DC40F41A041 /* FragmentWebSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014FC5154C974ED4F457D381 /* FragmentWebSheet.swift */; }; + 2245C93502274850A29D08C3 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD99E857900FD0F65CD6B108 /* ChatView.swift */; }; + 2A5C821580EECE3D7D8C7F3D /* MarkdownText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214C3D2F32BD4FA5AB5CDF41 /* MarkdownText.swift */; }; + 2D65CF61D8311C8A8A6B3271 /* AccountConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFC16F9330AE76994AAFAFC /* AccountConnection.swift */; }; + 4024A3397E46CADB1EC9D344 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48A78B0A3D4F8A90EEBD8DB /* Models.swift */; }; + 48CB6E8D2ED7B6FD4CFC7BF1 /* AppSettingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C665A34A78C1B7DD934F0A8F /* AppSettingsAPI.swift */; }; + 4FEE2A2E44CD058FD4666F85 /* ShellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C581195A0FDAC2EA88D081F /* ShellTests.swift */; }; + 5241090BFD2229E2945804AA /* P256K in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2A4999E690DD7D41154B0B /* P256K */; }; + 55978B58D06B7028C7799CB2 /* Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2E702AC579841626A1A67C /* Stub.swift */; }; + 5938CA76ED5569461BA445AB /* AppSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */; }; + 667D1E87E74EA4792B9BDDA8 /* AppCreationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE5117BADF017C3007F84F89 /* AppCreationSheet.swift */; }; + 73DEE42439F9219AE3FE0A67 /* AppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A419CCC396DDBF76202E0D42 /* AppSettingsView.swift */; }; + 73EE483533F3A6306B65148C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D261EC9048255D1B31FED623 /* HomeView.swift */; }; + 74B3174E28BE9F9B4968F808 /* AuthenticatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63AC3B6E5464205E0D215570 /* AuthenticatedImage.swift */; }; + 78F77F1984EC48618CA7801A /* FragmentUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E480260C706AE0F972CA37 /* FragmentUITests.swift */; }; + 7A8D5455AD2EABF343F2905B /* SearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F75C83DE72E8D3B8797E1 /* SearchTests.swift */; }; + 8356DCE8D6F00248A2D4A6A9 /* ShellViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E90511DA6D6F765EAF42F51 /* ShellViews.swift */; }; + 9B04659A7CA2A9C6E5F0276A /* FragmentApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5376FE0C2DCB1863EE49C7 /* FragmentApp.swift */; }; + 9B71537B7D2B70A697A383A2 /* TransportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1E859F65B5B4D304FF0D04 /* TransportTests.swift */; }; + 9CE04EDF8132C39D18DF1A9D /* ChatMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A9F828339CA70751D48F66 /* ChatMedia.swift */; }; + 9E3CA1CA0F64F5776602A767 /* ThirdPartyNotices.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6126FEDF8DC438F0E3C2A5EC /* ThirdPartyNotices.txt */; }; + A0F935E5B3AB7E00AF2297A0 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB0814CDF8824F8A8657129D /* MarkdownTests.swift */; }; + A6AE254FEF924590C893CC8E /* ChatDraftStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F151AE36F242D72835ED4D35 /* ChatDraftStore.swift */; }; + A8391B829C662A475899B98C /* TimelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C75B4172B10109A56753F7A /* TimelineTests.swift */; }; + A840242666B7A68F3A3EE5CA /* DeviceIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF4D6F0F154CDADA715DD37 /* DeviceIdentity.swift */; }; + A8A038F320ECBCC025021D8B /* DeviceIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */; }; + B00F01210DFD198F6F1180C9 /* FragmentAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F7C65701872682E560E008F /* FragmentAPI.swift */; }; + B0CC33D54ED91967CDED0F0D /* AppSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCEF88547BE59E217E5B219 /* AppSharingView.swift */; }; + CC7761F962CB619DD2A5BB51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */; }; + E4358E209B49F024F52BDCAE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 811C73B367475E8B5D4DB77F /* AppIcon.icon */; }; + ED598966FDB4DD6D3F4486A1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + A00A083525D87C2EA533CDDC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03B62A6F44730F69CF29B303 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A7A7E910E323B68F306752CC; + remoteInfo = Fragment; + }; + F1A7D59A573F9AE091A5072B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03B62A6F44730F69CF29B303 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A7A7E910E323B68F306752CC; + remoteInfo = Fragment; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 014FC5154C974ED4F457D381 /* FragmentWebSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentWebSheet.swift; sourceTree = ""; }; + 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsTests.swift; sourceTree = ""; }; + 1AFC16F9330AE76994AAFAFC /* AccountConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountConnection.swift; sourceTree = ""; }; + 214C3D2F32BD4FA5AB5CDF41 /* MarkdownText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownText.swift; sourceTree = ""; }; + 2C75B4172B10109A56753F7A /* TimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTests.swift; sourceTree = ""; }; + 2E90511DA6D6F765EAF42F51 /* ShellViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellViews.swift; sourceTree = ""; }; + 2F7C65701872682E560E008F /* FragmentAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentAPI.swift; sourceTree = ""; }; + 2FCEF88547BE59E217E5B219 /* AppSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSharingView.swift; sourceTree = ""; }; + 3A2F75C83DE72E8D3B8797E1 /* SearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTests.swift; sourceTree = ""; }; + 463D517A458071F0CD419B80 /* Fragment.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Fragment.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AE675325F3A8ED7EAE13267 /* FragmentTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = FragmentTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4FB5B600A80E7BE0157A6E77 /* ChatSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSession.swift; sourceTree = ""; }; + 58A68F1B6233D7E206EC07A8 /* FragmentUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = FragmentUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6126FEDF8DC438F0E3C2A5EC /* ThirdPartyNotices.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ThirdPartyNotices.txt; sourceTree = ""; }; + 63AC3B6E5464205E0D215570 /* AuthenticatedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedImage.swift; sourceTree = ""; }; + 74E480260C706AE0F972CA37 /* FragmentUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentUITests.swift; sourceTree = ""; }; + 811C73B367475E8B5D4DB77F /* AppIcon.icon */ = {isa = PBXFileReference; path = AppIcon.icon; sourceTree = ""; }; + 8C581195A0FDAC2EA88D081F /* ShellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellTests.swift; sourceTree = ""; }; + 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentityTests.swift; sourceTree = ""; }; + 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + A419CCC396DDBF76202E0D42 /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; + AB0814CDF8824F8A8657129D /* MarkdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = ""; }; + AD99E857900FD0F65CD6B108 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; + B84A48915AD6415232B1F460 /* AppAppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearanceView.swift; sourceTree = ""; }; + BE5117BADF017C3007F84F89 /* AppCreationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCreationSheet.swift; sourceTree = ""; }; + C665A34A78C1B7DD934F0A8F /* AppSettingsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsAPI.swift; sourceTree = ""; }; + CDF4D6F0F154CDADA715DD37 /* DeviceIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentity.swift; sourceTree = ""; }; + D261EC9048255D1B31FED623 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + D48A78B0A3D4F8A90EEBD8DB /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + D8A9F828339CA70751D48F66 /* ChatMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMedia.swift; sourceTree = ""; }; + DC2E702AC579841626A1A67C /* Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stub.swift; sourceTree = ""; }; + DE1E859F65B5B4D304FF0D04 /* TransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportTests.swift; sourceTree = ""; }; + EE5376FE0C2DCB1863EE49C7 /* FragmentApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentApp.swift; sourceTree = ""; }; + F151AE36F242D72835ED4D35 /* ChatDraftStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDraftStore.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EDFA061F064725017EC50210 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5241090BFD2229E2945804AA /* P256K in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 24C24A92D6AB3222BA612400 /* UITests */ = { + isa = PBXGroup; + children = ( + 74E480260C706AE0F972CA37 /* FragmentUITests.swift */, + ); + path = UITests; + sourceTree = ""; + }; + 57B3D4FF633D4590AED50E36 = { + isa = PBXGroup; + children = ( + CD982D4FA51CD976EE122B12 /* Sources */, + DE9A26D5B2874890535BB2B5 /* Tests */, + 24C24A92D6AB3222BA612400 /* UITests */, + 904A365FDF599B8D2EE5FAF7 /* Products */, + ); + sourceTree = ""; + }; + 904A365FDF599B8D2EE5FAF7 /* Products */ = { + isa = PBXGroup; + children = ( + 463D517A458071F0CD419B80 /* Fragment.app */, + 4AE675325F3A8ED7EAE13267 /* FragmentTests.xctest */, + 58A68F1B6233D7E206EC07A8 /* FragmentUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + CD982D4FA51CD976EE122B12 /* Sources */ = { + isa = PBXGroup; + children = ( + 1AFC16F9330AE76994AAFAFC /* AccountConnection.swift */, + B84A48915AD6415232B1F460 /* AppAppearanceView.swift */, + BE5117BADF017C3007F84F89 /* AppCreationSheet.swift */, + 811C73B367475E8B5D4DB77F /* AppIcon.icon */, + C665A34A78C1B7DD934F0A8F /* AppSettingsAPI.swift */, + A419CCC396DDBF76202E0D42 /* AppSettingsView.swift */, + 2FCEF88547BE59E217E5B219 /* AppSharingView.swift */, + 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */, + 63AC3B6E5464205E0D215570 /* AuthenticatedImage.swift */, + F151AE36F242D72835ED4D35 /* ChatDraftStore.swift */, + D8A9F828339CA70751D48F66 /* ChatMedia.swift */, + 4FB5B600A80E7BE0157A6E77 /* ChatSession.swift */, + AD99E857900FD0F65CD6B108 /* ChatView.swift */, + CDF4D6F0F154CDADA715DD37 /* DeviceIdentity.swift */, + 2F7C65701872682E560E008F /* FragmentAPI.swift */, + EE5376FE0C2DCB1863EE49C7 /* FragmentApp.swift */, + 014FC5154C974ED4F457D381 /* FragmentWebSheet.swift */, + D261EC9048255D1B31FED623 /* HomeView.swift */, + 214C3D2F32BD4FA5AB5CDF41 /* MarkdownText.swift */, + D48A78B0A3D4F8A90EEBD8DB /* Models.swift */, + 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */, + 2E90511DA6D6F765EAF42F51 /* ShellViews.swift */, + 6126FEDF8DC438F0E3C2A5EC /* ThirdPartyNotices.txt */, + ); + path = Sources; + sourceTree = ""; + }; + DE9A26D5B2874890535BB2B5 /* Tests */ = { + isa = PBXGroup; + children = ( + 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */, + 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */, + AB0814CDF8824F8A8657129D /* MarkdownTests.swift */, + 3A2F75C83DE72E8D3B8797E1 /* SearchTests.swift */, + 8C581195A0FDAC2EA88D081F /* ShellTests.swift */, + DC2E702AC579841626A1A67C /* Stub.swift */, + 2C75B4172B10109A56753F7A /* TimelineTests.swift */, + DE1E859F65B5B4D304FF0D04 /* TransportTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2938405DCF0BB5C112A3CCBC /* FragmentTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 06E292003E7442D1D8B9D829 /* Build configuration list for PBXNativeTarget "FragmentTests" */; + buildPhases = ( + 080E6F0D2D8AA2215A48F2BD /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 84230630DAFCC3A085FC53F3 /* PBXTargetDependency */, + ); + name = FragmentTests; + productName = FragmentTests; + productReference = 4AE675325F3A8ED7EAE13267 /* FragmentTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 8C52024F3CFAFD685787A04E /* FragmentUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FD9D6F862081CA8A2E8B54E2 /* Build configuration list for PBXNativeTarget "FragmentUITests" */; + buildPhases = ( + 632EE043D767158F99D6998A /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 059CA4C243EC9281F90C4DA0 /* PBXTargetDependency */, + ); + name = FragmentUITests; + productName = FragmentUITests; + productReference = 58A68F1B6233D7E206EC07A8 /* FragmentUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + A7A7E910E323B68F306752CC /* Fragment */ = { + isa = PBXNativeTarget; + buildConfigurationList = 90E614231237E44BB37C8991 /* Build configuration list for PBXNativeTarget "Fragment" */; + buildPhases = ( + EC4D398A1EE8D1812A34893E /* Sources */, + 10FAF92375959704DAF502C2 /* Resources */, + EDFA061F064725017EC50210 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Fragment; + packageProductDependencies = ( + 9E2A4999E690DD7D41154B0B /* P256K */, + ); + productName = Fragment; + productReference = 463D517A458071F0CD419B80 /* Fragment.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 03B62A6F44730F69CF29B303 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 2938405DCF0BB5C112A3CCBC = { + ProvisioningStyle = Automatic; + }; + 8C52024F3CFAFD685787A04E = { + ProvisioningStyle = Automatic; + TestTargetID = A7A7E910E323B68F306752CC; + }; + A7A7E910E323B68F306752CC = { + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = CD4001B831A37D88BA3B41F6 /* Build configuration list for PBXProject "Fragment" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 57B3D4FF633D4590AED50E36; + packageReferences = ( + 1A66842FDD099C8FE13F9F7E /* XCRemoteSwiftPackageReference "swift-secp256k1" */, + ); + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7A7E910E323B68F306752CC /* Fragment */, + 2938405DCF0BB5C112A3CCBC /* FragmentTests */, + 8C52024F3CFAFD685787A04E /* FragmentUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 10FAF92375959704DAF502C2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E4358E209B49F024F52BDCAE /* AppIcon.icon in Resources */, + CC7761F962CB619DD2A5BB51 /* Assets.xcassets in Resources */, + 9E3CA1CA0F64F5776602A767 /* ThirdPartyNotices.txt in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 080E6F0D2D8AA2215A48F2BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5938CA76ED5569461BA445AB /* AppSettingsTests.swift in Sources */, + A8A038F320ECBCC025021D8B /* DeviceIdentityTests.swift in Sources */, + A0F935E5B3AB7E00AF2297A0 /* MarkdownTests.swift in Sources */, + 7A8D5455AD2EABF343F2905B /* SearchTests.swift in Sources */, + 4FEE2A2E44CD058FD4666F85 /* ShellTests.swift in Sources */, + 55978B58D06B7028C7799CB2 /* Stub.swift in Sources */, + A8391B829C662A475899B98C /* TimelineTests.swift in Sources */, + 9B71537B7D2B70A697A383A2 /* TransportTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 632EE043D767158F99D6998A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78F77F1984EC48618CA7801A /* FragmentUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EC4D398A1EE8D1812A34893E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D65CF61D8311C8A8A6B3271 /* AccountConnection.swift in Sources */, + 08013A491653D5236024FE71 /* AppAppearanceView.swift in Sources */, + 667D1E87E74EA4792B9BDDA8 /* AppCreationSheet.swift in Sources */, + 48CB6E8D2ED7B6FD4CFC7BF1 /* AppSettingsAPI.swift in Sources */, + 73DEE42439F9219AE3FE0A67 /* AppSettingsView.swift in Sources */, + B0CC33D54ED91967CDED0F0D /* AppSharingView.swift in Sources */, + 74B3174E28BE9F9B4968F808 /* AuthenticatedImage.swift in Sources */, + A6AE254FEF924590C893CC8E /* ChatDraftStore.swift in Sources */, + 9CE04EDF8132C39D18DF1A9D /* ChatMedia.swift in Sources */, + 0891E14C0D3DD0876D0C138F /* ChatSession.swift in Sources */, + 2245C93502274850A29D08C3 /* ChatView.swift in Sources */, + A840242666B7A68F3A3EE5CA /* DeviceIdentity.swift in Sources */, + B00F01210DFD198F6F1180C9 /* FragmentAPI.swift in Sources */, + 9B04659A7CA2A9C6E5F0276A /* FragmentApp.swift in Sources */, + 1FA3D11FABC00DC40F41A041 /* FragmentWebSheet.swift in Sources */, + 73EE483533F3A6306B65148C /* HomeView.swift in Sources */, + 2A5C821580EECE3D7D8C7F3D /* MarkdownText.swift in Sources */, + 4024A3397E46CADB1EC9D344 /* Models.swift in Sources */, + ED598966FDB4DD6D3F4486A1 /* SettingsView.swift in Sources */, + 8356DCE8D6F00248A2D4A6A9 /* ShellViews.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 059CA4C243EC9281F90C4DA0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A7A7E910E323B68F306752CC /* Fragment */; + targetProxy = F1A7D59A573F9AE091A5072B /* PBXContainerItemProxy */; + }; + 84230630DAFCC3A085FC53F3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A7A7E910E323B68F306752CC /* Fragment */; + targetProxy = A00A083525D87C2EA533CDDC /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 11AABB6FF516E863920DE831 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Finite; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Connect to your local Finite development server."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.fragment; + }; + name = Debug; + }; + 39043B6B29F74B1CD6D7B46D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_TESTABILITY = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 0.3; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 46F94C0327E16DB510D0325F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + GENERATE_INFOPLIST_FILE = YES; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.FragmentTests; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Fragment.app/Fragment"; + }; + name = Debug; + }; + 85070E8A2550D2985A5F1D51 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Finite; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.fragment; + }; + name = Release; + }; + 90798D034C4F2238C203CAB0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GENERATE_INFOPLIST_FILE = YES; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.FragmentUITests; + TEST_TARGET_NAME = Fragment; + }; + name = Release; + }; + 993DEAAD33886E4311D22B12 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GENERATE_INFOPLIST_FILE = YES; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.FragmentUITests; + TEST_TARGET_NAME = Fragment; + }; + name = Debug; + }; + C87C160775B2F59D7B9C4EAD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + GENERATE_INFOPLIST_FILE = YES; + PRODUCT_BUNDLE_IDENTIFIER = build.finite.FragmentTests; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Fragment.app/Fragment"; + }; + name = Release; + }; + F16EC6677DE2791A6CE83756 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_TESTABILITY = YES; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 0.3; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 06E292003E7442D1D8B9D829 /* Build configuration list for PBXNativeTarget "FragmentTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 46F94C0327E16DB510D0325F /* Debug */, + C87C160775B2F59D7B9C4EAD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 90E614231237E44BB37C8991 /* Build configuration list for PBXNativeTarget "Fragment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 11AABB6FF516E863920DE831 /* Debug */, + 85070E8A2550D2985A5F1D51 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + CD4001B831A37D88BA3B41F6 /* Build configuration list for PBXProject "Fragment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F16EC6677DE2791A6CE83756 /* Debug */, + 39043B6B29F74B1CD6D7B46D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + FD9D6F862081CA8A2E8B54E2 /* Build configuration list for PBXNativeTarget "FragmentUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 993DEAAD33886E4311D22B12 /* Debug */, + 90798D034C4F2238C203CAB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 1A66842FDD099C8FE13F9F7E /* XCRemoteSwiftPackageReference "swift-secp256k1" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/21-DOT-DEV/swift-secp256k1.git"; + requirement = { + kind = exactVersion; + version = 0.21.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 9E2A4999E690DD7D41154B0B /* P256K */ = { + isa = XCSwiftPackageProductDependency; + package = 1A66842FDD099C8FE13F9F7E /* XCRemoteSwiftPackageReference "swift-secp256k1" */; + productName = P256K; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 03B62A6F44730F69CF29B303 /* Project object */; +} diff --git a/ios/Fragment.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Fragment.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/ios/Fragment.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Fragment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Fragment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..323404c26 --- /dev/null +++ b/ios/Fragment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "6827adc91558847f00a4993c64addf8bc378062fab8b489eb1c301118b704e0b", + "pins" : [ + { + "identity" : "swift-secp256k1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/21-DOT-DEV/swift-secp256k1.git", + "state" : { + "revision" : "8c62aba8a3011c9bcea232e5ee007fb0b34a15e2", + "version" : "0.21.1" + } + } + ], + "version" : 3 +} diff --git a/ios/Fragment.xcodeproj/xcshareddata/xcschemes/Fragment.xcscheme b/ios/Fragment.xcodeproj/xcshareddata/xcschemes/Fragment.xcscheme new file mode 100644 index 000000000..a98015d14 --- /dev/null +++ b/ios/Fragment.xcodeproj/xcshareddata/xcschemes/Fragment.xcscheme @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Info.plist b/ios/Info.plist new file mode 100644 index 000000000..9bf37aa14 --- /dev/null +++ b/ios/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Finite + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSLocalNetworkUsageDescription + Connect to your local Finite development server. + NSMicrophoneUsageDescription + Record voice memos to send to your agents. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 000000000..be3e36dd5 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,157 @@ +# Finite for iOS + +A native SwiftUI client for Finite.Computer, organized like the web shell. iOS 26+, +Swift 6, Apple frameworks and pinned P256K 0.21.1 for CLI-compatible NIP-98 signing. +No Rust bindings, shared runtime, or web chat UI. App fragments and imported +conversations open in a native WKWebView sheet. Every request is signed by this +iPhone's own approved key: there is no unsigned or offline mode. + +## Connect an account + +Enter your Finite platform’s HTTPS address and tap Connect. Enter the phone’s +approval code at that server’s `/cli` page and approve the Finite-iOS request: +sign-in stays the website’s WorkOS flow. The phone keeps its own approved key in +Keychain; never import the CLI’s private key. Settings → Account connection +revokes and disconnects this phone. If its server is unreachable, Remove from +this iPhone clears the saved connection locally; the key still needs revoking on +the web. Private apps and brains open through `POST /api/f/{name}/session`. See +[the integration contract](../docs/ios.md). + +## Run against a local platform + +The app talks to a real platform, never a stand-in. For development that is +`cargo xtask dev` (the repository's dev stack, with its vendor fakes) on the Linux +machine at `:8790`. Forward the port to the Mac, so the Simulator's loopback +reaches it; macOS resolves `.fragment.localhost` to loopback too, so app +sheets load: + +```sh +ssh -N -L 8790:127.0.0.1:8790 linux # on the Mac, while you work +open ios/Fragment.xcodeproj +``` + +Choose the **Fragment** scheme and an iPhone Simulator, then Run. Debug builds +take HTTP loopback and `.localhost` origins; `--pair-server http://127.0.0.1:8790` +fills in Connect. Approve the code at `http://127.0.0.1:8790/cli`, signed in +through the dev stack's WorkOS fake. The checked-in Xcode project needs no +generator; Xcode resolves the pinned signing package. + +From a terminal: + +```sh +xcrun simctl list devices available +FRAGMENT_IOS_SIMULATOR='paste-an-iOS-26.5-iPhone-udid-here' +xcodebuild -project ios/Fragment.xcodeproj -scheme Fragment -configuration Debug -destination "platform=iOS Simulator,id=$FRAGMENT_IOS_SIMULATOR" -derivedDataPath target/ios build +xcrun simctl boot "$FRAGMENT_IOS_SIMULATOR" # if it is not booted +xcrun simctl install "$FRAGMENT_IOS_SIMULATOR" target/ios/Build/Products/Debug-iphonesimulator/Fragment.app +xcrun simctl launch "$FRAGMENT_IOS_SIMULATOR" build.finite.fragment --pair-server http://127.0.0.1:8790 +open -a Simulator +``` + +A physical phone reaches a platform over HTTPS; Release builds take nothing else. + +## Scope + +- Opens on the fullscreen search/new-chat launcher over the exact web desktop + wallpaper, with system light/dark variants. System typography and iOS 26 glass + controls throughout. Product identity is Finite.Computer (Finite for short). +- A left drawer follows horizontal dismissal drags, fading the backdrop with + the distance and settling closed or back in place on release. Vertical chat + scrolling stays independent, and settling respects Reduce Motion. It mirrors + the web: agent menus and recent chats grouped by lead + agent, group-chat faces, History, Your Apps, Settings. The launcher selects an agent; + agent hero menus and chat-row context menus can add more agents, and a group composer addresses one or all. +- Your Apps is a native list over the shared photo background, with Add an app. Add opens the built-in app + prompt sheet; choosing an idea returns a draft to the launcher. + The top-right Manage Apps button opens a searchable list including hidden and + archived apps; each visible app also has a settings button. App Settings covers + naming, personal show/hide/archive, sharing links, member permissions and + invitations, icon generation/upload/removal, and preview generation. + No duplicate suggestions/empty state in the app library itself. +- Dismissible app sheets leave the conversation in place; a bounded in-memory + session cache retains composer drafts, pending post ids and cursors across navigation. +- Native message author identities, Markdown, timestamps, long-press Reply/Copy/ + Share, quoted replies, file import, and AAC voice memos (five minutes maximum). + Attachments use the existing blob API and chat record shape: eight per message, + 25 MiB per file. Download/share and audio playback are native. +- Connected accounts follow the signed list watcher and mark visible chats read. +- Existing HTTP/WebSocket streaming, Stop, same-id retry, independent cursors, + backwards paging, reconnect and foreground resumption remain. +- History searches message contents and lists archived chats and read-only imports + from the Session Archive's actual `index/chat.json` and lexical search contract. +- Native settings categories mirror web organization: account, billing, computer, + agents, skills, connections, developer setup, data, and this iPhone, all read + and written through signed platform calls. Account connection enrolls or + revokes this iPhone’s own key; billing, OAuth setup and renaming an agent + continue on the web (an agent's name is its job's too). No simulated purchases. +- Microphone and notification permission are native. Background reply delivery + still requires production push integration; permission alone does not deliver it. + +Push delivery, interactive approval cards, full Markdown tables and imported-chat +continuation are separate work. Drafts and exact pending sends survive relaunch; +message history is read again from the platform. Unknown records stay readable; +approval cards still direct you to the web. + +## Icon and composer + +The installed icon is `Sources/AppIcon.icon`, a layered Icon Composer document. +Its transparent vector foreground preserves the hand paths in +`cell/shell/finite-logo.svg`, centered at 680 points on a 1024-point canvas over +an independent charcoal background. Corners, lighting and appearance variants +are compiled by Xcode rather than painted into the artwork, following +[Apple’s app icon guidance](https://developer.apple.com/design/human-interface-guidelines/app-icons). +XcodeGen 2.42 needs the `icon` file-type mapping in `project.yml` to keep the +package intact. Edit the document in Icon Composer and regenerate the project. + +The chat composer has a single glass surface, 44-point controls and a 34-point +send/stop circle with semantic foreground/background colors. Chat addressing is +in the sidebar agent menu’s Agents in this chat → Messages setting, rather than +a caption above the composer. Your Apps displays each fragment’s original +`picture` (its versioned icon URL), preserving its transparency and aspect ratio. + +## Verification + +```sh +xcodebuild -project ios/Fragment.xcodeproj -scheme Fragment -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' -derivedDataPath target/ios test +``` + +The unit tests need no server and spend nothing. `TransportTests`, +`ShellTests`, `SearchTests` and `AppSettingsTests` run the app's real HTTP and +live-socket code against a stub of the platform's answers (`Tests/Stub.swift`: +a `URLProtocol` for HTTP, and a scripted `LiveSocket`, since `URLProtocol` never +sees a WebSocket). They cover what a phone meets: an acknowledgement lost after +the post committed (retried with the same id and body), a refusal (final: the +post leaves the outbox and its words return to the composer), a rate limit +(kept to retry), a dropped stream (resubscribed from its cursor, nothing twice), +Stop, earlier pages that never move the live cursor, an ended authorization, +and the requests behind lists, joins, renames, archiving, attachments, search, +sharing and icons. `TimelineTests` cover ordering, cursors, drafts and turns; +`DeviceIdentityTests` NIP-98 binding, origins, Keychain and the local outbox; +`MarkdownTests` a code fence arriving in parts. The stubs' answers are written +by hand from docs/api.md. + +`FragmentUITests` drive what needs no server: Connect, and an unreachable +server. They expect a simulator with no connected account +(`xcrun simctl keychain booted reset`). Flows against a paired account on the +dev stack are their own suite. + +The source of the generated project is `project.yml`. If changing project +structure, use XcodeGen 2.42+ and commit its result: + +```sh +xcodegen generate --spec ios/project.yml +``` + +## Design references + +The signed-in home follows Fragment's own fullscreen Command-K launcher +(`cell/shell/shell.js`): search everything and default to starting a new chat, +replacing an older three-recents home (`finitechat/ios/Sources/FocusedAppShell.swift` +at `finite-mono` commit `a1d5089c^`). + +[Enchanted](https://github.com/gluonfield/enchanted) supplies a native iOS +chat reference for conversation history, Markdown and a configurable server. +[Claude's iOS app](https://claude.com/resources/articles/team-plan-and-ios) +is a reference for a focused conversation-first mobile experience. No source +code was copied. SwiftUI's native scrolling follows +[Apple's scroll geometry API](https://developer.apple.com/documentation/swiftui/view/onscrollgeometrychange(for:of:action:)). diff --git a/ios/Sources/AccountConnection.swift b/ios/Sources/AccountConnection.swift new file mode 100644 index 000000000..83494addd --- /dev/null +++ b/ios/Sources/AccountConnection.swift @@ -0,0 +1,185 @@ +import SwiftUI +import WebKit + +@MainActor @Observable +final class AccountConnection { + struct Code: Decodable { let code: String; let expiresAt: Int64 } + var account: DeviceAccount? + var code: Code? + var error: String? + var busy = false + @ObservationIgnored private var cachedAPI: FragmentAPI? + var api: FragmentAPI? { + guard let account, account.approved, let key = try? account.signingKey else { return nil } + if let cachedAPI, cachedAPI.baseURL == account.origin, cachedAPI.identity.publicKey == key.publicKey, cachedAPI.accountID == account.identity { return cachedAPI } + let client = FragmentAPI(baseURL: account.origin, identity: key, accountID: account.identity) + cachedAPI = client + return client + } + init() { + do { + if let stored = try DeviceKeychain.read() { + guard try PlatformOrigin.parse(stored.origin.absoluteString) == stored.origin else { throw APIError.invalidResponse } + _ = try stored.signingKey + account = stored + } + } + catch { self.error = error.localizedDescription } + } + func begin(_ raw: String, newKey: Bool = false) async { + busy = true; error = nil; code = nil; defer { busy = false } + do { + let origin = try PlatformOrigin.parse(raw) + if let existing = account, existing.origin != origin && existing.approved { + throw APIError.server("Disconnect this iPhone before pairing a different account.") + } + if newKey || account == nil || account?.origin != origin { + let key = try DeviceIdentity() + let next = DeviceAccount(origin: origin, secret: key.secret, approved: false) + try DeviceKeychain.save(next); account = next + } + guard let account else { throw APIError.invalidResponse } + let api = try FragmentAPI(baseURL: origin, identity: account.signingKey) + if try await finish(api) { return } + code = try await api.request("cli/begin", method: "POST", body: .object([:])) + } catch { self.error = error.localizedDescription } + } + func poll() async { + guard let code, let account else { return } + do { + let api = try FragmentAPI(baseURL: account.origin, identity: account.signingKey) + // The same interval and ten-minute bound as fragment login. + for _ in 0..<300 { + try Task.checkCancellation() + guard Int64(Date().timeIntervalSince1970 * 1000) < code.expiresAt else { error = "This code expired. Start again for a new code."; return } + if try await finish(api) { self.code = nil; return } + try await Task.sleep(for: .seconds(2)) + } + error = "This code expired. Start again for a new code." + } catch { if !Task.isCancelled { self.error = error.localizedDescription } } + } + private func finish(_ api: FragmentAPI) async throws -> Bool { + do { + let person: JSONValue = try await api.request("api/identities/me") + try Task.checkCancellation() + guard var next = account, next.origin == api.baseURL, + try next.signingKey.publicKey == api.identity.publicKey else { throw CancellationError() } + guard person["kind"].string == "person", let identity = person["id"].string else { throw APIError.invalidResponse } + next.identity = identity; next.email = person["email"].string; next.approved = true + try DeviceKeychain.save(next); account = next; error = nil + return true + } catch APIError.unauthorized { return false } + } + func validate() async { + guard let api else { return } + do { + if try await finish(api) == false, account?.origin == api.baseURL, + (try? account?.signingKey.publicKey) == api.identity.publicKey { expire() } + } catch is CancellationError { return } + catch { self.error = error.localizedDescription } + } + func expire() { + guard var next = account, next.approved else { return } + next.approved = false + do { try DeviceKeychain.save(next); account = next; error = "This iPhone’s authorization ended. Pair again to reconnect." } + catch { self.error = error.localizedDescription; account = nil } + } + func disconnect() async { + busy = true; error = nil; defer { busy = false } + do { + let client = api + if let api = client, let account { + let _: JSONValue = try await api.request("api/identities/me/keys/\(try account.signingKey.publicKey)", method: "DELETE") + } + try await removeLocalAccount() + } catch { self.error = error.localizedDescription } + } + func forget() async { + busy = true; error = nil; defer { busy = false } + do { try await removeLocalAccount() } + catch { self.error = error.localizedDescription } + } + private func removeLocalAccount() async throws { + if let account { + let client = try FragmentAPI(baseURL: account.origin, identity: account.signingKey, accountID: account.identity) + try ChatDraftStore.removeAccount(api: client) + } + try DeviceKeychain.clear(); account = nil; code = nil; cachedAPI = nil + await WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: .distantPast) + } +} + +struct AccountConnectionView: View { + let connection: AccountConnection + var close: () -> Void = {} + @State private var server = "https://finite.chat" + @State private var replaceKey = false + @State private var forgetConnection = false + @Environment(\.openURL) private var openURL + @Environment(\.scenePhase) private var scenePhase + var body: some View { + NavigationStack { + Form { + Section { + Text("Finite.Computer").font(.title2.weight(.semibold)) + Text("Connect this iPhone to your account.").foregroundStyle(.secondary) + } + if let account = connection.account, account.approved { + Section("Connected") { + Text(account.email ?? "Your account") + Text(account.origin.absoluteString).font(.footnote).foregroundStyle(.secondary) + Button("Disconnect this iPhone", role: .destructive) { Task { await connection.disconnect() } } + .accessibilityIdentifier("disconnectAccount") + } + } else { + Section("Your Finite server") { + TextField("https://finite.chat", text: $server).textContentType(.URL).keyboardType(.URL) + .textInputAutocapitalization(.never).autocorrectionDisabled().accessibilityIdentifier("accountServer") + Button(connection.code == nil ? "Connect" : "Get a new code") { Task { await connection.begin(server) } } + .accessibilityIdentifier("pairAccount") + } + if let code = connection.code, let origin = connection.account?.origin { + Section("Approve this iPhone") { + Text(code.code).font(.system(.largeTitle, design: .monospaced).weight(.semibold)).textSelection(.enabled).accessibilityIdentifier("pairingCode") + Button("Copy code", systemImage: "doc.on.doc") { UIPasteboard.general.string = code.code } + Button("Open approval page", systemImage: "safari") { openURL(origin.appending(path: "cli")) } + Text("Enter this code on that page, check the Finite-iOS request, and approve. You can also use a browser on another device. Return here to finish connecting.").font(.footnote).foregroundStyle(.secondary) + ProgressView("Waiting for approval…") + } + } + if connection.account != nil && connection.code == nil { + Section { Button("Create a new device key") { replaceKey = true } } + } + } + if connection.account != nil { + Section { + Button("Remove from this iPhone", role: .destructive) { forgetConnection = true } + .accessibilityIdentifier("forgetAccount") + } footer: { + Text("Use this if the server is unreachable. This removes the saved key and drafts from this iPhone; revoke its approved key from the website when you can.") + } + } + if connection.busy { ProgressView() } + if let error = connection.error { Section { Text(error).foregroundStyle(.secondary) } } + } + .navigationTitle("Connect Finite").navigationBarTitleDisplayMode(.inline) + .disabled(connection.busy) + .task { server = connection.account?.origin.absoluteString ?? initialServer } + .task(id: "\(connection.code?.code ?? "")|\(scenePhase)") { if connection.code != nil && scenePhase == .active { await connection.poll() } } + .onChange(of: connection.account?.approved) { _, approved in if approved == true { close() } } + .confirmationDialog("Remove this connection from this iPhone?", isPresented: $forgetConnection, titleVisibility: .visible) { + Button("Remove saved connection", role: .destructive) { Task { await connection.forget() } } + } message: { Text("The key may still be approved on the server. This removes only the key and saved drafts on this iPhone.") } + .confirmationDialog("Create a new device key?", isPresented: $replaceKey, titleVisibility: .visible) { + Button("Create new key") { Task { await connection.begin(server, newKey: true) } } + } message: { Text("Use this if the previous key was revoked. Other devices keep their own keys.") } + } + } + private var initialServer: String { + #if DEBUG + let args = ProcessInfo.processInfo.arguments + if let i = args.firstIndex(of: "--pair-server"), args.indices.contains(i + 1) { return args[i + 1] } + #endif + return "https://finite.chat" + } +} diff --git a/ios/Sources/AppAppearanceView.swift b/ios/Sources/AppAppearanceView.swift new file mode 100644 index 000000000..d10e66251 --- /dev/null +++ b/ios/Sources/AppAppearanceView.swift @@ -0,0 +1,131 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct AppAppearanceView: View { + let fragment: FragmentSummary + let library: Library + let api: FragmentAPI + @State private var details: AppDetails? + @State private var hint = "" + @State private var options: AppIconOptions? + @State private var preview: Data? + @State private var busy = false + @State private var making = false + @State private var importer = false + @State private var error: String? + @State private var previewRun: UUID? + @State private var previewSaid: String? + private var app: FragmentSummary { library.fragments.first { $0.name == fragment.name } ?? fragment } + private var canEdit: Bool { details?.canEdit ?? false } + private var inFlight: Bool { busy || making || previewRun != nil } + var body: some View { + Form { + Section("App icon") { + HStack { Spacer(); FragmentIcon(fragment: app, fallback: "app", size: 96); Spacer() }.padding(.vertical, 12) + if canEdit { + TextField("Describe an icon (optional)", text: $hint, axis: .vertical).lineLimit(1...3).accessibilityIdentifier("iconHint") + Button("Generate icons", systemImage: "sparkles") { Task { await makeIcons() } }.accessibilityIdentifier("makeAppIcons") + .disabled(inFlight || hint.count > 200) + Button("Upload icon…", systemImage: "square.and.arrow.up") { importer = true }.disabled(inFlight) + if app.picture != nil { + Button("Remove icon", systemImage: "trash", role: .destructive) { Task { await change { let _: JSONValue = try await api.request("api/f/\(app.name)/icon", method: "DELETE") } } } + .disabled(inFlight).accessibilityIdentifier("removeAppIcon") + } + } + } + if making { Section { ProgressView("Generating icons…") } } + if let options { + Section("Choose an icon") { + Text(options.subject).font(.subheadline).foregroundStyle(.secondary) + ForEach(Array(options.candidates.prefix(8).enumerated()), id: \.offset) { index, candidate in + if let data = candidate.data, let image = UIImage(data: data) { + Button { Task { await chooseIcon(data) } } label: { + HStack(spacing: 16) { + Image(uiImage: image).resizable().scaledToFit().frame(width: 72, height: 72) + Text("Use this icon") + Spacer() + }.padding(.vertical, 4) + }.disabled(inFlight).accessibilityIdentifier("useAppIcon-\(index)") + } + } + if options.candidates.isEmpty { Text("No icons came out right. Try another description.").foregroundStyle(.secondary) } + } + } + Section("Preview") { + if let preview, let image = UIImage(data: preview) { + Image(uiImage: image).resizable().scaledToFit().clipShape(RoundedRectangle(cornerRadius: 12)).accessibilityLabel("App preview").accessibilityIdentifier("appPreviewImage") + } else { Text("No preview yet.").foregroundStyle(.secondary) } + if canEdit && details?.visibility != "members" { + Button("Generate preview", systemImage: "camera") { previewSaid = nil; previewRun = UUID() }.disabled(inFlight).accessibilityIdentifier("makeAppPreview") + } else if canEdit { Text("Previews are available for apps shared by link or publicly. Restricted apps are not photographed.").font(.footnote).foregroundStyle(.secondary) } + if previewRun != nil { ProgressView("Taking a new preview…") } + if let previewSaid { Text(previewSaid).font(.footnote).foregroundStyle(.secondary) } + if let report = details?.page, !report.errors.isEmpty { + DisclosureGroup("Page reported \(report.errors.count) issues") { + ForEach(Array(report.errors.prefix(10).enumerated()), id: \.offset) { _, issue in + Text("\(issue.kind): \(issue.text)").font(.caption).textSelection(.enabled) + } + } + } + } + if busy { ProgressView("Saving…") } + if let error { Section { Text(error).font(.footnote).foregroundStyle(.secondary); Button("Reload appearance") { Task { await load() } } } } + } + .navigationTitle("Icon & Preview").navigationBarTitleDisplayMode(.inline) + .task { await load() } + .task(id: previewRun) { if previewRun != nil { await generatePreview() } } + .fileImporter(isPresented: $importer, allowedContentTypes: [.png, .jpeg, .gif, UTType("org.webmproject.webp") ?? .image]) { result in + switch result { + case .success(let url): Task { await upload(url) } + case .failure(let error): self.error = error.localizedDescription + } + } + } + private func load() async { + do { + async let status = api.appDetails(app.name) + async let image = api.appImage(app.name, kind: "card") + (details, preview) = try await (status, image); error = nil + } catch { self.error = error.localizedDescription } + } + private func change(_ action: () async throws -> Void) async { + busy = true; error = nil; defer { busy = false } + do { try await action(); await library.load(api) } + catch { self.error = error.localizedDescription } + } + private func chooseIcon(_ data: Data) async { + await change { try await api.putAppIcon(data, name: app.name) } + if error == nil { options = nil } + } + private func makeIcons() async { + making = true; options = nil; error = nil; defer { making = false } + do { options = try await api.makeAppIcons(app.name, hint: hint) } + catch { self.error = error.localizedDescription } + } + private func upload(_ url: URL) async { + let scoped = url.startAccessingSecurityScopedResource() + defer { if scoped { url.stopAccessingSecurityScopedResource() } } + await change { try await api.putAppIcon(AppIconImage.fitted(url), name: app.name) } + } + private func generatePreview() async { + defer { previewRun = nil } + do { + struct Asked: Decodable { let since: Int64 } + let asked: Asked = try await api.request("api/f/\(app.name)/card", method: "POST") + // Same bound as the web Apps page. The view's task cancels polling + // when dismissed; a slow capture is never reported as a success. + for _ in 0..<180 { + try Task.checkCancellation() + let status = try await api.appDetails(app.name) + if let report = status.page, report.at >= asked.since { + details = status + preview = try await api.appImage(app.name, kind: "card") + previewSaid = preview == nil ? "The page reported, but no preview image is available." : "Preview updated." + return + } + try await Task.sleep(for: .seconds(1)) + } + previewSaid = "The preview is still being taken. Reload appearance to check again." + } catch { if !Task.isCancelled { self.error = error.localizedDescription } } + } +} diff --git a/ios/Sources/AppCreationSheet.swift b/ios/Sources/AppCreationSheet.swift new file mode 100644 index 000000000..41b148966 --- /dev/null +++ b/ios/Sources/AppCreationSheet.swift @@ -0,0 +1,28 @@ +import SwiftUI + +// Like the web shell's Add hand, this starts an agent conversation; it is +// not an app named "add-app" on the server. +struct AppCreationSheet: View { + let start: (String) -> Void + @State private var idea = "" + var body: some View { + NavigationStack { + Form { + Section { + Text("Ask for an app").font(.title2.weight(.semibold)) + Text("Tell your agent who it’s for and what it should do.").foregroundStyle(.secondary) + TextField("Describe your app…", text: $idea, axis: .vertical).lineLimit(3...8) + Button("Start a chat", systemImage: "arrow.up") { start(idea) } + .disabled(idea.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || idea.utf8.count > 32_768) + } + Section("A few ideas") { + Button("A grocery list for the family") { start("Make a shared grocery list my family can use") } + Button("A reading list worth returning to") { start("Make a reading list for things I want to come back to") } + Button("A little morning briefing") { start("Every morning send me a summary of the sites I follow") } + } + } + .navigationTitle("Add an app").navigationBarTitleDisplayMode(.inline) + } + .presentationDetents([.large]).presentationDragIndicator(.visible) + } +} diff --git a/ios/Sources/AppIcon.icon/Assets/FiniteHand.svg b/ios/Sources/AppIcon.icon/Assets/FiniteHand.svg new file mode 100644 index 000000000..da33c374f --- /dev/null +++ b/ios/Sources/AppIcon.icon/Assets/FiniteHand.svg @@ -0,0 +1 @@ + diff --git a/ios/Sources/AppIcon.icon/icon.json b/ios/Sources/AppIcon.icon/icon.json new file mode 100644 index 000000000..244799fa2 --- /dev/null +++ b/ios/Sources/AppIcon.icon/icon.json @@ -0,0 +1,38 @@ +{ + "fill": { + "automatic-gradient": "extended-srgb:0.09412,0.09412,0.09412,1.00000" + }, + "groups": [ + { + "blend-mode": "normal", + "hidden": false, + "layers": [ + { + "glass": true, + "image-name": "FiniteHand.svg", + "name": "Finite Computer hand", + "position": { + "scale": 1, + "translation-in-points": [ + 0, + 0 + ] + } + } + ], + "lighting": "individual", + "shadow": { + "kind": "neutral", + "opacity": 0.15 + }, + "specular": true, + "translucency": { + "enabled": false, + "value": 0.5 + } + } + ], + "supported-platforms": { + "squares": "shared" + } +} diff --git a/ios/Sources/AppSettingsAPI.swift b/ios/Sources/AppSettingsAPI.swift new file mode 100644 index 000000000..fc180d584 --- /dev/null +++ b/ios/Sources/AppSettingsAPI.swift @@ -0,0 +1,137 @@ +import Foundation +import ImageIO +import UIKit +import UniformTypeIdentifiers + +struct AppDetails: Decodable, Sendable { + struct URLs: Decodable, Sendable { let canonical: URL } + struct Page: Decodable, Sendable { + struct Problem: Decodable, Sendable { let kind: String; let text: String } + let at: Int64 + var errors: [Problem] = [] + } + let owner: String + let role: String + let visibility: String + let urls: URLs + var viewToken: String? + var page: Page? + var canEdit: Bool { role == "owner" || role == "editor" } + var shareURL: URL? { + if visibility == "public" { return urls.canonical } + guard visibility == "link", role == "owner", let viewToken else { return nil } + var components = URLComponents(url: urls.canonical, resolvingAgainstBaseURL: false) + components?.queryItems = [URLQueryItem(name: "view", value: viewToken)] + return components?.url + } +} + +struct AppMember: Decodable, Identifiable, Sendable { + let principal: String + let role: String + var kind: String? + var owner: String? + var email: String? + var title: String? + var id: String { principal } +} + +struct AppInvite: Decodable, Identifiable, Sendable { + let email: String + let role: String + let expiresAt: Int64 + var id: String { email } +} + +struct AppIconOptions: Decodable, Sendable { + struct Candidate: Decodable, Identifiable, Sendable { + let png: String + let score: Double + var id: String { png } + var data: Data? { Data(base64Encoded: png) } + } + let subject: String + let candidates: [Candidate] +} + +enum AppAccess: String, CaseIterable { + case members, link, `public` + var title: String { + switch self { case .members: "Restricted"; case .link: "Anyone with the link"; case .public: "Public" } + } + var explanation: String { + switch self { + case .members: "Only people with access can open this app." + case .link: "Anyone with its share link can open this app." + case .public: "Anyone can find and open this app." + } + } +} + +enum AppPermission: String, CaseIterable { + case viewer, editor + var title: String { self == .viewer ? "Can view" : "Can edit" } +} + +extension FragmentAPI { + func appDetails(_ name: String) async throws -> AppDetails { try await request("api/f/\(name)/status") } + + func appMembers(_ name: String) async throws -> [AppMember] { + struct Response: Decodable { let members: [AppMember] } + let result: Response = try await request("api/f/\(name)/members") + return result.members + } + + func appInvites(_ name: String) async throws -> [AppInvite] { + struct Response: Decodable { let invites: [AppInvite] } + let result: Response = try await request("api/f/\(name)/invites") + return result.invites + } + + func appImage(_ name: String, kind: String) async throws -> Data? { + precondition(kind == "icon" || kind == "card") + var request = URLRequest(url: url("api/f/\(name)/\(kind)")) + request.cachePolicy = .reloadIgnoringLocalCacheData + request.timeoutInterval = 30 + let (data, response) = try await self.data(for: request) + guard let response = response as? HTTPURLResponse else { throw APIError.invalidResponse } + if response.statusCode == 404 { return nil } + guard response.statusCode == 200 else { throw APIError.server("The image could not be loaded (HTTP \(response.statusCode)).") } + guard data.count <= 2 * 1_024 * 1_024, UIImage(data: data) != nil else { throw APIError.invalidResponse } + return data + } + + func putAppIcon(_ data: Data, name: String) async throws { + var request = URLRequest(url: url("api/f/\(name)/icon")) + request.httpMethod = "PUT"; request.httpBody = data; request.timeoutInterval = 30 + request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") + let (body, response) = try await self.data(for: request) + guard let response = response as? HTTPURLResponse else { throw APIError.invalidResponse } + guard (200..<300).contains(response.statusCode) else { throw APIError.answer(body, status: response.statusCode) } + } + + func makeAppIcons(_ name: String, hint: String) async throws -> AppIconOptions { + var fields: [String: JSONValue] = [:] + let clean = hint.trimmingCharacters(in: .whitespacesAndNewlines) + if !clean.isEmpty { fields["hint"] = .string(clean) } + return try await request("api/f/\(name)/icon/make", method: "POST", body: .object(fields), timeout: 90) + } +} + +enum AppIconImage { + /// The server fits any icon it is sent inside 256×256 (docs/api.md, + /// Icons). Fitting a picked photo here first, without decoding it whole, + /// only spares the phone sending megabytes the server would shrink. + static let side = 256 + static func fitted(_ file: URL) throws -> Data { + let options = [kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceThumbnailMaxPixelSize: side] as CFDictionary + guard let source = CGImageSourceCreateWithURL(file as CFURL, nil), + let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else { throw APIError.server("Choose a PNG, JPEG, WebP or GIF image.") } + let png = NSMutableData() + guard let destination = CGImageDestinationCreateWithData(png, UTType.png.identifier as CFString, 1, nil) else { throw APIError.invalidResponse } + CGImageDestinationAddImage(destination, image, nil) + guard CGImageDestinationFinalize(destination) else { throw APIError.invalidResponse } + return png as Data + } +} diff --git a/ios/Sources/AppSettingsView.swift b/ios/Sources/AppSettingsView.swift new file mode 100644 index 000000000..d38750dfa --- /dev/null +++ b/ios/Sources/AppSettingsView.swift @@ -0,0 +1,104 @@ +import SwiftUI + +struct AppManagementList: View { + let library: Library + let api: FragmentAPI + @State private var search = "" + private var apps: [FragmentSummary] { + library.allApps.filter { search.isEmpty || $0.displayTitle.localizedCaseInsensitiveContains(search) } + } + var body: some View { + List { + ForEach(apps) { app in + NavigationLink { + AppSettingsView(fragment: app, library: library, api: api) + } label: { + HStack(spacing: 12) { + FragmentIcon(fragment: app, fallback: "app") + VStack(alignment: .leading, spacing: 4) { + Text(app.displayTitle) + if app.archived == true { Text("Archived").font(.caption).foregroundStyle(.secondary) } + else if app.dock == false { Text("Hidden from Your Apps").font(.caption).foregroundStyle(.secondary) } + } + }.padding(.vertical, 4) + }.accessibilityIdentifier("manageApp-\(app.name)") + } + if apps.isEmpty { ContentUnavailableView.search(text: search) } + if let error = library.error { Text(error).font(.footnote).foregroundStyle(.secondary) } + } + .navigationTitle("Manage Apps").navigationBarTitleDisplayMode(.inline) + .searchable(text: $search, prompt: "Find an app") + .refreshable { await library.load(api) } + .task { await library.load(api) } + } +} + +struct AppSettingsView: View { + let fragment: FragmentSummary + let library: Library + let api: FragmentAPI + @State private var details: AppDetails? + @State private var title = "" + @State private var busy = false + @State private var error: String? + private var app: FragmentSummary { library.fragments.first { $0.name == fragment.name } ?? fragment } + private var canEdit: Bool { details?.canEdit ?? false } + var body: some View { + Form { + Section { + HStack(spacing: 16) { + FragmentIcon(fragment: app, fallback: "app", size: 56) + VStack(alignment: .leading, spacing: 4) { + Text(app.displayTitle).font(.headline) + Text(details?.role == "owner" ? "Your app" : "Shared with you").font(.subheadline).foregroundStyle(.secondary) + } + }.padding(.vertical, 8) + } + Section("General") { + if canEdit { + TextField("App name", text: $title).accessibilityIdentifier("appName") + Button("Save name") { Task { await change { try await api.rename(app, title: title) } } } + .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title == app.displayTitle) + } + Toggle("Show in Your Apps", isOn: Binding( + get: { app.dock != false }, + set: { value in Task { await change { let _: JSONValue = try await api.request("api/fragments/\(app.name)/dock", method: "PUT", body: .object(["dock": .bool(value)])) } } } + )).tint(.green).disabled(app.archived == true).accessibilityIdentifier("appShown") + if app.archived == true { Text("Unarchive this app to show it in Your Apps.").font(.footnote).foregroundStyle(.secondary) } + } + Section { + NavigationLink { + AppSharingView(fragment: app, library: library, api: api) + } label: { Label("Sharing & Permissions", systemImage: "person.2") }.accessibilityIdentifier("appSharing") + NavigationLink { + AppAppearanceView(fragment: app, library: library, api: api) + } label: { Label("Icon & Preview", systemImage: "photo") }.accessibilityIdentifier("appAppearance") + } + if let address = details?.urls.canonical { + Section("App address") { + Text(address.absoluteString).font(.footnote).textSelection(.enabled) + ShareLink(item: address) { Label("Share app address", systemImage: "square.and.arrow.up") } + } + } + Section { + Button(app.archived == true ? "Unarchive app" : "Archive app", systemImage: "archivebox") { + Task { await change { let _: JSONValue = try await api.request("api/fragments/\(app.name)/archived", method: "PUT", body: .object(["archived": .bool(app.archived != true)])) } } + }.accessibilityIdentifier("archiveApp") + } footer: { Text("Showing, hiding, and archiving change your own app list. They don’t change who can access the app.") } + if busy { ProgressView("Saving…") } + if let error { Section { Text(error).font(.footnote).foregroundStyle(.secondary); Button("Try again") { Task { await load() } } } } + } + .disabled(busy) + .navigationTitle("App Settings").navigationBarTitleDisplayMode(.inline) + .task { if details == nil { title = app.displayTitle; await load() } } + } + private func load() async { + do { details = try await api.appDetails(app.name); error = nil } + catch { self.error = error.localizedDescription } + } + private func change(_ action: () async throws -> Void) async { + busy = true; error = nil; defer { busy = false } + do { try await action(); await library.load(api); await load() } + catch { self.error = error.localizedDescription } + } +} diff --git a/ios/Sources/AppSharingView.swift b/ios/Sources/AppSharingView.swift new file mode 100644 index 000000000..2bafad398 --- /dev/null +++ b/ios/Sources/AppSharingView.swift @@ -0,0 +1,123 @@ +import SwiftUI + +struct AppSharingView: View { + let fragment: FragmentSummary + let library: Library + let api: FragmentAPI + @State private var details: AppDetails? + @State private var members: [AppMember] = [] + @State private var invites: [AppInvite] = [] + @FocusState private var emailFocused: Bool + @State private var email = "" + @State private var permission = AppPermission.viewer + @State private var busy = false + @State private var loading = true + @State private var error: String? + @State private var resetLink = false + private var owner: Bool { details?.role == "owner" } + private var visibleMembers: [AppMember] { + members.filter { member in + !(member.kind == "agent" && member.owner.map { id in members.contains { $0.principal == id } } == true) + } + } + var body: some View { + Form { + if loading { ProgressView("Loading permissions…") } + if let details { + Section { + if owner { + Picker("General access", selection: Binding( + get: { AppAccess(rawValue: details.visibility) ?? .members }, + set: { value in Task { await change { let _: JSONValue = try await api.request("api/f/\(fragment.name)/visibility", method: "PUT", body: .object(["visibility": .string(value.rawValue)])) } } } + )) { ForEach(AppAccess.allCases, id: \.self) { Text($0.title).tag($0) } } + .accessibilityIdentifier("appVisibility") + } else { LabeledContent("General access", value: AppAccess(rawValue: details.visibility)?.title ?? details.visibility) } + } footer: { Text(AppAccess(rawValue: details.visibility)?.explanation ?? "") } + if let url = details.shareURL { + Section("Share link") { + ShareLink(item: url) { Label("Share link", systemImage: "square.and.arrow.up") } + Button("Copy link", systemImage: "doc.on.doc") { UIPasteboard.general.url = url } + if owner && details.visibility == "link" { Button("Reset share link", systemImage: "arrow.clockwise") { resetLink = true } } + } + } + } + Section("People with access") { + ForEach(visibleMembers) { member in + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(memberTitle(member)).lineLimit(2) + if members.contains(where: { $0.kind == "agent" && $0.owner == member.principal }) { + Text("With their agents").font(.caption).foregroundStyle(.secondary) + } + } + Spacer() + if owner && member.role != "owner" { + Menu { + ForEach(AppPermission.allCases, id: \.self) { role in + Button(role.title) { Task { await change { let _: JSONValue = try await api.request("api/f/\(fragment.name)/members/\(member.principal)", method: "PUT", body: .object(["role": .string(role.rawValue)])) } } } + } + Button("Remove access", role: .destructive) { Task { await change { let _: JSONValue = try await api.request("api/f/\(fragment.name)/members/\(member.principal)", method: "DELETE") } } } + } label: { Text(AppPermission(rawValue: member.role)?.title ?? member.role.capitalized).font(.subheadline) } + .accessibilityIdentifier("memberRole-\(member.principal)") + } else { Text(member.role == "owner" ? "Owner" : AppPermission(rawValue: member.role)?.title ?? member.role).font(.subheadline).foregroundStyle(.secondary) } + }.padding(.vertical, 4) + } + } + if owner { + Section("Invite someone") { + TextField("Email address", text: $email).keyboardType(.emailAddress).textContentType(.emailAddress) + .textInputAutocapitalization(.never).autocorrectionDisabled().focused($emailFocused).accessibilityIdentifier("inviteEmail") + Picker("Permission", selection: $permission) { ForEach(AppPermission.allCases, id: \.self) { Text($0.title).tag($0) } } + Button("Send invitation", systemImage: "envelope") { emailFocused = false; Task { await invite() } } + .disabled(email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty).accessibilityIdentifier("sendAppInvite") + } + if !invites.isEmpty { + Section("Pending invitations") { + ForEach(invites) { invitation in + VStack(alignment: .leading, spacing: 5) { + Text(invitation.email) + Text(AppPermission(rawValue: invitation.role)?.title ?? invitation.role).font(.caption).foregroundStyle(.secondary) + Button("Revoke invitation", role: .destructive) { Task { await change { let _: JSONValue = try await api.request("api/f/\(fragment.name)/invites/\(invitation.email)", method: "DELETE") } } } + .font(.caption).accessibilityIdentifier("revokeInvite-\(invitation.email)") + }.padding(.vertical, 4) + } + } + } + } + if busy { ProgressView("Saving…") } + if let error { Section { Text(error).font(.footnote).foregroundStyle(.secondary); Button("Reload permissions") { Task { await load() } } } } + } + .disabled(busy) + .navigationTitle(owner ? "Sharing & Permissions" : "People with Access").navigationBarTitleDisplayMode(.inline) + .task { await load() } + .confirmationDialog("Reset this share link?", isPresented: $resetLink, titleVisibility: .visible) { + Button("Reset share link", role: .destructive) { Task { await change { let _: JSONValue = try await api.request("api/f/\(fragment.name)/rotate", method: "POST", body: .object(["scopes": .array([.string("view")])])) } } } + } message: { Text("The old link will stop working. People with membership keep their access.") } + } + private func memberTitle(_ member: AppMember) -> String { + if member.principal == details?.owner { return owner ? "You" : "Owner" } + return member.email ?? member.title ?? library.agent(for: member.principal)?.displayTitle ?? member.principal + } + private func load() async { + loading = true; defer { loading = false } + do { + async let status = api.appDetails(fragment.name) + async let people = api.appMembers(fragment.name) + let (newDetails, newMembers) = try await (status, people) + let newInvites = newDetails.role == "owner" ? try await api.appInvites(fragment.name) : [] + details = newDetails; members = newMembers; invites = newInvites; error = nil + } catch { self.error = error.localizedDescription } + } + private func change(_ action: () async throws -> Void) async { + busy = true; error = nil; defer { busy = false } + do { try await action(); await load(); await library.load(api) } + catch { self.error = error.localizedDescription } + } + private func invite() async { + let clean = email.trimmingCharacters(in: .whitespacesAndNewlines) + await change { + let _: JSONValue = try await api.request("api/f/\(fragment.name)/invites", method: "POST", body: .object(["email": .string(clean), "role": .string(permission.rawValue)])) + email = "" + } + } +} diff --git a/ios/Sources/Assets.xcassets/Contents.json b/ios/Sources/Assets.xcassets/Contents.json new file mode 100644 index 000000000..f223229d3 --- /dev/null +++ b/ios/Sources/Assets.xcassets/Contents.json @@ -0,0 +1 @@ +{"info":{"author":"xcode","version":1}} diff --git a/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/Contents.json b/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/Contents.json new file mode 100644 index 000000000..e2345e62c --- /dev/null +++ b/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images": [ + { + "filename": "wallpaper.jpg", + "idiom": "universal" + }, + { + "filename": "wallpaper-dark.jpg", + "idiom": "universal", + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ] + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper-dark.jpg b/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper-dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d18e51f9cb38f67d45160aa08ae276b55d46dc02 GIT binary patch literal 221144 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�M~}=z)f0CXIRh) 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 Toh#>SVrg}r?<`XEwLkyacA`Qb literal 0 HcmV?d00001 diff --git a/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper.jpg b/ios/Sources/Assets.xcassets/DesktopWallpaper.imageset/wallpaper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2decefe19cd7bee1aad02e59f1ebf89d8884bfd GIT binary patch literal 358745 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

Bn4f1has#6jwg1|O7ITufB6p~WLDA%lBb}GZ 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

BJ}KCJNJ5DdT5fIqI>~iI%6I*8sNp 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�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%0Am?wEw+0`7;*)k zVHK(wl=xFqu>-+|{wV(a;ozUjINCi!%|bG489;6To})a7p)|tP0Y=ek;MWNBD$T;CF}B&<<=%{Z zc!(S*syrHpZlcjqz^IBH8HsB7z2z^bj)apm!+Jv583B7m4yW**qT&N-wNErVhz{c7 z8^FgEGHl?$aMd=P8>YG|JwRr^#I6l{9@Fu0?G)|?Av=^=zN^}51)x1V6B`vn%u4Vu zDlX@|-vV`F1$B%SXwkUhl%k3qYRLLR3j^PpoR3xC0Cb4k%X83Uh-?)E*8;~#2hD;+ z(pnfb+#9$^Y!vr1lE7bSq(hqw*U9cYO(6PugJHg-#9v4uLXQGI)(lFIVVK~OZHG=~ zqiu1#Qu2Eb5U6lAu;RcLez6#@#3LWjLI_Ofiya|QTSx<4*z|}ChZjSg*r+hoy#D}+ zR%tJ{a|a7e=Ay6+0dBoAA)qJ%Jab+kK9Up;Y%FkDQ2+q-Y}Cz=zyrdV2Ef(k$gn1& z**9J$rrlAuViEbmwba*QXrD7xMKGm3Z=axx$D}Ce0IP~@>ZuverXW>N0)l8L7`W@T zAJPe~MH4iIShYWh)q^aM#*@c26pZcgKoM?%J5PD@d_qX+jLl8Ov_qQc#NOGUSM3Vl ztY9r^vI+A63RQ)SZ_hv0A;BYWLqZZ8RTL9-TltCVVpfM!T$!j&qiU*-ya-W)=>{|? z17F-hRB?R*iK(|wQGq3LYU*xewpE*b4SHpX28hiFu6)pX3IrMUg4%Qn5!BbG`{pG$ z)NU|Ea$2KvWsUMK;s6Vc!EtA)G1L`B1Gx$+hj`$fip&AUZL$RCS;;2&Q(_7wa`nxe zw6Zmq;wV#gxdcEO&!=I$Fu}MDY3s6<1CZmdnWU^$mugjl7Wop!o89NF%_n4MZ9O-> zI+zE<)&!#TTfisG(BAsLXLQnFG8`MR?*JP#9hqhBu*sj|;23^q&v6Z5PrVqmdKyGk zkbN8or_%LY^|*us_M7JlOv?r|M>SkdT-WCo!<7R60E!0F)nz4|WLa)117mY9Lqr&X z6zJ)VMP4jezjHNqAY;x>0io>(D!O`Ufy+0j!|Apvd(hxO0dcW`^=<_=)X1{CPkO

~jZDz~4KCh(2AfM9{&^ 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: View { + let url: URL + @ViewBuilder let content: (Image?) -> Content + @Environment(\.fragmentAPI) private var api + @State private var image: UIImage? + var body: some View { + content(image.map { Image(uiImage: $0) }) + .task(id: url) { + image = nil + do { + let request = URLRequest(url: url) + let data: Data + let response: URLResponse + if let api, PlatformOrigin.same(api.baseURL, url) { (data, response) = try await api.data(for: request) } + else { (data, response) = try await URLSession.shared.data(for: request) } + guard (response as? HTTPURLResponse)?.statusCode == 200, data.count <= 2 * 1_024 * 1_024 else { return } + image = UIImage(data: data) + } catch { /* The caller's placeholder remains when an icon is unavailable. */ } + } + } +} diff --git a/ios/Sources/ChatDraftStore.swift b/ios/Sources/ChatDraftStore.swift new file mode 100644 index 000000000..111080d47 --- /dev/null +++ b/ios/Sources/ChatDraftStore.swift @@ -0,0 +1,64 @@ +import Foundation +import CryptoKit + +struct SavedChat: Codable { + var version = 1 + var composer: String + var attachments: [ChatAttachment] + var replyTo: ChannelRecord? + var recipients: [String] + var pending: [PendingPost] + var isEmpty: Bool { composer.isEmpty && attachments.isEmpty && pending.isEmpty && replyTo == nil } +} + +struct ChatDraftStore { + let directory: URL + let file: URL + private let origin: URL + private let publicKey: String + private let accountID: String? + init(api: FragmentAPI, fragment: String, root: URL? = nil) throws { + origin = api.baseURL; publicKey = api.identity.publicKey; accountID = api.accountID + directory = try Self.accountDirectory(api: api, root: root).appending(path: "Chats", directoryHint: .isDirectory) + file = directory.appending(path: Data(SHA256.hash(data: Data(fragment.utf8))).hex + ".json") + } + private static func accountDirectory(api: FragmentAPI, root: URL? = nil) throws -> URL { + let support = try root ?? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + let scope = Data(SHA256.hash(data: Data((api.baseURL.absoluteString + "|" + (api.accountID ?? api.identity.publicKey)).utf8))).hex + return support.appending(path: "Finite/Accounts/" + scope, directoryHint: .isDirectory) + } + func load() throws -> SavedChat? { + guard FileManager.default.fileExists(atPath: file.path) else { return nil } + guard (try file.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0) <= 1_048_576 else { throw APIError.limitExceeded } + let saved = try JSONDecoder().decode(SavedChat.self, from: Data(contentsOf: file)) + guard saved.version == 1, saved.composer.utf8.count <= 32_768, saved.pending.count <= 16, + saved.attachments.count <= 8, saved.pending.allSatisfy({ !$0.id.isEmpty && $0.text.utf8.count <= 32_768 && $0.attachments.count <= 8 }) else { throw APIError.invalidResponse } + return saved + } + func save(_ saved: SavedChat) throws { + if let accountID { + // A send finishing after disconnect must not recreate erased files. + guard let active = try DeviceKeychain.read(), active.origin == origin, + active.identity == accountID, try active.signingKey.publicKey == publicKey else { return } + } + if saved.isEmpty { + if FileManager.default.fileExists(atPath: file.path) { try FileManager.default.removeItem(at: file) } + return + } + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete]) + if !FileManager.default.fileExists(atPath: file.path) { + let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) + guard files.count < 32 else { throw APIError.server("Finish or discard a draft in another chat before saving more conversations.") } + } + let data = try JSONEncoder().encode(saved) + guard data.count <= 1_048_576 else { throw APIError.limitExceeded } + try data.write(to: file, options: [.atomic, .completeFileProtection]) + var excluded = directory + var values = URLResourceValues(); values.isExcludedFromBackup = true + try excluded.setResourceValues(values) + } + static func removeAccount(api: FragmentAPI) throws { + let path = try accountDirectory(api: api) + if FileManager.default.fileExists(atPath: path.path) { try FileManager.default.removeItem(at: path) } + } +} diff --git a/ios/Sources/ChatMedia.swift b/ios/Sources/ChatMedia.swift new file mode 100644 index 000000000..7adb8d451 --- /dev/null +++ b/ios/Sources/ChatMedia.swift @@ -0,0 +1,118 @@ +import SwiftUI +import CryptoKit +import AVFoundation +import UniformTypeIdentifiers + +struct ChatAttachment: Codable, Identifiable, Equatable, Sendable { + let sha256: String + let name: String + let type: String + let size: Int + var id: String { sha256 } + var json: JSONValue { .object(["sha256": .string(sha256), "name": .string(name), "type": .string(type), "size": .number(Double(size))]) } +} + +extension ChatAttachment { + /// The app's own bound on one attachment: it is read whole into memory, + /// and sent over a phone's network. The server takes up to + /// `limits::BLOB_MAX_BYTES` (crates/proto). + static let bytesMax = 25 * 1_024 * 1_024 +} + +extension FragmentAPI { + func upload(_ data: Data, name: String, type: String, to fragment: String) async throws -> ChatAttachment { + let hash = SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() + var request = URLRequest(url: url("api/f/\(fragment)/blobs/\(hash)")) + request.httpMethod = "PUT"; request.httpBody = data; request.timeoutInterval = 60 + request.setValue(type, forHTTPHeaderField: "Content-Type") + request.setValue(String(data.count), forHTTPHeaderField: "Content-Length") + let (body, response) = try await self.data(for: request) + guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } + guard (200..<300).contains(http.statusCode) else { throw APIError.answer(body, status: http.statusCode) } + return ChatAttachment(sha256: hash, name: name, type: type, size: data.count) + } +} + +@MainActor @Observable +final class VoiceMemo { + private var recorder: AVAudioRecorder? + private var deadline: Task? + private var url: URL? + var recording = false + var error: String? + func start(finished: @escaping @MainActor (URL) -> Void) async { + guard await AVAudioApplication.requestRecordPermission() else { error = "Allow microphone access in iOS Settings to record a voice memo."; return } + do { + let session = AVAudioSession.sharedInstance() + try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker]) + try session.setActive(true) + let url = FileManager.default.temporaryDirectory.appendingPathComponent("voice-\(UUID().uuidString).m4a") + let recorder = try AVAudioRecorder(url: url, settings: [AVFormatIDKey: kAudioFormatMPEG4AAC, AVSampleRateKey: 44_100, AVNumberOfChannelsKey: 1, AVEncoderBitRateKey: 96_000]) + guard recorder.record() else { throw APIError.server("The microphone could not start recording.") } + self.recorder = recorder; self.url = url; recording = true + // Voice memos share the web's five-minute bound, including an unattended recording. + deadline = Task { do { try await Task.sleep(for: .seconds(300)); if let url = stop() { finished(url) } } catch {} } + } catch { self.error = error.localizedDescription } + } + func stop() -> URL? { + deadline?.cancel(); deadline = nil + recorder?.stop(); recorder = nil; recording = false + do { try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) } + catch { self.error = error.localizedDescription } + defer { url = nil } + return url + } + func cancel() { + if let url = stop() { do { try FileManager.default.removeItem(at: url) } catch { self.error = error.localizedDescription } } + } +} + +struct AttachmentView: View { + let attachment: ChatAttachment + let api: FragmentAPI + let fragment: String + @State private var downloaded: URL? + @State private var loading = false + @State private var error: String? + var body: some View { + VStack(alignment: .leading, spacing: 6) { + if let downloaded { + if attachment.type.hasPrefix("audio/") { AudioMemoPlayer(url: downloaded) } + ShareLink(item: downloaded) { Label(attachment.name, systemImage: "doc") } + } else { + Button { Task { await download() } } label: { Label(attachment.name, systemImage: attachment.type.hasPrefix("audio/") ? "waveform" : "arrow.down.doc") }.disabled(loading) + } + if loading { ProgressView() } + if let error { Text(error).font(.caption).foregroundStyle(.secondary) } + }.padding(12).background(.quaternary, in: RoundedRectangle(cornerRadius: 12)) + } + private func download() async { + loading = true; defer { loading = false } + do { + let (data, response) = try await api.data(for: URLRequest(url: api.url("api/f/\(fragment)/blobs/\(attachment.sha256)"))) + guard (response as? HTTPURLResponse)?.statusCode == 200, data.count <= 25 * 1_024 * 1_024 else { throw APIError.invalidResponse } + // The server's name is display-only; never let it choose a filesystem path. + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.sha256, isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + let target = directory.appendingPathComponent(URL(fileURLWithPath: attachment.name).lastPathComponent) + try data.write(to: target, options: .atomic); downloaded = target + } catch { self.error = error.localizedDescription } + } +} + +private struct AudioMemoPlayer: View { + let url: URL + @State private var player: AVAudioPlayer? + @State private var error: String? + var body: some View { + VStack(alignment: .leading, spacing: 6) { + HStack { + Button("Play voice memo", systemImage: "play.fill") { + do { player = try AVAudioPlayer(contentsOf: url); player?.play() } catch { self.error = error.localizedDescription; player = nil } + } + Button("Stop", systemImage: "stop.fill") { player?.stop() } + } + if let error { Text(error).font(.caption).foregroundStyle(.secondary) } + }.onDisappear { player?.stop() } + } +} diff --git a/ios/Sources/ChatSession.swift b/ios/Sources/ChatSession.swift new file mode 100644 index 000000000..03190d0ca --- /dev/null +++ b/ios/Sources/ChatSession.swift @@ -0,0 +1,211 @@ +import Foundation +import Observation + +@MainActor @Observable +final class ChatSession { + let fragment: FragmentSummary + let api: FragmentAPI + private(set) var timeline = Timeline() + private(set) var pending: [PendingPost] = [] { didSet { scheduleDraftSave() } } + private(set) var connected = false + private(set) var caughtUp = false + private(set) var principal = "" + private(set) var loadingHistory = false + private(set) var hasEarlier = true + var error: String? + var composer = "" { didSet { scheduleDraftSave() } } + var attachments: [ChatAttachment] = [] { didSet { scheduleDraftSave() } } + var replyTo: ChannelRecord? { didSet { scheduleDraftSave() } } + var recipients: [String] = [] { didSet { scheduleDraftSave() } } + + @ObservationIgnored private var persistence: ChatDraftStore? + @ObservationIgnored private var draftWrite: Task? + + init(fragment: FragmentSummary, api: FragmentAPI) { + self.fragment = fragment + self.api = api + do { + persistence = try ChatDraftStore(api: api, fragment: fragment.name) + if let saved = try persistence?.load() { + composer = saved.composer; attachments = saved.attachments; replyTo = saved.replyTo; recipients = saved.recipients + // A terminated send may already be committed. Retry remains an + // explicit action, with exactly the saved id and body. + pending = saved.pending.map { var post = $0; post.failed = true; return post } + } + } catch { self.error = "The local draft could not be opened: " + error.localizedDescription; persistence = nil } + } + private var saved: SavedChat { SavedChat(composer: composer, attachments: attachments, replyTo: replyTo, recipients: recipients, pending: pending) } + private func scheduleDraftSave() { + guard persistence != nil else { return } + draftWrite?.cancel() + draftWrite = Task { [weak self] in + do { try await Task.sleep(for: .milliseconds(300)) } catch { return } + self?.flushDraft() + } + } + func flushDraft() { + draftWrite?.cancel(); draftWrite = nil + do { try persistence?.save(saved) } + catch { self.error = "The local draft could not be saved: " + error.localizedDescription } + } + + // Owned by the view's .task: backgrounding or leaving the chat closes the + // socket, including a suspended receive. Each foreground visit catches up. + func run() async { + var attempt = 0 + while !Task.isCancelled { + let socket: LiveSocket + do { socket = try api.socket(fragment.name) } + catch { self.error = error.localizedDescription; return } + var refused = false + socket.resume() + do { + try await withTaskCancellationHandler { + let heartbeat = Task { + while !Task.isCancelled { + try await Task.sleep(for: .seconds(20)) + try await socket.send(.string("{\"type\":\"ping\"}")) + } + } + defer { heartbeat.cancel() } + while !Task.isCancelled { + let message = try await socket.receive() + let data: Data + switch message { + case .string(let text): data = Data(text.utf8) + case .data(let bytes): data = bytes + @unknown default: throw APIError.invalidResponse + } + let frame = try JSONDecoder().decode(LiveFrame.self, from: data) + if frame.type == "hello" { + principal = frame.principal ?? "" + connected = true + error = nil + attempt = 0 + for channel in ["chat", "work"] { + try await subscribe(socket, channel: channel, after: timeline.cursors[channel]) + } + } + try timeline.apply(frame) + if let clientID = frame.body?["client_id"].string, frame.principal == principal { + pending.removeAll { $0.id == clientID } + } + if frame.type == "subscribed", let channel = frame.channel { + if frame.more == true { + try await subscribe(socket, channel: channel, after: frame.next) + } else if channel == "chat" { + caughtUp = true + if timeline.records.filter({ $0.channel == "chat" }).count < 50 { hasEarlier = false } + } + } + } + } onCancel: { + socket.cancel(with: .goingAway, reason: nil) + } + } catch { + let status = (socket.response as? HTTPURLResponse)?.statusCode + if status == 401 { api.authorizationFailed() } + refused = status == 401 || status == 403 + if socket.closeCode.rawValue == 4003 { + do { let _: JSONValue = try await api.request("api/identities/me") } + catch APIError.unauthorized { refused = true } + catch { /* A transient check failure can retry with the usual backoff. */ } + } + if !Task.isCancelled { self.error = refused ? "Your access ended. Reconnect your account or check sharing permissions." : "Connection interrupted. Reconnecting…" } + } + socket.cancel(with: .goingAway, reason: nil) + connected = false + caughtUp = false + timeline.disconnected() + guard !Task.isCancelled, !refused else { return } + attempt = min(attempt + 1, 4) + do { try await Task.sleep(for: .seconds(min(15, 1 << attempt))) } + catch { return } + } + } + + private func subscribe(_ socket: LiveSocket, channel: String, after: Int64?) async throws { + var body: [String: JSONValue] = ["type": .string("subscribe"), "channel": .string(channel)] + if let after { body["after"] = .number(Double(after)) } + else { body["last"] = .number(50) } + let data = try JSONEncoder().encode(JSONValue.object(body)) + try await socket.send(.string(String(decoding: data, as: UTF8.self))) + } + + func send() async { + guard persistence != nil else { + error = "The local draft could not be opened. Reopen this chat before sending." + return + } + let text = composer.trimmingCharacters(in: .whitespacesAndNewlines) + guard (!text.isEmpty || !attachments.isEmpty), text.utf8.count <= 32_768, pending.count < 16 else { return } + let post = PendingPost(id: UUID().uuidString, text: text, attachments: attachments, replyTo: replyTo?.seq, recipients: recipients) + do { + // Persist the exact send before any request leaves the phone. + let next = SavedChat(composer: "", attachments: [], replyTo: nil, recipients: recipients, pending: pending + [post]) + try persistence?.save(next) + } catch { self.error = "The message could not be saved on this iPhone: " + error.localizedDescription; return } + pending.append(post) + composer = ""; attachments = []; replyTo = nil + await deliver(post) + } + + func retry(_ post: PendingPost) async { + guard let index = pending.firstIndex(where: { $0.id == post.id }), pending[index].failed else { return } + pending[index].failed = false + await deliver(post) + } + + private func deliver(_ post: PendingPost) async { + do { + var body: [String: JSONValue] = ["text": .string(post.text), "client_id": .string(post.id)] + if !post.attachments.isEmpty { body["attachments"] = .array(post.attachments.map(\.json)) } + if let replyTo = post.replyTo { body["reply_to"] = .number(Double(replyTo)) } + if !post.recipients.isEmpty { body["to"] = .array(post.recipients.map { .string($0) }) } + let record = try await api.post(fragment.name, id: post.id, body: .object(body)) + timeline.insert(record, advancingCursor: false) + pending.removeAll { $0.id == post.id } + error = nil + } catch APIError.refused(let message) { + // Refused, not lost: sending it again gets the same answer. It + // leaves the outbox, and its words go back to the composer. + guard let index = pending.firstIndex(where: { $0.id == post.id }) else { return } + let refused = pending.remove(at: index) + composer = [refused.text, composer].filter { !$0.isEmpty }.joined(separator: "\n\n") + attachments = refused.attachments + attachments.filter { kept in !refused.attachments.contains(kept) } + replyTo = replyTo ?? timeline.records.first { $0.seq == refused.replyTo } + self.error = message + } catch { + // A matching live record also acknowledges delivery. The HTTP + // acknowledgement may have been lost after the append committed. + guard let index = pending.firstIndex(where: { $0.id == post.id }) else { return } + pending[index].failed = true + self.error = error.localizedDescription + } + } + + func stop() async { + for turn in timeline.activeTurns.prefix(16) { + do { + let record = try await api.post(fragment.name, id: "stop:\(turn)", body: .object([ + "kind": .string("stop"), "turn": .string(turn) + ])) + timeline.insert(record, advancingCursor: false) + } catch { self.error = error.localizedDescription } + } + } + + func loadEarlier() async { + guard !loadingHistory, let first = timeline.records.filter({ $0.channel == "chat" }).map(\.seq).min() else { + hasEarlier = false + return + } + loadingHistory = true + defer { loadingHistory = false } + do { + let page = try await api.history(fragment.name, before: first) + for record in page { timeline.insert(record, advancingCursor: false) } + hasEarlier = page.count == 50 && timeline.records.count < Timeline.maximumRecords + } catch { self.error = error.localizedDescription } + } +} diff --git a/ios/Sources/ChatView.swift b/ios/Sources/ChatView.swift new file mode 100644 index 000000000..a2578601c --- /dev/null +++ b/ios/Sources/ChatView.swift @@ -0,0 +1,233 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct ChatView: View { + @Environment(\.scenePhase) private var scenePhase + @State private var session: ChatSession + @State private var followsBottom = true + @State private var scrollingByHand = false + let library: Library + @State private var importer = false + @State private var uploading = false + @State private var voice = VoiceMemo() + + init(session: ChatSession, library: Library) { + _session = State(initialValue: session) + self.library = library + } + + var body: some View { + ScrollViewReader { proxy in + ScrollView { + LazyVStack(alignment: .leading, spacing: 24) { + if session.hasEarlier && !session.timeline.records.isEmpty { + Button("Earlier messages") { Task { await session.loadEarlier() } } + .font(.footnote).frame(maxWidth: .infinity).disabled(session.loadingHistory) + } + ForEach(session.timeline.records) { record in + if record.isMessage { + VStack(alignment: .leading, spacing: 8) { + MessageView(text: record.text, isYou: record.principal == session.principal, agent: library.agent(for: record.principal)) + .contextMenu { + Button("Reply", systemImage: "arrowshape.turn.up.left") { session.replyTo = record } + Button("Copy", systemImage: "doc.on.doc") { UIPasteboard.general.string = record.text } + ShareLink(item: record.text) { Label("Share", systemImage: "square.and.arrow.up") } + } + ForEach(attachments(record)) { attachment in + AttachmentView(attachment: attachment, api: session.api, fragment: session.fragment.name) + } + Text(Date(timeIntervalSince1970: Double(record.at) / 1000), style: .time).font(.caption2).foregroundStyle(.tertiary) + }.id(record.id) + } else if record.channel == "work" { + WorkRecordView(record: record) + } + } + ForEach(session.pending) { pending in + VStack(alignment: .trailing, spacing: 6) { + MessageView(text: pending.text, isYou: true) + if pending.failed { + Button("Not sent. Tap to retry", systemImage: "arrow.clockwise") { + Task { await session.retry(pending) } + }.font(.caption).accessibilityIdentifier("retry") + } else { Text("Sending…").font(.caption).foregroundStyle(.secondary) } + }.frame(maxWidth: .infinity, alignment: .trailing) + } + ForEach(session.timeline.drafts.values.sorted { $0.at < $1.at }) { draft in + MessageView(text: draft.text, isYou: false, agent: library.agent(for: draft.principal), streaming: true) + } + if !session.timeline.activeTurns.isEmpty && session.timeline.drafts.isEmpty { + Label("Working…", systemImage: "ellipsis").font(.callout).foregroundStyle(.secondary) + } + Color.clear.frame(height: 1).id("bottom") + } + .frame(maxWidth: 660, alignment: .leading) + .padding(.horizontal, 20).padding(.vertical, 24).frame(maxWidth: .infinity) + } + .scrollDismissesKeyboard(.interactively) + .accessibilityIdentifier("timeline") + .defaultScrollAnchor(.bottom, for: .initialOffset) + .defaultScrollAnchor(.top, for: .alignment) + .onScrollGeometryChange(for: Bool.self) { geometry in + geometry.contentSize.height - geometry.visibleRect.maxY < 100 + } action: { _, nearBottom in + if scrollingByHand { followsBottom = nearBottom } + } + .onScrollPhaseChange { _, phase in + scrollingByHand = phase == .interacting || phase == .decelerating + } + .onChange(of: session.timeline.records.count + session.pending.count) { _, _ in + if followsBottom { proxy.scrollTo("bottom", anchor: .bottom) } + } + .onChange(of: session.timeline.drafts) { _, _ in + if followsBottom { proxy.scrollTo("bottom", anchor: .bottom) } + } + .overlay(alignment: .bottomTrailing) { + if !followsBottom { + Button("Latest messages", systemImage: "arrow.down") { + followsBottom = true + withAnimation { proxy.scrollTo("bottom", anchor: .bottom) } + } + .labelStyle(.iconOnly).buttonStyle(.borderedProminent).buttonBorderShape(.circle).padding() + } + } + .overlay { + if session.timeline.records.isEmpty && session.pending.isEmpty { + ContentUnavailableView("A fresh start", systemImage: "bubble.left.and.bubble.right", + description: Text("A new conversation with your agent.")) + } + } + } + .background(FragmentStyle.background) + .safeAreaInset(edge: .bottom, spacing: 0) { + VStack(spacing: 0) { + if let error = session.error { + Text(error).font(.caption).foregroundStyle(.secondary).padding(.horizontal, 20).padding(.top, 8) + } + composerAccessories + ComposerView(text: $session.composer, working: !session.timeline.activeTurns.isEmpty, + enabled: session.connected && session.caughtUp, busy: uploading, recording: voice.recording, + attach: { importer = true }, record: { Task { await recordVoice() } }, + send: { await session.send() }, stop: { await session.stop() }, hasAttachments: !session.attachments.isEmpty) + }.background(FragmentStyle.background) + } + .fileImporter(isPresented: $importer, allowedContentTypes: [.item], allowsMultipleSelection: true) { result in + switch result { + case .success(let urls): Task { await uploadFiles(urls) } + case .failure(let error): session.error = error.localizedDescription + } + } + .task(id: "\(session.caughtUp)|\(session.timeline.cursors["chat"] ?? 0)|\(scenePhase)") { + guard scenePhase == .active, session.caughtUp else { return } + do { let _: JSONValue = try await session.api.request("api/fragments/\(session.fragment.name)/seen", method: "PUT") } + catch { /* A later live cursor or foreground visit retries the read marker. */ } + } + .onDisappear { session.flushDraft() } + .onChange(of: scenePhase) { _, phase in if phase != .active { session.flushDraft() } } + .task(id: scenePhase) { + guard scenePhase == .active else { voice.cancel(); return } + await session.run() + } + .onDisappear { voice.cancel() } + } + + private var composerAccessories: some View { + VStack(alignment: .leading, spacing: 8) { + if let quoted = session.replyTo { + HStack { Label(quoted.text, systemImage: "arrowshape.turn.up.left").font(.caption).lineLimit(2); Spacer(); Button("Cancel reply", systemImage: "xmark") { session.replyTo = nil }.labelStyle(.iconOnly) } + } + if !session.attachments.isEmpty { + ScrollView(.horizontal) { HStack { + ForEach(session.attachments) { file in + Button { session.attachments.removeAll { $0.id == file.id } } label: { Label(file.name, systemImage: "xmark.circle").font(.caption) }.buttonStyle(.bordered) + } + } } + } + if let error = voice.error { Text(error).font(.caption).foregroundStyle(.secondary) } + if voice.recording { Label("Recording voice memo…", systemImage: "waveform").foregroundStyle(.red).font(.caption) } + }.padding(.horizontal, 20) + } + + private func attachments(_ record: ChannelRecord) -> [ChatAttachment] { + record.body["attachments"].array.compactMap { value in + guard let data = try? JSONEncoder().encode(value) else { return nil } + return try? JSONDecoder().decode(ChatAttachment.self, from: data) + } + } + private func uploadFiles(_ urls: [URL]) async { + guard urls.count + session.attachments.count <= 8 else { session.error = "Attach at most eight files per message."; return } + uploading = true; defer { uploading = false } + for url in urls { + let scoped = url.startAccessingSecurityScopedResource() + defer { if scoped { url.stopAccessingSecurityScopedResource() } } + do { + let size = try url.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0 + guard size > 0, size <= ChatAttachment.bytesMax else { throw APIError.server("Files must be between 1 byte and 25 MB.") } + let type = UTType(filenameExtension: url.pathExtension)?.preferredMIMEType ?? "application/octet-stream" + let file = try await session.api.upload(Data(contentsOf: url), name: url.lastPathComponent, type: type, to: session.fragment.name) + if !session.attachments.contains(where: { $0.id == file.id }) { session.attachments.append(file) } + } catch { session.error = error.localizedDescription; return } + } + } + private func recordVoice() async { + if voice.recording { + if let url = voice.stop() { await uploadFiles([url]); try? FileManager.default.removeItem(at: url) } + } else { + guard session.attachments.count < 8 else { session.error = "Attach at most eight files per message."; return } + await voice.start { url in Task { await uploadFiles([url]); try? FileManager.default.removeItem(at: url) } } + } + } +} + +struct MessageView: View { + let text: String + let isYou: Bool + var agent: FragmentSummary? = nil + var streaming = false + + var body: some View { + HStack(alignment: .top, spacing: 0) { + if isYou { Spacer(minLength: 44) } + VStack(alignment: .leading, spacing: 8) { + if !isYou { + HStack(spacing: 7) { + AgentAvatar(agent: agent, size: 22) + Text(agent?.displayTitle ?? "Agent").font(.caption.weight(.medium)).foregroundStyle(.secondary) + if streaming { ProgressView().controlSize(.mini).accessibilityLabel("Replying") } + } + } + if isYou { + Text(text).font(.body).lineSpacing(5).textSelection(.enabled) + .fixedSize(horizontal: false, vertical: true) + } else { MarkdownText(text: text) } + } + .padding(isYou ? 14 : 0) + .background(isYou ? FragmentStyle.bubble : .clear, in: RoundedRectangle(cornerRadius: 20)) + if !isYou { Spacer(minLength: 8) } + } + .frame(maxWidth: .infinity, alignment: isYou ? .trailing : .leading) + .accessibilityElement(children: .combine) + } +} + +struct WorkRecordView: View { + let record: ChannelRecord + var body: some View { + if record.body["kind"].string == "turn.step" { + DisclosureGroup { + Text(record.body["args"].string ?? "").font(.caption.monospaced()).textSelection(.enabled) + Text(record.body["excerpt"].string ?? "").font(.caption).textSelection(.enabled) + } label: { + Label(record.body["tool"].string ?? "Working", systemImage: "checkmark.circle") + .font(.callout).foregroundStyle(.secondary) + } + .padding(14).background(FragmentStyle.surface, in: RoundedRectangle(cornerRadius: 14)) + } else if record.body["kind"].string == "turn.end", let error = record.body["error"].string { + Label(error, systemImage: "exclamationmark.circle").font(.callout).foregroundStyle(.secondary) + } else if record.body["kind"].string == "turn.end", record.body["status"].string == "stopped" { + Text("Response stopped").font(.footnote).foregroundStyle(.secondary) + } else if record.body["kind"].string == "turn.prompt" { + Label("\(record.text)\nOpen this chat on the web to answer this request.", systemImage: "questionmark.bubble") + .font(.callout).foregroundStyle(.secondary) + } + } +} diff --git a/ios/Sources/DeviceIdentity.swift b/ios/Sources/DeviceIdentity.swift new file mode 100644 index 000000000..565b65aa7 --- /dev/null +++ b/ios/Sources/DeviceIdentity.swift @@ -0,0 +1,109 @@ +import Foundation +import CryptoKit +import Security +import P256K + +struct DeviceIdentity: Sendable { + let secret: Data + let publicKey: String + init(secret: Data) throws { + let key = try P256K.Schnorr.PrivateKey(dataRepresentation: secret) + self.secret = secret + publicKey = Data(key.xonly.bytes).hex + } + init() throws { try self.init(secret: P256K.Schnorr.PrivateKey().dataRepresentation) } + + func authorization(method: String, url: URL, body: Data = Data(), at: Int64 = Int64(Date().timeIntervalSince1970)) throws -> String { + // NIP-98 binds the HTTP URL (https/http, including on a WebSocket upgrade), + // method, exact encoded body, and time. JSONSerializer emits NIP-01's array. + var target = URLComponents(url: url, resolvingAgainstBaseURL: false)! + if target.scheme == "wss" { target.scheme = "https" } + if target.scheme == "ws" { target.scheme = "http" } + guard let absolute = target.url, ["http", "https"].contains(absolute.scheme) else { throw APIError.invalidResponse } + var tags = [["u", absolute.absoluteString], ["method", method]] + if !body.isEmpty { tags.append(["payload", Data(SHA256.hash(data: body)).hex]) } + let serialized = try JSONSerialization.data(withJSONObject: [0, publicKey, at, 27235, tags, ""], options: [.withoutEscapingSlashes]) + var digest = Array(SHA256.hash(data: serialized)) + let key = try P256K.Schnorr.PrivateKey(dataRepresentation: secret) + let signature = try key.signature(message: &digest, auxiliaryRand: nil, strict: true) + let event: [String: Any] = ["id": Data(digest).hex, "pubkey": publicKey, "created_at": at, "kind": 27235, + "tags": tags, "content": "", "sig": signature.dataRepresentation.hex] + return "Nostr " + (try JSONSerialization.data(withJSONObject: event, options: [.withoutEscapingSlashes])).base64EncodedString() + } +} + +extension Data { + var hex: String { map { String(format: "%02x", $0) }.joined() } +} + +struct DeviceAccount: Codable, Sendable { + let origin: URL + let secret: Data + var identity: String? + var email: String? + var approved: Bool + var signingKey: DeviceIdentity { get throws { try DeviceIdentity(secret: secret) } } +} + +enum DeviceKeychain { + private static let service = "build.finite.fragment.device" + private static func query(_ account: String) -> [String: Any] { + [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, + kSecAttrAccount as String: account, kSecAttrSynchronizable as String: false] + } + static func read(account: String = "active-account-v1") throws -> DeviceAccount? { + var request = query(account) + request[kSecReturnData as String] = true + request[kSecMatchLimit as String] = kSecMatchLimitOne + var result: CFTypeRef? + let status = SecItemCopyMatching(request as CFDictionary, &result) + if status == errSecItemNotFound { return nil } + guard status == errSecSuccess, let data = result as? Data else { throw APIError.keychain(status) } + return try JSONDecoder().decode(DeviceAccount.self, from: data) + } + static func save(_ account: DeviceAccount, account slot: String = "active-account-v1") throws { + let data = try JSONEncoder().encode(account) + let attributes: [String: Any] = [kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly] + let status = SecItemUpdate(query(slot) as CFDictionary, attributes as CFDictionary) + if status == errSecItemNotFound { + var insert = query(slot) + attributes.forEach { insert[$0.key] = $0.value } + let added = SecItemAdd(insert as CFDictionary, nil) + guard added == errSecSuccess else { throw APIError.keychain(added) } + } else if status != errSecSuccess { throw APIError.keychain(status) } + } + static func clear(account: String = "active-account-v1") throws { + let status = SecItemDelete(query(account) as CFDictionary) + guard status == errSecSuccess || status == errSecItemNotFound else { throw APIError.keychain(status) } + } +} + +extension Notification.Name { static let finiteAuthorizationFailed = Notification.Name("FiniteAuthorizationFailed") } + +// A signed request is never silently redirected with stale authorization or to +// another host. Web navigation has its own one-fragment session and policy. +final class NoSignedRedirects: NSObject, URLSessionTaskDelegate { + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { completionHandler(nil) } +} + +enum PlatformOrigin { + static func parse(_ raw: String) throws -> URL { + guard let url = URL(string: raw.trimmingCharacters(in: .whitespacesAndNewlines)), url.host != nil, + url.user == nil, url.password == nil, url.query == nil, url.fragment == nil, + url.path.isEmpty || url.path == "/" else { throw APIError.server("Enter the platform’s HTTPS address, without a path.") } + var allowed = url.scheme == "https" + #if DEBUG + let host = url.host! + allowed = allowed || (url.scheme == "http" && (host == "localhost" || host == "127.0.0.1" || host == "::1" || host.hasSuffix(".localhost") || host.hasSuffix(".local"))) + #endif + guard allowed else { throw APIError.server("Use HTTPS for your Finite account.") } + var parts = URLComponents(url: url, resolvingAgainstBaseURL: false)! + parts.path = "" + return parts.url! + } + static func same(_ a: URL, _ b: URL) -> Bool { + func scheme(_ url: URL) -> String? { switch url.scheme { case "wss": "https"; case "ws": "http"; default: url.scheme } } + return scheme(a) == scheme(b) && a.host?.lowercased() == b.host?.lowercased() && (a.port ?? (scheme(a) == "https" ? 443 : 80)) == (b.port ?? (scheme(b) == "https" ? 443 : 80)) + } +} diff --git a/ios/Sources/FragmentAPI.swift b/ios/Sources/FragmentAPI.swift new file mode 100644 index 000000000..53cfb0f12 --- /dev/null +++ b/ios/Sources/FragmentAPI.swift @@ -0,0 +1,187 @@ +import Foundation + +enum APIError: LocalizedError { + /// `refused`: the server answered 4xx (but a timeout or a rate limit), a + /// final answer: the same request again gets it again. + case invalidResponse, limitExceeded, server(String), refused(String), unauthorized, keychain(Int32) + var errorDescription: String? { + switch self { + case .unauthorized: "This iPhone is not authorized. Connect your account again." + case .keychain(let status): "The iPhone key could not be accessed (Keychain \(status))." + case .invalidResponse: "Finite sent an unreadable response." + case .limitExceeded: "This response is larger than the app can display." + case .server(let message), .refused(let message): message + } + } + /// A non-2xx answer as an error, with the server's own message. + static func answer(_ data: Data, status: Int) -> APIError { + let message = (try? JSONDecoder().decode(JSONValue.self, from: data))?["message"].string ?? "Finite returned HTTP \(status)." + return (400..<500).contains(status) && status != 408 && status != 429 ? .refused(message) : .server(message) + } +} + +/// A live socket a chat or the list watcher reads: a URLSessionWebSocketTask, +/// or a test's scripted one (URLProtocol never sees WebSocket tasks). +protocol LiveSocket: AnyObject, Sendable { + var response: URLResponse? { get } + var closeCode: URLSessionWebSocketTask.CloseCode { get } + func resume() + func send(_ message: URLSessionWebSocketTask.Message) async throws + func receive() async throws -> URLSessionWebSocketTask.Message + func cancel(with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) +} + +extension URLSessionWebSocketTask: LiveSocket {} + +/// Every request signs with this iPhone's approved key (NIP-98), to its own +/// platform origin only. +struct FragmentAPI: Sendable { + typealias Sockets = @Sendable (URLRequest) -> LiveSocket + let baseURL: URL + let session: URLSession + let identity: DeviceIdentity + let accountID: String? + private let sockets: Sockets? + init(baseURL: URL, identity: DeviceIdentity, accountID: String? = nil, session: URLSession? = nil, sockets: Sockets? = nil) { + self.baseURL = baseURL + self.identity = identity + self.accountID = accountID + self.sockets = sockets + if let session { self.session = session } + else { + let configuration = URLSessionConfiguration.ephemeral + configuration.httpShouldSetCookies = false + configuration.httpCookieStorage = nil + configuration.urlCredentialStorage = nil + configuration.urlCache = nil + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + self.session = URLSession(configuration: configuration, delegate: NoSignedRedirects(), delegateQueue: nil) + } + } + func authorized(_ original: URLRequest) throws -> URLRequest { + guard let url = original.url, PlatformOrigin.same(baseURL, url), url.user == nil, url.password == nil, + url.fragment == nil, original.httpBodyStream == nil else { throw APIError.server("The request does not belong to your Finite server.") } + var request = original + request.setValue(try identity.authorization(method: request.httpMethod ?? "GET", url: url, body: request.httpBody ?? Data()), forHTTPHeaderField: "Authorization") + request.setValue("Finite-iOS/0.3", forHTTPHeaderField: "User-Agent") + return request + } + func data(for request: URLRequest) async throws -> (Data, URLResponse) { + let result = try await session.data(for: authorized(request)) + if (result.1 as? HTTPURLResponse)?.statusCode == 401 { + authorizationFailed() + throw APIError.unauthorized + } + return result + } + func authorizationFailed() { + NotificationCenter.default.post(name: .finiteAuthorizationFailed, object: nil, + userInfo: ["origin": baseURL.absoluteString, "key": identity.publicKey]) + } + + func url(_ path: String) -> URL { baseURL.appending(path: path) } + + func request(_ path: String, method: String = "GET", body: JSONValue? = nil, + query: [URLQueryItem] = [], timeout: TimeInterval = 30) async throws -> T { + var components = URLComponents(url: url(path), resolvingAgainstBaseURL: false)! + components.queryItems = query.isEmpty ? nil : query + var request = URLRequest(url: components.url!) + request.httpMethod = method + request.timeoutInterval = timeout + if let body { + request.httpBody = try JSONEncoder().encode(body) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + let (data, response) = try await self.data(for: request) + guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } + guard (200..<300).contains(http.statusCode) else { throw APIError.answer(data, status: http.statusCode) } + guard data.count <= 4_194_304 else { throw APIError.limitExceeded } + return try JSONDecoder().decode(T.self, from: data) + } + + func fragments() async throws -> [FragmentSummary] { + struct Response: Decodable { let fragments: [FragmentSummary] } + let response: Response = try await request("api/fragments") + return response.fragments + } + + // Search is only a hint: the complete prompt, including words beyond the + // API's eight-word / 256-byte query limit, remains the new chat's message. + static func searchHint(_ text: String) -> String { + let words = text.split(whereSeparator: { $0.isWhitespace || $0.isNewline }).prefix(8) + var hint = "" + for character in words.joined(separator: " ") { + guard hint.utf8.count + String(character).utf8.count <= 256 else { break } + hint.append(character) + } + return hint + } + + func search(_ text: String) async throws -> SearchResults { + let hint = Self.searchHint(text) + guard !hint.isEmpty else { return SearchResults() } + return try await request("api/search", query: [URLQueryItem(name: "q", value: hint)]) + } + + func createChat() async throws -> FragmentSummary { + struct Created: Decodable { let name: String } + let created: Created = try await request("api/fragments", method: "POST", body: .object([ + "label": .string("mobile-chat"), "template": .string("chat"), "title": .string("New chat") + ])) + return FragmentSummary(name: created.name, kind: "chat", title: "New chat") + } + + func join(_ agent: FragmentSummary, to chat: FragmentSummary) async throws { + struct Status: Decodable { let npub: String } + let status: Status = try await request("api/f/\(agent.name)/status") + let _: JSONValue = try await request("api/f/\(chat.name)/members/\(status.npub)", + method: "PUT", body: .object(["role": .string("editor")])) + } + + /// A chat's or an app's name, in its manifest. An agent's name is its + /// job's too, which only the shell and the CLI rewrite today. + func rename(_ fragment: FragmentSummary, title: String) async throws { + precondition(fragment.kind != "agent", "an agent is renamed where its job is rewritten with it") + let clean = title.trimmingCharacters(in: .whitespacesAndNewlines) + guard !clean.isEmpty else { throw APIError.server("Enter a name.") } + let manifest: JSONValue = try await request("api/f/\(fragment.name)/manifest") + guard case .object(var fields) = manifest else { throw APIError.invalidResponse } + var meta: [String: JSONValue] = [:] + if case .object(let existing) = fields["meta"] { meta = existing } + meta["title"] = .string(clean); fields["meta"] = .object(meta) + let text = String(decoding: try JSONEncoder().encode(JSONValue.object(fields)), as: UTF8.self) + let _: JSONValue = try await request("api/f/\(fragment.name)/files", method: "POST", body: .object([ + "message": .string("Rename from Finite for iOS"), "files": .array([.object(["path": .string("fragment.json"), "text": .string(text)])]) + ])) + let _: JSONValue = try await request("api/f/\(fragment.name)/deploy", method: "POST", body: .object([:])) + } + + func post(_ fragment: String, id: String, body: JSONValue) async throws -> ChannelRecord { + struct Posted: Decodable { let record: ChannelRecord } + let result: Posted = try await request("api/f/\(fragment)/channels/chat", method: "POST", + body: .object(["id": .string(id), "body": body])) + return result.record + } + + func history(_ fragment: String, before: Int64) async throws -> [ChannelRecord] { + struct Page: Decodable { let records: [ChannelRecord] } + var components = URLComponents(url: url("api/f/\(fragment)/channels/chat"), resolvingAgainstBaseURL: false)! + components.queryItems = [URLQueryItem(name: "before", value: String(before)), URLQueryItem(name: "limit", value: "50")] + let (data, response) = try await self.data(for: URLRequest(url: components.url!)) + guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } + guard http.statusCode == 200 else { throw APIError.answer(data, status: http.statusCode) } + guard data.count <= 4_194_304 else { throw APIError.limitExceeded } + return try JSONDecoder().decode(Page.self, from: data).records + } + + func socket(_ fragment: String) throws -> LiveSocket { try webSocket("f/\(fragment)/__live") } + func webSocket(_ path: String) throws -> LiveSocket { + var components = URLComponents(url: url(path), resolvingAgainstBaseURL: false)! + components.scheme = baseURL.scheme == "https" ? "wss" : "ws" + let request = try authorized(URLRequest(url: components.url!)) + if let sockets { return sockets(request) } + let socket = session.webSocketTask(with: request) + socket.maximumMessageSize = 1_048_576 + return socket + } +} diff --git a/ios/Sources/FragmentApp.swift b/ios/Sources/FragmentApp.swift new file mode 100644 index 000000000..03900e3fe --- /dev/null +++ b/ios/Sources/FragmentApp.swift @@ -0,0 +1,261 @@ +import SwiftUI + +@main +struct FragmentApp: App { + var body: some Scene { WindowGroup { FragmentRoot() } } +} + +@MainActor @Observable +final class Library { + var fragments: [FragmentSummary] = [] + var error: String? + var loading = false + var creating = false + var unfinishedChat: FragmentSummary? + var selectedAgent: String? + var imported: [ImportedSession] = [] + var identities: [String: String] = [:] + var sessions: [String: ChatSession] = [:] + @ObservationIgnored private var loadID = UUID() + + var chats: [FragmentSummary] { fragments.filter { $0.kind == "chat" && $0.archived != true }.sorted { ($0.previewAt ?? 0) > ($1.previewAt ?? 0) } } + var allApps: [FragmentSummary] { fragments.filter { ["app", "brain"].contains($0.kind) && $0.name != "add-app" } } + var apps: [FragmentSummary] { allApps.filter { $0.archived != true } } + var shownApps: [FragmentSummary] { apps.filter { $0.dock != false } } + var agents: [FragmentSummary] { fragments.filter { $0.kind == "agent" && $0.archived != true } } + var agent: FragmentSummary? { agents.first { $0.name == selectedAgent } ?? agents.first } + func agent(for identity: String) -> FragmentSummary? { agents.first { identities[$0.name] == identity } } + func agents(in chat: FragmentSummary) -> [FragmentSummary] { (chat.agents ?? []).compactMap { agent(for: $0) } } + func session(for chat: FragmentSummary, api: FragmentAPI) throws -> ChatSession { + if let session = sessions[chat.name] { return session } + // A bounded cache preserves unsent messages and cursors while changing screens. + if sessions.count >= 32, let unused = sessions.first(where: { $0.value.pending.isEmpty && $0.value.composer.isEmpty && $0.value.attachments.isEmpty })?.key { + sessions.removeValue(forKey: unused) + } + guard sessions.count < 32 else { throw APIError.server("Finish or discard a draft in another chat before opening more conversations.") } + let session = ChatSession(fragment: chat, api: api) + sessions[chat.name] = session + return session + } + func load(_ api: FragmentAPI) async { + let requestID = UUID(); loadID = requestID + loading = true + defer { if loadID == requestID { loading = false } } + do { + let response = try await api.fragments() + guard loadID == requestID, !Task.isCancelled else { return } + fragments = response + for index in fragments.indices { + if let picture = fragments[index].picture { fragments[index].picture = URL(string: picture, relativeTo: api.baseURL)?.absoluteString } + } + if let archive = fragments.first(where: { $0.kind == "sessions" && $0.role == "owner" }) { + struct Index: Decodable { let sessions: [ImportedSession] } + let index: Index = try await api.request("api/f/\(archive.name)/file", query: [URLQueryItem(name: "path", value: "index/chat.json")]) + guard loadID == requestID, !Task.isCancelled else { return } + imported = Array(index.sessions.prefix(2_000)) + } else { imported = [] } + for agent in agents.prefix(32) where identities[agent.name] == nil { + let status: FragmentStatus = try await api.request("api/f/\(agent.name)/status") + identities[agent.name] = status.npub + } + if loadID == requestID { error = nil } + } catch { if loadID == requestID && !Task.isCancelled { self.error = error.localizedDescription } } + } + func watch(_ api: FragmentAPI) async { + var attempt = 0 + // One foreground watcher. Cancellation closes an outstanding receive; + // reconnect reloads the list so changes during a disconnect are included. + while !Task.isCancelled { + let socket: LiveSocket + do { socket = try api.webSocket("api/fragments/watch") } + catch { self.error = error.localizedDescription; return } + socket.resume() + do { + try await withTaskCancellationHandler { + while !Task.isCancelled { + let message = try await socket.receive() + let data: Data + switch message { + case .data(let bytes): data = bytes + case .string(let text): data = Data(text.utf8) + @unknown default: throw APIError.invalidResponse + } + let frame = try JSONDecoder().decode(JSONValue.self, from: data) + if ["hello", "changed", "invited"].contains(frame["type"].string ?? "") { + attempt = 0 + await load(api) + } + } + } onCancel: { socket.cancel(with: .goingAway, reason: nil) } + } catch { + let status = (socket.response as? HTTPURLResponse)?.statusCode + if status == 401 { api.authorizationFailed() } + if status == 401 || status == 403 { socket.cancel(with: .goingAway, reason: nil); return } + } + socket.cancel(with: .goingAway, reason: nil) + guard !Task.isCancelled else { return } + attempt = min(attempt + 1, 4) + do { try await Task.sleep(for: .seconds(min(15, 1 << attempt))) } catch { return } + } + } + func archive(_ fragment: FragmentSummary, api: FragmentAPI) async { + do { + let _: JSONValue = try await api.request("api/fragments/\(fragment.name)/archived", method: "PUT", body: .object(["archived": .bool(fragment.archived != true)])) + await load(api) + } catch { self.error = error.localizedDescription } + } +} + +struct FragmentStatus: Decodable { struct URLs: Decodable { let canonical: URL }; let npub: String; let urls: URLs } +enum ShellPage: Equatable { case launcher, chat(String), history, settings, apps } + +struct FragmentRoot: View { + @Environment(\.scenePhase) private var scenePhase + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var library = Library() + @State private var connection = AccountConnection() + @State private var showingConnection = false + @State private var page = ShellPage.launcher + @State private var sidebar = false + @State private var showingApps = false + @State private var openedApp: FragmentSummary? + @State private var homeDraft = "" + + var body: some View { + Group { + if let api = connection.api { shell(api).environment(\.fragmentAPI, api) } + else { AccountConnectionView(connection: connection) } + } + .tint(.primary) + .sheet(isPresented: $showingConnection) { AccountConnectionView(connection: connection, close: { showingConnection = false }) } + .onChange(of: connection.account?.identity) { _, _ in resetNavigation() } + .onChange(of: connection.account?.approved) { _, _ in resetNavigation() } + .onReceive(NotificationCenter.default.publisher(for: .finiteAuthorizationFailed).receive(on: RunLoop.main)) { notification in + guard let api = connection.api, notification.userInfo?["origin"] as? String == api.baseURL.absoluteString, + notification.userInfo?["key"] as? String == api.identity.publicKey else { return } + Task { await connection.validate() } + } + .task(id: scenePhase) { if scenePhase == .active { await connection.validate() } } + } + private func resetNavigation() { + library = Library(); page = .launcher; sidebar = false; showingApps = false; openedApp = nil + } + private func shell(_ api: FragmentAPI) -> some View { + ZStack(alignment: .leading) { + Group { + if showingApps { + NavigationStack { + AppsLibraryView(library: library, api: api, open: { openedApp = $0 }) + .navigationTitle("Your Apps").navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Show sidebar", systemImage: "sidebar.left") { toggleSidebar() }.accessibilityIdentifier("sidebar") + } + } + } + } else { + NavigationStack { + content(api) + .navigationTitle(title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button("Show sidebar", systemImage: "sidebar.left") { toggleSidebar() }.accessibilityIdentifier("sidebar") + } + ToolbarItemGroup(placement: .topBarTrailing) { + if page != .launcher && page != .apps { + Button("Search or start a new chat", systemImage: "square.and.pencil") { page = .launcher }.accessibilityIdentifier("launcher") + } + if page != .apps { + Button("Your Apps", systemImage: "list.bullet.rectangle") { showingApps = true }.accessibilityIdentifier("yourApps") + } + } + } + } + } + } + .accessibilityHidden(sidebar) + SidebarDrawer(isPresented: $sidebar, library: library, page: page, api: api) { destination in + page = destination; showingApps = false; toggleSidebar() + } + } + .sheet(item: $openedApp) { app in + if app.name == "add-app" { + AppCreationSheet { prompt in + openedApp = nil; showingApps = false; homeDraft = prompt; page = .launcher + } + } else { FragmentWebSheet(fragment: app, api: api) } + } + .task(id: "\(api.baseURL)|\(api.identity.publicKey)|\(scenePhase)") { + if scenePhase == .active { await library.load(api); await library.watch(api) } + } + .onChange(of: showingApps) { _, presented in + if presented { dismissKeyboard() } + } + } + @ViewBuilder private func content(_ api: FragmentAPI) -> some View { + switch page { + case .launcher: + HomeView(library: library, api: api, draft: $homeDraft, open: { open($0) }, start: { await start(api) }, refresh: { await library.load(api) }) + case .chat(let name): + if let chat = library.fragments.first(where: { $0.name == name }) { + if let session = try? library.session(for: chat, api: api) { + ChatView(session: session, library: library).id(chat.name) + } else { + ContentUnavailableView("Your drafts are safe", systemImage: "square.and.pencil", description: Text("Finish or discard a draft in another chat before opening more conversations.")) + } + } + case .history: HistoryView(library: library, api: api, open: { open($0) }) + case .apps: AppsLibraryView(library: library, api: api, open: { openedApp = $0 }) + case .settings: SettingsView(library: library, api: api, connect: { showingConnection = true }, open: { open($0) }) + } + } + private var title: String { + if showingApps { return "Your Apps" } + switch page { + case .launcher: return "Finite.Computer" + case .chat(let name): return library.fragments.first { $0.name == name }?.displayTitle ?? "Chat" + case .history: return "History" + case .apps: return "Your Apps" + case .settings: return "Settings" + } + } + private func dismissKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + private func toggleSidebar() { + if !sidebar { dismissKeyboard() } + withAnimation(reduceMotion ? nil : .snappy(duration: 0.25)) { sidebar.toggle() } + } + private func open(_ fragment: FragmentSummary) { + if fragment.kind == "chat" { page = .chat(fragment.name); showingApps = false } + else if fragment.kind == "launcher" { page = .launcher } + else { openedApp = fragment } + } + private func start(_ api: FragmentAPI) async { + guard let agent = library.agent, !library.creating else { return } + library.creating = true + defer { library.creating = false } + do { + if library.unfinishedChat == nil { library.unfinishedChat = try await api.createChat() } + guard let chat = library.unfinishedChat else { return } + try await api.join(agent, to: chat) + library.unfinishedChat = nil + await library.load(api) + let resolved = library.fragments.first { $0.name == chat.name } ?? chat + let session = try library.session(for: resolved, api: api) + session.composer = homeDraft + homeDraft = "" + page = .chat(chat.name) + await session.send() + await library.load(api) + } catch { library.error = error.localizedDescription } + } +} + +enum FragmentStyle { + static let background = Color(uiColor: UIColor { $0.userInterfaceStyle == .dark ? UIColor(white: 24 / 255, alpha: 1) : UIColor(red: 247 / 255, green: 246 / 255, blue: 243 / 255, alpha: 1) }) + static let surface = Color(uiColor: .secondarySystemGroupedBackground) + static let bubble = Color(uiColor: UIColor { $0.userInterfaceStyle == .dark ? UIColor(white: 57 / 255, alpha: 1) : UIColor(red: 233 / 255, green: 230 / 255, blue: 223 / 255, alpha: 1) }) + static let lilac = Color(red: 168 / 255, green: 139 / 255, blue: 234 / 255) +} diff --git a/ios/Sources/FragmentWebSheet.swift b/ios/Sources/FragmentWebSheet.swift new file mode 100644 index 000000000..8f11eb6c1 --- /dev/null +++ b/ios/Sources/FragmentWebSheet.swift @@ -0,0 +1,129 @@ +import SwiftUI +import WebKit + +struct FragmentWebSheet: View { + @Environment(\.dismiss) private var dismiss + let fragment: FragmentSummary + let api: FragmentAPI + @State private var url: URL? + @State private var error: String? + @State private var loading = true + @State private var needsConsent = false + @State private var consentEmail: String? + + var body: some View { + NavigationStack { + Group { + if needsConsent { + ContentUnavailableView { + Label("Open \(fragment.displayTitle)?", systemImage: "person.crop.circle.badge.checkmark") + } description: { + Text("Continue as \(consentEmail ?? "your account")? This app will be able to recognize you.") + } actions: { + Button("Continue") { Task { await load(consent: true) } } + Button("Cancel", role: .cancel) { dismiss() } + } + } else if let error { + ContentUnavailableView { + Label("Couldn’t open this app", systemImage: "wifi.exclamationmark") + } description: { Text(error) } actions: { + Button("Try again") { Task { await load() } } + } + } else if let url { + WebContent(url: url, loading: $loading, error: $error) + .overlay { if loading { ProgressView() } } + } else { ProgressView() } + } + .navigationTitle(fragment.displayTitle).navigationBarTitleDisplayMode(.inline) + .task { await load() } + } + .presentationDetents([.large]) + .presentationDragIndicator(.visible) + .accessibilityAction(.escape) { dismiss() } + } + + private func load(consent: Bool = false) async { + error = nil; needsConsent = false + loading = true + do { + struct Status: Decodable { struct URLs: Decodable { let canonical: URL }; let urls: URLs } + let status: Status = try await api.request("api/f/\(fragment.name)/status") + guard ["http", "https"].contains(status.urls.canonical.scheme), status.urls.canonical.host != nil, status.urls.canonical.user == nil, status.urls.canonical.password == nil else { + throw APIError.invalidResponse + } + var origin = URLComponents(url: status.urls.canonical, resolvingAgainstBaseURL: false)! + origin.path = ""; origin.query = nil; origin.fragment = nil + _ = try PlatformOrigin.parse(origin.url!.absoluteString) + var components = URLComponents(url: status.urls.canonical, resolvingAgainstBaseURL: false) + if let key = fragment.sessionKey { components?.queryItems = [URLQueryItem(name: "s", value: key)] } + guard let destination = components?.url else { throw APIError.invalidResponse } + struct Session: Decodable { let consentRequired: Bool; var url: URL?; var email: String? } + let back = components!.percentEncodedPath + (components!.percentEncodedQuery.map { "?" + $0 } ?? "") + let session: Session = try await api.request("api/f/\(fragment.name)/session", method: "POST", body: .object([ + "returnTo": .string(back.isEmpty ? "/" : back), "consent": .bool(consent) + ])) + if session.consentRequired { consentEmail = session.email; needsConsent = true; loading = false; return } + guard let redemption = session.url, PlatformOrigin.same(redemption, destination), + redemption.user == nil, redemption.password == nil, redemption.fragment == nil, + redemption.path.hasSuffix("/__signin") else { throw APIError.invalidResponse } + url = redemption + } catch { self.error = error.localizedDescription } + } +} + +private struct WebContent: UIViewRepresentable { + let url: URL + @Binding var loading: Bool + @Binding var error: String? + + func makeCoordinator() -> Coordinator { Coordinator(loading: $loading, error: $error, origin: url) } + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + // Each sheet has its own web storage. Native credentials never enter a + // fragment page; connected accounts use a scoped redemption (docs/ios.md). + configuration.websiteDataStore = .nonPersistent() + let web = WKWebView(frame: .zero, configuration: configuration) + web.navigationDelegate = context.coordinator + web.allowsBackForwardNavigationGestures = true + web.load(URLRequest(url: url)) + return web + } + func updateUIView(_ uiView: WKWebView, context: Context) {} + + @MainActor final class Coordinator: NSObject, WKNavigationDelegate { + @Binding var loading: Bool + @Binding var error: String? + let origin: URL + init(loading: Binding, error: Binding, origin: URL) { + _loading = loading; _error = error; self.origin = origin + } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { loading = false } + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + self.error = error.localizedDescription; loading = false + } + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + self.error = error.localizedDescription; loading = false + } + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy { + if navigationResponse.isForMainFrame, let response = navigationResponse.response as? HTTPURLResponse, response.statusCode >= 400 { + error = "The app could not be opened (HTTP \(response.statusCode)). Check your access and try again." + loading = false + return .cancel + } + return .allow + } + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + guard let url = navigationAction.request.url else { return .cancel } + guard ["http", "https", "about"].contains(url.scheme) else { return .cancel } + if url.scheme != "about", (url.host != origin.host || url.port != origin.port || url.scheme != origin.scheme) { + if navigationAction.navigationType == .linkActivated { _ = await UIApplication.shared.open(url) } + return .cancel + } + if navigationAction.targetFrame == nil, navigationAction.navigationType == .linkActivated { + webView.load(URLRequest(url: url)) + return .cancel + } + return .allow + } + } +} diff --git a/ios/Sources/HomeView.swift b/ios/Sources/HomeView.swift new file mode 100644 index 000000000..c515b5127 --- /dev/null +++ b/ios/Sources/HomeView.swift @@ -0,0 +1,208 @@ +import SwiftUI + +struct HomeView: View { + let library: Library + let api: FragmentAPI + @Binding var draft: String + let open: (FragmentSummary) -> Void + let start: () async -> Void + let refresh: () async -> Void + @State private var results = SearchResults() + @State private var searching = false + @State private var searchError: String? + @FocusState private var focused: Bool + private var query: String { draft.trimmingCharacters(in: .whitespacesAndNewlines) } + private var canStart: Bool { !query.isEmpty && query.utf8.count <= 32_768 && library.agent != nil && !library.creating } + + var body: some View { + ZStack { + DesktopWallpaper() + VStack(spacing: 0) { + Spacer(minLength: 24) + VStack(spacing: 0) { + HStack(spacing: 10) { + Image(systemName: "magnifyingglass").foregroundStyle(.secondary) + TextField("Ask anything, or find a chat or app…", text: $draft) + .focused($focused).submitLabel(.go).accessibilityIdentifier("launcherInput") + .disabled(library.creating).onSubmit(launch) + if !draft.isEmpty { + Button("Clear search", systemImage: "xmark.circle.fill") { draft = "" } + .labelStyle(.iconOnly).foregroundStyle(.secondary).disabled(library.creating) + } + Button(action: launch) { + if library.creating { ProgressView().frame(width: 36, height: 36) } + else { Image(systemName: "arrow.up").frame(width: 36, height: 36) } + }.buttonStyle(.glassProminent).buttonBorderShape(.circle) + .disabled(!canStart).accessibilityLabel("Start new chat").accessibilityIdentifier("startChat") + }.padding(16) + if let agent = library.agent { + HStack { + Menu { + ForEach(library.agents) { agent in + Button { library.selectedAgent = agent.name } label: { + Label(agent.displayTitle, systemImage: agent.name == library.agent?.name ? "checkmark" : "person") + } + } + } label: { + HStack(spacing: 8) { AgentAvatar(agent: agent, size: 22); Text(agent.displayTitle); Image(systemName: "chevron.down").font(.caption2) } + .font(.caption.weight(.medium)) + }.accessibilityIdentifier("launcherAgent") + Spacer() + }.padding(.horizontal, 18).padding(.bottom, 14) + } + if !query.isEmpty { + Divider() + ScrollView { + LazyVStack(alignment: .leading, spacing: 4) { + if library.agent != nil { + Button(action: launch) { + Label { + VStack(alignment: .leading, spacing: 5) { + Text("Start a new chat with \(library.agent?.displayTitle ?? "your agent")").font(.subheadline.weight(.medium)) + Text(query).font(.caption).foregroundStyle(.secondary).lineLimit(2) + } + } icon: { Image(systemName: "square.and.pencil").frame(width: 30) } + .frame(maxWidth: .infinity, alignment: .leading).padding(12) + }.disabled(!canStart).accessibilityIdentifier("newChatResult") + } + foundFragments("Chats", kinds: ["chat"]) + foundFragments("Apps", kinds: ["app", "brain"]) + if !results.messages.isEmpty { + heading("Messages") + ForEach(results.messages) { hit in + if let fragment = library.fragments.first(where: { $0.name == hit.fragment }) { + Button { focused = false; open(fragment) } label: { + VStack(alignment: .leading, spacing: 5) { + Text(fragment.displayTitle).font(.subheadline.weight(.medium)) + Text(hit.snippet).font(.caption).foregroundStyle(.secondary).lineLimit(3) + }.frame(maxWidth: .infinity, alignment: .leading).padding(12) + }.accessibilityIdentifier("messageResult-\(hit.id)") + } + } + } + if searching { ProgressView("Searching…").padding() } + if let searchError { Text(searchError).font(.footnote).foregroundStyle(.secondary).padding() } + }.padding(6).buttonStyle(.plain) + }.frame(maxHeight: 330).scrollDismissesKeyboard(.interactively) + } + if library.agent == nil && !library.loading { + VStack(spacing: 8) { + Text("Your account has no agent yet.").foregroundStyle(.secondary) + Link("Set up your agent on the web", destination: api.baseURL.appending(path: "welcome")) + }.font(.footnote).padding() + } + if let error = library.error { + Text(error).font(.footnote).foregroundStyle(.secondary).padding() + Button("Try again") { Task { await refresh() } }.padding(.bottom) + } + } + .glassEffect(.regular, in: .rect(cornerRadius: 26)) + .frame(maxWidth: 620).padding(.horizontal, 16) + Spacer(minLength: 24) + } + } + .task(id: query) { + results = SearchResults(); searchError = nil; searching = false + guard !query.isEmpty else { return } + let asked = query + do { + try await Task.sleep(for: .milliseconds(250)); searching = true + let found = try await api.search(asked) + try Task.checkCancellation() + guard asked == query else { return } + results = found; searching = false + } catch { guard !Task.isCancelled, asked == query else { return }; searchError = error.localizedDescription; searching = false } + } + } + private func heading(_ text: String) -> some View { Text(text).font(.caption.weight(.semibold)).foregroundStyle(.secondary).padding(.horizontal, 12).padding(.top, 10) } + @ViewBuilder private func foundFragments(_ title: String, kinds: Set) -> some View { + let found = results.fragments.filter { kinds.contains($0.kind) } + if !found.isEmpty { + heading(title) + ForEach(found) { fragment in + Button { focused = false; open(fragment) } label: { + HStack(spacing: 12) { + if fragment.kind == "chat" { AgentAvatar(agent: library.agents(in: fragment).first, size: 28) } + else { Image(systemName: "app").frame(width: 28) } + VStack(alignment: .leading, spacing: 4) { + Text(fragment.displayTitle).font(.subheadline.weight(.medium)) + if let preview = fragment.preview { Text(preview).font(.caption).foregroundStyle(.secondary).lineLimit(1) } + } + Spacer(minLength: 0) + if fragment.archived == true { Image(systemName: "archivebox").font(.caption).foregroundStyle(.secondary) } + }.padding(12) + }.accessibilityIdentifier("fragmentResult-\(fragment.name)") + } + } + } + private func launch() { guard canStart else { return }; focused = false; Task { await start() } } +} + +struct ComposerView: View { + @Binding var text: String + let working: Bool + let enabled: Bool + var busy = false + var recording = false + let attach: () -> Void + let record: () -> Void + let send: () async -> Void + let stop: () async -> Void + @FocusState private var focused: Bool + @State private var submitting = false + var hasAttachments = false + private var valid: Bool { (!text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || hasAttachments) && text.utf8.count <= 32_768 } + private var canSend: Bool { !submitting && !busy && !recording && enabled && (working || valid) } + var body: some View { + // A single glass surface, with equal hit targets and no nested glass + // button padding, keeps the single-line and growing field aligned. + HStack(alignment: .bottom, spacing: 0) { + Button(action: attach) { + Image(systemName: "plus").font(.system(size: 20)) + .frame(width: 44, height: 44).contentShape(Rectangle()) + } + .accessibilityLabel("Attach files").disabled(busy || recording) + Button(action: record) { + Image(systemName: recording ? "stop.circle.fill" : "mic") + .font(.system(size: 20)) + .foregroundStyle(recording ? Color.red : Color.primary) + .frame(width: 44, height: 44).contentShape(Rectangle()) + } + .accessibilityLabel(recording ? "Finish voice memo" : "Record a voice memo") + .disabled(busy) + TextField("Message your agent", text: $text, axis: .vertical) + .font(.body).lineLimit(1...6) + .padding(.vertical, 10).padding(.horizontal, 6) + .frame(minHeight: 44).focused($focused).accessibilityIdentifier("composer") + Button { + submitting = true + Task { await (working ? stop() : send()); submitting = false } + } label: { + ZStack { + // The adaptive label color keeps the filled control high-contrast; + // the glass foreground style is softened by vibrancy. + Circle().fill(canSend ? Color(uiColor: .label) : Color(uiColor: .tertiarySystemFill)) + if busy || submitting { + ProgressView().tint(.secondary) + } else { + Image(systemName: working ? "stop.fill" : "arrow.up") + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(canSend ? Color(uiColor: .systemBackground) : Color.secondary) + } + } + .frame(width: 34, height: 34) + .frame(width: 44, height: 44) + .contentShape(Circle()) + } + .disabled(!canSend) + .accessibilityLabel(working ? "Stop response" : "Send message") + .accessibilityIdentifier(working ? "stop" : "send") + } + .buttonStyle(.plain) + .padding(6) + .glassEffect(.regular, in: .rect(cornerRadius: 28)) + .frame(maxWidth: 660) + .padding(.horizontal, 12).padding(.top, 8).padding(.bottom, 8) + .frame(maxWidth: .infinity) + } +} diff --git a/ios/Sources/MarkdownText.swift b/ios/Sources/MarkdownText.swift new file mode 100644 index 000000000..60463b28e --- /dev/null +++ b/ios/Sources/MarkdownText.swift @@ -0,0 +1,82 @@ +import SwiftUI + +// A small native renderer for the chat slice: paragraphs, headings, lists, +// quotes and fenced code. Inline formatting and links are Apple's Markdown. +// Blocks have positional identities so a growing final block stays in place. +struct MarkdownBlock: Identifiable, Equatable { + enum Kind: Equatable { case paragraph, heading, code(String), quote } + let id: Int + let kind: Kind + let text: String + + static func parse(_ source: String) -> [MarkdownBlock] { + var blocks: [MarkdownBlock] = [] + var lines: [String] = [] + var code: String? + func append(_ kind: Kind) { + guard !lines.isEmpty else { return } + blocks.append(MarkdownBlock(id: blocks.count, kind: kind, text: lines.joined(separator: "\n"))) + lines = [] + } + for line in source.components(separatedBy: "\n") { + if line.hasPrefix("```") { + if let language = code { append(.code(language)); code = nil } + else { append(.paragraph); code = String(line.dropFirst(3)) } + } else if code != nil { lines.append(line) } + else if line.trimmingCharacters(in: .whitespaces).isEmpty { append(.paragraph) } + else if line.hasPrefix("#") { + let heading = line.drop(while: { $0 == "#" }) + if heading.first == " " { + append(.paragraph) + lines = [String(heading.dropFirst())] + append(.heading) + } else { lines.append(line) } + } else if line.hasPrefix("> ") { + append(.paragraph) + lines = [String(line.dropFirst(2))] + append(.quote) + } else { + lines.append(line.hasPrefix("- ") || line.hasPrefix("* ") ? "• " + line.dropFirst(2) : line) + } + } + append(code.map(Kind.code) ?? .paragraph) + return blocks + } +} + +struct MarkdownText: View { + let text: String + var body: some View { + VStack(alignment: .leading, spacing: 14) { + ForEach(MarkdownBlock.parse(text)) { block in + switch block.kind { + case .code(let language): + VStack(alignment: .leading, spacing: 10) { + HStack { + Text(language.isEmpty ? "Code" : language).font(.caption).foregroundStyle(.secondary) + Spacer() + Button("Copy code", systemImage: "doc.on.doc") { UIPasteboard.general.string = block.text } + .labelStyle(.iconOnly).font(.caption).frame(minWidth: 44, minHeight: 32) + } + ScrollView(.horizontal) { Text(block.text).font(.callout.monospaced()).textSelection(.enabled) } + } + .padding(14).background(FragmentStyle.surface, in: RoundedRectangle(cornerRadius: 14)) + case .heading: + inline(block.text).font(.title3.weight(.semibold)) + case .quote: + HStack(alignment: .top, spacing: 12) { + RoundedRectangle(cornerRadius: 2).fill(.secondary.opacity(0.3)).frame(width: 3) + inline(block.text).foregroundStyle(.secondary) + }.fixedSize(horizontal: false, vertical: true) + case .paragraph: + inline(block.text).font(.body).lineSpacing(5) + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + } + private func inline(_ source: String) -> some View { + Text((try? AttributedString(markdown: source, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))) ?? AttributedString(source)) + .textSelection(.enabled).fixedSize(horizontal: false, vertical: true) + } +} diff --git a/ios/Sources/Models.swift b/ios/Sources/Models.swift new file mode 100644 index 000000000..67e110fc9 --- /dev/null +++ b/ios/Sources/Models.swift @@ -0,0 +1,194 @@ +import Foundation + +struct FragmentSummary: Codable, Identifiable, Hashable, Sendable { + let name: String + var kind: String + var title: String? + var agents: [String]? + var preview: String? + var previewAt: Int64? + var archived: Bool? + var dock: Bool? + var role: String? + var picture: String? + // A navigation selection in the session archive, never an authentication token. + var sessionKey: String? + var id: String { name } + var displayTitle: String { title ?? name } +} + +struct SearchResults: Decodable, Sendable { + var fragments: [FragmentSummary] = [] + var messages: [MessageHit] = [] +} + +struct MessageHit: Decodable, Identifiable, Sendable { + let fragment: String + let channel: String + let seq: Int64 + let at: Int64 + let snippet: String + var id: String { "\(fragment):\(channel):\(seq)" } +} + +// Channels accept arbitrary JSON; unknown fields and record kinds must survive +// decoding without making an older app unable to read a newer chat. +enum JSONValue: Codable, Equatable, Sendable { + case string(String), number(Double), bool(Bool), object([String: JSONValue]), array([JSONValue]), null + + init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer() + if value.decodeNil() { self = .null } + else if let v = try? value.decode(String.self) { self = .string(v) } + else if let v = try? value.decode(Bool.self) { self = .bool(v) } + else if let v = try? value.decode(Double.self) { self = .number(v) } + else if let v = try? value.decode([String: JSONValue].self) { self = .object(v) } + else { self = .array(try value.decode([JSONValue].self)) } + } + + func encode(to encoder: Encoder) throws { + var value = encoder.singleValueContainer() + switch self { + case .string(let v): try value.encode(v) + case .number(let v): try value.encode(v) + case .bool(let v): try value.encode(v) + case .object(let v): try value.encode(v) + case .array(let v): try value.encode(v) + case .null: try value.encodeNil() + } + } + + subscript(_ key: String) -> JSONValue { + if case .object(let value) = self { return value[key] ?? .null } + return .null + } + var string: String? { if case .string(let v) = self { return v }; return nil } + var array: [JSONValue] { if case .array(let v) = self { return v }; return [] } +} + +struct ChannelRecord: Codable, Identifiable, Equatable, Sendable { + let channel: String + let seq: Int64 + let at: Int64 + let principal: String + let kind: String + let body: JSONValue + var id: String { "\(channel):\(seq)" } + var text: String { body["text"].string ?? body.string ?? "" } + var turn: String? { body["turn"].string } + var isMessage: Bool { + channel == "chat" && (body["kind"].string == nil || body["kind"].string == "message") + } +} + +struct LiveFrame: Decodable, Sendable { + let type: String + var channel: String? + var seq: Int64? + var at: Int64? + var principal: String? + var kind: String? + var body: JSONValue? + var turn: String? + var text: String? + var next: Int64? + var more: Bool? + var message: String? + + var record: ChannelRecord? { + guard let channel, let seq, seq > 0, let at, let principal, let kind, let body else { return nil } + return ChannelRecord(channel: channel, seq: seq, at: at, principal: principal, kind: kind, body: body) + } +} + +struct ReplyDraft: Identifiable, Equatable { + let turn: String + let principal: String + var text: String + var at: Int64 + var id: String { turn } +} + +struct Timeline { + private(set) var records: [ChannelRecord] = [] + private(set) var drafts: [String: ReplyDraft] = [:] + private(set) var cursors: [String: Int64] = [:] + // Bound the in-memory window independently of the server's retention. + static let maximumRecords = 2_000 + + mutating func insert(_ record: ChannelRecord, advancingCursor: Bool = true) { + if advancingCursor { cursors[record.channel] = max(cursors[record.channel] ?? 0, record.seq) } + if !records.contains(where: { $0.id == record.id }) { + records.append(record) + records.sort { + if $0.at != $1.at { return $0.at < $1.at } + if $0.channel != $1.channel { return $0.channel < $1.channel } + return $0.seq < $1.seq + } + if records.count > Self.maximumRecords { records.removeFirst(records.count - Self.maximumRecords) } + } else { return } + if advancingCursor, record.channel == "chat", let turn = record.turn, + drafts[turn]?.principal == record.principal { + drafts.removeValue(forKey: turn) + } + if record.channel == "work", record.body["kind"].string == "turn.end", let turn = record.turn { + drafts.removeValue(forKey: turn) + } + } + + mutating func apply(_ frame: LiveFrame) throws { + switch frame.type { + case "record": + guard let record = frame.record else { throw APIError.invalidResponse } + insert(record) + case "draft": + guard frame.channel == "chat", let turn = frame.turn, let principal = frame.principal else { return } + if let text = frame.text { + guard text.utf8.count <= 65_536, drafts.count < 32 || drafts[turn] != nil else { + throw APIError.limitExceeded + } + drafts[turn] = ReplyDraft(turn: turn, principal: principal, text: text, at: frame.at ?? 0) + } else { drafts.removeValue(forKey: turn) } + case "subscribed": + if let channel = frame.channel, let next = frame.next { + cursors[channel] = max(cursors[channel] ?? 0, next) + } + case "error": throw APIError.server(frame.message ?? "The live connection was refused.") + default: break + } + } + + mutating func disconnected() { drafts.removeAll() } + + var activeTurns: [String] { + var active: [String] = [] + for record in records where record.channel == "work" { + guard let turn = record.turn else { continue } + switch record.body["kind"].string { + case "turn.start": if !active.contains(turn) { active.append(turn) } + case "turn.end": active.removeAll { $0 == turn } + default: break + } + } + return active + } +} + +struct PendingPost: Codable, Identifiable, Sendable { + let id: String + let text: String + var failed = false + var attachments: [ChatAttachment] = [] + var replyTo: Int64? + var recipients: [String] = [] +} + +struct ImportedSession: Decodable, Identifiable, Sendable { + let key: String + var title: String? + var preview: String? + var started_at: String? + var ended_at: String? + var id: String { key } + var at: Int64 { Int64((ISO8601DateFormatter().date(from: ended_at ?? started_at ?? "")?.timeIntervalSince1970 ?? 0) * 1000) } +} diff --git a/ios/Sources/SettingsView.swift b/ios/Sources/SettingsView.swift new file mode 100644 index 000000000..b6aa20f36 --- /dev/null +++ b/ios/Sources/SettingsView.swift @@ -0,0 +1,187 @@ +import SwiftUI +import UserNotifications + +enum SettingsSection: String, CaseIterable, Identifiable { + case account = "Account", billing = "Billing", computer = "Computer", agents = "Agents", skills = "Skills", connections = "Connections", developer = "Build with Claude Code or Codex", data = "Your data", device = "This iPhone" + var id: String { rawValue } + var icon: String { + switch self { + case .account: "person.crop.circle" + case .billing: "creditcard" + case .computer: "desktopcomputer" + case .agents: "person.2" + case .skills: "sparkles" + case .connections: "point.3.connected.trianglepath.dotted" + case .developer: "terminal" + case .data: "square.and.arrow.down" + case .device: "iphone" + } + } +} + +struct SettingsView: View { + let library: Library + let api: FragmentAPI + var connect: () -> Void = {} + let open: (FragmentSummary) -> Void + var body: some View { + List { + Section { + HStack(spacing: 14) { + Image(systemName: "circle.hexagongrid.fill").font(.title).foregroundStyle(FragmentStyle.lilac) + VStack(alignment: .leading, spacing: 4) { Text("Finite.Computer").font(.headline); Text("A computer of your own.").font(.subheadline).foregroundStyle(.secondary) } + }.padding(.vertical, 8) + } + Section { + NavigationLink { AppManagementList(library: library, api: api) } label: { + Label("Apps", systemImage: "app.badge").padding(.vertical, 4) + }.accessibilityIdentifier("settings-Apps") + ForEach(SettingsSection.allCases) { section in + NavigationLink { SettingsDetail(section: section, library: library, api: api, open: open) } label: { + Label(section.rawValue, systemImage: section.icon).padding(.vertical, 4) + }.accessibilityIdentifier("settings-\(section.id)") + } + } + Section { + Button("Account connection", systemImage: "person.crop.circle.badge.checkmark", action: connect).accessibilityIdentifier("accountConnection") + NavigationLink("Open-source notices") { + ScrollView { + Text(thirdPartyNotices).font(.footnote).textSelection(.enabled).frame(maxWidth: .infinity, alignment: .leading).padding() + }.navigationTitle("Open-source notices") + } + LabeledContent("Version", value: "0.3") + } footer: { Text("Connected to " + api.baseURL.absoluteString) } + }.scrollContentBackground(.hidden).background(FragmentStyle.background) + } + private var thirdPartyNotices: String { + guard let url = Bundle.main.url(forResource: "ThirdPartyNotices", withExtension: "txt"), + let text = try? String(contentsOf: url, encoding: .utf8) else { return "Notices could not be loaded." } + return text + } + +} + +private struct SettingsDetail: View { + let section: SettingsSection + let library: Library + let api: FragmentAPI + let open: (FragmentSummary) -> Void + @State private var value: JSONValue = .null + @State private var loading = false + @State private var error: String? + @State private var confirmRestart = false + @State private var notificationStatus = "Not enabled" + var body: some View { + Form { + if loading { ProgressView("Loading…") } + if let error { Section { Text(error).foregroundStyle(.secondary); Button("Try again") { Task { await load() } } } } + fields + } + .navigationTitle(section == .developer ? "Build with an agent" : section.rawValue).navigationBarTitleDisplayMode(.inline) + .task { await load() } + .refreshable { await load() } + .confirmationDialog("Restart your computer?", isPresented: $confirmRestart, titleVisibility: .visible) { + Button("Restart computer", role: .destructive) { Task { await restart() } } + } message: { Text("Saves your computer if it can, then starts it fresh. Anything its agents are doing stops.") } + } + @ViewBuilder private var fields: some View { + switch section { + case .account: + Section("Account") { + LabeledContent("Email", value: value["email"].string ?? "Not connected") + LabeledContent("Identity", value: value["id"].string ?? "—").font(.caption).textSelection(.enabled) + } + Section { + Text("This iPhone uses its own approved device key. Manage or disconnect it from Account connection in Settings.").foregroundStyle(.secondary) + } + Section { Link("Privacy", destination: URL(string: "https://finite.chat/privacy")!); Link("Terms", destination: URL(string: "https://finite.chat/terms")!) } + case .billing: + Section("Your seat") { + LabeledContent("Plan", value: value["seat"]["kind"].string ?? "No seat") + LabeledContent("Organization", value: value["seat"]["org"]["name"].string ?? "—") + Text("Manage purchases and subscriptions on your Finite website.").font(.footnote).foregroundStyle(.secondary) + } + case .computer: + if let computer = computer { + Section("Your computer") { + LabeledContent("State", value: computer["state"].string?.capitalized ?? "—") + LabeledContent("Version", value: computer["version"].string ?? "—") + Button("Restart computer", systemImage: "arrow.clockwise") { confirmRestart = true } + } + } else if !loading { ContentUnavailableView("No computer", systemImage: "desktopcomputer", description: Text("Your computer will appear here when connected.")) } + case .agents: + ForEach(library.agents) { agent in + Section { + HStack { AgentAvatar(agent: agent); Text(agent.displayTitle).font(.headline) } + Button("Start a new chat", systemImage: "square.and.pencil") { library.selectedAgent = agent.name; openLauncher() } + LabeledContent("Chats", value: String(library.chats.filter { ($0.agents ?? []).contains(library.identities[agent.name] ?? "") }.count)) + } + } + case .skills: + Section { + Text("What your agents know how to do, kept in one place.") + if let skills = library.fragments.first(where: { $0.kind == "skills" || $0.name == "skills" }) { Button("Open skills", systemImage: "sparkles") { open(skills) } } + else { Text("No skills library is connected.").foregroundStyle(.secondary) } + } + case .connections: + ForEach(Array(value["providers"].array.enumerated()), id: \.offset) { _, provider in + Section(provider["provider"].string ?? "Connection") { + LabeledContent("Status", value: (provider["state"].string ?? "Not connected").replacingOccurrences(of: "_", with: " ").capitalized) + if let account = provider["account"].string { LabeledContent("Account", value: account) } + if let note = provider["note"].string { Text(note).font(.footnote).foregroundStyle(.secondary) } + } + } + Section { Text("Connect and authorize providers on the web. Credentials stay on your computer’s connection service.").font(.footnote).foregroundStyle(.secondary) } + case .developer: + Section("Build with Claude Code or Codex") { + Text("Paste this into your coding agent and finish the sentence.") + Text(prompt).font(.callout.monospaced()).textSelection(.enabled) + ShareLink(item: prompt) { Label("Share prompt", systemImage: "square.and.arrow.up") } + Button("Copy prompt", systemImage: "doc.on.doc") { UIPasteboard.general.string = prompt } + } + case .data: + Section("Export") { + Text("Download your fragments, messages, app data, and your computer’s newest save.") + LabeledContent("Status", value: value["export"]["state"].string ?? "No export yet") + Button("Export everything", systemImage: "square.and.arrow.down") { Task { await export() } }.disabled(loading) + if let raw = value["export"]["url"].string, let url = URL(string: raw), ["http", "https"].contains(url.scheme) { ShareLink(item: url) { Text("Share export") } } + } + case .device: + Section("Notifications") { + LabeledContent("Reply notifications", value: notificationStatus) + Button("Enable notifications", systemImage: "bell") { Task { await notifications() } } + Text("Notification delivery while the app is closed is not connected yet.").font(.footnote).foregroundStyle(.secondary) + } + } + } + private var computer: JSONValue? { value["computer"].string == nil ? value["computers"].array.first : value } + private var prompt: String { "Read \(api.url("llms.txt").absoluteString) and help me build: " } + private func openLauncher() { open(FragmentSummary(name: "__launcher", kind: "launcher")) } + private func load() async { + loading = true; error = nil; defer { loading = false } + let path: String + switch section { + case .account: path = "api/identities/me" + case .billing: path = "api/seat" + case .computer: path = "api/computers" + case .connections: path = "api/connections" + case .data: path = "api/export" + default: return + } + do { value = try await api.request(path) } catch { self.error = error.localizedDescription } + } + private func restart() async { + guard let computer, let id = computer["computer"].string else { return } + do { value = try await api.request("api/computers/\(id)/restart", method: "POST", body: .object(["generation": computer["generation"]])) } + catch { self.error = error.localizedDescription } + } + private func export() async { + loading = true; defer { loading = false } + do { let result: JSONValue = try await api.request("api/export", method: "POST", body: .object([:])); value = .object(["export": result]) } + catch { self.error = error.localizedDescription } + } + private func notifications() async { + do { let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]); notificationStatus = granted ? "Allowed" : "Disabled in iOS Settings" } + catch { self.error = error.localizedDescription } + } +} diff --git a/ios/Sources/ShellViews.swift b/ios/Sources/ShellViews.swift new file mode 100644 index 000000000..2b298a6ad --- /dev/null +++ b/ios/Sources/ShellViews.swift @@ -0,0 +1,450 @@ +import SwiftUI +import UIKit + +struct DesktopWallpaper: View { + var body: some View { + GeometryReader { proxy in + Image("DesktopWallpaper").resizable().scaledToFill() + .frame(width: proxy.size.width, height: proxy.size.height).clipped() + }.ignoresSafeArea().accessibilityHidden(true) + } +} + +struct AgentAvatar: View { + let agent: FragmentSummary? + var size: CGFloat = 32 + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: size * 0.28).fill(color.gradient) + if let picture = agent?.picture, let url = URL(string: picture) { + AuthenticatedImage(url: url) { image in + if let image { image.resizable().scaledToFill() } else { Color.clear } + } + .frame(width: size, height: size).clipShape(RoundedRectangle(cornerRadius: size * 0.28)) + } else { + Text(String((agent?.displayTitle ?? "?").prefix(1))).font(.system(size: size * 0.46, weight: .semibold)).foregroundStyle(.white) + } + }.frame(width: size, height: size).accessibilityHidden(true) + } + private var color: Color { + let index = (agent?.name.utf8.reduce(0) { ($0 + Int($1)) % 5 }) ?? 0 + return [FragmentStyle.lilac, .teal, .orange, .indigo, .pink][index] + } +} + +struct SidebarDrawer: View { + @Binding var isPresented: Bool + let library: Library + let page: ShellPage + let api: FragmentAPI + let navigate: (ShellPage) -> Void + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var dragOffset: CGFloat = 0 + private var settle: Animation? { reduceMotion ? nil : .snappy(duration: 0.25) } + + var body: some View { + GeometryReader { geometry in + let width = min(350, max(1, geometry.size.width - 36)) + ZStack(alignment: .leading) { + if isPresented { + Color.black.opacity(0.25 * max(0, 1 + dragOffset / width)) + .ignoresSafeArea() + .onTapGesture { close() } + .accessibilityHidden(true) + .transition(.opacity) + SidebarView(library: library, page: page, api: api, navigate: navigate) + .frame(width: width) + .offset(x: dragOffset) + .transition(.move(edge: .leading)) + .accessibilityAddTraits(.isModal) + .accessibilityAction(.escape) { close() } + .gesture(SidebarPan(changed: { distance in + dragOffset = max(-width, min(0, distance)) + }, ended: { distance, velocity in + let projected = distance + min(0, velocity) * 0.2 + withAnimation(settle) { + if distance < -60 && projected < -width * 0.35 { isPresented = false } + dragOffset = 0 + } + })) + } + } + .animation(settle, value: isPresented) + } + .allowsHitTesting(isPresented) + .onChange(of: isPresented) { _, _ in dragOffset = 0 } + .task(id: isPresented) { if isPresented { await library.load(api) } } + } + private func close() { withAnimation(settle) { isPresented = false; dragOffset = 0 } } +} + +// Directional recognition leaves vertical scrolling to the native scroll view +// and cancels button touches once a horizontal dismissal starts. +private struct SidebarPan: UIGestureRecognizerRepresentable { + let changed: (CGFloat) -> Void + let ended: (CGFloat, CGFloat) -> Void + + func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator { Coordinator() } + func makeUIGestureRecognizer(context: Context) -> UIPanGestureRecognizer { + let pan = UIPanGestureRecognizer() + pan.maximumNumberOfTouches = 1 + pan.delegate = context.coordinator + return pan + } + func handleUIGestureRecognizerAction(_ pan: UIPanGestureRecognizer, context: Context) { + // The window is stable while the drawer itself moves beneath the finger. + let distance = pan.translation(in: pan.view?.window).x + switch pan.state { + case .began, .changed: changed(distance) + case .ended: ended(distance, pan.velocity(in: pan.view?.window).x) + case .cancelled, .failed: ended(0, 0) + default: break + } + } + final class Coordinator: NSObject, UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool { + guard let pan = gesture as? UIPanGestureRecognizer else { return false } + let translation = pan.translation(in: pan.view?.window) + return translation.x < 0 && abs(translation.x) > abs(translation.y) + } + } +} + +struct SidebarView: View { + let library: Library + let page: ShellPage + let api: FragmentAPI + let navigate: (ShellPage) -> Void + @State private var renamingChat: FragmentSummary? + @State private var chatTitle = "" + @State private var managingChat: FragmentSummary? + @State private var actionError: String? + + private var currentChat: FragmentSummary? { + guard case .chat(let name) = page else { return nil } + return library.fragments.first { $0.name == name } + } + + var body: some View { + VStack(spacing: 0) { + HStack { + Text("Finite.Computer").font(.headline) + Spacer() + Button("Search or start a new chat", systemImage: "square.and.pencil") { navigate(.launcher) } + .labelStyle(.iconOnly).buttonStyle(.glass).accessibilityIdentifier("sidebarNewChat") + }.padding(20) + ScrollView { + LazyVStack(alignment: .leading, spacing: 8) { + ForEach(library.agents) { agent in + Menu { + Button("New chat with \(agent.displayTitle)", systemImage: "square.and.pencil") { + library.selectedAgent = agent.name; navigate(.launcher) + } + Button("Agent settings", systemImage: "person.crop.circle") { library.selectedAgent = agent.name; navigate(.settings) } + if let chat = currentChat, let identity = library.identities[agent.name], (chat.agents ?? []).contains(identity) { + Section(chat.displayTitle) { chatActions(chat) } + } + } label: { + HStack(spacing: 12) { + AgentAvatar(agent: agent) + Text(agent.displayTitle).font(.headline) + Spacer() + Image(systemName: "chevron.down").font(.caption).foregroundStyle(.secondary) + }.padding(12).background(.primary.opacity(0.045), in: RoundedRectangle(cornerRadius: 18)) + }.buttonStyle(.plain).accessibilityIdentifier("agentMenu-\(agent.name)") + ForEach(library.chats.filter { ($0.agents ?? []).first == library.identities[agent.name] }) { chat in + chatRow(chat) + } + } + let ungrouped = library.chats.filter { library.agents(in: $0).isEmpty } + if !ungrouped.isEmpty { + Text("Shared chats").font(.caption).foregroundStyle(.secondary).padding(.top) + ForEach(ungrouped) { chatRow($0) } + } + if library.loading && library.fragments.isEmpty { ProgressView().padding() } + if let error = library.error { + Text(error).font(.footnote).foregroundStyle(.secondary) + Button("Try again") { Task { await library.load(api) } } + } + }.padding(.horizontal, 12) + }.refreshable { await library.load(api) } + VStack(spacing: 4) { + footer("History", icon: "clock", page: .history) + footer("Your Apps", icon: "list.bullet.rectangle", page: .apps) + footer("Settings", icon: "gearshape", page: .settings) + }.padding(12) + } + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 24)) + .padding(.vertical, 8).padding(.leading, 8) + .alert("Name this chat", isPresented: Binding(get: { renamingChat != nil }, set: { if !$0 { renamingChat = nil } })) { + TextField("Chat name", text: $chatTitle) + Button("Save") { + guard let chat = renamingChat else { return } + Task { + do { try await api.rename(chat, title: chatTitle); await library.load(api) } + catch { actionError = error.localizedDescription } + } + renamingChat = nil + } + Button("Cancel", role: .cancel) { renamingChat = nil } + } + .alert("Couldn’t update chat", isPresented: Binding(get: { actionError != nil }, set: { if !$0 { actionError = nil } })) { + Button("OK") { actionError = nil } + } message: { Text(actionError ?? "") } + .sheet(item: $managingChat) { chat in AgentMembershipView(chat: chat, library: library, api: api) } + } + @ViewBuilder private func chatActions(_ chat: FragmentSummary) -> some View { + Button("Rename chat…", systemImage: "pencil") { chatTitle = chat.displayTitle; renamingChat = chat } + Button("Agents in this chat", systemImage: "person.2") { managingChat = chat } + Button(chat.archived == true ? "Unarchive chat" : "Archive chat", systemImage: "archivebox") { + Task { await library.archive(chat, api: api) } + } + } + private func chatRow(_ chat: FragmentSummary) -> some View { + Button { navigate(.chat(chat.name)) } label: { + VStack(alignment: .leading, spacing: 5) { + HStack { + Text(chat.displayTitle).font(.subheadline.weight(.medium)).lineLimit(1) + Spacer(minLength: 4) + if let at = chat.previewAt { Text(Date(timeIntervalSince1970: Double(at) / 1000), style: .relative).font(.caption2).foregroundStyle(.secondary).lineLimit(1) } + } + HStack(spacing: 4) { + if library.agents(in: chat).count > 1 { + ForEach(library.agents(in: chat).prefix(3)) { AgentAvatar(agent: $0, size: 16) } + } + Text(chat.preview ?? "No messages yet").font(.caption).foregroundStyle(.secondary).lineLimit(1) + } + }.padding(12).frame(maxWidth: .infinity, alignment: .leading) + .background(page == .chat(chat.name) ? Color.primary.opacity(0.08) : Color.clear, in: RoundedRectangle(cornerRadius: 12)) + }.buttonStyle(.plain).accessibilityIdentifier("sidebarChat-\(chat.name)") + .contextMenu { chatActions(chat) } + } + private func footer(_ title: String, icon: String, page destination: ShellPage) -> some View { + Button { navigate(destination) } label: { Label(title, systemImage: icon).frame(maxWidth: .infinity, alignment: .leading).padding(12) } + .buttonStyle(.plain).accessibilityIdentifier("sidebar-\(title.lowercased())") + } +} + +private enum AppsLibrarySheet: Identifiable { + case settings(FragmentSummary), manage + var id: String { switch self { case .settings(let app): "settings-" + app.name; case .manage: "manage" } } +} + +struct AppsLibraryView: View { + let library: Library + let api: FragmentAPI + @State private var sheet: AppsLibrarySheet? + let open: (FragmentSummary) -> Void + static let addApp = FragmentSummary(name: "add-app", kind: "app", title: "Add an app") + var body: some View { + ZStack { + DesktopWallpaper() + List { + Section { + ForEach(library.shownApps) { app in + appRow(app, symbol: app.kind == "brain" ? "brain" : "app") + } + } + Section { + appRow(Self.addApp, symbol: "plus") + } + } + .listStyle(.insetGrouped) + .scrollContentBackground(.hidden) + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Manage Apps", systemImage: "slider.horizontal.3") { sheet = .manage }.accessibilityIdentifier("manageApps") + } + } + .sheet(item: $sheet) { destination in + NavigationStack { + Group { + switch destination { + case .settings(let app): AppSettingsView(fragment: app, library: library, api: api) + case .manage: AppManagementList(library: library, api: api) + } + } + .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Close", systemImage: "xmark") { sheet = nil } } } + } + .presentationDragIndicator(.visible) + .presentationDetents([.large]) + } + } + private func appRow(_ app: FragmentSummary, symbol: String) -> some View { + HStack(spacing: 8) { + Button { open(app) } label: { + HStack(spacing: 14) { + FragmentIcon(fragment: app, fallback: symbol) + VStack(alignment: .leading, spacing: 4) { + Text(app.displayTitle).font(.body.weight(.medium)) + if let preview = app.preview, !preview.isEmpty { + Text(preview).font(.caption).foregroundStyle(.secondary).lineLimit(2) + } + } + Spacer(minLength: 8) + if app.name == "add-app" { Image(systemName: "chevron.right").font(.caption.weight(.semibold)).foregroundStyle(.tertiary) } + } + .padding(.vertical, 12).frame(maxWidth: .infinity, alignment: .leading).contentShape(Rectangle()) + }.buttonStyle(.plain).accessibilityIdentifier("app-\(app.name)") + if app.name != "add-app" { + Button { sheet = .settings(app) } label: { + Image(systemName: "gearshape").frame(width: 44, height: 44).contentShape(Rectangle()) + }.buttonStyle(.borderless).accessibilityLabel("Settings for \(app.displayTitle)") + .accessibilityIdentifier("appSettings-\(app.name)") + } + }.listRowBackground(Rectangle().fill(.regularMaterial)) + } +} + +struct FragmentIcon: View { + let fragment: FragmentSummary + let fallback: String + var size: CGFloat = 40 + var body: some View { + Group { + if let raw = fragment.picture, let url = URL(string: raw) { + AuthenticatedImage(url: url) { loaded in + if let image = loaded { image.resizable().scaledToFit() } + else { placeholder } + } + } else { placeholder } + }.frame(width: size, height: size).accessibilityHidden(true) + } + private var placeholder: some View { + Image(systemName: fallback).font(.system(size: size * 0.6)).foregroundStyle(.secondary) + } +} + +enum HistoryFilter: String, CaseIterable { case all = "All", archived = "Archived", imported = "Imported" } +struct HistoryView: View { + let library: Library + let api: FragmentAPI + let open: (FragmentSummary) -> Void + @State private var search = "" + @State private var filter = HistoryFilter.all + @State private var messageHits: [String: String] = [:] + @State private var importedHits: [String: String] = [:] + @State private var error: String? + private struct Row: Identifiable { + let id: String + let fragment: FragmentSummary + let at: Int64 + let preview: String + } + private var rows: [Row] { + var rows: [Row] = [] + if filter != .imported { + for chat in library.fragments where chat.kind == "chat" && chat.previewAt != nil { + guard filter != .archived || chat.archived == true else { continue } + guard search.isEmpty || chat.displayTitle.localizedCaseInsensitiveContains(search) || messageHits[chat.name] != nil else { continue } + rows.append(Row(id: "historyChat-\(chat.name)", fragment: chat, at: chat.previewAt ?? 0, preview: messageHits[chat.name] ?? chat.preview ?? "")) + } + } + if filter != .archived, let archive = library.fragments.first(where: { $0.kind == "sessions" && $0.role == "owner" }) { + for entry in library.imported { + guard search.isEmpty || (entry.title ?? "").localizedCaseInsensitiveContains(search) || (entry.preview ?? "").localizedCaseInsensitiveContains(search) || importedHits[entry.key] != nil else { continue } + let fragment = FragmentSummary(name: archive.name, kind: "imported", title: entry.title ?? "Untitled", sessionKey: entry.key) + rows.append(Row(id: "importedSession-\(entry.key)", fragment: fragment, at: entry.at, preview: importedHits[entry.key] ?? entry.preview ?? "")) + } + } + return rows.sorted { $0.at > $1.at } + } + var body: some View { + List { + Picker("Show", selection: $filter) { ForEach(HistoryFilter.allCases, id: \.self) { Text($0.rawValue).tag($0) } }.pickerStyle(.segmented).listRowBackground(Color.clear) + ForEach(rows) { row in + Button { open(row.fragment) } label: { + HStack(spacing: 12) { + if row.fragment.kind == "imported" { Image(systemName: "tray.and.arrow.down").frame(width: 32) } + else { AgentAvatar(agent: library.agents(in: row.fragment).first) } + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(row.fragment.displayTitle).foregroundStyle(.primary) + Spacer() + if row.fragment.archived == true { Image(systemName: "archivebox").font(.caption) } + Text(Date(timeIntervalSince1970: Double(row.at) / 1000), style: .date).font(.caption2).foregroundStyle(.secondary) + } + Text(row.preview).font(.caption).foregroundStyle(.secondary).lineLimit(2) + if row.fragment.kind == "imported" { Text("Imported · Read only").font(.caption2).foregroundStyle(.tertiary) } + } + }.padding(.vertical, 4) + }.accessibilityIdentifier(row.id) + .swipeActions { + if row.fragment.kind == "chat" { + Button(row.fragment.archived == true ? "Unarchive" : "Archive", systemImage: "archivebox") { Task { await library.archive(row.fragment, api: api) } }.tint(.indigo) + } + } + } + if rows.isEmpty { ContentUnavailableView.search(text: search) } + if let error { Text(error).font(.footnote).foregroundStyle(.secondary) } + if let error = library.error { Text(error).font(.footnote).foregroundStyle(.secondary) } + }.searchable(text: $search, prompt: "Search chats").refreshable { await library.load(api) } + .scrollContentBackground(.hidden).background(FragmentStyle.background) + .task(id: search) { + messageHits = [:]; importedHits = [:]; error = nil + guard !search.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } + do { + try await Task.sleep(for: .milliseconds(250)) + let result = try await api.search(search) + try Task.checkCancellation() + for hit in result.messages where messageHits[hit.fragment] == nil { messageHits[hit.fragment] = hit.snippet } + if let archive = library.fragments.first(where: { $0.kind == "sessions" && $0.role == "owner" }) { + let result: JSONValue = try await api.request("api/f/\(archive.name)/search", method: "POST", body: .object(["q": .string(FragmentAPI.searchHint(search)), "limit": .number(50), "mode": .string("lexical"), "fields": .object(["kind": .string("chat")])])) + try Task.checkCancellation() + for hit in result["results"].array { + guard let source = hit["source"].string, source.hasPrefix("sessions/"), source.hasSuffix(".json") else { continue } + let key = String(source.dropFirst(9).dropLast(5)) + if importedHits[key] == nil { importedHits[key] = hit["snippet"].string ?? "" } + } + } + } catch { if !Task.isCancelled { self.error = "Some messages could not be searched: \(error.localizedDescription)" } } + } + } +} + +struct AgentMembershipView: View { + @Environment(\.dismiss) private var dismiss + let chat: FragmentSummary + let library: Library + let api: FragmentAPI + @State private var working = false + @State private var error: String? + var body: some View { + NavigationStack { + List { + Section("Agents in this chat") { + ForEach(library.agents) { agent in + let joined = library.fragments.first(where: { $0.name == chat.name })?.agents?.contains(library.identities[agent.name] ?? "") == true + Button { Task { await add(agent) } } label: { + HStack { AgentAvatar(agent: agent); Text(agent.displayTitle); Spacer(); Image(systemName: joined ? "checkmark" : "plus") } + }.disabled(joined || working).accessibilityIdentifier("joinAgent-\(agent.name)") + } + } + if let session = library.sessions[chat.name] { + Section("Messages") { + Picker("Send to", selection: Binding( + get: { session.recipients.count > 1 ? "all" : session.recipients.first ?? "lead" }, + set: { selection in + let members = library.fragments.first(where: { $0.name == chat.name })?.agents ?? [] + session.recipients = selection == "lead" ? [] : selection == "all" ? members : [selection] + } + )) { + Text("Lead agent").tag("lead") + ForEach(library.agents(in: library.fragments.first(where: { $0.name == chat.name }) ?? chat)) { agent in + if let identity = library.identities[agent.name] { Text(agent.displayTitle).tag(identity) } + } + Text("All agents").tag("all") + }.accessibilityIdentifier("messageRecipient") + } + } + if let error { Text(error).foregroundStyle(.secondary) } + }.navigationTitle("Agents").toolbar { Button("Done") { dismiss() } } + } + } + private func add(_ agent: FragmentSummary) async { + working = true; defer { working = false } + do { try await api.join(agent, to: chat); await library.load(api) } + catch { self.error = error.localizedDescription } + } +} diff --git a/ios/Sources/ThirdPartyNotices.txt b/ios/Sources/ThirdPartyNotices.txt new file mode 100644 index 000000000..8d5d64308 --- /dev/null +++ b/ios/Sources/ThirdPartyNotices.txt @@ -0,0 +1,48 @@ +P256K 0.21.1 +https://github.com/21-DOT-DEV/swift-secp256k1 + +MIT License + +Copyright (c) 2020 GigaBitcoin LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Bitcoin Core libsecp256k1 (bundled by P256K) +https://github.com/bitcoin-core/secp256k1 + +Copyright (c) 2013 Pieter Wuille + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ios/Tests/AppSettingsTests.swift b/ios/Tests/AppSettingsTests.swift new file mode 100644 index 000000000..cbd923e62 --- /dev/null +++ b/ios/Tests/AppSettingsTests.swift @@ -0,0 +1,77 @@ +import Foundation +import ImageIO +import Testing +import UIKit +@testable import Fragment + +/// An app's settings against a stub of the platform's answers: what the +/// sheet reads of status, members and invites, and what it sends. +@MainActor +struct AppSettingsTests { + private func details(_ json: String) throws -> AppDetails { try JSONDecoder().decode(AppDetails.self, from: Data(json.utf8)) } + + @Test func theShareLinkIsTheOwnersOnALinkAppAndTheAddressOnAPublicOne() throws { + let canonical = #""urls":{"canonical":"https://reading--p1.finite.test/","platform":"https://finite.test"}"# + let link = try details(#"{"owner":"npub1me","role":"owner","visibility":"link","viewToken":"v1",\#(canonical)}"#) + #expect(link.shareURL?.absoluteString == "https://reading--p1.finite.test/?view=v1" && link.canEdit) + #expect(try details(#"{"owner":"npub1o","role":"editor","visibility":"link",\#(canonical)}"#).shareURL == nil) + #expect(try details(#"{"owner":"npub1o","role":"viewer","visibility":"public",\#(canonical)}"#).shareURL?.absoluteString == "https://reading--p1.finite.test/") + let members = try details(#"{"owner":"npub1me","role":"owner","visibility":"members","viewToken":"v1",\#(canonical)}"#) + #expect(members.shareURL == nil) + #expect(try details(#"{"owner":"npub1o","role":"viewer","visibility":"link",\#(canonical)}"#).canEdit == false) + } + + @Test func membersAndInvitesAreReadAsListed() async throws { + let stub = Stub { seen, _ in + switch seen.route { + case "GET /api/f/reading--p1/members": (200, #"{"members":[{"principal":"npub1me","role":"owner","addedBy":"npub1me","addedAt":1,"kind":"person"},{"principal":"npub1moss","role":"editor","addedBy":"npub1me","addedAt":2,"kind":"agent","owner":"npub1me"}]}"#) + case "GET /api/f/reading--p1/invites": (200, #"{"invites":[{"email":"friend@example.com","role":"viewer","expiresAt":9,"createdBy":"npub1me"}]}"#) + default: (404, "{}") + } + } + let api = try stub.api() + let members = try await api.appMembers("reading--p1") + #expect(members.map(\.principal) == ["npub1me", "npub1moss"] && members[1].owner == "npub1me") + #expect(try await api.appInvites("reading--p1").map(\.email) == ["friend@example.com"]) + } + + @Test func iconsAreMadeWithAHintAndARefusalSaysWhy() async throws { + let stub = Stub { seen, _ in + switch seen.route { + case "POST /api/f/reading--p1/icon/make": (200, #"{"subject":"a book","candidates":[{"png":"iVBORw0KGgo=","score":0.9}],"flaws":{}}"#) + case "PUT /api/f/reading--p1/icon": (400, #"{"error":"invalid_request","message":"an icon is a PNG, JPEG, WebP or GIF"}"#) + case "GET /api/f/reading--p1/icon": (404, #"{"error":"not_found","message":"no icon"}"#) + default: (404, "{}") + } + } + let api = try stub.api() + let options = try await api.makeAppIcons("reading--p1", hint: " a book ") + #expect(options.candidates.first?.data != nil) + #expect(stub.seen[0].json == .object(["hint": .string("a book")])) + _ = try await api.makeAppIcons("reading--p1", hint: " ") + #expect(stub.seen[1].json == .object([:])) + do { + try await api.putAppIcon(Data("not an image".utf8), name: "reading--p1") + Issue.record("a refused icon is an error") + } catch APIError.refused(let message) { #expect(message == "an icon is a PNG, JPEG, WebP or GIF") } + #expect(try await api.appImage("reading--p1", kind: "icon") == nil) + } + + @Test func aPickedPhotoIsFittedOnThePhoneBeforeItIsSent() throws { + let file = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString + ".png") + defer { try? FileManager.default.removeItem(at: file) } + let large = UIGraphicsImageRenderer(size: CGSize(width: 2400, height: 1200), format: { let f = UIGraphicsImageRendererFormat(); f.scale = 1; return f }()).image { context in + UIColor.systemTeal.setFill(); context.fill(CGRect(x: 0, y: 0, width: 2400, height: 1200)) + } + try #require(large.pngData()).write(to: file) + let fitted = try AppIconImage.fitted(file) + let source = try #require(CGImageSourceCreateWithData(fitted as CFData, nil)) + let properties = try #require(CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]) + #expect(properties[kCGImagePropertyPixelWidth] as? Int == 256 && properties[kCGImagePropertyPixelHeight] as? Int == 128) + #expect(CGImageSourceGetType(source) as String? == "public.png") + let text = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString + ".txt") + defer { try? FileManager.default.removeItem(at: text) } + try Data("not an image".utf8).write(to: text) + #expect(throws: APIError.self) { try AppIconImage.fitted(text) } + } +} diff --git a/ios/Tests/DeviceIdentityTests.swift b/ios/Tests/DeviceIdentityTests.swift new file mode 100644 index 000000000..9c8393697 --- /dev/null +++ b/ios/Tests/DeviceIdentityTests.swift @@ -0,0 +1,75 @@ +import Foundation +import CryptoKit +import P256K +import Testing +@testable import Fragment + +struct DeviceIdentityTests { + private func fixedKey(_ suffix: UInt8 = 3) throws -> DeviceIdentity { try DeviceIdentity(secret: Data(repeating: 0, count: 31) + Data([suffix])) } + private func event(_ header: String) throws -> [String: Any] { + let data = try #require(Data(base64Encoded: String(header.dropFirst(6)))) + return try #require(JSONSerialization.jsonObject(with: data) as? [String: Any]) + } + @Test func signatureBindsExactURLMethodBodyAndClock() throws { + #expect(Bundle.main.url(forResource: "ThirdPartyNotices", withExtension: "txt") != nil) + let key = try fixedKey() + #expect(key.publicKey == "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9") + let url = URL(string: "https://finite.example/api/f/notes/channels/chat?q=one%20two")! + let body = Data("{\"text\":\"hello ☀️\"}".utf8) + let signed = try event(key.authorization(method: "POST", url: url, body: body, at: 1_800_000_000)) + let tags = try #require(signed["tags"] as? [[String]]) + #expect(tags == [["u",url.absoluteString],["method","POST"],["payload",Data(SHA256.hash(data: body)).hex]]) + let preimage = try JSONSerialization.data(withJSONObject: [0,key.publicKey,1_800_000_000,27235,tags,""], options: [.withoutEscapingSlashes]) + #expect(signed["id"] as? String == Data(SHA256.hash(data: preimage)).hex) + let hex = try #require(signed["sig"] as? String) + let bytes = stride(from: 0, to: hex.count, by: 2).map { index in UInt8(hex[hex.index(hex.startIndex, offsetBy: index).. A thought") + #expect(blocks.map(\.kind) == [.heading, .paragraph, .quote]) + #expect(blocks[1].text == "• First\n• **Second**") + } +} diff --git a/ios/Tests/SearchTests.swift b/ios/Tests/SearchTests.swift new file mode 100644 index 000000000..c9950cfe6 --- /dev/null +++ b/ios/Tests/SearchTests.swift @@ -0,0 +1,29 @@ +import Foundation +import Testing +@testable import Fragment + +struct SearchTests { + @Test func longPromptOnlyBoundsSearchHint() { + let prompt = "one two three four five six seven eight nine ten" + #expect(FragmentAPI.searchHint(prompt) == "one two three four five six seven eight") + let unicode = String(repeating: "🙂", count: 100) + let hint = FragmentAPI.searchHint(unicode) + #expect(hint.utf8.count == 256) + #expect(FragmentAPI.searchHint(" \n\t ").isEmpty) + #expect(FragmentAPI.searchHint("one\ntwo") == "one two") + } + + @Test func searchSendsTheHintAndReadsFragmentsAndMessages() async throws { + let stub = Stub { seen, _ in + (200, #"{"fragments":[{"name":"reading--p1","kind":"app","title":"Reading"}],"messages":[{"fragment":"talk--c1","channel":"chat","seq":2,"at":2000,"snippet":"a few quiet minutes"}]}"#) + } + let api = try stub.api() + let found = try await api.search("quiet minutes, then a long tail of words past the eighth") + #expect(stub.seen.map(\.route) == ["GET /api/search"]) + #expect(stub.seen[0].query == ["q": "quiet minutes, then a long tail of words"]) + #expect(found.fragments.map(\.name) == ["reading--p1"]) + #expect(found.messages.map(\.id) == ["talk--c1:chat:2"]) + let empty = try await api.search(" ") + #expect(empty.fragments.isEmpty && empty.messages.isEmpty && stub.seen.count == 1) + } +} diff --git a/ios/Tests/ShellTests.swift b/ios/Tests/ShellTests.swift new file mode 100644 index 000000000..76e051c44 --- /dev/null +++ b/ios/Tests/ShellTests.swift @@ -0,0 +1,140 @@ +import Foundation +import Synchronization +import Testing +@testable import Fragment + +/// The shell's reads and writes against a stub of the platform's answers +/// (docs/api.md): what the app asks, and what it makes of the answers. +@MainActor +struct ShellTests { + /// A person's list: two agents, a chat with both, an app, and their + /// session archive. + nonisolated private static let list = #""" + {"fragments":[ + {"name":"moss--a1","kind":"agent","title":"Moss","role":"owner"}, + {"name":"juniper--a2","kind":"agent","title":"Juniper","role":"owner"}, + {"name":"talk--c1","kind":"chat","title":"A calmer morning","agents":["npub1moss","npub1juniper"],"previewAt":5,"role":"owner"}, + {"name":"reading--p1","kind":"app","title":"Reading","picture":"/api/f/reading--p1/icon?v=7","role":"owner","dock":true}, + {"name":"archive--s1","kind":"sessions","role":"owner"} + ]} + """# + nonisolated private static func platform(_ seen: Seen, _ before: [Seen]) -> (Int, String) { + switch seen.route { + case "GET /api/fragments": (200, list) + case "GET /api/f/moss--a1/status": (200, #"{"npub":"npub1moss","urls":{"canonical":"https://moss--a1.finite.test/"}}"#) + case "GET /api/f/juniper--a2/status": (200, #"{"npub":"npub1juniper","urls":{"canonical":"https://juniper--a2.finite.test/"}}"#) + case "GET /api/f/archive--s1/file": (200, #"{"sessions":[{"key":"imported-chat","title":"Before Finite","ended_at":"2026-10-01T10:00:00Z"}]}"#) + default: (404, #"{"error":"not_found","message":"no route \#(seen.route)"}"#) + } + } + + @Test func theListResolvesAgentsChatsAppsAndTheArchive() async throws { + let stub = Stub(Self.platform) + let api = try stub.api() + let library = Library() + await library.load(api) + #expect(library.error == nil) + #expect(library.agents.map(\.name) == ["moss--a1", "juniper--a2"]) + let chat = try #require(library.chats.first) + #expect(library.agents(in: chat).map(\.name) == ["moss--a1", "juniper--a2"]) + #expect(library.agent(for: "npub1juniper")?.displayTitle == "Juniper") + // a listed picture is the platform's, signed like every other read + #expect(library.apps.first?.picture == "https://\(stub.host)/api/f/reading--p1/icon?v=7") + #expect(library.imported.map(\.key) == ["imported-chat"] && library.imported[0].at > 0) + #expect(stub.seen.first { $0.path == "/api/f/archive--s1/file" }?.query == ["path": "index/chat.json"]) + } + + @Test func anAgentJoinsAChatAsAnEditorByItsNpub() async throws { + let stub = Stub { seen, before in + seen.route == "PUT /api/f/talk--c9/members/npub1moss" ? (200, #"{"principal":"npub1moss","role":"editor"}"#) : Self.platform(seen, before) + } + let api = try stub.api() + try await api.join(FragmentSummary(name: "moss--a1", kind: "agent"), to: FragmentSummary(name: "talk--c9", kind: "chat")) + #expect(stub.seen.map(\.route) == ["GET /api/f/moss--a1/status", "PUT /api/f/talk--c9/members/npub1moss"]) + #expect(stub.seen[1].json == .object(["role": .string("editor")])) + } + + @Test func aNewChatIsMadeFromTheChatTemplate() async throws { + let stub = Stub { seen, _ in seen.route == "POST /api/fragments" ? (200, #"{"name":"mobile-chat--x1","npub":"npub1c"}"#) : (404, "{}") } + let chat = try await stub.api().createChat() + #expect(chat.name == "mobile-chat--x1" && chat.kind == "chat") + #expect(stub.seen[0].json == .object(["label": .string("mobile-chat"), "template": .string("chat"), "title": .string("New chat")])) + } + + @Test func cachedDraftsAreNeverEvictedAtCapacity() throws { + let stub = Stub { _, _ in (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let library = Library() + for index in 0..<32 { + try library.session(for: FragmentSummary(name: "draft-\(index)", kind: "chat"), api: api).composer = "A draft to preserve" + } + #expect(throws: APIError.self) { try library.session(for: FragmentSummary(name: "one-more", kind: "chat"), api: api) } + #expect(library.sessions.count == 32) + #expect(library.sessions.values.allSatisfy { $0.composer == "A draft to preserve" }) + let again = try library.session(for: FragmentSummary(name: "draft-3", kind: "chat"), api: api) + #expect(again === library.sessions["draft-3"]) + } + + @Test func renameKeepsTheManifestAndDeploysIt() async throws { + let stub = Stub { seen, _ in + switch seen.route { + case "GET /api/f/talk--c1/manifest": (200, #"{"name":"talk--c1","meta":{"title":"Old","template":"chat"},"channels":{"chat":{}}}"#) + case "POST /api/f/talk--c1/files": (200, #"{"commit":"abc"}"#) + case "POST /api/f/talk--c1/deploy": (200, #"{"live":"abc","canonical":"https://talk--c1.finite.test/"}"#) + default: (404, "{}") + } + } + let api = try stub.api() + let chat = FragmentSummary(name: "talk--c1", kind: "chat") + await #expect(throws: APIError.self) { try await api.rename(chat, title: " ") } + #expect(stub.seen.isEmpty) + try await api.rename(chat, title: " A name to keep ") + #expect(stub.seen.map(\.route) == ["GET /api/f/talk--c1/manifest", "POST /api/f/talk--c1/files", "POST /api/f/talk--c1/deploy"]) + let file = stub.seen[1].json["files"].array.first + #expect(file?["path"].string == "fragment.json") + let manifest = try JSONDecoder().decode(JSONValue.self, from: Data(try #require(file?["text"].string).utf8)) + #expect(manifest["meta"] == .object(["title": .string("A name to keep"), "template": .string("chat")])) + #expect(manifest["channels"] == .object(["chat": .object([:])])) + } + + @Test func archivingIsThePersonsOwnView() async throws { + let stub = Stub { seen, before in + seen.route == "PUT /api/fragments/talk--c1/archived" ? (200, #"{"ok":true}"#) : Self.platform(seen, before) + } + let api = try stub.api() + let library = Library() + await library.archive(FragmentSummary(name: "talk--c1", kind: "chat"), api: api) + #expect(stub.seen.first?.json == .object(["archived": .bool(true)])) + await library.archive(FragmentSummary(name: "talk--c1", kind: "chat", archived: true), api: api) + #expect(stub.seen.last { $0.method == "PUT" }?.json == .object(["archived": .bool(false)])) + } + + @Test func anAttachmentIsPutByItsHashAndARefusalSaysWhy() async throws { + let stub = Stub { seen, _ in + seen.body.count > 4 ? (413, #"{"error":"too_large","message":"a blob is at most 4 bytes here"}"#) : (200, #"{"ok":true,"stored":true}"#) + } + let api = try stub.api() + let file = try await api.upload(Data("abc".utf8), name: "a.txt", type: "text/plain", to: "talk--c1") + #expect(file.sha256 == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" && file.size == 3) + #expect(stub.seen[0].route == "PUT /api/f/talk--c1/blobs/\(file.sha256)" && stub.seen[0].body == Data("abc".utf8)) + do { + _ = try await api.upload(Data("too big".utf8), name: "b.txt", type: "text/plain", to: "talk--c1") + Issue.record("a refused upload is an error") + } catch APIError.refused(let message) { #expect(message == "a blob is at most 4 bytes here") } + } + + @Test func anEndedAuthorizationAsksForTheConnectionAgain() async throws { + let stub = Stub { _, _ in (401, #"{"error":"unauthenticated","message":"the key was revoked"}"#) } + let api = try stub.api() + let heard = Heard() + let observer = NotificationCenter.default.addObserver(forName: .finiteAuthorizationFailed, object: nil, queue: nil) { note in + if let key = note.userInfo?["key"] as? String { heard.keys.withLock { $0.append(key) } } + } + defer { NotificationCenter.default.removeObserver(observer) } + await #expect(throws: APIError.self) { let _: JSONValue = try await api.request("api/identities/me") } + #expect(heard.keys.withLock { $0.contains(api.identity.publicKey) }) + } +} + +private final class Heard: Sendable { let keys = Mutex<[String]>([]) } diff --git a/ios/Tests/Stub.swift b/ios/Tests/Stub.swift new file mode 100644 index 000000000..1523b8687 --- /dev/null +++ b/ios/Tests/Stub.swift @@ -0,0 +1,149 @@ +import Foundation +import Synchronization +@testable import Fragment + +/// A request the app made, as a stub saw it. +struct Seen: Sendable { + let method: String + let path: String + let query: [String: String] + let body: Data + let authorization: String? + var json: JSONValue { (try? JSONDecoder().decode(JSONValue.self, from: body)) ?? .null } + var route: String { "\(method) \(path)" } +} + +/// A test's stand-in for the platform, on a host of its own: each request +/// is answered by the test's handler (through `StubProtocol`), each socket +/// is a `ScriptedSocket` the test drives, and both are recorded. Answers +/// are written by hand here; the wire they stand for is docs/api.md. +final class Stub: Sendable { + /// The answer to a request, given every request this stub saw before it. + typealias Handler = @Sendable (Seen, [Seen]) -> (Int, String) + private static let live = Mutex<[String: Stub]>([:]) + static func named(_ host: String) -> Stub? { live.withLock { $0[host] } } + + let host = "stub-\(UUID().uuidString.lowercased()).test" + private let handler: Handler + private let state = Mutex<(seen: [Seen], sockets: [ScriptedSocket])>(([], [])) + + init(_ handler: @escaping Handler) { + self.handler = handler + Self.live.withLock { $0[host] = self } + } + deinit { Self.live.withLock { _ = $0.removeValue(forKey: host) } } + + var seen: [Seen] { state.withLock { $0.seen } } + var sockets: [ScriptedSocket] { state.withLock { $0.sockets } } + func answer(_ request: Seen) -> (Int, String) { + let before = state.withLock { s in defer { s.seen.append(request) }; return s.seen } + return handler(request, before) + } + + /// An API on this stub, signed by a fresh key. + func api(accountID: String? = nil) throws -> FragmentAPI { + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [StubProtocol.self] + return FragmentAPI(baseURL: URL(string: "https://\(host)")!, identity: try DeviceIdentity(), accountID: accountID, + session: URLSession(configuration: configuration), sockets: { [weak self] request in + let socket = ScriptedSocket(request: request) + self?.state.withLock { $0.sockets.append(socket) } + return socket + }) + } +} + +final class StubProtocol: URLProtocol { + override class func canInit(with request: URLRequest) -> Bool { request.url?.host.flatMap(Stub.named) != nil } + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } + override func startLoading() { + guard let url = request.url, let stub = url.host.flatMap(Stub.named) else { + client?.urlProtocol(self, didFailWithError: URLError(.cannotFindHost)); return + } + var query: [String: String] = [:] + for item in URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? [] { query[item.name] = item.value ?? "" } + let seen = Seen(method: request.httpMethod ?? "GET", path: url.path, query: query, body: request.httpBody ?? Self.read(request.httpBodyStream), + authorization: request.value(forHTTPHeaderField: "Authorization")) + let (status, body) = stub.answer(seen) + let response = HTTPURLResponse(url: url, statusCode: status, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: Data(body.utf8)) + client?.urlProtocolDidFinishLoading(self) + } + override func stopLoading() {} + private static func read(_ stream: InputStream?) -> Data { + guard let stream else { return Data() } + stream.open(); defer { stream.close() } + var data = Data() + var buffer = [UInt8](repeating: 0, count: 16_384) + while stream.hasBytesAvailable { + let n = stream.read(&buffer, maxLength: buffer.count) + guard n > 0 else { break } + data.append(buffer, count: n) + } + return data + } +} + +/// A `__live` socket the test plays the server's side of: frames it pushes +/// arrive in order, `drop` ends it as a lost connection, and every frame +/// the app sent is kept. +final class ScriptedSocket: LiveSocket { + let request: URLRequest + private let state = Mutex<(queue: [Result], waiting: CheckedContinuation?, sent: [JSONValue], cancelled: Bool)>(([], nil, [], false)) + init(request: URLRequest) { self.request = request } + + var response: URLResponse? { nil } + var closeCode: URLSessionWebSocketTask.CloseCode { .invalid } + var sent: [JSONValue] { state.withLock { $0.sent } } + var cancelled: Bool { state.withLock { $0.cancelled } } + func resume() {} + func send(_ message: URLSessionWebSocketTask.Message) async throws { + guard case .string(let text) = message else { throw URLError(.cannotDecodeContentData) } + let frame = try JSONDecoder().decode(JSONValue.self, from: Data(text.utf8)) + state.withLock { $0.sent.append(frame) } + } + func receive() async throws -> URLSessionWebSocketTask.Message { + try await withCheckedThrowingContinuation { continuation in + let next: Result? = state.withLock { s in + if !s.queue.isEmpty { return s.queue.removeFirst() } + if s.cancelled { return .failure(URLError(.cancelled)) } + s.waiting = continuation + return nil + } + if let next { continuation.resume(with: next) } + } + } + func cancel(with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { + let waiting = state.withLock { s in s.cancelled = true; defer { s.waiting = nil }; return s.waiting } + waiting?.resume(throwing: URLError(.cancelled)) + } + func push(_ frame: String) { deliver(.success(.string(frame))) } + func drop() { deliver(.failure(URLError(.networkConnectionLost))) } + private func deliver(_ result: Result) { + let waiting = state.withLock { s -> CheckedContinuation? in + guard let waiting = s.waiting else { s.queue.append(result); return nil } + s.waiting = nil + return waiting + } + waiting?.resume(with: result) + } +} + +/// Polls `condition` on the main actor until it holds, or records a failure. +@MainActor +func waitUntil(_ what: String, timeout: Duration = .seconds(10), _ condition: @MainActor () -> Bool) async throws { + let clock = ContinuousClock() + let deadline = clock.now + timeout + while !condition() { + guard clock.now < deadline else { throw StubTimeout(what: what) } + try await Task.sleep(for: .milliseconds(20)) + } +} + +struct StubTimeout: Error, CustomStringConvertible { let what: String; var description: String { "timed out waiting for \(what)" } } + +/// A record frame or an answer's record, as the server writes it. +func record(_ channel: String, _ seq: Int64, principal: String = "npub1agent", body: String) -> String { + #"{"channel":"\#(channel)","seq":\#(seq),"at":\#(seq * 1000),"principal":"\#(principal)","kind":"message","body":\#(body)}"# +} diff --git a/ios/Tests/TimelineTests.swift b/ios/Tests/TimelineTests.swift new file mode 100644 index 000000000..454c9c605 --- /dev/null +++ b/ios/Tests/TimelineTests.swift @@ -0,0 +1,67 @@ +import Foundation +import Testing +@testable import Fragment + +struct TimelineTests { + private func record(_ seq: Int64, channel: String = "chat", principal: String = "agent", body: JSONValue) -> ChannelRecord { + ChannelRecord(channel: channel, seq: seq, at: 100, principal: principal, kind: "message", body: body) + } + private func frame(_ json: String) throws -> LiveFrame { try JSONDecoder().decode(LiveFrame.self, from: Data(json.utf8)) } + + @Test func replayAndOlderPagesNeverRegressLiveCursor() { + var timeline = Timeline() + let latest = record(20, body: .string("latest")) + timeline.insert(latest) + timeline.insert(latest) + timeline.insert(record(2, body: .string("earlier")), advancingCursor: false) + timeline.insert(record(10, body: .string("middle")), advancingCursor: false) + #expect(timeline.records.map(\.seq) == [2, 10, 20]) + #expect(timeline.cursors["chat"] == 20) + } + + @Test func draftIsWholeTextAndReplacedOnlyByItsAuthorsCommit() throws { + var timeline = Timeline() + try timeline.apply(frame(#"{"type":"draft","channel":"chat","turn":"a","principal":"agent","text":"One","at":100}"#)) + try timeline.apply(frame(#"{"type":"draft","channel":"chat","turn":"a","principal":"agent","text":"One two","at":101}"#)) + #expect(timeline.drafts["a"]?.text == "One two") + timeline.insert(record(1, principal: "someone-else", body: .object(["turn": .string("a"), "text": .string("forged")]))) + #expect(timeline.drafts["a"] != nil) + timeline.insert(record(2, body: .object(["turn": .string("a"), "text": .string("One two")]))) + #expect(timeline.drafts.isEmpty) + #expect(timeline.records.count == 2) + } + + @Test func replayedRecordDoesNotEraseNextPartOfTurn() throws { + var timeline = Timeline() + let commit = record(1, body: .object(["turn": .string("a"), "text": .string("First part")])) + timeline.insert(commit) + try timeline.apply(frame(#"{"type":"draft","channel":"chat","turn":"a","principal":"agent","text":"Next part","at":110}"#)) + timeline.insert(commit) + #expect(timeline.drafts["a"]?.text == "Next part") + timeline.disconnected() + #expect(timeline.drafts.isEmpty) + #expect(timeline.records.count == 1) + } + + @Test func channelsHaveIndependentCursorsAndUnknownKindsDecode() throws { + var timeline = Timeline() + try timeline.apply(frame(#"{"type":"record","channel":"chat","seq":8,"at":1,"principal":"person","kind":"message","body":"plain text"}"#)) + try timeline.apply(frame(#"{"type":"record","channel":"work","seq":2,"at":2,"principal":"agent","kind":"message","body":{"kind":"future.kind","newField":true}}"#)) + #expect(timeline.cursors == ["chat": 8, "work": 2]) + #expect(timeline.records.first?.text == "plain text") + #expect(timeline.records.last?.isMessage == false) + #expect(throws: (any Error).self) { + try timeline.apply(frame(#"{"type":"record","channel":"chat","seq":-1}"#)) + } + } + + @Test func turnEndClearsWorkingAndDraft() throws { + var timeline = Timeline() + timeline.insert(record(1, channel: "work", body: .object(["kind": .string("turn.start"), "turn": .string("a")]))) + #expect(timeline.activeTurns == ["a"]) + try timeline.apply(frame(#"{"type":"draft","channel":"chat","turn":"a","principal":"agent","text":"unfinished","at":100}"#)) + timeline.insert(record(2, channel: "work", body: .object(["kind": .string("turn.end"), "turn": .string("a")]))) + #expect(timeline.activeTurns.isEmpty) + #expect(timeline.drafts.isEmpty) + } +} diff --git a/ios/Tests/TransportTests.swift b/ios/Tests/TransportTests.swift new file mode 100644 index 000000000..1b865b9ae --- /dev/null +++ b/ios/Tests/TransportTests.swift @@ -0,0 +1,159 @@ +import Foundation +import Testing +@testable import Fragment + +/// A chat's HTTP and live socket against a stub of the platform's answers +/// (docs/api.md: channels, `__live`; docs/chat-records.md): the faults a +/// phone meets, a lost acknowledgement, a refusal and a dropped stream. +@MainActor +struct TransportTests { + private let chat = FragmentSummary(name: "chat--abc123", kind: "chat") + private var posts: String { "POST /api/f/\(chat.name)/channels/chat" } + private let hello = #"{"type":"hello","id":"s1","principal":"npub1me","role":"editor","platform":"https://finite.test","presence":[]}"# + + /// A record as a `__live` frame. + private func frame(_ channel: String, _ seq: Int64, body: String) -> String { + #"{"type":"record",\#(record(channel, seq, body: body).dropFirst())"# + } + + @Test func aLostAcknowledgementIsRetriedWithTheSameIDAndBody() async throws { + let route = posts + let stub = Stub { seen, before in + guard seen.route == route else { return (404, #"{"error":"not_found","message":"no route"}"#) } + // the first try's append committed, and its answer was lost + if !before.contains(where: { $0.route == route }) { return (503, #"{"error":"unavailable","message":"Try again shortly."}"#) } + return posted(seen, seq: 7, replayed: true) + } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + session.composer = "Hello there" + await session.send() + let failed = try #require(session.pending.first) + #expect(failed.failed && session.composer.isEmpty && session.error == "Try again shortly.") + await session.retry(failed) + #expect(session.pending.isEmpty) + #expect(session.timeline.records.map(\.seq) == [7]) + let sent = stub.seen.filter { $0.route == route } + #expect(sent.count == 2) + #expect(sent[0].json == sent[1].json && sent[0].json["id"].string == failed.id) + #expect(sent[0].json["body"]["client_id"].string == failed.id && sent[0].json["body"]["text"].string == "Hello there") + #expect(stub.seen.allSatisfy { $0.authorization?.hasPrefix("Nostr ") == true }) + } + + @Test func aRefusedPostLeavesTheOutboxAndItsWordsReturnToTheComposer() async throws { + let route = posts + let stub = Stub { seen, _ in + seen.route == route ? (409, #"{"error":"conflicting_body","message":"this post id was already used with a different body"}"#) : (404, "{}") + } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + session.composer = "Words to keep" + await session.send() + #expect(session.pending.isEmpty) + #expect(session.composer == "Words to keep") + #expect(session.error == "this post id was already used with a different body") + #expect(stub.seen.filter { $0.route == route }.count == 1) + } + + @Test func aRateLimitedPostStaysInTheOutboxToRetry() async throws { + let route = posts + let stub = Stub { seen, _ in seen.route == route ? (429, #"{"error":"rate_limited","message":"Slow down."}"#) : (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + session.composer = "Again later" + await session.send() + #expect(session.pending.first?.failed == true && session.composer.isEmpty) + } + + @Test func aDroppedStreamCatchesUpFromItsCursorWithoutDuplicates() async throws { + let stub = Stub { _, _ in (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + let run = Task { await session.run() } + defer { run.cancel() } + try await waitUntil("the first socket") { stub.sockets.count == 1 } + let first = stub.sockets[0] + #expect(first.request.url?.absoluteString == "wss://\(stub.host)/f/\(chat.name)/__live") + #expect(first.request.value(forHTTPHeaderField: "Authorization")?.hasPrefix("Nostr ") == true) + first.push(hello) + try await waitUntil("its subscriptions") { first.sent.count == 2 } + #expect(first.sent.map { $0["channel"].string } == ["chat", "work"]) + #expect(first.sent.allSatisfy { $0["type"].string == "subscribe" && $0["last"] == .number(50) && $0["after"] == .null }) + for seq: Int64 in [1, 2] { first.push(frame("chat", seq, body: #"{"text":"m\#(seq)"}"#)) } + first.push(#"{"type":"subscribed","channel":"chat","next":2,"more":false}"#) + first.push(#"{"type":"subscribed","channel":"work","next":0,"more":false}"#) + first.push(#"{"type":"draft","channel":"chat","principal":"npub1agent","turn":"t1","text":"Hal","at":2500}"#) + try await waitUntil("caught up, a reply being written") { session.caughtUp && session.timeline.drafts["t1"]?.text == "Hal" } + + first.drop() + try await waitUntil("the socket dropped") { !session.connected } + #expect(session.timeline.drafts.isEmpty) + try await waitUntil("the second socket") { stub.sockets.count == 2 } + let second = stub.sockets[1] + second.push(hello) + try await waitUntil("its subscriptions") { second.sent.count == 2 } + #expect(second.sent[0]["channel"].string == "chat" && second.sent[0]["after"] == .number(2) && second.sent[0]["last"] == .null) + // the page from the cursor may repeat what was seen: it is kept once + second.push(frame("chat", 2, body: #"{"text":"m2"}"#)) + second.push(frame("chat", 3, body: #"{"text":"Hallo","turn":"t1"}"#)) + second.push(#"{"type":"subscribed","channel":"chat","next":3,"more":false}"#) + try await waitUntil("caught up again") { session.caughtUp && session.timeline.records.count == 3 } + #expect(session.timeline.records.map(\.seq) == [1, 2, 3]) + #expect(session.timeline.cursors["chat"] == 3) + #expect(session.timeline.drafts.isEmpty) + } + + @Test func stopPostsAStopForEachActiveTurn() async throws { + let route = posts + let stub = Stub { seen, _ in seen.route == route ? posted(seen, seq: 9, replayed: false) : (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + let run = Task { await session.run() } + defer { run.cancel() } + try await waitUntil("a socket") { stub.sockets.count == 1 } + let socket = stub.sockets[0] + socket.push(hello) + socket.push(frame("work", 4, body: #"{"kind":"turn.start","turn":"t4"}"#)) + try await waitUntil("a turn running") { session.timeline.activeTurns == ["t4"] } + await session.stop() + let stop = try #require(stub.seen.first { $0.route == route }) + #expect(stop.json == .object(["id": .string("stop:t4"), "body": .object(["kind": .string("stop"), "turn": .string("t4")])])) + } + + @Test func earlierMessagesAreAPageBeforeTheOldestAndNeverMoveTheCursor() async throws { + let stub = Stub { seen, _ in + guard seen.route == "GET /api/f/chat--abc123/channels/chat" else { return (404, "{}") } + let before = Int64(seen.query["before"] ?? "") ?? 0 + let page = (1.. (Int, String) { + let body = String(decoding: (try? JSONEncoder().encode(seen.json["body"])) ?? Data("null".utf8), as: UTF8.self) + return (200, #"{"record":\#(record("chat", seq, principal: "npub1me", body: body)),"replayed":\#(replayed)}"#) +} diff --git a/ios/UITests/FragmentUITests.swift b/ios/UITests/FragmentUITests.swift new file mode 100644 index 000000000..3a2c47622 --- /dev/null +++ b/ios/UITests/FragmentUITests.swift @@ -0,0 +1,30 @@ +import XCTest + +/// What the app shows with no server behind it. Flows against a real +/// platform (`cargo xtask dev`, paired through `/cli`) are their own suite. +@MainActor +final class FragmentUITests: XCTestCase { + private func screenshot(_ app: XCUIApplication, _ name: String) { + let shot = XCTAttachment(screenshot: app.screenshot()); shot.name = name; shot.lifetime = .keepAlways; add(shot) + } + + /// A first launch asks for the person's Finite server, and a server that + /// does not answer says so, leaving the screen to try again. + func testConnectAsksForTheServerAndSaysWhenItIsUnreachable() throws { + let app = XCUIApplication() + // a closed port on this machine: nothing answers it + app.launchArguments = ["--pair-server", "http://127.0.0.1:9"] + app.launch() + let server = app.textFields["accountServer"] + guard server.waitForExistence(timeout: 10) else { + throw XCTSkip("This simulator holds a connected account; reset its keychain (`xcrun simctl keychain booted reset`) and run again.") + } + XCTAssertEqual(server.value as? String, "http://127.0.0.1:9") + XCTAssertFalse(app.buttons["tryPreview"].exists, "there is no unsigned preview") + screenshot(app, "Connect") + app.buttons["pairAccount"].tap() + XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "could not connect")).firstMatch.waitForExistence(timeout: 20)) + XCTAssertFalse(app.staticTexts["pairingCode"].exists) + XCTAssertTrue(app.buttons["pairAccount"].isEnabled) + } +} diff --git a/ios/project.yml b/ios/project.yml new file mode 100644 index 000000000..c93dadeba --- /dev/null +++ b/ios/project.yml @@ -0,0 +1,103 @@ +name: Fragment +options: + # XcodeGen 2.42 needs the Icon Composer package kept as one resource. + fileTypes: + icon: + file: true + bundleIdPrefix: build.finite + deploymentTarget: + iOS: "26.0" +packages: + secp256k1: + url: https://github.com/21-DOT-DEV/swift-secp256k1.git + exactVersion: 0.21.1 +settings: + base: + SWIFT_VERSION: "6.0" + SWIFT_STRICT_CONCURRENCY: complete + SWIFT_TREAT_WARNINGS_AS_ERRORS: YES + TARGETED_DEVICE_FAMILY: "1,2" + CODE_SIGN_STYLE: Automatic + PRODUCT_NAME: "$(TARGET_NAME)" + SDKROOT: iphoneos + SUPPORTED_PLATFORMS: "iphoneos iphonesimulator" + IPHONEOS_DEPLOYMENT_TARGET: "26.0" + ALWAYS_SEARCH_USER_PATHS: NO + ONLY_ACTIVE_ARCH: YES + CLANG_ENABLE_MODULES: YES + ENABLE_TESTABILITY: YES + GENERATE_INFOPLIST_FILE: YES + MARKETING_VERSION: "0.3" + CURRENT_PROJECT_VERSION: "1" + configs: + Debug: + SWIFT_ACTIVE_COMPILATION_CONDITIONS: "$(inherited) DEBUG" + SWIFT_OPTIMIZATION_LEVEL: "-Onone" + Release: + SWIFT_OPTIMIZATION_LEVEL: "-O" +targets: + Fragment: + type: application + platform: iOS + sources: [Sources] + dependencies: + - package: secp256k1 + product: P256K + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: build.finite.fragment + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + INFOPLIST_KEY_CFBundleDisplayName: Finite + INFOPLIST_KEY_UILaunchScreen_Generation: YES + INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES + INFOPLIST_KEY_UISupportedInterfaceOrientations: "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight" + configs: + Debug: + INFOPLIST_KEY_NSLocalNetworkUsageDescription: "Connect to your local Finite development server." + info: + path: Info.plist + properties: + CFBundleShortVersionString: "$(MARKETING_VERSION)" + CFBundleVersion: "$(CURRENT_PROJECT_VERSION)" + NSLocalNetworkUsageDescription: "Connect to your local Finite development server." + NSMicrophoneUsageDescription: "Record voice memos to send to your agents." + CFBundleDisplayName: Finite + UILaunchScreen: {} + UIApplicationSceneManifest: + UIApplicationSupportsMultipleScenes: true + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + NSAppTransportSecurity: + NSAllowsLocalNetworking: true + FragmentTests: + type: bundle.unit-test + platform: iOS + sources: [Tests] + dependencies: + - target: Fragment + settings: + base: + GENERATE_INFOPLIST_FILE: YES + BUNDLE_LOADER: "$(TEST_HOST)" + TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Fragment.app/Fragment" + FragmentUITests: + type: bundle.ui-testing + platform: iOS + sources: [UITests] + dependencies: + - target: Fragment + settings: + base: + GENERATE_INFOPLIST_FILE: YES + TEST_TARGET_NAME: Fragment +schemes: + Fragment: + build: + targets: + Fragment: all + run: + config: Debug + test: + targets: [FragmentTests, FragmentUITests] diff --git a/xtask/src/eval_sinks.rs b/xtask/src/eval_sinks.rs index 3625de7b0..023691e2e 100644 --- a/xtask/src/eval_sinks.rs +++ b/xtask/src/eval_sinks.rs @@ -66,7 +66,8 @@ use std::path::Path; use anyhow::{ensure, Context, Result}; /// Where runtime code is: what ships or runs for people. -pub const RUNTIME: [(&str, &str); 8] = [ +pub const RUNTIME: [(&str, &str); 9] = [ + ("ios/", "the native iOS client (its Tests and UITests are tests)"), ("cell/", "the Worker: its Rust, entry.mjs and the platform's JavaScript, and the shell"), ("templates/", "the templates' apps, sites and skills, which fragments and computers run"), ("images/", "the computer images: the bridge, Hermes' boot, plugins and hooks, the screen pages"), @@ -92,7 +93,9 @@ pub const NOT_RUNTIME: [(&str, &str); 9] = [ /// What runs a string as script, each with what it does: the name a failure /// reports, and an `ALLOWED` entry names. -pub const REFUSED: [(&str, &str); 17] = [ +pub const REFUSED: [(&str, &str); 19] = [ + ("evaluateJavaScript", "WKWebView: runs a JavaScript string in a page"), + ("callAsyncJavaScript", "WKWebView: runs a JavaScript function body from a string"), ("Runtime.evaluate", "DevTools: runs a string as script in a page"), ("callFunctionOn", "DevTools' Runtime.callFunctionOn: runs a function's source in a page"), ("compileScript", "DevTools' Runtime.compileScript: compiles a string as script, for Runtime.runScript"), @@ -131,6 +134,8 @@ const CARDS: &str = "cell/src/card.rs"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Lang { Rust, + /// Swift comments and quoted literals share the C-style lexer; native eval calls are checked directly. + Swift, JavaScript, /// a page: its inline scripts, as JavaScript Html, @@ -143,6 +148,7 @@ pub fn lang(path: &str) -> Option { let name = path.rsplit('/').next().unwrap_or(path); match name.rsplit_once('.').map(|(_, ext)| ext) { Some("rs") => Some(Lang::Rust), + Some("swift") => Some(Lang::Swift), Some("js" | "mjs" | "cjs") => Some(Lang::JavaScript), Some("html") => Some(Lang::Html), Some("py" | "sh") => Some(Lang::Hash), @@ -201,7 +207,7 @@ pub fn select(paths: impl Iterator) -> Result let mut found = vec![]; for path in paths { let runtime = RUNTIME.iter().any(|(dir, _)| path.starts_with(dir)); - let test = path.split('/').any(|c| c == "tests"); + let test = path.split('/').any(|c| matches!(c, "tests" | "Tests" | "UITests")); let Some(lang) = lang(&path).filter(|_| runtime && !test && !crate::js_syntax::vendored(&path)) else { continue }; found.push((path, lang)); ensure!(found.len() <= FILES_MAX, "more than {FILES_MAX} runtime files to read: is node_modules or target no longer ignored?"); @@ -237,6 +243,14 @@ pub fn hits(file: &File) -> Vec { devtools(b, &parts, &mut at); drivers(b, &parts, &mut at); } + Lang::Swift => { + let parts = lex_rust(b); + devtools(b, &parts, &mut at); + drivers(b, &parts, &mut at); + for word in ["evaluateJavaScript", "callAsyncJavaScript"] { + at.extend(words(b, &parts, word, code).filter(|&i| call_args(b, &parts, i + word.len()).is_some()).map(|i| (i, word))); + } + } Lang::Rust => { let parts = lex_rust(b); devtools(b, &parts, &mut at); @@ -793,13 +807,20 @@ mod tests { hits(&file(path, text)).into_iter().map(|h| (h.line, h.name)).collect() } + #[test] + fn swift_comments_and_data_do_not_evaluate_a_page() { + assert!(found("ios/Sources/Web.swift", "// web.evaluateJavaScript(source)\nlet label = \"evaluateJavaScript\"\nweb.load(request)\n").is_empty()); + } + /// Goal: a planted use in each category fails the check, named by its /// file, line and name, in each language it can be written in. Method: /// each beside clean code, alone and in a check of both. #[test] fn a_planted_eval_fails_the_check_naming_its_line() { let clean = file("cell/clean.mjs", "export const ok = 1;\n"); - let planted: [(&str, &str, usize, &str); 38] = [ + let planted: [(&str, &str, usize, &str); 40] = [ + ("ios/Sources/Web.swift", "// Native page scripts are not evaluated.\nweb.evaluateJavaScript(source)\n", 2, "evaluateJavaScript"), + ("ios/Sources/Async.swift", "try await web.callAsyncJavaScript(source, arguments: [:], in: nil, contentWorld: .page)\n", 1, "callAsyncJavaScript"), // DevTools, as a method on the wire and as a client's call ("cell/src/card.rs", "async fn f() {\n cdp.call(\"Runtime.evaluate\", json!({ \"expression\": js }), deadline).await?;\n}\n", 2, "Runtime.evaluate"), ("crates/core/src/a.rs", "const M: &str = \"Runtime.callFunctionOn\";\n", 1, "callFunctionOn"), @@ -920,6 +941,9 @@ mod tests { "templates/todo/site/index.html", "images/hermes/Dockerfile", "images/hermes/preload.py", + "ios/Sources/FragmentWebSheet.swift", + "ios/Tests/DeviceIdentityTests.swift", + "ios/UITests/FragmentUITests.swift", "images/bridge/tests/docker.rs", "cli/tests/mcp.rs", "crates/core/src/lib.rs", @@ -938,6 +962,7 @@ mod tests { ("templates/todo/site/index.html", Lang::Html), ("images/hermes/Dockerfile", Lang::Hash), ("images/hermes/preload.py", Lang::Hash), + ("ios/Sources/FragmentWebSheet.swift", Lang::Swift), ("crates/core/src/lib.rs", Lang::Rust), ] ); From 8f9171fb24d8470e385ba26a12fd57f5eaf1348e Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 4 Nov 2026 14:04:49 -0600 Subject: [PATCH 2/6] Drafts keep to what loads, and the eval guard reads Swift interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of #1028: - Editing, saving and loading a chat's draft hold one set of bounds (SavedChat): a paste past 32 KiB is cut at a character and said so; a save writes only what a load takes back. A saved file past its bounds loads what fits and says what was cut; one that is no saved chat is cleared and says so. No saved file stops a chat from opening or sending. - A refused send is never retried. Its words return to an empty composer; beside a draft they wait to be edited (never joined past a message's size), across restarts. - The eval guard lexes Swift itself: strings, multiline and raw ones (`#"…"#`), with each interpolation's code (`\(…)`, `\#(…)`) read as code, its own strings as text. - Generating icons no longer checks the hint's length: the server's refusal says it. - Connect shows Debug's --pair-server first, from its first frame, and the UI test never taps Connect for any other address. Co-Authored-By: Claude Opus 5.5 --- docs/MODEL.md | 2 +- docs/ios.md | 9 ++- ios/Fragment.xcodeproj/project.pbxproj | 4 + ios/README.md | 4 +- ios/Sources/AccountConnection.swift | 14 +++- ios/Sources/AppAppearanceView.swift | 2 +- ios/Sources/ChatDraftStore.swift | 69 +++++++++++++--- ios/Sources/ChatSession.swift | 47 ++++++++--- ios/Sources/ChatView.swift | 8 +- ios/Sources/FragmentApp.swift | 2 +- ios/Sources/Models.swift | 2 + ios/Tests/DeviceIdentityTests.swift | 4 +- ios/Tests/DraftTests.swift | 87 ++++++++++++++++++++ ios/UITests/FragmentUITests.swift | 5 +- xtask/src/eval_sinks.rs | 108 ++++++++++++++++++++++++- 15 files changed, 328 insertions(+), 39 deletions(-) create mode 100644 ios/Tests/DraftTests.swift diff --git a/docs/MODEL.md b/docs/MODEL.md index 9f5e185b9..e7f1595ec 100644 --- a/docs/MODEL.md +++ b/docs/MODEL.md @@ -106,7 +106,7 @@ Every change is checked against this table. | A person's session archive | its conversations' files, `sessions/.json`, in a fragment of theirs of kind `sessions` (templates/sessions/README.md) | its indexes (`index/.json`), a projection rewritten by each import; its search, a projection of `sessions/` each import writes (a source a file, its revision the file's sha256) and reconciles against the indexes; the store an import read is never written | | A fragment's preview card | its own storage (`fragment_core::card::Cards`) | status's `page`; errors also an `events` row | | What members are doing: each one's newest listed record | the channel's records | the fragment's `listed`, and each member's list row (docs/api.md, Listed records) | -| Native iPhone drafts and pending sends | protected, backup-excluded Application Support files, scoped to platform and person (`ios/Sources/ChatDraftStore.swift`) | bounded to 32 saved chats, 16 pending sends each; a send is saved before HTTP, retried only with the same id and body, and dropped back to the composer once refused; device keys live only in Keychain | +| Native iPhone drafts and pending sends | protected, backup-excluded Application Support files, scoped to platform and person (`ios/Sources/ChatDraftStore.swift`) | bounded to 32 saved chats, 16 pending sends each; a send is saved before HTTP, retried only with the same id and body, never once refused; editing, saving and loading hold one set of bounds (`SavedChat`), and no saved file stops a chat; device keys live only in Keychain | | A person's own view of their fragments (archived, docked, read up to) | their list, the `Principal` cell | none | | A person's export | their list, the `Principal` cell, and its archive in R2 | the archive is a copy as read, never updated, deleted after seven days (docs/api.md, Export) | diff --git a/docs/ios.md b/docs/ios.md index 4021e5d7a..3fbfced16 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -134,8 +134,13 @@ The complete post and its original id are saved before HTTP. After termination, ambiguous sends appear for explicit retry with that same id/body; they are never silently resent with a new id. The channel takes the same id and body again as the same post (`docs/chat-records.md`). A refusal (4xx, but 408 and 429) is final: -the post leaves the outbox at once and its words return to the composer, with the -server's message. Draft edits are debounced and flushed on leaving a chat or +it is never retried; its words return to an empty composer at once, or, beside a +draft, wait in the chat to be edited once the composer is empty, with the server's +message (a draft and a post joined might be more than a message holds). Editing, +saving and loading keep one set of bounds (`SavedChat`): a paste past 32 KiB is +cut at a character, and said so. A saved file past its bounds loads what fits and +says what was cut; one that is no saved chat is cleared and says so. A chat +always opens and sends. Draft edits are debounced and flushed on leaving a chat or backgrounding. Empty drafts are removed. The app keeps no copy of a limit the server enforces: it shows the server's diff --git a/ios/Fragment.xcodeproj/project.pbxproj b/ios/Fragment.xcodeproj/project.pbxproj index 42fe30b06..c1593182f 100644 --- a/ios/Fragment.xcodeproj/project.pbxproj +++ b/ios/Fragment.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ CC7761F962CB619DD2A5BB51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */; }; E4358E209B49F024F52BDCAE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 811C73B367475E8B5D4DB77F /* AppIcon.icon */; }; ED598966FDB4DD6D3F4486A1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */; }; + F8EBB41899022F0D4E70C646 /* DraftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B11860501F69BE73ECB3D0 /* DraftTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -91,6 +92,7 @@ D261EC9048255D1B31FED623 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; D48A78B0A3D4F8A90EEBD8DB /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; D8A9F828339CA70751D48F66 /* ChatMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMedia.swift; sourceTree = ""; }; + D8B11860501F69BE73ECB3D0 /* DraftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTests.swift; sourceTree = ""; }; DC2E702AC579841626A1A67C /* Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stub.swift; sourceTree = ""; }; DE1E859F65B5B4D304FF0D04 /* TransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportTests.swift; sourceTree = ""; }; EE5376FE0C2DCB1863EE49C7 /* FragmentApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentApp.swift; sourceTree = ""; }; @@ -172,6 +174,7 @@ children = ( 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */, 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */, + D8B11860501F69BE73ECB3D0 /* DraftTests.swift */, AB0814CDF8824F8A8657129D /* MarkdownTests.swift */, 3A2F75C83DE72E8D3B8797E1 /* SearchTests.swift */, 8C581195A0FDAC2EA88D081F /* ShellTests.swift */, @@ -300,6 +303,7 @@ files = ( 5938CA76ED5569461BA445AB /* AppSettingsTests.swift in Sources */, A8A038F320ECBCC025021D8B /* DeviceIdentityTests.swift in Sources */, + F8EBB41899022F0D4E70C646 /* DraftTests.swift in Sources */, A0F935E5B3AB7E00AF2297A0 /* MarkdownTests.swift in Sources */, 7A8D5455AD2EABF343F2905B /* SearchTests.swift in Sources */, 4FEE2A2E44CD058FD4666F85 /* ShellTests.swift in Sources */, diff --git a/ios/README.md b/ios/README.md index be3e36dd5..9e59f546c 100644 --- a/ios/README.md +++ b/ios/README.md @@ -126,7 +126,9 @@ post leaves the outbox and its words return to the composer), a rate limit Stop, earlier pages that never move the live cursor, an ended authorization, and the requests behind lists, joins, renames, archiving, attachments, search, sharing and icons. `TimelineTests` cover ordering, cursors, drafts and turns; -`DeviceIdentityTests` NIP-98 binding, origins, Keychain and the local outbox; +`DeviceIdentityTests` NIP-98 binding, origins and Keychain; `DraftTests` the +saved draft and outbox across restarts (a paste past a message, a refused send +beside a draft, a saved file past its bounds or unreadable); `MarkdownTests` a code fence arriving in parts. The stubs' answers are written by hand from docs/api.md. diff --git a/ios/Sources/AccountConnection.swift b/ios/Sources/AccountConnection.swift index 83494addd..ce30515c3 100644 --- a/ios/Sources/AccountConnection.swift +++ b/ios/Sources/AccountConnection.swift @@ -112,7 +112,14 @@ final class AccountConnection { struct AccountConnectionView: View { let connection: AccountConnection var close: () -> Void = {} - @State private var server = "https://finite.chat" + /// The address to pair with, as the screen first shows it: Debug's + /// `--pair-server`, else the saved account's, else Finite's own. + @State private var server: String + init(connection: AccountConnection, close: @escaping () -> Void = {}) { + self.connection = connection + self.close = close + _server = State(initialValue: Self.pairServer ?? connection.account?.origin.absoluteString ?? "https://finite.chat") + } @State private var replaceKey = false @State private var forgetConnection = false @Environment(\.openURL) private var openURL @@ -164,7 +171,6 @@ struct AccountConnectionView: View { } .navigationTitle("Connect Finite").navigationBarTitleDisplayMode(.inline) .disabled(connection.busy) - .task { server = connection.account?.origin.absoluteString ?? initialServer } .task(id: "\(connection.code?.code ?? "")|\(scenePhase)") { if connection.code != nil && scenePhase == .active { await connection.poll() } } .onChange(of: connection.account?.approved) { _, approved in if approved == true { close() } } .confirmationDialog("Remove this connection from this iPhone?", isPresented: $forgetConnection, titleVisibility: .visible) { @@ -175,11 +181,11 @@ struct AccountConnectionView: View { } message: { Text("Use this if the previous key was revoked. Other devices keep their own keys.") } } } - private var initialServer: String { + private static var pairServer: String? { #if DEBUG let args = ProcessInfo.processInfo.arguments if let i = args.firstIndex(of: "--pair-server"), args.indices.contains(i + 1) { return args[i + 1] } #endif - return "https://finite.chat" + return nil } } diff --git a/ios/Sources/AppAppearanceView.swift b/ios/Sources/AppAppearanceView.swift index d10e66251..d85be4a61 100644 --- a/ios/Sources/AppAppearanceView.swift +++ b/ios/Sources/AppAppearanceView.swift @@ -25,7 +25,7 @@ struct AppAppearanceView: View { if canEdit { TextField("Describe an icon (optional)", text: $hint, axis: .vertical).lineLimit(1...3).accessibilityIdentifier("iconHint") Button("Generate icons", systemImage: "sparkles") { Task { await makeIcons() } }.accessibilityIdentifier("makeAppIcons") - .disabled(inFlight || hint.count > 200) + .disabled(inFlight) Button("Upload icon…", systemImage: "square.and.arrow.up") { importer = true }.disabled(inFlight) if app.picture != nil { Button("Remove icon", systemImage: "trash", role: .destructive) { Task { await change { let _: JSONValue = try await api.request("api/f/\(app.name)/icon", method: "DELETE") } } } diff --git a/ios/Sources/ChatDraftStore.swift b/ios/Sources/ChatDraftStore.swift index 111080d47..4279f2fd3 100644 --- a/ios/Sources/ChatDraftStore.swift +++ b/ios/Sources/ChatDraftStore.swift @@ -9,6 +9,42 @@ struct SavedChat: Codable { var recipients: [String] var pending: [PendingPost] var isEmpty: Bool { composer.isEmpty && attachments.isEmpty && pending.isEmpty && replyTo == nil } + + /// One chat's bounds, which editing, saving and loading all hold it to: + /// a message's words, its attachments, and the sends waiting. + static let textMax = 32_768 + static let attachmentsMax = 8 + static let pendingMax = 16 + /// A saved file's size at most: the bounds above, with room for JSON's + /// escapes. + static let fileMax = 4 * 1_048_576 + + /// `text` cut to `textMax` UTF-8 bytes, at a character, and whether it was. + static func fitted(_ text: String) -> (String, Bool) { + guard text.utf8.count > textMax else { return (text, false) } + var kept = "", bytes = 0 + // whole characters: one that would cross the bound is dropped whole + for character in text { + bytes += character.utf8.count + guard bytes <= textMax else { break } + kept.append(character) + } + return (kept, true) + } + + /// Within the bounds, and whether anything was cut to get there: the + /// composer to `textMax`, its attachments to the first eight, and the + /// sends no send could be (no id, too long) and those past sixteen + /// dropped. + func fitted() -> (SavedChat, Bool) { + var chat = self + let (composer, long) = Self.fitted(composer) + chat.composer = composer + chat.attachments = Array(attachments.prefix(Self.attachmentsMax)) + let sendable = pending.filter { !$0.id.isEmpty && $0.text.utf8.count <= Self.textMax && $0.attachments.count <= Self.attachmentsMax } + chat.pending = Array(sendable.prefix(Self.pendingMax)) + return (chat, long || chat.attachments.count < attachments.count || chat.pending.count < pending.count) + } } struct ChatDraftStore { @@ -27,23 +63,34 @@ struct ChatDraftStore { let scope = Data(SHA256.hash(data: Data((api.baseURL.absoluteString + "|" + (api.accountID ?? api.identity.publicKey)).utf8))).hex return support.appending(path: "Finite/Accounts/" + scope, directoryHint: .isDirectory) } - func load() throws -> SavedChat? { + /// What this chat saved, within its bounds (`cut`: some of it was not), + /// or nothing. A file that is no saved chat (another version, too big, + /// not JSON) is removed, and said so (`unreadable`): a chat always opens. + func load() throws -> (chat: SavedChat, cut: Bool)? { guard FileManager.default.fileExists(atPath: file.path) else { return nil } - guard (try file.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0) <= 1_048_576 else { throw APIError.limitExceeded } - let saved = try JSONDecoder().decode(SavedChat.self, from: Data(contentsOf: file)) - guard saved.version == 1, saved.composer.utf8.count <= 32_768, saved.pending.count <= 16, - saved.attachments.count <= 8, saved.pending.allSatisfy({ !$0.id.isEmpty && $0.text.utf8.count <= 32_768 && $0.attachments.count <= 8 }) else { throw APIError.invalidResponse } - return saved + let size = try file.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0 + guard size <= SavedChat.fileMax, let saved = try? JSONDecoder().decode(SavedChat.self, from: Data(contentsOf: file)), saved.version == 1 else { + try FileManager.default.removeItem(at: file) + throw ChatDraftStore.unreadable + } + return saved.fitted() } - func save(_ saved: SavedChat) throws { + static let unreadable = APIError.server("This chat’s saved draft could not be read, and was cleared.") + + /// Saves `saved` as `load` takes it back: within its bounds. Answers + /// whether anything was cut to fit (nothing is when its chat keeps to + /// them as it is edited: `ChatSession`). + @discardableResult + func save(_ saved: SavedChat) throws -> Bool { + let (saved, cut) = saved.fitted() if let accountID { // A send finishing after disconnect must not recreate erased files. guard let active = try DeviceKeychain.read(), active.origin == origin, - active.identity == accountID, try active.signingKey.publicKey == publicKey else { return } + active.identity == accountID, try active.signingKey.publicKey == publicKey else { return cut } } if saved.isEmpty { if FileManager.default.fileExists(atPath: file.path) { try FileManager.default.removeItem(at: file) } - return + return cut } try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete]) if !FileManager.default.fileExists(atPath: file.path) { @@ -51,11 +98,13 @@ struct ChatDraftStore { guard files.count < 32 else { throw APIError.server("Finish or discard a draft in another chat before saving more conversations.") } } let data = try JSONEncoder().encode(saved) - guard data.count <= 1_048_576 else { throw APIError.limitExceeded } + // within its bounds a chat fits, but for text of nothing but JSON escapes + guard data.count <= SavedChat.fileMax else { throw APIError.server("This chat’s draft is too large to keep on this iPhone.") } try data.write(to: file, options: [.atomic, .completeFileProtection]) var excluded = directory var values = URLResourceValues(); values.isExcludedFromBackup = true try excluded.setResourceValues(values) + return cut } static func removeAccount(api: FragmentAPI) throws { let path = try accountDirectory(api: api) diff --git a/ios/Sources/ChatSession.swift b/ios/Sources/ChatSession.swift index 03190d0ca..db6525cf0 100644 --- a/ios/Sources/ChatSession.swift +++ b/ios/Sources/ChatSession.swift @@ -24,15 +24,19 @@ final class ChatSession { init(fragment: FragmentSummary, api: FragmentAPI) { self.fragment = fragment self.api = api + do { persistence = try ChatDraftStore(api: api, fragment: fragment.name) } + catch { self.error = "The local draft could not be opened: " + error.localizedDescription; return } + // What was saved, within its bounds; a file that is none is cleared. + // Either way the chat opens and sends. do { - persistence = try ChatDraftStore(api: api, fragment: fragment.name) - if let saved = try persistence?.load() { + if let (saved, cut) = try persistence?.load() { composer = saved.composer; attachments = saved.attachments; replyTo = saved.replyTo; recipients = saved.recipients // A terminated send may already be committed. Retry remains an // explicit action, with exactly the saved id and body. pending = saved.pending.map { var post = $0; post.failed = true; return post } + if cut { self.error = "Part of this chat’s saved draft was longer than a message can be, and was cut." } } - } catch { self.error = "The local draft could not be opened: " + error.localizedDescription; persistence = nil } + } catch { self.error = error.localizedDescription } } private var saved: SavedChat { SavedChat(composer: composer, attachments: attachments, replyTo: replyTo, recipients: recipients, pending: pending) } private func scheduleDraftSave() { @@ -49,6 +53,25 @@ final class ChatSession { catch { self.error = "The local draft could not be saved: " + error.localizedDescription } } + /// The composer as typed or pasted, kept to what a message (and its + /// saved draft) can hold: past it, cut at a character, and said so. + func edit(_ text: String) { + let (kept, cut) = SavedChat.fitted(text) + composer = kept + if cut { error = "A message is at most 32 KB: the rest was cut." } + } + + /// A refused send's words back in the composer, to change and send + /// anew, once the composer is empty (`deliver`). + func reopen(_ post: PendingPost) { + guard let index = pending.firstIndex(where: { $0.id == post.id }), pending[index].refused != nil else { return } + guard composer.isEmpty && attachments.isEmpty else { error = "Send or clear your draft first, then edit this message."; return } + let refused = pending.remove(at: index) + composer = refused.text; attachments = refused.attachments + replyTo = timeline.records.first { $0.channel == "chat" && $0.seq == refused.replyTo } + error = nil + } + // Owned by the view's .task: backgrounding or leaving the chat closes the // socket, including a suspended receive. Each foreground visit catches up. func run() async { @@ -138,7 +161,8 @@ final class ChatSession { return } let text = composer.trimmingCharacters(in: .whitespacesAndNewlines) - guard (!text.isEmpty || !attachments.isEmpty), text.utf8.count <= 32_768, pending.count < 16 else { return } + guard !text.isEmpty || !attachments.isEmpty else { return } + guard pending.count < SavedChat.pendingMax else { error = "Wait for the messages still sending, or edit the ones not sent."; return } let post = PendingPost(id: UUID().uuidString, text: text, attachments: attachments, replyTo: replyTo?.seq, recipients: recipients) do { // Persist the exact send before any request leaves the phone. @@ -151,7 +175,7 @@ final class ChatSession { } func retry(_ post: PendingPost) async { - guard let index = pending.firstIndex(where: { $0.id == post.id }), pending[index].failed else { return } + guard let index = pending.firstIndex(where: { $0.id == post.id }), pending[index].failed, pending[index].refused == nil else { return } pending[index].failed = false await deliver(post) } @@ -167,13 +191,14 @@ final class ChatSession { pending.removeAll { $0.id == post.id } error = nil } catch APIError.refused(let message) { - // Refused, not lost: sending it again gets the same answer. It - // leaves the outbox, and its words go back to the composer. + // Refused, not lost: sending it again gets the same answer, so + // it is never retried. Its words go back to an empty composer; + // beside a draft, it waits to be edited (`reopen`), as a draft + // and it joined might be more than a message holds. guard let index = pending.firstIndex(where: { $0.id == post.id }) else { return } - let refused = pending.remove(at: index) - composer = [refused.text, composer].filter { !$0.isEmpty }.joined(separator: "\n\n") - attachments = refused.attachments + attachments.filter { kept in !refused.attachments.contains(kept) } - replyTo = replyTo ?? timeline.records.first { $0.seq == refused.replyTo } + pending[index].failed = true + pending[index].refused = message + reopen(pending[index]) self.error = message } catch { // A matching live record also acknowledges delivery. The HTTP diff --git a/ios/Sources/ChatView.swift b/ios/Sources/ChatView.swift index a2578601c..bb0e45a38 100644 --- a/ios/Sources/ChatView.swift +++ b/ios/Sources/ChatView.swift @@ -45,7 +45,11 @@ struct ChatView: View { ForEach(session.pending) { pending in VStack(alignment: .trailing, spacing: 6) { MessageView(text: pending.text, isYou: true) - if pending.failed { + if let refused = pending.refused { + Button("Not sent: \(refused) Tap to edit", systemImage: "pencil") { + session.reopen(pending) + }.font(.caption).accessibilityIdentifier("editRefused") + } else if pending.failed { Button("Not sent. Tap to retry", systemImage: "arrow.clockwise") { Task { await session.retry(pending) } }.font(.caption).accessibilityIdentifier("retry") @@ -104,7 +108,7 @@ struct ChatView: View { Text(error).font(.caption).foregroundStyle(.secondary).padding(.horizontal, 20).padding(.top, 8) } composerAccessories - ComposerView(text: $session.composer, working: !session.timeline.activeTurns.isEmpty, + ComposerView(text: Binding(get: { session.composer }, set: { session.edit($0) }), working: !session.timeline.activeTurns.isEmpty, enabled: session.connected && session.caughtUp, busy: uploading, recording: voice.recording, attach: { importer = true }, record: { Task { await recordVoice() } }, send: { await session.send() }, stop: { await session.stop() }, hasAttachments: !session.attachments.isEmpty) diff --git a/ios/Sources/FragmentApp.swift b/ios/Sources/FragmentApp.swift index 03900e3fe..50cf5b48d 100644 --- a/ios/Sources/FragmentApp.swift +++ b/ios/Sources/FragmentApp.swift @@ -244,7 +244,7 @@ struct FragmentRoot: View { await library.load(api) let resolved = library.fragments.first { $0.name == chat.name } ?? chat let session = try library.session(for: resolved, api: api) - session.composer = homeDraft + session.edit(homeDraft) homeDraft = "" page = .chat(chat.name) await session.send() diff --git a/ios/Sources/Models.swift b/ios/Sources/Models.swift index 67e110fc9..4a26e7c4f 100644 --- a/ios/Sources/Models.swift +++ b/ios/Sources/Models.swift @@ -178,6 +178,8 @@ struct PendingPost: Codable, Identifiable, Sendable { let id: String let text: String var failed = false + /// Why the server refused it: final, so it is edited, never retried. + var refused: String? var attachments: [ChatAttachment] = [] var replyTo: Int64? var recipients: [String] = [] diff --git a/ios/Tests/DeviceIdentityTests.swift b/ios/Tests/DeviceIdentityTests.swift index 9c8393697..da8bb187b 100644 --- a/ios/Tests/DeviceIdentityTests.swift +++ b/ios/Tests/DeviceIdentityTests.swift @@ -65,8 +65,8 @@ struct DeviceIdentityTests { let saved = SavedChat(composer: "Keep this draft", attachments: [], recipients: [], pending: [post]) try store.save(saved) let reloaded = try ChatDraftStore(api: api, fragment: "chat", root: root).load() - #expect(reloaded?.composer == saved.composer) - #expect(reloaded?.pending.first?.id == post.id) + #expect(reloaded?.chat.composer == saved.composer && reloaded?.cut == false) + #expect(reloaded?.chat.pending.first?.id == post.id) let other = FragmentAPI(baseURL: api.baseURL, identity: try fixedKey(4)) #expect(try ChatDraftStore(api: other, fragment: "chat", root: root).load() == nil) try store.save(SavedChat(composer: "", attachments: [], recipients: [], pending: [])) diff --git a/ios/Tests/DraftTests.swift b/ios/Tests/DraftTests.swift new file mode 100644 index 000000000..66c83fdc6 --- /dev/null +++ b/ios/Tests/DraftTests.swift @@ -0,0 +1,87 @@ +import Foundation +import Testing +@testable import Fragment + +/// A chat's saved draft and outbox (ChatDraftStore) across restarts: what +/// is saved is always what loads, and no saved file ever stops a chat. +@MainActor +struct DraftTests { + private let chat = FragmentSummary(name: "chat--d1", kind: "chat") + private var posts: String { "POST /api/f/\(chat.name)/channels/chat" } + + @Test func anOversizedPasteIsCutVisiblyAndTheDraftSurvivesARestart() async throws { + let route = posts + let stub = Stub { seen, _ in seen.route == route ? (200, #"{"record":\#(record("chat", 1, principal: "npub1me", body: #"{"text":"ok"}"#)),"replayed":false}"#) : (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + session.edit(String(repeating: "é", count: 20_000)) // 40,000 bytes + #expect(session.composer.utf8.count == SavedChat.textMax && session.error?.contains("cut") == true) + session.flushDraft() + let reopened = ChatSession(fragment: chat, api: api) + #expect(reopened.composer == session.composer && reopened.error == nil) + await reopened.send() + #expect(stub.seen.filter { $0.route == route }.count == 1 && reopened.pending.isEmpty) + } + + @Test func aRefusedSendBesideADraftWaitsToBeEditedAcrossARestart() async throws { + let route = posts + let stub = Stub { seen, before in + guard seen.route == route else { return (404, "{}") } + // lost the first time, refused the second + return before.contains { $0.route == route } ? (409, #"{"error":"conflicting_body","message":"Not that one."}"#) : (503, #"{"message":"Try again."}"#) + } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let session = ChatSession(fragment: chat, api: api) + session.edit("First words") + await session.send() + let sent = try #require(session.pending.first) + session.edit("A draft of more words") + await session.retry(sent) + // the draft stays as it was; the refused send waits, never retried + #expect(session.composer == "A draft of more words") + #expect(session.pending.count == 1 && session.pending[0].refused == "Not that one.") + session.flushDraft() + + let reopened = ChatSession(fragment: chat, api: api) + #expect(reopened.error == nil && reopened.composer == "A draft of more words") + let refused = try #require(reopened.pending.first) + #expect(refused.refused == "Not that one." && refused.text == "First words") + await reopened.retry(refused) + #expect(stub.seen.filter { $0.route == route }.count == 2, "a refused send is never sent again") + reopened.reopen(refused) + #expect(reopened.pending.count == 1 && reopened.composer == "A draft of more words", "a draft is never joined past a message's size") + reopened.edit("") + reopened.reopen(refused) + #expect(reopened.pending.isEmpty && reopened.composer == "First words") + } + + @Test func aSavedDraftPastItsBoundsOrUnreadableNeverStopsTheChat() async throws { + let route = posts + let stub = Stub { seen, _ in seen.route == route ? (200, #"{"record":\#(record("chat", 1, principal: "npub1me", body: #"{"text":"ok"}"#)),"replayed":false}"#) : (404, "{}") } + let api = try stub.api() + defer { try? ChatDraftStore.removeAccount(api: api) } + let store = try ChatDraftStore(api: api, fragment: chat.name) + try FileManager.default.createDirectory(at: store.directory, withIntermediateDirectories: true) + + // written by something else: a composer past a message, a send no + // send could be, beside one that can + let over = SavedChat(composer: String(repeating: "x", count: 40_000), attachments: [], recipients: [], + pending: [PendingPost(id: "", text: "no id"), PendingPost(id: "p1", text: "kept")]) + try JSONEncoder().encode(over).write(to: store.file) + let cut = ChatSession(fragment: chat, api: api) + #expect(cut.composer.utf8.count == SavedChat.textMax && cut.pending.map(\.id) == ["p1"]) + #expect(cut.error?.contains("was cut") == true) + await cut.send() + #expect(stub.seen.filter { $0.route == route }.count == 1, "it still sends") + + try Data("not a saved chat".utf8).write(to: store.file) + let cleared = ChatSession(fragment: chat, api: api) + #expect(cleared.composer.isEmpty && cleared.pending.isEmpty && cleared.error?.contains("cleared") == true) + #expect(!FileManager.default.fileExists(atPath: store.file.path)) + cleared.edit("Fresh") + await cleared.send() + #expect(stub.seen.filter { $0.route == route }.count == 2, "and so does this one") + } +} diff --git a/ios/UITests/FragmentUITests.swift b/ios/UITests/FragmentUITests.swift index 3a2c47622..455b33e5c 100644 --- a/ios/UITests/FragmentUITests.swift +++ b/ios/UITests/FragmentUITests.swift @@ -19,7 +19,10 @@ final class FragmentUITests: XCTestCase { guard server.waitForExistence(timeout: 10) else { throw XCTSkip("This simulator holds a connected account; reset its keychain (`xcrun simctl keychain booted reset`) and run again.") } - XCTAssertEqual(server.value as? String, "http://127.0.0.1:9") + // never ask any other server than the closed port + guard server.value as? String == "http://127.0.0.1:9" else { + return XCTFail("Connect shows \(server.value ?? "nothing"), not the --pair-server address") + } XCTAssertFalse(app.buttons["tryPreview"].exists, "there is no unsigned preview") screenshot(app, "Connect") app.buttons["pairAccount"].tap() diff --git a/xtask/src/eval_sinks.rs b/xtask/src/eval_sinks.rs index 023691e2e..537e112ed 100644 --- a/xtask/src/eval_sinks.rs +++ b/xtask/src/eval_sinks.rs @@ -134,7 +134,7 @@ const CARDS: &str = "cell/src/card.rs"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Lang { Rust, - /// Swift comments and quoted literals share the C-style lexer; native eval calls are checked directly. + /// Swift: its strings' text as literals, each interpolation's code as code (`lex_swift`). Swift, JavaScript, /// a page: its inline scripts, as JavaScript @@ -244,7 +244,7 @@ pub fn hits(file: &File) -> Vec { drivers(b, &parts, &mut at); } Lang::Swift => { - let parts = lex_rust(b); + let parts = lex_swift(b); devtools(b, &parts, &mut at); drivers(b, &parts, &mut at); for word in ["evaluateJavaScript", "callAsyncJavaScript"] { @@ -674,6 +674,91 @@ fn lex_rust(b: &[u8]) -> Vec { parts } +/// Swift: `//` comments and nested `/* */`; strings (`"…"`, multiline +/// `"""…"""`, raw `#"…"#` with any number of `#`) as literals, but the code +/// of each interpolation in one (`\(…)`, `\#(…)` in a raw string with one +/// `#`) as code, its own strings and interpolations read the same way. No +/// `'` quotes anything in Swift. A regular expression literal is read as +/// code: a name in one is found, never missed. +fn lex_swift(b: &[u8]) -> Vec { + let mut parts = vec![Part::Code; b.len()]; + // each string an interpolation is open in: its `#`s, whether it is + // multiline, and the paren depth its `(` opened at (its `)` closes it) + let mut open: Vec<(usize, bool, usize)> = vec![]; + let mut depth = 0usize; + let mut i = 0; + // bounded: each pass moves `i` forward + while i < b.len() { + let (c, next) = (b[i], b.get(i + 1).copied()); + let start = i; + if c == b')' && open.last().is_some_and(|&(_, _, d)| d == depth) { + let (hashes, multiline, _) = open.pop().expect("an interpolation is open"); + parts[i] = Part::Literal; + i = swift_text(b, i + 1, hashes, multiline, &mut parts, &mut open, depth); + } else if c == b'/' && next == Some(b'/') { + i = line_end(b, i); + mark(&mut parts, start..i, Part::Comment); + } else if c == b'/' && next == Some(b'*') { + i = block_end(b, i, true); + mark(&mut parts, start..i, Part::Comment); + } else if c == b'"' || c == b'#' { + let hashes = b[i..].iter().take_while(|&&c| c == b'#').count(); + if b.get(i + hashes) != Some(&b'"') { + // a directive (`#if`, `#available`) or a raw identifier's `#` + i += hashes.max(1); + continue; + } + let multiline = b[i + hashes..].starts_with(b"\"\"\""); + let body = i + hashes + if multiline { 3 } else { 1 }; + mark(&mut parts, start..body.min(b.len()), Part::Literal); + i = swift_text(b, body, hashes, multiline, &mut parts, &mut open, depth); + } else { + match c { + b'(' => depth += 1, + b')' => depth = depth.saturating_sub(1), + _ => {} + } + i += 1; + } + } + parts +} + +/// A Swift string's text from `i`, marked as literal: to past its closing +/// quote (and `#`s), or past the `(` of an interpolation, which is pushed on +/// `open` (at paren `depth`). A single-line string unclosed at its line's +/// end ends there. +fn swift_text(b: &[u8], mut i: usize, hashes: usize, multiline: bool, parts: &mut [Part], open: &mut Vec<(usize, bool, usize)>, depth: usize) -> usize { + let quote: &[u8] = if multiline { b"\"\"\"" } else { b"\"" }; + // bounded: each pass moves `i` forward + while i < b.len() { + let rest = &b[i..]; + let hashed = |at: usize| rest.len() >= at + hashes && rest[at..at + hashes].iter().all(|&c| c == b'#'); + if rest[0] == b'\\' && hashed(1) { + let after = 1 + hashes; + if rest.get(after) == Some(&b'(') { + mark(parts, i..i + after + 1, Part::Literal); + open.push((hashes, multiline, depth)); + return i + after + 1; + } + // an escape: what it escapes is text too + let end = (i + after + 1).min(b.len()); + mark(parts, i..end, Part::Literal); + i = end; + } else if rest.starts_with(quote) && hashed(quote.len()) { + let end = i + quote.len() + hashes; + mark(parts, i..end, Part::Literal); + return end; + } else if rest[0] == b'\n' && !multiline { + return i; + } else { + parts[i] = Part::Literal; + i += 1; + } + } + i +} + /// A literal a word at its start prefixes, ending where it does: `r"…"`, /// `r#"…"#`, `b"…"`, `br"…"`, `c"…"`, `cr"…"`, `b'…'`; `None` for a word /// (`r#type` is a raw identifier). @@ -810,6 +895,17 @@ mod tests { #[test] fn swift_comments_and_data_do_not_evaluate_a_page() { assert!(found("ios/Sources/Web.swift", "// web.evaluateJavaScript(source)\nlet label = \"evaluateJavaScript\"\nweb.load(request)\n").is_empty()); + // text, however quoted: a raw string's `\(` is no interpolation, and + // an interpolation's own strings are text + for text in [ + "let a = #\"\\(web.evaluateJavaScript(source))\"#\n", + "let b = \"\\(label(\"evaluateJavaScript(x)\"))\"\n", + "let c = \"\"\"\n web.callAsyncJavaScript(source)\n \"\"\"\n", + "let d = ##\"say \"#\\#(web.evaluateJavaScript(x))\"# then\"##\n", + "/* outer /* web.evaluateJavaScript(x) */ still a comment */\n", + ] { + assert!(found("ios/Sources/Web.swift", text).is_empty(), "{text}"); + } } /// Goal: a planted use in each category fails the check, named by its @@ -818,9 +914,15 @@ mod tests { #[test] fn a_planted_eval_fails_the_check_naming_its_line() { let clean = file("cell/clean.mjs", "export const ok = 1;\n"); - let planted: [(&str, &str, usize, &str); 40] = [ + let planted: [(&str, &str, usize, &str); 45] = [ ("ios/Sources/Web.swift", "// Native page scripts are not evaluated.\nweb.evaluateJavaScript(source)\n", 2, "evaluateJavaScript"), ("ios/Sources/Async.swift", "try await web.callAsyncJavaScript(source, arguments: [:], in: nil, contentWorld: .page)\n", 1, "callAsyncJavaScript"), + // inside an interpolation, a string's code is code + ("ios/Sources/Said.swift", "let said = \"result: \\(try await web.evaluateJavaScript(source))\"\n", 1, "evaluateJavaScript"), + ("ios/Sources/Raw.swift", "let said = #\"a \"quote\" \\#(try await web.callAsyncJavaScript(source, arguments: [:], in: nil, contentWorld: .page))\"#\n", 1, "callAsyncJavaScript"), + ("ios/Sources/Lines.swift", "let said = \"\"\"\n page:\n \\(try await web.evaluateJavaScript(source))\n \"\"\"\n", 3, "evaluateJavaScript"), + ("ios/Sources/Nested.swift", "let said = \"\\(f(\"\\(web.evaluateJavaScript(js))\"))\"\n", 1, "evaluateJavaScript"), + ("ios/Sources/After.swift", "let said = \"\\(g(\")\"))\"; web.callAsyncJavaScript(js)\n", 1, "callAsyncJavaScript"), // DevTools, as a method on the wire and as a client's call ("cell/src/card.rs", "async fn f() {\n cdp.call(\"Runtime.evaluate\", json!({ \"expression\": js }), deadline).await?;\n}\n", 2, "Runtime.evaluate"), ("crates/core/src/a.rs", "const M: &str = \"Runtime.callFunctionOn\";\n", 1, "callFunctionOn"), From 9f579ce8b084f54d33440bbe99a6f92c6c85aadf Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 4 Nov 2026 14:11:30 -0600 Subject: [PATCH 3/6] A UI test's launch keeps its own store and reaches loopback alone Review of #1028: the UI test's guard ran after launch, when the app had already loaded and validated whatever account the simulator held, and could have asked that account's server. `--ui-test` (Debug only) now selects, before anything reads it, a Keychain service and Application Support folder of the launch's own, emptied as it starts (LaunchScope), and refuses any origin past loopback before a request: in the saved account it loads, in an address typed at Connect, and in every signed request. The UI tests launch that way; one seeds an approved account past loopback (`--ui-test-saved-account`) and finds it never used, another taps Connect for Finite's own address and is refused without a request. Co-Authored-By: Claude Opus 5.5 --- docs/ios.md | 5 ++- ios/Fragment.xcodeproj/project.pbxproj | 8 +++++ ios/README.md | 11 +++--- ios/Sources/AccountConnection.swift | 19 +++++----- ios/Sources/ChatDraftStore.swift | 2 +- ios/Sources/DeviceIdentity.swift | 20 +++++------ ios/Sources/FragmentAPI.swift | 1 + ios/Sources/FragmentApp.swift | 1 + ios/Sources/LaunchScope.swift | 49 ++++++++++++++++++++++++++ ios/Tests/LaunchScopeTests.swift | 34 ++++++++++++++++++ ios/UITests/FragmentUITests.swift | 26 +++++++++++--- 11 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 ios/Sources/LaunchScope.swift create mode 100644 ios/Tests/LaunchScopeTests.swift diff --git a/docs/ios.md b/docs/ios.md index 3fbfced16..c8c7c2fbb 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -167,7 +167,10 @@ original-id outbox recovery, and the existing transport and UI flows. For local commands, see `ios/README.md`. The app always opens on Connect until a key is approved. Debug accepts loopback and `.localhost` HTTP origins, and -`--pair-server ` fills in Connect for `cargo xtask dev`. +`--pair-server ` fills in Connect for `cargo xtask dev`. A UI test's +launch (`--ui-test`, Debug only) keeps its account and drafts in a store of its +own, emptied as it starts, and refuses any origin past loopback before a request +(`LaunchScope`): a test never reads a person's account or reaches a deployment. Hosted use needs the server changes deployed for private app/brain web sheets; existing signed native API calls reuse the already deployed CLI authentication. diff --git a/ios/Fragment.xcodeproj/project.pbxproj b/ios/Fragment.xcodeproj/project.pbxproj index c1593182f..e8f2220b4 100644 --- a/ios/Fragment.xcodeproj/project.pbxproj +++ b/ios/Fragment.xcodeproj/project.pbxproj @@ -37,8 +37,10 @@ A8A038F320ECBCC025021D8B /* DeviceIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */; }; B00F01210DFD198F6F1180C9 /* FragmentAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F7C65701872682E560E008F /* FragmentAPI.swift */; }; B0CC33D54ED91967CDED0F0D /* AppSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCEF88547BE59E217E5B219 /* AppSharingView.swift */; }; + C26F2704F9C3B89DD4186C3B /* LaunchScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF9BE87256547DD6FFAAE4 /* LaunchScopeTests.swift */; }; CC7761F962CB619DD2A5BB51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */; }; E4358E209B49F024F52BDCAE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 811C73B367475E8B5D4DB77F /* AppIcon.icon */; }; + EBB66B878C4362F14779FB52 /* LaunchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95702BF0C241F4A98C5F812 /* LaunchScope.swift */; }; ED598966FDB4DD6D3F4486A1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */; }; F8EBB41899022F0D4E70C646 /* DraftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B11860501F69BE73ECB3D0 /* DraftTests.swift */; }; /* End PBXBuildFile section */ @@ -62,6 +64,7 @@ /* Begin PBXFileReference section */ 014FC5154C974ED4F457D381 /* FragmentWebSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FragmentWebSheet.swift; sourceTree = ""; }; + 01EF9BE87256547DD6FFAAE4 /* LaunchScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScopeTests.swift; sourceTree = ""; }; 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsTests.swift; sourceTree = ""; }; 1AFC16F9330AE76994AAFAFC /* AccountConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountConnection.swift; sourceTree = ""; }; 214C3D2F32BD4FA5AB5CDF41 /* MarkdownText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownText.swift; sourceTree = ""; }; @@ -83,6 +86,7 @@ 934BF3B3E3B9A45CF6843505 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; A419CCC396DDBF76202E0D42 /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; + A95702BF0C241F4A98C5F812 /* LaunchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScope.swift; sourceTree = ""; }; AB0814CDF8824F8A8657129D /* MarkdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = ""; }; AD99E857900FD0F65CD6B108 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; B84A48915AD6415232B1F460 /* AppAppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearanceView.swift; sourceTree = ""; }; @@ -160,6 +164,7 @@ EE5376FE0C2DCB1863EE49C7 /* FragmentApp.swift */, 014FC5154C974ED4F457D381 /* FragmentWebSheet.swift */, D261EC9048255D1B31FED623 /* HomeView.swift */, + A95702BF0C241F4A98C5F812 /* LaunchScope.swift */, 214C3D2F32BD4FA5AB5CDF41 /* MarkdownText.swift */, D48A78B0A3D4F8A90EEBD8DB /* Models.swift */, 98B6FB1CA5638E3B92C0E2CD /* SettingsView.swift */, @@ -175,6 +180,7 @@ 0A44398C694D0FA2138E5A64 /* AppSettingsTests.swift */, 92F50038387E161FFC74D67A /* DeviceIdentityTests.swift */, D8B11860501F69BE73ECB3D0 /* DraftTests.swift */, + 01EF9BE87256547DD6FFAAE4 /* LaunchScopeTests.swift */, AB0814CDF8824F8A8657129D /* MarkdownTests.swift */, 3A2F75C83DE72E8D3B8797E1 /* SearchTests.swift */, 8C581195A0FDAC2EA88D081F /* ShellTests.swift */, @@ -304,6 +310,7 @@ 5938CA76ED5569461BA445AB /* AppSettingsTests.swift in Sources */, A8A038F320ECBCC025021D8B /* DeviceIdentityTests.swift in Sources */, F8EBB41899022F0D4E70C646 /* DraftTests.swift in Sources */, + C26F2704F9C3B89DD4186C3B /* LaunchScopeTests.swift in Sources */, A0F935E5B3AB7E00AF2297A0 /* MarkdownTests.swift in Sources */, 7A8D5455AD2EABF343F2905B /* SearchTests.swift in Sources */, 4FEE2A2E44CD058FD4666F85 /* ShellTests.swift in Sources */, @@ -341,6 +348,7 @@ 9B04659A7CA2A9C6E5F0276A /* FragmentApp.swift in Sources */, 1FA3D11FABC00DC40F41A041 /* FragmentWebSheet.swift in Sources */, 73EE483533F3A6306B65148C /* HomeView.swift in Sources */, + EBB66B878C4362F14779FB52 /* LaunchScope.swift in Sources */, 2A5C821580EECE3D7D8C7F3D /* MarkdownText.swift in Sources */, 4024A3397E46CADB1EC9D344 /* Models.swift in Sources */, ED598966FDB4DD6D3F4486A1 /* SettingsView.swift in Sources */, diff --git a/ios/README.md b/ios/README.md index 9e59f546c..d3873481d 100644 --- a/ios/README.md +++ b/ios/README.md @@ -132,10 +132,13 @@ beside a draft, a saved file past its bounds or unreadable); `MarkdownTests` a code fence arriving in parts. The stubs' answers are written by hand from docs/api.md. -`FragmentUITests` drive what needs no server: Connect, and an unreachable -server. They expect a simulator with no connected account -(`xcrun simctl keychain booted reset`). Flows against a paired account on the -dev stack are their own suite. +`FragmentUITests` drive what needs no server: Connect, an unreachable server, +and a server past loopback refused. Each launches with `--ui-test` (Debug only): +a Keychain service and Application Support folder of its own, emptied as it +starts, and loopback alone, so a simulator's own account is never read and no +other server is ever asked (`Sources/LaunchScope.swift`; +`--ui-test-saved-account ` seeds one, to show it unused). Flows against +a paired account on the dev stack are their own suite. The source of the generated project is `project.yml`. If changing project structure, use XcodeGen 2.42+ and commit its result: diff --git a/ios/Sources/AccountConnection.swift b/ios/Sources/AccountConnection.swift index ce30515c3..eed8739c2 100644 --- a/ios/Sources/AccountConnection.swift +++ b/ios/Sources/AccountConnection.swift @@ -16,10 +16,13 @@ final class AccountConnection { cachedAPI = client return client } - init() { + /// Where its account is kept, and what it may reach (`LaunchScope`). + private let scope: LaunchScope + init(scope: LaunchScope = .current) { + self.scope = scope do { - if let stored = try DeviceKeychain.read() { - guard try PlatformOrigin.parse(stored.origin.absoluteString) == stored.origin else { throw APIError.invalidResponse } + if let stored = try DeviceKeychain.read(service: scope.keychainService) { + guard try PlatformOrigin.parse(stored.origin.absoluteString, scope: scope) == stored.origin else { throw APIError.invalidResponse } _ = try stored.signingKey account = stored } @@ -29,14 +32,14 @@ final class AccountConnection { func begin(_ raw: String, newKey: Bool = false) async { busy = true; error = nil; code = nil; defer { busy = false } do { - let origin = try PlatformOrigin.parse(raw) + let origin = try PlatformOrigin.parse(raw, scope: scope) if let existing = account, existing.origin != origin && existing.approved { throw APIError.server("Disconnect this iPhone before pairing a different account.") } if newKey || account == nil || account?.origin != origin { let key = try DeviceIdentity() let next = DeviceAccount(origin: origin, secret: key.secret, approved: false) - try DeviceKeychain.save(next); account = next + try DeviceKeychain.save(next, service: scope.keychainService); account = next } guard let account else { throw APIError.invalidResponse } let api = try FragmentAPI(baseURL: origin, identity: account.signingKey) @@ -66,7 +69,7 @@ final class AccountConnection { try next.signingKey.publicKey == api.identity.publicKey else { throw CancellationError() } guard person["kind"].string == "person", let identity = person["id"].string else { throw APIError.invalidResponse } next.identity = identity; next.email = person["email"].string; next.approved = true - try DeviceKeychain.save(next); account = next; error = nil + try DeviceKeychain.save(next, service: scope.keychainService); account = next; error = nil return true } catch APIError.unauthorized { return false } } @@ -81,7 +84,7 @@ final class AccountConnection { func expire() { guard var next = account, next.approved else { return } next.approved = false - do { try DeviceKeychain.save(next); account = next; error = "This iPhone’s authorization ended. Pair again to reconnect." } + do { try DeviceKeychain.save(next, service: scope.keychainService); account = next; error = "This iPhone’s authorization ended. Pair again to reconnect." } catch { self.error = error.localizedDescription; account = nil } } func disconnect() async { @@ -104,7 +107,7 @@ final class AccountConnection { let client = try FragmentAPI(baseURL: account.origin, identity: account.signingKey, accountID: account.identity) try ChatDraftStore.removeAccount(api: client) } - try DeviceKeychain.clear(); account = nil; code = nil; cachedAPI = nil + try DeviceKeychain.clear(service: scope.keychainService); account = nil; code = nil; cachedAPI = nil await WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: .distantPast) } } diff --git a/ios/Sources/ChatDraftStore.swift b/ios/Sources/ChatDraftStore.swift index 4279f2fd3..4015ee8a0 100644 --- a/ios/Sources/ChatDraftStore.swift +++ b/ios/Sources/ChatDraftStore.swift @@ -61,7 +61,7 @@ struct ChatDraftStore { private static func accountDirectory(api: FragmentAPI, root: URL? = nil) throws -> URL { let support = try root ?? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let scope = Data(SHA256.hash(data: Data((api.baseURL.absoluteString + "|" + (api.accountID ?? api.identity.publicKey)).utf8))).hex - return support.appending(path: "Finite/Accounts/" + scope, directoryHint: .isDirectory) + return support.appending(path: LaunchScope.current.supportFolder + "/Accounts/" + scope, directoryHint: .isDirectory) } /// What this chat saved, within its bounds (`cut`: some of it was not), /// or nothing. A file that is no saved chat (another version, too big, diff --git a/ios/Sources/DeviceIdentity.swift b/ios/Sources/DeviceIdentity.swift index 565b65aa7..9e803c942 100644 --- a/ios/Sources/DeviceIdentity.swift +++ b/ios/Sources/DeviceIdentity.swift @@ -46,13 +46,12 @@ struct DeviceAccount: Codable, Sendable { } enum DeviceKeychain { - private static let service = "build.finite.fragment.device" - private static func query(_ account: String) -> [String: Any] { + private static func query(_ account: String, _ service: String) -> [String: Any] { [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecAttrSynchronizable as String: false] } - static func read(account: String = "active-account-v1") throws -> DeviceAccount? { - var request = query(account) + static func read(account: String = "active-account-v1", service: String = LaunchScope.current.keychainService) throws -> DeviceAccount? { + var request = query(account, service) request[kSecReturnData as String] = true request[kSecMatchLimit as String] = kSecMatchLimitOne var result: CFTypeRef? @@ -61,19 +60,19 @@ enum DeviceKeychain { guard status == errSecSuccess, let data = result as? Data else { throw APIError.keychain(status) } return try JSONDecoder().decode(DeviceAccount.self, from: data) } - static func save(_ account: DeviceAccount, account slot: String = "active-account-v1") throws { + static func save(_ account: DeviceAccount, account slot: String = "active-account-v1", service: String = LaunchScope.current.keychainService) throws { let data = try JSONEncoder().encode(account) let attributes: [String: Any] = [kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly] - let status = SecItemUpdate(query(slot) as CFDictionary, attributes as CFDictionary) + let status = SecItemUpdate(query(slot, service) as CFDictionary, attributes as CFDictionary) if status == errSecItemNotFound { - var insert = query(slot) + var insert = query(slot, service) attributes.forEach { insert[$0.key] = $0.value } let added = SecItemAdd(insert as CFDictionary, nil) guard added == errSecSuccess else { throw APIError.keychain(added) } } else if status != errSecSuccess { throw APIError.keychain(status) } } - static func clear(account: String = "active-account-v1") throws { - let status = SecItemDelete(query(account) as CFDictionary) + static func clear(account: String = "active-account-v1", service: String = LaunchScope.current.keychainService) throws { + let status = SecItemDelete(query(account, service) as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { throw APIError.keychain(status) } } } @@ -88,7 +87,7 @@ final class NoSignedRedirects: NSObject, URLSessionTaskDelegate { } enum PlatformOrigin { - static func parse(_ raw: String) throws -> URL { + static func parse(_ raw: String, scope: LaunchScope = .current) throws -> URL { guard let url = URL(string: raw.trimmingCharacters(in: .whitespacesAndNewlines)), url.host != nil, url.user == nil, url.password == nil, url.query == nil, url.fragment == nil, url.path.isEmpty || url.path == "/" else { throw APIError.server("Enter the platform’s HTTPS address, without a path.") } @@ -98,6 +97,7 @@ enum PlatformOrigin { allowed = allowed || (url.scheme == "http" && (host == "localhost" || host == "127.0.0.1" || host == "::1" || host.hasSuffix(".localhost") || host.hasSuffix(".local"))) #endif guard allowed else { throw APIError.server("Use HTTPS for your Finite account.") } + guard scope.allows(url) else { throw LaunchScope.refused } var parts = URLComponents(url: url, resolvingAgainstBaseURL: false)! parts.path = "" return parts.url! diff --git a/ios/Sources/FragmentAPI.swift b/ios/Sources/FragmentAPI.swift index 53cfb0f12..7b7eedf38 100644 --- a/ios/Sources/FragmentAPI.swift +++ b/ios/Sources/FragmentAPI.swift @@ -61,6 +61,7 @@ struct FragmentAPI: Sendable { func authorized(_ original: URLRequest) throws -> URLRequest { guard let url = original.url, PlatformOrigin.same(baseURL, url), url.user == nil, url.password == nil, url.fragment == nil, original.httpBodyStream == nil else { throw APIError.server("The request does not belong to your Finite server.") } + guard LaunchScope.current.allows(url) else { throw LaunchScope.refused } var request = original request.setValue(try identity.authorization(method: request.httpMethod ?? "GET", url: url, body: request.httpBody ?? Data()), forHTTPHeaderField: "Authorization") request.setValue("Finite-iOS/0.3", forHTTPHeaderField: "User-Agent") diff --git a/ios/Sources/FragmentApp.swift b/ios/Sources/FragmentApp.swift index 50cf5b48d..bc902c305 100644 --- a/ios/Sources/FragmentApp.swift +++ b/ios/Sources/FragmentApp.swift @@ -2,6 +2,7 @@ import SwiftUI @main struct FragmentApp: App { + init() { LaunchScope.start() } var body: some Scene { WindowGroup { FragmentRoot() } } } diff --git a/ios/Sources/LaunchScope.swift b/ios/Sources/LaunchScope.swift new file mode 100644 index 000000000..c5e2b3ce1 --- /dev/null +++ b/ios/Sources/LaunchScope.swift @@ -0,0 +1,49 @@ +import Foundation + +/// Where this launch keeps its account and drafts, and which servers it may +/// reach. A UI test's launch (`--ui-test`, Debug only) gets a store of its +/// own, a Keychain service and an Application Support folder emptied as it +/// starts, and reaches loopback alone (this machine's `cargo xtask dev`): a +/// phone's own account is never read, and no other server is ever asked. +struct LaunchScope: Sendable { + let keychainService: String + let supportFolder: String + let loopbackOnly: Bool + + static let app = LaunchScope(keychainService: "build.finite.fragment.device", supportFolder: "Finite", loopbackOnly: false) + static let uiTest = LaunchScope(keychainService: "build.finite.fragment.device.ui-test", supportFolder: "Finite-UITest", loopbackOnly: true) + static let current: LaunchScope = { + #if DEBUG + if ProcessInfo.processInfo.arguments.contains("--ui-test") { return .uiTest } + #endif + return .app + }() + + /// Whether this launch may send anything to `url`'s host. + func allows(_ url: URL) -> Bool { + guard loopbackOnly else { return true } + guard let host = url.host?.lowercased().trimmingCharacters(in: CharacterSet(charactersIn: "[]")) else { return false } + return host == "localhost" || host == "127.0.0.1" || host == "::1" || host.hasSuffix(".localhost") + } + + static let refused = APIError.server("This launch reaches only this machine.") + + /// Before anything reads the store: a UI test's starts empty, or holding + /// only the account its `--ui-test-saved-account ` names + /// (approved, with a fresh key), so a test can show that one past + /// loopback is never used. + static func start() { + #if DEBUG + guard current.loopbackOnly else { return } + try? DeviceKeychain.clear() + if let support = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) { + try? FileManager.default.removeItem(at: support.appending(path: current.supportFolder, directoryHint: .isDirectory)) + } + let args = ProcessInfo.processInfo.arguments + if let i = args.firstIndex(of: "--ui-test-saved-account"), args.indices.contains(i + 1), let origin = URL(string: args[i + 1]), + let key = try? DeviceIdentity() { + try? DeviceKeychain.save(DeviceAccount(origin: origin, secret: key.secret, identity: "npub1uitest", email: "ui-test@example.com", approved: true)) + } + #endif + } +} diff --git a/ios/Tests/LaunchScopeTests.swift b/ios/Tests/LaunchScopeTests.swift new file mode 100644 index 000000000..d05dcb13c --- /dev/null +++ b/ios/Tests/LaunchScopeTests.swift @@ -0,0 +1,34 @@ +import Foundation +import Testing +@testable import Fragment + +/// A UI test's launch (`--ui-test`): a store of its own, and loopback alone. +@MainActor +struct LaunchScopeTests { + @Test func aUITestLaunchReachesLoopbackAlone() throws { + let scope = LaunchScope.uiTest + for raw in ["http://127.0.0.1:8790", "http://localhost:8790", "http://moss--a1.fragment.localhost:8790/__signin", "http://[::1]:8790"] { + #expect(scope.allows(try #require(URL(string: raw))), "\(raw)") + } + for raw in ["https://finite.chat", "https://moss--a1.finite.place/", "https://127.0.0.1.example.com", "https://localhost.example"] { + #expect(!scope.allows(try #require(URL(string: raw))), "\(raw)") + } + #expect(throws: APIError.self) { try PlatformOrigin.parse("https://finite.chat", scope: scope) } + #expect(try PlatformOrigin.parse("http://127.0.0.1:8790", scope: scope).absoluteString == "http://127.0.0.1:8790") + #expect(LaunchScope.app.allows(try #require(URL(string: "https://finite.chat")))) + #expect(LaunchScope.uiTest.keychainService != LaunchScope.app.keychainService && LaunchScope.uiTest.supportFolder != LaunchScope.app.supportFolder) + } + + @Test func aUITestLaunchNeverLoadsAnAccountPastLoopback() throws { + let scope = LaunchScope(keychainService: "tests-" + UUID().uuidString, supportFolder: "Finite-Tests", loopbackOnly: true) + defer { try? DeviceKeychain.clear(service: scope.keychainService) } + let save = { (origin: String) in + try DeviceKeychain.save(DeviceAccount(origin: try #require(URL(string: origin)), secret: try DeviceIdentity().secret, identity: "npub1person", approved: true), service: scope.keychainService) + } + try save("https://finite.example") + let refused = AccountConnection(scope: scope) + #expect(refused.account == nil && refused.api == nil && refused.error == "This launch reaches only this machine.") + try save("http://127.0.0.1:8790") + #expect(AccountConnection(scope: scope).api?.baseURL.absoluteString == "http://127.0.0.1:8790") + } +} diff --git a/ios/UITests/FragmentUITests.swift b/ios/UITests/FragmentUITests.swift index 455b33e5c..0ffbb5d6b 100644 --- a/ios/UITests/FragmentUITests.swift +++ b/ios/UITests/FragmentUITests.swift @@ -8,17 +8,19 @@ final class FragmentUITests: XCTestCase { let shot = XCTAttachment(screenshot: app.screenshot()); shot.name = name; shot.lifetime = .keepAlways; add(shot) } - /// A first launch asks for the person's Finite server, and a server that + /// A UI test's launch keeps a store of its own and reaches loopback + /// alone (`--ui-test`): even an approved account past loopback saved + /// there is never used, so it asks for the server, and a server that /// does not answer says so, leaving the screen to try again. func testConnectAsksForTheServerAndSaysWhenItIsUnreachable() throws { let app = XCUIApplication() // a closed port on this machine: nothing answers it - app.launchArguments = ["--pair-server", "http://127.0.0.1:9"] + app.launchArguments = ["--ui-test", "--ui-test-saved-account", "https://finite.example", "--pair-server", "http://127.0.0.1:9"] app.launch() let server = app.textFields["accountServer"] - guard server.waitForExistence(timeout: 10) else { - throw XCTSkip("This simulator holds a connected account; reset its keychain (`xcrun simctl keychain booted reset`) and run again.") - } + XCTAssertTrue(server.waitForExistence(timeout: 10), "a saved account past loopback is never loaded") + XCTAssertTrue(app.staticTexts["This launch reaches only this machine."].exists) + XCTAssertFalse(app.buttons["forgetAccount"].exists) // never ask any other server than the closed port guard server.value as? String == "http://127.0.0.1:9" else { return XCTFail("Connect shows \(server.value ?? "nothing"), not the --pair-server address") @@ -30,4 +32,18 @@ final class FragmentUITests: XCTestCase { XCTAssertFalse(app.staticTexts["pairingCode"].exists) XCTAssertTrue(app.buttons["pairAccount"].isEnabled) } + + /// The boundary holds for the address Connect starts with, too: a test + /// launch tapping Connect for Finite's own server asks nothing of it. + func testATestLaunchRefusesAServerPastLoopback() { + let app = XCUIApplication() + app.launchArguments = ["--ui-test"] + app.launch() + let server = app.textFields["accountServer"] + XCTAssertTrue(server.waitForExistence(timeout: 10)) + XCTAssertEqual(server.value as? String, "https://finite.chat") + app.buttons["pairAccount"].tap() + XCTAssertTrue(app.staticTexts["This launch reaches only this machine."].waitForExistence(timeout: 5)) + XCTAssertFalse(app.staticTexts["pairingCode"].exists) + } } From 673400a1f9523c0abad2588c84593a6be7fc982a Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 4 Nov 2026 16:59:36 -0600 Subject: [PATCH 4/6] iOS: long turns keep Stop, sheets never reopen a spent link, exports download Review of #1028 (alexlwn123): - Which turns run is kept apart from the record window. A chat reads a page of work (1000), and a turn begun before it has its start read back from earlier work (ten pages at most, as the web's chat page reads), so Stop shows for a long turn however the chat is opened. - A web sheet's load drops its spent redemption before asking again, and each redemption is a new web view: Try again never opens a used token (WebSheetModel). - An archive's chats index is optional: 404 is none, as the web's History reads it, and a failed read keeps what was read and never stops agent resolution, which now comes first. - Exports: the ready ExportView's `download` path, checked as this export's on the platform, downloads signed to a file of the phone's own, shared from there. - Editing a refused message restores its addressees; the newer draft's stay until then. - Settings reads a computer's `phase`. Co-Authored-By: Claude Opus 5.5 --- docs/ios.md | 9 ++- ios/README.md | 8 ++- ios/Sources/AppSettingsAPI.swift | 50 +++++++++++++++ ios/Sources/ChatSession.swift | 33 ++++++++-- ios/Sources/FragmentAPI.swift | 25 ++++++-- ios/Sources/FragmentApp.swift | 24 ++++++-- ios/Sources/FragmentWebSheet.swift | 98 ++++++++++++++++++------------ ios/Sources/Models.swift | 54 +++++++++++++--- ios/Sources/SettingsView.swift | 65 ++++++++++++++++---- ios/Tests/DraftTests.swift | 18 ++++-- ios/Tests/ShellTests.swift | 89 +++++++++++++++++++++++++++ ios/Tests/TransportTests.swift | 43 ++++++++++++- 12 files changed, 428 insertions(+), 88 deletions(-) diff --git a/docs/ios.md b/docs/ios.md index c8c7c2fbb..9e44694fc 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -148,7 +148,14 @@ refusal. Its own bounds are its storage's (above) and what it sends over a phone's network: an attachment of at most 25 MiB, and an icon fitted inside 256×256 on the phone, as the server would fit it. -JSON text subscriptions match the Worker’s live protocol. Connected accounts follow the signed list watcher, discard +JSON text subscriptions match the Worker’s live protocol. A chat reads its last +50 messages and a page of `work` (1000, `CHANNEL_PAGE`); which turns run is kept +apart from the shown records, and a turn begun before that page has its start +read back from earlier work (at most ten pages, as the web's chat page reads), so +Stop shows for a long turn however it was opened. An archive's chats index is +optional: an archive of other kinds alone has none, and a failed read never +stops the list or its agents. An export downloads with the phone's signature to +a file of its own, and is shared from there. Connected accounts follow the signed list watcher, discard stale list reloads, and mark visible, caught-up chats read. HTTP/WebSocket interruption recovery, independent cursors, Stop and history remain unchanged. Push delivery, approval-card controls, imported-chat continuation, and full Markdown tables are not implemented. Notification permission alone does not diff --git a/ios/README.md b/ios/README.md index d3873481d..805189034 100644 --- a/ios/README.md +++ b/ios/README.md @@ -123,9 +123,11 @@ sees a WebSocket). They cover what a phone meets: an acknowledgement lost after the post committed (retried with the same id and body), a refusal (final: the post leaves the outbox and its words return to the composer), a rate limit (kept to retry), a dropped stream (resubscribed from its cursor, nothing twice), -Stop, earlier pages that never move the live cursor, an ended authorization, -and the requests behind lists, joins, renames, archiving, attachments, search, -sharing and icons. `TimelineTests` cover ordering, cursors, drafts and turns; +Stop, a long turn whose start is past the backlog (read back, so Stop shows), +earlier pages that never move the live cursor, an ended authorization, a web +sheet that never reopens a spent redemption, an archive with no chats index or +a failing read, an export built then downloaded signed, and the requests behind +lists, joins, renames, archiving, attachments, search, sharing and icons. `TimelineTests` cover ordering, cursors, drafts and turns; `DeviceIdentityTests` NIP-98 binding, origins and Keychain; `DraftTests` the saved draft and outbox across restarts (a paste past a message, a refused send beside a draft, a saved file past its bounds or unreadable); diff --git a/ios/Sources/AppSettingsAPI.swift b/ios/Sources/AppSettingsAPI.swift index fc180d584..7968541d2 100644 --- a/ios/Sources/AppSettingsAPI.swift +++ b/ios/Sources/AppSettingsAPI.swift @@ -135,3 +135,53 @@ enum AppIconImage { return png as Data } } + +/// One export, as its person sees it (`ExportView`, docs/api.md, Export). +struct ExportView: Decodable, Sendable { + struct Progress: Decodable, Sendable { let done: Int; let total: Int } + struct Problem: Decodable, Sendable { let message: String } + let id: String + let state: String + var progress: Progress? + var size: Int64? + var error: Problem? + /// While it is ready: a path on the platform's origin, for its person alone. + var download: String? + var said: String { + switch state { + case "building": progress.map { "Building (\($0.done) of \($0.total))" } ?? "Building" + case "ready": size.map { "Ready, " + ByteCountFormatter.string(fromByteCount: $0, countStyle: .file) } ?? "Ready" + case "failed": "Failed" + (error.map { ": " + $0.message } ?? "") + case "expired": "Expired" + default: state.capitalized + } + } +} + +extension FragmentAPI { + func export() async throws -> ExportView? { + struct Status: Decodable { let export: ExportView? } + let status: Status = try await request("api/export") + return status.export + } + + func startExport() async throws -> ExportView { try await request("api/export", method: "POST", body: .object([:])) } + + /// A ready export's archive, downloaded with this iPhone's signature to a + /// file of its own (`Caches/Exports`), streamed to disk: its path must be + /// the platform's own export download. + func download(_ export: ExportView) async throws -> URL { + guard export.state == "ready", let path = export.download, let url = URL(string: path, relativeTo: baseURL)?.absoluteURL, + PlatformOrigin.same(url, baseURL), url.path == "/api/export/\(export.id)/download" else { throw APIError.invalidResponse } + let (temporary, response) = try await session.download(for: authorized(URLRequest(url: url))) + guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } + if http.statusCode == 401 { authorizationFailed(); throw APIError.unauthorized } + guard http.statusCode == 200 else { throw APIError.answer(Data(), status: http.statusCode) } + let folder = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "Exports", directoryHint: .isDirectory) + try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) + let file = folder.appending(path: "fragment-export-\(export.id).tar") + if FileManager.default.fileExists(atPath: file.path) { try FileManager.default.removeItem(at: file) } + try FileManager.default.moveItem(at: temporary, to: file) + return file + } +} diff --git a/ios/Sources/ChatSession.swift b/ios/Sources/ChatSession.swift index db6525cf0..d29b09530 100644 --- a/ios/Sources/ChatSession.swift +++ b/ios/Sources/ChatSession.swift @@ -67,7 +67,8 @@ final class ChatSession { guard let index = pending.firstIndex(where: { $0.id == post.id }), pending[index].refused != nil else { return } guard composer.isEmpty && attachments.isEmpty else { error = "Send or clear your draft first, then edit this message."; return } let refused = pending.remove(at: index) - composer = refused.text; attachments = refused.attachments + // its own words, files and addressees: the draft's were kept until now + composer = refused.text; attachments = refused.attachments; recipients = refused.recipients replyTo = timeline.records.first { $0.channel == "chat" && $0.seq == refused.replyTo } error = nil } @@ -118,7 +119,9 @@ final class ChatSession { try await subscribe(socket, channel: channel, after: frame.next) } else if channel == "chat" { caughtUp = true - if timeline.records.filter({ $0.channel == "chat" }).count < 50 { hasEarlier = false } + if timeline.records.filter({ $0.channel == "chat" }).count < Self.chatLast { hasEarlier = false } + } else if channel == "work" { + await readTurnsBack() } } } @@ -147,14 +150,36 @@ final class ChatSession { } } + /// The backlog a chat first reads: its last messages, and a page of + /// `work` (`CHANNEL_PAGE`), which holds a long turn's steps (up to 200 + /// calls and 200 steps) and, mostly, its start (`readTurnsBack`). + static let chatLast = 50 + static let workPage = 1_000 + /// Pages of earlier `work` read back for turns begun before the backlog, + /// as the web's chat page reads (templates/chat `earlierWork`). + static let turnPagesMax = 10 + private func subscribe(_ socket: LiveSocket, channel: String, after: Int64?) async throws { var body: [String: JSONValue] = ["type": .string("subscribe"), "channel": .string(channel)] if let after { body["after"] = .number(Double(after)) } - else { body["last"] = .number(50) } + else { body["last"] = .number(Double(channel == "work" ? Self.workPage : Self.chatLast)) } let data = try JSONEncoder().encode(JSONValue.object(body)) try await socket.send(.string(String(decoding: data, as: UTF8.self))) } + /// Earlier `work`, read until each turn the backlog shows work of has its + /// start or its end, so one begun before it still shows running (and + /// Stop). Bounded by `turnPagesMax`; a failure leaves it for the next + /// visit. What it reads only says which turns run: none is shown. + private func readTurnsBack() async { + for _ in 0.. 1 else { return } + guard let page = try? await api.history(fragment.name, channel: "work", before: oldest, limit: Self.workPage) else { return } + for record in page { timeline.learn(record) } + if page.count < Self.workPage { return } + } + } + func send() async { guard persistence != nil else { error = "The local draft could not be opened. Reopen this chat before sending." @@ -190,7 +215,7 @@ final class ChatSession { timeline.insert(record, advancingCursor: false) pending.removeAll { $0.id == post.id } error = nil - } catch APIError.refused(let message) { + } catch APIError.refused(let message), APIError.absent(let message) { // Refused, not lost: sending it again gets the same answer, so // it is never retried. Its words go back to an empty composer; // beside a draft, it waits to be edited (`reopen`), as a draft diff --git a/ios/Sources/FragmentAPI.swift b/ios/Sources/FragmentAPI.swift index 7b7eedf38..c5bdc927e 100644 --- a/ios/Sources/FragmentAPI.swift +++ b/ios/Sources/FragmentAPI.swift @@ -3,14 +3,15 @@ import Foundation enum APIError: LocalizedError { /// `refused`: the server answered 4xx (but a timeout or a rate limit), a /// final answer: the same request again gets it again. - case invalidResponse, limitExceeded, server(String), refused(String), unauthorized, keychain(Int32) + /// `absent`: a 404, which `answer` says apart only to `optional` readers. + case invalidResponse, limitExceeded, server(String), refused(String), absent(String), unauthorized, keychain(Int32) var errorDescription: String? { switch self { case .unauthorized: "This iPhone is not authorized. Connect your account again." case .keychain(let status): "The iPhone key could not be accessed (Keychain \(status))." case .invalidResponse: "Finite sent an unreadable response." case .limitExceeded: "This response is larger than the app can display." - case .server(let message), .refused(let message): message + case .server(let message), .refused(let message), .absent(let message): message } } /// A non-2xx answer as an error, with the server's own message. @@ -95,11 +96,21 @@ struct FragmentAPI: Sendable { } let (data, response) = try await self.data(for: request) guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } - guard (200..<300).contains(http.statusCode) else { throw APIError.answer(data, status: http.statusCode) } + guard (200..<300).contains(http.statusCode) else { + let refusal = APIError.answer(data, status: http.statusCode) + if http.statusCode == 404, case .refused(let message) = refusal { throw APIError.absent(message) } + throw refusal + } guard data.count <= 4_194_304 else { throw APIError.limitExceeded } return try JSONDecoder().decode(T.self, from: data) } + /// A read of what may be absent: `nil` for a 404, and else as `request`. + func optional(_ type: T.Type, _ path: String, query: [URLQueryItem] = []) async throws -> T? { + do { return try await request(path, query: query) as T } + catch APIError.absent { return nil } + } + func fragments() async throws -> [FragmentSummary] { struct Response: Decodable { let fragments: [FragmentSummary] } let response: Response = try await request("api/fragments") @@ -164,10 +175,12 @@ struct FragmentAPI: Sendable { return result.record } - func history(_ fragment: String, before: Int64) async throws -> [ChannelRecord] { + /// A page of a channel's records just below `before` (at most + /// `CHANNEL_PAGE`, 1000, and a MiB: docs/api.md). + func history(_ fragment: String, channel: String = "chat", before: Int64, limit: Int = 50) async throws -> [ChannelRecord] { struct Page: Decodable { let records: [ChannelRecord] } - var components = URLComponents(url: url("api/f/\(fragment)/channels/chat"), resolvingAgainstBaseURL: false)! - components.queryItems = [URLQueryItem(name: "before", value: String(before)), URLQueryItem(name: "limit", value: "50")] + var components = URLComponents(url: url("api/f/\(fragment)/channels/\(channel)"), resolvingAgainstBaseURL: false)! + components.queryItems = [URLQueryItem(name: "before", value: String(before)), URLQueryItem(name: "limit", value: String(limit))] let (data, response) = try await self.data(for: URLRequest(url: components.url!)) guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse } guard http.statusCode == 200 else { throw APIError.answer(data, status: http.statusCode) } diff --git a/ios/Sources/FragmentApp.swift b/ios/Sources/FragmentApp.swift index bc902c305..9109f0a95 100644 --- a/ios/Sources/FragmentApp.swift +++ b/ios/Sources/FragmentApp.swift @@ -49,18 +49,30 @@ final class Library { for index in fragments.indices { if let picture = fragments[index].picture { fragments[index].picture = URL(string: picture, relativeTo: api.baseURL)?.absoluteString } } - if let archive = fragments.first(where: { $0.kind == "sessions" && $0.role == "owner" }) { - struct Index: Decodable { let sessions: [ImportedSession] } - let index: Index = try await api.request("api/f/\(archive.name)/file", query: [URLQueryItem(name: "path", value: "index/chat.json")]) - guard loadID == requestID, !Task.isCancelled else { return } - imported = Array(index.sessions.prefix(2_000)) - } else { imported = [] } for agent in agents.prefix(32) where identities[agent.name] == nil { let status: FragmentStatus = try await api.request("api/f/\(agent.name)/status") identities[agent.name] = status.npub } if loadID == requestID { error = nil } } catch { if loadID == requestID && !Task.isCancelled { self.error = error.localizedDescription } } + await loadImported(api, requestID) + } + /// The person's imported chats: their session archive's `index/chat.json` + /// (templates/sessions/README.md), apart from the rest of the list. An + /// archive of other kinds alone (cron, subagents) has no such index: + /// none, as the web's History reads it. A read that fails keeps what was + /// read before, and nothing else waits on it. + private func loadImported(_ api: FragmentAPI, _ requestID: UUID) async { + struct Index: Decodable { let sessions: [ImportedSession] } + guard let archive = fragments.first(where: { $0.kind == "sessions" && $0.role == "owner" }) else { + if loadID == requestID { imported = [] } + return + } + let index: Index? + do { index = try await api.optional(Index.self, "api/f/\(archive.name)/file", query: [URLQueryItem(name: "path", value: "index/chat.json")]) } + catch { return } + guard loadID == requestID, !Task.isCancelled else { return } + imported = Array((index?.sessions ?? []).prefix(2_000)) } func watch(_ api: FragmentAPI) async { var attempt = 0 diff --git a/ios/Sources/FragmentWebSheet.swift b/ios/Sources/FragmentWebSheet.swift index 8f11eb6c1..4eb79a440 100644 --- a/ios/Sources/FragmentWebSheet.swift +++ b/ios/Sources/FragmentWebSheet.swift @@ -1,49 +1,23 @@ import SwiftUI import WebKit -struct FragmentWebSheet: View { - @Environment(\.dismiss) private var dismiss +/// What a web sheet shows: a fragment's one-use `__signin` URL, asked of +/// `POST /api/f/{name}/session` (docs/ios.md), or why not. A redemption is +/// spent once its page loads: each load drops the last before asking for a +/// new one, so no web view ever opens a spent one. +@MainActor @Observable +final class WebSheetModel { let fragment: FragmentSummary let api: FragmentAPI - @State private var url: URL? - @State private var error: String? - @State private var loading = true - @State private var needsConsent = false - @State private var consentEmail: String? - - var body: some View { - NavigationStack { - Group { - if needsConsent { - ContentUnavailableView { - Label("Open \(fragment.displayTitle)?", systemImage: "person.crop.circle.badge.checkmark") - } description: { - Text("Continue as \(consentEmail ?? "your account")? This app will be able to recognize you.") - } actions: { - Button("Continue") { Task { await load(consent: true) } } - Button("Cancel", role: .cancel) { dismiss() } - } - } else if let error { - ContentUnavailableView { - Label("Couldn’t open this app", systemImage: "wifi.exclamationmark") - } description: { Text(error) } actions: { - Button("Try again") { Task { await load() } } - } - } else if let url { - WebContent(url: url, loading: $loading, error: $error) - .overlay { if loading { ProgressView() } } - } else { ProgressView() } - } - .navigationTitle(fragment.displayTitle).navigationBarTitleDisplayMode(.inline) - .task { await load() } - } - .presentationDetents([.large]) - .presentationDragIndicator(.visible) - .accessibilityAction(.escape) { dismiss() } - } + private(set) var url: URL? + var error: String? + var loading = true + private(set) var needsConsent = false + private(set) var consentEmail: String? + init(fragment: FragmentSummary, api: FragmentAPI) { self.fragment = fragment; self.api = api } - private func load(consent: Bool = false) async { - error = nil; needsConsent = false + func load(consent: Bool = false) async { + url = nil; error = nil; needsConsent = false loading = true do { struct Status: Decodable { struct URLs: Decodable { let canonical: URL }; let urls: URLs } @@ -71,6 +45,50 @@ struct FragmentWebSheet: View { } } +struct FragmentWebSheet: View { + @Environment(\.dismiss) private var dismiss + let fragment: FragmentSummary + @State private var model: WebSheetModel + init(fragment: FragmentSummary, api: FragmentAPI) { + self.fragment = fragment + _model = State(initialValue: WebSheetModel(fragment: fragment, api: api)) + } + + var body: some View { + @Bindable var model = model + NavigationStack { + Group { + if model.needsConsent { + ContentUnavailableView { + Label("Open \(fragment.displayTitle)?", systemImage: "person.crop.circle.badge.checkmark") + } description: { + Text("Continue as \(model.consentEmail ?? "your account")? This app will be able to recognize you.") + } actions: { + Button("Continue") { Task { await model.load(consent: true) } } + Button("Cancel", role: .cancel) { dismiss() } + } + } else if let error = model.error { + ContentUnavailableView { + Label("Couldn’t open this app", systemImage: "wifi.exclamationmark") + } description: { Text(error) } actions: { + Button("Try again") { Task { await model.load() } } + } + } else if let url = model.url { + // a new redemption is a new web view: one never reloads a spent one + WebContent(url: url, loading: $model.loading, error: $model.error) + .id(url) + .overlay { if model.loading { ProgressView() } } + } else { ProgressView() } + } + .navigationTitle(fragment.displayTitle).navigationBarTitleDisplayMode(.inline) + .task { await model.load() } + } + .presentationDetents([.large]) + .presentationDragIndicator(.visible) + .accessibilityAction(.escape) { dismiss() } + } +} + private struct WebContent: UIViewRepresentable { let url: URL @Binding var loading: Bool diff --git a/ios/Sources/Models.swift b/ios/Sources/Models.swift index 4a26e7c4f..b42672374 100644 --- a/ios/Sources/Models.swift +++ b/ios/Sources/Models.swift @@ -109,14 +109,56 @@ struct ReplyDraft: Identifiable, Equatable { var id: String { turn } } +/// A turn as its `work` records say it (docs/chat-records.md): begun by the +/// agent whose `turn.start` began it, and over at its `turn.end`. +struct TurnState: Equatable, Sendable { + var startedBy: String? + var startedAt: Int64 = 0 + var ended = false +} + struct Timeline { private(set) var records: [ChannelRecord] = [] private(set) var drafts: [String: ReplyDraft] = [:] private(set) var cursors: [String: Int64] = [:] + /// Each turn seen, kept apart from the record window, which a long + /// turn's steps outgrow: whether a turn runs (and Stop shows) never + /// depends on how much of it is on screen. + private(set) var turns: [String: TurnState] = [:] + /// The oldest `work` record seen, where reading earlier work goes on from. + private(set) var oldestWork: Int64? // Bound the in-memory window independently of the server's retention. static let maximumRecords = 2_000 + static let maximumTurns = 512 + + /// What a `work` record says of its turn, wherever it falls: in the + /// window, or read back only for this (`ChatSession`'s earlier work). + mutating func learn(_ record: ChannelRecord) { + guard record.channel == "work" else { return } + oldestWork = min(oldestWork ?? record.seq, record.seq) + guard let turn = record.turn else { return } + var state = turns[turn] ?? TurnState() + switch record.body["kind"].string { + case "turn.start" where state.startedBy == nil: + state.startedBy = record.principal; state.startedAt = record.at + // its later records count only from the agent that began it + case "turn.end" where state.startedBy == nil || state.startedBy == record.principal: + state.ended = true + default: break + } + turns[turn] = state + if turns.count > Self.maximumTurns { + let gone = turns.filter { $0.value.ended }.map(\.key) + turns.filter { $0.value.startedBy == nil }.map(\.key) + for key in gone.prefix(turns.count - Self.maximumTurns) where key != turn { turns.removeValue(forKey: key) } + } + } + + /// The turns the window shows work of, neither begun nor ended in it: + /// earlier work to read back. + var unresolvedTurns: [String] { turns.filter { $0.value.startedBy == nil && !$0.value.ended }.map(\.key).sorted() } mutating func insert(_ record: ChannelRecord, advancingCursor: Bool = true) { + learn(record) if advancingCursor { cursors[record.channel] = max(cursors[record.channel] ?? 0, record.seq) } if !records.contains(where: { $0.id == record.id }) { records.append(record) @@ -160,17 +202,9 @@ struct Timeline { mutating func disconnected() { drafts.removeAll() } + /// The turns running: begun and not ended, the earliest first. var activeTurns: [String] { - var active: [String] = [] - for record in records where record.channel == "work" { - guard let turn = record.turn else { continue } - switch record.body["kind"].string { - case "turn.start": if !active.contains(turn) { active.append(turn) } - case "turn.end": active.removeAll { $0 == turn } - default: break - } - } - return active + turns.filter { $0.value.startedBy != nil && !$0.value.ended }.sorted { ($0.value.startedAt, $0.key) < ($1.value.startedAt, $1.key) }.map(\.key) } } diff --git a/ios/Sources/SettingsView.swift b/ios/Sources/SettingsView.swift index b6aa20f36..0e99bc1aa 100644 --- a/ios/Sources/SettingsView.swift +++ b/ios/Sources/SettingsView.swift @@ -104,7 +104,7 @@ private struct SettingsDetail: View { case .computer: if let computer = computer { Section("Your computer") { - LabeledContent("State", value: computer["state"].string?.capitalized ?? "—") + LabeledContent("State", value: ComputerPhase.said(computer["phase"].string)) LabeledContent("Version", value: computer["version"].string ?? "—") Button("Restart computer", systemImage: "arrow.clockwise") { confirmRestart = true } } @@ -140,12 +140,7 @@ private struct SettingsDetail: View { Button("Copy prompt", systemImage: "doc.on.doc") { UIPasteboard.general.string = prompt } } case .data: - Section("Export") { - Text("Download your fragments, messages, app data, and your computer’s newest save.") - LabeledContent("Status", value: value["export"]["state"].string ?? "No export yet") - Button("Export everything", systemImage: "square.and.arrow.down") { Task { await export() } }.disabled(loading) - if let raw = value["export"]["url"].string, let url = URL(string: raw), ["http", "https"].contains(url.scheme) { ShareLink(item: url) { Text("Share export") } } - } + ExportSection(api: api) case .device: Section("Notifications") { LabeledContent("Reply notifications", value: notificationStatus) @@ -165,7 +160,6 @@ private struct SettingsDetail: View { case .billing: path = "api/seat" case .computer: path = "api/computers" case .connections: path = "api/connections" - case .data: path = "api/export" default: return } do { value = try await api.request(path) } catch { self.error = error.localizedDescription } @@ -175,13 +169,58 @@ private struct SettingsDetail: View { do { value = try await api.request("api/computers/\(id)/restart", method: "POST", body: .object(["generation": computer["generation"]])) } catch { self.error = error.localizedDescription } } - private func export() async { - loading = true; defer { loading = false } - do { let result: JSONValue = try await api.request("api/export", method: "POST", body: .object([:])); value = .object(["export": result]) } - catch { self.error = error.localizedDescription } - } private func notifications() async { do { let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]); notificationStatus = granted ? "Allowed" : "Disabled in iOS Settings" } catch { self.error = error.localizedDescription } } } + +/// The person's export (docs/api.md, Export): made on the platform, then +/// downloaded with this iPhone's own signature into a file of its own, and +/// shared from there. Its download is a path on the platform, never a link +/// anyone else could open. +private struct ExportSection: View { + let api: FragmentAPI + @State private var export: ExportView? + @State private var file: URL? + @State private var busy = false + @State private var error: String? + var body: some View { + Section("Export") { + Text("Download your fragments, messages, app data, and your computer’s newest save.") + LabeledContent("Status", value: export?.said ?? "No export yet") + if let export, export.state == "ready", export.download != nil { + if let file { ShareLink(item: file) { Label("Share export", systemImage: "square.and.arrow.up") } } + else { Button("Download export", systemImage: "arrow.down.circle") { Task { await download(export) } }.disabled(busy).accessibilityIdentifier("downloadExport") } + } else { + Button("Export everything", systemImage: "square.and.arrow.down") { Task { await start() } } + .disabled(busy || export?.state == "building").accessibilityIdentifier("startExport") + } + if busy { ProgressView() } + if let error { Text(error).font(.footnote).foregroundStyle(.secondary) } + } + .task { await refresh() } + .refreshable { await refresh() } + } + private func run(_ step: () async throws -> Void) async { + busy = true; error = nil; defer { busy = false } + do { try await step() } catch { self.error = error.localizedDescription } + } + private func refresh() async { await run { export = try await api.export() } } + private func start() async { await run { export = try await api.startExport(); file = nil } } + private func download(_ export: ExportView) async { await run { file = try await api.download(export) } } +} + +/// A computer's `phase` (`ComputerView`, docs/api.md), as Settings says it. +enum ComputerPhase { + static func said(_ phase: String?) -> String { + switch phase { + case "asleep": "Asleep" + case "starting": "Starting" + case "awake": "Awake" + case "sleeping": "Going to sleep" + case "wont_wake": "Won’t wake" + default: "—" + } + } +} diff --git a/ios/Tests/DraftTests.swift b/ios/Tests/DraftTests.swift index 66c83fdc6..08b4a53f7 100644 --- a/ios/Tests/DraftTests.swift +++ b/ios/Tests/DraftTests.swift @@ -28,15 +28,21 @@ struct DraftTests { let route = posts let stub = Stub { seen, before in guard seen.route == route else { return (404, "{}") } - // lost the first time, refused the second - return before.contains { $0.route == route } ? (409, #"{"error":"conflicting_body","message":"Not that one."}"#) : (503, #"{"message":"Try again."}"#) + // lost the first time, refused the second, taken after + switch before.filter({ $0.route == route }).count { + case 0: return (503, #"{"message":"Try again."}"#) + case 1: return (409, #"{"error":"conflicting_body","message":"Not that one."}"#) + default: return (200, #"{"record":\#(record("chat", 5, principal: "npub1me", body: "{}")),"replayed":false}"#) + } } let api = try stub.api() defer { try? ChatDraftStore.removeAccount(api: api) } let session = ChatSession(fragment: chat, api: api) + session.recipients = ["npub1first"] session.edit("First words") await session.send() let sent = try #require(session.pending.first) + session.recipients = ["npub1draft"] session.edit("A draft of more words") await session.retry(sent) // the draft stays as it was; the refused send waits, never retried @@ -45,16 +51,20 @@ struct DraftTests { session.flushDraft() let reopened = ChatSession(fragment: chat, api: api) - #expect(reopened.error == nil && reopened.composer == "A draft of more words") + #expect(reopened.error == nil && reopened.composer == "A draft of more words" && reopened.recipients == ["npub1draft"]) let refused = try #require(reopened.pending.first) #expect(refused.refused == "Not that one." && refused.text == "First words") await reopened.retry(refused) #expect(stub.seen.filter { $0.route == route }.count == 2, "a refused send is never sent again") reopened.reopen(refused) #expect(reopened.pending.count == 1 && reopened.composer == "A draft of more words", "a draft is never joined past a message's size") + #expect(reopened.recipients == ["npub1draft"], "the draft's addressees stay until the refused message is edited") reopened.edit("") reopened.reopen(refused) - #expect(reopened.pending.isEmpty && reopened.composer == "First words") + #expect(reopened.pending.isEmpty && reopened.composer == "First words" && reopened.recipients == ["npub1first"]) + await reopened.send() + let last = try #require(stub.seen.last { $0.route == route }) + #expect(last.json["body"]["text"].string == "First words" && last.json["body"]["to"] == .array([.string("npub1first")])) } @Test func aSavedDraftPastItsBoundsOrUnreadableNeverStopsTheChat() async throws { diff --git a/ios/Tests/ShellTests.swift b/ios/Tests/ShellTests.swift index 76e051c44..f265f9b14 100644 --- a/ios/Tests/ShellTests.swift +++ b/ios/Tests/ShellTests.swift @@ -44,6 +44,95 @@ struct ShellTests { #expect(stub.seen.first { $0.path == "/api/f/archive--s1/file" }?.query == ["path": "index/chat.json"]) } + /// The archive's index is optional: one of other kinds alone (cron, + /// subagents) has none, and one that fails to read stops nothing. + @Test func anArchiveWithoutChatsOrFailingNeverStopsAgents() async throws { + let failing = Stub { seen, before in + guard seen.route == "GET /api/f/archive--s1/file" else { return Self.platform(seen, before) } + // the first read finds no chats index; the next fails for a while + return before.contains { $0.route == seen.route } ? (503, #"{"error":"unavailable","message":"Try again."}"#) : (404, #"{"error":"not_found","message":"no file index/chat.json at main"}"#) + } + let api = try failing.api() + let library = Library() + await library.load(api) + #expect(library.error == nil && library.imported.isEmpty) + #expect(library.agent(for: "npub1juniper")?.name == "juniper--a2") + await library.load(api) + #expect(library.error == nil && library.imported.isEmpty) + #expect(library.agents(in: try #require(library.chats.first)).map(\.name) == ["moss--a1", "juniper--a2"]) + + // an index read once is kept through a failed read + let flaky = Stub { seen, before in + seen.route == "GET /api/f/archive--s1/file" && before.contains { $0.route == seen.route } ? (503, #"{"message":"Try again."}"#) : Self.platform(seen, before) + } + let kept = Library() + let flakyAPI = try flaky.api() + await kept.load(flakyAPI) + await kept.load(flakyAPI) + #expect(kept.imported.map(\.key) == ["imported-chat"] && kept.error == nil) + } + + @Test func anExportIsBuiltThenDownloadedSignedToAFile() async throws { + let id = "0123456789abcdef" + let stub = Stub { seen, before in + switch seen.route { + case "POST /api/export": (200, #"{"id":"\#(id)","state":"building","startedAt":1,"progress":{"done":0,"total":3,"bytes":0}}"#) + case "GET /api/export": + before.contains { $0.route == "GET /api/export" } + ? (200, #"{"export":{"id":"\#(id)","state":"ready","startedAt":1,"progress":{"done":3,"total":3,"bytes":11},"size":11,"readyAt":2,"expiresAt":3,"download":"/api/export/\#(id)/download"}}"#) + : (200, #"{"export":null}"#) + case "GET /api/export/\(id)/download": (200, "tar-archive") + default: (404, "{}") + } + } + let api = try stub.api() + #expect(try await api.export() == nil) + let building = try await api.startExport() + #expect(building.state == "building" && building.said == "Building (0 of 3)" && building.download == nil) + await #expect(throws: APIError.self) { _ = try await api.download(building) } + let ready = try #require(try await api.export()) + #expect(ready.state == "ready" && ready.download == "/api/export/\(id)/download") + let file = try await api.download(ready) + defer { try? FileManager.default.removeItem(at: file) } + #expect(try String(contentsOf: file, encoding: .utf8) == "tar-archive" && file.lastPathComponent == "fragment-export-\(id).tar") + let asked = try #require(stub.seen.last) + #expect(asked.route == "GET /api/export/\(id)/download" && asked.authorization?.hasPrefix("Nostr ") == true) + // a download path that is not this export's is never asked + var elsewhere = ready + elsewhere.download = "https://elsewhere.example/api/export/\(id)/download" + await #expect(throws: APIError.self) { _ = try await api.download(elsewhere) } + } + + @Test func aComputerSaysItsPhase() throws { + let listed = try JSONDecoder().decode(JSONValue.self, from: Data(#"{"computers":[{"computer":"c1","owner":"npub1me","image":"stub","phase":"wont_wake","generation":3,"agents":[]}],"support":null}"#.utf8)) + #expect(ComputerPhase.said(listed["computers"].array.first?["phase"].string) == "Won’t wake") + #expect(ComputerPhase.said("awake") == "Awake" && ComputerPhase.said(nil) == "—") + } + + /// A web sheet's spent redemption is never opened again: each load + /// drops it before asking for a new one, and a failed ask leaves none. + @Test func aWebSheetRetryNeverReusesASpentRedemption() async throws { + let stub = Stub { seen, before in + switch seen.route { + case "GET /api/f/reading--p1/status": return (200, #"{"npub":"npub1app","urls":{"canonical":"http://reading--p1.fragment.localhost:8790/"}}"#) + case "POST /api/f/reading--p1/session": + let n = before.filter { $0.route == seen.route }.count + if n == 1 { return (503, #"{"message":"Try again shortly."}"#) } + return (200, #"{"consentRequired":false,"url":"http://reading--p1.fragment.localhost:8790/__signin?token=t\#(n)"}"#) + default: return (404, "{}") + } + } + let model = WebSheetModel(fragment: FragmentSummary(name: "reading--p1", kind: "app"), api: try stub.api()) + await model.load() + #expect(model.url?.query == "token=t0" && model.error == nil) + // its page failed after the redemption was spent; Try again + model.error = "The app could not be opened (HTTP 502)." + await model.load() + #expect(model.url == nil && model.error == "Try again shortly.") + await model.load() + #expect(model.url?.query == "token=t2" && model.error == nil) + } + @Test func anAgentJoinsAChatAsAnEditorByItsNpub() async throws { let stub = Stub { seen, before in seen.route == "PUT /api/f/talk--c9/members/npub1moss" ? (200, #"{"principal":"npub1moss","role":"editor"}"#) : Self.platform(seen, before) diff --git a/ios/Tests/TransportTests.swift b/ios/Tests/TransportTests.swift index 1b865b9ae..909cca25c 100644 --- a/ios/Tests/TransportTests.swift +++ b/ios/Tests/TransportTests.swift @@ -82,7 +82,9 @@ struct TransportTests { first.push(hello) try await waitUntil("its subscriptions") { first.sent.count == 2 } #expect(first.sent.map { $0["channel"].string } == ["chat", "work"]) - #expect(first.sent.allSatisfy { $0["type"].string == "subscribe" && $0["last"] == .number(50) && $0["after"] == .null }) + // the chat's last messages, and a page of work (a long turn's steps, and its start) + #expect(first.sent.allSatisfy { $0["type"].string == "subscribe" && $0["after"] == .null }) + #expect(first.sent.map { $0["last"] } == [.number(50), .number(1000)]) for seq: Int64 in [1, 2] { first.push(frame("chat", seq, body: #"{"text":"m\#(seq)"}"#)) } first.push(#"{"type":"subscribed","channel":"chat","next":2,"more":false}"#) first.push(#"{"type":"subscribed","channel":"work","next":0,"more":false}"#) @@ -125,6 +127,45 @@ struct TransportTests { #expect(stop.json == .object(["id": .string("stop:t4"), "body": .object(["kind": .string("stop"), "turn": .string("t4")])])) } + /// A turn begun before the backlog (a start, then more steps than one + /// page holds) still runs, and Stop stops it, opening the chat and + /// opening it again: its start is read back from earlier work. + @Test func aBusyTurnKeepsItsStopPastTheBacklog() async throws { + let route = posts + // work 1 to 1100: turn t9's start, then its steps; the backlog is the last 1000 + let work: @Sendable (Int64) -> String = { seq in record("work", seq, body: seq == 1 ? #"{"kind":"turn.start","turn":"t9"}"# : #"{"kind":"turn.step","turn":"t9","text":"step \#(seq)"}"#) } + let stub = Stub { seen, _ in + switch seen.route { + case "GET /api/f/chat--abc123/channels/work": + let before = Int64(seen.query["before"] ?? "") ?? 0, limit = Int64(seen.query["limit"] ?? "") ?? 0 + let page = (max(1, before - limit).. Date: Wed, 4 Nov 2026 17:17:28 -0600 Subject: [PATCH 5/6] iOS: turns as the web's chat page reads them; exports followed and made again Review of #1028 at 987d9983: - Which turns run is the web chat page's logic (chat.js turnOf, control.js running), ported: a turn is its agent's and its id, a record counts only toward its own poster's turn, and a turn runs once its own start is read and until its end is, in either order. Another agent's end never stops it, and paging back never revives an ended turn: only turns both begun and ended are ever dropped (past 4096). - Earlier work is read back as the web's earlierWork reads it, to the oldest message shown, and also while a turn lacks its start, in batches of 50 (each answer within the app's 4 MiB, which the HTTP route's count-only pages do not promise), at most 1000 records a visit; a failed read says so. - An export is followed while it builds (every 2 s, as exports.js), the Form's pull to refresh reaches it, and Export again stays beside Download, dropping the old file. Co-Authored-By: Claude Opus 5.5 --- docs/ios.md | 17 +++++--- ios/Sources/AppSettingsAPI.swift | 44 ++++++++++++++++++++ ios/Sources/ChatSession.swift | 40 +++++++++++------- ios/Sources/Models.swift | 69 +++++++++++++++++++------------- ios/Sources/SettingsView.swift | 51 +++++++++++------------ ios/Tests/ShellTests.swift | 30 ++++++++++++++ ios/Tests/TimelineTests.swift | 52 ++++++++++++++++++++++++ ios/Tests/TransportTests.swift | 14 +++++-- 8 files changed, 239 insertions(+), 78 deletions(-) diff --git a/docs/ios.md b/docs/ios.md index 9e44694fc..3ce7c888d 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -149,13 +149,18 @@ phone's network: an attachment of at most 25 MiB, and an icon fitted inside 256×256 on the phone, as the server would fit it. JSON text subscriptions match the Worker’s live protocol. A chat reads its last -50 messages and a page of `work` (1000, `CHANNEL_PAGE`); which turns run is kept -apart from the shown records, and a turn begun before that page has its start -read back from earlier work (at most ten pages, as the web's chat page reads), so -Stop shows for a long turn however it was opened. An archive's chats index is +50 messages and a page of `work` (1000, `CHANNEL_PAGE`). Which turns run follows +the web's chat page (`templates/chat/site/chat.js`): a turn is its agent's and its +id, a record counts only toward its own poster's turn, and a turn runs once its +start is read and until its end is, in either order; this is kept apart from the +shown records. Earlier work is read back as the web reads it, to the oldest message +shown, and also while a turn was read without its start, in batches of 50 (each +answer within the app's 4 MiB) and at most 1000 records a visit, so Stop shows for +a long turn however the chat was opened. An archive's chats index is optional: an archive of other kinds alone has none, and a failed read never -stops the list or its agents. An export downloads with the phone's signature to -a file of its own, and is shared from there. Connected accounts follow the signed list watcher, discard +stops the list or its agents. An export is followed while it builds (every two +seconds, as the web's Settings), downloads with the phone's signature to a file of +its own, is shared from there, and can be made again beside it. Connected accounts follow the signed list watcher, discard stale list reloads, and mark visible, caught-up chats read. HTTP/WebSocket interruption recovery, independent cursors, Stop and history remain unchanged. Push delivery, approval-card controls, imported-chat continuation, and full Markdown tables are not implemented. Notification permission alone does not diff --git a/ios/Sources/AppSettingsAPI.swift b/ios/Sources/AppSettingsAPI.swift index 7968541d2..c4280a343 100644 --- a/ios/Sources/AppSettingsAPI.swift +++ b/ios/Sources/AppSettingsAPI.swift @@ -185,3 +185,47 @@ extension FragmentAPI { return file } } + +/// The person's export, as Settings follows it (cell/shell/exports.js): its +/// latest view, asked again every `every` while it builds, its archive once +/// downloaded, and a new one made at any time (`POST /api/export` replaces +/// the last, so the file downloaded from it goes). +@MainActor @Observable +final class ExportModel { + let api: FragmentAPI + let every: Duration + private(set) var export: ExportView? + private(set) var file: URL? + private(set) var busy = false + var error: String? + init(api: FragmentAPI, every: Duration = .seconds(2)) { self.api = api; self.every = every } + + var startLabel: String { + switch export?.state { + case "building": "Exporting…" + case "ready": "Export again" + default: "Export everything" + } + } + private func run(_ step: () async throws -> Void) async { + busy = true; error = nil; defer { busy = false } + do { try await step() } catch { self.error = error.localizedDescription } + } + func refresh() async { await run { export = try await api.export() } } + func start() async { + file = nil + await run { export = try await api.startExport() } + } + func download() async { + guard let export else { return } + await run { file = try await api.download(export) } + } + /// While it builds, its view again every `every`, until it is ready (or + /// failed, or gone), or the screen goes. + func follow() async { + while export?.state == "building" { + do { try await Task.sleep(for: every) } catch { return } + await refresh() + } + } +} diff --git a/ios/Sources/ChatSession.swift b/ios/Sources/ChatSession.swift index d29b09530..f61b32cc6 100644 --- a/ios/Sources/ChatSession.swift +++ b/ios/Sources/ChatSession.swift @@ -121,7 +121,7 @@ final class ChatSession { caughtUp = true if timeline.records.filter({ $0.channel == "chat" }).count < Self.chatLast { hasEarlier = false } } else if channel == "work" { - await readTurnsBack() + await earlierWork() } } } @@ -152,12 +152,16 @@ final class ChatSession { /// The backlog a chat first reads: its last messages, and a page of /// `work` (`CHANNEL_PAGE`), which holds a long turn's steps (up to 200 - /// calls and 200 steps) and, mostly, its start (`readTurnsBack`). + /// calls and 200 steps) and, mostly, its start (`earlierWork`). static let chatLast = 50 static let workPage = 1_000 - /// Pages of earlier `work` read back for turns begun before the backlog, - /// as the web's chat page reads (templates/chat `earlierWork`). - static let turnPagesMax = 10 + /// Earlier `work` is read in batches of this many: each record at most + /// `RECORD_BODY_MAX_BYTES` (64 KiB), so a batch stays within the 4 MiB + /// an answer may be here (`FragmentAPI.request`), which the HTTP route, + /// bounding its pages by count alone, does not promise. + static let workBatch = 50 + /// Earlier `work` read back at most, a visit. + static let earlierWorkMax = 1_000 private func subscribe(_ socket: LiveSocket, channel: String, after: Int64?) async throws { var body: [String: JSONValue] = ["type": .string("subscribe"), "channel": .string(channel)] @@ -167,16 +171,24 @@ final class ChatSession { try await socket.send(.string(String(decoding: data, as: UTF8.self))) } - /// Earlier `work`, read until each turn the backlog shows work of has its - /// start or its end, so one begun before it still shows running (and - /// Stop). Bounded by `turnPagesMax`; a failure leaves it for the next - /// visit. What it reads only says which turns run: none is shown. - private func readTurnsBack() async { - for _ in 0.. 1 else { return } - guard let page = try? await api.history(fragment.name, channel: "work", before: oldest, limit: Self.workPage) else { return } + /// Earlier `work`, read back as the web's chat page reads it + /// (templates/chat/site/chat.js `earlierWork`): until it reaches the + /// oldest message shown, and, here, also while a turn was read without + /// its start (a long turn's begun before the backlog), so it shows + /// running, and Stop, however the chat was opened. What it reads only + /// says which turns run; none of it is shown. Bounded by + /// `earlierWorkMax`; a failure leaves it for the next visit, and says so. + private func earlierWork() async { + var read = 0 + while read < Self.earlierWorkMax, let oldest = timeline.oldestWork, oldest.seq > 1 { + let shownFrom = timeline.records.first { $0.channel == "chat" }?.at + guard timeline.awaitsAStart || shownFrom.map({ oldest.at >= $0 }) == true else { return } + let page: [ChannelRecord] + do { page = try await api.history(fragment.name, channel: "work", before: oldest.seq, limit: Self.workBatch) } + catch { self.error = "Earlier progress could not be read: " + error.localizedDescription; return } for record in page { timeline.learn(record) } - if page.count < Self.workPage { return } + read += page.count + if page.count < Self.workBatch { return } } } diff --git a/ios/Sources/Models.swift b/ios/Sources/Models.swift index b42672374..ce64aaa52 100644 --- a/ios/Sources/Models.swift +++ b/ios/Sources/Models.swift @@ -109,53 +109,63 @@ struct ReplyDraft: Identifiable, Equatable { var id: String { turn } } -/// A turn as its `work` records say it (docs/chat-records.md): begun by the -/// agent whose `turn.start` began it, and over at its `turn.end`. +/// A turn by its agent and its id, as the web's chat page keys one +/// (templates/chat/site/chat.js `turnOf`): a `work` record counts toward +/// the turn of its id that its own poster began, so one agent's records +/// (an end among them) never touch another's turn (docs/chat-records.md). +struct TurnKey: Hashable, Sendable { + let agent: String + let id: String +} + +/// What a turn's records said: when its own `turn.start` was read, and +/// whether its `turn.end` was. It runs once started and until ended +/// (control.js `running`), in whichever order its records were read. struct TurnState: Equatable, Sendable { - var startedBy: String? - var startedAt: Int64 = 0 + var startAt: Int64? var ended = false + var running: Bool { startAt != nil && !ended } } struct Timeline { private(set) var records: [ChannelRecord] = [] private(set) var drafts: [String: ReplyDraft] = [:] private(set) var cursors: [String: Int64] = [:] - /// Each turn seen, kept apart from the record window, which a long + /// Each turn read, kept apart from the record window, which a long /// turn's steps outgrow: whether a turn runs (and Stop shows) never /// depends on how much of it is on screen. - private(set) var turns: [String: TurnState] = [:] - /// The oldest `work` record seen, where reading earlier work goes on from. - private(set) var oldestWork: Int64? + private(set) var turns: [TurnKey: TurnState] = [:] + /// The oldest `work` record read (its seq and time): where reading + /// earlier work goes on from, and how far back it reached. + private(set) var oldestWork: (seq: Int64, at: Int64)? // Bound the in-memory window independently of the server's retention. static let maximumRecords = 2_000 - static let maximumTurns = 512 + /// Turns kept, past which the oldest ones both begun and ended go: their + /// start was read, so no earlier page holds it to begin one again. + static let maximumTurns = 4_096 - /// What a `work` record says of its turn, wherever it falls: in the - /// window, or read back only for this (`ChatSession`'s earlier work). + /// What a `work` record says of its poster's turn, wherever it falls: + /// in the window, or read back for this alone (`ChatSession`). mutating func learn(_ record: ChannelRecord) { guard record.channel == "work" else { return } - oldestWork = min(oldestWork ?? record.seq, record.seq) - guard let turn = record.turn else { return } - var state = turns[turn] ?? TurnState() + if oldestWork.map({ record.seq < $0.seq }) ?? true { oldestWork = (record.seq, record.at) } + guard let id = record.turn else { return } + let key = TurnKey(agent: record.principal, id: id) + var state = turns[key] ?? TurnState() switch record.body["kind"].string { - case "turn.start" where state.startedBy == nil: - state.startedBy = record.principal; state.startedAt = record.at - // its later records count only from the agent that began it - case "turn.end" where state.startedBy == nil || state.startedBy == record.principal: - state.ended = true + case "turn.start": state.startAt = record.at + case "turn.end": state.ended = true default: break } - turns[turn] = state + turns[key] = state if turns.count > Self.maximumTurns { - let gone = turns.filter { $0.value.ended }.map(\.key) + turns.filter { $0.value.startedBy == nil }.map(\.key) - for key in gone.prefix(turns.count - Self.maximumTurns) where key != turn { turns.removeValue(forKey: key) } + let done = turns.filter { $0.value.startAt != nil && $0.value.ended && $0.key != key }.sorted { $0.value.startAt! < $1.value.startAt! } + for (gone, _) in done.prefix(turns.count - Self.maximumTurns) { turns.removeValue(forKey: gone) } } } - /// The turns the window shows work of, neither begun nor ended in it: - /// earlier work to read back. - var unresolvedTurns: [String] { turns.filter { $0.value.startedBy == nil && !$0.value.ended }.map(\.key).sorted() } + /// Whether a turn was read without its start: earlier work holds it. + var awaitsAStart: Bool { turns.values.contains { $0.startAt == nil } } mutating func insert(_ record: ChannelRecord, advancingCursor: Bool = true) { learn(record) @@ -202,9 +212,14 @@ struct Timeline { mutating func disconnected() { drafts.removeAll() } - /// The turns running: begun and not ended, the earliest first. + /// The turns running, the earliest begun first: each id once (a Stop + /// names a turn by its id). var activeTurns: [String] { - turns.filter { $0.value.startedBy != nil && !$0.value.ended }.sorted { ($0.value.startedAt, $0.key) < ($1.value.startedAt, $1.key) }.map(\.key) + var ids: [String] = [] + for (key, _) in turns.filter({ $0.value.running }).sorted(by: { ($0.value.startAt!, $0.key.id) < ($1.value.startAt!, $1.key.id) }) where !ids.contains(key.id) { + ids.append(key.id) + } + return ids } } diff --git a/ios/Sources/SettingsView.swift b/ios/Sources/SettingsView.swift index 0e99bc1aa..8fdb61ca2 100644 --- a/ios/Sources/SettingsView.swift +++ b/ios/Sources/SettingsView.swift @@ -71,6 +71,11 @@ private struct SettingsDetail: View { @State private var error: String? @State private var confirmRestart = false @State private var notificationStatus = "Not enabled" + @State private var exports: ExportModel + init(section: SettingsSection, library: Library, api: FragmentAPI, open: @escaping (FragmentSummary) -> Void) { + self.section = section; self.library = library; self.api = api; self.open = open + _exports = State(initialValue: ExportModel(api: api)) + } var body: some View { Form { if loading { ProgressView("Loading…") } @@ -140,7 +145,7 @@ private struct SettingsDetail: View { Button("Copy prompt", systemImage: "doc.on.doc") { UIPasteboard.general.string = prompt } } case .data: - ExportSection(api: api) + ExportSection(model: exports) case .device: Section("Notifications") { LabeledContent("Reply notifications", value: notificationStatus) @@ -160,6 +165,8 @@ private struct SettingsDetail: View { case .billing: path = "api/seat" case .computer: path = "api/computers" case .connections: path = "api/connections" + // the Form's pull to refresh asks the export again too + case .data: return await exports.refresh() default: return } do { value = try await api.request(path) } catch { self.error = error.localizedDescription } @@ -175,40 +182,28 @@ private struct SettingsDetail: View { } } -/// The person's export (docs/api.md, Export): made on the platform, then -/// downloaded with this iPhone's own signature into a file of its own, and -/// shared from there. Its download is a path on the platform, never a link -/// anyone else could open. +/// The person's export (docs/api.md, Export) as the web's Settings shows it +/// (cell/shell/exports.js): made on the platform, followed while it builds, +/// downloaded with this iPhone's own signature into a file of its own and +/// shared from there, and made again at any time. private struct ExportSection: View { - let api: FragmentAPI - @State private var export: ExportView? - @State private var file: URL? - @State private var busy = false - @State private var error: String? + let model: ExportModel var body: some View { Section("Export") { Text("Download your fragments, messages, app data, and your computer’s newest save.") - LabeledContent("Status", value: export?.said ?? "No export yet") - if let export, export.state == "ready", export.download != nil { - if let file { ShareLink(item: file) { Label("Share export", systemImage: "square.and.arrow.up") } } - else { Button("Download export", systemImage: "arrow.down.circle") { Task { await download(export) } }.disabled(busy).accessibilityIdentifier("downloadExport") } - } else { - Button("Export everything", systemImage: "square.and.arrow.down") { Task { await start() } } - .disabled(busy || export?.state == "building").accessibilityIdentifier("startExport") + LabeledContent("Status", value: model.export?.said ?? "No export yet") + if let export = model.export, export.state == "ready", export.download != nil { + if let file = model.file { ShareLink(item: file) { Label("Share export", systemImage: "square.and.arrow.up") } } + else { Button("Download export", systemImage: "arrow.down.circle") { Task { await model.download() } }.disabled(model.busy).accessibilityIdentifier("downloadExport") } } - if busy { ProgressView() } - if let error { Text(error).font(.footnote).foregroundStyle(.secondary) } + Button(model.startLabel, systemImage: "square.and.arrow.down") { Task { await model.start() } } + .disabled(model.busy || model.export?.state == "building").accessibilityIdentifier("startExport") + if model.busy { ProgressView() } + if let error = model.error { Text(error).font(.footnote).foregroundStyle(.secondary) } } - .task { await refresh() } - .refreshable { await refresh() } - } - private func run(_ step: () async throws -> Void) async { - busy = true; error = nil; defer { busy = false } - do { try await step() } catch { self.error = error.localizedDescription } + .task { await model.refresh() } + .task(id: model.export?.state == "building") { await model.follow() } } - private func refresh() async { await run { export = try await api.export() } } - private func start() async { await run { export = try await api.startExport(); file = nil } } - private func download(_ export: ExportView) async { await run { file = try await api.download(export) } } } /// A computer's `phase` (`ComputerView`, docs/api.md), as Settings says it. diff --git a/ios/Tests/ShellTests.swift b/ios/Tests/ShellTests.swift index f265f9b14..4a8981a49 100644 --- a/ios/Tests/ShellTests.swift +++ b/ios/Tests/ShellTests.swift @@ -103,6 +103,36 @@ struct ShellTests { await #expect(throws: APIError.self) { _ = try await api.download(elsewhere) } } + /// Settings follows a building export to ready on the same screen, then + /// downloads it; a new export stays at hand beside it and, made, drops + /// the file of the one it replaced (cell/shell/exports.js). + @Test func anExportIsFollowedToReadyAndCanBeMadeAgain() async throws { + let id = "0123456789abcdef" + let building = #"{"id":"\#(id)","state":"building","startedAt":1,"progress":{"done":1,"total":3,"bytes":5}}"# + let ready = #"{"id":"\#(id)","state":"ready","startedAt":1,"progress":{"done":3,"total":3,"bytes":11},"size":11,"readyAt":2,"expiresAt":3,"download":"/api/export/\#(id)/download"}"# + let stub = Stub { seen, before in + switch seen.route { + case "POST /api/export": return (200, building) + // ready on its third look, after the first export was asked for + case "GET /api/export": return (200, #"{"export":\#(before.filter { $0.route == seen.route }.count >= 2 ? ready : building)}"#) + case "GET /api/export/\(id)/download": return (200, "tar-archive") + default: return (404, "{}") + } + } + let model = ExportModel(api: try stub.api(), every: .milliseconds(10)) + await model.start() + #expect(model.export?.state == "building" && model.startLabel == "Exporting…") + await model.follow() + #expect(model.export?.state == "ready" && model.startLabel == "Export again" && model.error == nil) + await model.download() + let file = try #require(model.file) + defer { try? FileManager.default.removeItem(at: file) } + #expect(try String(contentsOf: file, encoding: .utf8) == "tar-archive") + await model.start() + #expect(model.export?.state == "building" && model.file == nil) + #expect(stub.seen.filter { $0.route == "POST /api/export" }.count == 2) + } + @Test func aComputerSaysItsPhase() throws { let listed = try JSONDecoder().decode(JSONValue.self, from: Data(#"{"computers":[{"computer":"c1","owner":"npub1me","image":"stub","phase":"wont_wake","generation":3,"agents":[]}],"support":null}"#.utf8)) #expect(ComputerPhase.said(listed["computers"].array.first?["phase"].string) == "Won’t wake") diff --git a/ios/Tests/TimelineTests.swift b/ios/Tests/TimelineTests.swift index 454c9c605..57cf15184 100644 --- a/ios/Tests/TimelineTests.swift +++ b/ios/Tests/TimelineTests.swift @@ -64,4 +64,56 @@ struct TimelineTests { #expect(timeline.activeTurns.isEmpty) #expect(timeline.drafts.isEmpty) } + + // ---- which turns run, as the web's chat page decides it (chat.js + // `turnOf`, control.js `running`), records read in any order ---- + private func work(_ seq: Int64, _ kind: String, _ turn: String, by agent: String = "npub1a") -> ChannelRecord { + ChannelRecord(channel: "work", seq: seq, at: seq * 10, principal: agent, kind: "message", body: .object(["kind": .string(kind), "turn": .string(turn)])) + } + + @Test func anEndBeforeItsStartCountsOnlyFromTheAgentThatBeganIt() { + // its own agent's end, read before its start (paging back): it ended + var same = Timeline() + same.learn(work(9, "turn.end", "t")) + #expect(same.awaitsAStart) + same.learn(work(1, "turn.start", "t")) + #expect(same.activeTurns.isEmpty && !same.awaitsAStart) + // another agent's end naming it: A's turn still runs once its start is read + var other = Timeline() + other.learn(work(9, "turn.end", "t", by: "npub1b")) + other.learn(work(5, "turn.step", "t")) + other.learn(work(1, "turn.start", "t")) + #expect(other.activeTurns == ["t"]) + other.learn(work(12, "turn.end", "t")) + #expect(other.activeTurns.isEmpty) + } + + /// Codex's probe: a 1000-record backlog that holds a boundary turn's end + /// (its start older), 499 whole turns and a busy turn's step, then the + /// page before it: the ended turn never runs again; the busy one does. + @Test func pagingBackNeverRevivesAnEndedTurn() { + var timeline = Timeline() + timeline.learn(work(1001, "turn.end", "boundary")) + for i in Int64(0)..<499 { + timeline.learn(work(1002 + 2 * i, "turn.start", "done\(i)")) + timeline.learn(work(1003 + 2 * i, "turn.end", "done\(i)")) + } + timeline.learn(work(2000, "turn.step", "busy")) + #expect(timeline.activeTurns.isEmpty && timeline.awaitsAStart) + for (seq, turn) in [(Int64(500), "boundary"), (Int64(990), "busy")] { timeline.learn(work(seq, "turn.start", turn)) } + #expect(timeline.activeTurns == ["busy"] && !timeline.awaitsAStart) + } + + @Test func pastItsBoundOnlyTurnsBegunAndEndedGo() { + var timeline = Timeline() + timeline.learn(work(1_000_000, "turn.end", "late")) + for i in Int64(0).. String = { seq in record("work", seq, body: seq == 1 ? #"{"kind":"turn.start","turn":"t9"}"# : #"{"kind":"turn.step","turn":"t9","text":"step \#(seq)"}"#) } + // work 1 to 1100: turn t9's start, then its steps; the backlog is the + // last 1000, and the 100 before it are large ones (about 60 KB each, + // within a record's 64 KiB): a page of them is 3 MB + let large = String(repeating: "x", count: 60_000) + let work: @Sendable (Int64) -> String = { seq in + record("work", seq, body: seq == 1 ? #"{"kind":"turn.start","turn":"t9"}"# : #"{"kind":"turn.step","turn":"t9","text":"\#(seq > 100 ? "step" : large)"}"#) + } let stub = Stub { seen, _ in switch seen.route { case "GET /api/f/chat--abc123/channels/work": @@ -158,7 +163,10 @@ struct TransportTests { socket.push(#"{"type":"subscribed","channel":"work","next":1100,"more":false}"#) socket.push(#"{"type":"subscribed","channel":"chat","next":0,"more":false}"#) try await waitUntil("the turn running") { session.timeline.activeTurns == ["t9"] } - #expect(stub.seen.contains { $0.route == "GET /api/f/chat--abc123/channels/work" && $0.query == ["before": "101", "limit": "1000"] }) + // read back in batches a response holds, until its start + let asked = stub.seen.filter { $0.route == "GET /api/f/chat--abc123/channels/work" }.suffix(2).map(\.query) + #expect(asked == [["before": "101", "limit": "50"], ["before": "51", "limit": "50"]]) + #expect(session.error == nil) await session.stop() #expect(stub.seen.last { $0.route == route }?.json["id"].string == "stop:t9") run.cancel() From 806c3627487a0664a60cf664aaf6acb5a31ba7fe Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 4 Nov 2026 17:44:07 -0600 Subject: [PATCH 6/6] iOS: an export's reads run in order, and its file goes with it Review of #1028 at c2a5c91d: - ExportModel runs reads and makes one after another, applying each answer in the order sent: an older read held in flight never lands after a newer export, and `busy` holds until every one has answered. The section's own first read is gone; the Form's is the one. - A downloaded archive is kept with its export's id: once the export shown is another (refreshed to one made on the web, or made again) it is deleted, and a download still in flight for one no longer shown is never kept. - The test stub answers each request on a queue of its own, so a test holding one answer (Gate) holds no other. Co-Authored-By: Claude Opus 5.5 --- docs/ios.md | 5 ++- ios/Sources/AppSettingsAPI.swift | 61 +++++++++++++++++++++++----- ios/Sources/SettingsView.swift | 2 +- ios/Tests/ShellTests.swift | 70 ++++++++++++++++++++++++++++++++ ios/Tests/Stub.swift | 32 +++++++++++---- 5 files changed, 150 insertions(+), 20 deletions(-) diff --git a/docs/ios.md b/docs/ios.md index 3ce7c888d..32c578b7a 100644 --- a/docs/ios.md +++ b/docs/ios.md @@ -160,7 +160,10 @@ a long turn however the chat was opened. An archive's chats index is optional: an archive of other kinds alone has none, and a failed read never stops the list or its agents. An export is followed while it builds (every two seconds, as the web's Settings), downloads with the phone's signature to a file of -its own, is shared from there, and can be made again beside it. Connected accounts follow the signed list watcher, discard +its own, is shared from there, and can be made again beside it. Its reads and makes +run one after another, so an older read never lands after a newer export; the file +is kept with its export, deleted once the export shown is another, and a download +still in flight for one no longer shown is never kept. Connected accounts follow the signed list watcher, discard stale list reloads, and mark visible, caught-up chats read. HTTP/WebSocket interruption recovery, independent cursors, Stop and history remain unchanged. Push delivery, approval-card controls, imported-chat continuation, and full Markdown tables are not implemented. Notification permission alone does not diff --git a/ios/Sources/AppSettingsAPI.swift b/ios/Sources/AppSettingsAPI.swift index c4280a343..03e628d6e 100644 --- a/ios/Sources/AppSettingsAPI.swift +++ b/ios/Sources/AppSettingsAPI.swift @@ -189,17 +189,25 @@ extension FragmentAPI { /// The person's export, as Settings follows it (cell/shell/exports.js): its /// latest view, asked again every `every` while it builds, its archive once /// downloaded, and a new one made at any time (`POST /api/export` replaces -/// the last, so the file downloaded from it goes). +/// the last). Asks and makes run one after another, each answer applied in +/// the order they were sent, so an older read never lands after a newer +/// one. A downloaded archive is kept with the export it is of: once the +/// export shown is another, it goes (deleted), and one still downloading +/// for an export no longer shown is never kept. @MainActor @Observable final class ExportModel { let api: FragmentAPI let every: Duration private(set) var export: ExportView? - private(set) var file: URL? - private(set) var busy = false + private var archive: (id: String, file: URL)? + private var pending = 0 + private var queue: Task? var error: String? init(api: FragmentAPI, every: Duration = .seconds(2)) { self.api = api; self.every = every } + /// The archive downloaded for the export shown, while it is ready. + var file: URL? { archive.flatMap { export?.state == "ready" && $0.id == export?.id ? $0.file : nil } } + var busy: Bool { pending > 0 } var startLabel: String { switch export?.state { case "building": "Exporting…" @@ -207,18 +215,49 @@ final class ExportModel { default: "Export everything" } } - private func run(_ step: () async throws -> Void) async { - busy = true; error = nil; defer { busy = false } - do { try await step() } catch { self.error = error.localizedDescription } + + /// `step` after every ask or make sent before it. + private func serially(_ step: @escaping @MainActor () async -> Void) async { + pending += 1 + let before = queue + let next = Task { @MainActor in + await before?.value + await step() + } + queue = next + await next.value + pending -= 1 + } + private func shown(_ view: ExportView?) { + export = view + if let kept = archive, kept.id != view?.id || view?.state != "ready" { drop(kept.file) } + } + private func drop(_ file: URL) { + try? FileManager.default.removeItem(at: file) + if archive?.file == file { archive = nil } + } + + func refresh() async { + await serially { [self] in + do { shown(try await api.export()); error = nil } catch { self.error = error.localizedDescription } + } } - func refresh() async { await run { export = try await api.export() } } func start() async { - file = nil - await run { export = try await api.startExport() } + await serially { [self] in + do { shown(try await api.startExport()); error = nil } catch { self.error = error.localizedDescription } + } } func download() async { - guard let export else { return } - await run { file = try await api.download(export) } + guard let export, export.state == "ready" else { return } + pending += 1; defer { pending -= 1 } + do { + let file = try await api.download(export) + // shown no longer (refreshed to another, or made again): never kept + guard self.export?.id == export.id, self.export?.state == "ready" else { return drop(file) } + if let old = archive?.file, old != file { drop(old) } + archive = (export.id, file) + error = nil + } catch { self.error = error.localizedDescription } } /// While it builds, its view again every `every`, until it is ready (or /// failed, or gone), or the screen goes. diff --git a/ios/Sources/SettingsView.swift b/ios/Sources/SettingsView.swift index 8fdb61ca2..fa7bf8c29 100644 --- a/ios/Sources/SettingsView.swift +++ b/ios/Sources/SettingsView.swift @@ -201,7 +201,7 @@ private struct ExportSection: View { if model.busy { ProgressView() } if let error = model.error { Text(error).font(.footnote).foregroundStyle(.secondary) } } - .task { await model.refresh() } + // its first read is the Form's (SettingsDetail `load`), as is a pull to refresh .task(id: model.export?.state == "building") { await model.follow() } } } diff --git a/ios/Tests/ShellTests.swift b/ios/Tests/ShellTests.swift index 4a8981a49..8f67cf0e3 100644 --- a/ios/Tests/ShellTests.swift +++ b/ios/Tests/ShellTests.swift @@ -133,6 +133,76 @@ struct ShellTests { #expect(stub.seen.filter { $0.route == "POST /api/export" }.count == 2) } + /// Asks and makes run one after another: an older read held in flight + /// never lands after the new export made behind it, and nothing is sent + /// before it answers. + @Test func anOlderExportReadNeverLandsAfterANewerExport() async throws { + let gate = Gate() + let building = #"{"id":"0123456789abcdef","state":"building","startedAt":1,"progress":{"done":0,"total":3,"bytes":0}}"# + let stub = Stub { seen, before in + switch seen.route { + case "GET /api/export": + gate.pass() + return (200, #"{"export":null}"#) + // a make sent while the older read is still held fails the test + case "POST /api/export": return gate.isOpen ? (200, building) : (500, #"{"message":"sent before the read answered"}"#) + default: return (404, "{}") + } + } + let model = ExportModel(api: try stub.api()) + let read = Task { await model.refresh() } + try await waitUntil("the read held") { stub.seen.contains { $0.route == "GET /api/export" } } + let make = Task { await model.start() } + await Task.yield() + #expect(model.busy && !stub.seen.contains { $0.route == "POST /api/export" }) + gate.open() + await read.value + await make.value + #expect(model.export?.state == "building" && model.error == nil && !model.busy) + #expect(stub.seen.map(\.route) == ["GET /api/export", "POST /api/export"]) + } + + /// A downloaded archive goes with its export: refreshed to another, + /// sharing needs that one downloaded, and a download still in flight + /// for one no longer shown is never kept. + @Test func aDownloadedArchiveGoesWithItsExport() async throws { + let gate = Gate() + let view: @Sendable (String) -> String = { id in #"{"id":"\#(id)","state":"ready","startedAt":1,"progress":{"done":1,"total":1,"bytes":1},"size":1,"download":"/api/export/\#(id)/download"}"# } + let (a, b) = ("aaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbb") + let stub = Stub { seen, before in + let reads = before.filter { $0.route == "GET /api/export" }.count + switch seen.route { + // A, then A again, then B (made on the web) + case "GET /api/export": return (200, #"{"export":\#(view(reads < 2 ? a : b))}"#) + case "GET /api/export/\(a)/download": + // its second download is held until B is shown + if before.contains(where: { $0.route == seen.route }) { gate.pass() } + return (200, "archive A") + case "GET /api/export/\(b)/download": return (200, "archive B") + default: return (404, "{}") + } + } + let model = ExportModel(api: try stub.api()) + await model.refresh() + await model.download() + let first = try #require(model.file) + #expect(try String(contentsOf: first, encoding: .utf8) == "archive A") + + // A's download again, held; meanwhile the export shown becomes B + await model.refresh() + let held = Task { await model.download() } + try await waitUntil("A's download held") { stub.seen.filter { $0.route == "GET /api/export/\(a)/download" }.count == 2 } + await model.refresh() + #expect(model.export?.id == b && model.file == nil && !FileManager.default.fileExists(atPath: first.path)) + gate.open() + await held.value + #expect(model.file == nil, "a download for an export no longer shown is never kept") + await model.download() + let second = try #require(model.file) + defer { try? FileManager.default.removeItem(at: second) } + #expect(try String(contentsOf: second, encoding: .utf8) == "archive B") + } + @Test func aComputerSaysItsPhase() throws { let listed = try JSONDecoder().decode(JSONValue.self, from: Data(#"{"computers":[{"computer":"c1","owner":"npub1me","image":"stub","phase":"wont_wake","generation":3,"agents":[]}],"support":null}"#.utf8)) #expect(ComputerPhase.said(listed["computers"].array.first?["phase"].string) == "Won’t wake") diff --git a/ios/Tests/Stub.swift b/ios/Tests/Stub.swift index 1523b8687..c42226eb7 100644 --- a/ios/Tests/Stub.swift +++ b/ios/Tests/Stub.swift @@ -53,7 +53,11 @@ final class Stub: Sendable { } } -final class StubProtocol: URLProtocol { +/// Each request answered on a queue of its own, never on the thread URLSession +/// loads every stubbed request on: a test that holds one answer (`Gate`) +/// holds no other. +final class StubProtocol: URLProtocol, @unchecked Sendable { + private let stopped = Mutex(false) override class func canInit(with request: URLRequest) -> Bool { request.url?.host.flatMap(Stub.named) != nil } override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } override func startLoading() { @@ -64,13 +68,16 @@ final class StubProtocol: URLProtocol { for item in URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? [] { query[item.name] = item.value ?? "" } let seen = Seen(method: request.httpMethod ?? "GET", path: url.path, query: query, body: request.httpBody ?? Self.read(request.httpBodyStream), authorization: request.value(forHTTPHeaderField: "Authorization")) - let (status, body) = stub.answer(seen) - let response = HTTPURLResponse(url: url, statusCode: status, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! - client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) - client?.urlProtocol(self, didLoad: Data(body.utf8)) - client?.urlProtocolDidFinishLoading(self) + DispatchQueue.global().async { [self] in + let (status, body) = stub.answer(seen) + guard !stopped.withLock({ $0 }) else { return } + let response = HTTPURLResponse(url: url, statusCode: status, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: Data(body.utf8)) + client?.urlProtocolDidFinishLoading(self) + } } - override func stopLoading() {} + override func stopLoading() { stopped.withLock { $0 = true } } private static func read(_ stream: InputStream?) -> Data { guard let stream else { return Data() } stream.open(); defer { stream.close() } @@ -147,3 +154,14 @@ struct StubTimeout: Error, CustomStringConvertible { let what: String; var descr func record(_ channel: String, _ seq: Int64, principal: String = "npub1agent", body: String) -> String { #"{"channel":"\#(channel)","seq":\#(seq),"at":\#(seq * 1000),"principal":"\#(principal)","kind":"message","body":\#(body)}"# } + +/// Holds the stub's answers it is asked in, until opened: a test's way to +/// keep one request in flight while others are made. +final class Gate: Sendable { + private let opened = DispatchSemaphore(value: 0) + private let state = Mutex(false) + var isOpen: Bool { state.withLock { $0 } } + /// On the stub's own thread: waits until the test opens it. + func pass() { if !isOpen { opened.wait(); opened.signal() } } + func open() { state.withLock { $0 = true }; opened.signal() } +}