Skip to content

fix(orchestrator): scope sessions per project + redesign the Open picker#2071

Closed
sinelaw wants to merge 9 commits into
masterfrom
claude/investigate-orchestration-bug-WFVCX
Closed

fix(orchestrator): scope sessions per project + redesign the Open picker#2071
sinelaw wants to merge 9 commits into
masterfrom
claude/investigate-orchestration-bug-WFVCX

Conversation

@sinelaw

@sinelaw sinelaw commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the reported orchestration bug where launching in one project pulled in another day's / another project's directories, files, and sessions, and reworks the Open picker around per-project scoping.

Root cause. In 0.3.7 orchestrator persistence moved from per-cwd files to a single global windows.json. Two surfaces then leaked across projects:

  1. the Open picker listed every session from every project in one flat, unlabeled list; and
  2. at boot the editor could drop you straight into a previous session (its worktree root / label / plugin state) rather than a clean editor for the project you launched in.

Changes

Boot (no cross-project bleed):

  • pick_active_window_for_cwd is now strictly cwd-scoped: reopen the session last used in the launch cwd's project (env.active if it belongs to the cwd, else the most-recent session for that cwd, else a clean base window). A different project's session is never auto-activated. Honors a directory passed on the CLI (fresh /path), not just the shell cwd.
  • A clean base keys off the picked window, so a stale persisted id-1 window from another project can't lend its label/root/state to it.

Open picker:

  • Scopes to the current project by default; an Alt+P toggle reveals all projects. Filtering is always global.
  • Tabular rows [id] NAME … PROJECT with a column header; the PROJECT column labels cross-project rows; sessions sort current-project-first. Active session's [id] renders in the active-tab colour.
  • Labelled Project: [ <name> ▾ (Alt+P) ] scope control and a labelled Filter field; / focuses the filter.
  • The floating panel now fits the session count instead of a tall box padded with blank rows.

Host fixes (found while testing):

  • Widget row-zip clamped byte-unit overlays to the pre-truncation length, so a multi-byte ellipsis from column truncation could land mid-char and panic the span splitter — now snaps overlays to a char boundary.
  • The floating-panel key dispatcher fed plain chars straight to the focused text input, consulting mode bindings only for named/Ctrl/Alt keys — so a bare key like / bound in a picker mode was dead. Plain chars now defer to an explicit mode binding first.

Docs: design doc updated with the scoping principles, wireframes, and the shipped layout.

Test plan

  • cargo test -p fresh-editor --lib orchestrator_persistence::tests (cwd-scoped boot pick: never-crosses-projects, last-used, most-recent fallback, clean-base)
  • cargo test -p fresh-editor --lib widgets::render (char-boundary regression test)
  • cargo test -p fresh-editor --test orchestrator_persistence_paths
  • cargo test -p fresh-editor --test e2e_tests orchestrator (scoping default + Alt+P reveal-all + /-focus live filtering; form typing unaffected)
  • Widget / search_replace text-input e2e (host key-dispatch change is non-regressive)
  • Manual tmux walkthrough: created git-worktree sessions across webapp/api-service + an in-place session in non-git data; verified scoped vs all-projects views, the Project control, /-focus + filtering, clean per-cwd boot, and compact panel sizing

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y

@sinelaw sinelaw changed the title docs(orchestrator): add project-scoping design to open-dialog plan fix(orchestrator): scope sessions per project + redesign the Open picker May 20, 2026
claude added 9 commits May 21, 2026 00:14
Document the cross-project confusion from v2 global session
persistence: scoping principles, current vs. proposed wireframes,
and the scope-toggle interaction model.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
The Open picker now foregrounds only the active window's project,
with an "N in other projects" affordance and an Alt+P scope toggle
that reveals every session (grouped current-project-first, each
cross-project row labeled). Filtering still searches globally, so
nothing is hidden — it's just not dumped on the user when they
relaunch in a different project.

Also fixes a latent panic in the widget row-zip: column truncation
appends a multi-byte ellipsis but byte-unit overlays were clamped
to the pre-truncation length, so an overlay end could point inside
a multi-byte char and the span splitter sliced mid-char. Overlays
now snap to a char boundary of the truncated line.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
At startup the editor reopens the session the user last used in the
launch cwd's project, and never a session from a different project.
The pick is strictly cwd-scoped (env.active if it belongs to the cwd,
else the most-recent session for the cwd, else a clean base window),
so launching in one project can't drag another project's worktree,
files, or tabs into the window. A clean base now keys off the picked
window rather than a lookup by id, so a stale persisted id-1 window
from another project can't lend its label/root/state to it.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
…ffordance

The "N in other projects" row added 2 lines below the sessions list
without shrinking the list's height budget, so the sessions column
outgrew the preview pane and pushed the footer hint bar off the
bottom of the fixed-height panel in the scoped view. Subtract the
affordance's rows from the list's reserved rows so both panes keep
the same height.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
Address the gaps from comparing the picker to the wireframes:

- Add a visible, clickable scope toggle (‹ This project › / ‹ All
  projects ›) under the filter; Alt+P still flips it (both call the
  same toggleScope()).
- Widen the sessions column 25% -> 34% and tag cross-project rows
  with the project basename so they read `· api-service` instead of
  truncating to `· tmp_o…`.
- Subtract the scope-toggle row from the list height budget so the
  footer hint stays on-screen.

Update the design-doc wireframes to match what shipped and
strengthen the e2e test to assert the scope control renders.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
Rework the Open picker layout per the reviewed wireframe:

- Tabular session rows: `[id]  NAME …  PROJECT` with a column
  header; drop the ACT/RUN glyph column (active id renders in the
  active-tab colour). PROJECT column shows the basename only for
  cross-project rows.
- Replace the inline scope toggle with a labelled `Project: [ <name>
  ▾  (Alt+P) ]` control (the Alt+P hint lives on the button) and a
  labelled Filter field.
- `/` focuses the filter; placeholder updated to advertise it.
- Fit the list (and so the floating panel) to the session count so a
  handful of sessions gives a compact panel instead of a tall box
  padded with blank rows.
- Shorten the New button to `[ + New  Alt+N ]`.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
The floating-panel key dispatcher fed plain chars straight to the
focused text input, consulting explicit mode bindings only for named
keys and Ctrl/Alt chords. So the orchestrator picker's `/`-to-focus-
filter binding never fired (and any plugin binding a bare key in a
floating-panel mode was dead). Defer plain chars to an explicit mode
binding first, mirroring the existing named-key / modified-char
deference; unbound chars still feed the focused input.

Lock `/`-focus + live filtering in the picker e2e test and update the
design-doc wireframes to the shipped tabular layout.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
The content-fit list height collapsed the floating panel to a short
box in the middle of the screen when there were only a few sessions.
Fill the full height budget instead (the list pads with blank rows)
so the dialog stays vertically full as before.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
After switching sessions, Quick Open and Live Grep kept targeting the
first project:

- FileProvider cached the file list with no cwd key, so it served the
  first-loaded project's files forever. Key the cache by cwd and carry
  the cwd through the async load message so results for a stale cwd
  (the user switched mid-load) are dropped.
- set_active_window updated working_dir but never refreshed the plugin
  state snapshot that getCwd() reads, so getCwd()-based plugins (Live
  Grep, finders) targeted the previous project. Refresh the snapshot
  before firing active_window_changed.

Also: the Open picker now defaults to all-projects and remembers the
scope across opens.

https://claude.ai/code/session_01UhCtdAe4SYg6QUsXuYKc9y
@sinelaw sinelaw force-pushed the claude/investigate-orchestration-bug-WFVCX branch from 122d172 to 9b081b8 Compare May 20, 2026 21:15
@sinelaw sinelaw closed this May 27, 2026
@sinelaw

sinelaw commented May 27, 2026

Copy link
Copy Markdown
Owner Author

Merged in a different PR

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.

2 participants