Merge fix/612

This commit is contained in:
bflatastic committed 2026-10-17 14:35:15 +07:00
commit 1328092a7e
2 files changed
+75 -4

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
//! Imported file bytes already live in the document: the command log keeps no second copy.
use base64::Engine as _;
use serde_json::{Value, json};
use crate::Session;
const SVG: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"><rect width="40" height="20" fill="#3366cc"/></svg>"##;
fn payload() -> String {
base64::engine::general_purpose::STANDARD.encode(SVG)
}
fn holds_payload(journal: &[(String, Value)]) -> bool {
let payload = payload();
journal.iter().any(|(_, params)| params.to_string().contains(&payload))
}
#[test]
fn byte_imports_keep_no_payload_in_the_journal() {
let mut s = Session::new();
let before = s.doc().unwrap().node_count();
s.execute("file.import", &json!({"data": payload(), "name": "pasted.svg"})).unwrap();
assert!(!holds_payload(&s.journal), "the journal must not retain the Base64 file");
// Not journaling is no change to the edit itself: still one undo step.
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(s.doc().unwrap().node_count(), before);
}
#[test]
fn batch_wrappers_do_not_retain_import_payloads() {
let mut s = Session::new();
let nested =
json!({"command": "command.batch", "params": {"commands": [{"command": "file.import", "params": {"data": payload(), "name": "a.svg"}}]}});
s.execute("command.batch", &json!({"commands": [nested]})).unwrap();
assert!(!holds_payload(&s.journal));
}
#[test]
fn path_imports_and_ordinary_edits_are_still_journaled() {
let dir = std::env::temp_dir().join(format!("figcraft-import-journal-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("kit.svg");
std::fs::write(&path, SVG).unwrap();
let mut s = Session::new();
s.execute("file.import", &json!({"path": path.to_string_lossy()})).unwrap();
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
let ids: Vec<_> = s.journal.iter().map(|(id, _)| id.as_str()).collect();
assert_eq!(ids, ["file.import", "shape.rectangle"]);
std::fs::remove_dir_all(&dir).ok();
}
+24 -4
View File
@@ -721,7 +721,8 @@ pub struct Session {
/// dark page colour while it is dark (added pages take the current page's). Headless sessions (CLI, MCP, exports) have no
/// theme, so what scripts make is the same everywhere.
pub ui_dark: Option<bool>,
/// Command log (id + params) for actions/automation.
/// Command log (id + params) for actions/automation. File imports that carry the file bytes
/// (`file.import` with `data`) are not logged, so the log never holds a copy of a pasted file.
pub journal: Vec<(String, Value)>,
/// Recently used colours.
pub recent_colors: Vec<figcraft_color::Color>,
@@ -746,6 +747,19 @@ impl Default for Session {
}
}
/// Whether a command, or a batch wrapping one, imports a file passed as Base64 `data`.
fn command_carries_import_bytes(id: &str, params: &Value) -> bool {
match id {
"file.import" => params::get(params, "data").is_some(),
"command.batch" => params.get("commands").and_then(Value::as_array).is_some_and(|commands| {
commands.iter().any(|c| {
c.get("command").and_then(Value::as_str).zip(c.get("params")).is_some_and(|(id, params)| command_carries_import_bytes(id, params))
})
}),
_ => false,
}
}
impl Session {
/// A session with one new document.
pub fn new() -> Self {
@@ -885,9 +899,13 @@ impl Session {
if spec.history && id != "edit.repeat" {
self.last_command = Some((id.to_string(), params.clone()));
}
self.journal.push((id.to_string(), params.clone()));
if self.journal.len() > 5000 {
self.journal.drain(..1000);
// Imported bytes already live in the document: a Base64 copy in the log would stay
// for the whole session.
if !command_carries_import_bytes(id, params) {
self.journal.push((id.to_string(), params.clone()));
if self.journal.len() > 5000 {
self.journal.drain(..1000);
}
}
}
Err(_) => {
@@ -1550,6 +1568,8 @@ mod html_codegen_tests;
#[cfg(test)]
mod import_export_tests;
#[cfg(test)]
mod import_journal_tests;
#[cfg(test)]
mod inspect_behaviour_tests;
#[cfg(test)]
mod interaction_rollback_tests;