Build the card window shell and lifecycle
The m4 scene plumbing was already honest — one WindowGroup value per CardWindowRef enforces one-window-per-card, and CardWindowFate's ancestor walk answered dismissal — so this card fills the window: a two-column shell whose body column takes all resize flex and whose sidebar width derives once from font metrics (26 characters of average body advance plus em gutters), the five 05-ordered section headers as placeholders, and the card body as selectable plain text until Preview mode lands. The fate walk now returns a CardPlacement (card + lane), so one pass answers both liveness and the live board › lane subtitle; a board rename lands for free through displayName. Card windows remember their frames per card in the board record (case-folded id keys, unchanged-writes-nothing), restoring instead of cascading; only unremembered cards take the last-used size and cascade. Store acquisition stays gated on liveStore — a card window never opens a board — and the close-flush hook stands with nothing to flush until the Edit-session card. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -32,8 +32,15 @@ private func makeBoard() throws -> WriterFixture {
|
||||
}
|
||||
|
||||
private func title(_ fate: CardWindowFate) -> String? {
|
||||
guard case let .shows(card) = fate else { return nil }
|
||||
return card.title.value
|
||||
guard case let .shows(placement) = fate else { return nil }
|
||||
return placement.card.title.value
|
||||
}
|
||||
|
||||
/// The lane the fate says the card is in — the subtitle's live half, resolved by the very same walk
|
||||
/// that decides whether the window lives at all (`CardPlacement`).
|
||||
private func laneTitle(_ fate: CardWindowFate) -> String? {
|
||||
guard case let .shows(placement) = fate else { return nil }
|
||||
return placement.lane.title.value
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
@@ -42,7 +49,7 @@ private func title(_ fate: CardWindowFate) -> String? {
|
||||
@Suite("Card window fate")
|
||||
struct CardWindowFateTests {
|
||||
|
||||
@Test("A live card in a live lane keeps its window")
|
||||
@Test("A live card in a live lane keeps its window, and names the lane it is in")
|
||||
func aLiveCardShows() throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
@@ -50,6 +57,9 @@ struct CardWindowFateTests {
|
||||
|
||||
let fate = CardWindowHost.cardWindowFate(cardID: Ident.card1, in: snapshot)
|
||||
#expect(title(fate) == "Fix login")
|
||||
// The window's subtitle rides on this same resolution rather than on a second walk, so the
|
||||
// lane it reports and the card it renders can never be one snapshot apart.
|
||||
#expect(laneTitle(fate) == "Todo")
|
||||
}
|
||||
|
||||
@Test("A tombstoned card dismisses its window")
|
||||
|
||||
Reference in New Issue
Block a user