mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 13:21:12 +00:00
* feat(macos): native-feel dashboard hosting: instant reopen, preload, frame autosave, ⌘N/⌘K, route memory The hosted Control UI dashboard now skips SPA reloads when reopening an unchanged endpoint (auth-equality gated), preloads after launch when a credentialed local/direct config exists, autosaves its window frame, maps ⌘N/⌘K menu items onto the shipped native web events, filters browser-tell context-menu items, restores the last committed route via web-side localStorage memory, and themes the pre-paint background. * refactor(ui): satisfy TS LOC ratchet: move optional lazy-element helpers out of app-host app-host.ts is over the 500-line ratchet ceiling and may not grow; move the self-contained optional custom-element block into lazy-custom-element.ts (its natural owner) and fold the route-restore decision into native-route-memory's considerRouteRestore so the shell only replaces-or-persists. * fix(macos): recover stuck dashboard command queue via deliverability gate Replace the failure-page-only fast-path check with canDeliverNativeCommands (live document or in-flight load); a terminally cancelled reload now falls through to the reload path instead of queueing ⌘N/⌘K forever. * fix(ui): let in-flight navigation win over native route restore The one-shot startup restore now checks the rendered/pending match against the committed bootstrap route; an explicit navigation already in flight (replayed native new-session, fast click) is no longer clobbered. * docs(ui): note idempotent collapse of pending native new-session replays * fix(macos): drop queued dashboard commands on terminal load failure Queued ⌘N/⌘K are moment-bound; surviving a failure page meant a later recovery reload replayed stale commands (double palette toggle nets closed, surprise navigation). Both failure entry points now clear the queue. * fix(ui): persist native route memory only for settled router states Mid-navigation emissions still carry the stale committed bootstrap route; an interrupted restore could overwrite the remembered destination with it. * fix(macos): coalesce dashboard opens for queued native commands One in-flight open drains a manager-level queue in press order; a Task per key press could race window creation (duplicate windows) and reorder ⌘N/⌘K delivery on the non-immediate remote path. * fix(macos): correct dashboard placement after frame-autosave restore setFrameAutosaveName re-applies the stored frame; run ensureOnScreen after both autosave-name assignments so a frame saved on a disconnected monitor cannot restore the window off-screen. * fix(macos): clear dashboard failure state when history restores a real document Swipe-back/⌘[ off the failure page commits an http(s) document without passing through load(); the flag previously stayed set and forced a reload on the next native command. * fix(macos): keep in-flight dashboard loads instead of restarting them An open/update during the launch preload previously cancelled and restarted the same-URL load; in-flight non-failure documents now count as usable in the reload decision. * fix(macos,ui): ⌘K legacy fallback + transient search param filtering Command palette dispatch now sends a cancelable toggle event and falls back to the shipped open-search event when no handler acknowledges it, so ⌘K keeps working against older gateway-served bundles. Route memory strips one-shot action params (?draft=) before persisting; navigation state like ?session= still restores. * chore(i18n): register dashboard menu strings + drop unused route-memory export native:i18n:sync inventory + per-locale artifacts for New Session / Command Palette…; StoredNativeRoute is module-internal after the considerRouteRestore refactor (deadcode exports gate). * chore(i18n): rebaseline raw-copy for relocated lazy-element labels The lazy-custom-element extraction moved three internal load-failure labels out of app-host.ts; refresh the keyless raw-copy baseline to match (main's new ui:i18n:verify gate).
78 lines
2.5 KiB
Swift
78 lines
2.5 KiB
Swift
import AppKit
|
|
import Foundation
|
|
import OSLog
|
|
|
|
let dashboardWindowLogger = Logger(subsystem: "ai.openclaw", category: "DashboardWindow")
|
|
|
|
enum DashboardWindowLayout {
|
|
static let windowSize = NSSize(width: 1240, height: 860)
|
|
static let windowMinSize = NSSize(width: 900, height: 620)
|
|
static let mainBrowserMinWidth: CGFloat = 520
|
|
static let linkBrowserMinWidth: CGFloat = 320
|
|
static let linkBrowserMaxWidth: CGFloat = 760
|
|
static let linkBrowserPreferredFraction: CGFloat = 0.4
|
|
static let linkBrowserTabBarHeight: CGFloat = 30
|
|
static let linkBrowserSplitAutosaveName = "OpenClawDashboardLinkBrowserSplit"
|
|
static let windowFrameAutosaveName = "OpenClawDashboardWindow"
|
|
}
|
|
|
|
/// Raw values are window event names the Control UI handles. `newSession`
|
|
/// reuses the shipped pre-web-chrome event; `commandPalette` gets a dedicated
|
|
/// toggle event because the legacy `native-open-search` contract is open-only.
|
|
enum DashboardNativeCommand: String {
|
|
case newSession = "openclaw:native-new-session"
|
|
case commandPalette = "openclaw:native-toggle-search"
|
|
|
|
/// Older gateway bundles lack the toggle listener; dispatch degrades to the
|
|
/// open-only legacy event when the primary event goes unhandled.
|
|
var legacyFallbackEventName: String? {
|
|
switch self {
|
|
case .newSession: nil
|
|
case .commandPalette: "openclaw:native-open-search"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum DashboardLinkTarget: String, Equatable {
|
|
case inline
|
|
case external
|
|
}
|
|
|
|
enum DashboardTargetlessNavigationAction: Equatable {
|
|
case allow
|
|
case openExternal
|
|
case cancel
|
|
}
|
|
|
|
enum DashboardNewWindowAction: Equatable {
|
|
case openTab(URL)
|
|
case openExternal(URL)
|
|
case ignore
|
|
}
|
|
|
|
struct DashboardLinkRequest: Equatable {
|
|
let url: URL
|
|
let target: DashboardLinkTarget
|
|
}
|
|
|
|
struct DashboardWindowAuth: Equatable {
|
|
var gatewayUrl: String?
|
|
var token: String?
|
|
var password: String?
|
|
|
|
var hasCredential: Bool {
|
|
self.token?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ||
|
|
self.password?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
|
|
}
|
|
}
|
|
|
|
/// Dashboard URLs carry the auth token in the `#token=...` fragment; strip the
|
|
/// fragment before logging so credentials never land in unified logs.
|
|
func dashboardLogString(for url: URL) -> String {
|
|
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
|
|
return "<unparseable-url>"
|
|
}
|
|
components.fragment = nil
|
|
return components.url?.absoluteString ?? "<unparseable-url>"
|
|
}
|