Avoid blocking browser PR metadata updates (#1564)
This commit is contained in:
parent
7f220dc8e4
commit
9bf6ad9457
2 changed files with 142 additions and 89 deletions
|
|
@ -203,6 +203,35 @@ func resolvedBrowserOmnibarPillBackgroundColor(
|
|||
return themeBackgroundColor.blended(withFraction: darkenMix, of: .black) ?? themeBackgroundColor
|
||||
}
|
||||
|
||||
private struct BrowserChromeStyle {
|
||||
let backgroundColor: NSColor
|
||||
let colorScheme: ColorScheme
|
||||
let omnibarPillBackgroundColor: NSColor
|
||||
|
||||
static func resolve(
|
||||
for colorScheme: ColorScheme,
|
||||
themeBackgroundColor: NSColor
|
||||
) -> BrowserChromeStyle {
|
||||
let backgroundColor = resolvedBrowserChromeBackgroundColor(
|
||||
for: colorScheme,
|
||||
themeBackgroundColor: themeBackgroundColor
|
||||
)
|
||||
let chromeColorScheme = resolvedBrowserChromeColorScheme(
|
||||
for: colorScheme,
|
||||
themeBackgroundColor: backgroundColor
|
||||
)
|
||||
let omnibarPillBackgroundColor = resolvedBrowserOmnibarPillBackgroundColor(
|
||||
for: chromeColorScheme,
|
||||
themeBackgroundColor: backgroundColor
|
||||
)
|
||||
return BrowserChromeStyle(
|
||||
backgroundColor: backgroundColor,
|
||||
colorScheme: chromeColorScheme,
|
||||
omnibarPillBackgroundColor: omnibarPillBackgroundColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// View for rendering a browser panel with address bar
|
||||
struct BrowserPanelView: View {
|
||||
@ObservedObject var panel: BrowserPanel
|
||||
|
|
@ -220,6 +249,8 @@ struct BrowserPanelView: View {
|
|||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconNameKey) private var devToolsIconNameRaw = BrowserDevToolsButtonDebugSettings.defaultIcon.rawValue
|
||||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconColorKey) private var devToolsIconColorRaw = BrowserDevToolsButtonDebugSettings.defaultColor.rawValue
|
||||
@AppStorage(BrowserThemeSettings.modeKey) private var browserThemeModeRaw = BrowserThemeSettings.defaultMode.rawValue
|
||||
@AppStorage(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultsKey)
|
||||
private var toggleBrowserDeveloperToolsShortcutData = Data()
|
||||
@State private var suggestionTask: Task<Void, Never>?
|
||||
@State private var isLoadingRemoteSuggestions: Bool = false
|
||||
@State private var latestRemoteSuggestionQuery: String = ""
|
||||
|
|
@ -236,7 +267,11 @@ struct BrowserPanelView: View {
|
|||
@State private var pendingAddressBarFocusRetryRequestId: UUID?
|
||||
@State private var pendingAddressBarFocusRetryGeneration: UInt64 = 0
|
||||
@State private var isBrowserThemeMenuPresented = false
|
||||
@State private var ghosttyBackgroundGeneration: Int = 0
|
||||
@State private var browserChromeStyle = BrowserChromeStyle.resolve(
|
||||
for: .light,
|
||||
themeBackgroundColor: GhosttyBackgroundTheme.currentColor()
|
||||
)
|
||||
@State private var toggleBrowserDeveloperToolsShortcut = KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultShortcut
|
||||
// Keep this below half of the compact omnibar height so it reads as a squircle,
|
||||
// not a capsule.
|
||||
private let omnibarPillCornerRadius: CGFloat = 10
|
||||
|
|
@ -282,24 +317,15 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
|
||||
private var browserChromeBackground: Color {
|
||||
_ = ghosttyBackgroundGeneration
|
||||
return Color(nsColor: GhosttyBackgroundTheme.currentColor())
|
||||
Color(nsColor: browserChromeStyle.backgroundColor)
|
||||
}
|
||||
|
||||
private var browserChromeBackgroundColor: NSColor {
|
||||
_ = ghosttyBackgroundGeneration
|
||||
return resolvedBrowserChromeBackgroundColor(
|
||||
for: colorScheme,
|
||||
themeBackgroundColor: GhosttyBackgroundTheme.currentColor()
|
||||
)
|
||||
browserChromeStyle.backgroundColor
|
||||
}
|
||||
|
||||
private var browserChromeColorScheme: ColorScheme {
|
||||
_ = ghosttyBackgroundGeneration
|
||||
return resolvedBrowserChromeColorScheme(
|
||||
for: colorScheme,
|
||||
themeBackgroundColor: GhosttyBackgroundTheme.currentColor()
|
||||
)
|
||||
browserChromeStyle.colorScheme
|
||||
}
|
||||
|
||||
private var browserContentAccessibilityIdentifier: String {
|
||||
|
|
@ -307,10 +333,12 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
|
||||
private var omnibarPillBackgroundColor: NSColor {
|
||||
resolvedBrowserOmnibarPillBackgroundColor(
|
||||
for: browserChromeColorScheme,
|
||||
themeBackgroundColor: browserChromeBackgroundColor
|
||||
)
|
||||
browserChromeStyle.omnibarPillBackgroundColor
|
||||
}
|
||||
|
||||
private var developerToolsButtonHelp: String {
|
||||
let base = String(localized: "browser.toggleDevTools", defaultValue: "Toggle Developer Tools")
|
||||
return "\(base) (\(toggleBrowserDeveloperToolsShortcut.displayString))"
|
||||
}
|
||||
|
||||
private var owningWorkspace: Workspace? {
|
||||
|
|
@ -420,6 +448,8 @@ struct BrowserPanelView: View {
|
|||
BrowserSearchSettings.searchSuggestionsEnabledKey: BrowserSearchSettings.defaultSearchSuggestionsEnabled,
|
||||
BrowserThemeSettings.modeKey: BrowserThemeSettings.defaultMode.rawValue,
|
||||
])
|
||||
refreshBrowserChromeStyle()
|
||||
refreshToggleBrowserDeveloperToolsShortcut()
|
||||
let resolvedThemeMode = BrowserThemeSettings.mode(defaults: .standard)
|
||||
if browserThemeModeRaw != resolvedThemeMode.rawValue {
|
||||
browserThemeModeRaw = resolvedThemeMode.rawValue
|
||||
|
|
@ -459,8 +489,12 @@ struct BrowserPanelView: View {
|
|||
panel.setBrowserThemeMode(normalizedMode)
|
||||
}
|
||||
.onChange(of: colorScheme) { _ in
|
||||
refreshBrowserChromeStyle()
|
||||
panel.refreshAppearanceDrivenColors()
|
||||
}
|
||||
.onChange(of: toggleBrowserDeveloperToolsShortcutData) { _ in
|
||||
refreshToggleBrowserDeveloperToolsShortcut()
|
||||
}
|
||||
.onChange(of: panel.pendingAddressBarFocusRequestId) { _ in
|
||||
applyPendingAddressBarFocusRequestIfNeeded()
|
||||
}
|
||||
|
|
@ -552,7 +586,7 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { _ in
|
||||
ghosttyBackgroundGeneration &+= 1
|
||||
refreshBrowserChromeStyle()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -668,7 +702,7 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
.buttonStyle(OmnibarAddressButtonStyle())
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
.safeHelp(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.tooltip(String(localized: "browser.toggleDevTools", defaultValue: "Toggle Developer Tools")))
|
||||
.safeHelp(developerToolsButtonHelp)
|
||||
.accessibilityIdentifier("BrowserToggleDevToolsButton")
|
||||
}
|
||||
|
||||
|
|
@ -907,6 +941,28 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func refreshBrowserChromeStyle() {
|
||||
browserChromeStyle = BrowserChromeStyle.resolve(
|
||||
for: colorScheme,
|
||||
themeBackgroundColor: GhosttyBackgroundTheme.currentColor()
|
||||
)
|
||||
}
|
||||
|
||||
private func refreshToggleBrowserDeveloperToolsShortcut() {
|
||||
toggleBrowserDeveloperToolsShortcut = decodeShortcut(
|
||||
from: toggleBrowserDeveloperToolsShortcutData,
|
||||
fallback: KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultShortcut
|
||||
)
|
||||
}
|
||||
|
||||
private func decodeShortcut(from data: Data, fallback: StoredShortcut) -> StoredShortcut {
|
||||
guard !data.isEmpty,
|
||||
let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else {
|
||||
return fallback
|
||||
}
|
||||
return shortcut
|
||||
}
|
||||
|
||||
private func syncWebViewResponderPolicyWithViewState(
|
||||
reason: String,
|
||||
isPanelFocusedOverride: Bool? = nil
|
||||
|
|
|
|||
|
|
@ -13025,6 +13025,61 @@ class TerminalController {
|
|||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
|
||||
private func schedulePanelMetadataMutation(
|
||||
args: String,
|
||||
options: [String: String],
|
||||
missingPanelUsage: String,
|
||||
mutation: @escaping (Tab, UUID) -> Void
|
||||
) -> String {
|
||||
let rawPanelArg = options["panel"] ?? options["surface"]
|
||||
let surfaceIdFromOptions: UUID?
|
||||
if let rawPanelArg {
|
||||
if rawPanelArg.isEmpty {
|
||||
return "ERROR: Missing panel id — usage: \(missingPanelUsage)"
|
||||
}
|
||||
guard let surfaceId = UUID(uuidString: rawPanelArg) else {
|
||||
return "ERROR: Invalid panel id '\(rawPanelArg)'"
|
||||
}
|
||||
surfaceIdFromOptions = surfaceId
|
||||
} else {
|
||||
surfaceIdFromOptions = nil
|
||||
}
|
||||
|
||||
if let tabArg = options["tab"]?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!tabArg.isEmpty,
|
||||
UUID(uuidString: tabArg) == nil,
|
||||
Int(tabArg) == nil {
|
||||
return "ERROR: Tab not found"
|
||||
}
|
||||
|
||||
if let scope = Self.explicitSocketScope(options: options) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self,
|
||||
let tab = self.tabForSidebarMutation(id: scope.workspaceId) else {
|
||||
return
|
||||
}
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
guard validSurfaceIds.contains(scope.panelId) else { return }
|
||||
mutation(tab, scope.panelId)
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self,
|
||||
let tab = self.resolveTabForReport(args) else {
|
||||
return
|
||||
}
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
guard let surfaceId = surfaceIdFromOptions ?? tab.focusedPanelId else { return }
|
||||
guard validSurfaceIds.contains(surfaceId) else { return }
|
||||
mutation(tab, surfaceId)
|
||||
}
|
||||
return "OK"
|
||||
}
|
||||
|
||||
private func upsertSidebarMetadata(_ args: String, missingError: String) -> String {
|
||||
guard tabManager != nil else { return "ERROR: TabManager not available" }
|
||||
let parsed = parseOptionsNoStop(args)
|
||||
|
|
@ -13611,40 +13666,13 @@ class TerminalController {
|
|||
}
|
||||
let label = String(labelRaw.prefix(16))
|
||||
|
||||
var result = "OK"
|
||||
DispatchQueue.main.sync {
|
||||
guard let tab = resolveTabForReport(args) else {
|
||||
result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected"
|
||||
return
|
||||
}
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
|
||||
let panelArg = parsed.options["panel"] ?? parsed.options["surface"]
|
||||
let surfaceId: UUID
|
||||
if let panelArg {
|
||||
if panelArg.isEmpty {
|
||||
result = "ERROR: Missing panel id — usage: report_pr <number> <url> [--label=PR] [--state=open|merged|closed] [--tab=X] [--panel=Y]"
|
||||
return
|
||||
}
|
||||
guard let parsedId = UUID(uuidString: panelArg) else {
|
||||
result = "ERROR: Invalid panel id '\(panelArg)'"
|
||||
return
|
||||
}
|
||||
surfaceId = parsedId
|
||||
} else {
|
||||
guard let focused = tab.focusedPanelId else {
|
||||
result = "ERROR: Missing panel id (no focused surface)"
|
||||
return
|
||||
}
|
||||
surfaceId = focused
|
||||
}
|
||||
|
||||
guard validSurfaceIds.contains(surfaceId) else {
|
||||
result = "ERROR: Panel not found '\(surfaceId.uuidString)'"
|
||||
return
|
||||
}
|
||||
|
||||
// Shell integration provides explicit workspace/panel UUIDs for browser metadata.
|
||||
// Keep this telemetry path off-main so SwiftUI render passes can't deadlock the socket handler.
|
||||
return schedulePanelMetadataMutation(
|
||||
args: args,
|
||||
options: parsed.options,
|
||||
missingPanelUsage: "report_pr <number> <url> [--label=PR] [--state=open|merged|closed] [--tab=X] [--panel=Y]"
|
||||
) { tab, surfaceId in
|
||||
guard Self.shouldReplacePullRequest(
|
||||
current: tab.panelPullRequests[surfaceId],
|
||||
number: number,
|
||||
|
|
@ -13663,48 +13691,17 @@ class TerminalController {
|
|||
status: status
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func clearPullRequest(_ args: String) -> String {
|
||||
let parsed = parseOptions(args)
|
||||
var result = "OK"
|
||||
DispatchQueue.main.sync {
|
||||
guard let tab = resolveTabForReport(args) else {
|
||||
result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected"
|
||||
return
|
||||
}
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
|
||||
let panelArg = parsed.options["panel"] ?? parsed.options["surface"]
|
||||
let surfaceId: UUID
|
||||
if let panelArg {
|
||||
if panelArg.isEmpty {
|
||||
result = "ERROR: Missing panel id — usage: clear_pr [--tab=X] [--panel=Y]"
|
||||
return
|
||||
}
|
||||
guard let parsedId = UUID(uuidString: panelArg) else {
|
||||
result = "ERROR: Invalid panel id '\(panelArg)'"
|
||||
return
|
||||
}
|
||||
surfaceId = parsedId
|
||||
} else {
|
||||
guard let focused = tab.focusedPanelId else {
|
||||
result = "ERROR: Missing panel id (no focused surface)"
|
||||
return
|
||||
}
|
||||
surfaceId = focused
|
||||
}
|
||||
|
||||
guard validSurfaceIds.contains(surfaceId) else {
|
||||
result = "ERROR: Panel not found '\(surfaceId.uuidString)'"
|
||||
return
|
||||
}
|
||||
|
||||
return schedulePanelMetadataMutation(
|
||||
args: args,
|
||||
options: parsed.options,
|
||||
missingPanelUsage: "clear_pr [--tab=X] [--panel=Y]"
|
||||
) { tab, surfaceId in
|
||||
tab.clearPanelPullRequest(panelId: surfaceId)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func reportPorts(_ args: String) -> String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue