From 08b228aaf8fd7b199b46dc0314a3c78c3dfa4cdb Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:31:37 +0100 Subject: [PATCH 1/4] docs: separate precision suite roles and ForthWall safety --- README.adoc | 64 +++++++++++++++---- docs/DOCUMENTATION-SYSTEM-ARCHITECTURE.adoc | 71 +++++++++++++-------- docs/MOSCOW-REQUIREMENTS.adoc | 42 ++++++------ docs/SEAM-CHECK-MUSTS.adoc | 13 ++-- docs/V1-PUBLISH-ROADMAP.adoc | 33 +++++----- 5 files changed, 145 insertions(+), 78 deletions(-) diff --git a/README.adoc b/README.adoc index 833a980..a3c31e2 100644 --- a/README.adoc +++ b/README.adoc @@ -20,17 +20,58 @@ This project must declare **MPL-2.0-or-later** for platform/tooling compatibilit Philosophy: **Palimpsest**. The MPL-2.0 (PMPL) text is provided in `license/MPL-2.0.txt`, and the canonical source is the palimpsest-license repository. -Cross-platform document editor with format tabs (TXT/MD/ADOC/DJOT/ORG/RST/TYP). Gossamer GUI + Ada TUI. Graph visualization, OCR, TTS/STT, Nickel pipelines. +DocMatrix is multi-format document conversion and precision infrastructure for +the wider document suite. The tabbed multi-format viewer/editor is the separate +*Formatrix Docs* project. GUI, TUI, graph, and editor claims in copied or +ecosystem-level documents must not be attributed to DocMatrix itself. + +ForthWall is a proposed capability-bounded Forth execution layer for critical +precision operations. It is not currently implemented or proved. == Features -* *Format Tabs* - View and edit the same document in multiple markup formats -* *Unified AST* - Lossless conversion between formats -* *GUI* - Gossamer with AffineScript frontend -* *TUI* - Ada with AdaCurses for terminal usage -* *Graph Visualization* - ArangoDB for document relationships -* *Accessibility* - OCR, TTS, STT support -* *Pipelines* - Nickel-based import/export transformations +* *Current core* - Parse and render supported formats through a unified AST +* *Current interfaces* - Rust conversion APIs plus a C ABI/FFI surface +* *Suite boundary* - Formatrix Docs owns the tabbed viewer/editor experience +* *Critical-mode proposal* - ForthWall rules bounded to declared inputs, + approved document operations, and independently verified outputs + +== Precision document suite + +DocMatrix is designed to cooperate with two distinct user-facing tools: + +* https://github.com/hyperpolymath/formatrix-docs[Formatrix Docs] — view one + logical document through tabbed TXT, delimiter-selected tabular text, + Markdown, AsciiDoc, Djot, and A2ML representations, with synchronised editing + as the intended progression; and +* https://github.com/hyperpolymath/blocky-writer[Blocky Writer] — fit content + into fixed-layout PDF and application-form boxes, baselines, and + per-character cells that were designed for hand spacing rather than reliable + computer entry. + +The responsibilities must remain separate in code and evidence. Conversion +correctness does not prove synchronised editing, and synchronised editing does +not prove page-coordinate placement. A ForthWall operation may coordinate a +bounded critical step only after the relevant component's own semantic, +round-trip, geometry, confinement, and independent-verification gates pass. +The versioned composition contract is tracked in +https://github.com/hyperpolymath/docmatrix/issues/71[issue #71]; it explicitly +forbids making Microsoft Word or another lossy hub format mandatory. + +== Conversion delivery gate + +DocMatrix conversion claims require independently reproducible tests that +round trips preserve every construct described as lossless, lossy conversions +are reported, input and output formats are identified, and malformed or +ambiguous documents fail without silent rewriting. Viewer/editor behaviours +such as cursor mapping, synchronised tabs, and undo/redo belong to Formatrix +Docs and must be proved there. + +Critical-mode automatic editing through the proposed ForthWall layer has an +additional safety gate: bounded capabilities, exact input hashes and evidence +spans, semantic refusal conditions, replay traces, and independent verification +of every applied edit. See +https://github.com/hyperpolymath/docmatrix/issues/70[issue #70]. == Supported Formats @@ -57,11 +98,10 @@ just deps # Build all components just build -# Run GUI -just run-gui +# Exercise the currently evidenced conversion core +just test-core -# Run TUI -just run-tui +# GUI and TUI recipes are ecosystem scaffolding, not DocMatrix capability proof ---- == Architecture diff --git a/docs/DOCUMENTATION-SYSTEM-ARCHITECTURE.adoc b/docs/DOCUMENTATION-SYSTEM-ARCHITECTURE.adoc index a433839..36748ba 100644 --- a/docs/DOCUMENTATION-SYSTEM-ARCHITECTURE.adoc +++ b/docs/DOCUMENTATION-SYSTEM-ARCHITECTURE.adoc @@ -10,6 +10,12 @@ The Hyperpolymath Documentation System consists of four integrated components that handle document creation, reconciliation, enforcement, and distribution. +WARNING: This document describes a target architecture, not the currently +delivered system. Boxes, source trees, examples, and arrows are proposals unless +linked to executable code and independently reproducible evidence. The current +recon-silly-ation state records both its reconciliation engine and proposed +ForthWall VM as 0% complete. + [plantuml,components,svg] ---- @startuml @@ -17,12 +23,12 @@ skinparam componentStyle rectangle package "Human Side" { [Formatrix Docs] as FD - note right of FD : GUI (Tauri)\nTUI (Ada)\nMulti-format editor + note right of FD : Multi-format viewer\nGUI/TUI editor target\nSynchronised views } package "Machine Side" { [Recon-Silly-Ation] as RSA - note right of RSA : ReconForth DSL\nReconciliation engine\nConflict resolution + note right of RSA : Proposed ForthWall DSL\nConsistency reconciler\nConflict reporting [Docubot] as DB note right of DB : LLM integration\nDoc generation\nGuardrails @@ -51,20 +57,21 @@ PS --> ARANGO : Pack manifests == Component Breakdown -=== 1. Formatrix Docs (Human Editor) +=== 1. Formatrix Docs (Multi-Format Viewer; Editor Target) *Location*: `~/repos/formatrix-docs` -*Purpose*: Cross-platform document editor with format tabs +*Current purpose*: Multi-format document viewing and conversion through a +unified AST -*Capabilities*: +*Target purpose*: Cross-platform document editor with synchronised format tabs + +*Current and proposed capabilities*: -* View/edit same document in 7 formats (TXT, MD, ADOC, DJOT, ORG, RST, TYP) -* Live format conversion via unified AST -* Graph visualization of document links -* OCR, TTS/STT, spell checking -* Git integration -* Nickel-based content pipelines +* Current: view, parse, and render supported formats through a unified AST +* Proposed: view/edit the same document in 7 synchronised format views +* Proposed: graph visualisation, OCR, TTS/STT, spell checking, Git integration, + and Nickel-based content pipelines *Architecture*: @@ -105,24 +112,33 @@ pub struct Document { *Location*: `~/repos/recon-silly-ation` -*Purpose*: Reconcile, deduplicate, and validate documentation bundles +*Purpose*: Find and safely reconcile contradictions across documentation, +history, metadata, terminology, language policy, attribution, and repository +surfaces -*Capabilities*: +*Proposed capabilities (not currently delivered)*: -* ReconForth DSL for reconciliation rules +* ForthWall DSL for bounded reconciliation rules * 7-stage idempotent pipeline (Scan → Normalize → Deduplicate → Detect → Resolve → Ingest → Report) * Content-addressable storage (SHA-256) * Graph-based conflict resolution * Enforcement bot for automated policy compliance * Pack shipper for bundle distribution -*Architecture*: +ForthWall is an optional implementation layer beneath recon-silly-ation, not +the meaning of “recon” or the identity of the product. Its permitted vocabulary +must operate only on declared documents and evidence. Raw memory, unrestricted +filesystem writes, shell execution, and networking are outside the wall. +Ambiguous semantics, stale inputs, parser disagreement, or coordinate drift +must cause refusal rather than automatic repair. + +*Proposed architecture*: [source] ---- recon-silly-ation/ ├── src/ -│ ├── ReconForth.res # AffineScript bindings to WASM +│ ├── ForthWall.res # proposed AffineScript bindings to bounded WASM │ ├── EnforcementBot.res # Policy enforcement │ ├── PackShipper.res # Bundle distribution │ ├── LogicEngine.res # Datalog-style inference @@ -130,7 +146,7 @@ recon-silly-ation/ ├── wasm-modules/ │ └── src/ │ ├── lib.rs # WASM exports -│ └── reconforth/ # Forth interpreter +│ └── forthwall/ # proposed capability-bounded Forth interpreter │ ├── lexer.rs │ ├── types.rs │ ├── vm.rs @@ -139,7 +155,7 @@ recon-silly-ation/ └── validator/ # Haskell schema validation ---- -*ReconForth Example*: +*Illustrative ForthWall syntax (not executable evidence)*: [source,forth] ---- @@ -254,8 +270,8 @@ Docubot ┌──────────────────────┐ ┌──────────────────────────────────────┐ │ DOCUBOT │ │ RECON-SILLY-ATION │ │ ┌────────────────┐ │ │ ┌──────────────┐ ┌──────────────┐ │ -│ │ LLM Engine │ │ │ │ ReconForth │ │ LogicEngine │ │ -│ │ (guardrailed) │ │ │ │ VM │ │ (Datalog) │ │ +│ │ LLM Engine │ │ │ │ ForthWall │ │ LogicEngine │ │ +│ │ (guardrailed) │ │ │ │ (proposed) │ │ (Datalog) │ │ │ └────────┬───────┘ │ │ └──────┬───────┘ └──────┬───────┘ │ └───────────┼──────────┘ │ └─────────────────┘ │ │ │ │ │ @@ -432,13 +448,13 @@ services: === Adding a New Document Type 1. *formatrix-docs*: Add parser and renderer to formatrix-core -2. *recon-silly-ation*: Add format detection in ReconForth +2. *recon-silly-ation*: Add and prove format detection in ForthWall 3. *Docubot*: Update templates for the new format 4. *Docudactyl*: Add format to pipeline configuration === Adding a New Enforcement Rule -1. Define rule in ReconForth: +1. Define a bounded rule in ForthWall: + [source,forth] ---- @@ -452,7 +468,7 @@ services: === Adding a New Pack Specification -1. Define pack in ReconForth: +1. Define pack in ForthWall: + [source,forth] ---- @@ -489,10 +505,12 @@ services: |Component |Role |*Formatrix Docs* -|Human-facing editor with format tabs and graph view +|Multi-format converter being developed into a human-facing editor; format tabs +and graph view remain delivery-gated targets |*Recon-Silly-Ation* -|Machine reconciliation with ReconForth DSL +|Cross-document consistency reconciliation with an optional, proposed +capability-bounded ForthWall DSL |*Docubot* |LLM-powered generation with mandatory guardrails @@ -501,7 +519,8 @@ services: |Orchestrator connecting all components |=== -The four components form a complete documentation lifecycle: +The four proposed components are intended to form a complete documentation +lifecycle once their implementation and evidence gates are satisfied: ---- Edit (formatrix) → Reconcile (RSA) → Enforce (bot) → Ship (packs) diff --git a/docs/MOSCOW-REQUIREMENTS.adoc b/docs/MOSCOW-REQUIREMENTS.adoc index b1db376..1db8208 100644 --- a/docs/MOSCOW-REQUIREMENTS.adoc +++ b/docs/MOSCOW-REQUIREMENTS.adoc @@ -194,68 +194,68 @@ This document defines Must/Should/Could requirements for each component of the d |ID |Requirement |Status |RSA-M01 -|ReconForth lexer (tokenization) -|✅ Done +|ForthWall lexer (tokenization) +|🔲 Pending |RSA-M02 -|ReconForth VM (stack, dictionary) -|✅ Done +|ForthWall VM (stack, dictionary) +|🔲 Pending |RSA-M03 |Stack manipulation words (dup, drop, swap, over, rot) -|✅ Done +|🔲 Pending |RSA-M04 |Arithmetic words (+, -, *, /, mod) -|✅ Done +|🔲 Pending |RSA-M05 |Comparison words (=, <, >, <=, >=, <>) -|✅ Done +|🔲 Pending |RSA-M06 |Logic words (and, or, not) -|✅ Done +|🔲 Pending |RSA-M07 |Control flow words (if/else/then, call) -|✅ Done +|🔲 Pending |RSA-M08 |Document words (new-doc, doc-hash, doc-content) -|✅ Done +|🔲 Pending |RSA-M09 |Bundle words (new-bundle, bundle-add, bundle-count) -|✅ Done +|🔲 Pending |RSA-M10 |Pack words (new-pack, require-doc, optional-doc) -|✅ Done +|🔲 Pending |RSA-M11 |Validation words (emit-error, emit-warning, emit-suggestion) -|✅ Done +|🔲 Pending |RSA-M12 |Format detection (detect-format) -|✅ Done +|🔲 Pending |RSA-M13 -|WASM bindings for AffineScript -|✅ Done +|Capability-bounded WASM bindings for AffineScript +|🔲 Pending |RSA-M14 |EnforcementBot rule definitions -|✅ Done +|🔲 Pending |RSA-M15 |PackShipper manifest creation -|✅ Done +|🔲 Pending |RSA-M16 |Content hashing (SHA-256) -|✅ Done +|🔲 Pending |RSA-M17 |RSR compliance files @@ -270,7 +270,7 @@ This document defines Must/Should/Could requirements for each component of the d |RSA-S01 |Format parsing words (parse-content, get-headings) -|✅ Done +|🔲 Pending |RSA-S02 |EnforcementBot job scheduling @@ -290,7 +290,7 @@ This document defines Must/Should/Could requirements for each component of the d |RSA-S06 |LogicEngine Datalog rules -|✅ Done +|🔲 Pending |RSA-S07 |Conflict detection algorithms diff --git a/docs/SEAM-CHECK-MUSTS.adoc b/docs/SEAM-CHECK-MUSTS.adoc index f80c897..53c0d25 100644 --- a/docs/SEAM-CHECK-MUSTS.adoc +++ b/docs/SEAM-CHECK-MUSTS.adoc @@ -8,7 +8,12 @@ == Overview -This document validates integration seams between ecosystem components after implementing all MUST requirements. +WARNING: This is a historical design-time seam report. It does not validate the +current checkouts and must not be used as evidence that the listed components +or seams are implemented. In particular, the current recon-silly-ation state +records its actual reconciliation engine and proposed ForthWall VM as 0% +complete. Every status below requires revalidation against executable code and +independently reproducible tests. == Components Checked @@ -17,9 +22,9 @@ This document validates integration seams between ecosystem components after imp | Component | Repository | Status | Formatrix Docs | formatrix-docs | ✓ Implemented -| Recon-Silly-Ation | recon-silly-ation | ✓ Implemented -| Docubot | recon-silly-ation/Docubot.res | ✓ Implemented -| Docudactyl | recon-silly-ation/Docudactyl.res | ✓ Implemented +| Recon-Silly-Ation | recon-silly-ation | ✗ Reconciliation engine not implemented +| Docubot | recon-silly-ation/Docubot.res | ? Historical path; revalidation required +| Docudactyl | recon-silly-ation/Docudactyl.res | ? Historical path; revalidation required |=== == SEAM-1: Formatrix ↔ RSA diff --git a/docs/V1-PUBLISH-ROADMAP.adoc b/docs/V1-PUBLISH-ROADMAP.adoc index c10ac1c..47601c3 100644 --- a/docs/V1-PUBLISH-ROADMAP.adoc +++ b/docs/V1-PUBLISH-ROADMAP.adoc @@ -10,8 +10,10 @@ This roadmap outlines the path to publishing v1 of the Formatrix documentation ecosystem, comprising four components: -1. **Formatrix Docs** - Human document editor with multi-format tabs -2. **Recon-Silly-Ation** - Document reconciliation engine (ReconForth VM) +1. **Formatrix Docs** - Multi-format viewer being developed into an editor with + synchronised format tabs +2. **Recon-Silly-Ation** - Cross-document consistency reconciler; proposed + bounded rule layer named ForthWall 3. **Docubot** - AI document generation assistant 4. **Docudactyl** - Orchestration and pipeline management @@ -22,15 +24,15 @@ This roadmap outlines the path to publishing v1 of the Formatrix documentation e |Component |Description |MUSTs |SHOULDs |COULDs |Formatrix Docs -|Multi-format document editor +|Multi-format viewer; editor under development |9/14 (64%) |6/12 (50%) |1/12 (8%) |Recon-Silly-Ation -|ReconForth reconciliation engine -|17/17 (100%) -|2/12 (17%) +|Consistency reconciler with proposed ForthWall rule engine +|1/17 (6%) +|0/12 (0%) |0/8 (0%) |Docubot @@ -61,9 +63,9 @@ For v1 publish, the following are **required**: * [ ] File open/save operations .Recon-Silly-Ation Core -* [x] ReconForth VM complete -* [x] All core word sets -* [x] WASM bindings +* [ ] ForthWall VM specified, implemented, and proved +* [ ] All core and document word sets implemented and individually evidenced +* [ ] Bounded WASM bindings with capability-confinement tests * [ ] 7-stage pipeline implementation * [ ] CLI interface @@ -133,7 +135,8 @@ For v1 publish, the following are **required**: * Basic document editing with format tabs (7 formats) * Document storage in ArangoDB with graph links -* ReconForth-based reconciliation with format detection +* ForthWall-based reconciliation with format detection, only if its safety and + proof gates are implemented and independently reproducible * Basic orchestration for pipeline execution * TUI for terminal-based editing * Container deployment @@ -210,9 +213,9 @@ docudactyl |Large |Recon-Silly-Ation -|0 items -|10 items -|Medium +|16 items +|12 items +|Large |Docubot |10 items @@ -227,11 +230,11 @@ docudactyl == Milestones -=== M1: Core Complete (Current) +=== M1: Core Complete (Not Yet Reached) * [x] All format handlers implemented * [x] FFI exports for Ada TUI * [x] ArangoDB client functional -* [x] ReconForth VM operational +* [ ] ForthWall VM operational, capability-confined, and proved === M2: Basic GUI Functional * [ ] Tauri shell running From 0fdf36f77d36afc939b5593c99e7e7e0d73563f6 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:10:05 +0100 Subject: [PATCH 2/4] fix(ci): resolve Gossamer without sibling checkout --- Cargo.lock | 1 + crates/formatrix-gui/Cargo.toml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index bf67f5e..9ad5e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,6 +1178,7 @@ dependencies = [ [[package]] name = "gossamer-rs" version = "0.1.0" +source = "git+https://github.com/metadatastician/gossamer.git?rev=4d360f9f02d96a5b17a0590af9595af61db153be#4d360f9f02d96a5b17a0590af9595af61db153be" dependencies = [ "serde", "serde_json", diff --git a/crates/formatrix-gui/Cargo.toml b/crates/formatrix-gui/Cargo.toml index b136cf4..fc1ac6a 100644 --- a/crates/formatrix-gui/Cargo.toml +++ b/crates/formatrix-gui/Cargo.toml @@ -19,7 +19,10 @@ path = "src/main.rs" [dependencies] formatrix-core = { path = "../formatrix-core" } -gossamer-rs = { path = "../../../gossamer/bindings/rust" } +# A sibling checkout exists in the developer estate but not on CI runners or in +# published source archives. Pin the actual upstream repository so this +# workspace is self-resolving and reproducible outside that local layout. +gossamer-rs = { git = "https://github.com/metadatastician/gossamer.git", rev = "4d360f9f02d96a5b17a0590af9595af61db153be" } serde.workspace = true serde_json.workspace = true From 92a1c72bcb64138c08cae3e2cad2154f326a98cd Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 02:36:04 +0100 Subject: [PATCH 3/4] fix(rust): clear strict all-target diagnostics --- crates/formatrix-core/benches/format_bench.rs | 75 ++++++++++--------- crates/formatrix-core/src/ast.rs | 16 ++-- crates/formatrix-core/src/formats/djot.rs | 10 +-- crates/formatrix-core/src/formats/markdown.rs | 41 +++++----- crates/formatrix-core/src/formats/orgmode.rs | 8 +- crates/formatrix-core/src/formats/rst.rs | 29 ++++--- crates/formatrix-core/src/formats/typst.rs | 18 ++--- crates/formatrix-core/tests/aspect_test.rs | 46 ++++++++---- crates/formatrix-core/tests/e2e_test.rs | 73 +++++++++++------- crates/formatrix-core/tests/property_test.rs | 12 +-- crates/formatrix-core/tests/unit_test.rs | 75 +++++++++++-------- crates/formatrix-gui/src/commands.rs | 7 +- 12 files changed, 226 insertions(+), 184 deletions(-) diff --git a/crates/formatrix-core/benches/format_bench.rs b/crates/formatrix-core/benches/format_bench.rs index 286dc23..3adf652 100644 --- a/crates/formatrix-core/benches/format_bench.rs +++ b/crates/formatrix-core/benches/format_bench.rs @@ -2,12 +2,12 @@ // Copyright (c) Jonathan D.A. Jewell //! Benchmark tests for format conversion performance +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use formatrix_core::{ ast::{Block, Document, DocumentMeta, Inline, SourceFormat}, - traits::{Parser, ParseConfig, RenderConfig, Renderer}, formats::PlainTextHandler, + traits::{ParseConfig, Parser, RenderConfig, Renderer}, }; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; /// Benchmark plaintext parsing of small documents fn bench_parse_small_plaintext(c: &mut Criterion) { @@ -26,7 +26,8 @@ fn bench_parse_medium_plaintext(c: &mut Criterion) { c.bench_function("parse_medium_plaintext_10kb", |b| { b.iter(|| { let parser = PlainTextHandler::new(); - let input = black_box(&"Lorem ipsum dolor sit amet.\n\n".repeat(500)); + let source = "Lorem ipsum dolor sit amet.\n\n".repeat(500); + let input = black_box(source.as_str()); let config = ParseConfig::default(); parser.parse(input, &config) }) @@ -38,7 +39,8 @@ fn bench_parse_large_plaintext(c: &mut Criterion) { c.bench_function("parse_large_plaintext_100kb", |b| { b.iter(|| { let parser = PlainTextHandler::new(); - let input = black_box(&"Lorem ipsum dolor sit amet.\n\n".repeat(5000)); + let source = "Lorem ipsum dolor sit amet.\n\n".repeat(5000); + let input = black_box(source.as_str()); let config = ParseConfig::default(); parser.parse(input, &config) }) @@ -51,14 +53,12 @@ fn bench_render_plaintext(c: &mut Criterion) { let doc = Document { source_format: SourceFormat::PlainText, meta: DocumentMeta::default(), - content: vec![ - Block::Paragraph { - content: vec![Inline::Text { - content: "Test paragraph content.".to_string(), - }], - span: None, - }, - ], + content: vec![Block::Paragraph { + content: vec![Inline::Text { + content: "Test paragraph content.".to_string(), + }], + span: None, + }], raw_source: None, }; @@ -93,8 +93,10 @@ fn bench_parse_with_source_preservation(c: &mut Criterion) { b.iter(|| { let parser = PlainTextHandler::new(); let input = black_box("Test content with preservation.\n\nAnother paragraph."); - let mut config = ParseConfig::default(); - config.preserve_raw_source = true; + let config = ParseConfig { + preserve_raw_source: true, + ..Default::default() + }; parser.parse(input, &config) }) @@ -107,8 +109,10 @@ fn bench_parse_with_span_preservation(c: &mut Criterion) { b.iter(|| { let parser = PlainTextHandler::new(); let input = black_box("Test content with spans.\n\nAnother paragraph."); - let mut config = ParseConfig::default(); - config.preserve_spans = true; + let config = ParseConfig { + preserve_spans: true, + ..Default::default() + }; parser.parse(input, &config) }) @@ -118,26 +122,22 @@ fn bench_parse_with_span_preservation(c: &mut Criterion) { /// Benchmark document creation fn bench_document_creation(c: &mut Criterion) { c.bench_function("create_document_with_metadata", |b| { - b.iter(|| { - Document { - source_format: SourceFormat::PlainText, - meta: DocumentMeta { - title: Some("Test Title".to_string()), - authors: vec!["Author".to_string()], - date: Some("2026-04-04".to_string()), - language: Some("en".to_string()), - ..Default::default() - }, - content: vec![ - Block::Paragraph { - content: vec![Inline::Text { - content: black_box("Content".to_string()), - }], - span: None, - }, - ], - raw_source: None, - } + b.iter(|| Document { + source_format: SourceFormat::PlainText, + meta: DocumentMeta { + title: Some("Test Title".to_string()), + authors: vec!["Author".to_string()], + date: Some("2026-04-04".to_string()), + language: Some("en".to_string()), + ..Default::default() + }, + content: vec![Block::Paragraph { + content: vec![Inline::Text { + content: black_box("Content".to_string()), + }], + span: None, + }], + raw_source: None, }) }); } @@ -150,7 +150,8 @@ fn bench_batch_parsing(c: &mut Criterion) { let config = ParseConfig::default(); for i in 0..10 { - let input = black_box(&format!("Document {}.\n\nContent.", i)); + let source = format!("Document {}.\n\nContent.", i); + let input = black_box(source.as_str()); let _ = parser.parse(input, &config); } }) diff --git a/crates/formatrix-core/src/ast.rs b/crates/formatrix-core/src/ast.rs index 5b426cd..4c1884d 100644 --- a/crates/formatrix-core/src/ast.rs +++ b/crates/formatrix-core/src/ast.rs @@ -683,16 +683,18 @@ mod proptests { prop_assert!(format.extension() != format.label() || format.extension() == format.label().to_lowercase()); } - // Property: Document word_count is non-negative + // Property: Document word_count composes from its blocks #[test] - fn prop_document_word_count_nonnegative(doc in document_strategy()) { - prop_assert!(doc.word_count() >= 0); + fn prop_document_word_count_composes(doc in document_strategy()) { + let expected: usize = doc.content.iter().map(Block::word_count).sum(); + prop_assert_eq!(doc.word_count(), expected); } - // Property: Document char_count is non-negative + // Property: Document char_count composes from its blocks #[test] - fn prop_document_char_count_nonnegative(doc in document_strategy()) { - prop_assert!(doc.char_count() >= 0); + fn prop_document_char_count_composes(doc in document_strategy()) { + let expected: usize = doc.content.iter().map(Block::char_count).sum(); + prop_assert_eq!(doc.char_count(), expected); } // Property: Empty document has zero word count @@ -742,7 +744,7 @@ mod proptests { span: None, }; if let Block::Heading { level: l, .. } = block { - prop_assert!(l >= 1 && l <= 6); + prop_assert!((1..=6).contains(&l)); } } diff --git a/crates/formatrix-core/src/formats/djot.rs b/crates/formatrix-core/src/formats/djot.rs index bb930e3..6caf763 100644 --- a/crates/formatrix-core/src/formats/djot.rs +++ b/crates/formatrix-core/src/formats/djot.rs @@ -280,7 +280,7 @@ fn container_to_block( Container::Div { class } => { // Check if it's an admonition - let admonition = match class.as_ref() { + let admonition = match class { "note" => Some(AdmonitionType::Note), "tip" => Some(AdmonitionType::Tip), "warning" => Some(AdmonitionType::Warning), @@ -537,11 +537,11 @@ fn render_inline(output: &mut String, inline: &Inline) { } Inline::Strong { content } => { - output.push_str("*"); + output.push('*'); for i in content { render_inline(output, i); } - output.push_str("*"); + output.push('*'); } Inline::Strikethrough { content } => { @@ -602,9 +602,9 @@ fn render_inline(output: &mut String, inline: &Inline) { } Inline::RawInline { content, .. } => { - output.push_str("`"); + output.push('`'); output.push_str(content); - output.push_str("`"); + output.push('`'); } _ => {} diff --git a/crates/formatrix-core/src/formats/markdown.rs b/crates/formatrix-core/src/formats/markdown.rs index 8fc85b6..8ac3b95 100644 --- a/crates/formatrix-core/src/formats/markdown.rs +++ b/crates/formatrix-core/src/formats/markdown.rs @@ -159,29 +159,26 @@ fn parse_node<'a>(node: &'a AstNode<'a>) -> Option { let columns = Vec::new(); // Would need to extract from table alignments for child in node.children() { - match child.data.borrow().value { - NodeValue::TableRow(is_header) => { - let cells: Vec = child - .children() - .map(|cell| TableCell { - content: vec![Block::Paragraph { - content: parse_inlines(cell), - span: None, - }], - colspan: 1, - rowspan: 1, - alignment: None, - }) - .collect(); - - let row = TableRow { cells }; - if is_header { - header = Some(row); - } else { - body.push(row); - } + if let NodeValue::TableRow(is_header) = child.data.borrow().value { + let cells: Vec = child + .children() + .map(|cell| TableCell { + content: vec![Block::Paragraph { + content: parse_inlines(cell), + span: None, + }], + colspan: 1, + rowspan: 1, + alignment: None, + }) + .collect(); + + let row = TableRow { cells }; + if is_header { + header = Some(row); + } else { + body.push(row); } - _ => {} } } diff --git a/crates/formatrix-core/src/formats/orgmode.rs b/crates/formatrix-core/src/formats/orgmode.rs index 891d81f..862bd20 100644 --- a/crates/formatrix-core/src/formats/orgmode.rs +++ b/crates/formatrix-core/src/formats/orgmode.rs @@ -276,7 +276,7 @@ where use orgize::Event; let mut inlines = Vec::new(); - while let Some(event) = events.next() { + for event in events.by_ref() { match &event { Event::End(elem) if is_end_element(elem) => break, Event::Start(Element::Text { value }) | Event::End(Element::Text { value }) => { @@ -480,7 +480,7 @@ where I: Iterator>, { use orgize::Event; - while let Some(event) = events.next() { + for event in events.by_ref() { if matches!(event, Event::End(Element::TableRow(_))) { break; } @@ -531,11 +531,11 @@ where use orgize::Event; let mut text = String::new(); - while let Some(event) = events.next() { + for event in events.by_ref() { match event { Event::End(Element::TableCell(_)) => break, Event::Start(Element::Text { value }) | Event::End(Element::Text { value }) => { - text.push_str(&value); + text.push_str(value); } _ => {} } diff --git a/crates/formatrix-core/src/formats/rst.rs b/crates/formatrix-core/src/formats/rst.rs index 33b1a5b..b984d8b 100644 --- a/crates/formatrix-core/src/formats/rst.rs +++ b/crates/formatrix-core/src/formats/rst.rs @@ -142,13 +142,10 @@ fn convert_body_element(element: &BodyElement) -> Option { BodyElement::BlockQuote(bq) => { let mut inner_blocks = Vec::new(); for child in bq.children() { - match child { - document_tree::element_categories::SubBlockQuote::BodyElement(be) => { - if let Some(block) = convert_body_element(be) { - inner_blocks.push(block); - } + if let document_tree::element_categories::SubBlockQuote::BodyElement(be) = child { + if let Some(block) = convert_body_element(be) { + inner_blocks.push(block); } - _ => {} } } Some(Block::BlockQuote { @@ -163,18 +160,18 @@ fn convert_body_element(element: &BodyElement) -> Option { let items: Vec = bl .children() .iter() - .filter_map(|item| { + .map(|item| { let item_blocks: Vec = item .children() .iter() - .filter_map(|child| convert_body_element(child)) + .filter_map(convert_body_element) .collect(); - Some(ListItem { + ListItem { content: item_blocks, checked: None, marker: None, - }) + } }) .collect(); @@ -190,18 +187,18 @@ fn convert_body_element(element: &BodyElement) -> Option { let items: Vec = el .children() .iter() - .filter_map(|item| { + .map(|item| { let item_blocks: Vec = item .children() .iter() - .filter_map(|child| convert_body_element(child)) + .filter_map(convert_body_element) .collect(); - Some(ListItem { + ListItem { content: item_blocks, checked: None, marker: None, - }) + } }) .collect(); @@ -434,7 +431,7 @@ fn render_block(output: &mut String, block: &Block, _depth: usize) { 4 => '^', _ => '\'', }; - let len = content.iter().map(|i| inline_text_len(i)).sum::(); + let len = content.iter().map(inline_text_len).sum::(); output.push_str(&underline.to_string().repeat(len.max(1))); } @@ -583,7 +580,7 @@ fn render_inline(output: &mut String, inline: &Inline) { } Inline::LineBreak => { - output.push_str("\n"); + output.push('\n'); } Inline::SoftBreak => { diff --git a/crates/formatrix-core/src/formats/typst.rs b/crates/formatrix-core/src/formats/typst.rs index 54dce31..b25c94f 100644 --- a/crates/formatrix-core/src/formats/typst.rs +++ b/crates/formatrix-core/src/formats/typst.rs @@ -88,14 +88,14 @@ fn parse_syntax_tree(root: &SyntaxNode) -> Vec { } // Parse heading - if let Some(heading) = parse_heading(&child) { + if let Some(heading) = parse_heading(child) { blocks.push(heading); } } SyntaxKind::ListItem => { // Handle list items - if let Some(item) = parse_list_item(&child) { + if let Some(item) = parse_list_item(child) { // Check if we can append to existing list if let Some(Block::List { items, .. }) = blocks.last_mut() { items.push(item); @@ -111,7 +111,7 @@ fn parse_syntax_tree(root: &SyntaxNode) -> Vec { } SyntaxKind::EnumItem => { - if let Some(item) = parse_list_item(&child) { + if let Some(item) = parse_list_item(child) { if let Some(Block::List { kind: ListKind::Ordered, items, @@ -132,8 +132,8 @@ fn parse_syntax_tree(root: &SyntaxNode) -> Vec { SyntaxKind::Raw => { // Code block - let content = extract_raw_content(&child); - let language = extract_raw_language(&child); + let content = extract_raw_content(child); + let language = extract_raw_language(child); blocks.push(Block::CodeBlock { language, content, @@ -156,21 +156,21 @@ fn parse_syntax_tree(root: &SyntaxNode) -> Vec { } SyntaxKind::Strong => { - current_text.push_str(&format!("*{}*", extract_text(&child))); + current_text.push_str(&format!("*{}*", extract_text(child))); } SyntaxKind::Emph => { - current_text.push_str(&format!("_{}_", extract_text(&child))); + current_text.push_str(&format!("_{}_", extract_text(child))); } SyntaxKind::Link => { - let url = extract_text(&child); + let url = extract_text(child); current_text.push_str(&url); } SyntaxKind::Markup => { // Recurse into markup content - let inner_blocks = parse_syntax_tree(&child); + let inner_blocks = parse_syntax_tree(child); blocks.extend(inner_blocks); } diff --git a/crates/formatrix-core/tests/aspect_test.rs b/crates/formatrix-core/tests/aspect_test.rs index d530291..e5f7a25 100644 --- a/crates/formatrix-core/tests/aspect_test.rs +++ b/crates/formatrix-core/tests/aspect_test.rs @@ -3,9 +3,9 @@ //! Aspect and cross-cutting concern tests use formatrix_core::{ - ast::{Document, DocumentMeta, SourceFormat}, - traits::{Parser, ParseConfig}, + ast::{DocumentMeta, SourceFormat}, formats::PlainTextHandler, + traits::{ParseConfig, Parser}, }; /// Test handling of oversized documents (1MB+) @@ -18,7 +18,10 @@ fn test_large_document_handling() { let config = ParseConfig::default(); let result = parser.parse(&large_content, &config); - assert!(result.is_ok(), "should handle large documents without panic"); + assert!( + result.is_ok(), + "should handle large documents without panic" + ); } /// Test handling of extremely deep nesting @@ -34,7 +37,10 @@ fn test_deeply_nested_elements() { let config = ParseConfig::default(); let result = parser.parse(&nested, &config); - assert!(result.is_ok(), "should handle deep nesting without stack overflow"); + assert!( + result.is_ok(), + "should handle deep nesting without stack overflow" + ); } /// Test handling of null bytes (safety aspect) @@ -73,7 +79,10 @@ fn test_unicode_cjk_preservation() { let config = ParseConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - assert!(!doc.content.is_empty(), "CJK text should parse successfully"); + assert!( + !doc.content.is_empty(), + "CJK text should parse successfully" + ); } /// Test Unicode emoji preservation @@ -97,7 +106,10 @@ fn test_unicode_rtl_preservation() { let config = ParseConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - assert!(!doc.content.is_empty(), "RTL text should parse successfully"); + assert!( + !doc.content.is_empty(), + "RTL text should parse successfully" + ); } /// Test zero-width characters @@ -122,7 +134,11 @@ fn test_empty_document_no_panic() { let config = ParseConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - assert_eq!(doc.content.len(), 0, "empty input should produce empty content"); + assert_eq!( + doc.content.len(), + 0, + "empty input should produce empty content" + ); } /// Test single whitespace character @@ -171,7 +187,10 @@ fn test_consecutive_blank_lines() { let doc = parser.parse(input, &config).expect("parse failed"); // Should handle multiple blank lines gracefully - assert!(doc.content.len() <= 2, "consecutive blanks should not create extra blocks"); + assert!( + doc.content.len() <= 2, + "consecutive blanks should not create extra blocks" + ); } /// Test document with only punctuation @@ -216,9 +235,7 @@ fn test_document_metadata_large_values() { let large_title = "A".repeat(100_000); let meta = DocumentMeta { title: Some(large_title.clone()), - authors: (0..1000) - .map(|i| format!("Author {}", i)) - .collect(), + authors: (0..1000).map(|i| format!("Author {}", i)).collect(), ..Default::default() }; @@ -233,10 +250,9 @@ fn test_parse_config_large_options() { // Add many options for i in 0..1000 { - config.format_options.insert( - format!("option_{}", i), - format!("value_{}", i), - ); + config + .format_options + .insert(format!("option_{}", i), format!("value_{}", i)); } assert_eq!(config.format_options.len(), 1000); diff --git a/crates/formatrix-core/tests/e2e_test.rs b/crates/formatrix-core/tests/e2e_test.rs index e0fbfd8..21bbd7e 100644 --- a/crates/formatrix-core/tests/e2e_test.rs +++ b/crates/formatrix-core/tests/e2e_test.rs @@ -4,8 +4,8 @@ use formatrix_core::{ ast::{Block, Document, DocumentMeta, Inline, SourceFormat}, - traits::{Parser, ParseConfig, RenderConfig, Renderer}, formats::PlainTextHandler, + traits::{ParseConfig, Parser, RenderConfig, Renderer}, }; /// Test basic plaintext parsing @@ -49,8 +49,10 @@ fn test_plaintext_parse_multiple_paragraphs() { fn test_plaintext_preserve_raw_source() { let parser = PlainTextHandler::new(); let input = "Test content with\nmultiple lines."; - let mut config = ParseConfig::default(); - config.preserve_raw_source = true; + let config = ParseConfig { + preserve_raw_source: true, + ..Default::default() + }; let doc = parser.parse(input, &config).expect("parse failed"); assert!(doc.raw_source.is_some(), "raw_source should be preserved"); @@ -60,7 +62,6 @@ fn test_plaintext_preserve_raw_source() { /// Test plaintext render from AST #[test] fn test_plaintext_render_document() { - let parser = PlainTextHandler::new(); let renderer = PlainTextHandler::new(); let doc = Document { @@ -69,14 +70,12 @@ fn test_plaintext_render_document() { title: Some("Test Document".to_string()), ..Default::default() }, - content: vec![ - Block::Paragraph { - content: vec![Inline::Text { - content: "Hello, world!".to_string(), - }], - span: None, - }, - ], + content: vec![Block::Paragraph { + content: vec![Inline::Text { + content: "Hello, world!".to_string(), + }], + span: None, + }], raw_source: None, }; @@ -93,7 +92,11 @@ fn test_plaintext_empty_document() { let config = ParseConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - assert_eq!(doc.content.len(), 0, "empty input should produce empty content"); + assert_eq!( + doc.content.len(), + 0, + "empty input should produce empty content" + ); } /// Test document with only whitespace @@ -104,7 +107,11 @@ fn test_plaintext_whitespace_only() { let config = ParseConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - assert_eq!(doc.content.len(), 0, "whitespace-only input should produce no blocks"); + assert_eq!( + doc.content.len(), + 0, + "whitespace-only input should produce no blocks" + ); } /// Test round-trip: parse then render @@ -121,14 +128,22 @@ fn test_plaintext_round_trip() { let doc = parser.parse(input, &parse_config).expect("parse failed"); // Render - let output = renderer.render(&doc, &render_config).expect("render failed"); + let output = renderer + .render(&doc, &render_config) + .expect("render failed"); // Both should be valid (content-wise equivalent) assert!(!output.is_empty(), "round-trip output should not be empty"); // Re-parse the output - let doc2 = parser.parse(&output, &parse_config).expect("re-parse failed"); - assert_eq!(doc.content.len(), doc2.content.len(), "round-trip block count mismatch"); + let doc2 = parser + .parse(&output, &parse_config) + .expect("re-parse failed"); + assert_eq!( + doc.content.len(), + doc2.content.len(), + "round-trip block count mismatch" + ); } /// Test parser format identification @@ -210,14 +225,12 @@ fn test_document_clone() { title: Some("Original".to_string()), ..Default::default() }, - content: vec![ - Block::Paragraph { - content: vec![Inline::Text { - content: "Content".to_string(), - }], - span: None, - }, - ], + content: vec![Block::Paragraph { + content: vec![Inline::Text { + content: "Content".to_string(), + }], + span: None, + }], raw_source: Some("Raw".to_string()), }; @@ -240,10 +253,12 @@ fn test_parse_config_immutability() { /// Test render config customization #[test] fn test_render_config_customization() { - let mut config = RenderConfig::default(); - config.line_width = 120; - config.indent = "\t".to_string(); - config.hard_breaks = true; + let config = RenderConfig { + line_width: 120, + indent: "\t".to_string(), + hard_breaks: true, + ..Default::default() + }; assert_eq!(config.line_width, 120); assert_eq!(config.indent, "\t"); diff --git a/crates/formatrix-core/tests/property_test.rs b/crates/formatrix-core/tests/property_test.rs index 819fa38..9ad36c8 100644 --- a/crates/formatrix-core/tests/property_test.rs +++ b/crates/formatrix-core/tests/property_test.rs @@ -4,8 +4,8 @@ use formatrix_core::{ ast::{DocumentMeta, SourceFormat}, - traits::{Parser, ParseConfig, RenderConfig, Renderer}, formats::PlainTextHandler, + traits::{ParseConfig, Parser, RenderConfig, Renderer}, }; use proptest::prelude::*; @@ -37,10 +37,10 @@ proptest! { ) { let config = RenderConfig::default(); - // Line width should be positive or zero - prop_assert!(config.line_width >= 0); - // Indent should exist - prop_assert!(!config.indent.is_empty()); + prop_assert_eq!(config.line_width, 80); + prop_assert_eq!(config.indent, " "); + prop_assert!(!config.hard_breaks); + prop_assert!(config.format_options.is_empty()); } /// Property: Parse config format options preserve insertion order @@ -85,7 +85,7 @@ proptest! { let doc2 = parser.parse(&output, &parse_config).expect("reparse"); // Should have non-empty blocks - prop_assert!(doc2.content.len() > 0); + prop_assert!(!doc2.content.is_empty()); } } diff --git a/crates/formatrix-core/tests/unit_test.rs b/crates/formatrix-core/tests/unit_test.rs index 818ad49..6a9d1c9 100644 --- a/crates/formatrix-core/tests/unit_test.rs +++ b/crates/formatrix-core/tests/unit_test.rs @@ -3,9 +3,9 @@ //! Comprehensive unit tests for formatrix-core use formatrix_core::{ - ast::{Block, Document, DocumentMeta, Inline, SourceFormat, MetaValue}, - traits::{FormatHandler, Parser, ParseConfig, RenderConfig, Renderer}, + ast::{Block, Document, DocumentMeta, Inline, MetaValue, SourceFormat}, formats::PlainTextHandler, + traits::{FormatHandler, ParseConfig, Parser, RenderConfig, Renderer}, }; use std::collections::HashMap; @@ -38,8 +38,10 @@ fn test_parser_multiple_paragraphs() { fn test_parser_preserves_raw_source() { let parser = PlainTextHandler::new(); let input = "Test input"; - let mut config = ParseConfig::default(); - config.preserve_raw_source = true; + let config = ParseConfig { + preserve_raw_source: true, + ..Default::default() + }; let doc = parser.parse(input, &config).expect("parse failed"); assert!(doc.raw_source.is_some()); @@ -91,7 +93,9 @@ fn test_renderer_single_paragraph() { raw_source: None, }; - let output = renderer.render(&doc, &RenderConfig::default()).expect("render failed"); + let output = renderer + .render(&doc, &RenderConfig::default()) + .expect("render failed"); assert_eq!(output, "Hello world"); } @@ -118,7 +122,9 @@ fn test_renderer_multiple_paragraphs() { raw_source: None, }; - let output = renderer.render(&doc, &RenderConfig::default()).expect("render failed"); + let output = renderer + .render(&doc, &RenderConfig::default()) + .expect("render failed"); assert_eq!(output, "Para 1\n\nPara 2"); } @@ -132,7 +138,9 @@ fn test_renderer_empty_document() { raw_source: None, }; - let output = renderer.render(&doc, &RenderConfig::default()).expect("render failed"); + let output = renderer + .render(&doc, &RenderConfig::default()) + .expect("render failed"); assert_eq!(output, ""); } @@ -153,7 +161,9 @@ fn test_renderer_heading() { raw_source: None, }; - let output = renderer.render(&doc, &RenderConfig::default()).expect("render failed"); + let output = renderer + .render(&doc, &RenderConfig::default()) + .expect("render failed"); assert_eq!(output, "Title"); } @@ -177,7 +187,9 @@ fn test_roundtrip_simple() { let render_config = RenderConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - let output = renderer.render(&doc, &render_config).expect("render failed"); + let output = renderer + .render(&doc, &render_config) + .expect("render failed"); assert_eq!(output, input); } @@ -192,7 +204,9 @@ fn test_roundtrip_multiple_paragraphs() { let render_config = RenderConfig::default(); let doc = parser.parse(input, &config).expect("parse failed"); - let output = renderer.render(&doc, &render_config).expect("render failed"); + let output = renderer + .render(&doc, &render_config) + .expect("render failed"); assert_eq!(output, input); } @@ -340,11 +354,15 @@ fn test_parse_config_default() { #[test] fn test_parse_config_customization() { - let mut config = ParseConfig::default(); - config.preserve_spans = true; - config.preserve_raw_source = true; - config.front_matter_delimiter = Some("---".to_string()); - config.format_options.insert("key".to_string(), "value".to_string()); + let mut config = ParseConfig { + preserve_spans: true, + preserve_raw_source: true, + front_matter_delimiter: Some("---".to_string()), + ..Default::default() + }; + config + .format_options + .insert("key".to_string(), "value".to_string()); assert!(config.preserve_spans); assert!(config.preserve_raw_source); @@ -363,11 +381,15 @@ fn test_render_config_default() { #[test] fn test_render_config_customization() { - let mut config = RenderConfig::default(); - config.line_width = 120; - config.indent = "\t".to_string(); - config.hard_breaks = true; - config.format_options.insert("opt".to_string(), "val".to_string()); + let mut config = RenderConfig { + line_width: 120, + indent: "\t".to_string(), + hard_breaks: true, + ..Default::default() + }; + config + .format_options + .insert("opt".to_string(), "val".to_string()); assert_eq!(config.line_width, 120); assert_eq!(config.indent, "\t"); @@ -420,9 +442,9 @@ fn test_metavalue_integer() { #[test] fn test_metavalue_float() { - let val = MetaValue::Float(3.14); + let val = MetaValue::Float(1.25); match val { - MetaValue::Float(f) => assert!((f - 3.14).abs() < 0.01), + MetaValue::Float(f) => assert!((f - 1.25).abs() < f64::EPSILON), _ => panic!("expected float"), } } @@ -447,14 +469,7 @@ fn test_metavalue_list() { fn test_parse_never_panics_on_input() { let parser = PlainTextHandler::new(); let large_str = "A".repeat(100_000); - let malformed_inputs: Vec<&str> = vec![ - "", - " ", - "\n", - "\0", - &large_str, - "\u{FFFD}", - ]; + let malformed_inputs: Vec<&str> = vec!["", " ", "\n", "\0", &large_str, "\u{FFFD}"]; for input in malformed_inputs { let result = parser.parse(input, &ParseConfig::default()); diff --git a/crates/formatrix-gui/src/commands.rs b/crates/formatrix-gui/src/commands.rs index a8d5599..7da1bfc 100644 --- a/crates/formatrix-gui/src/commands.rs +++ b/crates/formatrix-gui/src/commands.rs @@ -178,8 +178,8 @@ pub struct ConversionResult { /// Load a document from the filesystem (synchronous — uses std::fs) pub fn load_document(path: String) -> Result { - let content = std::fs::read_to_string(&path) - .map_err(|e| format!("Failed to read file: {}", e))?; + let content = + std::fs::read_to_string(&path).map_err(|e| format!("Failed to read file: {}", e))?; // Detect format from extension let format = std::path::Path::new(&path) @@ -219,8 +219,7 @@ pub fn save_document( content: String, format: String, ) -> Result { - std::fs::write(&path, &content) - .map_err(|e| format!("Failed to write file: {}", e))?; + std::fs::write(&path, &content).map_err(|e| format!("Failed to write file: {}", e))?; let word_count = content.split_whitespace().count(); let char_count = content.chars().count(); From cb83bdc91384a19352e934bcbb46c2effb5745a0 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 29 Aug 2026 03:40:11 +0100 Subject: [PATCH 4/4] fix(gui): pin Gossamer native-link delivery --- Cargo.lock | 2 +- crates/formatrix-gui/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ad5e8e..54794d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,7 +1178,7 @@ dependencies = [ [[package]] name = "gossamer-rs" version = "0.1.0" -source = "git+https://github.com/metadatastician/gossamer.git?rev=4d360f9f02d96a5b17a0590af9595af61db153be#4d360f9f02d96a5b17a0590af9595af61db153be" +source = "git+https://github.com/metadatastician/gossamer.git?rev=73d8c077616777cdcd99a3c3eda50d5fa8865e2c#73d8c077616777cdcd99a3c3eda50d5fa8865e2c" dependencies = [ "serde", "serde_json", diff --git a/crates/formatrix-gui/Cargo.toml b/crates/formatrix-gui/Cargo.toml index fc1ac6a..68b394f 100644 --- a/crates/formatrix-gui/Cargo.toml +++ b/crates/formatrix-gui/Cargo.toml @@ -22,7 +22,7 @@ formatrix-core = { path = "../formatrix-core" } # A sibling checkout exists in the developer estate but not on CI runners or in # published source archives. Pin the actual upstream repository so this # workspace is self-resolving and reproducible outside that local layout. -gossamer-rs = { git = "https://github.com/metadatastician/gossamer.git", rev = "4d360f9f02d96a5b17a0590af9595af61db153be" } +gossamer-rs = { git = "https://github.com/metadatastician/gossamer.git", rev = "73d8c077616777cdcd99a3c3eda50d5fa8865e2c" } serde.workspace = true serde_json.workspace = true