Skip to content

Round-trip guarantees

Imported mode exists so that scholars can edit an existing edition without disturbing the parts they did not touch. The project treats this as a guarantee, not a best effort.

The user’s file is privileged. Reading it must never change it.

  • Unedited content is emitted byte-for-byte. The importer records the source span of every tag, and the patcher splices edits into the original bytes.
  • Whitespace-only or otherwise non-semantic DSL changes produce no change in the XML.
  • Invalid DSL is an error, never an empty document. Save and export refuse to write a failed compile.
  • Confirmed lemmas are written into the words’ start tags, but only where they differ from what the tag already says with references resolved. Lemmas loaded from the file leave it byte-identical.
  • The XML and the DSL generated from it read as the same tokens, one to one. DSL → XML → DSL likewise.
  • A new XML construct that the DSL cannot express breaks this correspondence: it must be given DSL syntax rather than special-cased in the patcher.

Word identity is preserved across edits.

  • Lemmas and annotations are stored by word number.
  • The backend tracks which token is which across edits in both modes, and each compile returns a renumbering report: where the previous compile’s words went.
  • The frontend applies the renumbering before the next compile gathers its payload, so annotations sent to the backend are always numbered by its previous compile.
  • Guidance from the editor’s edit history lets untouched words keep their identity outright; only edited ranges are matched by comparison. Without guidance (or if it is out of step) the patcher compares whole documents, and the output must be correct either way.
  • A word edited slightly — a typo fix, changing at most half its letters — keeps its lemma, analysis and hand-curated normalized form. Its diplomatic form follows the edit.
  • A different word typed in its place is a new word, written in the same place; it does not inherit the old word’s lemma.
  • Confirming a lemma writes lemma and me:msa (or msa) on the word. On a MENOTA file, changed normalized forms are written into <me:norm>.
  • Notes and other annotations stay in the project, because they would add markup the file does not have.
  • A paragraph can be split by adding .p and joined by removing one. A split has to fall between elements such as <s>, not inside one; otherwise the change is reported and not written.

The guarantees are enforced by regression tests in src-tauri/src/importer/tests.rs, including:

  • test_menota_file_unchanged_is_byte_identical — a full MENOTA manuscript read and written without edits is identical.
  • test_menota_file_with_its_own_lemmas_stays_byte_identical — lemmas already in the file survive without rewriting.
  • test_menota_file_xml_and_dsl_read_as_the_same_tokens — the XML and its DSL describe the same tokens.
  • test_guided_character_edits_always_reimport_as_edited — a fuzz test of guided edits, checking that the output is correct with or without guidance.

The full-manuscript equivalence test is slow and runs with HOLMPERG=1 bun run test:js in addition to the normal frontend tests.

If you change how imported XML is written as DSL, changes users’ stored DSL: bump DSL_RULES (src-tauri/src/importer/tei/segments.rs) and keep older documents on their rules.

Parse TEI documents with crate::xml::parse, never libxml2 directly. It declares the document’s entities first and disables network access. CI tests on Ubuntu’s libxml2 2.9 while local machines usually have 2.14 or newer, so entity-handling bugs may show up in only one of them.

If reading an imported file and saving it without edits changes the file, that is a bug. Please include the file (or a minimal excerpt), the version of Saga-Scribe, and the relevant log entries. See Logs and troubleshooting.