mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
UX update
This commit is contained in:
parent
3e6a50ce4b
commit
0e5e809894
5 changed files with 397 additions and 48 deletions
178
.cursor/plans/destructive_syncback_mode_acb8cb54.plan.md
Normal file
178
.cursor/plans/destructive_syncback_mode_acb8cb54.plan.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
---
|
||||
name: Destructive Syncback Mode
|
||||
overview: Add a `--clean` flag to syncback that ignores existing file structure and middleware, treating all instances as new to ensure consistent 1:1 output regardless of existing filesystem state.
|
||||
todos: []
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Destructive Syncback Mode
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
Your inconsistency stems from three preservation mechanisms in syncback:
|
||||
|
||||
### 1. File Name Preservation (`[src/syncback/file_names.rs](src/syncback/file_names.rs)` lines 19-34)
|
||||
|
||||
When an old instance exists, the **existing filename is always preserved**, even if the instance was renamed:
|
||||
|
||||
```rust
|
||||
if let Some(old_inst) = old_inst {
|
||||
if let Some(source) = old_inst.metadata().relevant_paths.first() {
|
||||
source.file_name() // Returns OLD filename, ignores new instance name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Middleware Preservation (`[src/syncback/mod.rs](src/syncback/mod.rs)` lines 353-357)
|
||||
|
||||
When an old instance exists, its middleware format is preserved:
|
||||
|
||||
```rust
|
||||
} else if let Some(old_middleware) = old_middleware {
|
||||
// Use old middleware, even if a clean sync would choose differently
|
||||
middleware = old_middleware;
|
||||
}
|
||||
```
|
||||
|
||||
This means a `Part.rbxm` stays as `.rbxm` instead of potentially becoming a `Part/` directory with children.
|
||||
|
||||
### 3. Name-Based Child Matching (`[src/snapshot_middleware/dir.rs](src/snapshot_middleware/dir.rs)` lines 196-216)
|
||||
|
||||
Children are matched by name between old and new DOMs. If a child exists in both, it inherits the old structure:
|
||||
|
||||
```rust
|
||||
if let Some(old_child) = old_child_map.remove(new_child.name.as_str()) {
|
||||
// Existing child - pass old reference, preserving its structure
|
||||
children.push(snapshot.with_joined_path(*new_child_ref, Some(old_child.id()))?);
|
||||
} else {
|
||||
// New child - gets fresh structure
|
||||
children.push(snapshot.with_joined_path(*new_child_ref, None)?);
|
||||
}
|
||||
```
|
||||
|
||||
## Solution: Add `--clean` Flag
|
||||
|
||||
Add a `--clean` flag that makes syncback treat all instances as "new" by setting `old` references to `None`, ensuring consistent output regardless of existing filesystem state.
|
||||
|
||||
### Changes Required
|
||||
|
||||
#### 1. Add CLI Flag (`[src/cli/syncback.rs](src/cli/syncback.rs)`)
|
||||
|
||||
Add `--clean` flag to the command definition:
|
||||
|
||||
```rust
|
||||
/// If provided, syncback will ignore existing file structure and create
|
||||
/// a fresh project layout. This ensures consistent output regardless of
|
||||
/// existing filesystem state.
|
||||
#[clap(long)]
|
||||
pub clean: bool,
|
||||
```
|
||||
|
||||
#### 2. Pass Flag to `syncback_loop` (`[src/cli/syncback.rs](src/cli/syncback.rs)`)
|
||||
|
||||
Modify the `syncback_loop` call to pass the clean flag:
|
||||
|
||||
```rust
|
||||
let snapshot = syncback_loop(
|
||||
session_old.vfs(),
|
||||
&mut dom_old,
|
||||
dom_new,
|
||||
session_old.root_project(),
|
||||
self.clean, // New parameter
|
||||
)?;
|
||||
```
|
||||
|
||||
#### 3. Modify `syncback_loop` (`[src/syncback/mod.rs](src/syncback/mod.rs)`)
|
||||
|
||||
Update function signature and initial snapshot:
|
||||
|
||||
```rust
|
||||
pub fn syncback_loop(
|
||||
vfs: &Vfs,
|
||||
old_tree: &mut RojoTree,
|
||||
mut new_tree: WeakDom,
|
||||
project: &Project,
|
||||
clean_mode: bool, // New parameter
|
||||
) -> anyhow::Result<FsSnapshot>
|
||||
```
|
||||
|
||||
When `clean_mode` is true, set the initial `old` reference to `None`:
|
||||
|
||||
```rust
|
||||
let mut snapshots = vec![SyncbackSnapshot {
|
||||
data: syncback_data,
|
||||
old: if clean_mode { None } else { Some(old_tree.get_root_id()) },
|
||||
new: new_tree.root_ref(),
|
||||
path: project.file_location.clone(),
|
||||
middleware: Some(Middleware::Project),
|
||||
}];
|
||||
```
|
||||
|
||||
#### 4. Track Removed Files in Clean Mode (`[src/syncback/mod.rs](src/syncback/mod.rs)`)
|
||||
|
||||
In clean mode, we need to remove all existing files in the target paths. Add logic to collect existing paths for removal:
|
||||
|
||||
```rust
|
||||
if clean_mode {
|
||||
// Collect all existing paths under the project tree for removal
|
||||
for ref_id in descendants(old_tree.inner(), old_tree.get_root_id()) {
|
||||
if let Some(inst) = old_tree.get_instance(ref_id) {
|
||||
for path in inst.metadata().relevant_paths.iter() {
|
||||
if path.is_dir() {
|
||||
fs_snapshot.remove_dir(path);
|
||||
} else {
|
||||
fs_snapshot.remove_file(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Update Child Snapshot Creation (`[src/snapshot_middleware/dir.rs](src/snapshot_middleware/dir.rs)`)
|
||||
|
||||
The `SyncbackSnapshot::with_joined_path` method already handles `old = None` correctly - it will create a fresh child snapshot without old references.
|
||||
|
||||
### Data Flow Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph currentBehavior [Current Behavior]
|
||||
A1[Syncback on Existing Files] --> B1{Old Instance Exists?}
|
||||
B1 -->|Yes| C1[Preserve Filename]
|
||||
B1 -->|Yes| D1[Preserve Middleware]
|
||||
B1 -->|Yes| E1[Match Children by Name]
|
||||
C1 --> F1[Inconsistent Structure]
|
||||
D1 --> F1
|
||||
E1 --> F1
|
||||
end
|
||||
|
||||
subgraph cleanMode [Clean Mode]
|
||||
A2[Syncback with --clean] --> B2[Set old = None]
|
||||
B2 --> C2[Generate Fresh Filename]
|
||||
B2 --> D2[Choose Best Middleware]
|
||||
B2 --> E2[All Children Are New]
|
||||
C2 --> F2[Consistent 1:1 Structure]
|
||||
D2 --> F2
|
||||
E2 --> F2
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Current behavior (preserves existing structure)
|
||||
rojo syncback -i game.rbxl
|
||||
|
||||
# Destructive mode (ignores existing structure, creates fresh layout)
|
||||
rojo syncback -i game.rbxl --clean
|
||||
|
||||
# Combine with other flags
|
||||
rojo syncback -i game.rbxl --clean --list --dry-run
|
||||
```
|
||||
|
||||
### Alternative Consideration
|
||||
|
||||
If you also want to support project-level configuration (so you don't need to pass `--clean` every time), we could add a `syncbackRules.cleanMode: true` option in the project file. Let me know if you'd prefer that approach.
|
||||
Loading…
Add table
Add a link
Reference in a new issue