The fuzzy selector takes several workspaces for the verbs that can use them - #336
Conversation
…can use them A verb that applies per workspace and returns — up, stop, rm, code, dotfiles, and the --rm/--stop spellings — now opens the picker in skim's multi-select: TAB marks any number of rows and Enter applies the verb to each in turn, so dl rm clears five dead workspaces in one visit. Every marked workspace is attempted whatever happened to the ones before it, and the command's ending is the first that was not Done. The forms that end in an interactive session (attach, --, restart, recreate, reset) still take exactly one — several of those would just be sessions queued behind each other's exit. The completion-cache latch is re-armed between workspaces, the same reasoning as --autorm's re-arm: each workspace after the first is one more state change after whatever refresh the last one spawned.
skim keys every marked row by (run, get_index()), and the SkimItem trait's get_index() defaults to 0 — so rows that carry no index of their own all collide on one key, and each TAB after the first removed the previous mark instead of adding to it. Observed live through a pty: mark two workspaces, Enter, and only one is acted on; skim's own sk -m binary returns both because its item type implements the index. Every row now carries its position among the offers as its index, and the test pins that the indices are distinct — the property the accumulation depends on.
several_at_once was a matches! over the batching arms, so a verb added to Verb later would silently fall to single-select instead of breaking the build until somebody answers the question. An exhaustive match is this module's own rule for that obligation. Not testable — the failure is a compile that should have happened.
Reviewer's GuideThis PR teaches the embedded fuzzy selector to return multiple workspaces for verbs that operate per workspace and finish on their own, wires verb-controlled arity through the selection pipeline, and fixes a skim multi-select embedding bug by giving each row a distinct index and mapping multiple selected rows back to workspace IDs in order. Sequence diagram for multi-workspace selector executionsequenceDiagram
participant User
participant Selector as FuzzySelector
participant Commands as render_select
participant Workspace as render_workspace
participant Refresh as RefreshLatch
User->>Selector: TAB marks workspaces
User->>Selector: Enter
Selector-->>Commands: Pick::Chose(workspace_ids)
loop Each selected workspace in selection order
alt Not the first workspace
Commands->>Refresh: rearm()
end
Commands->>Workspace: render_workspace(workspace_id, verb)
Workspace-->>Commands: Ending
end
Commands-->>User: First non-Done ending
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is
Additional details and impacted files
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
blooop
left a comment
There was a problem hiding this comment.
This was generated by AI during review.
Retrospective wf-review. #336, #350, #363 and #370 were reviewed together as one surface — the full two-axis report is on #370: #370 (review) — and every finding there was verified against current main (a5f7ed8), not against the intermediate state this PR left behind.
Specific to #336:
- Nothing broken in the multi-select itself.
Verb::several_at_onceis an exhaustive match,Arity::Onesetsmulti: falseso TAB is inert rather than misleading, and theget_indexfix is genuinely pinned (removing the impl turns the test red). I tried hard to break the batch's first-failure latch with theEnding::Child(Exit::Code(0))case — a success that is notEnding::Donewould latch and swallow every later failure — butDevpodRefusedis only constructed behind!exit.is_success()in bothworkspace_stopandworkspace_delete, andup/code/dotfilesreturnEnding::DoneviaLaunched::Ready. The contract holds. It holds by coincidence of the other arms though;if matches!(ending, Ending::Done)would be sturdier as "latch the first ending whosecode()is non-zero". - P1 (non-blocking) —
CHANGELOG.md:535-537, shipped in[0.6.0]and still onmain, is wrong. The entry says multi-select applies to the five verbs "(and the--rm/--stopspellings)". Verified by drivingcli::resolveonmain:dl --rmresolves toSelect { Attach { rm: Yes } }withseveral_at_once = false(single-select — andcli.rs:171says so outright: "This is--rm, andVerb::Removeisrm— docker's split"), anddl --stopisErr(RetiredFlag(Stop)), which never opens a picker. README gets it right; drop the parenthesis from the CHANGELOG. - Non-blocking —
refresh.rearm()runs once per workspace, so a batch of ten spawns up to ten detacheddl --update-cachechildren, nine of them describing a world the batch is still changing. No corruption risk (write_atomicallystages through a pid-unique temp name), but re-arming once after the loop is what the entry's own reasoning asks for.
The fuzzy selector took one workspace and only ever one. For the verbs that
apply per workspace and return, that made
dl rma five-visit job to clear fivedead workspaces. It now takes as many as you mark, the way
fzf --multiand thevslauncher this borrows from do.What changed
Multi-select for the verbs that finish on their own —
up,stop,rm,code,dotfiles, and the--rm/--stopflag spellings. TAB marks any numberof rows, Enter applies the verb to each in turn.
Single-select, unchanged, for everything that ends in an interactive session:
attach,
-- <command>,restart,recreate,reset. Several of those would besessions queued behind each other's exit, which is not what marking five rows
asks for.
Verb::several_at_onceis an exhaustive match, so a verb added laterhas to answer the question rather than inheriting single-select by omission.
A batch does not stop at the first refusal. Every marked workspace is
attempted whatever happened to the ones before it — one
rmrefused over unsavedwork must not silently drop the other four — and the command's ending is the
first that was not
Done, so a script still learns something failed. Thecompletion-cache latch is re-armed between workspaces, the same reasoning
--autormre-arms for: each workspace after the first is one more state changeafter whatever refresh the last one spawned.
The invitation line names what the picker will take, since it is the only place
TAB is discoverable:
The bug the self-review caught
The first working version degraded to last-toggle-wins: mark two workspaces,
Enter, and only one was acted on — the exact failure the feature exists to
prevent.
skim keys every marked row by
(run, get_index()), and theSkimItemtrait'sget_index()defaults to 0 "for retro-compatibility".Rowneverimplemented it, so every row collided on one key and each TAB removed the
previous mark instead of adding to it. Proven by driving the built binary
through a pty against a fake devpod, and by instrumenting a vendored skim
(
TOGGLE idx=(1, 0) text="alpha" now=[(1,0)]thenTOGGLE idx=(1, 0) text="bravo" now=[]). skim's ownsk -mreturns both rows,which is what placed the defect in the embedding rather than in skim.
Each row now carries its position as its index, and a test pins that the indices
are distinct — the property the accumulation depends on.
Testing
clippy --all-targets -D warningsclean,cargo fmt --checkclean, codespell clean.the right invitation line.
devpod stop alphaanddevpod stop bravo, in the order marked.Worth knowing: the pty harness exercised
stop.rm's unsaved-work refusalmid-batch is covered by unit tests and by reading, not by a live run.
🤖 Generated with Claude Code
Summary by Sourcery
Allow the fuzzy workspace selector to batch self-contained operations while retaining single selection for interactive commands.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: