Skip to content

Fix refresh and subprocess bugs; rebuild the interface with native macOS components - #36

Merged
euforic merged 9 commits into
mainfrom
review-fixes
Oct 30, 2026
Merged

euforic merged 9 commits into
mainfrom
review-fixes

Conversation

@euforic

@euforic euforic commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes from a full review of the app, plus a native macOS rebuild of the interface.

Bugs

  • Child processes inherited unrelated descriptors. BoundedSubprocess and GroupedSubprocess called posix_spawn without POSIX_SPAWN_CLOEXEC_DEFAULT, and their pipes aren't close-on-exec. A helper spawned at the same time as another session (ps, lsof, sqlite3, or a Codex app-server) inherited that session's pipe ends. This can hold a pipe open after its child exits, and it exposed unrelated AgentDock descriptors to provider tooling. Regression tests fail without the fix.
  • Opening a profile threw away the in-flight full refresh. The single-profile refresh after a launch cancelled a running full refresh. Every other profile and both official sources then kept stale or empty stats and usage until the next interval. Partial refreshes now defer to a running full refresh, and back-to-back partial refreshes merge. The existing test that asserted the old cancel behavior is updated.
  • Deleting a running Claude profile told the user to "quit every Codex window".
  • The edit sheet could present empty, because it was Bool-driven while the selection could go nil.
  • ⌘N, ⌘,, and ⌘F / ⌘⇧F were each bound twice.
  • A zero-length usage window was labelled "0-day", and several counts were mis-pluralized ("1 expire", "1 available resets").
  • Profile images were decoded from disk on every render (including every keystroke in the editor). App icons and versions were read on every render.

Added after live testing (a review build launched beside the installed app with a separate bundle ID and driven through the Accessibility API; nothing was created, deleted, or launched)

  • Home is now a condensed, sortable account table: a summary strip with running accounts, banked resets, closest to limit, and next usage reset; provider sections; inline session and weekly meters with countdowns; banked-reset urgency; and Open/Focus. Double-click opens details.
  • Sidebar rows announced their running state twice in VoiceOver. Each row is now one element.
  • Automatic window tabbing is off (the View and Window menus offered tab commands).
  • Binding setters write only real changes, so re-applied values no longer republish state, re-save preferences, or record duplicate analytics. The Codex provider picker could rewrite Built-in as Default and restart a running account.
  • Migration test fixtures use a .noindex name. Spotlight briefly opening fixture files made the lsof in-use check fail different migration tests at random in full-suite runs.

Open item: in about the first minute after launch, while the initial refresh lands, showing a managed profile's detail logs SwiftUI "Publishing changes from within view updates" runtime warnings. Nothing renders or behaves incorrectly. I couldn't trace the source, and none appear once the initial refresh finishes.

Interface

  • Sidebar: a source-list List with sections, a reset-count badge, search, drag reordering, double-click to open, and context menus.
  • Real window toolbar: title and subtitle, the Overview/Advanced picker, Refresh, and Add Profile. There's a new Profile menu and View › Home / Available Resets / Refresh.
  • Home, Overview, Advanced, the sheets, and notification options use grouped Forms and semantic text styles, with no custom cards, palette, or uppercase labels.
  • Settings is a standard tabbed Settings window: General, Updates, Notifications, Provider Apps, Privacy. Every toggle now has an accessible label; before, some were Toggle("").
  • Logs and archived sessions use Table.
  • The appearance setting applies app-wide through NSApp.appearance.
  • The profile editor accepts any monogram letter and offers named color swatches plus a custom color.
  • The AI-generated Home mock-up is removed from the docs, and README and the focused docs describe the new commands and shortcuts.

Net: about −1,450 lines in the app target.

Validation

  • swift test after merging main (Remove unreachable chat-browser scanner APIs from core #37): 296 core tests plus 65 app tests, 0 failures; two consecutive full runs also passed before the merge. Earlier: 389 tests (app and core targets). One failure, LegacyBrandMigrationTests/testExternalOpenFileBlocksMigrationUntilClosed, is a pre-existing intermittent failure of the lsof +D idle check. On an unmodified main, the same check failed three other migration tests on this machine. All 24 migration tests pass when run on their own, and an instrumented rerun didn't reproduce it.
  • swift build -c release --product AgentDock
  • New tests: descriptor inheritance (Bounded and Grouped), partial and full refresh coalescing, list-move index mapping, and the toolbar picker test now finds the control in the bridged window toolbar.
  • Documentation updated.
  • Not run: packaging scripts (no packaging change) and installed-app lifecycle checks (opt-in; they open the provider apps).
  • UI was checked with synthetic offscreen renders, which can't capture sidebar vibrancy or glass. Click through the sidebar, context menus, and Settings tabs in a packaged build before release.

Privacy and Security

  • No credentials, account data, transcripts, usernames, home paths, or logs are included.
  • Ownership, path containment, process identity, signatures, and destructive-action boundaries are unchanged. The spawn change only narrows what child processes inherit.

Screenshots

None committed. Renders used synthetic fixtures only.

🤖 Generated with Claude Code

euforic and others added 5 commits October 29, 2026 23:39
BoundedSubprocess and GroupedSubprocess used posix_spawn without
POSIX_SPAWN_CLOEXEC_DEFAULT, and their pipes are not close-on-exec.
A helper spawned concurrently with another session (ps, lsof, sqlite3,
or a Codex app-server) inherited that session's pipe ends, which can
hold a pipe open past its child's exit, and exposed unrelated
AgentDock descriptors to provider tooling.

Spawn with POSIX_SPAWN_CLOEXEC_DEFAULT and give bounded helpers an
explicit /dev/null stdin so only the configured stdio survives exec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting a running Claude profile told the user to quit every Codex
window. Name only the profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a profile requested a stats and usage refresh for that profile,
which cancelled any full refresh still in flight. Every other managed
profile and both official sources kept stale or empty data, and their
loading state was cleared, until the next automatic interval.

A partial refresh now defers to a running full refresh, and successive
partial refreshes coalesce instead of dropping earlier profiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window used a hand-drawn sidebar (custom selection fills, iOS-style
chevrons, uppercase labels), an in-content toolbar, card-styled detail
pages, and a second Settings surface embedded in the main window.

- Sidebar: a source-list List with sections, a reset-count badge,
  search, drag reordering, double-click to open, and context menus.
- Toolbar: window title and subtitle, the Overview/Advanced picker,
  Refresh, and Add Profile.
- Menus: File › New/Restore Profile, View › Home, Available Resets,
  Refresh, and a Profile menu. This removes the conflicting duplicate
  Command-N, Command-Comma, and Command-Shift-F bindings.
- Home, Overview, Advanced, Settings, sheets, and notification options
  use grouped Forms, semantic text styles, and system materials.
  Settings is a standard tabbed window.
- Activity logs and archived sessions use Table. Log rows are identified
  by position, so identical lines no longer share an identity.
- The appearance preference applies app-wide through NSApp.appearance,
  so Settings and sheets follow it and System restores correctly.
- Profile and app icons are decoded once and cached by file identity
  instead of on every render; app versions are cached per Info.plist.
- The editor accepts any monogram letter, offers named colors plus a
  custom color, and uses a file importer.
- The edit sheet is item-driven, so it never presents empty.
- Usage window titles share one formatter; a zero-length window is
  no longer labelled "0-day". Fix singular/plural counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the menu commands, context menus, Settings tabs, and reminder
controls. Replace the generated Home mock-up with a description of the
native layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
euforic and others added 4 commits October 30, 2026 00:43
- Sidebar rows applied their running state as the value of every child
  text, so VoiceOver read the status twice and lost the row structure.
  Combine each row into one element.
- Disable automatic window tabbing; the View and Window menus offered
  tab bar commands for a single-window utility.
- Binding setters for the sidebar selection, toolbar section picker,
  Settings controls, reminder policy, and sheet and alert dismissal now
  write only real changes. Re-applied values no longer republish model
  state, re-save preferences, reschedule reminders, or record duplicate
  navigation analytics.
- The Codex provider picker shows Built-in as Default when no named
  default exists. Re-applying that displayed value no longer rewrites
  the stored selection, which could restart a running account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the per-account rows with a summary strip (running accounts,
banked resets and next expiration, closest to a usage limit, next usage
reset) and a native Table grouped by provider. Each row shows the
account and running state, session and weekly usage as inline meters
with reset countdowns, banked Codex resets with expiration urgency, and
Open or Focus. Columns sort within each provider; double-click opens a
source's details and the context menu offers Open and Show Details.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixtures live under the home folder to keep socket paths short.
Spotlight indexing them could briefly hold a file open, which the
migration's open-file check correctly reports as in use, so different
migration tests failed intermittently in full-suite runs. Name the
fixture with a `.noindex` suffix at the same length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@euforic
euforic merged commit 9cdb58b into main Oct 30, 2026
1 check passed
euforic added a commit that referenced this pull request Oct 30, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant