Stand up the window architecture — welcome, board, card
Four scenes (welcome, restore bootstrap, board group, card group) with system restoration disabled in favor of the registry's open-now flags: set when a window actually opens, cleared only on user close, so quit — and crash — leave exactly the restoration set behind. AppModel joins windows to sessions (shared store, registry record, card refs, held security scope); CloseFlushCoordinator pins 02's strict close order as a seam-injected machine (card sessions end, windows drain, store flushes, record stamps, teardown) with named slots where m6/m7 flushes land. HostedWindowController proxies — never replaces — SwiftUI's window delegate to intercept windowShouldClose for the flush, report frames, and place saved frames onto live screens. Card windows are (board path, case-folded card id) values: reopen focuses, and a snapshot-pure fate function dismisses on delete, tombstone, tombstoned lane, or cross-board move. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import AppKit
|
||||
import Testing
|
||||
@testable import Kanban
|
||||
|
||||
/// Per-board frame memory has one rule that is not "put it back where it was": a frame saved on a
|
||||
/// display that is no longer attached must land somewhere visible (02-architecture.md § Windows).
|
||||
/// That rule is untestable against real hardware — the interesting case *is* the monitor that is not
|
||||
/// plugged in — which is why the decision takes its screens as an argument.
|
||||
|
||||
@MainActor
|
||||
@Suite("Window placement")
|
||||
struct WindowPlacementTests {
|
||||
|
||||
private let laptop = NSRect(x: 0, y: 0, width: 1512, height: 916)
|
||||
private let external = NSRect(x: 1512, y: 0, width: 2560, height: 1415)
|
||||
|
||||
@Test("A frame on an attached screen is restored exactly")
|
||||
func aVisibleFrameIsUntouched() {
|
||||
let saved = WindowFrame(x: 120, y: 80, width: 1200, height: 700)
|
||||
let placed = HostedWindowController.placement(for: saved, onScreens: [laptop, external], fallback: laptop)
|
||||
#expect(placed == NSRect(x: 120, y: 80, width: 1200, height: 700))
|
||||
}
|
||||
|
||||
@Test("A frame straddling two screens is still where the user left it")
|
||||
func aStraddlingFrameIsUntouched() {
|
||||
// AppKit's own `constrainFrameRect(_:to:)` nudges the remainder into view when the frame is
|
||||
// set, so intersection — not containment — is the right test: a window hanging slightly off
|
||||
// an edge is a place, not a problem.
|
||||
let saved = WindowFrame(x: 1400, y: 100, width: 900, height: 600)
|
||||
let placed = HostedWindowController.placement(for: saved, onScreens: [laptop, external], fallback: laptop)
|
||||
#expect(placed.origin.x == 1400)
|
||||
}
|
||||
|
||||
@Test("A frame on a screen that is gone keeps its size and centers on the fallback")
|
||||
func aVanishedScreenRelocates() {
|
||||
let saved = WindowFrame(x: 3000, y: 200, width: 1000, height: 600)
|
||||
let placed = HostedWindowController.placement(for: saved, onScreens: [laptop], fallback: laptop)
|
||||
|
||||
// Size is a preference and survives; position is a place, and the place stopped existing.
|
||||
#expect(placed.size == NSSize(width: 1000, height: 600))
|
||||
#expect(placed.midX == laptop.midX)
|
||||
#expect(placed.midY == laptop.midY)
|
||||
#expect(laptop.intersects(placed))
|
||||
}
|
||||
|
||||
@Test("With no screens at all the fallback still produces a frame")
|
||||
func noScreensStillPlaces() {
|
||||
// Not a real state, but the accessor's `NSScreen.screens` can be empty during a display
|
||||
// reconfiguration, and returning something sane beats a window at the saved coordinates of a
|
||||
// display nobody has.
|
||||
let saved = WindowFrame(x: 4000, y: 4000, width: 800, height: 500)
|
||||
let placed = HostedWindowController.placement(for: saved, onScreens: [], fallback: laptop)
|
||||
#expect(placed.size == NSSize(width: 800, height: 500))
|
||||
#expect(laptop.contains(placed.origin))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user