# aesel for Mac and iPhone `Aesel` (iOS 17+) and `AeselMac` (macOS 14+) compile the same SwiftUI sources. `ApplePlatform.swift` adapts WebKit hosting and native image drawing to UIKit or AppKit. Both run the bundled JavaScript session, notebook renderer, and AC piece preview; the Mac target does not use Electron or start a Node process. The live WebKit preview stays in the upper-right corner, including for new pieces and thread changes, and can expand to fill the workspace. The animated title and version share a fixed top strip with the Mac window controls. A blank notebook starts editing on its first ruled line; the remaining paper accepts clicks. Letter animation never changes the title's hit area. Click the version to view providers, the reported model, the 🧠 balance and its daily/purchased breakdown, purchases, and saved versions. Account costs stay inside this panel rather than occupying a row above the notebook. On Mac, Command-N opens a separate notebook window. Each window keeps its own session and draft; saved threads share one store and merge per-thread updates. The notebook routes vertical trackpad events to its native sheet for momentum and edge bounce. Selecting a saved version shows its source and transcript checkpoint read-only; return to the current version to write. Settings refreshes provider availability automatically. Connected MCP tool confirmations are allowed by default, without a Settings checkbox. If the helper is configured to prompt, confirmations offer Deny, Allow once, and Always allow. Forms needing answers and sign-in still require input; shell and file permissions keep their separate approval choices. Appearance follows the system automatically. Native controls and the notebook share the system palette; enabled Mac buttons use a pointing hand. The [experience model](EXPERIENCE.md) defines draft, sign-in, generation, and publishing behavior and the mandatory AC login-and-handle gate. The [integration roadmap](ROADMAP.md) inventories the remaining ports and their acceptance gates, including providers, paired hosts, media, files, and releases. The native provider dropdown lists Aesthetic.Computer, Claude, and Codex. Codex uses the OpenAI knot mark; provider artwork comes from the [attributed provider assets](../../aesel/shared/assets/provider-marks.txt). Claude and Codex use the optional local helper, installed from the repo root with `node aesel/native/install.mjs`. Every provider requires a verified Aesthetic Computer login and an @handle. Account setup blocks workspace access until both are present. AC's model is Automatic. iPhone pairing to the helper is not implemented. Upload interruptions retry twice, with a 20-second deadline per attempt, outside notebook history. After retries, a temporary notice offers Retry; source remains saved locally. AC inference has a 45-second connection deadline and a 60-second idle-stream deadline, without replaying paid requests. A speaker beside the title opens volume when the preview produces audio; mute remains reachable through silence. The sound popover's 30–240 BPM control scales the preview's shared `clock.time()` from the default 120 BPM without restarting the piece. Clock-driven sequences and visuals follow together. Sync restarts the preview on the network clock's epoch grid, so a backwards clock reset cannot strand a piece's beat counter. JavaScript wall-clock timers and audio pitch remain unchanged. Native CLI sessions use the shared piece-first, responsive-layout, network-clock, sound-design and reply instructions, plus the bundled AC guides. Each turn includes a fresh preview image when available. `ac_frame` and `ac_preview` inspect only the matching native thread; unavailable captures are reported explicitly. Native observations distinguish snapshot size from drawable canvas size. Updated runtimes acknowledge the source SHA-256, thread, and revision after drawing its frame; `renderedRevisionVerified` reports whether that evidence matches the capture. Up to 100 worker-console events are retained, each bounded to 2,000 characters. Older runtimes remain unverified; no claim should exceed the evidence. ```sh ./run.sh mac # build and open Aesel.app ``` To measure a built debug app without inference, run from the repository root: ```sh python3 apple/aesel/bin/bench-native.py --app /path/to/Debug/Aesel.app --runs 3 --out tmp/native-speed.json ``` This launches the empty notebook fixture and measures automation readiness, the first state query, and verified composer edits. It refuses an existing draft and clears its test input. Control round trips include the 150 ms mailbox timer; they do not measure physical keystroke-to-pixel latency. The app fingerprint is computed once, off the UI thread, when automation first requests it. Compare provider inference separately with `aesel/bin/bench-providers.mjs --live`. The native Mac target currently supports the shared Piece workflow: AC sign-in, hosted inference, drafts, publishing, notebook, saved threads and braincells. App Store builds use the existing identity `computer.aesthetic.easel`. Direct and debug builds keep `computer.aesthetic.aesel.native` so local development notebooks and sign-in survive the app rename. Electron Aesel is retired; the native app is the only desktop runtime. Existing Electron data is retained separately and is not automatically imported. Mac sessions live in the app's Application Support directory; iPhone sessions retain their existing Documents location. Tokens use Keychain. ## Release: app and terminal together `./release-mac.sh` builds the Developer ID DMG: `AeselMac` unsandboxed (`MacDirect.entitlements`), the Easel TUI in `Contents/Resources/easel`, and Node from nodejs.org (checksum-verified) in `Contents/Helpers`. On launch the app links `~/.local/bin/aesel` (opens the app) and `aes` (the TUI), leaving any existing checkout link alone, and installs the Claude/Codex host once per version. App and TUI share `aesel/package.json`'s version; the build number is the commit count. `./release-mac.sh publish` uploads to `releases.aesthetic.computer/aesel/mac/` and packs the matching TUI tarball. The App Store build stays sandboxed (`Mac.entitlements`) and carries neither. To build both targets without installing, run `./bundle-session.sh` and `xcodegen generate`, then build schemes `AeselMac` (macOS) and `Aesel` (`generic/platform=iOS`, with signing disabled for a compile check). The editor focus regression and preview transitions have standalone native checks: ```sh xcrun swiftc -parse-as-library Sources/AeselComposer.swift Tests/ComposerFocusChecks.swift -o /tmp/aesel-composer-check /tmp/aesel-composer-check xcrun swiftc Sources/Session.swift Tests/SessionPreviewChecks.swift -o /tmp/aesel-preview-check /tmp/aesel-preview-check ``` Native SwiftUI chrome uses the desktop palette, typeface, mascot scene and slash commands. A hidden WKWebView runs the shared JavaScript AC agent; a separate WKWebView opens the signed-in user's published piece. Unpublished edits can preview through AC's JavaScript `dropped:piece` interface. The runtime and model still need an internet connection. The notebook bundles the shared renderer's sanitized rich-reply renderer: ruled pages, Markdown, highlighted code, math, diagrams and color swatches. Tap the piece title to open its published URL, or the eye to hide/show the preview. The account's daily and purchased braincells refresh after turns and on foreground; tap the balance to retry. Desktop source-value editing, version scrubbing and hover interactions are not yet available on iPhone. ```sh ./run.sh # simulator ./run.sh device # USB-connected iPhone ``` The script copies `aesel/{src,context,phone}` and the shared notebook renderer into the app, generates the Xcode project and builds with two compiler jobs. It starts no local HTTP server and shares no laptop credentials. `./bundle-session.sh` refreshes these resources before a manual Xcode build. Keep the iPhone unlocked for installation/launch; `DEVICE=` selects a device. `AESEL_HOST` is an optional explicit development host override; normal launches use the bundled custom scheme with no listener. ## AC sign-in `/login` opens the AC Auth0 PKCE flow in the login sheet. The app accepts only the registered callback with the matching one-time state, then exchanges the code natively. Access and refresh credentials go into device-only Keychain; draft source goes into Documents. Old prototype tokens borrowed from the laptop are discarded on upgrade. `/logout` clears aesel's token and web login data. This signs into the same AC account. It does **not** share another iOS app's cookies or Keychain. A retained web login can renew the token when `/login` is opened again; native PKCE sign-in now requests offline access and keeps rotating refresh credentials in device-only Keychain. Windows share one refresh operation; sign-out fences late responses. Older grants without refresh credentials require sign-in again. Account verification may retry; paid turns and uploads are not replayed. Browser SSO remains separate work. A new native OAuth callback must be registered before adopting ASWebAuthenticationSession. No unregistered callback is assumed here. The account needs an AC handle to publish; missing handles produce a linkable instruction. Generated writes retain the existing automatic publishing behavior; `/publish` retries explicitly, and `/open` is available after publication. Draft preview is separate from the public sharing URL. ## Release gates The bundle identity remains `computer.aesthetic.easel`, matching macOS's existing App Store record; the visible name is `aesel`. The icon is opaque at 1024 pixels. An iOS platform can be added to that record; phone functionality, sign-in, preview, model errors and background/foreground behavior need device acceptance before uploading. App Review approval is not guaranteed by a successful build. The JS bridge signals `ready` after module evaluation, not on `didFinish`. Publishing strips Node's forbidden `User-Agent` header for WebKit. Bundle guide text is injected at startup so it does not depend on custom-scheme `fetch`. ## Threads and media The native home screen resumes local threads or starts a Piece. Each saved thread retains its source, transcript, publication owner and AC model conversation; account tokens are excluded. The previous single draft migrates on first launch. Thread switching waits for an interrupted turn and any upload before changing source. Returning home leaves the current preview alive. Picture, Sound, Paper and Game Boy remain terminal-only until their actual render and tool backends are ported; their chooser rows do not create pretend sessions. The editor keeps only the piece title, preview, notebook and message input visible. The version button opens account, status, balance and thread controls. Braincell models are automatic, including resumed threads with old manual choices. Dollar values come from the allowance endpoint and distinguish free from purchased credit. Account character colors come from AC's saved palette, including the `@` character, with the same fallback palette as desktop. ## MCP and visual acceptance Slab imports each native window’s private `slab/windows/*.json` status export from the app container. It contains window identity, measured title-strip geometry, piece name/revision and lifecycle state, with no prompts, source, transcripts or credentials. Closed processes and exports older than 30 seconds are removed. The app works without Slab; sandbox entitlements are unchanged. The native app exposes a private same-user automation mailbox. The monorepo adapter and Aesthetic Eye workflow live at `slab/bin/aesel-mcp.mjs`, `slab/bin/aesel-eye.mjs` and `slab/AESEL-EYE.md`. Tests inspect and act through stable UI control IDs without activating the window. Preview URLs use the `nogap`, `nolabel` and `autoreload` contract; the top strip shows the persisted piece revision starting at v0. ## Historical native beta 3 — 22 September 2026 The Blueberry-installed native app identifies `aesel-provider-readiness` in its debug symbols; the matching source baseline is `eb8a7aa275`. Blueberry main (`547e19b517`) predates the native title-bar work and is not the build source. Beta 3 retains that title-bar implementation, matches version/title type sizes, and adds AppKit cursor tracking. Build `AeselMac` in Release with Developer ID signing. Before packaging, sign the app explicitly with `--options runtime --timestamp --entitlements Mac.entitlements` to omit Xcode's development `get-task-allow` entitlement. Sign the DMG with a secure timestamp, submit it to `notarytool`, require `Accepted`, and staple and validate the DMG before uploading. No credentials belong in the release or repository. ## Direct Mac release `release-mac.sh` builds the signed, notarized DMG with the terminal and Node included. `aesel` opens the native app; `a` and `aes` open the terminal. All providers require verified AC login and an @handle. The direct build keeps `computer.aesthetic.aesel.native` so existing notebooks and Keychain entries remain available; the App Store target retains its own identity and sandbox. Build from current upstream source. The 0.8.1 recovery includes Frisbee’s account curtain, embedded login/signup panel, and GUI/TUI gates preserved in `f6574e12cc`; 0.8.0 was built from the older Neo checkout without those changes. `release-mac.sh publish` uploads the DMG and feed and prepares the terminal archive; deploy the archive, manifest, installers and website separately.