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:
@@ -745,6 +745,16 @@ enum ChangeNarrator {
|
||||
/// `created`, `modified-by` and the on-touch heal's backfilled `kind` are all schema-owned, and a
|
||||
/// diff touching only those composes nothing (06 ▸ the bookkeeping rule).
|
||||
///
|
||||
/// **`labels` is the one key added back by hand** (2026-08-09): it joined `schemaOwned` when the
|
||||
/// owner claimed it for first-party use (`FrontmatterKeys.labels`), which took it out of
|
||||
/// `unknownFields` and would have taken the **Relabel** event with it — the one event here that
|
||||
/// 06-history-undo.md names verbatim, and which predates the control by months precisely because
|
||||
/// the composer is origin-agnostic. Being schema-owned changes where the *sidebar* renders the key
|
||||
/// and nothing about whether a change to it is worth narrating; an agent, a tracker sync and the
|
||||
/// app's own submenu all still move it, and all three still say "Relabel". `assignees` and `due`
|
||||
/// are untouched — they are still reserved, still unknown keys, and still arrive through the
|
||||
/// dictionary above.
|
||||
///
|
||||
/// Values compare **as written** (`rawValue`) rather than as parsed YAML: the composer's business
|
||||
/// is that the file changed, and re-deriving an equivalence between `[a, b]` and `[a,b]` would be
|
||||
/// a second YAML semantics to keep honest.
|
||||
@@ -757,8 +767,18 @@ enum ChangeNarrator {
|
||||
kinds: (label: Kind, assignee: Kind, due: Kind, customKey: Kind),
|
||||
paths: [String]
|
||||
) -> [Event] {
|
||||
let before = Dictionary(previous.unknownFields.map { ($0.key, $0.rawValue) }, uniquingKeysWith: { _, last in last })
|
||||
let after = Dictionary(current.unknownFields.map { ($0.key, $0.rawValue) }, uniquingKeysWith: { _, last in last })
|
||||
var before = Dictionary(previous.unknownFields.map { ($0.key, $0.rawValue) }, uniquingKeysWith: { _, last in last })
|
||||
var after = Dictionary(current.unknownFields.map { ($0.key, $0.rawValue) }, uniquingKeysWith: { _, last in last })
|
||||
// `labels`, put back by hand — see the note above. Absent stays absent (no entry), and the
|
||||
// text is `FrontmatterField.rawValue`'s own rule restated, so a key with no span of its own
|
||||
// (an uneditable whole-frontmatter flow mapping) compares by the parse's rendering exactly as
|
||||
// its neighbours in the dictionary do.
|
||||
func labelsText(of document: FrontmatterDocument) -> String? {
|
||||
guard let value = document.value(for: Keys.labels) else { return nil }
|
||||
return document.rawValue(for: Keys.labels) ?? value.description
|
||||
}
|
||||
before[Keys.labels] = labelsText(of: previous)
|
||||
after[Keys.labels] = labelsText(of: current)
|
||||
guard before != after else { return [] }
|
||||
|
||||
func phrase(_ verb: String, _ render: (String) -> String) -> String {
|
||||
|
||||
Reference in New Issue
Block a user