Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6733393
chore(terminal): delete disabled SurfacePool machinery
arzafran Jul 9, 2026
b13bf09
refactor(contentview): extract TabItemView.swift (mechanical move)
arzafran Jul 9, 2026
a4c4506
refactor: extract shared FileWatcher, dedup config file-watch boilerp…
arzafran Jul 9, 2026
3a6620d
refactor(contentview): extract FileDropOverlayView.swift (mechanical …
arzafran Jul 9, 2026
3938784
refactor(contentview): extract CommandPaletteSearchEngine.swift (mech…
arzafran Jul 9, 2026
3a0b294
chore: rename KeyboardShortcutSettingsFileStore.swift to ProgramaSett…
arzafran Jul 9, 2026
dd1fe83
fix(terminal): fix lost Ghostty focus push after reparent resume
arzafran Jul 9, 2026
4b17278
refactor(terminal): dedupe triplicated hide/retry block in synchroniz…
arzafran Jul 9, 2026
5b236db
refactor: split free-standing types out of AppDelegate.swift
arzafran Jul 9, 2026
5303045
refactor(browser): consolidate hosted-inspector dock detection into I…
arzafran Jul 9, 2026
fde4223
fix(workspace): unify remote-state reset between configure and discon…
arzafran Jul 9, 2026
e8d4c2a
refactor(workspace-remote): dedup SSH quoting, connection policy, and…
arzafran Jul 9, 2026
32e8c20
refactor: extract handleCustomShortcut app-shortcut dispatch table
arzafran Jul 9, 2026
d161e9f
fix(contentview): widen access for symbols split across the #94 file …
arzafran Jul 9, 2026
33132b1
refactor(workspace-remote): split WorkspaceRemoteDaemon.swift by type
arzafran Jul 9, 2026
2140f5e
refactor(terminal): split IME and SwiftUI wrapper out of GhosttyTermi…
arzafran Jul 9, 2026
5294e0c
refactor: consolidate command-palette per-window state into one dicti…
arzafran Jul 9, 2026
5d4b33f
refactor(tabmanager): extract DEBUG UI-test harness into TabManager+U…
arzafran Jul 9, 2026
5563548
refactor(browser): split BrowserPanel.swift along concern seams
arzafran Jul 9, 2026
5f44a87
refactor(contentview): extract SidebarDragDrop.swift and SidebarVisua…
arzafran Jul 9, 2026
c7be63a
refactor(tabmanager): extract stateless git/GitHub CLI prober into Gi…
arzafran Jul 9, 2026
e9c9ee8
refactor(terminal): split RenderStats debug introspection out of Ghos…
arzafran Jul 9, 2026
39f25aa
refactor(tabmanager): add canonical workspace(withId:) lookup, replac…
arzafran Jul 9, 2026
69d78c1
refactor(tabmanager): delete typealias Tab = Workspace, migrate call …
arzafran Jul 9, 2026
58ac04f
refactor(notifications): extract sound-transcoding subsystem into Not…
arzafran Jul 9, 2026
f10f59e
refactor(browser): split WebViewRepresentable out of BrowserPanelView…
arzafran Jul 9, 2026
593ea63
refactor(workspace-remote): split WorkspaceRemoteSessionController by…
arzafran Jul 9, 2026
9419a94
refactor: split ProgramaApp.swift into DebugWindows/SettingsView/Sett…
arzafran Jul 9, 2026
705d187
docs: document sidebar tint UserDefaults precedence between GhosttyCo…
arzafran Jul 9, 2026
aac4826
refactor(contentview): extract CommandPaletteController for palette s…
arzafran Jul 9, 2026
0d6e802
refactor(workspace): extract Persistence/Layout/Bonsplit from Workspa…
arzafran Jul 9, 2026
8ad5fef
Merge remote-tracking branch 'origin/main' into train-cv
arzafran Jul 9, 2026
32a95f9
Merge branch 'train-cv' into train-ad
arzafran Jul 9, 2026
b5e2c7c
Merge branch 'train-ad' into train-gv
arzafran Jul 9, 2026
6d0a63a
Merge branch 'train-gv' into train-ws
arzafran Jul 9, 2026
03a2920
Merge branch 'train-ws' into train-br
arzafran Jul 9, 2026
e0d5435
Merge branch 'train-br' into train-pa
arzafran Jul 9, 2026
52b750d
Merge branch 'train-pa' into train-tm
arzafran Jul 9, 2026
17a5ccf
Merge main into nr/consolidated
arzafran Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 204 additions & 12 deletions GhosttyTabs.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

17,651 changes: 7,377 additions & 10,274 deletions Sources/AppDelegate.swift

Large diffs are not rendered by default.

76 changes: 16 additions & 60 deletions Sources/BrowserWindowPortal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -951,8 +951,8 @@ final class WindowBrowserHostView: NSView {
}

private func hostedInspectorDividerCandidate(in slot: WindowBrowserSlotView) -> HostedInspectorDividerHit? {
let inspectorCandidates = Self.visibleDescendants(in: slot)
.filter { Self.isVisibleHostedInspectorCandidate($0) && Self.isInspectorView($0) }
let inspectorCandidates = InspectorDock.visibleDescendants(in: slot)
.filter { InspectorDock.isVisibleCandidate($0) && InspectorDock.isInspectorView($0) }
.sorted { lhs, rhs in
let lhsFrame = slot.convert(lhs.bounds, from: lhs)
let rhsFrame = slot.convert(rhs.bounds, from: rhs)
Expand Down Expand Up @@ -987,9 +987,9 @@ final class WindowBrowserHostView: NSView {
guard let containerView = inspectorView.superview else { break }

let pageCandidates = containerView.subviews.compactMap { candidate -> (view: NSView, dockSide: HostedInspectorDockSide)? in
guard Self.isVisibleHostedInspectorSiblingCandidate(candidate) else { return nil }
guard InspectorDock.isVisibleSiblingCandidate(candidate, requireMinWidth: false) else { return nil }
guard candidate !== inspectorView else { return nil }
guard Self.verticalOverlap(between: candidate.frame, and: inspectorView.frame) > 8 else {
guard InspectorDock.verticalOverlap(between: candidate.frame, and: inspectorView.frame) > 8 else {
return nil
}
guard let dockSide = HostedInspectorDockSide.resolve(
Expand Down Expand Up @@ -1035,13 +1035,13 @@ final class WindowBrowserHostView: NSView {
private func hostedInspectorDividerCandidateScore(_ hit: HostedInspectorDividerHit) -> CGFloat {
let pageFrame = hit.slotView.convert(hit.pageView.bounds, from: hit.pageView)
let inspectorFrame = hit.slotView.convert(hit.inspectorView.bounds, from: hit.inspectorView)
let overlap = Self.verticalOverlap(between: pageFrame, and: inspectorFrame)
let overlap = InspectorDock.verticalOverlap(between: pageFrame, and: inspectorFrame)
let coverageWidth = max(pageFrame.maxX, inspectorFrame.maxX) - min(pageFrame.minX, inspectorFrame.minX)
return (overlap * 1_000) + coverageWidth + pageFrame.width
}

private func hostedInspectorPageCandidateScore(_ pageView: NSView, inspectorView: NSView) -> CGFloat {
let overlap = Self.verticalOverlap(between: pageView.frame, and: inspectorView.frame)
let overlap = InspectorDock.verticalOverlap(between: pageView.frame, and: inspectorView.frame)
let coverageWidth = max(pageView.frame.maxX, inspectorView.frame.maxX) - min(pageView.frame.minX, inspectorView.frame.minX)
return (overlap * 1_000) + coverageWidth + pageView.frame.width
}
Expand Down Expand Up @@ -1083,8 +1083,8 @@ final class WindowBrowserHostView: NSView {
minimumInspectorWidth: Self.minimumHostedInspectorWidth,
reason: reason
)
return !Self.rectApproximatelyEqual(oldPageFrame, hit.pageView.frame, epsilon: 0.5) ||
!Self.rectApproximatelyEqual(oldInspectorFrame, hit.inspectorView.frame, epsilon: 0.5)
return !InspectorDock.rectApproximatelyEqual(oldPageFrame, hit.pageView.frame, epsilon: 0.5) ||
!InspectorDock.rectApproximatelyEqual(oldInspectorFrame, hit.inspectorView.frame, epsilon: 0.5)
}

@discardableResult
Expand All @@ -1107,8 +1107,8 @@ final class WindowBrowserHostView: NSView {

let oldPageFrame = hit.pageView.frame
let oldInspectorFrame = hit.inspectorView.frame
let pageChanged = !Self.rectApproximatelyEqual(pageFrame, oldPageFrame, epsilon: 0.5)
let inspectorChanged = !Self.rectApproximatelyEqual(inspectorFrame, oldInspectorFrame, epsilon: 0.5)
let pageChanged = !InspectorDock.rectApproximatelyEqual(pageFrame, oldPageFrame, epsilon: 0.5)
let inspectorChanged = !InspectorDock.rectApproximatelyEqual(inspectorFrame, oldInspectorFrame, epsilon: 0.5)
guard pageChanged || inspectorChanged else {
return (pageFrame, inspectorFrame)
}
Expand Down Expand Up @@ -1145,49 +1145,11 @@ final class WindowBrowserHostView: NSView {
return (pageFrame, inspectorFrame)
}

private static func verticalOverlap(between lhs: NSRect, and rhs: NSRect) -> CGFloat {
max(0, min(lhs.maxY, rhs.maxY) - max(lhs.minY, rhs.minY))
}

private static func rectApproximatelyEqual(_ lhs: NSRect, _ rhs: NSRect, epsilon: CGFloat = 0.01) -> Bool {
abs(lhs.origin.x - rhs.origin.x) <= epsilon &&
abs(lhs.origin.y - rhs.origin.y) <= epsilon &&
abs(lhs.size.width - rhs.size.width) <= epsilon &&
abs(lhs.size.height - rhs.size.height) <= epsilon
}

private static func sizeApproximatelyEqual(_ lhs: NSSize, _ rhs: NSSize, epsilon: CGFloat = 0.01) -> Bool {
abs(lhs.width - rhs.width) <= epsilon &&
abs(lhs.height - rhs.height) <= epsilon
}

private static func visibleDescendants(in root: NSView) -> [NSView] {
var descendants: [NSView] = []
var stack = Array(root.subviews.reversed())
while let view = stack.popLast() {
descendants.append(view)
stack.append(contentsOf: view.subviews.reversed())
}
return descendants
}

private static func isInspectorView(_ view: NSView) -> Bool {
String(describing: type(of: view)).contains("WKInspector")
}

private static func isVisibleHostedInspectorCandidate(_ view: NSView) -> Bool {
!view.isHidden &&
view.alphaValue > 0 &&
view.frame.width > 1 &&
view.frame.height > 1
}

private static func isVisibleHostedInspectorSiblingCandidate(_ view: NSView) -> Bool {
!view.isHidden &&
view.alphaValue > 0 &&
view.frame.height > 1
}

private static func collectSplitDividerRegions(
in view: NSView,
hostView: WindowBrowserHostView,
Expand Down Expand Up @@ -2400,15 +2362,10 @@ final class WindowBrowserPortal: HostedViewPortalRegistry {
private static func hasVisibleInspectorDescendant(in root: NSView) -> Bool {
var stack: [NSView] = [root]
while let current = stack.popLast() {
if current !== root {
let className = String(describing: type(of: current))
if className.contains("WKInspector"),
!current.isHidden,
current.alphaValue > 0,
current.frame.width > 1,
current.frame.height > 1 {
return true
}
if current !== root,
InspectorDock.isInspectorView(current),
InspectorDock.isVisibleCandidate(current) {
return true
}
stack.append(contentsOf: current.subviews)
}
Expand Down Expand Up @@ -2468,7 +2425,7 @@ final class WindowBrowserPortal: HostedViewPortalRegistry {
var count = 0
while let current = stack.popLast() {
for subview in current.subviews {
if String(describing: type(of: subview)).contains("WKInspector") {
if InspectorDock.isInspectorView(subview) {
count += 1
}
stack.append(subview)
Expand Down Expand Up @@ -2519,8 +2476,7 @@ final class WindowBrowserPortal: HostedViewPortalRegistry {
if view === primaryWebView { continue }
let className = String(describing: type(of: view))
guard className.contains("WK") else { continue }
if className.contains("WKInspector") &&
(view.isHidden || view.alphaValue <= 0 || view.frame.width <= 1 || view.frame.height <= 1) {
if InspectorDock.isInspectorView(view) && !InspectorDock.isVisibleCandidate(view) {
continue
}
append(view)
Expand Down
Loading
Loading