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:
@@ -46,11 +46,16 @@ extension FrontmatterDocument {
|
||||
read(FrontmatterKeys.schema) { value, _ in if case let .int(value) = value { value } else { nil } }
|
||||
}
|
||||
|
||||
/// A non-finite reading (`.nan`, `.inf`) has no place in the total order the tie-break and
|
||||
/// midpoint math assume (01-storage-format.md § Frontmatter, settled) — it is the same loud
|
||||
/// malformed-input rejection as a non-numeric value, not a `.valid(Double.nan)` silently
|
||||
/// poisoning every comparison downstream. An `Int` reading is always finite, so only the
|
||||
/// `.double` case needs the check.
|
||||
public var order: FieldValue<Double> {
|
||||
read(FrontmatterKeys.order) { value, _ in
|
||||
switch value {
|
||||
case let .int(value): Double(value)
|
||||
case let .double(value): value
|
||||
case let .double(value): value.isFinite ? value : nil
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
@@ -67,13 +72,16 @@ extension FrontmatterDocument {
|
||||
public var icon: FieldValue<String> { read(FrontmatterKeys.icon, Self.string) }
|
||||
public var iconColor: FieldValue<String> { read(FrontmatterKeys.iconColor, Self.string) }
|
||||
|
||||
/// Width multiplier. An int stays; a string or double with an exact integer reading ≥ 1
|
||||
/// coerces (`"2"`, `2.0` → `2`). Everything else — zero, negative, fractional, non-numeric,
|
||||
/// bool, a sequence/mapping — is malformed and renders as the default 1.
|
||||
/// Width multiplier. An exact-integer reading — from an int, a double, or a numeric string —
|
||||
/// always coerces: at or above 1 to itself (`"2"`, `2.0` → `2`), below 1 to 1 (**ranges are
|
||||
/// part of the sensible reading**, 01-storage-format.md § Frontmatter, settled — the table's
|
||||
/// "≥ 1" is a validity bound on the coerced reading, not a gate on which readings are
|
||||
/// sensible). Everything else — fractional, non-numeric, bool, a sequence/mapping — has no
|
||||
/// integer reading at all and is malformed, rendering as the default 1.
|
||||
public var width: FieldValue<Int> {
|
||||
read(FrontmatterKeys.width) { value, _ in
|
||||
switch value {
|
||||
case let .int(value): value >= 1 ? value : nil
|
||||
case let .int(value): value >= 1 ? value : 1
|
||||
case let .double(value): Self.exactIntWidth(value)
|
||||
case let .string(text): Double(text).flatMap(Self.exactIntWidth)
|
||||
default: nil
|
||||
@@ -109,11 +117,14 @@ extension FrontmatterDocument {
|
||||
}
|
||||
}
|
||||
|
||||
/// An integer-valued double or numeric string ≥ 1 coerces; anything else (fractional,
|
||||
/// non-numeric, out of `Int` range) has no sensible width reading.
|
||||
/// An integer-valued double or numeric string coerces — below 1 to 1, at or above 1 to the
|
||||
/// value itself; a fractional reading, a non-numeric one, or one outside `Int` range on the
|
||||
/// high end has no sensible width reading at all. The high-end guard is what makes `Int(value)`
|
||||
/// safe below; there is no matching low-end guard because anything below 1 short-circuits to
|
||||
/// the literal `1` without ever converting the (possibly enormous negative) double to `Int`.
|
||||
private static func exactIntWidth(_ value: Double) -> Int? {
|
||||
guard value.truncatingRemainder(dividingBy: 1) == 0, value >= 1, value <= Double(Int.max) else { return nil }
|
||||
return Int(value)
|
||||
guard value.truncatingRemainder(dividingBy: 1) == 0, value <= Double(Int.max) else { return nil }
|
||||
return value >= 1 ? Int(value) : 1
|
||||
}
|
||||
|
||||
/// A quoted timestamp reads the same as an unquoted one — same YAML 1.1 timestamp grammar,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user