The card window's title learns Edit mode — a sibling session rides the body's own doors
CardWindowView's header is now the title, live: a static Text in Preview, an editable single-line TextField in Edit, both reading a new CardTitleEditSession's buffer rather than the card's own snapshot value — the same "buffer outranks the snapshot" reason the body surface already reads bodySession.text instead of card.body. CardTitleEditSession is CardBodyEditSession's shape one field over: the one-isDirty write gate, dirty-buffer-wins on adopt(diskTitle:), the ~700ms injectable debounce, flush() / flushOrThrow() for DirtyBufferGuard, and beginEditSession()/endEditSession() with a session-coalesced undo step (one per session, never per debounced tick). It rides the body's own begin/end/flush doors rather than opening a second session boundary — title is only ever editable while the body column is in Edit mode — because the two write through different WriteOperations with different validation and merging them would conflate two unrelated frontmatter keys behind one buffer. BoardStore.commitCardTitle(inCard:title:) reuses the same private setTitle helper and the same .rename WriteOperation the board's own inline rename commits through, so trimming, empty-removes-the-key, unchanged-writes-nothing and banner enrichment are one code path, not a re-implementation. It resolves through cardBodyTarget (spans lanes and the trash), not boardItem (board only), because a card window's title field stays live through the same dismissal-into-trash flush the body already gets — the one deliberate divergence from the board's own rename, which treats a trashed target as vanished. registerTitleEdit(inCard:priorTitle:newTitle🔛) mirrors registerBodyEdit, anchored by card identity and the already-reserved ExpectedField.title, folding into the same one-coarse-step-per-window-close undo model. Every place the body's Edit buffer flushes, the title's now does too: mode exit (bodyPresentation.flushEdits/beginEdits), window close and the dismissal path (CardWindowSession.endSession()), raw-source entry (configureRawSource), the close-time DirtyBufferGuard modal (attemptSave tries body then title), and the fast-path close gate (closeAfterFlushing() now checks title.isDirty alongside body.isDirty). holdsUnsavedContent and settlement carry the title too. Tests: CardTitleEditSessionTests.swift mirrors CardBodyEditSessionTests.swift (write gates, normalization and newline-stripping, dirty-buffer-wins, debounce, undo-step coalescing). CardTitleWriteTests.swift covers commitCardTitle/registerTitleEdit: byte-identity no-op, empty-removes-key, vanished, the trashed-card-is-still-writable divergence, a readable-but-uneditable target refusing and bannering, and a read-only board suspending quietly. CardSessionUndoTests.swift gains coverage that a title edit folds into the coarse close step alongside a body edit and registers on the window's own stack, never the board's. RawSourceTests.swift's hand-wired rig picks up the title session configureRawSource now also flushes. 3148 KanbanTests pass, 0 failures. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -526,10 +526,20 @@ struct RawSourceSessionTests {
|
||||
return store.writeCardBody(inCard: cardID, body: text)
|
||||
}
|
||||
body.adopt(diskBody: try FrontmatterDocument.parse(fixture.indexText(cardPath)).body)
|
||||
presentation.flushEdits = { body.endEditSession() }
|
||||
// The title field's own session — not this suite's subject, but `configureRawSource` now
|
||||
// flushes it alongside the body, so the wiring under test needs one to flush.
|
||||
let title = CardTitleEditSession()
|
||||
title.save = { [weak store] title in
|
||||
guard let store else { return .vanished }
|
||||
return store.commitCardTitle(inCard: cardID, title: title)
|
||||
}
|
||||
presentation.flushEdits = {
|
||||
body.endEditSession()
|
||||
title.endEditSession()
|
||||
}
|
||||
|
||||
let raw = CardRawSourceSession()
|
||||
CardWindowHost.configureRawSource(raw, body: body, presentation: presentation, store: store, cardID: cardID)
|
||||
CardWindowHost.configureRawSource(raw, body: body, title: title, presentation: presentation, store: store, cardID: cardID)
|
||||
|
||||
return Rig(fixture: fixture, store: store, presentation: presentation, body: body, raw: raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user