| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Harden external TestFlight review retries | 3 个月前 | |
Add internal TestFlight group support with auto-sync to main (#8180) - Create new assign-internal-group job in ios-testflight.yml workflow - Add asc_assign_internal_testflight_group.py for internal group assignment - Change display name for scheduled builds to 'cmux INTERNAL' - Remove --external flag from scheduled path (internal builds only) - Scheduled iOS changes auto-sync to internal group for team dogfooding - External builds remain gated and manual (via workflow_dispatch override) This enables internal testers to dogfood every iOS change on main automatically, while keeping external releases intentional and reviewed. Co-authored-by: cmux reload-cloud <cmux-reload-cloud@users.noreply.github.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> | 2 个月前 | |
fix: honor App Store Connect request timeout | 2 个月前 | |
Add internal TestFlight group support with auto-sync to main (#8180) - Create new assign-internal-group job in ios-testflight.yml workflow - Add asc_assign_internal_testflight_group.py for internal group assignment - Change display name for scheduled builds to 'cmux INTERNAL' - Remove --external flag from scheduled path (internal builds only) - Scheduled iOS changes auto-sync to internal group for team dogfooding - External builds remain gated and manual (via workflow_dispatch override) This enables internal testers to dogfood every iOS change on main automatically, while keeping external releases intentional and reviewed. Co-authored-by: cmux reload-cloud <cmux-reload-cloud@users.noreply.github.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> | 2 个月前 | |
Add demo variant to the iOS TestFlight lane (#8876) * Add demo variant to the iOS TestFlight lane Manual workflow_dispatch variant=demo ships the current main head to a separate cmux DEMO app (dev.cmux.app.demo) with a DEMO-badged app icon, isolated from the internal lane's per-app upload limit. The demo provisioning profile is fetched from the ASC API by name at build time (asc_download_profile.py) instead of a repository secret, the AppIcon PNGs are swapped in the CI checkout to avoid a workspace-wide ASSETCATALOG_COMPILER_APPICON_NAME override breaking SwiftPM resource bundles, and the assignment job targets the cmux DEMO internal group. Push-triggered internal uploads are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address review: surface openssl stderr, close temp fd on write failure Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
ci(ios): enforce monotonic TestFlight build numbers against App Store Connect (#5504) * ci(ios): enforce monotonic TestFlight build numbers against App Store Connect The build-number scheme regressing from 14 to 12 digits silently shipped builds with a CFBundleVersion below the existing max, so TestFlight never offered them as an update (it ranks the highest build number as "latest"). Restoring the scheme (#5499, #5503) fixed the symptom but nothing enforced the actual invariant, and a bad manual --build-number would reproduce it. Add a behavioral guard: before archiving, asc_max_build.py asks App Store Connect for the current max integer CFBundleVersion (mints an ES256 JWT, resolves the app by bundle id, pages builds and maxes as int because ASC sort=-version is a string sort). upload-testflight.sh self-heals BUILD_NUMBER up to max+1 when it would not be the highest, with a loud warning. Fail-open by design: any ASC/network/JWT error (or missing `cryptography`) logs a warning and keeps the timestamp build number, so a transient API hiccup never blocks a publish. The workflow best-effort installs `cryptography` for the guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): address autoreview on build-number guard - Reused archives (--archive-path): the embedded CFBundleVersion ships, not the shell BUILD_NUMBER, so the guard now reads the archive's CFBundleVersion and fails (re-archive needed) instead of self-healing a value that won't apply. Skipped for --export-only (no upload). - Supply chain: install `cryptography` BEFORE the App Store Connect private key is written to disk, and pin to a wheel-satisfiable range, so a compromised install can't read the signing credential. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): never return a partial App Store Connect build max Page until ASC stops returning a `next` link instead of capping at 20 pages and returning whatever was seen so far. A truncated read could be below the true max, letting the caller self-heal to a number still <= the real max (the exact non-updatable build this guard prevents). MAX_PAGES is now only a runaway backstop; hitting it with more pages pending raises, so the caller fails open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): address PR-bot findings on the build-number guard - Invoke asc_max_build.py via explicit `python3` (not a bare path), so a lost exec bit can't silently make the guard always fail open. (cursor) - Reused --archive-path: require a NUMERIC embedded CFBundleVersion; if it can't be read, skip the guard with a warning instead of falsely "bumping" a value that never applies to the archive. (cursor) - asc_max_build.py: raise on a `next` URL that doesn't start with API_BASE, so a malformed pagination link can't silently truncate to a partial max. (CodeRabbit, cursor) - asc_max_build.py: emit only HTTP status + ASC error code, never the raw response body, to keep upstream payloads out of logs. (CodeRabbit) - asc_max_build.py: drop the ambiguous saw_any flag; return highest (0 = no floor). (greptile) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): report the post-guard CFBundleVersion in the workflow summary After the monotonic guard self-heals BUILD_NUMBER, the workflow summary was still printing the pre-guard value, so a release audit could show a CFBundleVersion that doesn't match the uploaded IPA. The script now writes the shipped build number to CMUX_BUILD_NUMBER_OUT_FILE (the archive's embedded version for reused archives), and the workflow reads it into a step output the summary consumes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): remove PyPI dep from signing job; fail closed on reused archives Addresses two autoreview findings on the build-number guard: - Supply chain: drop the `cryptography` dependency entirely. asc_max_build.py now mints the ES256 JWT via `openssl` (preinstalled) + stdlib, so the TestFlight job that holds the signing/upload credential never `pip install`s third-party code that could persist in site-packages and run once the key is on disk. The workflow's "Ensure cryptography" install step is removed. - Reused --archive-path can't be renumbered, so it must be verifiable before an upload: fail CLOSED when its embedded CFBundleVersion is unreadable or when App Store Connect can't be reached, instead of fail-open. Fresh builds keep fail-open (the timestamp scheme is already correct). --export-only never uploads, so it only warns. Verified: openssl-signed JWT authenticates to ASC (both key-path and base64-key paths); all guard branches (fresh fail-open, reused fail-closed x3, export-only skip) behave correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): close reused-archive verification hole on the Apple ID upload path The reused-archive fail-closed guarantee only held when ASC API creds gated the guard block. A reused --archive-path uploaded via the Apple ID/app-specific- password path (no ASC creds) skipped the block and shipped unverified. Track REUSED_ARCHIVE_VERIFIED (set only after a real ASC max comparison) and refuse the upload for any reused archive that reaches it unverified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): fail closed for explicit build numbers when ASC can't be verified Fail-open is only safe for the generated UTC timestamp (monotonic by construction). An explicit --build-number could be stale, so if App Store Connect can't be reached the guard now fails closed for explicit values while keeping fail-open for the generated default. To make the distinction real, the workflow no longer passes --build-number for push/schedule runs: the script generates the timestamp itself (single source of the numbering scheme, removing the workflow/script duplication that caused the 12-vs-14-digit regression). --build-number is passed only for a manual workflow_dispatch build_number input, which is exactly the explicit case that now fails closed when unverifiable. The summary reads the shipped number back from CMUX_BUILD_NUMBER_OUT_FILE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): close explicit-build-number bypass on the Apple ID upload path Symmetric to the reused-archive fix: an explicit --build-number uploaded via the Apple ID path (no ASC API creds) skipped the guard and could ship a stale build. Generalize the verification flag (REUSED_ARCHIVE_VERIFIED -> GUARD_VERIFIED, set only after a real ASC max comparison) and the final pre-upload gate now refuses BOTH an unverified reused archive AND an unverified explicit --build-number. The generated UTC timestamp stays exempt (monotonic by construction, fail-open). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(ios): only mark a build number verified after a real numeric comparison GUARD_VERIFIED was set as soon as asc_max_build.py succeeded, before checking that both values are numeric. A non-numeric explicit --build-number then fell to the "keep" branch already marked verified, bypassing the fail-closed gate. Restructure: set GUARD_VERIFIED only inside the numeric branch, after a real comparison. Non-numeric or unreadable ASC max leaves it unset. Consolidate the fail-closed check into one gate run BEFORE the archive (fail fast): an unverified reused archive or explicit --build-number is refused; the generated UTC timestamp stays exempt (fail-open). Verified-but-stale explicit values now also fail with a clear message instead of being silently bumped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> | 4 个月前 | |
iOS changelog + auto TestFlight "What to Test" notes (#6154) Testers got an opaque MARKETING_VERSION (timestamp) build with no notes on every install and auto-update. Add a per-build changelog and wire it into the upload so each TestFlight build shows what changed. - ios/CHANGELOG.md: single source of truth, newest version on top. Each entry has an Internal block (terse, dev-facing, per-build) and an External block (curated, user-facing). Seeded with the real 1.0.2 and 1.0.3 shipped history. - ios/scripts/asc_set_testflight_notes.py: dependency-free ASC API helper (ES256 JWT via openssl, mirrors asc_max_build.py). Resolves the app, finds the build by CFBundleVersion (polls while it processes), creates/PATCHes the en-US betaBuildLocalizations.whatsNew. - ios/scripts/set-testflight-notes.sh: extracts the top entry's Internal/External block and calls the helper. --validate-only checks local preconditions with no network; --expect-marketing-version refuses to attach notes for the wrong version. - upload-testflight.sh: preflights the notes (changelog present, version match) BEFORE the archive (fatal, deterministic), then sets the notes after a successful upload (non-fatal; the binary is already up, notes can be re-applied). Internal lane -> Internal block, --external -> External block. --skip-notes opts out. - cloud-testflight.sh: passes --external / --skip-notes through. - Shared.xcconfig: bump MARKETING_VERSION to 1.0.3 so the default lane matches the changelog top entry (the upload guard requires they agree). - docs/ios-release-notes.md: presentation design (version scheme, internal vs external) and the phase-2 in-app What's New sheet spec (deferred). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> | 3 个月前 | |
Separate iOS beta and App Store versions (#7854) | 3 个月前 | |
iOS: public App Store lane (com.cmux.app), privacy manifest, fastlane screenshots (#6697) * iOS: public App Store lane (com.cmux.app), privacy manifest, fastlane screenshots Prep cmux iOS for a public App Store release alongside the existing dev.cmux.app.beta dogfood channel. - PrivacyInfo.xcprivacy wired into the app target: NSPrivacyTracking=false, UserDefaults (CA92.1) + file-timestamp (DDA9.1) reasons, product-interaction analytics label. No Sentry/IDFA in iOS. - upload-testflight.sh + cloud-testflight.sh: new appstore lane (com.cmux.app, on-device name "cmux", cmux Distribution profile), sharing the existing release entitlements and cmux-ios URL scheme. - web/services/apns/routePolicy.ts: route com.cmux.app to production APNs (+ test). - MobileBuildType: document/test com.cmux.app as a prod bundle id. - ios/fastlane: snapshot config (en-US + ja; iPhone 6.9" + iPad 13") driving the CMUX_UITEST_MOCK_DATA DEBUG state via a SnapshotUITests case. - .github/workflows/ios-screenshots.yml: capture screenshots in CI on a DEBUG build (no signing), resolving the required iPhone/iPad classes at runtime; optional upload to App Store Connect on workflow_dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: read SNAPSHOT_DEVICES from GITHUB_ENV (set -u fix) The resolve step exported SNAPSHOT_DEVICES to $GITHUB_ENV (for later steps) but then echoed $SNAPSHOT_DEVICES, which is unset in the current shell, so set -u aborted the step. Confirm by grepping the env file instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: pick devices from available simulators, skip RAM variants Resolver picked the device TYPE 'iPad Pro 13-inch (M5) (16GB)', which has no pre-created simulator, so fastlane errored 'not in list of available simulators'. Resolve against available simulator DEVICES and exclude RAM-variant (GB) names; prefer iPhone NN Pro Max + iPad Pro/Air 13-inch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: drive devices/languages from Fastfile, not Snapfile The Snapfile's devices([...]) overrode the action's devices param, so CI's runtime-resolved simulators were ignored and fastlane looked for the stale 'iPhone 16 Pro Max'. Move devices+languages to the Fastfile (env-overridable, SNAPSHOT_DEVICES/SNAPSHOT_LANGUAGES) as the single source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: capture via standalone preview screens (real images) CMUX_UITEST_MOCK_DATA alone lands on the add-device screen, so snapshots were empty (0 images). Use the standalone preview hooks that render real UI with no sign-in/pairing: WORKSPACE_LIST_PREVIEW + TERMINAL_PREVIEW (+ fake keyboard), settling on window/foreground instead of identifiers the preview views do not expose. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: populated terminal, clean status bar, dismiss banner Make the captured screenshots presentable for the App Store: - TerminalLayoutPreviewView feeds a sample ANSI agent-session transcript when CMUX_UITEST_TERMINAL_PREVIEW_CONTENT=1, so the terminal shot shows real content instead of a blank surface (blank layout preview unchanged). - SnapshotUITests enables that flag, drops the debug zoom overlay, and swipes away the one-time 'Ready for Apple Intelligence' notification banner. - Fastfile capture uses override_status_bar for a clean 9:41 status bar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: feed terminal sample content on didResize updateUIView never re-ran with a non-zero size, so the sample transcript was never fed and the terminal shot came out blank. Feed it from the surface's first didResize (grid sized = can render). Also trigger the screenshots workflow on preview-view changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * iOS: localize app into 12 more languages (machine translation) Add zh-Hans, zh-Hant, ko, de, fr, es, pt-BR, it, ru, nl, tr, pl to all iOS xcstrings (app, agent chat UI, InfoPlist permission strings) and the project knownRegions, alongside the existing en + ja. Translations are a machine-translation first pass (placeholders/format specifiers preserved, brand/tech terms kept) and should get native review before public release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios: localized App Store listing metadata (14 locales) Store the App Store listing copy (description, keywords, promotional text, URLs; en-US also name/subtitle) for en-US + ja, zh-Hans, zh-Hant, ko, de-DE, fr-FR, es-ES, pt-BR, it, ru, nl-NL, tr, pl. Applied to App Store Connect and kept here so the listing is reproducible via fastlane deliver and the machine-translated copy is reviewable before public release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: realistic framed shots (agents, keyboard, notifications, frameit) Make App Store screenshots realistic and on-message: - TerminalPreviewTranscripts: Claude Code / Codex / OpenCode / pi sample sessions, selected via CMUX_UITEST_TERMINAL_TRANSCRIPT. - ScreenshotKeyboardView: drawn dark iOS keyboard overlaid in the reserved keyboard region (CMUX_UITEST_SCREENSHOT_KEYBOARD=1); the simulator won't render the system keyboard in CI. Device-aware height (iPhone vs iPad). - ScreenshotNotificationBanner: iOS push banner over the workspace list (CMUX_UITEST_NOTIFICATION_BANNER=1) to show agent notifications. - SnapshotUITests: 7 screens (workspaces, notifications, 4 agents w/ keyboard, full Ghostty terminal). - frameit pipeline: tranquil gradient background, Framefile.json, localized title.strings (prepare_frames.py from titles.*.json), framed in the lane after capture. Workflow installs imagemagick; deliver uploads the framed images. Dynamic island comes from the frameit device frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: re-trigger screenshots workflow Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: fix MainActor setupSnapshot call; add localized frame titles setupSnapshot is @MainActor; calling it from nonisolated setUpWithError failed to compile, so the snapshot test never ran (0 screenshots, frameit found nothing). Call it from the @MainActor test method. Also add titles.json (localized screenshot captions, 13 locales) consumed by prepare_frames.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: real frames (frameit) + real recorded agent TUIs Make everything real: - Device frame: fastlane frameit photographic frames (font supplied via the Framefile fonts array; ImageMagick has no default font). iPhone 17 Pro Max frames natively; iPad captures are resized 2064x2752 -> 2048x2732 in prepare_frames.py so frameit's real 12.9 iPad Pro frame applies. - Terminal content: replay REAL recorded sessions from the actual claude/codex/ opencode/pi CLIs (tmux capture-pane), base64-embedded in TerminalPreviewTranscripts. record_sessions.sh + embed_sessions.py reproduce them; the screenshot CI replays the committed fixtures (no agent auth needed on the runner). - Removed the drawn fake keyboard (ScreenshotKeyboardView) and the custom PIL compositor (compose_frames.py); terminal shots show the full real terminal, keyboard down. - SnapshotUITests: 6 screens (workspaces, notifications, claude, codex, opencode, pi). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: grid probe + font override + 39-locale titles - Add CMUX_UITEST_TERMINAL_FONT_SIZE override and a 'probe' transcript that prints the live cols x rows + ruler, to measure the exact iOS terminal grid and re-record agent fixtures at matching width (fixes OpenCode/Pi wrap). - titles.json localized to all 39 App Store locales; titles.en trimmed to the 6 current screens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: auto-fit terminal width, real 76-col agent sessions, dark mode, real notif icon, landscape iPad - Terminal auto-fits font to CMUX_UITEST_TERMINAL_TARGET_COLS=76 (via setLiveFontSize on first didResize), so one 76-col fixture fills the width edge-to-edge on both iPhone (portrait) and iPad (landscape). Measured grids: iPhone 93x88, iPad 205 cols @ font 8 -> fixed-width fixtures couldn't fill both. - Re-recorded all 4 agents (claude/codex/opencode/pi) at 76 cols with richer prompts that fill the screen (fixes narrow + OpenCode/Pi breakage). - Capture in dark mode (Fastfile dark_mode: true). - Notification banner uses the real embedded cmux app icon + tighter iOS styling. - iPad captured in landscape and composited bezel-less (latest real screen, no dated 2020 frame); iPhone keeps the real frameit frame. Removed grid probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: generate HTML gallery in CI (screenshots/preview/index.html) frame lane now builds a self-contained preview gallery referencing the framed PNGs in place; CI uploads it inside the screenshots artifact (automatable, not a local /tmp one-off). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: install Pillow in CI for the iPad compositor compose_ipad.py needs PIL; the runner didn't have it (ModuleNotFoundError), failing the frame step after the iPhone frames succeeded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: iPad compositor via ImageMagick (fast, no Pillow) Rewrite compose_ipad.py to use magick (the C lib frameit already requires) instead of Pillow: faster on the large landscape iPad images and removes the extra Python dependency from CI. Validated locally (rounded screen + soft shadow + caption on the tranquil background). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: gallery at screenshots root with subdir+URL-encoded img refs Browsers block file:// access to parent dirs (../) and don't auto-encode spaces in filenames, so the preview images showed broken. Write index.html at the screenshots root referencing <locale>/<file> (a subdir) with URL-encoded paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: premium composition (stitch real frame, large device, bold header) Self-review: frameit shrank the tall device under the title leaving big dead margins, and the header read weak. Replace with compose_shots.py: stitch the screenshot into the real iPhone 17 Pro Max frame and place it LARGE (bleeding off the bottom) under a bold 2-line header; iPad is a large bezel-less landscape screen with a bold header. Removed frameit action + prepare_frames/compose_ipad/ Framefile. Uses the same frameit frame PNG (downloaded on demand) + ImageMagick. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: SF Pro header + nature backgrounds Per review: switch the header to Apple's SF Pro (SFNS, heavy weight; Unicode fallback for CJK/RTL) and replace the gradient with tranquil nature photos (mountains for iPhone portrait, lake+mountains for iPad landscape), darkened with a top gradient so the device + header pop. Compositor picks bg by orientation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: round capture corners before stitching (clean frame top) The square simulator capture poked its corners past the frame's rounded screen opening, leaving square artifacts at the top corners. Round the capture to the screen corner radius before compositing under the device frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: opening-mask frame stitch + agent logos in headers - compose_shots.py: mask each iPhone screenshot to the device frame's real screen opening (extracted from the frame alpha) so the screen follows the bezel's exact rounded corners (fixes the square-corner artifacts poking past the frame). Render headers in bold SF Pro with the agent logo (Claude/Codex/ OpenCode/pi) smushed in before the localized title. - logos/: agent mark PNGs used in the headers. - Fastfile: accept the machine-store secret names (ASC_KEY_ID / ASC_ISSUER_ID / ASC_PRIVATE_KEY_PATH) in addition to the ASC_API_* names so upload_screenshots works from ~/.secrets without manual remapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: real terminal titlebar, smaller inline logos, brighter bg, pi title - TerminalLayoutPreviewView: wrap the capture in a NavigationStack with the real terminal nav bar (back chevron + centered workspace name + chat/terminal icons, mirroring WorkspaceDetailView). The terminal no longer bleeds under the status bar / Dynamic Island; there is a proper cmux titlebar below the safe area, so the device-frame stitch reads cleanly at the top. SnapshotUITests passes a believable per-agent workspace name via CMUX_UITEST_TERMINAL_TITLE. - compose_shots.py: shrink the header agent logo to ~cap height and tighten the gap so it sits inline before the title instead of as a large badge. - titles: Pi screen now names pi (mirrors each locale's Claude title, Claude Code -> pi) instead of 'Ship from anywhere', with the pi logo inline. - bg_portrait/bg_landscape: brighter nature photos (sunlit meadow + sky) with a top gradient kept for white-header legibility. - propagate_locales.py + frame lane: capture shoots only en-US+ja (the localized app UI); fan those raws out to all ~39 App Store locales before framing so the whole pipeline is reproducible in CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: unified Dynamic Island, per-screen bg variation, raw toggle - compose_shots.py: paint a single rounded Dynamic Island over the frame's physical cutouts (the frame PNG draws a pill + a separate camera hole; iOS shows one unified black pill). Fixes the weird double-cutout header. - Per-screen backgrounds: 6 bright, clean nature scenes (sky gradients + ocean, all Pexels License = free for commercial use, no attribution) under frame_assets/backgrounds/{p,l}; each screen (01..06) gets its own, so the listing has varied backdrops instead of one repeated photo. iPhone + iPad of the same screen share a theme. - generate_preview.py: add a 'Show original (unframed) captures' toggle so the raw screenshots can be inspected next to the framed ones. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: terminal preview fills header with terminal color, real glass nav chrome The screenshot preview only extended the terminal background (#272822 Monokai) under the horizontal + bottom safe areas, so the status-bar / nav-bar region fell back to black — which is NOT what the running app shows. WorkspaceDetailView fills the whole window (including under the top) with the terminal color and uses mobileTerminalNavigationChrome() (translucent Liquid Glass on iOS 26, material on iOS 18). Mirror both here so the captured header matches the real device instead of showing a black band. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: glass-pill nav title in terminal preview Put the preview's nav title on a Liquid Glass pill (mobileGlassNavigationTitle), matching WorkspaceDetailView.glassTitle, so it stays legible over terminal text now that the bar background is cleared on iOS 26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: bigger device, smaller title, horizon-free backgrounds - compose_iphone: enlarge the device (0.885 -> 0.95 width), raise it slightly, and shrink the header (120 -> 104pt, wider box) so titles like 'OpenCode, pi, any agent' fit on one line and the device is the prominent element. - backgrounds 03/04/05: replace the ocean/beach photos (visible horizon band behind the device read as 'weird', esp. OpenCode) with clean horizon-free skies; keeps the bright per-screen variation without a busy seam. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: strip OpenCode's explicit bg so it renders uniformly The OpenCode fixture painted its content on its own near-black (#0a0a0a) and let bold headings reset to the terminal default bg, which on the mobile terminal is Monokai #272822 — so heading/emphasis spans showed as olive boxes against the dark content (the 'weird rendering'). The other agents never set an explicit bg, so they render cleanly. Strip OpenCode's background SGR codes so it renders uniformly on the terminal background like the others. Also add the transcripts file to the screenshot workflow triggers so fixture changes re-capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: OpenCode keeps its dark card, made uniform (no olive boxes) Stripping OpenCode's background lost its distinctive near-black card. Instead, pin the background to OpenCode's own #0a0a0a everywhere (re-assert it after every reset / default-bg) so the card stays dark and uniform with no fallback to the Monokai #272822 default (which caused the olive boxes). The status panel's #1e1e1e is set explicitly and survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: render OpenCode on a matching #0a0a0a terminal background Definitive fix for OpenCode's olive boxes. The boxes were cells that fell back to the Monokai #272822 default (heading resets AND line-ends the agent didn't pad to the 76-col display width); ANSI surgery on the fixture couldn't cover every case. Instead set libghostty's default background to #0a0a0a for the OpenCode shot via CMUX_UITEST_TERMINAL_BG (each screenshot is its own app launch, so it's scoped to that one shot), and match the preview chrome fill to it. Now every cell — painted, reset, or unpadded — is #0a0a0a, so OpenCode is a clean uniform dark card with no boxes, and the original (unedited) capture fixture is restored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: re-record OpenCode with a full-screen response The previous OpenCode capture was sparse (a short answer + a large empty 'Build' panel), so it left a big empty area at the bottom while the other agents fill the screen. Re-recorded with a richer prompt captured after completion, so OpenCode now shows a dense full-screen response (entry point + readability + #Preview) matching the fill of claude/codex/pi. Renders on the #0a0a0a terminal background added previously, so no boxes and no surgery needed. * ios screenshots: real notification (not a drawn banner) Replace the hand-drawn ScreenshotNotificationBanner with a REAL local notification: in the workspace-list preview, request notification authorization and schedule a genuine UNNotificationRequest, so the system renders the actual banner (real blur/fonts, the app's real icon, and the 'cmux' display name — lowercase). The snapshot UITest taps the springboard 'Allow' prompt and captures the real NotificationShortLookView instead of swiping it away. Deletes the fake banner view + its embedded icon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: auto-derive terminal bg from transcript (no hardcoded color) Replace the hardcoded per-agent #0a0a0a override with auto-derivation: scan each transcript for its dominant explicit background color and render the terminal on it (OpenCode -> its near-black card; claude/codex/pi -> terminal default). The preview sets CMUX_UITEST_TERMINAL_BG from the derived value before the surface is created, so libghostty's default background matches and no reset/unpadded cell falls back to Monokai. Removes the magic constant from the snapshot test. * ios screenshots: capture the real notification banner at fixed timing The foreground notification banner renders correctly (verified: real icon, 'cmux', over the workspace list) but is a transient system overlay (~5s) that isn't reliably queryable, so the UITest was snapshotting after it dismissed. Fire the notification ~0.6s after the auth grant and snapshot at a fixed 2.5s (inside the banner's visible window) instead of waiting on an element. * ios screenshots: real Mac-streamed capture orchestration (WIP) Adds the orchestration for capturing the live Mac-streamed agent terminals on a paired simulator (capture-streamed.py) + the recorded agent sessions as raw .ans files for deterministic Mac-side content. Proven end to end (live OpenCode terminal streamed to the sim, framed, approved). Known blockers being worked: the device's workspace list only resyncs grouped workspaces after a pairing reconnect, and the device mirrors a workspace's own streamed surface (so the workspace must be created with its --command), plus local machine contention drops the tagged Mac app/pairing intermittently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: Blacksmith streamed-screenshot validation workflow (WIP, manual) Validates the real Mac-streamed screenshot pipeline headless on Blacksmith: build + run the desktop Mac app, pair an iOS sim, capture the live streamed terminal. De-risks running the desktop app + paired sim in CI (cmux streams terminal content rendered on-device, so the Mac's GUI rendering should not matter). Gated on dogfood account secrets (CMUX_DOGFOOD_STACK_EMAIL/PASSWORD) for sim sign-in — not yet configured in CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: trigger streamed-validate via push-to-self on the feature branch workflow_dispatch needs the workflow on the default branch (unavailable here), so fire the validation by pushing edits to the workflow file on the feature branch. * ci: streamed-validate stands up dev web backend (Postgres + Next.js) on Blacksmith Run the dev web app locally on the runner (local Postgres via db-local.sh + bun dev, dev Stack project) so the DEBUG sim signs into localhost:3000 + the dev project where the dedicated CI account lives — no prod, no email verification. Dev secret bundle provided via CMUXTERM_DEV_ENV_B64 (dogfood creds swapped to the dedicated CI account). Then build the Mac + iOS apps, pair the sim, capture one streamed terminal. * ci: streamed-validate uses native Postgres + next dev (no Docker) Blacksmith macOS has no Docker, so run native Postgres (cmux:cmux@localhost:13000) + drizzle migrate, and run 'next dev --port 3000' directly instead of bun dev / dev-local.sh (which call the Docker db-local.sh). Detach the server with setsid so it survives into the pairing step. * ci: detach web server with nohup (macOS has no setsid) Native Postgres + drizzle migrate work; the server step failed only because 'setsid' doesn't exist on macOS. Use nohup + disown, and dump webdev.log on failure for faster diagnosis. * ci: run streamed-validate on GitHub-hosted macos-26 (Blacksmith queue dead) The Blacksmith run sat queued 24h with no runner and was auto-cancelled, so use a GitHub-hosted macOS runner (separate pool) to actually execute the validation. macos-26 has Xcode 26 / iOS 26 sims. (Burns paid macOS minutes.) * ios-streamed-validate: run on Blacksmith macos-26, not paid GitHub-hosted Use the same Blacksmith iOS lane as test-ios.yml (vars.MACOS_RUNNER_IOS || blacksmith-6vcpu-macos-26). The desktop Mac app link failure (undefined libc symbols) was a GitHub-hosted macos-26 toolchain quirk; Blacksmith macos-26 is the proven fleet that builds cmux releases, and the prebuilt GhosttyKit is SHA-pinned for the current ghostty submodule so no from-source build runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: pin Blacksmith macos-26 + Xcode 26 (not Depot/16.4) The vars.MACOS_RUNNER_IOS override routes the iOS lane to a Depot macOS image whose default Xcode is 16.4; cmux's iOS targets need Xcode 26's Swift toolchain (Swift 6 actor-isolation + interpolation errors otherwise) and the iPhone 17 sim only exists on iOS 26. Pin blacksmith-6vcpu-macos-26 directly and select the newest Xcode 26 explicitly instead of the default symlink. The desktop Mac app already builds clean on this lane; this unblocks the iOS sim build + the in-CI pairing/capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: mirror the Blacksmith fleet build recipe (fix Xcode 26 link) The Mac build failed under Xcode 26 with undefined libc symbols (_abort/_free/_malloc_size/...): the AArch64 GlobalISel codegen path miscompiles under -O/wholemodule. Adopt reload-build.yml's exact fleet recipe: - Select Xcode via scripts/select-ci-xcode.sh (ranks by macOS SDK, picks 26.x, aligns xcode-select) instead of the default Xcode.app symlink. - Provision GhosttyKit via download-prebuilt-ghosttykit.sh (SHA-pinned), the path ci.yml/reload-build use. - Build the Mac app with --swift-frontend-workaround (disables GlobalISel). - Add CMUX_SKIP_ZIG_BUILD=1 + SWIFT_BACKTRACE env. The iOS sim build forces -O/wholemodule too, so it needs the same workaround. ios/scripts/reload.sh had no escape hatch, so add --swift-frontend-workaround (also via CMUX_SWIFT_FRONTEND_WORKAROUND=1), mirroring scripts/reload.sh, and apply it to both the simulator and device xcodebuild invocations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: make the streamed capture actually run (idb, sign-in, pairing) The capture step passed only because of '|| true'; it produced no screenshot. Three real fixes: - idb: install fb-idb into a pinned-Python venv (/tmp/idbvenv) and put it on GITHUB_PATH. The runner's default python3 (3.14) is too new for fb-idb, so the 'idb' CLI was missing (FileNotFoundError) and all device navigation failed. - sign-in: inject the DEDICATED CI screenshot account (secrets CMUX_DOGFOOD_STACK_EMAIL/PASSWORD, @cmux.com, not a personal account) as env; dev-secrets reads CMUX_DOGFOOD_STACK_* from the environment first, so mobile-dev-launch signs the device in instead of erroring 'no credentials'. - pairing order: launch the tagged Mac app and wait for its debug socket BEFORE mobile-dev-launch --ensure-mac, which mints the pairing ticket from the running Mac app; otherwise the device is signed-in-only with no workspaces to stream. Also drop '|| true' on capture and assert a *.png exists, so the step fails loudly if no real streamed shot is produced; use --detach so the sim launch returns instead of blocking on --console-pty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: capture device-state diagnostics on capture failure Sign-in + pairing + Mac workspace creation now all succeed, but the device shows 'workspace not visible' (likely the new workspace was created after the device paired and the dev workspace list didn't resync). Before guessing the fix across CI iterations, capture hard evidence: a device screenshot + accessibility tree right after pairing and again post-capture, plus the Mac workspace list, into _diag/ artifacts. Keep the top-level *.png assert (diagnostics live in _diag/). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: install idb_companion (facebook/fb tap) + /usr/local/bin symlink The device pairs and shows the workspace list fine; the capture failed only because idb ui describe-all threw FileNotFoundError: '/usr/local/bin/idb_companion'. Two causes: (1) 'brew install idb-companion' => 'No available formula' (it lives in the facebook/fb tap), so the companion was never installed; (2) the python idb client spawns it from the hardcoded /usr/local/bin path while arm64 brew installs to /opt/homebrew/bin. Install from the tap and symlink to /usr/local/bin, and assert idb_companion in the capture prereqs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: real claude agent content in the streamed shot The streamed terminals run real agents on the runner, but the agents weren't installed (zsh: command not found). Install claude/codex/opencode/pi and symlink them into /usr/local/bin (cmux workspace shells launch from the GUI Mac app and lack ~/.bun/bin + ~/.local/bin on PATH). Propagate agent auth into the GUI session with launchctl setenv BEFORE (open uses launchd env, not the step env), so workspace terminals inherit CLAUDE_CODE_OAUTH_TOKEN (claude, native) and DEEPSEEK_API_KEY (the others, via DeepSeek's OpenAI-compatible API). Validate the real-agent path with claude first (strongest auth, no provider config): launch with --permission-mode plan (clean read-only UI) and pre-seed ~/.claude.json so fresh-$HOME onboarding + folder-trust don't block. pi switched to --provider deepseek. Capture --agents claude; opencode/codex provider config to follow once claude proves the path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: trust claude sandbox by realpath (/private/tmp) The claude shot proved auth propagation works (no login screen) but stalled on the folder-trust prompt: the pre-seed keyed /tmp/cmux-stream-claude, while macOS resolves /tmp -> /private/tmp and claude keys its trust map by the realpath it sees (/private/tmp/cmux-stream-claude, visible in the screenshot). Seed both spellings so the trust prompt is pre-accepted and claude answers the prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: test the DeepSeek-backed agent path (pi) claude renders a real session now (trust cleared) but the CLAUDE_CODE_OAUTH_TOKEN account is over its monthly spend limit, so it shows the rate-limit screen. Verify the DeepSeek-backed path independently by capturing pi (--provider deepseek, reads DEEPSEEK_API_KEY propagated via launchctl) before fanning out to opencode/codex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: replay recorded agent sessions (claude) instead of live Live agents in CI hit auth/billing limits (the CLAUDE_CODE_OAUTH_TOKEN account is over its monthly spend limit) and are nondeterministic. Instead, record a real session locally with a funded account, commit the transcript, seed it into the agent's on-disk session store at the matching project cwd (/private/tmp/cmux-stream-claude), and resume it read-only in CI. claude: recorded via 'claude -p' locally (genuine answer w/ #Preview code block, no account/email/token fields in the jsonl), committed as ios/fastlane/streamed-sessions/claude/<sessionId>.jsonl, copied into ~/.claude/projects/-private-tmp-cmux-stream-claude/ in CI, launched with 'claude --continue'. Resuming renders the transcript without an API call, so the over-budget token still works. opencode/codex/pi transcripts to follow once this proves out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: resume claude by session id, seed both cwd encodings claude --continue returned 'No conversation found' even though the jsonl seeded correctly: --continue relies on the ~/.claude.json history index (not seeded), and the CI cwd may encode as -tmp- (logical) vs -private-tmp- (resolved symlink). Switch to 'claude --resume <session-id>' (opens the jsonl by id) and seed the transcript into both project-dir encodings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: real recorded sessions for all 4 agents (claude/codex/opencode/pi) claude --resume <id> proved the record->resume approach renders a real session in the streamed capture. Extend to all four: - codex: seed rollout jsonl into ~/.codex/sessions/<date>/, resume by id. - pi: seed cwd-keyed jsonl (both /tmp spellings), resume via --session <uuid>. - opencode: sessions live in a sqlite DB, so import the exported JSON, resume via --session <id>. All transcripts recorded locally with funded accounts (genuine answers with #Preview code blocks), scanned clean of secrets. Capture all four in one run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: reliable back-navigation between agent captures Only claude captured (1/4): the nav-bar back chevron has no accessibility label, so navigate_back (which searched for '<'/'Back'/'chevron') failed and the device stayed on claude's terminal, so codex/opencode/pi workspace rows were never found. Tap the back chevron by its known nav-bar position with an iOS edge-swipe pop fallback, and verify we returned to the list (>=2 'Terminal' rows) before moving to the next agent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: give codex a DeepSeek provider so resume renders (4/4) 3/4 agents rendered real resumed sessions (claude, opencode, pi). codex showed the 'Sign in with ChatGPT' onboarding because it has no CI login, which blocks codex resume. Write ~/.codex/config.toml with a DeepSeek (OpenAI-compatible) provider keyed on DEEPSEEK_API_KEY so codex is authenticated and resumes the seeded rollout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * capture-streamed: screencapture the Mac per agent (theme diagnostic) To debug the white bands on the phone (likely the Mac streaming a light/default theme), grab the CI Mac's cmux window right after each workspace is created (foreground), into _diag/mac-<agent>.png, so we can view Mac vs phone side by side for the same agent. * streamed shots: force dark theme on CI Mac + codex press-enter nudge White bands on the phone: the fresh CI runner boots in Light Mode, so cmux resolves a LIGHT terminal theme and streams a white background; unpainted cells render white on the phone. Force cmux to dark (appearanceMode=dark for the cistream bundle + system AppleInterfaceStyle=Dark) before launch — temporary CI screenshot setup. codex: send one Enter after 'codex resume' to advance past the 'Press enter to continue' welcome to the resumed session. * streamed shots: codex uses fake OpenAI auth (real codex, no DeepSeek) Per feedback: fake auth values are enough to pass codex's login gate, and resume makes no API call, so seed a dummy ~/.codex/auth.json instead of a DeepSeek provider. Codex authenticates as itself and renders the real recorded gpt-5 session with no DeepSeek label. * streamed shots: drop DeepSeek, fake per-provider auth so each agent shows its real model All three (codex/opencode/pi) fell back to DeepSeek because DEEPSEEK_API_KEY was the only provider in the GUI env. Remove it entirely and seed a fake credential for each agent's OWN recorded provider so it restores its real model with no DeepSeek label: - codex: fake ~/.codex/auth.json (openai apikey) -> gpt-5 - pi: fake ~/.pi/agent/auth.json (openai-codex oauth) -> gpt-5.5 - opencode: fake ~/.local/share/opencode/auth.json (zai) -> glm-5.2 Resume makes no API call, so the fake keys are never validated. * capture-streamed: set terminal font once up front (fix OpenCode bottom gap) OpenCode still showed a bottom letterbox even on latest main (with #7150/#7175/ #7172): the capture set the font PER-AGENT right before each screenshot, and OpenCode doesn't repaint the newly-added bottom rows after that late resize (its pure-black bg exposes the terminal-default gray in the unpainted rows; claude/ codex/pi hide it because their bg matches the default). Set the font ONCE before opening any terminal so each surface opens at its final grid with no resize-while-shown, and give it a longer settle to fully paint. * capture-streamed: per-agent set_font + long settle (consistent size AND fills) Setting the font once up front left each agent at whatever size its surface opened with, so OpenCode rendered larger (scaled-up narrow grid) than the others. Restore the per-agent focused set_font (all four at the same size) but keep the long 6s settle so OpenCode fully repaints the resized grid and fills the height (a short settle was the original cause of its bottom gap, not the resize itself). * capture-streamed: log each agent's Mac PTY grid (diagnose OpenCode scaling) OpenCode still renders ~20% larger than the others at the same set_font, which points to its terminal grid being narrower (scaled up to fill width) rather than a font-value difference. Log the read-screen cols x rows per agent so we can see the actual grids and target the real cause. * capture-streamed: smaller font for OpenCode so it matches the others' size OpenCode's TUI negotiates a narrower grid that the phone scales up, so its glyphs look ~1.3x larger than claude/codex/pi at the same font. Add a per-agent font override and set OpenCode to 11 (vs 15) to compensate; grid diagnostic stays so we can dial it in. * streamed shots: opencode layout=stretch (full width), revert font hack Root cause of OpenCode looking larger/narrower: its TUI defaults to layout=auto, which caps content to a narrow readable column, so on the phone it's narrower than claude/codex/pi (scaled up or big margins). The per-agent font hack made it worse (clipped + margins). Revert the font override and seed ~/.config/opencode/opencode.json with layout=stretch so OpenCode uses the full terminal width and matches the others. Grid diagnostic stays to verify. * iOS streamed capture: claude theme dark-ansi so it renders on cmux Monokai bg The 'dark' theme paints claude's own #1e1e1e background, overriding cmux's Monokai (#272822) terminal default; codex/pi never paint a full-screen bg so they already show Monokai. dark-ansi uses the terminal's ANSI palette + background, so claude matches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * iOS: add dispatch-only App Store upload workflow + --marketing-version CI plumbing for public com.cmux.app builds, mirroring the beta lane's signing/ ASC setup but with the prod 'cmux Distribution' profile and no schedule (App Store builds are cut deliberately, one per submission). Gated to main so only reviewed code ships. Needs the new IOS_PROD_PROVISIONING_PROFILE_BASE64 secret (set from ASC profile VF3CDPFLX9, app id 7WLXT3NR37.com.cmux.app, aps=production). upload-testflight.sh gains --marketing-version <X.Y[.Z]> to stamp an explicit version train (mutually exclusive with the beta --auto-version bump) so an appstore build lands in the exact ASC store-version train (1.0) and is attachable, instead of auto-bumping to 1.0.4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * screenshots: drop pi from OpenCode header (pi has its own shot) 05-Opencode said 'OpenCode, pi, any agent' but pi is the very next screenshot (06-Pi). Reworded to 'OpenCode and any agent' (and each locale's equivalent) so the two shots don't overlap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix App Store lane alias guard * Address screenshot preview policy findings * Localize screenshot notification fixture * Limit app-declared iOS locales * Guard screenshot workflow secrets * Address App Store screenshot PR feedback * Fix streamed simulator lookup * Handle localized notification permission in screenshots * Fix screenshot preview task modifier * Address remaining screenshot review feedback * Fix terminal preview keyboard height parse * Restrict streamed validation secrets to main * Preserve raw App Store screenshot captures * Parallelize App Store screenshot framing --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> | 2 个月前 | |
iOS: public App Store lane (com.cmux.app), privacy manifest, fastlane screenshots (#6697) * iOS: public App Store lane (com.cmux.app), privacy manifest, fastlane screenshots Prep cmux iOS for a public App Store release alongside the existing dev.cmux.app.beta dogfood channel. - PrivacyInfo.xcprivacy wired into the app target: NSPrivacyTracking=false, UserDefaults (CA92.1) + file-timestamp (DDA9.1) reasons, product-interaction analytics label. No Sentry/IDFA in iOS. - upload-testflight.sh + cloud-testflight.sh: new appstore lane (com.cmux.app, on-device name "cmux", cmux Distribution profile), sharing the existing release entitlements and cmux-ios URL scheme. - web/services/apns/routePolicy.ts: route com.cmux.app to production APNs (+ test). - MobileBuildType: document/test com.cmux.app as a prod bundle id. - ios/fastlane: snapshot config (en-US + ja; iPhone 6.9" + iPad 13") driving the CMUX_UITEST_MOCK_DATA DEBUG state via a SnapshotUITests case. - .github/workflows/ios-screenshots.yml: capture screenshots in CI on a DEBUG build (no signing), resolving the required iPhone/iPad classes at runtime; optional upload to App Store Connect on workflow_dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: read SNAPSHOT_DEVICES from GITHUB_ENV (set -u fix) The resolve step exported SNAPSHOT_DEVICES to $GITHUB_ENV (for later steps) but then echoed $SNAPSHOT_DEVICES, which is unset in the current shell, so set -u aborted the step. Confirm by grepping the env file instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: pick devices from available simulators, skip RAM variants Resolver picked the device TYPE 'iPad Pro 13-inch (M5) (16GB)', which has no pre-created simulator, so fastlane errored 'not in list of available simulators'. Resolve against available simulator DEVICES and exclude RAM-variant (GB) names; prefer iPhone NN Pro Max + iPad Pro/Air 13-inch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: drive devices/languages from Fastfile, not Snapfile The Snapfile's devices([...]) overrode the action's devices param, so CI's runtime-resolved simulators were ignored and fastlane looked for the stale 'iPhone 16 Pro Max'. Move devices+languages to the Fastfile (env-overridable, SNAPSHOT_DEVICES/SNAPSHOT_LANGUAGES) as the single source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: capture via standalone preview screens (real images) CMUX_UITEST_MOCK_DATA alone lands on the add-device screen, so snapshots were empty (0 images). Use the standalone preview hooks that render real UI with no sign-in/pairing: WORKSPACE_LIST_PREVIEW + TERMINAL_PREVIEW (+ fake keyboard), settling on window/foreground instead of identifiers the preview views do not expose. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: populated terminal, clean status bar, dismiss banner Make the captured screenshots presentable for the App Store: - TerminalLayoutPreviewView feeds a sample ANSI agent-session transcript when CMUX_UITEST_TERMINAL_PREVIEW_CONTENT=1, so the terminal shot shows real content instead of a blank surface (blank layout preview unchanged). - SnapshotUITests enables that flag, drops the debug zoom overlay, and swipes away the one-time 'Ready for Apple Intelligence' notification banner. - Fastfile capture uses override_status_bar for a clean 9:41 status bar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: feed terminal sample content on didResize updateUIView never re-ran with a non-zero size, so the sample transcript was never fed and the terminal shot came out blank. Feed it from the surface's first didResize (grid sized = can render). Also trigger the screenshots workflow on preview-view changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * iOS: localize app into 12 more languages (machine translation) Add zh-Hans, zh-Hant, ko, de, fr, es, pt-BR, it, ru, nl, tr, pl to all iOS xcstrings (app, agent chat UI, InfoPlist permission strings) and the project knownRegions, alongside the existing en + ja. Translations are a machine-translation first pass (placeholders/format specifiers preserved, brand/tech terms kept) and should get native review before public release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios: localized App Store listing metadata (14 locales) Store the App Store listing copy (description, keywords, promotional text, URLs; en-US also name/subtitle) for en-US + ja, zh-Hans, zh-Hant, ko, de-DE, fr-FR, es-ES, pt-BR, it, ru, nl-NL, tr, pl. Applied to App Store Connect and kept here so the listing is reproducible via fastlane deliver and the machine-translated copy is reviewable before public release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: realistic framed shots (agents, keyboard, notifications, frameit) Make App Store screenshots realistic and on-message: - TerminalPreviewTranscripts: Claude Code / Codex / OpenCode / pi sample sessions, selected via CMUX_UITEST_TERMINAL_TRANSCRIPT. - ScreenshotKeyboardView: drawn dark iOS keyboard overlaid in the reserved keyboard region (CMUX_UITEST_SCREENSHOT_KEYBOARD=1); the simulator won't render the system keyboard in CI. Device-aware height (iPhone vs iPad). - ScreenshotNotificationBanner: iOS push banner over the workspace list (CMUX_UITEST_NOTIFICATION_BANNER=1) to show agent notifications. - SnapshotUITests: 7 screens (workspaces, notifications, 4 agents w/ keyboard, full Ghostty terminal). - frameit pipeline: tranquil gradient background, Framefile.json, localized title.strings (prepare_frames.py from titles.*.json), framed in the lane after capture. Workflow installs imagemagick; deliver uploads the framed images. Dynamic island comes from the frameit device frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: re-trigger screenshots workflow Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: fix MainActor setupSnapshot call; add localized frame titles setupSnapshot is @MainActor; calling it from nonisolated setUpWithError failed to compile, so the snapshot test never ran (0 screenshots, frameit found nothing). Call it from the @MainActor test method. Also add titles.json (localized screenshot captions, 13 locales) consumed by prepare_frames.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: real frames (frameit) + real recorded agent TUIs Make everything real: - Device frame: fastlane frameit photographic frames (font supplied via the Framefile fonts array; ImageMagick has no default font). iPhone 17 Pro Max frames natively; iPad captures are resized 2064x2752 -> 2048x2732 in prepare_frames.py so frameit's real 12.9 iPad Pro frame applies. - Terminal content: replay REAL recorded sessions from the actual claude/codex/ opencode/pi CLIs (tmux capture-pane), base64-embedded in TerminalPreviewTranscripts. record_sessions.sh + embed_sessions.py reproduce them; the screenshot CI replays the committed fixtures (no agent auth needed on the runner). - Removed the drawn fake keyboard (ScreenshotKeyboardView) and the custom PIL compositor (compose_frames.py); terminal shots show the full real terminal, keyboard down. - SnapshotUITests: 6 screens (workspaces, notifications, claude, codex, opencode, pi). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: grid probe + font override + 39-locale titles - Add CMUX_UITEST_TERMINAL_FONT_SIZE override and a 'probe' transcript that prints the live cols x rows + ruler, to measure the exact iOS terminal grid and re-record agent fixtures at matching width (fixes OpenCode/Pi wrap). - titles.json localized to all 39 App Store locales; titles.en trimmed to the 6 current screens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: auto-fit terminal width, real 76-col agent sessions, dark mode, real notif icon, landscape iPad - Terminal auto-fits font to CMUX_UITEST_TERMINAL_TARGET_COLS=76 (via setLiveFontSize on first didResize), so one 76-col fixture fills the width edge-to-edge on both iPhone (portrait) and iPad (landscape). Measured grids: iPhone 93x88, iPad 205 cols @ font 8 -> fixed-width fixtures couldn't fill both. - Re-recorded all 4 agents (claude/codex/opencode/pi) at 76 cols with richer prompts that fill the screen (fixes narrow + OpenCode/Pi breakage). - Capture in dark mode (Fastfile dark_mode: true). - Notification banner uses the real embedded cmux app icon + tighter iOS styling. - iPad captured in landscape and composited bezel-less (latest real screen, no dated 2020 frame); iPhone keeps the real frameit frame. Removed grid probe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: generate HTML gallery in CI (screenshots/preview/index.html) frame lane now builds a self-contained preview gallery referencing the framed PNGs in place; CI uploads it inside the screenshots artifact (automatable, not a local /tmp one-off). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: install Pillow in CI for the iPad compositor compose_ipad.py needs PIL; the runner didn't have it (ModuleNotFoundError), failing the frame step after the iPhone frames succeeded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: iPad compositor via ImageMagick (fast, no Pillow) Rewrite compose_ipad.py to use magick (the C lib frameit already requires) instead of Pillow: faster on the large landscape iPad images and removes the extra Python dependency from CI. Validated locally (rounded screen + soft shadow + caption on the tranquil background). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: gallery at screenshots root with subdir+URL-encoded img refs Browsers block file:// access to parent dirs (../) and don't auto-encode spaces in filenames, so the preview images showed broken. Write index.html at the screenshots root referencing <locale>/<file> (a subdir) with URL-encoded paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: premium composition (stitch real frame, large device, bold header) Self-review: frameit shrank the tall device under the title leaving big dead margins, and the header read weak. Replace with compose_shots.py: stitch the screenshot into the real iPhone 17 Pro Max frame and place it LARGE (bleeding off the bottom) under a bold 2-line header; iPad is a large bezel-less landscape screen with a bold header. Removed frameit action + prepare_frames/compose_ipad/ Framefile. Uses the same frameit frame PNG (downloaded on demand) + ImageMagick. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: SF Pro header + nature backgrounds Per review: switch the header to Apple's SF Pro (SFNS, heavy weight; Unicode fallback for CJK/RTL) and replace the gradient with tranquil nature photos (mountains for iPhone portrait, lake+mountains for iPad landscape), darkened with a top gradient so the device + header pop. Compositor picks bg by orientation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-screenshots: round capture corners before stitching (clean frame top) The square simulator capture poked its corners past the frame's rounded screen opening, leaving square artifacts at the top corners. Round the capture to the screen corner radius before compositing under the device frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: opening-mask frame stitch + agent logos in headers - compose_shots.py: mask each iPhone screenshot to the device frame's real screen opening (extracted from the frame alpha) so the screen follows the bezel's exact rounded corners (fixes the square-corner artifacts poking past the frame). Render headers in bold SF Pro with the agent logo (Claude/Codex/ OpenCode/pi) smushed in before the localized title. - logos/: agent mark PNGs used in the headers. - Fastfile: accept the machine-store secret names (ASC_KEY_ID / ASC_ISSUER_ID / ASC_PRIVATE_KEY_PATH) in addition to the ASC_API_* names so upload_screenshots works from ~/.secrets without manual remapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: real terminal titlebar, smaller inline logos, brighter bg, pi title - TerminalLayoutPreviewView: wrap the capture in a NavigationStack with the real terminal nav bar (back chevron + centered workspace name + chat/terminal icons, mirroring WorkspaceDetailView). The terminal no longer bleeds under the status bar / Dynamic Island; there is a proper cmux titlebar below the safe area, so the device-frame stitch reads cleanly at the top. SnapshotUITests passes a believable per-agent workspace name via CMUX_UITEST_TERMINAL_TITLE. - compose_shots.py: shrink the header agent logo to ~cap height and tighten the gap so it sits inline before the title instead of as a large badge. - titles: Pi screen now names pi (mirrors each locale's Claude title, Claude Code -> pi) instead of 'Ship from anywhere', with the pi logo inline. - bg_portrait/bg_landscape: brighter nature photos (sunlit meadow + sky) with a top gradient kept for white-header legibility. - propagate_locales.py + frame lane: capture shoots only en-US+ja (the localized app UI); fan those raws out to all ~39 App Store locales before framing so the whole pipeline is reproducible in CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: unified Dynamic Island, per-screen bg variation, raw toggle - compose_shots.py: paint a single rounded Dynamic Island over the frame's physical cutouts (the frame PNG draws a pill + a separate camera hole; iOS shows one unified black pill). Fixes the weird double-cutout header. - Per-screen backgrounds: 6 bright, clean nature scenes (sky gradients + ocean, all Pexels License = free for commercial use, no attribution) under frame_assets/backgrounds/{p,l}; each screen (01..06) gets its own, so the listing has varied backdrops instead of one repeated photo. iPhone + iPad of the same screen share a theme. - generate_preview.py: add a 'Show original (unframed) captures' toggle so the raw screenshots can be inspected next to the framed ones. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: terminal preview fills header with terminal color, real glass nav chrome The screenshot preview only extended the terminal background (#272822 Monokai) under the horizontal + bottom safe areas, so the status-bar / nav-bar region fell back to black — which is NOT what the running app shows. WorkspaceDetailView fills the whole window (including under the top) with the terminal color and uses mobileTerminalNavigationChrome() (translucent Liquid Glass on iOS 26, material on iOS 18). Mirror both here so the captured header matches the real device instead of showing a black band. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: glass-pill nav title in terminal preview Put the preview's nav title on a Liquid Glass pill (mobileGlassNavigationTitle), matching WorkspaceDetailView.glassTitle, so it stays legible over terminal text now that the bar background is cleared on iOS 26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: bigger device, smaller title, horizon-free backgrounds - compose_iphone: enlarge the device (0.885 -> 0.95 width), raise it slightly, and shrink the header (120 -> 104pt, wider box) so titles like 'OpenCode, pi, any agent' fit on one line and the device is the prominent element. - backgrounds 03/04/05: replace the ocean/beach photos (visible horizon band behind the device read as 'weird', esp. OpenCode) with clean horizon-free skies; keeps the bright per-screen variation without a busy seam. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: strip OpenCode's explicit bg so it renders uniformly The OpenCode fixture painted its content on its own near-black (#0a0a0a) and let bold headings reset to the terminal default bg, which on the mobile terminal is Monokai #272822 — so heading/emphasis spans showed as olive boxes against the dark content (the 'weird rendering'). The other agents never set an explicit bg, so they render cleanly. Strip OpenCode's background SGR codes so it renders uniformly on the terminal background like the others. Also add the transcripts file to the screenshot workflow triggers so fixture changes re-capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: OpenCode keeps its dark card, made uniform (no olive boxes) Stripping OpenCode's background lost its distinctive near-black card. Instead, pin the background to OpenCode's own #0a0a0a everywhere (re-assert it after every reset / default-bg) so the card stays dark and uniform with no fallback to the Monokai #272822 default (which caused the olive boxes). The status panel's #1e1e1e is set explicitly and survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: render OpenCode on a matching #0a0a0a terminal background Definitive fix for OpenCode's olive boxes. The boxes were cells that fell back to the Monokai #272822 default (heading resets AND line-ends the agent didn't pad to the 76-col display width); ANSI surgery on the fixture couldn't cover every case. Instead set libghostty's default background to #0a0a0a for the OpenCode shot via CMUX_UITEST_TERMINAL_BG (each screenshot is its own app launch, so it's scoped to that one shot), and match the preview chrome fill to it. Now every cell — painted, reset, or unpadded — is #0a0a0a, so OpenCode is a clean uniform dark card with no boxes, and the original (unedited) capture fixture is restored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: re-record OpenCode with a full-screen response The previous OpenCode capture was sparse (a short answer + a large empty 'Build' panel), so it left a big empty area at the bottom while the other agents fill the screen. Re-recorded with a richer prompt captured after completion, so OpenCode now shows a dense full-screen response (entry point + readability + #Preview) matching the fill of claude/codex/pi. Renders on the #0a0a0a terminal background added previously, so no boxes and no surgery needed. * ios screenshots: real notification (not a drawn banner) Replace the hand-drawn ScreenshotNotificationBanner with a REAL local notification: in the workspace-list preview, request notification authorization and schedule a genuine UNNotificationRequest, so the system renders the actual banner (real blur/fonts, the app's real icon, and the 'cmux' display name — lowercase). The snapshot UITest taps the springboard 'Allow' prompt and captures the real NotificationShortLookView instead of swiping it away. Deletes the fake banner view + its embedded icon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios screenshots: auto-derive terminal bg from transcript (no hardcoded color) Replace the hardcoded per-agent #0a0a0a override with auto-derivation: scan each transcript for its dominant explicit background color and render the terminal on it (OpenCode -> its near-black card; claude/codex/pi -> terminal default). The preview sets CMUX_UITEST_TERMINAL_BG from the derived value before the surface is created, so libghostty's default background matches and no reset/unpadded cell falls back to Monokai. Removes the magic constant from the snapshot test. * ios screenshots: capture the real notification banner at fixed timing The foreground notification banner renders correctly (verified: real icon, 'cmux', over the workspace list) but is a transient system overlay (~5s) that isn't reliably queryable, so the UITest was snapshotting after it dismissed. Fire the notification ~0.6s after the auth grant and snapshot at a fixed 2.5s (inside the banner's visible window) instead of waiting on an element. * ios screenshots: real Mac-streamed capture orchestration (WIP) Adds the orchestration for capturing the live Mac-streamed agent terminals on a paired simulator (capture-streamed.py) + the recorded agent sessions as raw .ans files for deterministic Mac-side content. Proven end to end (live OpenCode terminal streamed to the sim, framed, approved). Known blockers being worked: the device's workspace list only resyncs grouped workspaces after a pairing reconnect, and the device mirrors a workspace's own streamed surface (so the workspace must be created with its --command), plus local machine contention drops the tagged Mac app/pairing intermittently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: Blacksmith streamed-screenshot validation workflow (WIP, manual) Validates the real Mac-streamed screenshot pipeline headless on Blacksmith: build + run the desktop Mac app, pair an iOS sim, capture the live streamed terminal. De-risks running the desktop app + paired sim in CI (cmux streams terminal content rendered on-device, so the Mac's GUI rendering should not matter). Gated on dogfood account secrets (CMUX_DOGFOOD_STACK_EMAIL/PASSWORD) for sim sign-in — not yet configured in CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: trigger streamed-validate via push-to-self on the feature branch workflow_dispatch needs the workflow on the default branch (unavailable here), so fire the validation by pushing edits to the workflow file on the feature branch. * ci: streamed-validate stands up dev web backend (Postgres + Next.js) on Blacksmith Run the dev web app locally on the runner (local Postgres via db-local.sh + bun dev, dev Stack project) so the DEBUG sim signs into localhost:3000 + the dev project where the dedicated CI account lives — no prod, no email verification. Dev secret bundle provided via CMUXTERM_DEV_ENV_B64 (dogfood creds swapped to the dedicated CI account). Then build the Mac + iOS apps, pair the sim, capture one streamed terminal. * ci: streamed-validate uses native Postgres + next dev (no Docker) Blacksmith macOS has no Docker, so run native Postgres (cmux:cmux@localhost:13000) + drizzle migrate, and run 'next dev --port 3000' directly instead of bun dev / dev-local.sh (which call the Docker db-local.sh). Detach the server with setsid so it survives into the pairing step. * ci: detach web server with nohup (macOS has no setsid) Native Postgres + drizzle migrate work; the server step failed only because 'setsid' doesn't exist on macOS. Use nohup + disown, and dump webdev.log on failure for faster diagnosis. * ci: run streamed-validate on GitHub-hosted macos-26 (Blacksmith queue dead) The Blacksmith run sat queued 24h with no runner and was auto-cancelled, so use a GitHub-hosted macOS runner (separate pool) to actually execute the validation. macos-26 has Xcode 26 / iOS 26 sims. (Burns paid macOS minutes.) * ios-streamed-validate: run on Blacksmith macos-26, not paid GitHub-hosted Use the same Blacksmith iOS lane as test-ios.yml (vars.MACOS_RUNNER_IOS || blacksmith-6vcpu-macos-26). The desktop Mac app link failure (undefined libc symbols) was a GitHub-hosted macos-26 toolchain quirk; Blacksmith macos-26 is the proven fleet that builds cmux releases, and the prebuilt GhosttyKit is SHA-pinned for the current ghostty submodule so no from-source build runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: pin Blacksmith macos-26 + Xcode 26 (not Depot/16.4) The vars.MACOS_RUNNER_IOS override routes the iOS lane to a Depot macOS image whose default Xcode is 16.4; cmux's iOS targets need Xcode 26's Swift toolchain (Swift 6 actor-isolation + interpolation errors otherwise) and the iPhone 17 sim only exists on iOS 26. Pin blacksmith-6vcpu-macos-26 directly and select the newest Xcode 26 explicitly instead of the default symlink. The desktop Mac app already builds clean on this lane; this unblocks the iOS sim build + the in-CI pairing/capture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: mirror the Blacksmith fleet build recipe (fix Xcode 26 link) The Mac build failed under Xcode 26 with undefined libc symbols (_abort/_free/_malloc_size/...): the AArch64 GlobalISel codegen path miscompiles under -O/wholemodule. Adopt reload-build.yml's exact fleet recipe: - Select Xcode via scripts/select-ci-xcode.sh (ranks by macOS SDK, picks 26.x, aligns xcode-select) instead of the default Xcode.app symlink. - Provision GhosttyKit via download-prebuilt-ghosttykit.sh (SHA-pinned), the path ci.yml/reload-build use. - Build the Mac app with --swift-frontend-workaround (disables GlobalISel). - Add CMUX_SKIP_ZIG_BUILD=1 + SWIFT_BACKTRACE env. The iOS sim build forces -O/wholemodule too, so it needs the same workaround. ios/scripts/reload.sh had no escape hatch, so add --swift-frontend-workaround (also via CMUX_SWIFT_FRONTEND_WORKAROUND=1), mirroring scripts/reload.sh, and apply it to both the simulator and device xcodebuild invocations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: make the streamed capture actually run (idb, sign-in, pairing) The capture step passed only because of '|| true'; it produced no screenshot. Three real fixes: - idb: install fb-idb into a pinned-Python venv (/tmp/idbvenv) and put it on GITHUB_PATH. The runner's default python3 (3.14) is too new for fb-idb, so the 'idb' CLI was missing (FileNotFoundError) and all device navigation failed. - sign-in: inject the DEDICATED CI screenshot account (secrets CMUX_DOGFOOD_STACK_EMAIL/PASSWORD, @cmux.com, not a personal account) as env; dev-secrets reads CMUX_DOGFOOD_STACK_* from the environment first, so mobile-dev-launch signs the device in instead of erroring 'no credentials'. - pairing order: launch the tagged Mac app and wait for its debug socket BEFORE mobile-dev-launch --ensure-mac, which mints the pairing ticket from the running Mac app; otherwise the device is signed-in-only with no workspaces to stream. Also drop '|| true' on capture and assert a *.png exists, so the step fails loudly if no real streamed shot is produced; use --detach so the sim launch returns instead of blocking on --console-pty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: capture device-state diagnostics on capture failure Sign-in + pairing + Mac workspace creation now all succeed, but the device shows 'workspace not visible' (likely the new workspace was created after the device paired and the dev workspace list didn't resync). Before guessing the fix across CI iterations, capture hard evidence: a device screenshot + accessibility tree right after pairing and again post-capture, plus the Mac workspace list, into _diag/ artifacts. Keep the top-level *.png assert (diagnostics live in _diag/). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: install idb_companion (facebook/fb tap) + /usr/local/bin symlink The device pairs and shows the workspace list fine; the capture failed only because idb ui describe-all threw FileNotFoundError: '/usr/local/bin/idb_companion'. Two causes: (1) 'brew install idb-companion' => 'No available formula' (it lives in the facebook/fb tap), so the companion was never installed; (2) the python idb client spawns it from the hardcoded /usr/local/bin path while arm64 brew installs to /opt/homebrew/bin. Install from the tap and symlink to /usr/local/bin, and assert idb_companion in the capture prereqs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: real claude agent content in the streamed shot The streamed terminals run real agents on the runner, but the agents weren't installed (zsh: command not found). Install claude/codex/opencode/pi and symlink them into /usr/local/bin (cmux workspace shells launch from the GUI Mac app and lack ~/.bun/bin + ~/.local/bin on PATH). Propagate agent auth into the GUI session with launchctl setenv BEFORE (open uses launchd env, not the step env), so workspace terminals inherit CLAUDE_CODE_OAUTH_TOKEN (claude, native) and DEEPSEEK_API_KEY (the others, via DeepSeek's OpenAI-compatible API). Validate the real-agent path with claude first (strongest auth, no provider config): launch with --permission-mode plan (clean read-only UI) and pre-seed ~/.claude.json so fresh-$HOME onboarding + folder-trust don't block. pi switched to --provider deepseek. Capture --agents claude; opencode/codex provider config to follow once claude proves the path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: trust claude sandbox by realpath (/private/tmp) The claude shot proved auth propagation works (no login screen) but stalled on the folder-trust prompt: the pre-seed keyed /tmp/cmux-stream-claude, while macOS resolves /tmp -> /private/tmp and claude keys its trust map by the realpath it sees (/private/tmp/cmux-stream-claude, visible in the screenshot). Seed both spellings so the trust prompt is pre-accepted and claude answers the prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: test the DeepSeek-backed agent path (pi) claude renders a real session now (trust cleared) but the CLAUDE_CODE_OAUTH_TOKEN account is over its monthly spend limit, so it shows the rate-limit screen. Verify the DeepSeek-backed path independently by capturing pi (--provider deepseek, reads DEEPSEEK_API_KEY propagated via launchctl) before fanning out to opencode/codex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: replay recorded agent sessions (claude) instead of live Live agents in CI hit auth/billing limits (the CLAUDE_CODE_OAUTH_TOKEN account is over its monthly spend limit) and are nondeterministic. Instead, record a real session locally with a funded account, commit the transcript, seed it into the agent's on-disk session store at the matching project cwd (/private/tmp/cmux-stream-claude), and resume it read-only in CI. claude: recorded via 'claude -p' locally (genuine answer w/ #Preview code block, no account/email/token fields in the jsonl), committed as ios/fastlane/streamed-sessions/claude/<sessionId>.jsonl, copied into ~/.claude/projects/-private-tmp-cmux-stream-claude/ in CI, launched with 'claude --continue'. Resuming renders the transcript without an API call, so the over-budget token still works. opencode/codex/pi transcripts to follow once this proves out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: resume claude by session id, seed both cwd encodings claude --continue returned 'No conversation found' even though the jsonl seeded correctly: --continue relies on the ~/.claude.json history index (not seeded), and the CI cwd may encode as -tmp- (logical) vs -private-tmp- (resolved symlink). Switch to 'claude --resume <session-id>' (opens the jsonl by id) and seed the transcript into both project-dir encodings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: real recorded sessions for all 4 agents (claude/codex/opencode/pi) claude --resume <id> proved the record->resume approach renders a real session in the streamed capture. Extend to all four: - codex: seed rollout jsonl into ~/.codex/sessions/<date>/, resume by id. - pi: seed cwd-keyed jsonl (both /tmp spellings), resume via --session <uuid>. - opencode: sessions live in a sqlite DB, so import the exported JSON, resume via --session <id>. All transcripts recorded locally with funded accounts (genuine answers with #Preview code blocks), scanned clean of secrets. Capture all four in one run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: reliable back-navigation between agent captures Only claude captured (1/4): the nav-bar back chevron has no accessibility label, so navigate_back (which searched for '<'/'Back'/'chevron') failed and the device stayed on claude's terminal, so codex/opencode/pi workspace rows were never found. Tap the back chevron by its known nav-bar position with an iOS edge-swipe pop fallback, and verify we returned to the list (>=2 'Terminal' rows) before moving to the next agent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ios-streamed-validate: give codex a DeepSeek provider so resume renders (4/4) 3/4 agents rendered real resumed sessions (claude, opencode, pi). codex showed the 'Sign in with ChatGPT' onboarding because it has no CI login, which blocks codex resume. Write ~/.codex/config.toml with a DeepSeek (OpenAI-compatible) provider keyed on DEEPSEEK_API_KEY so codex is authenticated and resumes the seeded rollout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * capture-streamed: screencapture the Mac per agent (theme diagnostic) To debug the white bands on the phone (likely the Mac streaming a light/default theme), grab the CI Mac's cmux window right after each workspace is created (foreground), into _diag/mac-<agent>.png, so we can view Mac vs phone side by side for the same agent. * streamed shots: force dark theme on CI Mac + codex press-enter nudge White bands on the phone: the fresh CI runner boots in Light Mode, so cmux resolves a LIGHT terminal theme and streams a white background; unpainted cells render white on the phone. Force cmux to dark (appearanceMode=dark for the cistream bundle + system AppleInterfaceStyle=Dark) before launch — temporary CI screenshot setup. codex: send one Enter after 'codex resume' to advance past the 'Press enter to continue' welcome to the resumed session. * streamed shots: codex uses fake OpenAI auth (real codex, no DeepSeek) Per feedback: fake auth values are enough to pass codex's login gate, and resume makes no API call, so seed a dummy ~/.codex/auth.json instead of a DeepSeek provider. Codex authenticates as itself and renders the real recorded gpt-5 session with no DeepSeek label. * streamed shots: drop DeepSeek, fake per-provider auth so each agent shows its real model All three (codex/opencode/pi) fell back to DeepSeek because DEEPSEEK_API_KEY was the only provider in the GUI env. Remove it entirely and seed a fake credential for each agent's OWN recorded provider so it restores its real model with no DeepSeek label: - codex: fake ~/.codex/auth.json (openai apikey) -> gpt-5 - pi: fake ~/.pi/agent/auth.json (openai-codex oauth) -> gpt-5.5 - opencode: fake ~/.local/share/opencode/auth.json (zai) -> glm-5.2 Resume makes no API call, so the fake keys are never validated. * capture-streamed: set terminal font once up front (fix OpenCode bottom gap) OpenCode still showed a bottom letterbox even on latest main (with #7150/#7175/ #7172): the capture set the font PER-AGENT right before each screenshot, and OpenCode doesn't repaint the newly-added bottom rows after that late resize (its pure-black bg exposes the terminal-default gray in the unpainted rows; claude/ codex/pi hide it because their bg matches the default). Set the font ONCE before opening any terminal so each surface opens at its final grid with no resize-while-shown, and give it a longer settle to fully paint. * capture-streamed: per-agent set_font + long settle (consistent size AND fills) Setting the font once up front left each agent at whatever size its surface opened with, so OpenCode rendered larger (scaled-up narrow grid) than the others. Restore the per-agent focused set_font (all four at the same size) but keep the long 6s settle so OpenCode fully repaints the resized grid and fills the height (a short settle was the original cause of its bottom gap, not the resize itself). * capture-streamed: log each agent's Mac PTY grid (diagnose OpenCode scaling) OpenCode still renders ~20% larger than the others at the same set_font, which points to its terminal grid being narrower (scaled up to fill width) rather than a font-value difference. Log the read-screen cols x rows per agent so we can see the actual grids and target the real cause. * capture-streamed: smaller font for OpenCode so it matches the others' size OpenCode's TUI negotiates a narrower grid that the phone scales up, so its glyphs look ~1.3x larger than claude/codex/pi at the same font. Add a per-agent font override and set OpenCode to 11 (vs 15) to compensate; grid diagnostic stays so we can dial it in. * streamed shots: opencode layout=stretch (full width), revert font hack Root cause of OpenCode looking larger/narrower: its TUI defaults to layout=auto, which caps content to a narrow readable column, so on the phone it's narrower than claude/codex/pi (scaled up or big margins). The per-agent font hack made it worse (clipped + margins). Revert the font override and seed ~/.config/opencode/opencode.json with layout=stretch so OpenCode uses the full terminal width and matches the others. Grid diagnostic stays to verify. * iOS streamed capture: claude theme dark-ansi so it renders on cmux Monokai bg The 'dark' theme paints claude's own #1e1e1e background, overriding cmux's Monokai (#272822) terminal default; codex/pi never paint a full-screen bg so they already show Monokai. dark-ansi uses the terminal's ANSI palette + background, so claude matches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * iOS: add dispatch-only App Store upload workflow + --marketing-version CI plumbing for public com.cmux.app builds, mirroring the beta lane's signing/ ASC setup but with the prod 'cmux Distribution' profile and no schedule (App Store builds are cut deliberately, one per submission). Gated to main so only reviewed code ships. Needs the new IOS_PROD_PROVISIONING_PROFILE_BASE64 secret (set from ASC profile VF3CDPFLX9, app id 7WLXT3NR37.com.cmux.app, aps=production). upload-testflight.sh gains --marketing-version <X.Y[.Z]> to stamp an explicit version train (mutually exclusive with the beta --auto-version bump) so an appstore build lands in the exact ASC store-version train (1.0) and is attachable, instead of auto-bumping to 1.0.4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * screenshots: drop pi from OpenCode header (pi has its own shot) 05-Opencode said 'OpenCode, pi, any agent' but pi is the very next screenshot (06-Pi). Reworded to 'OpenCode and any agent' (and each locale's equivalent) so the two shots don't overlap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix App Store lane alias guard * Address screenshot preview policy findings * Localize screenshot notification fixture * Limit app-declared iOS locales * Guard screenshot workflow secrets * Address App Store screenshot PR feedback * Fix streamed simulator lookup * Handle localized notification permission in screenshots * Fix screenshot preview task modifier * Address remaining screenshot review feedback * Fix terminal preview keyboard height parse * Restrict streamed validation secrets to main * Preserve raw App Store screenshot captures * Parallelize App Store screenshot framing --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> | 2 个月前 | |
Reclaim hidden Ghostty renderer memory (#8998) * Add five-tab renderer memory regression test * Reclaim hidden terminal renderers by default * Pin shared Metal pipeline Ghostty build * Pin final Ghostty memory build * Pin competitive Ghostty memory build * Test renderer reclamation catalog defaults * Use catalog renderer reclamation defaults * test: require atomic first renderer presentation * fix: make first renderer presentation atomic * fix: resolve renderer defaults through catalog * Exercise renderer defaults through UserDefaults * Pin forced renderer rebuild Ghostty head * Pin forced rebuild GhosttyKit checksum * Test forced renderer rebuild presentation * Preserve forced renderer rebuild presentation * Make renderer defaults regression test throwable * Pin merged Ghostty renderer reclamation head * Pin final GhosttyKit checksum * Pin reviewed Ghostty renderer retry fix * Pin reviewed Ghostty shader cache follow-up * Add red test for Ghostty Zig version drift * Derive Zig version from pinned Ghostty * Run Ghostty Zig version drift test in CI * Test all Ghostty Zig workflow consumers * Synchronize Ghostty Zig workflows * Test Ghostty Zig helper as TestFlight input * Track Ghostty Zig helper in TestFlight inputs * Pin Ghostty shader failure backoff * Pin Ghostty shader attempt backoff * test: require renderer reclaim deadline scheduling * test: initialize linked Ghostty runtime * fix: schedule renderer reclaim at idle deadlines * test: retain synthetic Ghostty argv * fix: coalesce renderer visibility evaluation * test: retain Ghostty runtime argv * fix: wire renderer visibility coalescing * Pin integrated Ghostty mailbox fix * refactor: inject renderer reclaim scheduler inputs * test: exercise renderer reclaim scheduler lifecycle * fix: bound renderer visibility scheduling * test: look up linked Ghostty runtime dynamically * Validate per-consumer Ghostty Zig wiring * test: require fail-closed Ghostty Zig workflows * fix: fail closed on Ghostty Zig resolution * fix: make renderer scheduling verification deterministic * test: coalesce staggered renderer reclaim deadlines * fix: coalesce renderer reclaim deadlines * Update Ghostty renderer retry artifact * test: measure five-tab renderer memory * test: cover compatible Zig patch releases * fix: accept compatible Zig patch releases * refactor: separate renderer realization surface seam --------- Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com> Co-authored-by: austinpower1258 <austinwang115@gmail.com> | 2 个月前 | |
Enforce iPhone+simulator default for iOS verification with an offline install queue (#9232) * Enforce iPhone+simulator default for iOS verification with an offline install queue iOS verification reloads now target BOTH an isolated per-tag simulator (cmux-dev-<slug>, created on demand) and the configured iPhone (CMUX_IPHONE_DEVICE_ID or ~/.config/cmux/iphone-device-id; never hardcoded). When the phone is unreachable at build time, the signed build is parked in a persistent queue (scripts/iphone-install-queue.sh, under ~/Library/Application Support/cmux-dev/iphone-install-queue) and a LaunchAgent (scripts/install-iphone-queue-agent.sh) auto-installs and launches it within seconds of the phone reconnecting, via launchd IOKit matching on Apple USB attach, WatchPaths on the queue, and a periodic network backstop, then sends a cmux notification. Every phone build hard-requires the same-tag Mac dev build: ios/scripts/reload.sh builds the Mac tag first when missing and refuses phone-only otherwise. scripts/ios-sim-install.sh installs cloud-built simulator apps into the isolated simulator for the reload-cloud-ios path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Probe device reachability through the queue script in ios/scripts/reload.sh One probe implementation (iphone-install-queue.sh probe) now decides "unreachable" for both the local and cloud reload paths, including the CMUX_IPHONE_QUEUE_FORCE_UNREACHABLE test hook; select_device still owns name/ambiguity resolution for reachable devices and its failure is treated as unreachable as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address review findings: name-target queueing, enqueue race, fail-closed sim install A --device-name target no longer probes or queues against the DEFAULT device id (queueing for a different phone than the one named would install on the wrong device); name targets error with a hint to use --device-id when unreachable. drain_entry now re-reads enqueued_at before every terminal action so a re-enqueue during an in-flight drain leaves the newer build queued instead of silently deleting or failing it. ios-sim-install.sh fails closed on an unreadable CFBundleIdentifier. Also: quote $tab expansions (SC2295), correct help sed ranges, document the one-time LaunchAgent install in CLAUDE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Nudge PR sync Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
Separate iOS beta and App Store versions (#7854) | 3 个月前 | |
iOS: unified animated toast system (CmuxMobileToast) (#8376) * iOS: unified toast system (CmuxMobileToast) replacing ad-hoc toasts New CmuxMobileToast package: ToastCenter (@Observable, clock-injected dwell/queue/coalescing policy, fully unit-tested) + one toastHost overlay mounted at the root scene. Toasts render in a passthrough UIWindow above sheets, with Liquid Glass (material fallback), spring+blur arrival, interactive drag-to-dismiss with velocity projection, coalescing bump pulse, haptics, VoiceOver announcements, and Reduce Motion/Transparency fallbacks. Replaces WorkspaceActionToast (bottom capsule in WorkspaceShellView) and ChatScreen's Timer-based error banner; workspace-action failures now read as title + sentence reason (localization keys reworked en+ja). DEBUG gallery behind CMUX_TOAST_GALLERY=1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Toast gallery: scripted autorun for recorded animation verification CMUX_TOAST_GALLERY_AUTORUN=1 walks every style, placement, coalescing bump, and queue advance on a fixed cadence so a simulator screen recording captures the full motion vocabulary without UI driving. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix toast touch capture: geometry-gated passthrough window SwiftUI renders the card without dedicated UIViews, so UIWindow.hitTest returns the hosting view for card and empty space alike — the previous view-identity filter passed EVERY touch through, leaving tap/drag/action dead. The card now publishes its window-space frame (onGeometryChange → ToastHostChrome.interactiveRegion) and the window captures only inside it. Verified on-device via the DEBUG hitTest probe (empty overlay nil / card region captured / beside-card nil), written to the app container by the gallery autorun. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * iOS: adopt toasts across app flows (copy, feedback sent, reconnected) One copy-confirmation vocabulary via Toast.copied(): chat bubble copy (threaded through ChatRowActions.notifyCopied to respect the snapshot boundary), chat detail Copy All, artifact copy contents/path, terminal text-sheet Copy All (replacing its button morph), and artifact gallery Copy path (closure-threaded through lazy rows). Feedback submit success now toasts after the composer dismisses; regaining a Mac connection toasts Reconnected. Ad-hoc UINotificationFeedbackGenerator calls at these sites removed: the toast supplies the haptic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * iOS: DEBUG remote toast trigger for agents and scripts ToastDebugTrigger (DEBUG builds only) listens for darwin notifications and presents a toast from a JSON spec in the app's defaults, so a shell can exercise the toast layer inside the running app without touch input: simctl spawn defaults write + notifyutil -p. ios/scripts/toast-debug.sh wraps both (style/title/message/icon/placement/persistent/action, plus --dismiss-all). Registered by the toast window host, torn down with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Toast chrome: material plate under the glass for legibility Bare Liquid Glass (.regular, untinted) is nearly transparent over busy content; over a terminal screen the toast text disappeared entirely. All cards now sit on a .regularMaterial plate (vibrancy-aware in both modes); on iOS 26 the glass layer rides on top purely for its rim and specular response. Verified over the dark workspace UI and in light mode via the debug trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Settings: self-serve toast demo with delay (Developer section) Extracts the demo choreography into ToastDemo (DEBUG-only, shared by the gallery autorun, the remote trigger's new --demo verb, and Settings). Developer section gains Toast Gallery (sheet), Run Toast Demo, and a 0-30s delay stepper (persisted via AppStorage) so you can start the demo, navigate to any screen, and watch it play there. Strings localized en+ja. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Gate the toast system behind a Beta Features flag (off by default) ToastCenter.isEnabled (persisted, default false) drops every present() while off and is surfaced as Settings > Beta Features > Toasts. Off-state falls back to the pre-toast surfaces, restored from main: the dismissible bottom workspace-action banner (title+reason joined), ChatScreen's inline error banner with its VoiceOver announcement, the text sheet's Copied button morph, and the success haptics at copy/feedback sites. The DEBUG gallery env force-enables the flag so harness runs stay meaningful. Verified live on the sim: trigger drops with flag off, presents with flag on; 16 ToastCenter tests incl. the disabled gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address review findings: initial-delivery bridges, immutable Toast, scoped holds CodeRabbit majors: onChange(initial: true) so a pre-existing chat error and an already-connected mount are handled; Toast fields are lets; the default coalescing key joins on a unit separator instead of an aliasable delimiter; interaction holds are scoped to the presented toast id (a straggling gesture from a departed toast can no longer pause or resume its successor); DocC on the four factories; gallery autorun propagates cancellation. Skipped Greptile's caseless-enum-namespace nit: that is the repo's established idiom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: cmux reload-cloud <cmux-reload-cloud@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 2 个月前 | |
Add iOS App Store production lane (#7644) * Add iOS App Store release lane * Fix App Store workflow build output env * Harden App Store release lane dispatch * Fix App Store ASC workflow build-number args * Harden App Store workflow operations * Block App Store external purchase links * Require Apple sign-in entitlement for App Store uploads * Localize App Store billing unavailable copy | 3 个月前 | |
Upload cmux INTERNAL for every main push (#8694) * Test TestFlight upload for every main push * Upload cmux INTERNAL for every main push * Harden TestFlight upload ordering * Remove stale TestFlight schedule event | 2 个月前 | |
fix: validate App Store device screenshot directories | 2 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 3 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 4 个月前 | ||
| 3 个月前 | ||
| 3 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 3 个月前 | ||
| 2 个月前 | ||
| 3 个月前 | ||
| 2 个月前 | ||
| 2 个月前 |