aboutsummaryrefslogtreecommitdiff
path: root/docs/reviews/library-selection.md
diff options
context:
space:
mode:
authorrottedfm <rottedfm@proton.me>2026-08-19 11:21:47 -0400
committerrottedfm <rottedfm@proton.me>2026-08-19 11:21:47 -0400
commitea0bd36167b684c0accdb5ce2b2e21b8d84aeb25 (patch)
treebe1267972b5de2f1ae592577dfabce67f1fe6e87 /docs/reviews/library-selection.md
parent8c0b4c53b130555f040884c1f52b90f16b23e241 (diff)
feat: implement Helix-style modal buffer under DO-178C DAL-C
The repository was an unmodified ratatui component template: no editor code, JSON5 config, and placeholder widgets. This establishes the first working baseline — `moji <file>` opens a file into a ropey rope and edits it with Helix selection-first semantics. Requirements, implementation and tests land together because they must: the traceability check rejects requirements with no implementation and tests naming requirements that do not exist, so neither half is a valid commit on its own. Package renamed to mojibake-editor (mojibake was taken on crates.io); binary is moji, library target stays mojibake. Class: New behaviour Requirements: MJB-HLR-001..019, MJB-LLR-001..205 Derived: MJB-DR-001..007 (DR-001 resolved, six open for review) Verified: cargo build; clippy --all-targets -D warnings clean; cargo test 294 passing; ./scripts/check-trace.sh 98/98/98; cargo package clean Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'docs/reviews/library-selection.md')
-rw-r--r--docs/reviews/library-selection.md98
1 files changed, 98 insertions, 0 deletions
diff --git a/docs/reviews/library-selection.md b/docs/reviews/library-selection.md
new file mode 100644
index 0000000..3b9efa2
--- /dev/null
+++ b/docs/reviews/library-selection.md
@@ -0,0 +1,98 @@
+# Library selection record
+
+Software Level: **DAL-C** (DO-178C)
+Reviewed by: author (independence not required at DAL-C)
+
+Records the non-obvious dependency choices for the buffer component and the
+reasoning behind each, so a reviewer can judge them without re-deriving the
+trade-offs.
+
+---
+
+## ropey 2.0.0-beta.1 — a pre-release dependency
+
+**Decision.** ropey 2.0.0-beta.1, default features, **byte-indexed**.
+`metric_chars` is deliberately **not** enabled.
+
+**Alternatives considered.**
+
+| Option | Assessment |
+|---|---|
+| ropey 1.6.1 (stable) | Char-indexed by default; exactly what Helix pins, so Helix's algorithms port near-verbatim. The conservative choice. |
+| 2.0.0-beta.1 + `metric_chars` | Keeps the char API, but re-adds the per-node metadata 2.0 exists to remove — the beta risk without the benefit. |
+| **2.0.0-beta.1, byte-indexed (selected)** | Lowest memory and fastest edits; requires translating Helix's algorithms into byte offsets rather than porting them. |
+
+**Known risk.** The crate is self-described as "not battle-tested like Ropey
+1.x", with minor breaking API changes possible before release. This was raised
+before implementation and the requester selected it anyway; the choice is
+theirs and is recorded here rather than re-litigated.
+
+**Mitigations in force.**
+
+1. The version is pinned exactly in `Cargo.toml`.
+2. `ropey::Rope` is *owned* only by `src/buffer/document.rs`; other modules
+ receive `RopeSlice` parameters. Replacing the rope is therefore confined to
+ one module. (MJB-DR-006)
+3. The buffer core carries 93.85% statement coverage against invariants we
+ assert ourselves, rather than trusting the library's own guarantees.
+
+**Consequence the reviewer must accept.** Byte indexing admits a failure mode
+char indexing cannot express: an offset landing inside a character, which makes
+`Rope::insert`/`remove` panic. This is addressed by MJB-LLR-011 (clamp and snap
+at construction) and MJB-LLR-044 (validate before mutating). See MJB-DR-002.
+
+---
+
+## encoding_rs — asymmetric, and the asymmetry matters
+
+**Decision.** Used for decoding all encodings and for encoding everything
+*except* UTF-16, which is encoded by hand.
+
+**Why.** `encoding_rs::Encoding::encode` will not encode to UTF-16; it silently
+substitutes UTF-8 and signals the substitution only through a return value that
+is easy to discard. Delegating the save path to it wrote UTF-8 bytes beneath a
+UTF-16 byte order mark — a file inconsistent with its own BOM.
+
+This was **not caught by inspection**. It was caught by a round-trip test
+(`mjb_llr_115_utf16le_round_trips`) that decoded, re-encoded, and compared
+bytes. Any encoding added later should be guarded by the same test shape.
+
+Recorded as MJB-DR-007.
+
+---
+
+## unicode-segmentation and unicode-width
+
+**Decision.** `unicode_segmentation::GraphemeCursor` for cluster boundaries;
+`unicode_width` for terminal display width.
+
+**Why.** `GraphemeCursor` is chunk-aware: it reports `PrevChunk`/`NextChunk`/
+`PreContext` when a cluster straddles a fragment edge, which pairs exactly with
+ropey's `chunk(byte_idx) -> (&str, chunk_start)`. Clusters spanning rope chunk
+boundaries therefore resolve correctly (MJB-LLR-022) without materialising the
+text. This is the same pairing Helix uses.
+
+`unicode_width` is required because byte length, character count, and display
+width are three different numbers; the cursor must track the third. A CJK
+character occupies two terminal columns and three UTF-8 bytes.
+
+---
+
+## thiserror
+
+**Decision.** Used for error types in the buffer core.
+
+**Why.** MJB-HLR-018 forbids abnormal termination, so every failure path must
+carry a reportable message. `thiserror` generates `Display` and `From` without
+runtime cost or a dynamic error type.
+
+---
+
+## config, with default features disabled
+
+**Decision.** `config = { default-features = false, features = ["toml"] }`.
+
+**Why.** MJB-HLR-014 requires that no parser for another format remain in the
+dependency graph. Removing the `json5` *call site* would not satisfy that —
+the crate's default features pull `json5` in regardless. Disabling default
+features is what actually removes it, and is verifiable with `cargo tree`.