Realign the width and lane-move commands — three fresh settlements
DESIGN edits landing from the parallel session, honored: - Caret chords yield to any focused text control (04 ▸ Grammar): Move Left/Right ⌘←/⌘→ and the width pair ⌥⌘←/⌥⌘→ now disable while the board popover is open — its fields are the one non-inline text surface a board window has today; the search field and card-window fields extend the rule with their own cards. - The width pair batches over a multi-lane selection (03 ▸ Lane, the styling precedent): each selected lane steps one unit through one bracket (stepLaneWidths); floor members hold on a decrease; the context-menu stepper stays single-lane by nature. - A width write landing on 1 removes the width key (03 ▸ Lane, the remove-at-default family), every mechanism alike — writeLaneWidths is the one commit point; a hand-written width: 1 is preserved by the unchanged guard until the app itself next edits width. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -114,14 +114,16 @@ struct LaneWidthWriteTests {
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
// Lane two is at 3×, so a clamped 1 is a real change and actually reaches disk.
|
||||
// Lane two is at 3×, so a clamped 1 is a real change and actually reaches disk — where the
|
||||
// remove-at-default rule turns it into an absent key (03-board-ui.md § Lane, settled), which
|
||||
// the read side displays as one unit.
|
||||
store.setLaneWidth(ItemID(rawValue: Ident.lane2), units: 0)
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width == .valid(1))
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width.isMissing)
|
||||
|
||||
try fixture.item(Ident.lane2, laneText(order: "2048", title: "Doing", width: "3"))
|
||||
let reopened = try BoardStore(rootURL: fixture.root)
|
||||
reopened.setLaneWidth(ItemID(rawValue: Ident.lane2), units: -7)
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width == .valid(1))
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width.isMissing)
|
||||
}
|
||||
|
||||
@Test("A lane that is not in the snapshot is a no-op")
|
||||
@@ -160,6 +162,58 @@ struct LaneWidthWriteTests {
|
||||
#expect(try fixture.entryNames(Ident.lane1) == ["index.md"], "no temp-file residue either")
|
||||
}
|
||||
|
||||
@Test("A width landing on one removes the key — the remove-at-default family")
|
||||
func landingOnOneRemovesTheKey() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
// Lane two carries `width: 3`; stepping it down to one must not leave `width: 1` behind —
|
||||
// "a default lane's frontmatter stays clean, drag, stepper, and menu items alike"
|
||||
// (03-board-ui.md § Lane, settled). The read side then displays one unit off the absent key.
|
||||
store.setLaneWidth(ItemID(rawValue: Ident.lane2), units: 1)
|
||||
|
||||
let after = try fixture.indexText(Ident.lane2)
|
||||
#expect(!after.contains("width"))
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width.isMissing)
|
||||
}
|
||||
|
||||
@Test("A hand-written width of one is preserved until the app itself next edits width")
|
||||
func handWrittenOneIsPreserved() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
try fixture.item(Ident.lane1, laneText(order: "1024", title: "Todo", width: "1"))
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let before = try fixture.indexData(Ident.lane1)
|
||||
|
||||
// Displays one unit, asked for one unit: the unchanged-units guard fires before the
|
||||
// remove-at-default rule can, so the legal hand-written `width: 1` stays byte-for-byte.
|
||||
store.setLaneWidth(ItemID(rawValue: Ident.lane1), units: 1)
|
||||
#expect(try fixture.indexData(Ident.lane1) == before)
|
||||
}
|
||||
|
||||
@Test("The menu batch steps every selected lane one unit in one bracket, floor members holding")
|
||||
func batchStepsEverySelectedLane() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
let ids: Set<ItemID> = [ItemID(rawValue: Ident.lane1), ItemID(rawValue: Ident.lane2)]
|
||||
|
||||
// Lane one displays 1× (no key), lane two 3×: one decrease holds the floor member and
|
||||
// steps the other — "each selected lane steps one unit, one gesture, one commit"
|
||||
// (03-board-ui.md § Lane, settled).
|
||||
store.stepLaneWidths(ids, by: -1)
|
||||
#expect(try loadedLane(Ident.lane1, in: fixture).width.isMissing, "held at the floor, key never materializes")
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width == .valid(2))
|
||||
|
||||
// A fresh store, because the step reads the snapshot and the first write's reload is the
|
||||
// watcher's (async) — the second gesture in a real session steps off the reloaded board.
|
||||
let reopened = try BoardStore(rootURL: fixture.root)
|
||||
reopened.stepLaneWidths(ids, by: 1)
|
||||
#expect(try loadedLane(Ident.lane1, in: fixture).width == .valid(2))
|
||||
#expect(try loadedLane(Ident.lane2, in: fixture).width == .valid(3))
|
||||
}
|
||||
|
||||
@Test("A readable-but-uneditable lane refuses the write, banners it, and keeps its bytes")
|
||||
func uneditableLaneBannersAndChangesNothing() throws {
|
||||
let fixture = try makeBoard()
|
||||
|
||||
Reference in New Issue
Block a user