Skip to content

Refresh the project switcher: full-viewport Projects view (#77)#86

Open
thalida wants to merge 12 commits into
mainfrom
feat/issue-77-project-switcher
Open

Refresh the project switcher: full-viewport Projects view (#77)#86
thalida wants to merge 12 commits into
mainfrom
feat/issue-77-project-switcher

Conversation

@thalida

@thalida thalida commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Closes #77

Replaces the form-first source-picker modal with a persistent, returnable full-viewport Projects view.

What changed

  • New surface: views/ProjectsView/ (replaces views/SourcePicker/). Full-viewport, opaque above the shell; the city canvas stays mounted underneath. SOURCE_PICKER store reshaped to PROJECTS_VIEW.
  • Recents-forward: MRU list is the hero, with filter-as-you-type + keyboard nav (↑/↓/Enter), kind glyphs (host/folder), a real (default) branch tag, and active state derived from CURRENT_SOURCE (not the URL). Remove is now inline + non-destructive (no longer silently clears the scan cache).
  • Repo-resolved branch dropdown: new GET /api/branches (git ls-remote --symref, remote-only, reusing the clone error taxonomy) feeds a dropdown whose value resets whenever the URL changes — fixing the stale-branch-across-repos bug.
  • Cancelable loads: an AbortController is threaded through the fetch layer; the view stays open through a load, shows inline progress with a Cancel, and a user cancel is not an error (no SOURCE_ERROR, no CURRENT_SOURCE/MANIFEST commit — including cancel-after-skeleton). This fixes the "no way back during a load" trap.
  • Auto-close on a committed switch via an explicit reaction to CURRENT_SOURCE (the hook stays UI-free). LoadingOverlay is retained for deep-link cold boot and suppressed while the view is open.
  • Local vs git clarity: smart-but-explicit Git/Local segment; local repos disabled is a concise inline note, not a dead-end tab.

Bugs fixed

  1. Branch field persisting a stale branch across repos → newrepo@oldbranch load failures.
  2. No way to go back / cancel during a load.

Verification

  • Backend /api/branches verified live end-to-end (real ls-remote; local→400, missing repo→404).
  • Frontend suite 2449 passing; backend 264 passing; just lint clean. (The one layoutGolden.bench failure pre-exists on main and this branch never touches layout code.)
  • Design spec + plan + a per-task review trail (spec + quality, with adversarial verification) drove the work.

🤖 Generated with Claude Code

thalida and others added 12 commits July 4, 2026 17:08
Recovered onto the branch after the original commit (f298e939) was orphaned
by a HEAD reset during subagent execution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
streamManifest now accepts an options bag ({ signal, EventSourceImpl })
instead of a positional ctor; useManifestSource threads an
AbortController through loadSource/pumpManifestStream so a new load or
an explicit cancelLoad() aborts the in-flight one. A user-canceled load
is treated as a clean no-op (no SOURCE_ERROR write), since that signal
drives App's picker-reopen reaction. The hook now returns
{ submitSource, cancelLoad }.
)

streamManifest ends an aborted stream as done (not a throw), so a cancel
landing after the skeleton but before the final made pumpManifestStream
return the partial manifest and loadSource's success path committed it as
CURRENT_SOURCE. Guard the commit with the abort signal, mirroring the
catch-branch guard, so a user cancel never mutates CURRENT_SOURCE. Adds a
cancel-after-skeleton regression test plus an already-aborted-at-iterator
test that locks in the abort-wiring placement.
#77)

The single-field NewProjectForm unmounted the source input on the first
keystroke of a git URL when allowLocalRepos=false: srcKind classifies a
half-typed URL ("h") as Local, flipping kind->Local -> localOff true ->
the {!localOff && <input/>} branch dropped the field and focus. Pin Remote
when local repos are disabled (Local is unreachable there). Adds a
char-by-char typing regression test, plus aria-controls on the Advanced
toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on commit (#77)

Cuts App over from the modal <SourcePicker> to <ProjectsView>: the view stays
mounted through a submit (no dismiss-then-submit), renders inline load
progress (pending label + step + Cancel) in place of the form/recents, and
closes itself via the existing CURRENT_SOURCE effect (written only on a
committed success) rather than any ordering of signal writes.

LoadingOverlay now suppresses itself while ProjectsView is open, per the
design's "two loading surfaces, each with a crisp job" split — otherwise a
switch would show the overlay's no-controls backdrop stacked on top of the
view's own Cancel button. Its narrow remaining job is deep-link cold boot.

Extracted the phase→step mapping shared by both surfaces into
constants/loadingSteps.stepForPhase (behavior-preserving refactor of
loadingReactions.ts) instead of duplicating it in ProjectsView.

NewProjectForm's own collapsed "Opening project… / Cancel" affordance is now
unreachable (ProjectsView unmounts it during a load) and is removed along with
its onCancel prop and dead CSS.
Delete the dead SourcePicker view/test now that ProjectsView fully
replaces it, and clean up stale comment references. Also recover the
.modal-backdrop/.modal-card/.modal-header/.modal-body chrome that
DebugModal, ShortcutsModal, and LoadingOverlay depended on from
SourcePicker.css into a new shared styles/modal.css — SourcePicker.tsx
was already unmounted so that CSS was dead weight, silently leaving
those three surfaces unstyled.

Includes incidental just fmt/prettier fixes for pre-existing drift in
api/tests/test_server_branches.py and three app test files, needed to
get just lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…abs--modal (#77)

A cancel landing in the skeleton->complete window left the canceled repo's
partial manifest in MANIFEST while CURRENT_SOURCE stayed put, so its geometry
lingered under the prior source's header. Snapshot MANIFEST at load start and
restore it on an aborted load (both success and catch abort paths), ordered
after the supersede guard so a newer load's MANIFEST is never clobbered.
Aligns setManifest's param with the signal's ManifestValue union so the
rollback typechecks without a cast. Also drops the orphaned .pane-tabs--modal
CSS rule (only consumer was the deleted SourcePicker).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thalida thalida linked an issue Jul 4, 2026 that may be closed by this pull request
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.

Refresh the project selector / switcher experience

1 participant