Window Frame Persistence
Overview
An invisible view modifier that persists a window's position and size between sessions using the platform's native frame autosave mechanism. Attached as a background modifier to any window's root content. On macOS, accesses the hosting NSWindow and calls setFrameAutosaveName. Supports a close callback for cleanup.
Behavioral Requirements
- persist-frame-autosave: The component MUST persist the window's frame (origin + size) across app sessions using
NSWindow.setFrameAutosaveName. - unique-autosave-name: Each window MUST have a unique autosave name. For document windows, this SHOULD be derived from the document's file path (e.g., SHA256 hash prefix).
- invisible-background-view: The component MUST NOT be visible — it renders as an empty
NSViewadded as a background. - delayed-window-access: The component MUST access the hosting
NSWindowvia the view hierarchy after a brief delay (next main run loop cycle) to ensure the window exists. - on-close-callback: The component MAY accept an
onClosecallback that fires when the window is closed (viaNSWindow.willCloseNotification). - cleanup-observers: The component MUST clean up notification observers on deinit.
- default-first-launch: On first launch (no saved frame), the window SHOULD use its default position/size defined elsewhere.
Appearance
Not applicable — this component is invisible.
States
| State | Behavior |
|---|---|
| Window appears | Autosave name applied, previous frame restored if saved |
| Window moved/resized | Frame auto-saved by AppKit (no manual intervention needed) |
| Window closed | onClose callback fired, observers cleaned up |
| No saved frame | Window uses default layout |
Accessibility
Not applicable — this component has no visual or interactive surface.
Conformance Test Vectors
| ID | Requirements | Input | Expected |
|---|---|---|---|
| frame-001 | persist-frame-autosave | Open window, move to (200,300), close, reopen | Window appears at (200,300) |
| frame-002 | persist-frame-autosave | Open window, resize to 800×600, close, reopen | Window appears at 800×600 |
| frame-003 | unique-autosave-name | Open two document windows | Each has unique autosave name, saved independently |
| frame-004 | invisible-background-view | Inspect view hierarchy | No visible NSView from this component |
| frame-005 | on-close-callback | Close window with onClose callback | Callback fires |
Edge Cases
- Window opens off-screen (e.g., saved on external monitor now disconnected): AppKit handles this —
setFrameAutosaveNameadjusts to visible screen area. - Duplicate autosave names: Two windows with the same name will interfere. Use unique identifiers.
- Rapid open/close: Observer cleanup in deinit prevents leaks.
Configuration
This ingredient has no configurable options.
Logging
Subsystem: {{bundle_id}} | Category: WindowFrame
| Event | Level | Message |
|---|---|---|
| Frame autosave set | debug | WindowFrame: autosave name set to "{{name}}" |
| Window closed | debug | WindowFrame: window "{{name}}" closed |
Platform Notes
- SwiftUI (macOS): Implement as
NSViewRepresentable. InmakeNSView, return an emptyNSView. InupdateNSView, dispatch to main queue to accessview.window, then callwindow.setFrameAutosaveName(name). ObserveNSWindow.willCloseNotificationfor close callback. Attach via.background(WindowAccessor(name: "...", onClose: { })). - Other platforms: Not applicable. iOS, Android, and Web handle window/view positioning differently (iOS has no user-movable windows, Android has activity lifecycle, Web uses CSS/URL state).
Design Decisions
None yet.