// // Tab.swift // Prisme // // Author: Simon-Pierre Boucher // import Foundation import Observation /// What Prisme understood of the current page, deterministically and /// instantly โ€” the visible proof that the browser read the page. The /// digest enriches it when the local model delivers. struct PageInsight: Equatable, Sendable { let url: URL let approxWords: Int let sectionCount: Int var readingMinutes: Int { max(1, approxWords / 200) } } /// A browsing tab. Holds no `WKWebView` of its own โ€” web views live in the /// pool (CLAUDE.md ยง8) and are attached only while the tab is on screen. /// Page state survives detachment through `interactionState`, which /// preserves the back/forward history and scroll position. @MainActor @Observable final class Tab: Identifiable { let id = UUID() let containerID: IdentityContainer.ID /// URL to load when the tab first gets a web view. Nil = start page. var initialURL: URL? /// Live navigation state and commands, bridged from the engine. let page = WebPageProxy() /// Opaque WebKit session state, captured when the tab goes off screen. @ObservationIgnored var interactionState: Any? /// Set after each settled load; drives the understanding strip. var insight: PageInsight? /// The user dismissed the strip for the current page. var insightDismissed = false let createdAt = Date() var lastActivatedAt = Date() init(initialURL: URL?, containerID: IdentityContainer.ID) { self.initialURL = initialURL self.containerID = containerID } var displayTitle: String { if !page.title.isEmpty { return page.title } if let host = (page.url ?? initialURL)?.host() { return host } return "Nouvel onglet" } /// True while the tab has nothing to render โ€” show the start page. var isBlank: Bool { page.url == nil && initialURL == nil && interactionState == nil } }