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:
2026-08-09 12:06:01 -04:00
parent fd31ed24f4
commit da37ed61bf
24 changed files with 1928 additions and 29 deletions
+21 -1
View File
@@ -3025,6 +3025,24 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
case migrateTombstone(title: String?)
case style(title: String?) // updateIndex on behalf of styling flows (03-board-ui.md)
/// **A card's `labels` list being rewritten** the sidebar section's chips and the context menu's
/// submenu, the two surfaces the key's 2026-08-09 activation shipped with
/// (`FrontmatterKeys.labels`; `BoardStore.setLabels(_:onCard:on:)`).
///
/// **Its own case rather than a fold into `.style`**, on the vocabulary's standing reasoning. A
/// restyle picks an appearance; this edits what the card *is about*, and 06-history-undo.md's
/// commit-message composer has had a separate word for it since long before there was a control
/// "Relabel card 'X'" (§ Commit messages, named there among the three keys "invisible in the UI
/// though they are"). The word already existed; this is the gesture arriving to claim it. A banner
/// telling someone the app "couldn't style 'Fix login'" after they clicked a label row would name a
/// control they never touched.
///
/// **One case for both directions**, unlike `.collapse`/`.expand`: adding and removing a label are
/// the same control pressed twice a chip's and a menu row's checkmark both just rewrite the
/// list where collapse and expand are two menu rows with two words. `title` is the card's, filled
/// in by `updateIndex` off the document it reads.
case relabel(title: String?)
/// **A generated board background landing** the PNG written into the board folder and the
/// `background` mapping's two subkeys pointed at it, one bracket
/// (`BoardStore.applyGeneratedBackground`; `FacetsGenerator`).
@@ -3359,6 +3377,7 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
case .purge: .purge(title: title)
case .migrateTombstone: .migrateTombstone(title: title)
case .style: .style(title: title)
case .relabel: .relabel(title: title)
case .resize: .resize(title: title)
case .collapse: .collapse(title: title)
case .expand: .expand(title: title)
@@ -3410,7 +3429,7 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
// reading to be about and `.deleteComment`'s move into `comments/.trash/` stamps for the
// plain container reason its board-level twin does.
case .createBoard, .createLane, .createCard, .move, .copy, .delete, .purge, .migrateTombstone,
.style, .resize, .collapse, .expand, .rename, .duplicateBoard, .saveAsTemplate, .shareBoard, .paste,
.style, .relabel, .resize, .collapse, .expand, .rename, .duplicateBoard, .saveAsTemplate, .shareBoard, .paste,
.exportBoard, .importBoard,
.importAttachment,
.listAttachments, .removeAttachment, .relocateLooseFile, .tidyBackgroundImage, .agentGuide,
@@ -3441,6 +3460,7 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
case let .purge(title): Self.phrase("purge", title)
case let .migrateTombstone(title): Self.phrase("migrate the legacy 'deleted' key on", title)
case let .style(title): Self.phrase("style", title)
case let .relabel(title): Self.phrase("relabel", title)
case .setBoardBackground: "set this board's background"
case let .resize(title): Self.phrase("resize", title)
case let .collapse(title): Self.phrase("collapse", title)