escriba-memori 0.1.26

memori (目盛) — the positioning vocabulary: scale-tagged offsets, endpoint bounds, and freshness. A leaf crate with zero dependencies, so every layer that manipulates a position can depend on it.
Documentation

memori (目盛) — the graduation marks an offset was counted in.

The problem this vocabulary corners

An offset into text is a usize, and a usize says nothing about which ruler it was measured on. escriba measures on three, and they disagree on every non-ASCII character:

scale "héllo" end who demands it
bytes 6 regex, every &str index
chars 5 escriba's own Position/match offsets
UTF-16 units 5 LSP, and anything speaking to an editor client

Passing one where another is expected compiles perfectly and is wrong only for users with non-ASCII text — the worst possible failure profile, because it survives every test written in English.

Five distinct bugs in one session were this class. That is the signal a primitive is announcing itself rather than five tasks:

  1. a commit stepping from the wrong anchor,
  2. saturating_sub(1) making offset 0 unreachable,
  3. match offsets used against text that had since been edited,
  4. byte/char/UTF-16 conversion correct only because one function guarded it,
  5. an exclusive-vs-inclusive endpoint chosen by picking a function name.

The three axes, made orthogonal

  • Scale — [Offset<S>] is phantom-tagged, so Offset<Bytes> and Offset<Chars> are different types and mixing them is a compile error. Conversion is possible only through a [Ruler], which cannot exist without the text it measures.
  • Bound — [Bound] makes "does the endpoint count itself?" a value the caller states, not a function name they must pick correctly, and not an arithmetic fudge at the call site.
  • Freshness — [Anchored] carries the [EditGen] an offset was computed against, so using it after an edit is a None rather than a silently wrong column.

Tier honesty

Scale-mixing is truly unrepresentable (a type error, E0308). Bound and freshness are parse-time-rejected at this border — you can still build a Ruler for the wrong text. The full ledger is in docs/memori.md.

The (defmemori …) tatara-lisp surface is a NAMED FOLLOW-UP, not shipped. This crate is the typed Rust border only.

Why a leaf crate

It has no dependencies, deliberately. escriba-core imports escriba_search::Direction, so core depends on SEARCH — and a positioning primitive living in core would be invisible to the search engine, which is where the step/step_inclusive twins [Bound] exists to replace live. The vocabulary has to sit below both, so it does.