Keyboard ←/→ keep their place — a sticky ordinal that outlives the clamp

Lateral card navigation was pure geometry: the nearest drawn frame in the
direction. That loses the walk in the card's own title — stepping from a
10-card lane's 8th card into a 3-card lane clamps to its 3rd, and coming
back out, "the nearest frame at that height" is the 3rd card's height. The
information the user was walking at stopped being on screen, so no rule
over rectangles could have recovered it.

So it is remembered instead. `TransientBoardState.lateralOrdinal` holds the
1-based position a run of ←/→ started from, counted over the cards the board
is showing, and `NavigationMath.lateralHop` lands each hop on
`min(ordinal, target lane's count)` of the next lane that is showing cards —
collapsed and query-emptied lanes hopped over on `firstCard`'s rule rather
than by the accident of registering no frames. 8th → 3rd → 8th.

Every reset comes from one funnel and needs no enumeration anywhere: the
ordinal is a defaulted `nil` parameter on `select`, so a click, a marquee, a
↑/↓ step, an ⌥-jump, a ⌫ successor, a lane-domain arrow and the reload's
focus recovery all end the run by saying nothing. `resolve` adds the one
rule a value referencing no item can need — the ordinal never outlives the
head it was counted from — while a reload that leaves the cursor standing
leaves the run standing too.

A wide lane's interior masonry columns keep their spatial step and carry the
ordinal through untouched: a column hop is not a lane hop, and stickiness is
lane-granular over the logical order. With no lane in the direction the
geometry has the last word, which is how → still reaches the shown trash.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-08 23:28:05 -04:00
parent eb950ee881
commit f9f284cac9
6 changed files with 552 additions and 13 deletions
+366
View File
@@ -24,6 +24,9 @@ import Testing
private enum More {
static let card5 = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
static let card6 = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
/// A card an agent files mid-navigation a reload that changes the board without disturbing the
/// cursor, which the sticky ordinal's survival rule needs.
static let filed = "cccccccc-cccc-4ccc-8ccc-cccccccccccc"
}
private let lane1 = ItemID(rawValue: Ident.lane1)
@@ -226,6 +229,369 @@ struct NavigationMathTests {
}
}
// MARK: - The sticky ordinal
/// A UUID-shaped folder name for the walk board `(lane, position)`, with position `0` naming the
/// lane itself, so ten cards in a lane cost no ten literals.
private func name(_ lane: Int, _ position: Int) -> String {
String(format: "%08x-0000-4000-8000-%012x", lane, position)
}
private func walkID(_ lane: Int, _ position: Int) -> ItemID {
ItemID(rawValue: name(lane, position))
}
/// **The card's own board**: ten cards, three, ten. The middle lane is what a walk has to be clamped
/// by, and the third is what proves the clamp was not remembered.
///
/// The third lane is **two units wide**, so its cards lay out across interior masonry columns the
/// case the ruling settles by saying interior position "does not participate". Nothing here can see
/// that: the hop takes no frames, which is the enforcement.
///
/// Every card's body carries two tokens the filter suites query for: `even`/`odd` by position, and
/// `keeplane` on the outer lanes only so one query narrows all three lanes and another empties the
/// middle one entirely.
@MainActor
private func makeWalk() throws -> WriterFixture {
let fixture = try WriterFixture()
try fixture.board()
for (lane, count) in [(1, 10), (2, 3), (3, 10)] {
try fixture.lane(
name(lane, 0),
order: "\(lane * 1024)",
title: "Lane \(lane)",
width: lane == 3 ? 2 : nil
)
for position in 1...count {
try fixture.card(
name(lane, position),
in: name(lane, 0),
order: "\(position * 1024)",
title: "L\(lane)C\(position)",
body: "\(position.isMultiple(of: 2) ? "even" : "odd") \(lane == 2 ? "" : "keeplane")"
)
}
}
return fixture
}
@Suite("NavigationMath ▸ the sticky ordinal")
@MainActor
struct LateralHopTests {
@Test("10-3-10: the 8th card walks out through the 3rd and back to the 8th")
func roundTrip() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
// Out: clamped to the short lane's last card, and still carrying the 8 it started with
// the clamp is a landing, never a new position to remember.
let out = try #require(NavigationMath.lateralHop(from: walkID(1, 8), .right, scanning: lanes))
#expect(out.target == walkID(2, 3))
#expect(out.ordinal == 8)
// Onward: the wide lane's 8th in *logical* order, whichever masonry column drew it.
let onward = try #require(
NavigationMath.lateralHop(from: out.target, .right, scanning: lanes, sticky: out.ordinal)
)
#expect(onward.target == walkID(3, 8))
#expect(onward.ordinal == 8)
// And back the way it came, which is the card's title in full.
let returning = try #require(
NavigationMath.lateralHop(from: onward.target, .left, scanning: lanes, sticky: onward.ordinal)
)
#expect(returning.target == walkID(2, 3))
let home = try #require(
NavigationMath.lateralHop(from: returning.target, .left, scanning: lanes, sticky: returning.ordinal)
)
#expect(home.target == walkID(1, 8), "the 8th again — the whole point")
}
@Test("Without a run in flight the hop starts one at the origin's own position")
func noStickyStartsARun() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
// This is what a *reset* leaves behind, and it is also the failure the sticky exists to fix:
// starting a fresh run from the clamped 3rd card lands on the third, not the eighth.
let fresh = try #require(NavigationMath.lateralHop(from: walkID(2, 3), .right, scanning: lanes))
#expect(fresh.target == walkID(3, 3))
#expect(fresh.ordinal == 3)
let first = try #require(NavigationMath.lateralHop(from: walkID(1, 4), .right, scanning: lanes))
#expect(first.ordinal == 4, "the origin's 1-based position in its lane")
}
@Test("The clamp is the target lane's count, and a lane of one takes everything")
func clampsToTheShortLane() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
for ordinal in 3...10 {
let hop = try #require(
NavigationMath.lateralHop(from: walkID(1, ordinal), .right, scanning: lanes, sticky: ordinal)
)
#expect(hop.target == walkID(2, 3), "everything past the 3rd lands on the 3rd")
#expect(hop.ordinal == ordinal)
}
// Under the *filter* the middle lane shows one card, and the clamp follows what is showing
// rather than what is on disk.
let narrowed = try #require(
NavigationMath.lateralHop(
from: walkID(1, 8), .right, scanning: lanes, filter: SearchFilter(query: "even"), sticky: 4
)
)
#expect(narrowed.target == walkID(2, 2))
}
@Test("The ordinal counts the cards the query left showing, not the cards on disk")
func countsTheFilteredLane() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
let evens = SearchFilter(query: "even")
// Lane 1 shows 2,4,6,8,10 so card 8 is the *4th* thing the user can see, and starting a run
// there carries 4 rather than 8.
let out = try #require(
NavigationMath.lateralHop(from: walkID(1, 8), .right, scanning: lanes, filter: evens)
)
#expect(out.ordinal == 4)
#expect(out.target == walkID(2, 2), "the middle lane shows one card; 4 clamps onto it")
let onward = try #require(
NavigationMath.lateralHop(
from: out.target, .right, scanning: lanes, filter: evens, sticky: out.ordinal
)
)
#expect(onward.target == walkID(3, 8), "the 4th showing card of the far lane is card 8")
}
@Test("A lane the query emptied is hopped straight over, exactly as an absent one would be")
func skipsAQueryEmptiedLane() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
// `keeplane` is on lanes 1 and 3 only: the middle lane shows nothing, so it is not a
// destination and one press crosses it.
let hop = try #require(
NavigationMath.lateralHop(
from: walkID(1, 8), .right, scanning: lanes, filter: SearchFilter(query: "keeplane")
)
)
#expect(hop.target == walkID(3, 8))
#expect(hop.ordinal == 8)
}
@Test("A collapsed lane is hopped over too, and its cards are never a landing")
func skipsACollapsedLane() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
// 03-board-ui.md § Lane Collapsed lanes: the slim strip lays out no card faces, so a hop
// that landed in there would select something drawn nowhere.
try fixture.item(name(2, 0), "---\nschema: 1\ntitle: Lane 2\norder: 2048\ncollapsed: true\n---\n\n")
let lanes = try fixture.snapshot().lanes
let hop = try #require(NavigationMath.lateralHop(from: walkID(1, 8), .right, scanning: lanes))
#expect(hop.target == walkID(3, 8), "one press, straight past the strip")
#expect(hop.ordinal == 8)
// And back: the strip is no more a destination from the right than from the left.
let back = try #require(
NavigationMath.lateralHop(from: hop.target, .left, scanning: lanes, sticky: hop.ordinal)
)
#expect(back.target == walkID(1, 8))
// Folding the *last* lane leaves nothing to the right at all the handler's cue to fall back
// to geometry, which is how the shown trash stays reachable by .
try fixture.item(name(3, 0), "---\nschema: 1\ntitle: Lane 3\norder: 3072\ncollapsed: true\n---\n\n")
#expect(
NavigationMath.lateralHop(from: walkID(1, 8), .right, scanning: try fixture.snapshot().lanes) == nil
)
}
@Test("The ends, the vertical directions and an origin the board is not showing are all nil")
func refusals() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let lanes = try fixture.snapshot().lanes
#expect(NavigationMath.lateralHop(from: walkID(1, 1), .left, scanning: lanes) == nil)
#expect(NavigationMath.lateralHop(from: walkID(3, 1), .right, scanning: lanes) == nil)
#expect(
NavigationMath.lateralHop(from: walkID(1, 8), .down, scanning: lanes) == nil,
"↑/↓ are not a lateral run and must never consume the ordinal"
)
#expect(NavigationMath.lateralHop(from: walkID(1, 8), .up, scanning: lanes) == nil)
#expect(NavigationMath.lateralHop(from: card1, .right, scanning: lanes) == nil, "not on this board")
#expect(
NavigationMath.lateralHop(
from: walkID(1, 1), .right, scanning: lanes, filter: SearchFilter(query: "even")
) == nil,
"an origin the query hid is in no showing lane, so there is no position to carry"
)
}
}
// MARK: - The sticky ordinal's resets
@Suite("TransientBoardState ▸ the lateral run's resets")
@MainActor
struct LateralOrdinalResetTests {
@Test("Only a lateral hop stores one — every other selection change is a reset")
func selectResets() {
let state = TransientBoardState()
state.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
#expect(state.lateralOrdinal == 8)
// Verbatim `BoardView.replaceSelection`, which is what /, the -jumps and the lane domain
// all land through no ordinal passed, so the run ends.
state.select([card2], in: .board, anchor: card2, head: card2)
#expect(state.lateralOrdinal == nil, "a vertical step ends the run")
state.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
state.select([card1, card2], in: .board, anchor: card1, head: card2)
#expect(state.lateralOrdinal == nil, "so does a ⇧-arrow's extension")
state.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
state.select([card1, card2], in: .board, anchor: nil, head: nil, defaultsSoleMember: false)
#expect(state.lateralOrdinal == nil, "and so does a rubber band")
state.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
state.clearSelection()
#expect(state.lateralOrdinal == nil, "Escape leaves no run to be in the middle of")
}
@Test("A click ends the run, whatever it selects")
func clickResets() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
store.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
store.click(SelectionTarget(id: card3, kind: .card, container: .board), modifier: .plain)
#expect(store.selection.ids == [card3])
#expect(store.transient.lateralOrdinal == nil)
// A -click's toggle and a -click's range go through the same door, so they reset too.
store.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
store.click(SelectionTarget(id: card3, kind: .card, container: .board), modifier: .shift)
#expect(store.transient.lateralOrdinal == nil)
}
@Test("A ⌫ successor ends it, because a run's cursor was just deleted out from under it")
func deleteResets() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
store.select([card2], in: .board, anchor: card2, head: card2, lateralOrdinal: 8)
store.deleteSelection()
#expect(store.selection.ids == [card3])
#expect(store.transient.lateralOrdinal == nil)
}
@Test("A reload keeps the run while the cursor stands, and drops it when the cursor goes")
func reloadKeepsThenDrops() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
store.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
// An agent filing a card is not the user changing their mind: the head still names a card, so
// the run survives untouched.
try fixture.item("\(Ident.lane1)/\(More.filed)", Item.rich(order: "5120", title: "Filed"))
store.handleWatcherEvent(.treeChanged(.foreign))
await store.awaitQuiescence()
#expect(store.transient.selectionHead == card1)
#expect(store.transient.lateralOrdinal == 8)
// The cursor's own card vanishing is the other case, and the ordinal goes with it. What lands
// afterwards is 10-accessibility.md's vanishing-focus recovery (`SelectionHeadTests` pins it):
// the selection had emptied, so focus falls to the card's lane and takes the head with it
// through `select`, which is exactly the door that ends a run.
try FileManager.default.removeItem(at: fixture.url("\(Ident.lane1)/\(Ident.card1)"))
store.handleWatcherEvent(.treeChanged(.foreign))
await store.awaitQuiescence()
#expect(store.transient.selectionHead == lane1)
#expect(store.transient.lateralOrdinal == nil)
}
@Test("A vanished cursor takes the ordinal with it, before any recovery gets a say")
func resolveDropsTheOrdinalWithTheHead() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let state = TransientBoardState()
state.select([card1, card2], in: .board, anchor: card1, head: card2, lateralOrdinal: 8)
// The head's card goes and the selection does not: so it is the head rule doing this, not
// `clearSelection` sweeping everything out on the way past.
try FileManager.default.removeItem(at: fixture.url("\(Ident.lane1)/\(Ident.card2)"))
state.resolve(against: try load(fixture))
#expect(state.selection.ids == [card1])
#expect(state.selectionHead == nil)
#expect(state.lateralOrdinal == nil)
}
@Test("A query that hides the cursor ends the run, because the ordinal counts what is showing")
func hidingTheCursorResets() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
store.select([card1], in: .board, anchor: card1, head: card1, lateralOrdinal: 8)
store.searchQuery = "Second"
#expect(store.selection.isEmpty)
#expect(store.transient.selectionHead == nil)
#expect(store.transient.lateralOrdinal == nil)
}
@Test("The store's walk: hop, store, hop again — 10-3-10 through the selection itself")
func theWalkThroughTheStore() throws {
let fixture = try makeWalk()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
// Exactly what `BoardView.step` does on a lane hop, three presses running.
func press(_ direction: NavigationMath.Direction) throws {
let head = try #require(store.transient.selectionHead)
let hop = try #require(
NavigationMath.lateralHop(
from: head,
direction,
scanning: store.snapshot.lanes,
filter: store.searchFilter,
sticky: store.transient.lateralOrdinal
)
)
store.select(
[hop.target], in: .board, anchor: hop.target, head: hop.target, lateralOrdinal: hop.ordinal
)
}
store.select([walkID(1, 8)], in: .board)
#expect(store.transient.lateralOrdinal == nil, "a fresh selection is not a run")
try press(.right)
#expect(store.selection.ids == [walkID(2, 3)])
try press(.right)
#expect(store.selection.ids == [walkID(3, 8)])
try press(.left)
try press(.left)
#expect(store.selection.ids == [walkID(1, 8)], "back where the walk began")
}
}
// MARK: - The registry the arrows and the band read
/// The registry is the one piece of the marquee/arrow pair that is *written* from a view every card