Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/SMOKE_TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ reviewer.
between two scopes that share a file: the apply should fail with a
typed error naming both scopes and the shared path. Close and
re-open against a normal project root; the banner disappears.
- [ ] **Cross-project Move-to: same-scope unlock (#179, #181).** Set up
two project sandboxes (e.g. `/tmp/cs-smoke-a` and `/tmp/cs-smoke-b`,
each with a `.claude/settings.local.json` containing a different
rule). Launch ClaudeScope against Project A. Right-click a rule
in **Local** → **Move to** → **project-b** submenu — both
`Local` **and** `Project` appear (pre-#181 only one of them did).
Pick `Local`: the diff modal shows source = Project A's Local
and destination = Project B's Local (two different files); apply
and confirm both files on disk changed. Open Project B's
`.claude/settings.local.json` to verify the moved rule landed
there, NOT in Project A's `settings.json`. Run **Undo** — both
files revert in one shot. Inspect History — the entry's
`project_dir` shows Project A, `project_dir_to` shows Project B.
- [ ] **Kind-disagreement badge (#156).** Hand-edit a rule into two
scopes under different kinds — e.g. `Bash(git push)` as `allow` in
User and `deny` in Project. Reload. A red ⚠ badge appears next to
Expand Down
29 changes: 28 additions & 1 deletion src-tauri/src/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,24 @@ pub struct Record {
pub actor: Actor,
/// Project root that scoped the operation. `None` for user-scope-only
/// ops (e.g. moving a rule into User scope from another user-level
/// scope — there is no project context).
/// scope — there is no project context). For a single-project op
/// (the usual case), this is the only project root the record
/// references. For a cross-project move, this is the **source**'s
/// project root; the destination's lives in
/// [`project_dir_to`](Record::project_dir_to). See #179.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_dir: Option<PathBuf>,
/// Destination project root, set ONLY when a [`Kind::Move`] crossed
/// project boundaries — i.e. `apply_move_leaf` was invoked with a
/// `project_dir_to` distinct from `project_dir_from`. Single-project
/// moves leave this `None` and the wire format stays identical to
/// pre-#179 records (the `skip_serializing_if` ensures the field is
/// omitted, not emitted as `null`). On read, a missing field
/// deserializes to `None` and the restore path falls back to
/// `project_dir` for the to-side's allowlist resolution, so existing
/// audit logs replay unchanged.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_dir_to: Option<PathBuf>,
/// Source side. `None` for [`Kind::Add`] (no source) and for every
/// [`Kind::Restore`] entry — restores carry their per-file snapshots in
/// [`Record::restore`] instead, since they can touch more than two files.
Expand Down Expand Up @@ -249,6 +264,7 @@ impl Record {
leaf_kind,
actor,
project_dir,
project_dir_to: None,
from,
to,
path,
Expand All @@ -258,6 +274,16 @@ impl Record {
}
}

/// Tag a [`Kind::Move`] record with a distinct destination project
/// root for the cross-project case (#179). Single-project callers
/// don't need this — they leave `project_dir_to` at its default
/// `None`, which the restore path treats as "same as project_dir."
/// Returning `Self` keeps the call site a one-liner builder chain.
pub fn with_project_dir_to(mut self, project_dir_to: Option<PathBuf>) -> Self {
self.project_dir_to = project_dir_to;
self
}

/// Construct a [`Kind::Restore`] record for an undo / redo /
/// restore-to-point op (#19 Phases 3-4). `from` / `to` / `to_kind` are
/// always `None` on a restore record — the affected sides live in
Expand All @@ -277,6 +303,7 @@ impl Record {
leaf_kind,
actor,
project_dir,
project_dir_to: None,
from: None,
to: None,
path,
Expand Down
7 changes: 6 additions & 1 deletion src-tauri/src/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,11 @@ fn cmd_move(
};

if dry_run {
let preview = diff_move_leaf_impl(paths, &req)?;
// CLI move is single-project: pass `paths` to both sides of the
// #179-split impl. The cross-project case lives only on the GUI's
// Move-to submenu (#111); the CLI's `move` subcommand keeps its
// single `--project-dir` surface until #140's `move-key` lands.
let preview = diff_move_leaf_impl(paths, paths, &req)?;
print_preview(&preview, json)?;
return Ok(());
}
Expand All @@ -761,6 +765,7 @@ fn cmd_move(
let from_file = paths.path_for(from).map(Path::to_path_buf);
let to_file = paths.path_for(to).map(Path::to_path_buf);
let outcome = apply_move_leaf_impl(
paths,
paths,
&req,
cli_backups_for_session(),
Expand Down
Loading
Loading