feat(scopes): cross-project Move-to via project_dir_from/_to split (#179, #181)#202
Merged
Conversation
, #181) Finishes the half-disabled cross-project Move-to submenu that shipped in v0.6. Before this: - The submenu hid `OtherProject > Local` whenever the source was Local (and same for Project), because the skip ran in every nested project, not just the current one. - The backend's `apply_move_leaf` took a single `project_dir`, so a move whose source lived in the current project's Local/Project scope into another project's local/project couldn't even be expressed — both sides resolved under one root, and the destination silently won (the codex 6th-pass [P1] half-fix in PR #178 routed `project_dir` to the destination to dodge silent corruption, but the genuine cross-project move stayed blocked). #179 — backend split ==================== `apply_move_leaf` / `diff_move_leaf` gain `project_dir_from` and `project_dir_to` IPC fields alongside the existing `project_dir`. Each side's `ScopePaths` resolves under its own root. The single-project `project_dir` argument is the fallback for either missing side, so existing same-project callers don't have to switch IPC shape. - `resolve_move_paths` returns a `(paths_from, paths_to)` pair, collapsing to one resolution when both sides match (the 99% case). - `apply_move_leaf_impl` / `diff_move_leaf_impl` take two `ScopePaths`. Same-scope change-kind is now dispatched on whether `from_path == to_path` rather than `req.from == req.to` — pre-#179 the two conditions were equivalent, but cross-project `Local → Local` is a real two-file move and needs the cross-scope branch. - `validate_move_request`'s path-collision check (#153) folds the pre-#179 `from == to` rejection into the same shared "source and destination resolve to the same file" message. One wording, two cases, identical effect. - Audit record gains `project_dir_to: Option<PathBuf>`, set only when the move actually crossed projects. Wire format stays backwards compatible — `skip_serializing_if` omits the field for every existing record. - `validate_audit_records` checks each side's `file_path` against BOTH project allowlists (`project_dir` and `project_dir_to`); passing under either is acceptable. A path outside both still gets rejected — the cross-project surface doesn't weaken the #183 injection gate. #181 — submenu unlock ===================== `buildMoveToSubmenu`'s `target === from` skip now fires ONLY when the nested project is the currently-loaded one. For other projects, the full `local + project` matrix is fair game. The chosen project root threads through `MoveOptions.projectDirTo`, which `moveLeaf` in `main.ts` sends as `project_dir_to` — the backend then resolves the destination side under that root. - `MoveOptions.projectDir` → `MoveOptions.projectDirTo`. The previous field was a partial half-fix; renaming makes the semantics ("override the DESTINATION root") explicit so a future cross-project source override has a name to take. - `moveLeaf` always sends both `project_dir_from` (= viewed project) and `project_dir_to` (= override or viewed project). Backend's `resolve_move_paths` collapses the equal case so the IPC stays zero-extra-cost on the hot path. Tests ===== Rust (278 → 285, +7): - apply_move_leaf_impl_moves_local_rule_across_project_boundaries - apply_move_leaf_impl_moves_project_rule_across_project_boundaries - resolve_move_paths_falls_back_to_project_dir_when_per_side_unset - resolve_move_paths_honors_per_side_overrides_when_set - validate_audit_records_accepts_cross_project_move_with_both_project_dirs - validate_audit_records_refuses_cross_project_path_outside_both_allowlists - existing `move_leaf_rejects_same_scope_source_and_destination` and `validate_move_request_refuses_cross_scope_move_into_a_colliding_pair` updated to assert against the new shared "overwrite the file with itself" wording. JS (137 → 140, +3): - offers same-scope-name targets when the project differs (#181) - still hides the same-scope-name target under the CURRENT project - dispatches onMoveLeaf with projectDirTo for a cross-project pick CLI: - cli.rs's `move` subcommand wraps the new impl signature by passing `paths` to both sides — the CLI itself stays single-project until #140's `move-key` lands and we revisit. Docs: - SMOKE_TEST.md: new bullet for the two-project cross-scope flow. Closes #179, closes #181. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Finishes the half-disabled cross-project Move-to submenu from v0.6.
Before
OtherProject > Localwhenever the source was Local (and same for Project) — the skip ran in every nested project, not just the current one.After
Test plan
Reviewer notes
move-keysubcommand for moving whole top-level keys #140.Closes #179, closes #181.
🤖 Generated with Claude Code