From ec670761eaa3b7f2360eb1299762e11111e9bed7 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 17:34:25 +0300 Subject: [PATCH 01/16] feat(C/N2): shared declarative TikZ library catalogue and evidence API --- .../src/bin/n2-library-fixture.rs | 16 ++ crates/tikzarium-extensions/src/primitives.rs | 65 +++++ .../src/project_disconnect.rs | 5 +- .../src/project_imports.rs | 57 ++++- .../tests/library_catalog.rs | 234 ++++++++++++++++++ docs/EXTENSIONS.md | 11 + extensions/primitives/catalog-v1.toml | 114 +++++++++ .../2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md | 15 ++ plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md | 14 ++ 9 files changed, 525 insertions(+), 6 deletions(-) create mode 100644 crates/tikzarium-extensions/src/bin/n2-library-fixture.rs create mode 100644 crates/tikzarium-extensions/tests/library_catalog.rs create mode 100644 insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md create mode 100644 plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md diff --git a/crates/tikzarium-extensions/src/bin/n2-library-fixture.rs b/crates/tikzarium-extensions/src/bin/n2-library-fixture.rs new file mode 100644 index 00000000..149b9e8e --- /dev/null +++ b/crates/tikzarium-extensions/src/bin/n2-library-fixture.rs @@ -0,0 +1,16 @@ +//! C-owned generated TeX documents for independent N2 XeLaTeX verification. +//! Usage: cargo run -p tikzarium-extensions --bin n2-library-fixture -- calc +use std::{env, process}; +use tikzarium_extensions::primitives::Catalog; + +fn main() { + let name = env::args().nth(1).unwrap_or_default(); + let catalog = Catalog::bundled().expect("valid bundled declarative catalogue"); + match catalog.library(&name) { + Some(library) => print!("{}", library.example), + None => { + eprintln!("unknown N2 library; select arrows.meta, positioning, calc or fit"); + process::exit(2); + } + } +} diff --git a/crates/tikzarium-extensions/src/primitives.rs b/crates/tikzarium-extensions/src/primitives.rs index 0575713f..9f6149f9 100644 --- a/crates/tikzarium-extensions/src/primitives.rs +++ b/crates/tikzarium-extensions/src/primitives.rs @@ -6,6 +6,7 @@ use std::{ }; use thiserror::Error; pub const SCHEMA_VERSION: u32 = 1; +pub const N2_LIBRARY_NAMES: [&str; 4] = ["arrows.meta", "positioning", "calc", "fit"]; const IDS: [&str; 6] = [ "tikz.line", "tikz.arrow", @@ -23,6 +24,8 @@ pub enum PrimitiveError { Toml(#[from] toml::de::Error), #[error("Unknown tool: {0}")] UnknownTool(String), + #[error("Unknown TikZ library: {0}")] + UnknownLibrary(String), #[error("Unknown parameter: {0}")] UnknownParameter(String), #[error("Missing parameter: {0}")] @@ -41,6 +44,8 @@ pub struct Catalog { pub schema_version: u32, pub id: String, pub tools: Vec, + #[serde(default)] + pub libraries: Vec, } #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] @@ -59,6 +64,21 @@ pub struct ToolSpec { } #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] +pub struct LibrarySpec { + pub id: String, + pub name: String, + pub title_ru: String, + pub syntax: String, + pub help_ru: String, + pub example: String, + pub warning_ru: String, + pub interaction_ru: String, + pub manual_section: String, + pub manual_url: String, + pub requirements: Requirements, +} +#[derive(Debug, Clone, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Requirements { pub packages: Vec, pub libraries: Vec, @@ -119,6 +139,10 @@ impl Catalog { pub fn tool(&self, id: &str) -> Option<&ToolSpec> { self.tools.iter().find(|t| t.id == id) } + /// Declarative N2 help/requirements only; not proof of installed packages. + pub fn library(&self, name: &str) -> Option<&LibrarySpec> { + self.libraries.iter().find(|library| library.name == name) + } /// Installation status and project-loaded status are independent observations. pub fn capabilities( &self, @@ -296,6 +320,47 @@ impl Catalog { return Err(invalid("example mismatches executable schema")); } } + if self.libraries.len() != N2_LIBRARY_NAMES.len() { + return Err(invalid("incomplete N2 TikZ library help")); + } + let mut seen_libraries = HashSet::new(); + for library in &self.libraries { + let expected_syntax = format!("\\usetikzlibrary{{{}}}", library.name); + let expected_id = format!("tikz.library.{}", library.name.replace('.', "-")); + if !N2_LIBRARY_NAMES.contains(&library.name.as_str()) + || !seen_libraries.insert(library.name.as_str()) + || library.id != expected_id + || library.syntax != expected_syntax + || library.requirements.packages != ["tikz"] + || library.requirements.libraries != [library.name.as_str()] + || library.requirements.engine != "xelatex" + || library.requirements.pgf_version_min != "UNKNOWN" + || library.requirements.documented_against != "3.1.12" + || [ + library.title_ru.as_str(), + &library.help_ru, + &library.warning_ru, + &library.interaction_ru, + &library.manual_section, + ] + .iter() + .any(|value| value.trim().is_empty()) + || !library.manual_url.starts_with("https://tikz.dev/") + || !library.example.contains("\\usepackage{tikz}") + || !library.example.contains(&expected_syntax) + || !library.example.contains("\\begin{document}") + || !library.example.contains("\\end{document}") + { + return Err(invalid("invalid or incomplete N2 library record")); + } + } + if self.tools.iter().any(|tool| { + tool.requirements.libraries.iter().any(|name| { + !self.libraries.iter().any(|library| library.name == *name) + }) + }) { + return Err(invalid("tool requirement lacks shared library help")); + } Ok(()) } } diff --git a/crates/tikzarium-extensions/src/project_disconnect.rs b/crates/tikzarium-extensions/src/project_disconnect.rs index 3cc63df4..727fe1d1 100644 --- a/crates/tikzarium-extensions/src/project_disconnect.rs +++ b/crates/tikzarium-extensions/src/project_disconnect.rs @@ -6,13 +6,12 @@ use crate::{ primitive_edit::SourceTarget, - primitives::{Evidence, PrimitiveError, SourcePatch}, + primitives::{Evidence, N2_LIBRARY_NAMES, PrimitiveError, SourcePatch}, project_imports::{ImportKind, inspect, literal_names, uncommented}, }; use std::ops::Range; const BEGIN_DOCUMENT: &str = "\\begin{document}"; -const SUPPORTED_LIBRARIES: [&str; 4] = ["arrows.meta", "positioning", "calc", "fit"]; /// A proposed, source-visible deletion. B owns confirmation and Undo UI. #[derive(Debug, Clone, PartialEq, Eq)] @@ -140,7 +139,7 @@ pub fn plan_library_disconnect( target: SourceTarget, name: &str, ) -> DisconnectDecision { - if !SUPPORTED_LIBRARIES.contains(&name) { + if !N2_LIBRARY_NAMES.contains(&name) { return DisconnectDecision::Refused { reason: "library has no N2 disconnect adapter; use source", }; diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index 8f7a3f25..0af5e447 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -6,12 +6,11 @@ use crate::{ primitive_edit::SourceTarget, - primitives::{Capabilities, Catalog, Evidence, PrimitiveError, SourcePatch}, + primitives::{Capabilities, Catalog, Evidence, N2_LIBRARY_NAMES, PrimitiveError, SourcePatch}, }; use std::{collections::BTreeSet, ops::Range}; const BEGIN_DOCUMENT: &str = "\\begin{document}"; -const SUPPORTED_LIBRARIES: [&str; 4] = ["arrows.meta", "positioning", "calc", "fit"]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ImportKind { @@ -446,6 +445,58 @@ pub fn capabilities_for_tool( catalog.capabilities(tool_id, installed, loaded, engine) } +/// N2 library panel status from the *same* declarative catalogue as the six +/// primitive tools. Installation is caller-provided, never inferred from TeX +/// declarations; visual_editable stays false for arbitrary library constructs. +/// tool_implemented means at least one listed native generator depends on this +/// library (currently arrows.meta only), not full library editing coverage. +pub fn capabilities_for_library( + catalog: &Catalog, + name: &str, + project: &ProjectImports, + installed: Evidence, + engine: Option<&str>, +) -> Result { + let library = catalog + .library(name) + .ok_or_else(|| PrimitiveError::UnknownLibrary(name.to_owned()))?; + let package = project.status(ImportKind::Package, "tikz"); + let loaded_library = project.status(ImportKind::TikzLibrary, name); + let project_loaded = + if package == Evidence::Confirmed && loaded_library == Evidence::Confirmed { + Evidence::Confirmed + } else if package == Evidence::Unknown || loaded_library == Evidence::Unknown { + Evidence::Unknown + } else { + Evidence::Absent + }; + let tool_implemented = if catalog.tools.iter().any(|tool| { + tool.requirements + .libraries + .iter() + .any(|required| required == name) + }) { + Evidence::Confirmed + } else { + Evidence::Absent + }; + let engine_compatible = match engine { + Some(actual) if actual.eq_ignore_ascii_case(&library.requirements.engine) => { + Evidence::Confirmed + } + Some(_) => Evidence::Absent, + None => Evidence::Unknown, + }; + Ok(Capabilities { + installed, + project_loaded, + engine_compatible, + tool_implemented, + help_available: Evidence::Confirmed, + visual_editable: false, + }) +} + /// An explicit source proposal; B must show the diff and request the user action. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ImportPlan { @@ -492,7 +543,7 @@ pub fn plan_import( reason: "only the tikz package has an N2 automatic source adapter", }; } - ImportKind::TikzLibrary if !SUPPORTED_LIBRARIES.contains(&name) => { + ImportKind::TikzLibrary if !N2_LIBRARY_NAMES.contains(&name) => { return ImportDecision::Refused { reason: "library has no N2 literal import adapter; use source", }; diff --git a/crates/tikzarium-extensions/tests/library_catalog.rs b/crates/tikzarium-extensions/tests/library_catalog.rs new file mode 100644 index 00000000..e1a5eac1 --- /dev/null +++ b/crates/tikzarium-extensions/tests/library_catalog.rs @@ -0,0 +1,234 @@ +//! C/N2 single-schema library panel contract; no B UI or package installation. +use tikzarium_extensions::{ + primitive_edit::SourceTarget, + primitives::{Catalog, Evidence, N2_LIBRARY_NAMES}, + project_disconnect::{DisconnectDecision, plan_library_disconnect}, + project_imports::{ + ImportDecision, ImportKind, capabilities_for_library, inspect, plan_import, + }, +}; +use std::collections::BTreeMap; + +fn target(source: &str, revision: u64) -> SourceTarget { + inspect(source) + .begin_document_target(revision) + .expect("complete literal preamble") +} + +#[test] +fn four_libraries_are_in_the_same_schema_as_six_native_tools() { + let catalog = Catalog::bundled().unwrap(); + assert_eq!(catalog.tools.len(), 6); + assert_eq!(catalog.libraries.len(), 4); + for name in N2_LIBRARY_NAMES { + let record = catalog.library(name).expect("declarative record"); + assert_eq!(record.name, name); + assert_eq!(record.syntax, format!("\\usetikzlibrary{{{name}}}")); + assert_eq!(record.requirements.libraries, [name]); + assert_eq!(record.requirements.packages, ["tikz"]); + assert_eq!(record.requirements.engine, "xelatex"); + assert_eq!(record.requirements.pgf_version_min, "UNKNOWN"); + assert!(!record.help_ru.trim().is_empty()); + assert!(!record.warning_ru.trim().is_empty()); + assert!(!record.interaction_ru.trim().is_empty()); + assert!(!record.manual_section.trim().is_empty()); + assert!(record.manual_url.starts_with("https://tikz.dev/")); + assert!(record.example.contains("\\begin{tikzpicture}")); + assert!(record.example.contains("\\end{tikzpicture}")); + } + // Shared library additions never rewrite the original N1 source output. + assert_eq!( + catalog.fixture_document().unwrap(), + include_str!("../../../extensions/primitives/fixtures/all-six.tex") + ); + for tool in &catalog.tools { + assert_eq!( + catalog + .generate(&tool.id, &BTreeMap::new()) + .unwrap() + .source, + tool.example + ); + } +} + +#[test] +fn installation_loaded_engine_tool_and_help_remain_five_independent_states() { + let catalog = Catalog::bundled().unwrap(); + let source = "\\documentclass{article}\n\\usepackage{tikz}\n\\usetikzlibrary{arrows.meta,calc}\n\\begin{document}\n"; + let project = inspect(source); + for name in N2_LIBRARY_NAMES { + let states = capabilities_for_library( + &catalog, + name, + &project, + Evidence::Unknown, + Some("xelatex"), + ) + .unwrap(); + assert_eq!(states.installed, Evidence::Unknown); + assert_eq!(states.engine_compatible, Evidence::Confirmed); + assert_eq!(states.help_available, Evidence::Confirmed); + assert!(!states.visual_editable); + assert_eq!( + states.project_loaded, + if ["arrows.meta", "calc"].contains(&name) { + Evidence::Confirmed + } else { + Evidence::Absent + } + ); + assert_eq!( + states.tool_implemented, + if name == "arrows.meta" { + Evidence::Confirmed + } else { + Evidence::Absent + } + ); + } + let different_engine = + capabilities_for_library(&catalog, "fit", &project, Evidence::Confirmed, Some("lualatex")) + .unwrap(); + assert_eq!(different_engine.installed, Evidence::Confirmed); + assert_eq!(different_engine.engine_compatible, Evidence::Absent); + assert_eq!(different_engine.project_loaded, Evidence::Absent); + assert!(capabilities_for_library( + &catalog, + "graphdrawing", + &project, + Evidence::Confirmed, + Some("xelatex") + ) + .is_err()); +} + +#[test] +fn opaque_conditional_import_is_unknown_not_missing_or_installed() { + let catalog = Catalog::bundled().unwrap(); + let source = "\\documentclass{article}\n\\iftrue\n\\usepackage{tikz}\n\\usetikzlibrary{fit}\n\\fi\n\\begin{document}\n"; + let project = inspect(source); + let capabilities = + capabilities_for_library(&catalog, "fit", &project, Evidence::Unknown, Some("xelatex")) + .unwrap(); + assert_eq!(capabilities.project_loaded, Evidence::Unknown); + assert_eq!(capabilities.installed, Evidence::Unknown); + assert!(project.declaration_sites(ImportKind::TikzLibrary, "fit").is_empty()); + assert!(matches!( + plan_import(source, SourceTarget::new(0..0, 8), ImportKind::TikzLibrary, "fit"), + ImportDecision::Refused { .. } + )); + assert!(matches!( + plan_library_disconnect(source, SourceTarget::new(0..0, 8), "fit"), + DisconnectDecision::Refused { .. } + )); +} + +#[test] +fn four_library_connect_disconnect_proposals_are_reversible_with_crlf_and_unicode() { + let catalog = Catalog::bundled().unwrap(); + for name in N2_LIBRARY_NAMES { + let mut source = "\\documentclass{article}\r\n% Живой комментарий\r\n\\begin{document}\r\n\\Unknown{сохранить}\r\n\\end{document}\r\n".to_owned(); + let original = source.clone(); + let mut revision = 31; + let plan = match plan_import( + &source, + target(&source, revision), + ImportKind::TikzLibrary, + name, + ) { + ImportDecision::Proposed(plan) => plan, + other => panic!("expected literal import proposal: {other:?}"), + }; + assert_eq!( + plan.declarations, + ["\\usepackage{tikz}".to_owned(), format!("\\usetikzlibrary{{{name}}}")] + ); + assert_eq!(source, original); + let undo_import = plan.apply(&mut source, &mut revision).unwrap(); + assert_eq!(revision, 32); + assert_eq!( + capabilities_for_library( + &catalog, + name, + &inspect(&source), + Evidence::Unknown, + Some("xelatex") + ) + .unwrap() + .project_loaded, + Evidence::Confirmed + ); + assert!(source.contains("% Живой комментарий\r\n")); + assert!(source.contains("\\Unknown{сохранить}\r\n")); + assert!(!source.replace("\r\n", "").contains('\n')); + let disconnect = match plan_library_disconnect(&source, target(&source, revision), name) { + DisconnectDecision::Proposed(plan) => plan, + other => panic!("expected literal disconnect proposal: {other:?}"), + }; + let after_import = source.clone(); + let undo_disconnect = disconnect.apply(&mut source, &mut revision).unwrap(); + assert_eq!(revision, 33); + assert_eq!( + inspect(&source).status(ImportKind::TikzLibrary, name), + Evidence::Absent + ); + undo_disconnect.apply(&mut source, &mut revision).unwrap(); + assert_eq!(source, after_import); + // The imported state has changed revision; a fresh revision-correct inverse + // patch is needed. Older undo_import is intentionally stale now. + assert!(undo_import.apply(&mut source, &mut revision).is_err()); + assert_eq!(source, after_import); + } +} + +#[test] +fn unmodified_inverse_import_restores_every_byte_and_rejects_stale_revision() { + let mut source = "\\documentclass{article}\n\\begin{document}\n".to_owned(); + let original = source.clone(); + let mut revision = 8; + let plan = match plan_import( + &source, + target(&source, revision), + ImportKind::TikzLibrary, + "positioning", + ) { + ImportDecision::Proposed(plan) => plan, + other => panic!("proposal expected: {other:?}"), + }; + let inverse = plan.apply(&mut source, &mut revision).unwrap(); + let inserted = source.clone(); + let mut wrong_revision = revision + 1; + assert!(inverse.apply(&mut source, &mut wrong_revision).is_err()); + assert_eq!(source, inserted); + inverse.apply(&mut source, &mut revision).unwrap(); + assert_eq!(source, original); +} + +#[test] +fn already_connected_and_missing_are_distinct_from_unknown() { + let catalog = Catalog::bundled().unwrap(); + let source = "\\documentclass{article}\n\\usepackage{tikz}\n\\usetikzlibrary{calc}\n\\begin{document}\n"; + let state = inspect(source); + assert_eq!( + capabilities_for_library( + &catalog, + "calc", + &state, + Evidence::Absent, + Some("xelatex") + ) + .unwrap() + .project_loaded, + Evidence::Confirmed + ); + assert_eq!(state.status(ImportKind::TikzLibrary, "fit"), Evidence::Absent); + assert_eq!( + plan_import(source, target(source, 0), ImportKind::TikzLibrary, "calc"), + ImportDecision::AlreadyLoaded + ); + assert_eq!( + plan_library_disconnect(source, target(source, 0), "fit"), + DisconnectDecision::AlreadyAbsent + ); +} diff --git a/docs/EXTENSIONS.md b/docs/EXTENSIONS.md index 2c73b500..6dc18cd0 100644 --- a/docs/EXTENSIONS.md +++ b/docs/EXTENSIONS.md @@ -117,3 +117,14 @@ Only the arrow tool needs `\\usetikzlibrary{arrows.meta}`; all six require `\\us The `SourcePatch` contract changes one caller-owned known byte span with an expected previous substring and source revision; it emits its inverse for undo. It neither decompiles a PDF nor rewrites unknown TeX. Raw mathematical TeX in a node is user-authored and intentionally not blocked by the literal coordinate validator. XeLaTeX remains an external prerequisite: MiKTeX on actual Windows 10, user-installed XeLaTeX on physical macOS ARM64. Lua-only graphdrawing is still incompatible with XeLaTeX, and existing installed compatible packages remain source-compilable without a GUI adapter. Review primary PGF/TikZ 3.1.12 manual sections [14 — paths](https://tikz.dev/tikz-paths), [16 — arrows](https://tikz.dev/tikz-arrows) and [17 — nodes](https://tikz.dev/tikz-shapes); linked descriptions here are original summaries, not copied manual pages. + + +## N2 source-backed library catalogue (same schema v1) + +The \`[[libraries]]\` entries now describe \`arrows.meta\`, \`positioning\`, \`calc\` and \`fit\` in the **same versioned TOML file** as the six primitive tools. They carry stable ids, native \`\usetikzlibrary\` syntax, package and engine requirements, Russian effect/help/warnings/interactions, a complete source example and a primary PGF/TikZ manual section. Unverified minimum PGF versions remain \`UNKNOWN\`; library record examples are not claims of GUI implementations. No defaults are invented for library key parameters that have not been adapted. + +**Additive C→B contract**: \`Catalog::bundled()?.libraries\`, \`Catalog::library(name)\`, \`project_imports::capabilities_for_library(&catalog, name, &inspect(source), installed_evidence, Some("xelatex"))\`, \`ProjectImports::declaration_sites(TikzLibrary,name)\`. The five status dimensions remain separate. \`tool_implemented=CONFIRMED\` for \`arrows.meta\` means *only* that the native arrow generator depends on it; no general-purpose library authoring is implied. \`positioning/calc/fit\` currently have usable TeX snippets and import adapters, not visual tools. \`visual_editable=false\` for arbitrary library syntax. The \`installed\` argument is externally obtained evidence, never inferred from \`\usetikzlibrary\`. + +C's existing \`plan_import\` and \`plan_library_disconnect\` build revision-checked minimal source patches; B owns diff preview, explicit confirmation, source focus and inverse Undo in egui. Unsafe/opaque preambles yield \`UNKNOWN\`, a reason and no mutation. A found library file is not project-loaded; an unadapted installed XeLaTeX package may still compile raw source. Lua-only graphdrawing remains incompatible with XeLaTeX. + +**C→D fixture contract**: \`cargo run -p tikzarium-extensions --bin n2-library-fixture -- > example.tex\`, then external XeLaTeX with shell escape disabled and explicit artifacts. The four examples are full \`standalone\` documents. Hosted CI cannot replace physical Windows 10 + separately installed MiKTeX or M1/M4 + user XeLaTeX testing. The published manual sections are [arrows 16.5](https://tikz.dev/tikz-arrows), [positioning 17.5.3](https://tikz.dev/tikz-shapes), [calc 13.5](https://tikz.dev/tikz-coordinates), [fit 54](https://tikz.dev/library-fit). diff --git a/extensions/primitives/catalog-v1.toml b/extensions/primitives/catalog-v1.toml index 0ae93c90..f825de4d 100644 --- a/extensions/primitives/catalog-v1.toml +++ b/extensions/primitives/catalog-v1.toml @@ -613,3 +613,117 @@ manual_section = "14.3" manual_url = "https://tikz.dev/tikz-paths" requirements_inherit = "tool" + + +# N2 shared library panel catalogue — source imports, NOT executed plugins. +# Requirements describe explicit project declarations, not package installation. +[[libraries]] +id = "tikz.library.arrows-meta" +name = "arrows.meta" +title_ru = "Наконечники arrows.meta" +syntax = '\usetikzlibrary{arrows.meta}' +help_ru = "Добавляет настраиваемые виды наконечников стрелок, в том числе Stealth и Latex. Это библиотека PGF/TikZ, а не отдельный редактор." +warning_ru = "Наличие файла в MiKTeX не означает подключение проекта. Установка пакета, подключение и редактируемость — разные статусы." +interaction_ru = "Нативный инструмент «Стрелка» использует Stealth/Latex; другие свойства и стили доступны через исходный TeX." +manual_section = "16.5" +manual_url = "https://tikz.dev/tikz-arrows" +example = ''' +\documentclass{standalone} +\usepackage{tikz} +\usetikzlibrary{arrows.meta} +\begin{document} +\begin{tikzpicture} +\draw[-{Stealth}] (0,0) -- (2,0); +\end{tikzpicture} +\end{document} +''' +[libraries.requirements] +packages = ["tikz"] +libraries = ["arrows.meta"] +engine = "xelatex" +pgf_version_min = "UNKNOWN" +documented_against = "3.1.12" + +[[libraries]] +id = "tikz.library.positioning" +name = "positioning" +title_ru = "Взаимное расположение узлов" +syntax = '\usetikzlibrary{positioning}' +help_ru = "Позволяет задавать положение узла относительно другого, например right=of a; сам TikZ рассчитывает смещение и якоря." +warning_ru = "Библиотека меняет смысл некоторых ключей размещения. Не конвертировать формулы пользователя в приближённые координаты Rust." +interaction_ru = "Используется в ручных узлах TikZ; отдельный инструмент позиционирования пока не реализован." +manual_section = "17.5.3" +manual_url = "https://tikz.dev/tikz-shapes" +example = ''' +\documentclass{standalone} +\usepackage{tikz} +\usetikzlibrary{positioning} +\begin{document} +\begin{tikzpicture} +\node (a) {A}; +\node[right=of a] {B}; +\end{tikzpicture} +\end{document} +''' +[libraries.requirements] +packages = ["tikz"] +libraries = ["positioning"] +engine = "xelatex" +pgf_version_min = "UNKNOWN" +documented_against = "3.1.12" + +[[libraries]] +id = "tikz.library.calc" +name = "calc" +title_ru = "Вычисление координат TikZ" +syntax = '\usetikzlibrary{calc}' +help_ru = "Разрешает выражения над координатами: суммы, доли отрезка и проекции вычисляет TikZ при компиляции." +warning_ru = "Выражения TeX могут содержать пользовательские макросы. Их нельзя заменять вычисленным результатом без согласия автора." +interaction_ru = "Подключение разрешает исходный синтаксис TikZ; вычислитель/solver в редакторе не реализован." +manual_section = "13.5; 46" +manual_url = "https://tikz.dev/tikz-coordinates" +example = ''' +\documentclass{standalone} +\usepackage{tikz} +\usetikzlibrary{calc} +\begin{document} +\begin{tikzpicture} +\draw (0,0) -- ($(0,0)+(2cm,1cm)$); +\end{tikzpicture} +\end{document} +''' +[libraries.requirements] +packages = ["tikz"] +libraries = ["calc"] +engine = "xelatex" +pgf_version_min = "UNKNOWN" +documented_against = "3.1.12" + +[[libraries]] +id = "tikz.library.fit" +name = "fit" +title_ru = "Охватывающий узел fit" +syntax = '\usetikzlibrary{fit}' +help_ru = "Позволяет охватить набор названных узлов или координат отдельным узлом с опцией fit; размеры вычисляет TikZ." +warning_ru = "Габариты зависят от текста, шрифтов и других узлов. Нельзя объявлять геометрию точной до запуска XeLaTeX." +interaction_ru = "Доступно в обычном исходнике; специальный GUI-инструмент охвата пока не реализован." +manual_section = "54" +manual_url = "https://tikz.dev/library-fit" +example = ''' +\documentclass{standalone} +\usepackage{tikz} +\usetikzlibrary{fit} +\begin{document} +\begin{tikzpicture} +\node (a) at (0,0) {A}; +\node (b) at (2,0) {B}; +\node[draw,fit=(a)(b)] {}; +\end{tikzpicture} +\end{document} +''' +[libraries.requirements] +packages = ["tikz"] +libraries = ["fit"] +engine = "xelatex" +pgf_version_min = "UNKNOWN" +documented_against = "3.1.12" diff --git a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md new file mode 100644 index 00000000..c40abb14 --- /dev/null +++ b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md @@ -0,0 +1,15 @@ +# C/N2 library catalogue — QA ledger + +2026-10-31; base main \`b7c8e993b9da64060dfd3261833e4be3a1d280ba\`. Work limited to C-owned Rust, extensions/, docs/EXTENSIONS.md, C plans/insights. + +## Engineering changes + +The library panel can consume four versioned declarative records, including original Russian help, actual native import syntax and complete XeLaTeX examples, without duplicating knowledge in egui. All four library imports remain ordinary source patches. The six primitive generators and all parameter help are untouched. A library may be present in the installation, not loaded in the project, compatible with XeLaTeX, lack a dedicated tool, and still have documented help: five axes are deliberately not collapsed. \`arrows.meta\` has one narrow generator (arrow), other three have no dedicated visual generator; generic \`visual_editable\` stays false. No package is installed or silently connected. + +Primary manuals checked on 2026-10-31: tikz.dev/tikz-arrows §16.5, tikz.dev/tikz-shapes §17.5.3, tikz.dev/tikz-coordinates §13.5, tikz.dev/library-fit §54. No full manual copied; unsupported PGF minimum is UNKNOWN. + +## Validation required (never infer PASS) + +New Rust tests: catalogue cardinality/help, six-tool source identity, five independent library states, opaque import UNKNOWN, all-four CRLF connect/disconnect and Undo, inverse source revision mismatch, already-loaded vs absent. A C-owned binary prints a full original TeX fixture for any of four libraries; D must run real XeLaTeX and archive logs/PDF/hash before a compiler PASS can be claimed. + +Local sandbox lacks cargo/rustc and cannot resolve GitHub; local Rust/TeX execution NOT_RUN. This is a source-level implementation requiring exact SHA CI confirmation. B #50 was notified of the additive contract before code. Physical Win10 external MiKTeX, physical M1/M4 GUI/screenshot and display of library panel: NOT_RUN. diff --git a/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md b/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md new file mode 100644 index 00000000..0f6ba80e --- /dev/null +++ b/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md @@ -0,0 +1,14 @@ +# C/N2 — One schema for four source-backed PGF/TikZ libraries + +2026-10-31. Scope strictly stream C in i-shutov/tikzarium. Base main \`b7c8e993b9da64060dfd3261833e4be3a1d280ba\` (#44/#49/#51/#55 already merged), no replay of #39. + +## End-to-end delivery +1. Existing six generators and 28 Russian parameter help records stay unchanged. Extend the existing versioned \`catalog-v1.toml\` with exactly four declarative library descriptions: arrows.meta, positioning, calc, fit, with native TikZ syntax, requirements, accurate primary-manual pointers, effects, examples and warnings. Missing real PGF minimum version remains UNKNOWN. Library metadata cannot execute code. +2. Add a small additive C API for B's future library panel: \`Catalog::library\`, \`Catalog::libraries\`, \`capabilities_for_library\` and existing read-only declaration sites. Keep installation external/caller-provided and independent from project-loaded, XeLaTeX compatibility, tool existence and help. No visual-edit PASS from a catalogue entry. +3. Share the four stable names with existing literal connect/disconnect planners, preserving source byte ranges/revisions, CRLF, comments, user macros and reversible SourcePatch. On UNKNOWN, refuse with reason. No full CST, TeX/PGF solver, PDF inversion or package installation. +4. Provide generated full TeX examples for D via \`n2-library-fixture\` and independent Rust tests for status separation, library metadata, four connect/disconnect paths, roundtrip inverse, stale revision and N1 source fixture identity. +5. Coordination: contract comment on B #50 before editing shared types. No B app/A build/D/E/F files changed. Publish code+tests+docs+plans/insights as ONE C PR. +6. Evidence gate: exact HEAD cargo fmt/check/test/clippy + hosted XeLaTeX integration to be verified from workflow logs. Physical Windows 10/MiKTeX and macOS M1/M4 GUI NOT_RUN; no release claim. + +## Rollout +B can read \`Catalog::libraries\` with non-fabricated statuses, show the actual package/library source link, then call C planners after explicit user choice. D can execute example fixture generator and negative cases. Visual library-specific tooling remains an N2 follow-up outside this C metadata slice. From c3ee62adbe9887509638897ab40bbc57ca1f7848 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 17:36:57 +0300 Subject: [PATCH 02/16] docs(C/N2): render Markdown links and inline code correctly --- docs/EXTENSIONS.md | 12 ++++++------ insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md | 4 ++-- plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/EXTENSIONS.md b/docs/EXTENSIONS.md index 6dc18cd0..0987a4fb 100644 --- a/docs/EXTENSIONS.md +++ b/docs/EXTENSIONS.md @@ -99,9 +99,9 @@ The C02 registry now prioritizes a definite missing file over another indetermin ## 10. C03 artifact-integrity audit (still not package certification) -The C03 tool [extensions/qa/verify_smoke.py](../extensions/qa/verify_smoke.py) independently checks the *bytes* behind a C02 observation: the fixed fixture SHA-256 in the current source tree, captured console and TeX logs, positive PDF hash/signature/EOF plus \`pdfinfo\` page count, and the specific expected LuaTeX-only diagnostic for the negative graphdrawing probe. It reconciles the five observations with summary.json and rejects incomplete, contradictory, mixed-host and symlinked evidence by default. See the [QA runbook](../extensions/qa/README.md), [C03 plan](../plans/2026-10-31_C03_ARTIFACT_AUDIT.md), and [C03 insights](../insights/2026-10-31_C03_ARTIFACT_AUDIT_QA.md). +The C03 tool [extensions/qa/verify_smoke.py](../extensions/qa/verify_smoke.py) independently checks the *bytes* behind a C02 observation: the fixed fixture SHA-256 in the current source tree, captured console and TeX logs, positive PDF hash/signature/EOF plus `pdfinfo` page count, and the specific expected LuaTeX-only diagnostic for the negative graphdrawing probe. It reconciles the five observations with summary.json and rejects incomplete, contradictory, mixed-host and symlinked evidence by default. See the [QA runbook](../extensions/qa/README.md), [C03 plan](../plans/2026-10-31_C03_ARTIFACT_AUDIT.md), and [C03 insights](../insights/2026-10-31_C03_ARTIFACT_AUDIT_QA.md). -\`--allow-partial\` is explicitly *selected-only* auditing, never an accepted full five-fixture suite. A recorded \`runner_kind=hosted_ci\` is self-reported metadata, not an attested GitHub runner. Matching hashes also cannot prove the compiler really produced those bytes or identify the exact operating system. The auditor does not promote registry capabilities, authenticate machines, certify package versions, or prove visual edits. The PACKAGE_MATRIX.csv remains conservative until per-platform evidence has been reviewed, not merely parsed. +`--allow-partial` is explicitly *selected-only* auditing, never an accepted full five-fixture suite. A recorded `runner_kind=hosted_ci` is self-reported metadata, not an attested GitHub runner. Matching hashes also cannot prove the compiler really produced those bytes or identify the exact operating system. The auditor does not promote registry capabilities, authenticate machines, certify package versions, or prove visual edits. The PACKAGE_MATRIX.csv remains conservative until per-platform evidence has been reviewed, not merely parsed. ## I05 real package evidence integration @@ -121,10 +121,10 @@ Review primary PGF/TikZ 3.1.12 manual sections [14 — paths](https://tikz.dev/t ## N2 source-backed library catalogue (same schema v1) -The \`[[libraries]]\` entries now describe \`arrows.meta\`, \`positioning\`, \`calc\` and \`fit\` in the **same versioned TOML file** as the six primitive tools. They carry stable ids, native \`\usetikzlibrary\` syntax, package and engine requirements, Russian effect/help/warnings/interactions, a complete source example and a primary PGF/TikZ manual section. Unverified minimum PGF versions remain \`UNKNOWN\`; library record examples are not claims of GUI implementations. No defaults are invented for library key parameters that have not been adapted. +The `[[libraries]]` entries now describe `arrows.meta`, `positioning`, `calc` and `fit` in the **same versioned TOML file** as the six primitive tools. They carry stable ids, native `\usetikzlibrary` syntax, package and engine requirements, Russian effect/help/warnings/interactions, a complete source example and a primary PGF/TikZ manual section. Unverified minimum PGF versions remain `UNKNOWN`; library record examples are not claims of GUI implementations. No defaults are invented for library key parameters that have not been adapted. -**Additive C→B contract**: \`Catalog::bundled()?.libraries\`, \`Catalog::library(name)\`, \`project_imports::capabilities_for_library(&catalog, name, &inspect(source), installed_evidence, Some("xelatex"))\`, \`ProjectImports::declaration_sites(TikzLibrary,name)\`. The five status dimensions remain separate. \`tool_implemented=CONFIRMED\` for \`arrows.meta\` means *only* that the native arrow generator depends on it; no general-purpose library authoring is implied. \`positioning/calc/fit\` currently have usable TeX snippets and import adapters, not visual tools. \`visual_editable=false\` for arbitrary library syntax. The \`installed\` argument is externally obtained evidence, never inferred from \`\usetikzlibrary\`. +**Additive C→B contract**: `Catalog::bundled()?.libraries`, `Catalog::library(name)`, `project_imports::capabilities_for_library(&catalog, name, &inspect(source), installed_evidence, Some("xelatex"))`, `ProjectImports::declaration_sites(TikzLibrary,name)`. The five status dimensions remain separate. `tool_implemented=CONFIRMED` for `arrows.meta` means *only* that the native arrow generator depends on it; no general-purpose library authoring is implied. `positioning/calc/fit` currently have usable TeX snippets and import adapters, not visual tools. `visual_editable=false` for arbitrary library syntax. The `installed` argument is externally obtained evidence, never inferred from `\usetikzlibrary`. -C's existing \`plan_import\` and \`plan_library_disconnect\` build revision-checked minimal source patches; B owns diff preview, explicit confirmation, source focus and inverse Undo in egui. Unsafe/opaque preambles yield \`UNKNOWN\`, a reason and no mutation. A found library file is not project-loaded; an unadapted installed XeLaTeX package may still compile raw source. Lua-only graphdrawing remains incompatible with XeLaTeX. +C's existing `plan_import` and `plan_library_disconnect` build revision-checked minimal source patches; B owns diff preview, explicit confirmation, source focus and inverse Undo in egui. Unsafe/opaque preambles yield `UNKNOWN`, a reason and no mutation. A found library file is not project-loaded; an unadapted installed XeLaTeX package may still compile raw source. Lua-only graphdrawing remains incompatible with XeLaTeX. -**C→D fixture contract**: \`cargo run -p tikzarium-extensions --bin n2-library-fixture -- > example.tex\`, then external XeLaTeX with shell escape disabled and explicit artifacts. The four examples are full \`standalone\` documents. Hosted CI cannot replace physical Windows 10 + separately installed MiKTeX or M1/M4 + user XeLaTeX testing. The published manual sections are [arrows 16.5](https://tikz.dev/tikz-arrows), [positioning 17.5.3](https://tikz.dev/tikz-shapes), [calc 13.5](https://tikz.dev/tikz-coordinates), [fit 54](https://tikz.dev/library-fit). +**C→D fixture contract**: `cargo run -p tikzarium-extensions --bin n2-library-fixture -- > example.tex`, then external XeLaTeX with shell escape disabled and explicit artifacts. The four examples are full `standalone` documents. Hosted CI cannot replace physical Windows 10 + separately installed MiKTeX or M1/M4 + user XeLaTeX testing. The published manual sections are [arrows 16.5](https://tikz.dev/tikz-arrows), [positioning 17.5.3](https://tikz.dev/tikz-shapes), [calc 13.5](https://tikz.dev/tikz-coordinates), [fit 54](https://tikz.dev/library-fit). diff --git a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md index c40abb14..c119ed68 100644 --- a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md +++ b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md @@ -1,10 +1,10 @@ # C/N2 library catalogue — QA ledger -2026-10-31; base main \`b7c8e993b9da64060dfd3261833e4be3a1d280ba\`. Work limited to C-owned Rust, extensions/, docs/EXTENSIONS.md, C plans/insights. +2026-10-31; base main `b7c8e993b9da64060dfd3261833e4be3a1d280ba`. Work limited to C-owned Rust, extensions/, docs/EXTENSIONS.md, C plans/insights. ## Engineering changes -The library panel can consume four versioned declarative records, including original Russian help, actual native import syntax and complete XeLaTeX examples, without duplicating knowledge in egui. All four library imports remain ordinary source patches. The six primitive generators and all parameter help are untouched. A library may be present in the installation, not loaded in the project, compatible with XeLaTeX, lack a dedicated tool, and still have documented help: five axes are deliberately not collapsed. \`arrows.meta\` has one narrow generator (arrow), other three have no dedicated visual generator; generic \`visual_editable\` stays false. No package is installed or silently connected. +The library panel can consume four versioned declarative records, including original Russian help, actual native import syntax and complete XeLaTeX examples, without duplicating knowledge in egui. All four library imports remain ordinary source patches. The six primitive generators and all parameter help are untouched. A library may be present in the installation, not loaded in the project, compatible with XeLaTeX, lack a dedicated tool, and still have documented help: five axes are deliberately not collapsed. `arrows.meta` has one narrow generator (arrow), other three have no dedicated visual generator; generic `visual_editable` stays false. No package is installed or silently connected. Primary manuals checked on 2026-10-31: tikz.dev/tikz-arrows §16.5, tikz.dev/tikz-shapes §17.5.3, tikz.dev/tikz-coordinates §13.5, tikz.dev/library-fit §54. No full manual copied; unsupported PGF minimum is UNKNOWN. diff --git a/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md b/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md index 0f6ba80e..f324ae2f 100644 --- a/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md +++ b/plans/2026-10-31_C_N2_LIBRARY_CATALOGUE.md @@ -1,14 +1,14 @@ # C/N2 — One schema for four source-backed PGF/TikZ libraries -2026-10-31. Scope strictly stream C in i-shutov/tikzarium. Base main \`b7c8e993b9da64060dfd3261833e4be3a1d280ba\` (#44/#49/#51/#55 already merged), no replay of #39. +2026-10-31. Scope strictly stream C in i-shutov/tikzarium. Base main `b7c8e993b9da64060dfd3261833e4be3a1d280ba` (#44/#49/#51/#55 already merged), no replay of #39. ## End-to-end delivery -1. Existing six generators and 28 Russian parameter help records stay unchanged. Extend the existing versioned \`catalog-v1.toml\` with exactly four declarative library descriptions: arrows.meta, positioning, calc, fit, with native TikZ syntax, requirements, accurate primary-manual pointers, effects, examples and warnings. Missing real PGF minimum version remains UNKNOWN. Library metadata cannot execute code. -2. Add a small additive C API for B's future library panel: \`Catalog::library\`, \`Catalog::libraries\`, \`capabilities_for_library\` and existing read-only declaration sites. Keep installation external/caller-provided and independent from project-loaded, XeLaTeX compatibility, tool existence and help. No visual-edit PASS from a catalogue entry. +1. Existing six generators and 28 Russian parameter help records stay unchanged. Extend the existing versioned `catalog-v1.toml` with exactly four declarative library descriptions: arrows.meta, positioning, calc, fit, with native TikZ syntax, requirements, accurate primary-manual pointers, effects, examples and warnings. Missing real PGF minimum version remains UNKNOWN. Library metadata cannot execute code. +2. Add a small additive C API for B's future library panel: `Catalog::library`, `Catalog::libraries`, `capabilities_for_library` and existing read-only declaration sites. Keep installation external/caller-provided and independent from project-loaded, XeLaTeX compatibility, tool existence and help. No visual-edit PASS from a catalogue entry. 3. Share the four stable names with existing literal connect/disconnect planners, preserving source byte ranges/revisions, CRLF, comments, user macros and reversible SourcePatch. On UNKNOWN, refuse with reason. No full CST, TeX/PGF solver, PDF inversion or package installation. -4. Provide generated full TeX examples for D via \`n2-library-fixture\` and independent Rust tests for status separation, library metadata, four connect/disconnect paths, roundtrip inverse, stale revision and N1 source fixture identity. +4. Provide generated full TeX examples for D via `n2-library-fixture` and independent Rust tests for status separation, library metadata, four connect/disconnect paths, roundtrip inverse, stale revision and N1 source fixture identity. 5. Coordination: contract comment on B #50 before editing shared types. No B app/A build/D/E/F files changed. Publish code+tests+docs+plans/insights as ONE C PR. 6. Evidence gate: exact HEAD cargo fmt/check/test/clippy + hosted XeLaTeX integration to be verified from workflow logs. Physical Windows 10/MiKTeX and macOS M1/M4 GUI NOT_RUN; no release claim. ## Rollout -B can read \`Catalog::libraries\` with non-fabricated statuses, show the actual package/library source link, then call C planners after explicit user choice. D can execute example fixture generator and negative cases. Visual library-specific tooling remains an N2 follow-up outside this C metadata slice. +B can read `Catalog::libraries` with non-fabricated statuses, show the actual package/library source link, then call C planners after explicit user choice. D can execute example fixture generator and negative cases. Visual library-specific tooling remains an N2 follow-up outside this C metadata slice. From 23003dabebf99eabe39457b10b326e45d281fa31 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 17:39:47 +0300 Subject: [PATCH 03/16] fix(C/N2): apply hosted rustfmt diff and record real XeLaTeX example QA --- crates/tikzarium-extensions/src/primitives.rs | 7 +- .../src/project_imports.rs | 16 +-- .../tests/library_catalog.rs | 98 +++++++++++-------- .../2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md | 15 +++ 4 files changed, 82 insertions(+), 54 deletions(-) diff --git a/crates/tikzarium-extensions/src/primitives.rs b/crates/tikzarium-extensions/src/primitives.rs index 9f6149f9..7b737f72 100644 --- a/crates/tikzarium-extensions/src/primitives.rs +++ b/crates/tikzarium-extensions/src/primitives.rs @@ -355,9 +355,10 @@ impl Catalog { } } if self.tools.iter().any(|tool| { - tool.requirements.libraries.iter().any(|name| { - !self.libraries.iter().any(|library| library.name == *name) - }) + tool.requirements + .libraries + .iter() + .any(|name| !self.libraries.iter().any(|library| library.name == *name)) }) { return Err(invalid("tool requirement lacks shared library help")); } diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index 0af5e447..10fc37c2 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -462,14 +462,14 @@ pub fn capabilities_for_library( .ok_or_else(|| PrimitiveError::UnknownLibrary(name.to_owned()))?; let package = project.status(ImportKind::Package, "tikz"); let loaded_library = project.status(ImportKind::TikzLibrary, name); - let project_loaded = - if package == Evidence::Confirmed && loaded_library == Evidence::Confirmed { - Evidence::Confirmed - } else if package == Evidence::Unknown || loaded_library == Evidence::Unknown { - Evidence::Unknown - } else { - Evidence::Absent - }; + let project_loaded = if package == Evidence::Confirmed && loaded_library == Evidence::Confirmed + { + Evidence::Confirmed + } else if package == Evidence::Unknown || loaded_library == Evidence::Unknown { + Evidence::Unknown + } else { + Evidence::Absent + }; let tool_implemented = if catalog.tools.iter().any(|tool| { tool.requirements .libraries diff --git a/crates/tikzarium-extensions/tests/library_catalog.rs b/crates/tikzarium-extensions/tests/library_catalog.rs index e1a5eac1..f3c12c1b 100644 --- a/crates/tikzarium-extensions/tests/library_catalog.rs +++ b/crates/tikzarium-extensions/tests/library_catalog.rs @@ -1,13 +1,11 @@ //! C/N2 single-schema library panel contract; no B UI or package installation. +use std::collections::BTreeMap; use tikzarium_extensions::{ primitive_edit::SourceTarget, primitives::{Catalog, Evidence, N2_LIBRARY_NAMES}, project_disconnect::{DisconnectDecision, plan_library_disconnect}, - project_imports::{ - ImportDecision, ImportKind, capabilities_for_library, inspect, plan_import, - }, + project_imports::{ImportDecision, ImportKind, capabilities_for_library, inspect, plan_import}, }; -use std::collections::BTreeMap; fn target(source: &str, revision: u64) -> SourceTarget { inspect(source) @@ -43,10 +41,7 @@ fn four_libraries_are_in_the_same_schema_as_six_native_tools() { ); for tool in &catalog.tools { assert_eq!( - catalog - .generate(&tool.id, &BTreeMap::new()) - .unwrap() - .source, + catalog.generate(&tool.id, &BTreeMap::new()).unwrap().source, tool.example ); } @@ -58,14 +53,9 @@ fn installation_loaded_engine_tool_and_help_remain_five_independent_states() { let source = "\\documentclass{article}\n\\usepackage{tikz}\n\\usetikzlibrary{arrows.meta,calc}\n\\begin{document}\n"; let project = inspect(source); for name in N2_LIBRARY_NAMES { - let states = capabilities_for_library( - &catalog, - name, - &project, - Evidence::Unknown, - Some("xelatex"), - ) - .unwrap(); + let states = + capabilities_for_library(&catalog, name, &project, Evidence::Unknown, Some("xelatex")) + .unwrap(); assert_eq!(states.installed, Evidence::Unknown); assert_eq!(states.engine_compatible, Evidence::Confirmed); assert_eq!(states.help_available, Evidence::Confirmed); @@ -87,20 +77,27 @@ fn installation_loaded_engine_tool_and_help_remain_five_independent_states() { } ); } - let different_engine = - capabilities_for_library(&catalog, "fit", &project, Evidence::Confirmed, Some("lualatex")) - .unwrap(); - assert_eq!(different_engine.installed, Evidence::Confirmed); - assert_eq!(different_engine.engine_compatible, Evidence::Absent); - assert_eq!(different_engine.project_loaded, Evidence::Absent); - assert!(capabilities_for_library( + let different_engine = capabilities_for_library( &catalog, - "graphdrawing", + "fit", &project, Evidence::Confirmed, - Some("xelatex") + Some("lualatex"), ) - .is_err()); + .unwrap(); + assert_eq!(different_engine.installed, Evidence::Confirmed); + assert_eq!(different_engine.engine_compatible, Evidence::Absent); + assert_eq!(different_engine.project_loaded, Evidence::Absent); + assert!( + capabilities_for_library( + &catalog, + "graphdrawing", + &project, + Evidence::Confirmed, + Some("xelatex") + ) + .is_err() + ); } #[test] @@ -108,14 +105,28 @@ fn opaque_conditional_import_is_unknown_not_missing_or_installed() { let catalog = Catalog::bundled().unwrap(); let source = "\\documentclass{article}\n\\iftrue\n\\usepackage{tikz}\n\\usetikzlibrary{fit}\n\\fi\n\\begin{document}\n"; let project = inspect(source); - let capabilities = - capabilities_for_library(&catalog, "fit", &project, Evidence::Unknown, Some("xelatex")) - .unwrap(); + let capabilities = capabilities_for_library( + &catalog, + "fit", + &project, + Evidence::Unknown, + Some("xelatex"), + ) + .unwrap(); assert_eq!(capabilities.project_loaded, Evidence::Unknown); assert_eq!(capabilities.installed, Evidence::Unknown); - assert!(project.declaration_sites(ImportKind::TikzLibrary, "fit").is_empty()); + assert!( + project + .declaration_sites(ImportKind::TikzLibrary, "fit") + .is_empty() + ); assert!(matches!( - plan_import(source, SourceTarget::new(0..0, 8), ImportKind::TikzLibrary, "fit"), + plan_import( + source, + SourceTarget::new(0..0, 8), + ImportKind::TikzLibrary, + "fit" + ), ImportDecision::Refused { .. } )); assert!(matches!( @@ -142,7 +153,10 @@ fn four_library_connect_disconnect_proposals_are_reversible_with_crlf_and_unicod }; assert_eq!( plan.declarations, - ["\\usepackage{tikz}".to_owned(), format!("\\usetikzlibrary{{{name}}}")] + [ + "\\usepackage{tikz}".to_owned(), + format!("\\usetikzlibrary{{{name}}}") + ] ); assert_eq!(source, original); let undo_import = plan.apply(&mut source, &mut revision).unwrap(); @@ -208,21 +222,19 @@ fn unmodified_inverse_import_restores_every_byte_and_rejects_stale_revision() { #[test] fn already_connected_and_missing_are_distinct_from_unknown() { let catalog = Catalog::bundled().unwrap(); - let source = "\\documentclass{article}\n\\usepackage{tikz}\n\\usetikzlibrary{calc}\n\\begin{document}\n"; + let source = + "\\documentclass{article}\n\\usepackage{tikz}\n\\usetikzlibrary{calc}\n\\begin{document}\n"; let state = inspect(source); assert_eq!( - capabilities_for_library( - &catalog, - "calc", - &state, - Evidence::Absent, - Some("xelatex") - ) - .unwrap() - .project_loaded, + capabilities_for_library(&catalog, "calc", &state, Evidence::Absent, Some("xelatex")) + .unwrap() + .project_loaded, Evidence::Confirmed ); - assert_eq!(state.status(ImportKind::TikzLibrary, "fit"), Evidence::Absent); + assert_eq!( + state.status(ImportKind::TikzLibrary, "fit"), + Evidence::Absent + ); assert_eq!( plan_import(source, target(source, 0), ImportKind::TikzLibrary, "calc"), ImportDecision::AlreadyLoaded diff --git a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md index c119ed68..61f445ed 100644 --- a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md +++ b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md @@ -13,3 +13,18 @@ Primary manuals checked on 2026-10-31: tikz.dev/tikz-arrows §16.5, tikz.dev/tik New Rust tests: catalogue cardinality/help, six-tool source identity, five independent library states, opaque import UNKNOWN, all-four CRLF connect/disconnect and Undo, inverse source revision mismatch, already-loaded vs absent. A C-owned binary prints a full original TeX fixture for any of four libraries; D must run real XeLaTeX and archive logs/PDF/hash before a compiler PASS can be claimed. Local sandbox lacks cargo/rustc and cannot resolve GitHub; local Rust/TeX execution NOT_RUN. This is a source-level implementation requiring exact SHA CI confirmation. B #50 was notified of the additive contract before code. Physical Win10 external MiKTeX, physical M1/M4 GUI/screenshot and display of library panel: NOT_RUN. + + +## Exact first-head hosted CI and correction + +Initial PR HEAD `ec670761eaa3b7f2360eb1299762e11111e9bed7` and formatting-only-doc follow-up `c3ee62adbe9887509638897ab40bbc57ca1f7848` are NOT green. Hosted macOS/Windows `TikZarium CI` and Ubuntu/Windows/macOS D QA Rust jobs stopped at `cargo fmt --check` before compiling/testing the new Rust code. First-head Rust CI: https://github.com/i-shutov/tikzarium/actions/runs/41210798104; corrected-doc Rust CI: https://github.com/i-shutov/tikzarium/actions/runs/41210967266. Full hosted rustfmt diff retrieved from job 124565667127 and applied to the three C-owned Rust files without suppressing CI checks. Do not inherit any older success; only a later exact-head execution can establish Rust PASS. + +## Direct local example compilation (not Rust generator proof) + +On Debian TeX Live 2025/dev XeTeX 0.999996, four *literal full-document catalogue examples* were independently transcribed and compiled with `xelatex -no-shell-escape -halt-on-error -interaction=nonstopmode`. All four exited 0 and `pdfinfo` reported exactly one PDF page. Source SHA-256: +- arrows.meta: `38d89c1db82cdffa0a2537791fa0edd85907ccfe262cc3edeb3652ec14c1e163` +- positioning: `258496dcdafac6969646a31c563f1d8a6a32741a0b4aaf73a729984035bffba2` +- calc: `9a561e03192b24db20e5fb180c74cf9c43428bb21af79251ce3a9e2d852816fa` +- fit: `667d6e765d359890e30a870c1db758d79a722025fd415a7e98e6538e297f832e` + +These verify the individual TikZ examples on ONE local Linux XeLaTeX installation. They do not prove the binary `n2-library-fixture` ran or validate Rust, hosted Windows Server/MiKTeX or physical Win10/M1/M4. The `fit` and `positioning` PDF pages were also rasterized and inspected; no content clipping observed. Full physical GUI validation remains NOT_RUN. From 021ed1abdaa052b52770d8307249cd919d2ccc0c Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 17:43:40 +0300 Subject: [PATCH 04/16] test(C/N2): compare Windows CRLF fixture as source-equivalent LF and document QA --- crates/tikzarium-extensions/tests/library_catalog.rs | 3 ++- extensions/primitives/README.md | 4 ++++ insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/tikzarium-extensions/tests/library_catalog.rs b/crates/tikzarium-extensions/tests/library_catalog.rs index f3c12c1b..554e82e1 100644 --- a/crates/tikzarium-extensions/tests/library_catalog.rs +++ b/crates/tikzarium-extensions/tests/library_catalog.rs @@ -35,9 +35,10 @@ fn four_libraries_are_in_the_same_schema_as_six_native_tools() { assert!(record.example.contains("\\end{tikzpicture}")); } // Shared library additions never rewrite the original N1 source output. + let original_fixture = include_str!("../../../extensions/primitives/fixtures/all-six.tex"); assert_eq!( catalog.fixture_document().unwrap(), - include_str!("../../../extensions/primitives/fixtures/all-six.tex") + original_fixture.replace("\r\n", "\n") ); for tool in &catalog.tools { assert_eq!( diff --git a/extensions/primitives/README.md b/extensions/primitives/README.md index 81210358..dc83d3b6 100644 --- a/extensions/primitives/README.md +++ b/extensions/primitives/README.md @@ -8,3 +8,7 @@ The bundled TOML file is the **single** schema for six source-first commands: `t **Contract for D:** `cargo run -p tikzarium-extensions --bin primitive-fixtures > generated.tex` emits the same file as `fixtures/all-six.tex`; the in-crate snapshot test detects drift. Run with `xelatex -no-shell-escape -halt-on-error -interaction=nonstopmode -output-directory generated.tex` and inspect one-page PDF. `fixtures/negative-cases.toml` contains literals which must be rejected *before* compiling. Rust tests also check help completeness, text-vs-math handling, stale ranges and undo. The math node intentionally inserts user-written raw math TeX, not an execution sandbox. Unknown source macros survive untouched. Generic user-installed XeLaTeX-compatible packages remain available for compilation without a visual adapter. Lua-only graphdrawing remains incompatible with XeLaTeX. Manual target: [PGF/TikZ 3.1.12](https://tikz.dev/) sections 14.2/14.3/14.4/14.6, 16.2/16.5, 17.2/17.4; minimum PGF version is UNKNOWN because not independently verified. + +## N2 library metadata in the same primitive schema + +`Catalog::bundled()?.libraries` exposes four data-only entries (`arrows.meta`, `positioning`, `calc`, `fit`). Each has a stable ID, native `\\usetikzlibrary` syntax, requirements, RU help, warning, manual anchor and a complete XeLaTeX example. Use `Catalog::library(name)` and `project_imports::capabilities_for_library` to obtain separate INSTALLED (externally supplied), PROJECT_LOADED (source evidence), ENGINE_COMPATIBLE, TOOL_IMPLEMENTED (only a native arrow generator for `arrows.meta`) and HELP_AVAILABLE. For the other three libraries there is **no dedicated visual tool yet**. Safe import/disconnect proposals stay revision checked; UI diff and Undo belong to B. Run `cargo run -p tikzarium-extensions --bin n2-library-fixture -- positioning` for a full TeX example; D owns independent compile evidence. All original six generators and their 28 parameter help records are unchanged. diff --git a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md index 61f445ed..90016b97 100644 --- a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md +++ b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md @@ -28,3 +28,7 @@ On Debian TeX Live 2025/dev XeTeX 0.999996, four *literal full-document catalogu - fit: `667d6e765d359890e30a870c1db758d79a722025fd415a7e98e6538e297f832e` These verify the individual TikZ examples on ONE local Linux XeLaTeX installation. They do not prove the binary `n2-library-fixture` ran or validate Rust, hosted Windows Server/MiKTeX or physical Win10/M1/M4. The `fit` and `positioning` PDF pages were also rasterized and inspected; no content clipping observed. Full physical GUI validation remains NOT_RUN. + +## Hosted Windows regression and precise fix + +Exact SHA `23003dabebf99eabe39457b10b326e45d281fa31`: [native Rust Windows Server job 124566322698](https://github.com/i-shutov/tikzarium/actions/runs/41211177456) **FAIL**, 5/6 new library tests passed; only `four_libraries_are_in_the_same_schema_as_six_native_tools` failed because Git checked out `all-six.tex` with CRLF while the generator deliberately emits LF. The complete assertion diff showed identical TikZ commands and a pure line-ending difference. Ubuntu D QA at the same head [41211177404](https://github.com/i-shutov/tikzarium/actions/runs/41211177404) passed rustfmt/workspace tests/Clippy including 6/6 new library tests; macOS native Rust had passed tests at the check. Fix **only the test** to compare an LF-normalized fixture; never change the generator to host-dependent CRLF or alter any user source. This is a content-equivalence assertion only; separate CRLF/Undo byte-identity checks remain exact. Subsequent exact-head CI required before PASS. From fd8e624822cf84f93e7768e93107fad4cc06b199 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 17:45:23 +0300 Subject: [PATCH 05/16] fix(C/N2): unknown alternative-engine compatibility is not incompatibility --- crates/tikzarium-extensions/src/project_imports.rs | 5 +++-- crates/tikzarium-extensions/tests/library_catalog.rs | 2 +- docs/EXTENSIONS.md | 2 ++ insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md | 4 ++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index 10fc37c2..844b3e51 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -484,8 +484,9 @@ pub fn capabilities_for_library( Some(actual) if actual.eq_ignore_ascii_case(&library.requirements.engine) => { Evidence::Confirmed } - Some(_) => Evidence::Absent, - None => Evidence::Unknown, + // The catalogue records an XeLaTeX proof target, not a claim that + // other engines reject this PGF/TikZ library. + _ => Evidence::Unknown, }; Ok(Capabilities { installed, diff --git a/crates/tikzarium-extensions/tests/library_catalog.rs b/crates/tikzarium-extensions/tests/library_catalog.rs index 554e82e1..2d62818e 100644 --- a/crates/tikzarium-extensions/tests/library_catalog.rs +++ b/crates/tikzarium-extensions/tests/library_catalog.rs @@ -87,7 +87,7 @@ fn installation_loaded_engine_tool_and_help_remain_five_independent_states() { ) .unwrap(); assert_eq!(different_engine.installed, Evidence::Confirmed); - assert_eq!(different_engine.engine_compatible, Evidence::Absent); + assert_eq!(different_engine.engine_compatible, Evidence::Unknown); assert_eq!(different_engine.project_loaded, Evidence::Absent); assert!( capabilities_for_library( diff --git a/docs/EXTENSIONS.md b/docs/EXTENSIONS.md index 0987a4fb..50f428cd 100644 --- a/docs/EXTENSIONS.md +++ b/docs/EXTENSIONS.md @@ -128,3 +128,5 @@ The `[[libraries]]` entries now describe `arrows.meta`, `positioning`, `calc` an C's existing `plan_import` and `plan_library_disconnect` build revision-checked minimal source patches; B owns diff preview, explicit confirmation, source focus and inverse Undo in egui. Unsafe/opaque preambles yield `UNKNOWN`, a reason and no mutation. A found library file is not project-loaded; an unadapted installed XeLaTeX package may still compile raw source. Lua-only graphdrawing remains incompatible with XeLaTeX. **C→D fixture contract**: `cargo run -p tikzarium-extensions --bin n2-library-fixture -- > example.tex`, then external XeLaTeX with shell escape disabled and explicit artifacts. The four examples are full `standalone` documents. Hosted CI cannot replace physical Windows 10 + separately installed MiKTeX or M1/M4 + user XeLaTeX testing. The published manual sections are [arrows 16.5](https://tikz.dev/tikz-arrows), [positioning 17.5.3](https://tikz.dev/tikz-shapes), [calc 13.5](https://tikz.dev/tikz-coordinates), [fit 54](https://tikz.dev/library-fit). + +**Compatibility evidence nuance:** the four N2 library records certify only their declared XeLaTeX target; an untested alternative engine is `UNKNOWN`, not asserted incompatible. Lua-only graphdrawing remains explicitly incompatible with XeLaTeX by its separate manifest. Do not silently change compilers. diff --git a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md index 90016b97..11f34c61 100644 --- a/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md +++ b/insights/2026-10-31_C_N2_LIBRARY_CATALOGUE_QA.md @@ -32,3 +32,7 @@ These verify the individual TikZ examples on ONE local Linux XeLaTeX installatio ## Hosted Windows regression and precise fix Exact SHA `23003dabebf99eabe39457b10b326e45d281fa31`: [native Rust Windows Server job 124566322698](https://github.com/i-shutov/tikzarium/actions/runs/41211177456) **FAIL**, 5/6 new library tests passed; only `four_libraries_are_in_the_same_schema_as_six_native_tools` failed because Git checked out `all-six.tex` with CRLF while the generator deliberately emits LF. The complete assertion diff showed identical TikZ commands and a pure line-ending difference. Ubuntu D QA at the same head [41211177404](https://github.com/i-shutov/tikzarium/actions/runs/41211177404) passed rustfmt/workspace tests/Clippy including 6/6 new library tests; macOS native Rust had passed tests at the check. Fix **only the test** to compare an LF-normalized fixture; never change the generator to host-dependent CRLF or alter any user source. This is a content-equivalence assertion only; separate CRLF/Undo byte-identity checks remain exact. Subsequent exact-head CI required before PASS. + +## Engine compatibility evidence precision + +For the four declared PGF/TikZ libraries, `engine=xelatex` records the **primary engine verified by fixture**, not evidence that alternate TeX engines reject that library. `capabilities_for_library(..., Some("lualatex"))` now conservatively returns `ENGINE_COMPATIBLE=UNKNOWN` rather than false `Absent`; an actual Lua-only library such as graphdrawing remains explicitly incompatible with native XeLaTeX via its separate registry metadata. No engine switching or feature promotion is performed. From 3b13ac0263b3a4f9f45d2e60e7887771db3ecfd2 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:00:02 +0300 Subject: [PATCH 06/16] fix(C/N2): do not claim libraries absent when document body can import them --- .../src/project_imports.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index 8f7a3f25..34e40da0 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -239,6 +239,44 @@ fn literal_flat_font_assignment(line: &str, command: &str) -> bool { .all(|b| b.is_ascii_alphanumeric() || b" ._+-".contains(&b)) } +/// A *hint* that source after \begin{document} might import packages or +/// libraries. This never grants Confirmed evidence: verbatim and macro scopes +/// cannot be resolved by a lexical scanner. False positives fail closed. +fn contains_control_word(line: &str, command: &str) -> bool { + line.match_indices(command).any(|(start, _)| { + // A preceding escaped backslash is a TeX \\ control symbol, not + // the start of this control word. Handle longer backslash runs too. + let previous_slashes = line[..start] + .bytes() + .rev() + .take_while(|byte| *byte == b'\\') + .count(); + previous_slashes.is_multiple_of(2) + && !line[start + command.len()..] + .chars() + .next() + .is_some_and(|ch| ch.is_alphabetic() || ch == '@') + }) +} + +fn document_body_may_import(body: &str) -> bool { + const IMPORT_LIKE_COMMANDS: [&str; 6] = [ + "\\usetikzlibrary", + "\\usepackage", + "\\RequirePackage", + "\\input", + "\\include", + "\\csname", + ]; + body.split_inclusive('\n').any(|raw| { + let line = raw.trim_end_matches(['\r', '\n']); + let active = uncommented(line); + IMPORT_LIKE_COMMANDS + .iter() + .any(|command| contains_control_word(active, command)) + }) +} + /// Recognize only complete, literal preamble lines. Never expand arbitrary macros. /// Text after begin(document) is left entirely opaque to this scanner. #[must_use] @@ -402,6 +440,18 @@ pub fn inspect(source: &str) -> ProjectImports { } offset += raw.len(); } + // Direct library loading after \begin{document} is supported by TeX. + // Likewise, body-side \input/\include can pull in external imports. Do + // not turn a preamble-only absence into a false NOT_LOADED verdict or + // silently add an earlier duplicate. Existing trusted sites stay known. + if let Some(begin) = result.begin_document.as_ref() { + if document_body_may_import(&source[begin.end..]) { + first_reason( + &mut result.uncertain_reason, + "possible late import or external source in document body", + ); + } + } if result.begin_document.is_none() { first_reason( &mut result.uncertain_reason, From 5973fa77447bb6d7b88c1bb353c12b9b70c61d34 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:00:23 +0300 Subject: [PATCH 07/16] test(C/N2): add genuine XeLaTeX document-body calc import fixture --- extensions/primitives/fixtures/late-library-in-body.tex | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 extensions/primitives/fixtures/late-library-in-body.tex diff --git a/extensions/primitives/fixtures/late-library-in-body.tex b/extensions/primitives/fixtures/late-library-in-body.tex new file mode 100644 index 00000000..0dd2ffd2 --- /dev/null +++ b/extensions/primitives/fixtures/late-library-in-body.tex @@ -0,0 +1,9 @@ +\documentclass{standalone} +\usepackage{tikz} +\begin{document} +% This library is loaded after begin-document, not in the preamble. +\usetikzlibrary{calc} +\begin{tikzpicture} + \draw let \p1=(1cm,2cm) in (0,0) -- (\x1,\y1); +\end{tikzpicture} +\end{document} From a792f6657801fc7f6edd57fc3dfac24049c8c398 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:00:44 +0300 Subject: [PATCH 08/16] test(C/N2): late/imported dependencies, source refusal, CRLF and lexical negatives --- .../tests/project_imports.rs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/crates/tikzarium-extensions/tests/project_imports.rs b/crates/tikzarium-extensions/tests/project_imports.rs index 07fafaa3..14584cf7 100644 --- a/crates/tikzarium-extensions/tests/project_imports.rs +++ b/crates/tikzarium-extensions/tests/project_imports.rs @@ -426,3 +426,111 @@ fn direct_package_before_conditional_remains_known_but_conditional_library_unkno ImportDecision::Refused { .. } )); } + + +#[test] +fn real_late_calc_import_is_unknown_without_false_absence_or_unsafe_patch() { + // Compiles under real external XeLaTeX; the preamble-only inspection + // must not misreport the late library declaration as NOT_LOADED. + let source = include_str!("../../../extensions/primitives/fixtures/late-library-in-body.tex"); + let project = inspect(source); + assert_eq!( + project.status(ImportKind::Package, "tikz"), + Evidence::Confirmed + ); + assert_eq!( + project.status(ImportKind::TikzLibrary, "calc"), + Evidence::Unknown + ); + assert_eq!( + project.status(ImportKind::TikzLibrary, "fit"), + Evidence::Unknown + ); + assert!(project.uncertain_reason.unwrap().contains("document body")); + assert!(project.declaration_sites(ImportKind::TikzLibrary, "calc").is_empty()); + assert_eq!(project.declaration_sites(ImportKind::Package, "tikz").len(), 1); + assert!(project.begin_document_target(19).is_none()); + assert!(matches!( + plan_import( + source, + SourceTarget::new(0..0, 19), + ImportKind::TikzLibrary, + "calc" + ), + ImportDecision::Refused { .. } + )); + // Disconnect also leaves the source untouched until manual inspection. + assert!(matches!( + tikzarium_extensions::project_disconnect::plan_library_disconnect( + source, + SourceTarget::new(0..0, 19), + "calc" + ), + tikzarium_extensions::project_disconnect::DisconnectDecision::Refused { .. } + )); +} + +#[test] +fn body_side_imports_external_files_and_dynamic_names_fail_closed() { + for body in [ + r"\input{external-preamble}", + r"\include{figure-with-library}", + r"\RequirePackage{tikz}", + r"\csname dynamicimport\endcsname", + r"\usetikzlibrary{positioning}", + ] { + let source = format!( + "\\documentclass{{article}}\n\\usepackage{{tikz}}\n\\begin{{document}}\n{body}\n\\end{{document}}\n" + ); + let project = inspect(&source); + assert!(project.uncertain_reason.is_some(), "{body}"); + assert_eq!( + project.status(ImportKind::Package, "tikz"), + Evidence::Confirmed, + "{body}" + ); + assert_eq!( + project.status(ImportKind::TikzLibrary, "positioning"), + Evidence::Unknown, + "{body}" + ); + assert!(project.begin_document_target(5).is_none(), "{body}"); + assert!(matches!( + plan_import( + &source, + SourceTarget::new(0..0, 5), + ImportKind::TikzLibrary, + "fit" + ), + ImportDecision::Refused { .. } + )); + } +} + +#[test] +fn body_comments_escaped_control_and_longer_names_do_not_forge_import_hints() { + let source = concat!( + "\\documentclass{article}\r\n", + "\\usepackage{tikz}\r\n", + "\\begin{document}\r\n", + "% \\usetikzlibrary{calc} and \\input{external}\r\n", + "\\\\usetikzlibrary is just text after a line break\r\n", + "\\usetikzlibraryCustom is a different control word\r\n", + "\\end{document}\r\n", + ); + let project = inspect(source); + assert_eq!(project.uncertain_reason, None); + assert_eq!( + project.status(ImportKind::Package, "tikz"), + Evidence::Confirmed + ); + assert_eq!( + project.status(ImportKind::TikzLibrary, "calc"), + Evidence::Absent + ); + let target = project.begin_document_target(2).expect("safe preamble"); + assert!(matches!( + plan_import(source, target, ImportKind::TikzLibrary, "calc"), + ImportDecision::Proposed(_) + )); +} From 4a2a756f4ab7107f13536518533e3598edb020ec Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:01:36 +0300 Subject: [PATCH 09/16] docs(C/N2): explain late imports and UNKNOWN source authority contract --- extensions/primitives/PROJECT_IMPORTS.md | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/extensions/primitives/PROJECT_IMPORTS.md b/extensions/primitives/PROJECT_IMPORTS.md index fc8b6eda..b544efca 100644 --- a/extensions/primitives/PROJECT_IMPORTS.md +++ b/extensions/primitives/PROJECT_IMPORTS.md @@ -124,3 +124,36 @@ evidence, but it must still show explicit diffs and obtain consent before applying N2 `SourcePatch`. No automatic package download, engine change, source rewrite or GUI implementation is implied. D/E/F remain responsible for independent compiled fixtures/help/source-integrity tests respectively. + + +## N2 document-body / external-file import ambiguity + +A *complete preamble scan is not a complete TeX document dependency scan*. +External XeLaTeX can load a TikZ library after \begin{document}. The C-owned +executable fixture [late-library-in-body.tex](fixtures/late-library-in-body.tex) +loads calc after the preamble and uses its coordinate syntax successfully. + +The targeted inspect() recognizer therefore looks for possible late +\usetikzlibrary, \usepackage, \RequirePackage, \input, \include or +dynamic \csname control words **after** the confirmed begin-document +anchor. It ignores ordinary % comments and escaped backslashes, and +does not confuse longer command names with exact control words. This +is **ambiguity detection**, not execution or complete TeX parsing. +Source text in a verbatim-like environment can conservatively produce +UNKNOWN: the scanner cannot prove the runtime scope. + +When such a hint is found, existing trusted preamble declarations and +their exact source ranges remain Confirmed; missing requirements become +Unknown, never Absent. begin_document_target() returns None, +and both source-add and source-disconnect planners refuse with +uncertain_reason = "possible late import or external source in document body". +The entire source remains untouched. B displays the reason and offers +manual raw TeX editing; it does not silently add a duplicate or install +a TeX package. Indirect unknown macros which do not resemble these +control words remain outside this bounded recognizer, not a claim of +complete dependency discovery. + +Regression gates: cargo test -p tikzarium-extensions --test project_imports, +the original six tool/help tests, and an *independent* external XeLaTeX +compile of the fixture (source text and PDF proof are separate from +GUI authoring). Physical Win10/MiKTeX and Mac M1/M4 remain NOT_RUN. From 3375645be8ec7bf1b50362da6efe7818f31199c8 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:01:59 +0300 Subject: [PATCH 10/16] plan(C/N2): close real late-library source-evidence regression --- plans/2026-10-31_C_N2_LATE_IMPORTS.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 plans/2026-10-31_C_N2_LATE_IMPORTS.md diff --git a/plans/2026-10-31_C_N2_LATE_IMPORTS.md b/plans/2026-10-31_C_N2_LATE_IMPORTS.md new file mode 100644 index 00000000..88d2d4f3 --- /dev/null +++ b/plans/2026-10-31_C_N2_LATE_IMPORTS.md @@ -0,0 +1,23 @@ +# C/N2 — late library loading and external-source ambiguity + +Date: 2026-10-31. Repository i-shutov/tikzarium. Base main SHA ab5705d4dd7e3dcefd3ef6ddfd695f6d0ee4dfd1. N1 #44 and earlier C/N2 #49/#51/#55 are merged; **do not replay #39**. Read live AGENTS, main, open PRs, C plans and insights before editing. No A/B/D/E/F files touched. + +## Real correctness gap + +The old targeted scanner stopped at literal \begin{document} and declared the missing calc/fit libraries Absent even when they were explicitly loaded later in the document body. This is a demonstrable XeLaTeX-valid pattern, not just a theoretical concern. An Absent UI state can lead to an incorrect user-visible "not loaded" statement and an earlier duplicate import diff. The exact external XeLaTeX fixture is extensions/primitives/fixtures/late-library-in-body.tex. + +## Single bounded source-first vertical + +1. Preserve existing trusted direct preamble declaration sites, UTF-8 offsets, five independent capability dimensions, the six primitive generators and all Russian help. C adds no visual application, CST, macro expansion, geometry solver or package installer. +2. After the explicit begin-document anchor, conservatively look for exact TeX control words usepackage, RequirePackage, usetikzlibrary, input, include and csname. Ignore commented and escaped spellings; do not falsely match longer control names. This signals **possible** runtime imports, not actual confirmed package loading, since verbatim and macro scopes are opaque. +3. If a possible late import is detected, mark missing dependencies UNKNOWN; preserve already-confirmed preamble imports. Report a bounded diagnostic and refuse source connection/disconnection plans. B retains source diff/Undo UI and raw source editing. +4. Add C-owned Rust tests for a genuine late calc fixture, body file inclusion/dynamic controls, LF/CRLF, commented/escaped/long-control negatives, no mutation, and original six-tool regression. Keep E/F source/help test files untouched. +5. Independently compile the fixture with externally installed XeLaTeX and record source/PDF SHA; hosted CI gates are fmt/check/test/clippy plus N1 regression, not physical Windows 10 MiKTeX or Mac M1/M4 GUI tests. +6. Handoff to B via an additive API note: same inspect()/status()/begin_document_target() contract; no new B type changes required. D may incorporate this fixture in its native XeLaTeX smoke matrix. + +## Acceptance conditions and scope limit + +- Exact branch HEAD and CI outcomes must be recorded; no PASS based on older commits. +- No returned SourcePatch when the body might load libraries; source remains byte-for-byte unchanged. +- The scanner is a targeted ambiguity detector, **not complete TeX evaluation**; arbitrary unrecognized macros may still import indirectly. If exact source proof is unavailable, use UNKNOWN where a supported ambiguity is detected. +- No claims about installed packages, visual edits, physical Windows 10 x64 MiKTeX or Apple Silicon Mac. From 9045b56d4f2c843454809606ac8531e624118236 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:02:23 +0300 Subject: [PATCH 11/16] insight(C/N2): record verified XeLaTeX bug fixture and pending Rust CI --- insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md diff --git a/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md new file mode 100644 index 00000000..a64a1ccc --- /dev/null +++ b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md @@ -0,0 +1,26 @@ +# C/N2 late imports — exact evidence and QA ledger + +Date 2026-10-31. Base main ab5705d4dd7e3dcefd3ef6ddfd695f6d0ee4dfd1. Independent C branch feat/c-n2-late-import-ambiguity-20261031. N1 #44, C #49/#51/#55 already merged; old #39 not replayed. Scope: C project-import recognizer, tests, own TeX fixture, C contract, plan and insight only. + +## Observed bug before change + +The C scanner recognized the preamble, stopped at begin-document, and assigned calc=Absent to a source where \usetikzlibrary{calc} appears after begin-document. This is a real executable case: TeX loads calc and then evaluates its "let \p1" coordinate syntax. The earlier planner could propose a duplicate import instead of acknowledging uncertain dependency provenance. + +## Implementation and source contract + +- Bounded read-only body scan after a recognized begin-document anchor detects targeted TeX control words: usetikzlibrary/usepackage/RequirePackage/input/include/csname. No macro expansion, TeX interpreter, CST or universal scene model. +- Whole-word boundary and escaped-backslash parity checks avoid obvious false positives; ordinary % comments are excluded. Verbatim-like occurrences may conservatively yield UNKNOWN; this is intentional rather than claiming Confirmed. +- Existing trusted preamble declarations remain Confirmed and keep the same read-only ranges. Missing dependencies become Unknown; begin_document_target=None and both connection/disconnection planners refuse with reason. No source mutation, package installation or compiler change. +- Same five independent capabilities; six TikZ primitives and their Russian help untouched. B can use existing three-way evidence and refusal UX with no type migration. + +## Genuine local XeLaTeX proof + +Fixture exact text committed under extensions/primitives/fixtures/late-library-in-body.tex. Local external Debian TeX Live 2025/dev XeLaTeX, command -no-shell-escape -interaction=nonstopmode -halt-on-error, exit 0; pdfinfo reports 1 page. Input SHA-256 d65e61f464b5afb5c1712a0c1143ff600a55a6bfca36dc55c6c1f8e80104e2ca; PDF SHA-256 e254a0655350494d2572ea68dfccd575110b97a0893aea5efc07abcdd969e1ce. This is a **local fixture compilation only**: not a Windows 10/MiKTeX or physical macOS M1/M4 test, nor visual egui editing. + +## Rust tests and hosted gates + +Added source regression tests for literal late library, external body input/include, dynamic csname, RequirePackage, commented/escaped/longer-control negatives, CRLF and existing positive preamble sites. Await exact-head cargo fmt/check/test/clippy and native N1 GitHub Actions. Local cargo/rustc unavailable; local Rust validation NOT_RUN. No hosted CI PASS asserted before actual job conclusion. + +## Remaining limitation + +Body-side arbitrary macro expansion cannot be resolved without executing TeX; unsupported cases remain source-only. The conservative scanner cannot distinguish runtime literal from verbatim text; it may refuse safe automatic edits rather than produce a false definite "not loaded". E help and F source tests belong to independent streams. B owns visible diff/confirmation/Undo; D owns independent native CI coverage. From 6e3173eca2896c3557c0e283b4e4ac9a78393d64 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:04:25 +0300 Subject: [PATCH 12/16] style(C/N2): apply exact hosted rustfmt diff without suppressions --- crates/tikzarium-extensions/tests/project_imports.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/tikzarium-extensions/tests/project_imports.rs b/crates/tikzarium-extensions/tests/project_imports.rs index 14584cf7..f6027b70 100644 --- a/crates/tikzarium-extensions/tests/project_imports.rs +++ b/crates/tikzarium-extensions/tests/project_imports.rs @@ -427,7 +427,6 @@ fn direct_package_before_conditional_remains_known_but_conditional_library_unkno )); } - #[test] fn real_late_calc_import_is_unknown_without_false_absence_or_unsafe_patch() { // Compiles under real external XeLaTeX; the preamble-only inspection @@ -447,8 +446,15 @@ fn real_late_calc_import_is_unknown_without_false_absence_or_unsafe_patch() { Evidence::Unknown ); assert!(project.uncertain_reason.unwrap().contains("document body")); - assert!(project.declaration_sites(ImportKind::TikzLibrary, "calc").is_empty()); - assert_eq!(project.declaration_sites(ImportKind::Package, "tikz").len(), 1); + assert!( + project + .declaration_sites(ImportKind::TikzLibrary, "calc") + .is_empty() + ); + assert_eq!( + project.declaration_sites(ImportKind::Package, "tikz").len(), + 1 + ); assert!(project.begin_document_target(19).is_none()); assert!(matches!( plan_import( From 1adb5aa2bed6ba1efcc01f5fb79f3034429821ef Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:04:43 +0300 Subject: [PATCH 13/16] qa(C/N2): preserve failed initial rustfmt provenance and exact correction --- insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md index a64a1ccc..e8544a54 100644 --- a/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md +++ b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md @@ -24,3 +24,9 @@ Added source regression tests for literal late library, external body input/incl ## Remaining limitation Body-side arbitrary macro expansion cannot be resolved without executing TeX; unsupported cases remain source-only. The conservative scanner cannot distinguish runtime literal from verbatim text; it may refuse safe automatic edits rather than produce a false definite "not loaded". E help and F source tests belong to independent streams. B owns visible diff/confirmation/Undo; D owns independent native CI coverage. + + +## Initial exact-head CI failure and correction + +PR #61 initial HEAD 9045b56d4f2c843454809606ac8531e624118236: +[Native Rust CI run 41212945414](https://github.com/i-shutov/tikzarium/actions/runs/41212945414) **FAILED** macOS and Windows Server at cargo fmt --check, before Rust tests. Retrieved job log 124571547263. Formatter requested only: remove one excess blank line ahead of the first new test, and wrap two long declaration_sites() assertions. Those exact changes were applied in C test code (commit 6e3173eca2896c3557c0e283b4e4ac9a78393d64). This is a correction, **not a PASS** until the subsequent exact-head CI is checked. TeX compilation evidence remains the independent local fixture proof, not the initial failed Rust CI. From cba46ec8f29cc15a70284e54859c44802a610c31 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:05:07 +0300 Subject: [PATCH 14/16] docs(C/N2): align recognizer comment with conservative body hint detection --- crates/tikzarium-extensions/src/project_imports.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index 34e40da0..eca7d5f1 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -278,7 +278,8 @@ fn document_body_may_import(body: &str) -> bool { } /// Recognize only complete, literal preamble lines. Never expand arbitrary macros. -/// Text after begin(document) is left entirely opaque to this scanner. +/// After begin(document), detect only possible late import hints: these never +/// establish positive evidence or authorize a source edit. #[must_use] pub fn inspect(source: &str) -> ProjectImports { let has_crlf = source.contains("\r\n"); From 731c6563f91360075ec1abafd97ebc37e461d3d7 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:09:00 +0300 Subject: [PATCH 15/16] fix(C/N2): satisfy strict Clippy collapsible-if without lint suppression --- .../tikzarium-extensions/src/project_imports.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/tikzarium-extensions/src/project_imports.rs b/crates/tikzarium-extensions/src/project_imports.rs index eca7d5f1..ab873823 100644 --- a/crates/tikzarium-extensions/src/project_imports.rs +++ b/crates/tikzarium-extensions/src/project_imports.rs @@ -445,13 +445,15 @@ pub fn inspect(source: &str) -> ProjectImports { // Likewise, body-side \input/\include can pull in external imports. Do // not turn a preamble-only absence into a false NOT_LOADED verdict or // silently add an earlier duplicate. Existing trusted sites stay known. - if let Some(begin) = result.begin_document.as_ref() { - if document_body_may_import(&source[begin.end..]) { - first_reason( - &mut result.uncertain_reason, - "possible late import or external source in document body", - ); - } + if result + .begin_document + .as_ref() + .is_some_and(|begin| document_body_may_import(&source[begin.end..])) + { + first_reason( + &mut result.uncertain_reason, + "possible late import or external source in document body", + ); } if result.begin_document.is_none() { first_reason( From e66ed1f97cf39e824dea61c8c54ef96dec7b1001 Mon Sep 17 00:00:00 2001 From: i-shutov Date: Sat, 31 Oct 2026 18:09:23 +0300 Subject: [PATCH 16/16] qa(C/N2): record exact Clippy failure/fix and negative XeLaTeX control --- insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md index e8544a54..5f1f4a41 100644 --- a/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md +++ b/insights/2026-10-31_C_N2_LATE_IMPORTS_QA.md @@ -30,3 +30,11 @@ Body-side arbitrary macro expansion cannot be resolved without executing TeX; un PR #61 initial HEAD 9045b56d4f2c843454809606ac8531e624118236: [Native Rust CI run 41212945414](https://github.com/i-shutov/tikzarium/actions/runs/41212945414) **FAILED** macOS and Windows Server at cargo fmt --check, before Rust tests. Retrieved job log 124571547263. Formatter requested only: remove one excess blank line ahead of the first new test, and wrap two long declaration_sites() assertions. Those exact changes were applied in C test code (commit 6e3173eca2896c3557c0e283b4e4ac9a78393d64). This is a correction, **not a PASS** until the subsequent exact-head CI is checked. TeX compilation evidence remains the independent local fixture proof, not the initial failed Rust CI. + + +## Second hosted gate and negative compiler control + +On source HEAD cba46ec8f29cc15a70284e54859c44802a610c31, +[D QA run 41213143785](https://github.com/i-shutov/tikzarium/actions/runs/41213143785) Ubuntu reported workspace Rust tests SUCCESS but strict Clippy FAILURE at project_imports.rs:448 (collapsible_if). The actual error was retrieved from job 124572119835; the nested conditional was rewritten into is_some_and() in commit 731c6563f91360075ec1abafd97ebc37e461d3d7 without disabling any lint. All-gates PASS requires the new exact HEAD; the older run remains FAILED. + +Independent *negative* local compiler control removed the post-begin \usetikzlibrary{calc} line while keeping the same let \p1 drawing: external Debian XeLaTeX (TeX Live 2025/dev, -no-shell-escape, -halt-on-error) exited 1 with the explicit TikZ diagnostic "You need to say \usetikzlibrary{calc} to use the let command"; no PDF was produced. This shows the positive fixture's post-begin import is semantically essential. The positive one-page PDF was rasterized and visually inspected: the expected diagonal line is present. This remains local fixture-level proof, not native GUI editing or physical Windows/macOS validation.