mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-20 10:06:54 +03:00
Fix: isolate review components from assembled context
AI-assisted implementation with Codex under maintainer direction.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -85,8 +85,11 @@ fn view(
|
||||
pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String, Vec<u8>>), String> {
|
||||
let canonical = project.canonicalize().map_err(|e| e.to_string())?;
|
||||
let project = canonical.as_path();
|
||||
if input["schemaVersion"] != 1 {
|
||||
return Err("manifest schemaVersion must be 1".into());
|
||||
if !matches!(input["schemaVersion"].as_u64(), Some(1 | 2)) {
|
||||
return Err("manifest schemaVersion must be 1 or 2".into());
|
||||
}
|
||||
if input["schemaVersion"] == 2 && !matches!(input["stage"].as_str(), Some("components" | "hero")) {
|
||||
return Err("schemaVersion 2 requires stage components or hero".into());
|
||||
}
|
||||
let mut packet = input.clone();
|
||||
for key in ["capture", "captureVerified"] {
|
||||
@@ -126,6 +129,24 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
|
||||
}
|
||||
|
||||
view(&mut packet["comp"], project, &mut files, &mut comp_files)?;
|
||||
// Selector ownership is part of each shared document's input contract. A peer
|
||||
// target changing must invalidate captures that previously excluded it.
|
||||
let mut targets: BTreeMap<String, Vec<Value>> = BTreeMap::new();
|
||||
if input["schemaVersion"] == 2 && input["stage"] == "components" {
|
||||
for c in input["components"].as_array().ok_or("components must be an array")? {
|
||||
if c["preview"]["kind"] != "page" && c["preview"].get("selector").is_some() {
|
||||
return Err("only code previews can declare a selector; place raster DOM targets in context".into());
|
||||
}
|
||||
let target = if c["preview"]["kind"] == "page" { Some(&c["preview"]) }
|
||||
else if c["context"]["kind"] == "page" { Some(&c["context"]) } else { None };
|
||||
if let Some(target) = target {
|
||||
let selector = string(target, "selector")?;
|
||||
if selector.len() > 1024 { return Err("component selector too long".into()); }
|
||||
let path = string(target, "path")?;
|
||||
targets.entry(path.into()).or_default().push(json!({"id":c["id"],"selector":selector}));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut ids = BTreeSet::new();
|
||||
let components = packet["components"]
|
||||
.as_array_mut()
|
||||
@@ -143,6 +164,7 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
|
||||
if let Some(view) = c.get_mut(key).and_then(Value::as_object_mut) {
|
||||
view.remove("sourceKind");
|
||||
view.remove("capture");
|
||||
view.remove("isolation");
|
||||
}
|
||||
}
|
||||
let id = string(c, "id")?.to_string();
|
||||
@@ -196,9 +218,13 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap<String,
|
||||
}
|
||||
}
|
||||
c.as_object_mut().unwrap().remove("revision");
|
||||
c["revision"] = json!(digest(
|
||||
&serde_json::to_vec(&json!({"component":c,"files":used})).unwrap()
|
||||
));
|
||||
let mut identity = json!({"component":c,"files":used});
|
||||
let target_path = if c["preview"]["kind"] == "page" { Some(preview_path.as_str()) }
|
||||
else { c["context"]["url"].as_str().and_then(|url| url.strip_prefix("/files/")) };
|
||||
if let Some(peers) = target_path.and_then(|path| targets.get(path)) {
|
||||
identity["targets"] = json!(peers);
|
||||
}
|
||||
c["revision"] = json!(digest(&serde_json::to_vec(&identity).unwrap()));
|
||||
}
|
||||
let total: usize = files.values().map(Vec::len).sum();
|
||||
if total > 256 * 1024 * 1024 {
|
||||
|
||||
@@ -489,3 +489,39 @@ fn measured_inventory_is_bound_without_repeated_author_dependencies() {
|
||||
fs::write(path, br#"{"regions":[]}"#).unwrap();
|
||||
assert!(store::sources_current(&state).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolated_components_require_targets_and_pin_their_ownership() {
|
||||
let f = Fixture::new();
|
||||
fs::create_dir_all(f.project.join(".impeccable/build")).unwrap();
|
||||
fs::write(f.project.join(".impeccable/build/spec.json"), br#"{"regions":[{"id":"art","kind":"plate"},{"id":"control","kind":"control"}]}"#).unwrap();
|
||||
let mut input = f.manifest();
|
||||
input["schemaVersion"] = json!(2);
|
||||
input["stage"] = json!("components");
|
||||
assert!(manifest::freeze(&f.project, &input).unwrap_err().contains("selector"));
|
||||
input["components"][1]["preview"]["selector"] = json!("button");
|
||||
let (first, _) = manifest::freeze(&f.project, &input).unwrap();
|
||||
input["components"][1]["preview"]["selector"] = json!("#cta");
|
||||
let (changed, _) = manifest::freeze(&f.project, &input).unwrap();
|
||||
assert_ne!(first["components"][1]["revision"], changed["components"][1]["revision"]);
|
||||
assert_eq!(first["components"][0]["revision"], changed["components"][0]["revision"]);
|
||||
input["components"][0]["preview"]["selector"] = json!("#fake-raster-target");
|
||||
assert!(manifest::freeze(&f.project, &input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_target_changes_invalidate_other_isolated_components() {
|
||||
let f = Fixture::new();
|
||||
fs::create_dir_all(f.project.join(".impeccable/build")).unwrap();
|
||||
fs::write(f.project.join(".impeccable/build/spec.json"), br#"{"regions":[{"id":"art","kind":"chrome"},{"id":"control","kind":"control"}]}"#).unwrap();
|
||||
let mut input = f.manifest();
|
||||
input["schemaVersion"] = json!(2); input["stage"] = json!("components");
|
||||
input["components"][0]["preview"] = json!({"kind":"page","path":"control.html","selector":"#background"});
|
||||
input["components"][1]["preview"]["selector"] = json!("button");
|
||||
let (before,_) = manifest::freeze(&f.project,&input).unwrap();
|
||||
input["components"][1]["preview"]["selector"] = json!("#cta");
|
||||
let (after,_) = manifest::freeze(&f.project,&input).unwrap();
|
||||
assert_ne!(before["components"][0]["revision"],after["components"][0]["revision"]);
|
||||
input["stage"] = Value::Null;
|
||||
assert!(manifest::freeze(&f.project,&input).is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user