| 1 | import AppKit |
| 2 | import LChatCore |
| 3 | import SwiftUI |
| 4 | |
| 5 | // 🤖 "Media, links and docs" as a real window, one per chat — the spec's |
| 6 | // "window-pushed view on Mac" (docs/superpowers/specs/ |
| 7 | // 2026-08-05-media-links-docs-design.md § Native). A sheet was the first |
| 8 | // shape and it was wrong: macOS sheets can't be resized, and this surface is |
| 9 | // exactly the one you want to make big — a screenshot full of small text is |
| 10 | // unreadable at any fixed size. A window resizes, zooms, and goes full |
| 11 | // screen, and the collection viewer inside it grows to match. |
| 12 | // |
| 13 | // LChatMacApp deliberately uses `Window` (not `WindowGroup`), so `openWindow` |
| 14 | // isn't available: hand-rolled NSWindow + NSHostingController is the |
| 15 | // sanctioned pattern here, same as ThreadDebugWindows. |
| 16 | @MainActor |
| 17 | enum MediaWindows { |
| 18 | private static var windows: [String: NSWindow] = [:] |
| 19 | private static var closeObservers: [String: NSObjectProtocol] = [:] |
| 20 | |
| 21 | static func show(instance: SavedInstance, thread: ChatThread, store: ThreadStore, |
| 22 | model: ChatFeedModel, onJump: @escaping (Int64) -> Void) { |
| 23 | let key = "\(instance.origin)|\(thread.id)" |
| 24 | if let w = windows[key] { |
| 25 | w.makeKeyAndOrderFront(nil) |
| 26 | return |
| 27 | } |
| 28 | let view = MediaLinksDocsSheet(instance: instance, thread: thread, store: store, |
| 29 | model: model, |
| 30 | onJump: { sid in |
| 31 | close(key) |
| 32 | onJump(sid) |
| 33 | }) |
| 34 | let w = NSWindow(contentViewController: NSHostingController(rootView: view)) |
| 35 | w.title = "Media, links and docs" |
| 36 | w.subtitle = thread.title |
| 37 | w.setContentSize(NSSize(width: 820, height: 660)) |
| 38 | // The dict keeps the window alive; releasedWhenClosed would have |
| 39 | // AppKit deallocate it under us on close. |
| 40 | w.isReleasedWhenClosed = false |
| 41 | windows[key] = w |
| 42 | closeObservers[key] = NotificationCenter.default.addObserver( |
| 43 | forName: NSWindow.willCloseNotification, object: w, queue: .main |
| 44 | ) { _ in |
| 45 | Task { @MainActor in |
| 46 | windows[key] = nil |
| 47 | if let o = closeObservers.removeValue(forKey: key) { |
| 48 | NotificationCenter.default.removeObserver(o) |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | w.center() |
| 53 | w.makeKeyAndOrderFront(nil) |
| 54 | } |
| 55 | |
| 56 | private static func close(_ key: String) { |
| 57 | windows[key]?.close() |
| 58 | } |
| 59 | } |
| 60 | |