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
+15 -7
View File
@@ -62,7 +62,7 @@ struct CardDetailsKeyTests {
// twice-written `project` reads once, and it reads where its *winning* occurrence sits: the
// effective view a duplicate collapses to (`FrontmatterDocument.parse`, last-wins), which is
// also the order the file itself takes the moment anything rewrites that key.
#expect(rows.map(\.key) == ["labels", "assignees", "due", "remote", "project"])
#expect(rows.map(\.key) == ["assignees", "due", "remote", "project"])
}
@Test("Reserved enhanced-schema keys are ordinary unknown keys in this version")
@@ -73,9 +73,15 @@ struct CardDetailsKeyTests {
// appear here like any other no special rendering" (05 Details). The day they gain
// meaning they leave this section for a control; until then, hiding them would hide data the
// file plainly has.
for reserved in ["labels", "assignees", "due", "remote"] {
//
// **`labels` is the first to leave, and this is the day** (2026-08-09): the owner claimed it
// for first-party use, it joined `schemaOwned`, and the Labels section is the control the
// sentence above anticipated. Its absence here is therefore the *rule* holding rather than an
// exception to it the three that are still reserved are still here.
for reserved in ["assignees", "due", "remote"] {
#expect(rows.contains { $0.key == reserved }, "\(reserved) belongs in Details")
}
#expect(!rows.contains { $0.key == FrontmatterKeys.labels }, "labels has its own section now")
}
@Test("Every schema-owned key is excluded — including the ones the sidebar shows elsewhere")
@@ -89,7 +95,7 @@ struct CardDetailsKeyTests {
// them would be the same fact stated twice, in a section whose whole premise is "keys the app
// does not own".
#expect(keys.isDisjoint(with: FrontmatterKeys.schemaOwned))
#expect(keys == ["labels", "assignees", "due", "remote", "project"])
#expect(keys == ["assignees", "due", "remote", "project"])
}
@Test("A card with no unknown keys has no section at all")
@@ -133,8 +139,9 @@ struct CardDetailsKeyTests {
let model = try BoardLoader.load(boardRoot: fixture.root).model
let card = try #require(model.lanes.first?.cards.first)
#expect(CardDetails.rows(of: card.document).map(\.key) == ["labels", "assignees", "due", "remote", "project"])
#expect(CardDetails.rows(of: card.document).first?.value == "[a, b, c]")
#expect(CardDetails.rows(of: card.document).map(\.key) == ["assignees", "due", "remote", "project"])
// And the key that left: it is read as a field now, not shown as raw text.
#expect(card.labels.value == ["a", "b", "c"])
}
}
@@ -266,8 +273,9 @@ struct CardDetailsValueTests {
""")
#expect(document.uneditableShape != nil)
#expect(CardDetails.rows(of: document).map(\.key) == ["project", "labels"])
#expect(CardDetails.rows(of: document).map(\.value) == ["lanework", "[a, b]"])
#expect(CardDetails.rows(of: document).map(\.key) == ["project"])
#expect(CardDetails.rows(of: document).map(\.value) == ["lanework"])
#expect(document.labels == .valid(["a", "b"]), "and the schema-owned one still reads")
}
@Test("Nothing a parsed document can hold makes a row throw or vanish")