diff --git a/.github/workflows/ci-macos-compat.yml b/.github/workflows/ci-macos-compat.yml index 08c859d08..2e0cc11ba 100644 --- a/.github/workflows/ci-macos-compat.yml +++ b/.github/workflows/ci-macos-compat.yml @@ -161,21 +161,18 @@ jobs: # on a macOS version other than the one `release.yml` ships from. That is # the build + smoke steps below, and they now run FIRST and gate the job. # - # Previously the full ~1,500-test unit suite ran first and gated. That was - # backwards twice over: + # The full ~1,500-test unit suite runs after the build and smoke steps, not + # before them, for two reasons: # # 1. Those tests are OS-agnostic logic and already run on every PR in - # ci.yml. Re-running them on two more macOS versions produced no - # unique signal. - # 2. They failed on the macos-15 runner (async starvation, see - # QUARANTINED_ON_COMPAT in scripts/ci-run-unit-tests.sh), so steps - # 13-15 -- the build and smoke test, the actual point -- were SKIPPED - # on every run. The compatibility check never once verified - # compatibility, for over a week, while five successive attempts to - # quarantine the failing tests each fixed what was in front of them - # and revealed the next thing. + # ci.yml. Re-running them on more macOS versions gives no unique + # signal. + # 2. They can fail on the macos-15 runner (async starvation, see + # QUARANTINED_ON_COMPAT in scripts/ci-run-unit-tests.sh). If they + # gated the job, a failure would skip the build and smoke steps, + # the actual point, and the job would never verify compatibility. # - # Unit tests still run here, after, and are now informational: a failure + # Unit tests still run here, after, and are informational: a failure # marks the step red without failing the job. That keeps the data (and the # uploaded xcresult bundles) without letting OS-agnostic test noise mask # the one result this job exists to produce. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50e3b74be..16f9eb2f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -780,8 +780,8 @@ jobs: # Independent of unit-tests: same "programa" scheme app build-app already # produces, so this reuses that artifact instead of paying for its own - # ~3 min build. Split out so it no longer serializes behind the unit-test - # shards either. + # ~3 min build. Split out so it does not serialize behind the unit-test + # shards. theme-picker-helper-regression: needs: - build-app diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fae9b823..51a1d4ad1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -348,17 +348,15 @@ jobs: env: APPLE_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISION_PROFILE_BASE64 }} run: | - # REQUIRED now, despite the name of the check below. The app declares - # restricted entitlements (CloudKit), and an app carrying those with no - # embedded profile is killed by AMFI at launch for every user (POSIX 163) - # after signing and notarizing cleanly. This step still tolerates the - # secret being absent so the failure is reported by - # verify-provision-profile.sh with a full explanation, rather than as a - # bare base64 error here — that script is the gate, and it fails the - # build. Do not "fix" a red verify step by removing it. + # The app declares no restricted (com.apple.developer.*) entitlements + # today, so the profile is a safety net. Adding a restricted + # entitlement makes it required: without an embedded profile AMFI + # kills the app at launch (POSIX 163) even though signing and + # notarization pass. verify-provision-profile.sh is the gate for that + # case; do not "fix" a red verify step by removing it. if [ -z "$APPLE_PROVISION_PROFILE_BASE64" ]; then echo "No APPLE_PROVISION_PROFILE_BASE64 secret set; skipping profile embedding." - echo "The build will fail verification: this app declares restricted entitlements." + echo "verify-provision-profile.sh fails the build if the app declares restricted entitlements." echo "PROGRAMA_PROVISION_PROFILE=" >> "$GITHUB_ENV" exit 0 fi diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml deleted file mode 100644 index 21299aadf..000000000 --- a/.github/workflows/test-depot.yml +++ /dev/null @@ -1,166 +0,0 @@ -name: Run tests on Depot - -on: - workflow_dispatch: - inputs: - ref: - description: Branch or SHA to test - required: false - default: "" - skip_unit_tests: - description: Skip unit tests (run only UI tests) - required: false - default: false - type: boolean - skip_ui_tests: - description: Skip UI tests (run only unit tests) - required: false - default: false - type: boolean - test_filter: - description: "Run specific UI test class (e.g. UpdatePillUITests) or empty for all" - required: false - default: "" - test_timeout: - description: "Per-test timeout in seconds (default: 120)" - required: false - default: "120" - -permissions: - contents: read - -jobs: - tests: - runs-on: macos-15 - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: ${{ github.event.inputs.ref || github.ref }} - submodules: recursive - - - name: Select Xcode - run: | - set -euo pipefail - if [ -d "/Applications/Xcode.app/Contents/Developer" ]; then - XCODE_DIR="/Applications/Xcode.app/Contents/Developer" - else - XCODE_APP="$(ls -d /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)" - if [ -n "$XCODE_APP" ]; then - XCODE_DIR="$XCODE_APP/Contents/Developer" - else - echo "No Xcode.app found under /Applications" >&2 - exit 1 - fi - fi - sudo xcode-select -s "$XCODE_DIR" - echo "DEVELOPER_DIR=$XCODE_DIR" >> "$GITHUB_ENV" - export DEVELOPER_DIR="$XCODE_DIR" - xcodebuild -version - xcrun --sdk macosx --show-sdk-path - - - name: Download pre-built GhosttyKit.xcframework - env: - GHOSTTYKIT_DOWNLOAD_RETRIES: 29 - GHOSTTYKIT_DOWNLOAD_RETRY_DELAY: 20 - run: ./scripts/download-prebuilt-ghosttykit.sh - - - name: Install zig - run: ./scripts/install-zig.sh - - - name: Create virtual display - run: | - set -euo pipefail - echo "=== Display before ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - echo "" - clang -framework Foundation -framework CoreGraphics \ - -o /tmp/create-virtual-display scripts/create-virtual-display.m - /tmp/create-virtual-display & - VDISPLAY_PID=$! - echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" - sleep 3 - echo "=== Display after ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - - - name: Clean DerivedData - run: | - rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* - - - name: Resolve Swift packages - run: | - set -euo pipefail - SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - rm -rf "$SOURCE_PACKAGES_DIR" - mkdir -p "$SOURCE_PACKAGES_DIR" - - for attempt in 1 2 3; do - if xcodebuild -project GhosttyTabs.xcodeproj -scheme programa-unit -configuration Debug \ - -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ - -resolvePackageDependencies; then - exit 0 - fi - if [ "$attempt" -eq 3 ]; then - echo "Failed to resolve Swift packages after 3 attempts" >&2 - exit 1 - fi - echo "Package resolution failed on attempt $attempt, retrying..." - sleep $((attempt * 5)) - done - - - name: Run unit tests - if: ${{ !inputs.skip_unit_tests }} - run: | - set -euo pipefail - SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - run_unit_tests() { - xcodebuild -project GhosttyTabs.xcodeproj -scheme programa-unit -configuration Debug \ - -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ - -disableAutomaticPackageResolution \ - -destination "platform=macOS" test 2>&1 - } - - set +e - OUTPUT=$(run_unit_tests) - EXIT_CODE=$? - set -e - - # SwiftPM binary artifact resolution can occasionally fail with - # "Could not resolve package dependencies". Retry once after clearing - # SwiftPM/DerivedData caches to recover from transient corruption. - if [ "$EXIT_CODE" -ne 0 ] && echo "$OUTPUT" | grep -q "Could not resolve package dependencies"; then - echo "SwiftPM package resolution failed, clearing caches and retrying once" - rm -rf ~/Library/Caches/org.swift.swiftpm - rm -rf ~/Library/Developer/Xcode/DerivedData/GhosttyTabs-* - set +e - OUTPUT=$(run_unit_tests) - EXIT_CODE=$? - set -e - fi - - echo "$OUTPUT" - exit "$EXIT_CODE" - - - name: Run UI tests - if: ${{ !inputs.skip_ui_tests }} - env: - TEST_FILTER: ${{ inputs.test_filter }} - TEST_TIMEOUT: ${{ inputs.test_timeout || '120' }} - run: | - set -euo pipefail - SOURCE_PACKAGES_DIR="$PWD/.ci-source-packages" - - # Build the -only-testing argument - if [ -n "$TEST_FILTER" ]; then - ONLY_TESTING="-only-testing:programaUITests/$TEST_FILTER" - else - ONLY_TESTING="-only-testing:programaUITests" - fi - - xcodebuild -project GhosttyTabs.xcodeproj -scheme programa -configuration Debug \ - -clonedSourcePackagesDirPath "$SOURCE_PACKAGES_DIR" \ - -disableAutomaticPackageResolution \ - -destination "platform=macOS" \ - -maximum-test-execution-time-allowance "$TEST_TIMEOUT" \ - $ONLY_TESTING test diff --git a/.gitignore b/.gitignore index dae9cd33f..61ce5c63f 100644 --- a/.gitignore +++ b/.gitignore @@ -47,22 +47,10 @@ tests/visual_report.html # Local scratch (screenshots, etc.) tmp/ tmp-*/ -node_modules # Working plan documents. These are scratch thinking, not deliverables, and they # accumulate fast enough to bury real docs in review diffs. -# -# `plans/*` rather than `plans/`: git cannot re-include a file whose parent -# DIRECTORY is excluded, so the negation below only works against a glob on the -# directory's contents. Do not "simplify" this to `plans/`. -# -# golden-tumbling-gray.md stays tracked because shipped code cites it as the -# rationale for a security rule — Sources/MobileBridge/MobileBridgePush.swift -# points at its "Security implications" section for why prompt and output text -# never goes into a notification payload. Untracking it would leave that comment -# pointing at a file nobody who clones the repo has. -plans/* -!plans/golden-tumbling-gray.md +plans/ # Agent-written recaps, written into the repo root by `programa recap`. The CLI # also drops a self-ignoring .gitignore inside the directory on first use; this diff --git a/Resources/Info.plist b/Resources/Info.plist index 6e434dd8d..961ad76d5 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -113,21 +113,6 @@ - NSAppTransportSecurity - - NSAllowsArbitraryLoadsInWebContent - - NSExceptionDomains - - programa-loopback.localtest.me - - NSExceptionAllowsInsecureHTTPLoads - - NSIncludesSubdomains - - - - SUAutomaticallyUpdate SUEnableAutomaticChecks diff --git a/SKILL.md b/SKILL.md index 6bf94e98a..c13b95d25 100644 --- a/SKILL.md +++ b/SKILL.md @@ -218,21 +218,9 @@ Use the same panel that renders `programa markdown open` for it, so lean on its ## Browser work -Two browsers, two jobs. Do not reach for a Chrome extension for either. +Do not reach for a Chrome extension. Programa has no built-in browser; browser work goes through Aside. -- **Local previews, smoke tests, screenshots you read back, DOM checks, console errors:** use programa's embedded browser. It opens beside your pane, keeps its own profile, and never moves the user's focus: - - ```bash - programa browser open-split http://localhost:3000 # prints the new surface id - programa browser --surface surface:7 snapshot --interactive # interactive elements only - programa browser --surface surface:7 click "button.submit" --snapshot-after - programa browser --surface surface:7 screenshot --out /tmp/after.png - programa browser --surface surface:7 tab close - ``` - - `programa browser --help` lists the rest (wait, fill, eval, cookies, console, errors). Network routing, viewport control, and raw input injection are not available on WKWebView, so there are no commands for them. Over MCP the same calls are the `browser_*` tools of `programa-mcp`. - -- **Logged-in sites, private dashboards, CI logs, anything that needs the user's real browser profile:** use Aside through its MCP server if it is registered (tools from the `aside` server, or `aside-devtools` for raw Chrome DevTools control), or delegate a whole task from the shell: +- **Previews, smoke tests, screenshots, DOM checks, console errors, logged-in sites, private dashboards, CI logs:** use Aside through its MCP server if it is registered (tools from the `aside` server, or `aside-devtools` for raw Chrome DevTools control), or delegate a whole task from the shell: ```bash aside "Open the staging dashboard and tell me whether the last deploy is green" diff --git a/Sources/ProgramStatus.swift b/Sources/ProgramStatus.swift index f10efe276..45a76ec51 100644 --- a/Sources/ProgramStatus.swift +++ b/Sources/ProgramStatus.swift @@ -2,7 +2,7 @@ // // Ghostty's parser has already validated every report before the action reaches Swift. The // checks here are defense in depth, so the store never holds a record the sidebar or the socket -// could not safely show. See docs/plans/osc7501-program-status.md (D6, D7). +// could not safely show. See docs/program-status.md. import Foundation enum ProgramStatusState: String, Sendable, CaseIterable { diff --git a/docs/README.md b/docs/README.md index 13acc3675..490d88b16 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,7 @@ ## Plans -Design and planning documents. Each starts with a `Status:` line. +Design and planning documents. Most start with a `Status:` line. - [plans/agent-events.md](plans/agent-events.md): normalized agent lifecycle events. - [plans/agent-state-unification.md](plans/agent-state-unification.md): one source of agent state and one sidebar indicator. diff --git a/docs/assets/built-in-browser.png b/docs/assets/built-in-browser.png deleted file mode 100644 index 21c76f172..000000000 Binary files a/docs/assets/built-in-browser.png and /dev/null differ diff --git a/docs/assets/claude-code-teams.png b/docs/assets/claude-code-teams.png deleted file mode 100644 index 40eafa9eb..000000000 Binary files a/docs/assets/claude-code-teams.png and /dev/null differ diff --git a/docs/assets/ko/built-in-browser.png b/docs/assets/ko/built-in-browser.png deleted file mode 100644 index 89877bf86..000000000 Binary files a/docs/assets/ko/built-in-browser.png and /dev/null differ diff --git a/docs/assets/ko/notification-rings.png b/docs/assets/ko/notification-rings.png deleted file mode 100644 index b8a602c54..000000000 Binary files a/docs/assets/ko/notification-rings.png and /dev/null differ diff --git a/docs/assets/ko/sidebar-notification-badge.png b/docs/assets/ko/sidebar-notification-badge.png deleted file mode 100644 index 58cc116bd..000000000 Binary files a/docs/assets/ko/sidebar-notification-badge.png and /dev/null differ diff --git a/docs/assets/notification-rings.png b/docs/assets/notification-rings.png deleted file mode 100644 index db3dab0d2..000000000 Binary files a/docs/assets/notification-rings.png and /dev/null differ diff --git a/docs/assets/sidebar-notification-badge.png b/docs/assets/sidebar-notification-badge.png deleted file mode 100644 index 155482865..000000000 Binary files a/docs/assets/sidebar-notification-badge.png and /dev/null differ diff --git a/docs/assets/ssh.png b/docs/assets/ssh.png deleted file mode 100644 index aa2c736ab..000000000 Binary files a/docs/assets/ssh.png and /dev/null differ diff --git a/docs/assets/vertical-horizontal-tabs-and-splits.png b/docs/assets/vertical-horizontal-tabs-and-splits.png deleted file mode 100644 index c77690f4c..000000000 Binary files a/docs/assets/vertical-horizontal-tabs-and-splits.png and /dev/null differ diff --git a/docs/ghostty-fork.md b/docs/ghostty-fork.md index 5a36576f3..5dcce76d4 100644 --- a/docs/ghostty-fork.md +++ b/docs/ghostty-fork.md @@ -194,12 +194,12 @@ tend to conflict together during rebases. ### cursor-click-to-move respects OSC 133 click-to-move - Was local in the fork as `10a585754`. -- Landed upstream as `bb646926f`, so it is no longer carried as a fork-only patch. +- Landed upstream as `bb646926f`, so the fork does not carry it as a separate patch. ### zsh prompt redraw follow-ups - Were local in the fork as `8ade43ce5`, `0cf559581`, `312c7b23a`, and `404a3f175`. -- Dropped during the March 30, 2026 rebase because newer Ghostty prompt-marking changes on the refreshed base superseded these fork-only zsh redraw patches, so Programa no longer carries them separately. +- Not carried: newer Ghostty prompt-marking changes on the current base supersede these fork-only zsh redraw patches (dropped in the March 30, 2026 rebase). ### initial focus seeding and DECSET 1004 startup behavior diff --git a/docs/images/liquid-glass/all-surfaces-off-light-browser.png b/docs/images/liquid-glass/all-surfaces-off-light-browser.png deleted file mode 100644 index 623609388..000000000 Binary files a/docs/images/liquid-glass/all-surfaces-off-light-browser.png and /dev/null differ diff --git a/docs/images/liquid-glass/all-surfaces-on-light-browser.png b/docs/images/liquid-glass/all-surfaces-on-light-browser.png deleted file mode 100644 index eb6132afb..000000000 Binary files a/docs/images/liquid-glass/all-surfaces-on-light-browser.png and /dev/null differ diff --git a/docs/images/liquid-glass/default-dark-terminal.png b/docs/images/liquid-glass/default-dark-terminal.png deleted file mode 100644 index c6c4221ed..000000000 Binary files a/docs/images/liquid-glass/default-dark-terminal.png and /dev/null differ diff --git a/docs/images/liquid-glass/overlay-on-dark-omnibar.png b/docs/images/liquid-glass/overlay-on-dark-omnibar.png deleted file mode 100644 index ead527130..000000000 Binary files a/docs/images/liquid-glass/overlay-on-dark-omnibar.png and /dev/null differ diff --git a/docs/plans/browser-developer-tools.md b/docs/plans/browser-developer-tools.md deleted file mode 100644 index 005e840a2..000000000 --- a/docs/plans/browser-developer-tools.md +++ /dev/null @@ -1,69 +0,0 @@ -# Browser developer tools and the hosted inspector dock - -**Status: kept.** This feature was scoped for removal in the 2026-09-02 reductive pass, together -with browser data import, browser extensions, and React Grab, and was not removed. Nothing under -this heading has been deleted; this doc records why, so a future removal does not repeat the -discovery work. - -## What it does (still present) - -Cmd+Option+I (Safari default) toggles WebKit's native Web Inspector attached to (or detached from) -a browser panel's page. A palette command, a `showBrowserJavaScriptConsole` shortcut, and an -AppDelegate-routed shortcut all reach it. When docked, the inspector shares the same NSView -hierarchy as the page content, and the app actively manages the divider between them (drag-resize, -side detection, layout reflow on window/pane changes). The automation API and this feature are -independent — nothing under `surface.browser.*` depends on `toggleBrowserDeveloperTools`. - -## Why the implementer stopped - -The prompt estimated the hosted-inspector code as roughly lines 67-772 of -`Sources/Panels/WebViewRepresentable.swift`. Grepping the actual file found `inspector`-related -identifiers as late as line 2160 of a 2251-line file — the docking/geometry logic is not confined -to a clean sub-range, it is threaded through the same `HostContainerView` coordinator code that -positions and resizes the page's own WKWebView. `Sources/BrowserWindowHostView.swift` (1,067 -lines) is almost entirely one class, `WindowBrowserHostView`, that both hosts the browser page -portal (a file explicitly listed as must-keep-working for this cluster) and manages inspector dock -geometry — the two are not separable into distinct files or extensions the way React Grab's panel -routing was. `Sources/BrowserWindowPortal.swift` (2,061 lines) and `Sources/WebKitSubviewTransfer.swift` -are in the same position. `Sources/Panels/InspectorDock.swift` is a small (116-line) namespace of -pure geometry/detection helpers, but it is called from all three of the files above, so deleting it -requires reworking call sites inside code paths explicitly marked as must-stay-working, not just -deleting a self-contained file. - -`Sources/Panels/BrowserPanel+DeveloperTools.swift` (707 lines) is itself a `BrowserPanel` extension -that calls into `InspectorDock` and into members defined on `WindowBrowserHostView` -(`setPreferredHostedInspectorWidth`, `setHostedInspectorFrontendWebView`, -`scheduleHostedInspectorDividerReapply`, `scheduleHostedInspectorDockConfigurationSync`) — deleting -it without also removing those `WindowBrowserHostView` members leaves dead public API on a -must-keep-working class; removing those members requires editing the geometry/layout code in -`BrowserWindowHostView.swift` directly. - -Given the size (roughly 6,300 combined lines across the five files above) and the risk of -regressing the core browser panel — which this cluster's brief explicitly requires to keep working -— the implementer judged this outside what could be done reliably without extensive manual and -automated testing of browser panel layout, focus, and resize behavior, and stopped per the -brief's own guardrail: "STOP and report instead of guessing when... a removal would change the -behavior of a feature outside this cluster." - -## What a future attempt should do differently - -1. Budget this as its own task, separate from the other three sub-features in this cluster — it is - larger than all three combined. -2. Start from `Sources/Panels/InspectorDock.swift`'s own doc comment, which already names every - call site that duplicated its logic before consolidation (`BrowserPanel.swift`, - `BrowserPanelView.swift`'s `WebViewRepresentable.Coordinator.HostContainerView`, and - `BrowserWindowPortal.swift`'s `WindowBrowserHostView`) — that comment is close to a complete map - of what needs to change. -3. Decide up front whether to (a) keep WebKit's native Web Inspector fully wired but delete only - the app's UI entry points (shortcut, palette command, toolbar button), leaving the dock-geometry - code in place as dead-but-safe, or (b) do the full removal including geometry code. Option (a) - is much lower risk and still satisfies "remove everything not essential" from the user's - perspective, since WebKit's own right-click "Inspect Element" is the only remaining way in. -4. If doing (b), write or run the existing `BrowserPanelTests.swift` `WindowBrowserHostViewTests`, - `BrowserPanelHostContainerViewTests`, and `BrowserWindowPortalLifecycleTests` classes before and - after each edit — they are the closest thing to a regression safety net for this code. - -## Shared portal code - -`WebKitSubviewTransfer.swift` is shared browser/terminal portal infrastructure that replaced earlier -duplicated transfer code. It is not inspector-specific and should not be deleted wholesale. diff --git a/docs/plans/detached-sessions.md b/docs/plans/detached-sessions.md index ade8dcaaa..c8a910e72 100644 --- a/docs/plans/detached-sessions.md +++ b/docs/plans/detached-sessions.md @@ -49,7 +49,7 @@ POC_RESULT: PASS Both checks held: no SIGHUP reached the child, and the shell stayed fully interactive through the escrowed fd (marker echo round-trip). The original Phase 0 spike (2-3 days probing -`ghostty/src/termio` for a spawn-side seam) is **no longer the gating question**. +`ghostty/src/termio` for a spawn-side seam) is not the gating question. ### Revised layering diff --git a/docs/plans/rust-core-concepts.md b/docs/plans/rust-core-concepts.md index 98571951d..cec4ed66e 100644 --- a/docs/plans/rust-core-concepts.md +++ b/docs/plans/rust-core-concepts.md @@ -340,7 +340,7 @@ have to reimplement or find an equivalent for: `ghostty_surface_set_pty_tee_cb` callback (runs pre-VT-parse) backs the session WAL and superseded an older Programa-only output-tap export (intentionally not restored). -Also upstreamed (no longer fork-only): cursor-click-to-move honoring OSC 133. Dropped as +Also upstreamed (not fork-only): cursor-click-to-move honoring OSC 133. Dropped as superseded: several zsh prompt-redraw patches (upstream's newer prompt-marking made them redundant after the 2026-03-30 rebase); an older initial-focus-seeding/DECSET 1004 patch (replaced by post-create focus synchronization, which the current fork preserves as items 6/10's @@ -392,37 +392,8 @@ built by the same workflow on tag push). Diagnostics log: `[[release-diagnostics-log]]` — ask for it first on release bug reports); `programa-update.log` for update-flow-specific bugs. (Sparkle is the updater: `Sources/Update/UpdateController.swift` drives `SPUUpdater` with a custom delegate and UI in `Sources/Update/`; the feed points at the GitHub `rolling` release described in CLAUDE.md.) -## 11. Things removed on purpose (`docs/removed/*.md`) - -Reductive pass of 2026-09-02, base commit `903027ccef`. Every entry names the commit to restore -from (`git checkout 903027ccef -- `) and a "what we learned" section (not reproduced here -— read the individual file before re-adding). - -- **`applescript.md`** — AppleScript support (`Sources/AppleScriptSupport.swift`, - `Resources/programa.sdef`). -- **`browser-data-import.md`** — browser data import wizard. -- **`browser-developer-tools.md`** (in `docs/plans/`): **not actually removed**; scoped for the same pass but the - implementer stopped and reported back instead of guessing. The hosted inspector dock is still - live (`Sources/Panels/InspectorDock.swift`). -- **`browser-extensions.md`** — browser extension support - (`BrowserExtensionManager.swift`, `BrowserExtensionAdapters.swift`). -- **`browser-react-grab.md`** — React Grab (`Sources/Panels/ReactGrab.swift`). -- **`custom-notification-sounds.md`** — custom notification sound files. -- **`inline-vscode.md`** — inline VS Code / `serve-web` integration - (`Sources/VSCodeIntegration.swift`). -- **`mobile-bridge-and-ios.md`** — Mobile Bridge and an iOS companion app - (`Sources/MobileBridge`, `ios/`, `vendor/CmuxIrohTransport`, `vendor/CMUXMobileCore`, iOS - TestFlight CI workflows). -- **`ssh-remote-workspaces.md`** — SSH remote workspaces: the largest removal, a full remote - daemon/session/proxy stack (`Sources/Workspace+Remote.swift` and ~15 sibling files, - `CLI/CLI+SSH.swift`, a `daemon/` directory, `docs/remote-daemon-spec.md`, ~15 `tests_v2/ - test_ssh_remote_*.py` files). Notable because `docs/plans/detached-sessions.md` explicitly - models its local escrow design on this removed feature's `session.*` naming/resize semantics — - the removed remote daemon is still a live design reference even though its code is gone. - -Core kept per the removal pass's own summary (`docs/removed/README.md`): the Ghostty terminal, -workspaces and splits, the sidebar, agent status detection and hooks, notifications, the browser -panel and its automation API, the diff review panel, worktrees and race, layouts, the markdown -recap panel, the CLI, socket API, and MCP server, updates, session persistence and escrow, the -Claude quota footer, and the local tmux-compat CLI — i.e. everything covered in §1-§10 above is -the deliberately-retained surface a cross-platform core spec should target. +## 11. Things removed on purpose + +Removed features, with the commit to restore from and what was learned, are listed in +[`docs/removed/README.md`](../removed/README.md). Nothing in the removed set is part of the +surface a cross-platform core targets: §1-§10 above cover the retained surface. diff --git a/docs/release.md b/docs/release.md index 3539edbfb..1ff2d04ea 100644 --- a/docs/release.md +++ b/docs/release.md @@ -66,7 +66,7 @@ repository secret with the same name is ignored by the release job. | `APPLE_APP_SPECIFIC_PASSWORD` | App-specific password for notarization | | `APPLE_TEAM_ID` | Apple team id | | `SPARKLE_PRIVATE_KEY` | Signs the appcast and enclosures; the workflow derives the public key embedded in the app from it. The release fails without it | -| `APPLE_PROVISION_PROFILE_BASE64` | Provisioning profile embedded in the app. Required: the app declares restricted entitlements (CloudKit), and without an embedded profile macOS kills it at launch even though signing and notarization succeed. The profile verification step fails the build when the secret is missing | +| `APPLE_PROVISION_PROFILE_BASE64` | Provisioning profile embedded in the app. Kept as a safety net: no entitlement in `programa.entitlements` is Apple-restricted now, so the app launches without a profile and an embedded one is harmless. If a restricted `com.apple.developer.*` entitlement is added back, the profile is required, because macOS kills the app at launch without it even though signing and notarization succeed. The workflow step still fails the build when the secret is missing. Candidate for removal once a release is confirmed to launch without it | ## Milestone version bumps diff --git a/docs/removed/README.md b/docs/removed/README.md index 17373b20f..778704286 100644 --- a/docs/removed/README.md +++ b/docs/removed/README.md @@ -2,13 +2,13 @@ Each file here records one feature that was deleted from Programa on purpose, so it can be brought back with what we learned the first time. Every doc names the last commit that contained the feature and the exact `git checkout -- ` command that restores its files. Restoring the files is the easy part; read the "What we learned" section before wiring it back in. -Reductive pass of 2026-09-02, base commit 903027ccef. Core kept: the Ghostty terminal, workspaces and splits, the sidebar, agent status detection and hooks, notifications, the browser panel and its automation API, the diff review panel, worktrees and race, layouts, the markdown recap panel, the CLI, socket API, and MCP server, updates, session persistence and escrow, the Claude quota footer, and the local tmux-compat CLI. +Reductive pass of 2026-09-02, base commit 903027ccef. Core kept: the Ghostty terminal, workspaces and splits, the sidebar, agent status detection and hooks, notifications, the diff review panel, worktrees and race, layouts, the markdown recap panel, the CLI, socket API, and MCP server, updates, session persistence and escrow, the Claude quota footer, and the local tmux-compat CLI. | Feature | Doc | Approx. lines removed | |---|---|---| | SSH remote workspaces and the Go daemon | [ssh-remote-workspaces.md](ssh-remote-workspaces.md) | 25,600 | | Mobile bridge, iOS companion, vendored iroh packages | [mobile-bridge-and-ios.md](mobile-bridge-and-ios.md) | 67,000 | -| Built-in browser and its automation API | [built-in-browser.md](built-in-browser.md) | LINES | +| Built-in browser and its automation API | [built-in-browser.md](built-in-browser.md) | 35,000 | | Browser data import wizard | [browser-data-import.md](browser-data-import.md) | 3,800 | | Browser extensions | [browser-extensions.md](browser-extensions.md) | 950 | | Browser React Grab overlay | [browser-react-grab.md](browser-react-grab.md) | 470 | diff --git a/docs/socket-api.md b/docs/socket-api.md index 7db6c992e..05872b4bc 100644 --- a/docs/socket-api.md +++ b/docs/socket-api.md @@ -943,8 +943,8 @@ a live `system.capabilities` response. **The rule this exists to enforce: a v2 handler's params, result shape, or error codes change only after `contracts/v2/methods.json` changes first**, then regenerate (`python3 scripts/gen-v2-contract.py`) before touching the handler. This keeps the CLI's method -names and `system.capabilities` from drifting away from the handlers (the SSH remote-workspaces -effort is the cautionary case; see `docs/removed/ssh-remote-workspaces.md`, "What we learned"). +names and `system.capabilities` from drifting away from the handlers (see `docs/removed/ssh-remote-workspaces.md`, +"What we learned", for the drift this prevents). The contract's params, required lists, threading and `focus_intent` were extracted from the handler bodies once. The generator does not re-derive them, so a handler change that is not diff --git a/programa.entitlements b/programa.entitlements index 821d1fc94..d4a4d70a2 100644 --- a/programa.entitlements +++ b/programa.entitlements @@ -10,13 +10,5 @@ com.apple.security.automation.apple-events - com.apple.developer.icloud-services - - CloudKit - - com.apple.developer.icloud-container-identifiers - - iCloud.com.darkroom.programa - diff --git a/programaTests/AgentActivityStateTests.swift b/programaTests/AgentActivityStateTests.swift index 9892dc877..b803585cf 100644 --- a/programaTests/AgentActivityStateTests.swift +++ b/programaTests/AgentActivityStateTests.swift @@ -559,7 +559,7 @@ final class AgentActivityStateTests: XCTestCase { XCTAssertFalse(freshIndicator?.isStale ?? true) } - // MARK: - OSC 7501 program authority (docs/plans/osc7501-program-status.md D7) + // MARK: - OSC 7501 program authority (docs/program-status.md) func testProgramActiveBlocksHooksAndInferredWrites() { let workspace = Workspace(title: "Test") diff --git a/scripts/measure-glass-memory.sh b/scripts/measure-glass-memory.sh deleted file mode 100755 index e121f80b9..000000000 --- a/scripts/measure-glass-memory.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -usage() { - echo "usage: PROGRAMA_SOCKET_PATH=/tmp/programa-debug-.sock $0 [output-directory]" >&2 -} - -surface="${1:-}" -output_dir="${2:-/tmp/programa-glass-memory}" -socket_path="${PROGRAMA_SOCKET_PATH:-}" - -case "$surface" in - window|sidebar|tabBar|overlays) ;; - *) usage; exit 2 ;; -esac - -if [[ -z "$socket_path" || ! -S "$socket_path" ]]; then - echo "PROGRAMA_SOCKET_PATH must point to a running tagged Debug app socket" >&2 - exit 2 -fi - -pid="$(lsof -t "$socket_path" | head -n 1)" -if [[ -z "$pid" ]]; then - echo "No Programa process owns $socket_path" >&2 - exit 1 -fi - -mkdir -p "$output_dir" - -set_surface() { - local target="$1" - local enabled="$2" - local response - response="$(printf '{"id":1,"method":"debug.glass.set","params":{"surface":"%s","enabled":%s}}\n' "$target" "$enabled" | nc -w 2 -U "$socket_path")" - if ! printf '%s\n' "$response" | grep -Eq '"ok"[[:space:]]*:[[:space:]]*true'; then - echo "debug.glass.set failed: $response" >&2 - exit 1 - fi -} - -capture() { - local state="$1" - local prefix="$output_dir/${surface}-${state}" - footprint --pid "$pid" --format bytes --noCategories --json "$prefix.json" > "$prefix.txt" - echo "$state snapshot: $prefix.txt" - jq -r '" total footprint: \(.["total footprint"]) B"' "$prefix.json" -} - -for candidate in window sidebar tabBar overlays; do - set_surface "$candidate" false -done -sleep 2 -capture off - -set_surface "$surface" true -sleep 2 -capture on - -set_surface "$surface" false - -off_total="$(jq -r '.["total footprint"]' "$output_dir/${surface}-off.json")" -on_total="$(jq -r '.["total footprint"]' "$output_dir/${surface}-on.json")" -delta_bytes="$((on_total - off_total))" -ratio="$(awk -v on="$on_total" -v off="$off_total" 'BEGIN { printf "%.4f", on / off }')" - -echo "OFF total: $off_total B" -echo "ON total: $on_total B" -echo "ON - OFF: $delta_bytes B" -echo "ON / OFF: ${ratio}x" -echo "Snapshots are report-only in Phase 1; compare equal-idle-state deltas for later phase gates." diff --git a/scripts/run-tests-v2-ci.sh b/scripts/run-tests-v2-ci.sh index 1ccdbe424..61ebae3c7 100755 --- a/scripts/run-tests-v2-ci.sh +++ b/scripts/run-tests-v2-ci.sh @@ -3,7 +3,7 @@ set -euo pipefail # CI runner for a curated stable subset of tests_v2 (see tests_v2/ci_subset.txt). # -# Unlike scripts/run-tests-v2.sh (which is guarded to only run on the programa-vm +# Unlike scripts/run-tests-v2.sh (which is guarded to only run on a disposable VM # and runs the entire tests_v2 suite), this script is intended to run as a # required PR-gating job on GitHub-hosted macOS runners. It expects the # `programa` scheme to already be built (see the `socket-integration-tests` job in diff --git a/scripts/run-tests-v2.sh b/scripts/run-tests-v2.sh index f5cdb9f36..b82a516ad 100755 --- a/scripts/run-tests-v2.sh +++ b/scripts/run-tests-v2.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -# This runner is intended for the UTM macOS VM (ssh cmux-vm). -# It is intentionally guarded so we don't accidentally kill the host user's programa instances. -if [ "$(id -un)" != "cmux" ]; then - echo "ERROR: This script is intended to be run on the cmux-vm (user: cmux)." >&2 - echo "Run via: ssh cmux-vm 'cd /Users/cmux/GhosttyTabs && ./scripts/run-tests-v2.sh'" >&2 +# This runner targets a disposable macOS VM. It kills running Programa instances, +# so it refuses to run unless the caller opts in with PROGRAMA_TESTS_V2_VM=1. +if [ "${PROGRAMA_TESTS_V2_VM:-}" != "1" ]; then + echo "ERROR: This script kills running Programa instances and is meant for a disposable VM." >&2 + echo "Run via: PROGRAMA_TESTS_V2_VM=1 ./scripts/run-tests-v2.sh" >&2 exit 2 fi diff --git a/scripts/sign-release-app.sh b/scripts/sign-release-app.sh index 61c2435e3..643d61c68 100755 --- a/scripts/sign-release-app.sh +++ b/scripts/sign-release-app.sh @@ -52,7 +52,7 @@ sign_if_present "$app_path/Contents/Resources/bin/programa-mcp" # exact AMFI-kill failure mode this comment is warning about. # # When PROGRAMA_PROVISION_PROFILE is unset or the file doesn't exist, this is -# a no-op here — but it is no longer harmless, because the app does now declare +# a no-op here, but not harmless, because the app declares # restricted entitlements. scripts/verify-provision-profile.sh runs after this # and fails the build in that case rather than letting an AMFI-killed app ship. if [[ -n "${PROGRAMA_PROVISION_PROFILE:-}" && -f "${PROGRAMA_PROVISION_PROFILE:-}" ]]; then diff --git a/scripts/verify-provision-profile.sh b/scripts/verify-provision-profile.sh index 12954fc59..bc04fecae 100755 --- a/scripts/verify-provision-profile.sh +++ b/scripts/verify-provision-profile.sh @@ -12,12 +12,8 @@ set -euo pipefail # opens it (POSIX 163). Notarization does not catch this class of failure; # only a real launch does, and by then it has shipped to everyone. # -# This script previously treated a missing profile as fine, on the stated -# grounds that "Programa ships no restricted entitlements." That stopped -# being true when CloudKit (com.apple.developer.icloud-services / -# icloud-container-identifiers) landed, which left the one check that exists -# to catch an AMFI brick asserting the brick was expected. It now derives the -# answer from the bundle itself rather than from a comment that can go stale. +# The answer comes from the signed bundle itself, not from a comment that can +# go stale. # # Exit codes: # 0 no restricted entitlements and no profile, or profile covers them all diff --git a/tests_v2/test_command_palette_backspace_go_back.py b/tests_v2/test_command_palette_backspace_go_back.py index 15ef13210..111fee73a 100644 --- a/tests_v2/test_command_palette_backspace_go_back.py +++ b/tests_v2/test_command_palette_backspace_go_back.py @@ -65,8 +65,8 @@ def main(): window_id = client.current_window() try: - # Rename always opens with the existing name selected now, so this - # no longer has to pin a setting to get a deterministic starting state. + # Rename always opens with the existing name selected, so no setting + # needs pinning for a deterministic starting state. _open_rename_input(client, window_id) _wait_until( diff --git a/tests_v2/test_command_palette_rename_select_all.py b/tests_v2/test_command_palette_rename_select_all.py index 33005ea5d..56d9ce358 100644 --- a/tests_v2/test_command_palette_rename_select_all.py +++ b/tests_v2/test_command_palette_rename_select_all.py @@ -6,9 +6,8 @@ - Rename input selects all existing text immediately on open, and the selection survives interaction with the field. -Rename selecting the existing name used to be a setting. It is now the only -behaviour, so this exercises it directly instead of toggling -`debug.command_palette.rename_input.select_all`, which no longer exists. +Rename always selects the existing name, so this exercises that behaviour +directly with no setting to toggle. """ import os diff --git a/tests_v2/test_program_status_osc7501.py b/tests_v2/test_program_status_osc7501.py index ce4f43c66..6cd187acd 100644 --- a/tests_v2/test_program_status_osc7501.py +++ b/tests_v2/test_program_status_osc7501.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""OSC 7501 Program Status Protocol end-to-end test (docs/plans/osc7501-program-status.md, T-14). +"""OSC 7501 Program Status Protocol end-to-end test (docs/program-status.md). Drives real terminal output: each case runs `printf '\\033]7501;...\\033\\\\'` in the surface's shell, then asserts through `surface.wait` (`agent_state` / `program_state`) and `surface.list` diff --git a/tests_v2/test_worktree_and_layout_cli_argument_contract.py b/tests_v2/test_worktree_and_layout_cli_argument_contract.py index 6858c0b7e..6e2306ad5 100644 --- a/tests_v2/test_worktree_and_layout_cli_argument_contract.py +++ b/tests_v2/test_worktree_and_layout_cli_argument_contract.py @@ -2,14 +2,14 @@ """Regression: `worktree` and `layout` CLI subcommands must have a registered argument contract. -Both commands' descriptors previously had no `case` in the CLI's -`validateRegisteredArguments` switch, so every invocation -- even harmless +Both commands' descriptors need a `case` in the CLI's +`validateRegisteredArguments` switch. Without one, every invocation -- even harmless ones like `worktree list` / `layout list` -- failed before socket dispatch with: Internal CLI registry error: no argument contract for worktree -This shipped to main and made the entire documented `worktree`/`layout` CLI +That makes the entire documented `worktree`/`layout` CLI surface dead on arrival, even though the underlying `worktree.*`/`layout.*` socket methods worked fine via `programa rpc`. """ @@ -67,13 +67,13 @@ def _merged(proc: "subprocess.CompletedProcess[str]") -> str: def main() -> int: cli = _find_cli_binary() - # `worktree list` previously died before ever reaching the socket. + # `worktree list` must reach the socket. worktree_list = _run(cli, ["worktree", "list", "--repo", REPO_ROOT]) worktree_out = _merged(worktree_list) _must(REGISTRY_ERROR not in worktree_out, f"worktree list hit registry error: {worktree_out!r}") _must(worktree_list.returncode == 0, f"worktree list should succeed: {worktree_list.returncode} {worktree_out!r}") - # `layout list` previously died the same way. + # `layout list` must reach the socket too. layout_list = _run(cli, ["layout", "list"]) layout_out = _merged(layout_list) _must(REGISTRY_ERROR not in layout_out, f"layout list hit registry error: {layout_out!r}")