From 03a514f38d369323ef630fde497fecba53efe91d Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Thu, 14 May 2026 15:00:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(shell):=20wire=20Document.components:=20Co?= =?UTF-8?q?mponentLibrary=20(P2=20=E2=80=94=20storage)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the var_table wiring from commit e81e8e09. Adds the component-library field to Document so the canonical .op loader (and future "Save as Component" mutator) has a place to land component definitions. - `Document.components: ComponentLibrary` — Default = empty. - Patched the same 9 test fixtures (`tests_geometry.rs` × 7 + `canvas_viewport.rs` + `layer_panel_tests.rs`) to seed the field alongside `var_table`. - Library lookup methods (`find_by_id` / `find_by_name` / `insert` with id-replace) are usable on `doc.components` from anywhere. Pen-doc-adapter integration (`pen_document_to_payload` doesn't yet carry components) + `NodeKind::Instance` variant for cross-document instances + Components panel widget all stay pending — the data shape lands first so the loader integration is one focused commit. document.rs / mutators.rs both back at the 800-line cap via doc-comment compaction (Node field docs dropped to inline naming, `t()` Doc compressed to one line). Tests: 187 shell-core (no new assertions; the 3 components tests landed in commit 0fecab0a). Wasm32 build clean. --- crates/openpencil-shell-core/src/document.rs | 3 +-- crates/openpencil-shell-core/src/document/mutators.rs | 10 ++++------ .../src/document/tests_geometry.rs | 7 +++++++ .../src/widgets/canvas_viewport.rs | 1 + .../src/widgets/layer_panel_tests.rs | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/openpencil-shell-core/src/document.rs b/crates/openpencil-shell-core/src/document.rs index 799addcea..3d362b527 100644 --- a/crates/openpencil-shell-core/src/document.rs +++ b/crates/openpencil-shell-core/src/document.rs @@ -267,9 +267,8 @@ pub struct Document { pub ui: UiState, /// Undo / redo stacks. Push BEFORE a transactional mutation. pub history: History, - /// Design variables + theme registry. Populated by the canonical - /// `.op` loader; lookup via `Variable::resolve` / `VariableTable::resolve`. pub var_table: VariableTable, + pub components: ComponentLibrary, } /// Document undo / redo stacks. Snapshot = deep copy of the diff --git a/crates/openpencil-shell-core/src/document/mutators.rs b/crates/openpencil-shell-core/src/document/mutators.rs index 350066b00..fe7adc49d 100644 --- a/crates/openpencil-shell-core/src/document/mutators.rs +++ b/crates/openpencil-shell-core/src/document/mutators.rs @@ -26,16 +26,12 @@ impl Document { } } - /// Translate `key` via the active locale; falls through to `key`. - /// `key` is `&'static str` so the result is also `'static`, - /// letting callers store the string slice instead of cloning - /// it into an owned `String` per paint frame. + /// Translate `key` via active locale; falls through to `key`. `'static` to skip per-frame clones. pub fn t(&self, key: &'static str) -> &'static str { crate::i18n::translate(self.ui.locale, key) } - /// Empty document with one empty default page named "Page 1". - /// Used by host smoke fixtures. + /// Empty document with one default page; used by host smoke fixtures. pub fn empty() -> Self { Self { pages: vec![Page::new(1, "Page 1", Vec::new())], @@ -49,6 +45,7 @@ impl Document { ui: UiState::default(), history: History::default(), var_table: VariableTable::default(), + components: ComponentLibrary::default(), } } @@ -97,6 +94,7 @@ impl Document { ui: UiState::default(), history: History::default(), var_table: VariableTable::default(), + components: ComponentLibrary::default(), }; debug_assert!( doc.validate().is_ok(), diff --git a/crates/openpencil-shell-core/src/document/tests_geometry.rs b/crates/openpencil-shell-core/src/document/tests_geometry.rs index 0bf6ef866..e428befb6 100644 --- a/crates/openpencil-shell-core/src/document/tests_geometry.rs +++ b/crates/openpencil-shell-core/src/document/tests_geometry.rs @@ -473,6 +473,7 @@ fn document_validate_catches_duplicate_node_id() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let result = doc.validate(); assert!(result.is_err()); @@ -508,6 +509,7 @@ fn document_validate_catches_empty_pages() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let result = doc.validate(); assert!(result.is_err()); @@ -533,6 +535,7 @@ fn document_validate_catches_empty_pages() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let err2 = doc2.validate().unwrap_err(); assert!( @@ -563,6 +566,7 @@ fn document_selected_node_scopes_to_active_page() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; // Selection is on a non-active page → returns None. assert!(doc.selected_node().is_none()); @@ -593,6 +597,7 @@ fn document_active_page_returns_indexed_page() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; assert_eq!(doc.active_page().unwrap().name, "second"); } @@ -611,6 +616,7 @@ fn document_active_page_returns_none_when_index_out_of_range() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; assert!(doc.active_page().is_none()); } @@ -664,6 +670,7 @@ fn add_page_returns_none_on_id_overflow() { ui: UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let mut doc = doc; assert_eq!(doc.add_page(), None); diff --git a/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs b/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs index e979cfbaf..fe2fc59e5 100644 --- a/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs +++ b/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs @@ -811,6 +811,7 @@ mod tests { ui: crate::document::UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let viewport = CanvasViewport::from_document(&doc); let mut backend = RecordingBackend::default(); diff --git a/crates/openpencil-shell-core/src/widgets/layer_panel_tests.rs b/crates/openpencil-shell-core/src/widgets/layer_panel_tests.rs index 90056c407..10107420f 100644 --- a/crates/openpencil-shell-core/src/widgets/layer_panel_tests.rs +++ b/crates/openpencil-shell-core/src/widgets/layer_panel_tests.rs @@ -136,6 +136,7 @@ fn from_document_scopes_to_active_page_only() { ui: crate::document::UiState::default(), history: crate::document::History::default(), var_table: crate::document::VariableTable::default(), + components: crate::document::ComponentLibrary::default(), }; let panel = LayerPanel::from_document(&doc); assert_eq!(panel.items.len(), 1);