Skip to content

feat(ios): browse and resume existing on-disk sessions from home - #330

Open
kingbootoshi wants to merge 2 commits into
0xSero:mainfrom
kingbootoshi:feat/resume-existing-sessions
Open

feat(ios): browse and resume existing on-disk sessions from home#330
kingbootoshi wants to merge 2 commits into
0xSero:mainfrom
kingbootoshi:feat/resume-existing-sessions

Conversation

@kingbootoshi

Copy link
Copy Markdown

Lets you browse every existing claude/codex session already on the connected machine and continue any of them as a litter thread - instead of only seeing chats started in the app.

What

  • New "All Sessions" entry point (clock icon) in the home toolbar, opening the existing sessions browser across all servers
  • Tapping an on-disk session resumes it (the server's existing thread/resume path), opens it with full history, and pins it to home so it persists as a durable litter thread

Why this shape

Litter threads already ARE claude/codex sessions, and the server already enumerates ~/.claude/projects and ~/.codex/sessions into thread/list - the browsing and resume machinery existed with no entry point from home, and the home list only ever showed app-created (pinned) threads. This PR is wiring, not new machinery: +18/-5 for the whole feature surface.

The perf commit (needed at scale)

On a machine with thousands of on-disk sessions, opening the browser hard-froze the UI for minutes: list_threads with limit: nil drained every thread/list cursor page, and each drained thread emitted an unbatched .threadUpserted doing O(n) main-thread work - O(n²) overall.

Second commit bounds hydration at the fan-out source: page draining stops at the caller's limit (200-per-runtime default when legacy callers pass nil, so no caller can reintroduce drain-all), the sessions browser requests 100 with an explicit "Load more sessions" row, and the finalize prune only runs when every runtime's cursor is exhausted so a truncated drain never deletes threads. Search stays a pure local filter.

Verification

  • cargo test -p codex-mobile-client --lib: 784 passed, 0 failed (includes new hydration-budget test)
  • iOS sim build: BUILD SUCCEEDED
  • On-device against a machine with thousands of real sessions: browser fully populated ~3s after open, search filters instantly, tapping a result opens the resumed conversation in under 3s - the same repro that froze for minutes before the perf commit

Demo

resume-existing-sessions.mp4

Demo: home → All Sessions → live search over on-disk sessions → a 7-day-old CLI session (never opened in the app) resumes with full history and a live composer.

🤖 Generated with Claude Code

kingbootoshi and others added 2 commits August 22, 2026 22:24
The server side already surfaces every on-disk CLI session through
thread/list: the alleycat claude bridge hydrates
~/.claude/projects/<encoded-cwd>/<session-id>.jsonl into its thread
index at startup, and codex app-server enumerates ~/.codex/sessions
rollouts directly. SessionsScreen already lists those threads grouped
by workspace with previews and resumes them over the existing
thread/resume path (claude --resume <session-id> under the hood).

What was missing was reachability and persistence: SessionsScreen was
only navigable from inside an open conversation, and the home list
shows pinned threads only, so sessions started on the Mac never
surfaced and never stuck.

Add an All Sessions button to the home toolbar that pushes the
existing SessionsScreen (scoped to the selected server, or all servers
when none is selected), allow the sessions route to carry no server
filter, and pin a thread when it is opened from the browser so a
resumed on-disk session registers as a durable litter thread on the
home list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ot stall the UI

At real scale (thousands of on-disk sessions in ~/.claude/projects and
~/.codex/sessions) the All Sessions screen froze the main thread for
minutes: list_threads drained every thread/list page whenever no limit
was set, and each of the thousands of resulting ThreadUpserted events
pays an O(n) scan plus a full snapshot copy on the main actor, so total
hydration cost grew quadratically while SwiftUI re-diffed the growing
list underneath.

Give list_threads a hydration budget at the fan-out source: page
draining stops once the requested limit (or a 200-thread default for
legacy nil-limit callers such as the pinned-listing repair paths) is
reached per runtime. Cursor, search, and state-db-only queries keep
their existing single-page behavior. The finalize prune now requires
every runtime to have fully exhausted its cursor, since a truncated
drain cannot know the true thread set.

SessionsScreen requests 100 most recent per runtime and grows the
budget only through an explicit Load more row, so worst-case work per
user action is one bounded page instead of the entire session history.
Search stays a local filter over loaded rows and triggers no
hydration.

Co-Authored-By: Claude Fable 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