Realign read-side rules — width range coercion, finite order, symlink pins

The design corpus ratified that ranges are part of a sensible reading:
an exact-integer width below 1 now coerces to 1 read-side (bytes
untouched) instead of reading as malformed — the width division must
never see a zero or negative unit — while a non-finite order (.nan,
.inf) is now the same loud malformed-order rejection as a non-numeric
one, guarded at the single point where the double arrives so loader
and Writer inherit it together. The symlink-never-traversed rule
turned out to be already enforced (the loader has filtered symlinks
ahead of the directory check since the first commit); it and the
copy-preserves-the-link-verbatim behavior are now pinned by tests,
alongside the two hostile shapes the corpus names (width: 0,
order: .nan). Five new tests.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-27 14:21:03 -04:00
parent b4c90838b4
commit bea6d02d1d
6 changed files with 155 additions and 27 deletions
+9 -7
View File
@@ -50,13 +50,15 @@ enum LaneLayoutMath {
/// The whole units a lane spans on screen: its `width` when that read as a valid integer, 1
/// otherwise.
///
/// `Lane.width` is a **lenient** field (01-storage-format.md § Frontmatter): a missing key, a
/// non-numeric value, a fraction, a zero or a negative all arrive here as `.missing` or
/// `.malformed` and render as one unit the bytes on disk are left exactly as the author wrote
/// them until the user actually changes the width, at which point the Writer replaces them with
/// an integer (`BoardStore.setLaneWidth`). The `max(1,)` is belt over braces: the read side
/// already refuses anything below 1, and this function is the single place the rest of the UI
/// asks "how many units does this lane span".
/// `Lane.width` is a **lenient** field (01-storage-format.md § Frontmatter): a missing key or
/// a non-numeric/fractional value arrives here as `.missing` or `.malformed` and renders as
/// one unit, while an exact-integer reading below 1 (zero, negative) is no longer malformed at
/// all it coerces to 1 at the read side (**ranges are part of the sensible reading**,
/// settled). Either way the bytes on disk are left exactly as the author wrote them until the
/// user actually changes the width, at which point the Writer replaces them with an integer
/// (`BoardStore.setLaneWidth`). The `max(1,)` is belt over braces: the read side already never
/// produces anything below 1, and this function is the single place the rest of the UI asks
/// "how many units does this lane span".
static func displayUnits(of lane: Lane) -> Int {
max(1, lane.width.value ?? 1)
}