A reserved key comes to life — the card window's sidebar grows a Labels section, and labels stops being somebody else's
`labels` has been a reserved tracker key since the rewrite: preserved verbatim, never interpreted, drawn only as an anonymous row in the Details section beside `assignees` and `due`. The owner's cards claim it for first-party use, so it joins the schema — read leniently (a list of names, a bare scalar coercing to one, a mapping malformed and preserved), written canonically (a quoted flow list in the order the user arranged, no auto-sort), and removed outright when the last label goes, the way an expanded lane drops `collapsed`. Identity is case-insensitive and display is case-preserving, so a card carries `bug` once however many ways the board spells it, and entries the reading cannot name ride through the write untouched at the tail. The section sits second, above Details — which is the point rather than a layout preference: Details is where keys the app does *not* own are shown, and this key just stopped being one. Rows rather than chips, because the sidebar is twenty-six characters wide. The add field autocompletes against the board's own used-labels universe, derived from every live and trashed card with no store beside the files, and says out loud when Return would mint a word the board has never used. Writes ride a `.relabel` operation of their own, because the commit composer has said "Relabel card 'X'" since long before there was a control to press — and now the undo row says it too. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -55,6 +55,20 @@ public enum HistoryPhrase {
|
||||
case reorder = "Reorder"
|
||||
case rename = "Rename"
|
||||
case restyle = "Restyle"
|
||||
|
||||
/// A card's `labels` list rewritten — the sidebar's chips and the context menu's submenu
|
||||
/// (`FrontmatterKeys.labels`, activated 2026-08-09; `BoardStore.setLabels(_:onCard:on:)`).
|
||||
///
|
||||
/// **Its own verb rather than `restyle`**, on `collapse`'s reasoning: the row has to read back
|
||||
/// the gesture it undoes, and "Undo Restyle Card" after clicking a label would name a control
|
||||
/// the user never touched. The word is 06-history-undo.md ▸ Commit messages' own for a change
|
||||
/// to this key — the composer has said "Relabel card 'X'" since before there was a control to
|
||||
/// press (`ChangeNarrator`) — so the menu row and the change journal now speak the same one.
|
||||
///
|
||||
/// **One verb for both directions**, unlike `collapse`/`expand`: adding and removing a label
|
||||
/// are the same control pressed twice, where collapsing and expanding are two menu rows with
|
||||
/// two words.
|
||||
case relabel = "Relabel"
|
||||
case resize = "Resize"
|
||||
/// A lane folded to its slim strip (03-board-ui.md § Lane ▸ Collapsed lanes).
|
||||
///
|
||||
|
||||
@@ -55,6 +55,20 @@ public enum ExpectedField: Sendable, Equatable {
|
||||
/// everywhere else here.
|
||||
case hero(String?)
|
||||
|
||||
/// `labels` — the card's label list, whole (`BoardStore.setLabels(_:onCard:on:)`, the key's
|
||||
/// 2026-08-09 activation).
|
||||
///
|
||||
/// **The whole list, not one label**, which is what makes the comparison right: a labels write
|
||||
/// rewrites the key's entire value, so the after-value it declares is the entire value it wrote. A
|
||||
/// per-label expectation would let a step that added `bug` cross safely onto a card somebody else
|
||||
/// has since retagged completely, which is exactly the foreign edit staleness exists to catch.
|
||||
///
|
||||
/// `nil` is the removed key — what taking the last label off leaves behind (the remove-at-default
|
||||
/// family; `FrontmatterKeys.labels`). A card whose list has been emptied but whose key survives for
|
||||
/// preserved non-name entries reads `.valid([])` and matches `[]`, not `nil`: those are different
|
||||
/// bytes and the step knows which one it wrote.
|
||||
case labels([String]?)
|
||||
|
||||
/// `icon` — the styling gesture's symbol dimension.
|
||||
case icon(String?)
|
||||
|
||||
@@ -79,6 +93,7 @@ public enum ExpectedField: Sendable, Equatable {
|
||||
case .background: .background
|
||||
case .backgroundImage: .backgroundImage
|
||||
case .hero: .hero
|
||||
case .labels: .labels
|
||||
case .icon: .icon
|
||||
case .iconColor: .iconColor
|
||||
case .body: .body
|
||||
@@ -95,6 +110,7 @@ public enum ExpectedField: Sendable, Equatable {
|
||||
case background
|
||||
case backgroundImage
|
||||
case hero
|
||||
case labels
|
||||
case icon
|
||||
case iconColor
|
||||
case body
|
||||
@@ -360,6 +376,7 @@ public enum HistoryStaleness {
|
||||
case let .background(expected): equal(document.background, expected)
|
||||
case let .backgroundImage(expected): equal(document.backgroundImage, expected)
|
||||
case let .hero(expected): equal(document.hero, expected)
|
||||
case let .labels(expected): equal(document.labels, expected)
|
||||
case let .icon(expected): equal(document.icon, expected)
|
||||
case let .iconColor(expected): equal(document.iconColor, expected)
|
||||
case let .body(expected): document.body == expected
|
||||
|
||||
Reference in New Issue
Block a user