Repository navigation
Backlog sweep: thirteen items done, six dropped - #694
Merged
Merged
Conversation
libghostty runs a surface command through `exec -l`, which puts a dash on argv[0]. The overlay wrapper was a bare `sh -c`, so a `/bin/sh` that is dash or zsh started as a login shell and ran `~/.profile` before the caller's command. The stock bash selection was unaffected. the wrapper is now `/usr/bin/env /bin/sh -c`, the form the remote pane command already uses: `env` takes the dash and `sh` starts non-login.
`agterm-status.js` uses the 1.x plugin API and OpenCode 2 does not load it, so on a 2.x machine the installer reported success and no status ever appeared. The install dialog and both troubleshooting pages told the user to restart OpenCode, which does nothing there. They now name the major the plugin serves. Related to #637
`revealRestoredWindow` published the frontmost id by hand and left the sidebar reconcile to `didBecomeKey`, which AppKit does not deliver while agterm is inactive. A `reopen_recent` fired from the quick terminal over another app could restore into a window other than the frontmost one, and with auto-hide of inactive sidebars on, that window was raised with its sidebar still collapsed. it now calls `takeFrontmost`, the same publish block with the reconcile in it.
#679 paced only the final Return. One very long line can still be taken whole as a paste by the receiver: Claude Code ran a 2000-character `/rename` as a prompt. Pacing inside a line would block the main thread per piece on both routes, so the docs now say the caller sends shorter pieces and checks the result.
release builds keep only the exported `ghostty_*` symbols, so `atos` cannot identify the stripped Zig-internal functions. `scripts/symbolicate-frames.py BINARY ARCHIVE OFFSET...` matches the instructions before each return address against `libghostty-internal.a` with immediates masked, 24 instructions and then 12, the method that resolved #606. A frame is named only on a single match; an ambiguous match, no match, and an offset off an instruction boundary are reported as such and exit 1.
- accept-thread-waits-on-main-actor-without-bound: a design task that only keeps reads answering during a main-thread freeze, and does nothing for the freeze - host-side-remote-client-events: no consumer asked for it - no-draft-marker-independent-of-the-status-slot: no confirmed demand for a new persisted, rendered state - wrapper-pane-foreground-capture-unverified: unresolved, with no installation left to reproduce it on - edit-keymap-menu-hides-the-extension-mechanism: none of its ideas was clearly right - claude-hook-table-changes-never-reach-existing-installs: `mergeClaudeSettings` already migrates the historical generated forms, and a generic refresh has no mapping change to deliver also corrects the "fires once" sentence in the settings-reorder item, and rewords the `client.attached` line in `control-api.md` that pointed at a removed item.
a Live pane's daemon keeps its own scrollback. `session text` can read it through a realized pane, but resolves only open-window sessions. `agtermctl` had no direct read for a closed-window daemon. `agtermctl zmx screen NAME [--all|--lines N]` (`zmx.screen`) prints one daemon's screen by the name `zmx list` shows. The default is the screen at its last leader's size, `--all` adds the retained scrollback, `--lines N` keeps the last N lines of that. It attaches nothing and changes no pane's size. A name with no readable daemon is an error.
the stream had no selection event, so a watcher reacting to a session losing the selection had to poll `tree` and diff the `active` flag. `session.selected` carries the newly selected session and, as `previous`, the id of the one that lost it. It fires for every cause: a click, navigation, `session select`, a selected `session new` after its `session.created`, and the reselection after the active session closes. Re-selecting the selected session, a window coming forward and a store reloading its snapshot emit nothing.
a Debug build launched with the variable unset resolved the deployed app's directory, so it loaded the live windows, attached the live daemons and rewrote the live window files at quit. A Dock click on a lingering Debug tile is such a launch. a Debug build now adopts `~/Library/Application Support/agterm-debug` when the variable is unset or empty and exports it before anything reads it, so its shells and their `agtermctl` resolve the same socket. A non-empty value wins, the live directory included. Release is unchanged.
`keymap list` shows the user's custom commands and no verb could run one, so the control API was the one surface that could not reach them. `agtermctl keymap run NAME [--target T] [--window W]` (`keymap.run`) starts the command with that exact name against the addressed session, as the palette does for the active one. The reply means the process started: the command is detached, so its exit status is not reported, and a launch failure is an error carrying the reason.
the reconnect probe discarded its stderr, so a host-key or authentication failure that began during the wait had no fresh diagnostic: the pane said "reconnecting" and looked the same as an offline host. each failed probe now keeps its last non-empty stderr line. A waiting pane shows it as one line along its bottom edge, and its surface node in `tree` carries `reconnect` with the retry streak and that reason. Both go when the pane is attached again or closed. The message is not classified and the retry schedule is unchanged, so a host that is only offline is still never given up on.
after a Live restore of an idle Claude Code session a black block sat between the logo and its title. On re-attach, zmx replays the daemon's screen through ghostty's formatter, which wrote cells a program skipped with a cursor move as spaces under the previous cell's style. zmx now builds against a private checkout of the ghostty revision it pins, with a formatter patch that closes the style before a skipped cell; spaces a program wrote keep their style. `setup.sh` reads that revision from zmx's `build.zig.zon`, patches the checkout and points zmx's dependency at it, and the stamp covers both patch sets. `check.py` gains the two replay cases: the gap one fails on the unpatched build. A daemon already running keeps the old formatter until it is recreated.
a failed index write could leave a newly created window's snapshot missing from an otherwise valid index. A crash before another index write then restored the old index and silently skipped that window. a failed write now sets `WindowLibrary.indexUnsaved`. While it stands, every window snapshot save that succeeds retries the index, and the exit flush writes snapshots and index together before a Live reset is armed. The tree reports `indexUnsaved` at its top level. A crash with no later write still leaves the stale index: this narrows the gap and exposes the failure in `tree`, and adds no orphan scan. `saveIndex` and `loadIndex` move to `WindowLibrary+Index.swift` and the snapshot save block to `AppStore+Persistence.swift`, since both files sat at the length limit.
a pane counted as on screen whenever its view had a window, and a window keeps its views while it is minimized, ordered out or hidden with the app. Each such window held the swap chains of its selected session's panes, a shown split and a floating overlay included. a pane is now on screen only while its window is visible. The view observes `isVisible` on its current window, since ordering a window out or in has no notification of its own and occlusion notifications can miss an order-in. Whole-window occlusion and other Spaces are not part of the test. A program using mode 2033 now gets a visibility report when its window is hidden this way. measured on an isolated Debug instance with one pane: IOSurface 34.4 MB before, 11.8 MB with 0 resident four seconds after minimizing, 560 KB 38 seconds after minimizing, 34.4 MB three seconds after restoring. Hiding the app gave the same drop, to 11.8 MB with 0 resident after five seconds, and the same return on unhide. Hosted tests order a real window out and in, and attach a pane to a window before it is ordered in. the accessibility visibility observers move to `GhosttySurfaceView+Accessibility.swift`, since the view file sat at the length limit.
…description the bundled skill's description and trigger list are what an agent matches a request against, and the three new capabilities were only in the body. They are now named in both, so a request to run a custom command, read a closed window's session or watch the selection reaches the skill.
`RemoteReconnectBook.Entry.failures` and `reason` were public with no reader outside the package. The app reads the reconnect state through `readback(pane:)`.
the `PaneLeadCover` doc said the view draws only while the pane is covered. The reconnect note inside it also draws on an uncovered pane and takes no hits.
the exit flush writes every open store and the index through `saveAllChecked`. The `saveAllOpen` doc comment and the windows and settings rules still described `saveAllOpen` plus `saveIndex` as the quit-time flush.
`ExitFlushSteps` carried a checked and an unchecked save that both called `saveAllChecked`. It now has one save returning whether everything was written, and `exitFlush` reads that result only when a Live sessions reset is pending.
a hosted test miniaturizes a real window and restores it, waiting for the pending hide and for its cancel. Minimize was covered only by a manual measurement. the accessibility observer becomes `observeAccessibilityExposure`, since `observeWindowVisibilityChanges` read like the renderer's `observeWindowVisibility`. Its comment no longer says nothing else reports `isVisible`.
a Debug build that adopted `agterm-debug` as its state directory also resolved its config to `agterm-debug/config`, so `make run` started without the user's keymap, ghostty config and hooks. `DebugStateDirectory.configStateDirectory` returns no state directory for the adopted sibling, and the three config resolvers pass its answer to `ConfigPaths.configDirectory`. Any other `AGTERM_STATE_DIR` still redirects config to `<stateDir>/config`.
naming keymap run, the closed-window screen read and selection events pushed the bundled skill description past the spec limit, which `bundledSkillDescriptionFitsTheSpecLimit` checks. The description is reworded to 1023 characters with every trigger kept.
Deploying agterm with
|
| Latest commit: |
96414bd
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2a4661fe.agterm.pages.dev |
| Branch Preview URL: | https://backlog-sweep.agterm.pages.dev |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The symbolication matcher can falsely identify instruction sequences spanning unrelated archive functions.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Sweeps the backlog by implementing control APIs, persistence and rendering fixes, diagnostics, and documentation updates.
Changes:
- Adds
zmx screen,keymap run, selection events, reconnect read-back, and index-write status. - Fixes hidden-window rendering, persistence retries, Debug-state isolation, overlays, and zmx replay styling.
- Adds broad test and documentation coverage while retiring completed or rejected backlog items.
| File | Description |
|---|---|
site/docs.html |
Documents events, reconnects, OpenCode, and zmx screen. |
site/commands.html |
Documents new commands and read-back fields. |
scripts/zmx-patches/README.md |
Explains private Ghostty patching. |
scripts/zmx-patches/ghostty/0001-formatter-unstyled-blanks.patch |
Fixes styled replay gaps. |
scripts/zmx-patches/check.py |
Tests replay formatting. |
scripts/zmx-patches/0002-private-ghostty-dependency.patch |
Redirects zmx to patched Ghostty. |
scripts/symbolicate-frames.py |
Adds stripped-frame symbolication. |
scripts/setup.sh |
Builds zmx with private Ghostty patches. |
project.yml |
Updates Debug isolation guidance. |
plugins/agterm/skills/agterm/troubleshooting.md |
Notes OpenCode compatibility. |
plugins/agterm/skills/agterm/SKILL.md |
Advertises new control capabilities. |
plugins/agterm/skills/agterm/reference.md |
Documents new API contracts. |
docs/troubleshooting.md |
Clarifies OpenCode support. |
docs/backlog/zmx-replay-paints-unwritten-cells-with-the-previous-style.md |
Removes completed backlog item. |
docs/backlog/wrapper-pane-foreground-capture-unverified.md |
Removes dropped backlog item. |
docs/backlog/stripped-libghostty-frames-need-a-symbolication-script.md |
Removes completed backlog item. |
docs/backlog/stale-window-index-orphans-a-window-file-silently.md |
Removes completed backlog item. |
docs/backlog/session-type-long-or-multi-line-input-reads-as-paste-in-an-agent-tui.md |
Removes documented backlog item. |
docs/backlog/restored-window-reveal-skips-sidebar-reconcile.md |
Removes completed backlog item. |
docs/backlog/reconnect-probe-hides-a-permanent-ssh-failure.md |
Removes completed backlog item. |
docs/backlog/overlay-wrapper-reads-login-profile-under-non-bash-sh.md |
Removes completed backlog item. |
docs/backlog/opencode-integration-does-not-say-it-is-v1-only.md |
Removes completed backlog item. |
docs/backlog/no-draft-marker-independent-of-the-status-slot.md |
Removes dropped backlog item. |
docs/backlog/no-control-verb-runs-a-custom-command.md |
Removes completed backlog item. |
docs/backlog/no-cli-read-of-a-detached-daemon-scrollback.md |
Removes completed backlog item. |
docs/backlog/host-side-remote-client-events.md |
Removes dropped backlog item. |
docs/backlog/hidden-windows-keep-swap-chains.md |
Removes completed backlog item. |
docs/backlog/events-stream-has-no-selection-change-event.md |
Removes completed backlog item. |
docs/backlog/edit-keymap-menu-hides-the-extension-mechanism.md |
Removes dropped backlog item. |
docs/backlog/debug-build-defaults-to-live-state.md |
Removes completed backlog item. |
docs/backlog/claude-settings-merge-reorders-the-whole-file.md |
Records the retained decision. |
docs/backlog/claude-hook-table-changes-never-reach-existing-installs.md |
Removes resolved backlog item. |
docs/backlog/accept-thread-waits-on-main-actor-without-bound.md |
Removes dropped backlog item. |
CLAUDE.md |
Updates build and Debug-state guidance. |
ARCHITECTURE.md |
Documents hidden-window occlusion. |
agtermTests/LiveResetQuitTests.swift |
Tests checked exit persistence. |
agtermTests/GhosttySurfaceViewTrackingTests.swift |
Tests window visibility transitions. |
agtermTests/CustomCommandRunnerTests.swift |
Tests control-triggered custom commands. |
agtermTests/ControlServerZmxTests.swift |
Tests daemon screen reads. |
agtermTests/ControlServerRemoteReconnectTests.swift |
Tests reconnect read-back. |
agtermTests/AppActionsFocusTests.swift |
Tests sidebar reconciliation. |
agtermCore/Tests/agtermctlKitTests/ZmxCommandsTests.swift |
Tests zmx screen CLI parsing. |
agtermCore/Tests/agtermctlKitTests/MiscCommandsTests.swift |
Tests keymap run CLI parsing. |
agtermCore/Tests/agtermctlKitTests/EventCommandsTests.swift |
Tests selection event formatting. |
agtermCore/Tests/agtermCoreTests/WindowLibraryIndexTests.swift |
Tests index retry behavior. |
agtermCore/Tests/agtermCoreTests/RemoteReconnectTests.swift |
Tests reconnect reasons and streaks. |
agtermCore/Tests/agtermCoreTests/OverlayCaptureTests.swift |
Tests non-login overlay execution. |
agtermCore/Tests/agtermCoreTests/MockControlActions.swift |
Extends control mocks. |
agtermCore/Tests/agtermCoreTests/HtmlBridgeTests.swift |
Tests page-scoped keymap execution. |
agtermCore/Tests/agtermCoreTests/DebugStateDirectoryTests.swift |
Tests Debug-state adoption. |
agtermCore/Tests/agtermCoreTests/ControlEventProtocolTests.swift |
Tests selection event encoding. |
agtermCore/Tests/agtermCoreTests/ControlDispatcherZmxTests.swift |
Tests zmx screen dispatch. |
agtermCore/Tests/agtermCoreTests/ControlDispatcherTests.swift |
Tests keymap run dispatch. |
agtermCore/Tests/agtermCoreTests/AppStoreEventTests.swift |
Tests selection event emission. |
agtermCore/Sources/agtermctlKit/ZmxCommands.swift |
Adds zmx screen. |
agtermCore/Sources/agtermctlKit/MiscCommands.swift |
Adds keymap run. |
agtermCore/Sources/agtermctlKit/EventCommands.swift |
Formats selection events. |
agtermCore/Sources/agtermCore/WindowLibrary+Index.swift |
Implements checked index persistence. |
agtermCore/Sources/agtermCore/WindowLibrary.swift |
Integrates index retries. |
agtermCore/Sources/agtermCore/RemoteReconnect.swift |
Stores reconnect diagnostics. |
agtermCore/Sources/agtermCore/OverlayCapture.swift |
Defines a non-login wrapper. |
agtermCore/Sources/agtermCore/HtmlBridge.swift |
Targets keymap runs to page sessions. |
agtermCore/Sources/agtermCore/DebugStateDirectory.swift |
Resolves isolated Debug state. |
agtermCore/Sources/agtermCore/ControlProtocol.swift |
Adds new protocol commands. |
agtermCore/Sources/agtermCore/ControlProjection.swift |
Adds reconnect and index projections. |
agtermCore/Sources/agtermCore/ControlEvents.swift |
Adds selection events. |
agtermCore/Sources/agtermCore/ControlDispatcher+Zmx.swift |
Dispatches screen reads. |
agtermCore/Sources/agtermCore/ControlDispatcher.swift |
Dispatches custom commands. |
agtermCore/Sources/agtermCore/ControlActionsDefaults.swift |
Adds compatibility defaults. |
agtermCore/Sources/agtermCore/ConfigPaths.swift |
Documents Debug config resolution. |
agtermCore/Sources/agtermCore/AppStore+Persistence.swift |
Extracts persistence behavior. |
agtermCore/Sources/agtermCore/AppStore+Events.swift |
Emits selection events. |
agtermCore/Sources/agtermCore/AppStore.swift |
Integrates events, retries, and projections. |
agterm/Views/SettingsView.swift |
Uses Debug-aware config resolution. |
agterm/Views/PaneLeadCover.swift |
Displays reconnect reasons. |
agterm/SettingsModel.swift |
Uses Debug-aware config resolution. |
agterm/Ghostty/GhosttySurfaceView+Visibility.swift |
Tracks window visibility for rendering. |
agterm/Ghostty/GhosttySurfaceView+Accessibility.swift |
Separates accessibility notifications. |
agterm/Ghostty/GhosttySurfaceView.swift |
Manages visibility observation lifecycle. |
agterm/Ghostty/GhosttyApp.swift |
Preserves default Debug config lookup. |
agterm/Control/ControlServer+Zmx.swift |
Reads daemon screens. |
agterm/Control/ControlServer+RemoteReconnect.swift |
Captures probe errors. |
agterm/Control/ControlServer+AppCommands.swift |
Runs named custom commands. |
agterm/Control/ControlServer.swift |
Exposes new commands and index status. |
agterm/Commands/CustomCommandRunner.swift |
Runs commands for addressed sessions. |
agterm/AppDelegate.swift |
Checks snapshots and index before reset. |
agterm/AppActions+Focus.swift |
Reuses frontmost-window reconciliation. |
agterm/agtermApp.swift |
Adopts Debug state and overlay wrapper. |
agterm/AgentHooksInstaller.swift |
Reports OpenCode 1.x support. |
.github/workflows/ci.yml |
Includes nested patches in cache keys. |
.claude/rules/windows.md |
Documents index retry semantics. |
.claude/rules/settings.md |
Documents Debug config and exit saves. |
.claude/rules/libghostty.md |
Documents occlusion and symbolication. |
.claude/rules/control-api.md |
Documents new control contracts. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+93
to
+94
| return [i for i, token in enumerate(self.tokens) | ||
| if token == last and i + 1 >= size and self.tokens[i + 1 - size:i + 1] == window] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

a sweep of
docs/backlog/: thirteen items done, six dropped, three kept. Each done item has its own commit, followed by review fixes, so this merges with a merge commit.New control surface
zmx screen NAMEreads the screen of a daemon whose window is closedkeymap run NAMEruns a custom command fromkeymap.conf, with--targetand--windowsession.selectedevent on selection changes, carrying the previous sessionreconnecton a surface node: the retry streak and the last failed probe's reason. The pane shows the same reason while it waitsindexUnsavedon the tree while a failedwindows.jsonwrite is outstandingFixes
windows.jsonwrite is retried after the next snapshot save that succeeds and at exit, ahead of arming a Live resetscripts/setup.shrebuilds zmx onceAGTERM_STATE_DIRunset or empty uses~/Library/Application Support/agterm-debugfor state and socket, and keeps the default config directorySmaller ones: a window restored by a reopen shows its sidebar, the overlay wrapper starts as a non-login shell,
scripts/symbolicate-frames.pynames stripped libghostty frames, and docs for OpenCode 1.x, longsession typelines and the skill description.Kept in the backlog
claude-settings-merge-reorders-the-whole-file: a recorded "no", preserving formatting is not worth the merge-path changesession-host-sometimes-creates-an-app-rooted-daemon: no cause found yetsurface-free-deadlock-waits-on-a-ghostty-pin-bump: waits on surface: release mailbox producers before joining them ghostty-org/ghostty#14444, still openMeasured and not checked