Skip to content

refactor: unify window portal registries behind one hosted-view base#69

Merged
arzafran merged 1 commit into
mainfrom
refactor/n4-unify-portal-registries
Jul 8, 2026
Merged

refactor: unify window portal registries behind one hosted-view base#69
arzafran merged 1 commit into
mainfrom
refactor/n4-unify-portal-registries

Conversation

@arzafran

@arzafran arzafran commented Jul 8, 2026

Copy link
Copy Markdown
Member

What this does

Removes the line-for-line duplication between the two window-level portal registries — WindowTerminalPortal (hosts terminal surfaces) and WindowBrowserPortal (hosts browser web views) — by pulling out the parts of their lifecycle that are genuinely identical into a new shared base class. This is nuclear-review finding N4/WP3, called out as the highest-risk item in the audit, so the goal here was provable correctness over maximal dedup: only code that is byte-identical (or identical modulo a per-class debug-log line) moved into the shared base; everything with real behavioral differences was left alone, even where it shares a name.

Summary

New file Sources/HostedViewPortalRegistry.swift (@mainactor class, +147 lines) holds:

  • Shared bookkeeping: window, installedContainerView, installedReferenceView, geometryObservers
  • removeGeometryObservers()
  • effectiveAnchorFrameInWindow(for:)
  • synchronizeHostFrameToReference() — per-class dlog preserved verbatim via an overridable logHostFrameUpdate(_:) hook
  • debugHostedSubviewCount()
  • Static geometry helpers: rectApproximatelyEqual, pixelSnappedRect, isHiddenOrAncestorHidden, isView(_:above:in:)

WindowTerminalPortal and WindowBrowserPortal now subclass HostedViewPortalRegistry and override hostViewForGeometry to expose their concrete host view (WindowTerminalHostView / WindowBrowserHostView, both unmodified) to the shared geometry code.

Line deltas:

File Before After Delta
Sources/TerminalWindowPortal.swift 2148 2045 -103
Sources/BrowserWindowPortal.swift 4177 4075 -102
Sources/HostedViewPortalRegistry.swift 147 +147

Kept-divergent methods (compared method-by-method, left alone deliberately)

Method Why it stays split
pruneDeadEntries Terminal prunes non-visible entries once their anchor is dead; browser deliberately keeps them so a hidden WKWebView can be rebound on workspace switch without a full reload. Opposite pruning policy, not incidental.
resetTransientRecoveryRetryIfNeeded / scheduleTransientRecoveryRetryIfNeeded Terminal's retry budget is gated behind PROGRAMA_ISSUE_483_PORTAL_RECOVERY and resets only when exhausted. Browser's is always enabled and resets whenever the recovery reason changes (tracked per-entry, extra Entry.transientRecoveryReason field). Different coalescing semantics — explicitly called out as must-not-touch.
installGeometryObservers(for:) / ensureInstalled() Terminal uses Auto Layout constraints and orders itself below the last WindowBrowserHostView; browser is frame-based and orders itself above the last WindowTerminalHostView/reference. This pair is the actual z-order coordination contract between the two portal types.
bind / detachHostedViewdetachWebView / hideEntryhideWebView / updateEntryVisibility / synchronizeHostedViewsynchronizeWebView Browser wraps each WKWebView in a WindowBrowserSlotView container (drop-zone overlay, search overlay, hosted-inspector-divider, WebKit lifecycle notifications); terminal parents the hosted view directly into the host. Two-level vs one-level containment — not safely generalizable without touching WebKit-specific and terminal-specific code.
tearDown() Terminal additionally deactivates/clears Auto Layout constraints (2-line diff). Left as two small separate implementations rather than adding an abstract-method hook for a trivial difference.
scheduleDeferredFullSynchronizeAll() / debugEntryCount() Same shape but reference different backing dictionaries, and the schedule method has asymmetric debug logging (terminal has none, browser logs entry counts). Left as near-duplicate one-liners rather than adding indirection for marginal benefit.

Off-limits, untouched

  • WindowTerminalHostView and its hitTest() (typing-latency-sensitive path per repo CLAUDE.md) — not modified at all.
  • WindowBrowserHostView — not modified at all.
  • All WebKit-specific recovery, drop-zone/search-overlay/hosted-inspector-divider logic, and all terminal-specific attach/refresh logic — untouched, stayed in the adapters.
  • Geometry-sync coalescing semantics (generation counters, live-resize immediate paths, double-async settle delay) — unchanged on both sides.

Xcode project

Registered Sources/HostedViewPortalRegistry.swift in project.pbxproj with the standard 4 entries (PBXBuildFile, PBXFileReference, group child, Sources build phase), IDs A5FF0008/A5FF0018 — verified no collision with existing IDs.

Test Plan

  • xcodebuild -scheme programa -configuration Debug build** BUILD SUCCEEDED **
  • xcodebuild -scheme programa-unit build-for-testing** TEST BUILD SUCCEEDED **
  • CI: ui-regressions and tests-build-and-lag workflows are the real arbiters here, not the local build — this touches the terminal/browser portal geometry-sync and z-ordering paths on every window resize, split-divider drag, and pane bind/detach. Please wait for both to go green before merging.
  • Not done (per repo policy): did not launch the app locally, did not run tests locally.

N4/WP3 (nuclear-review): WindowTerminalPortal and WindowBrowserPortal were
near line-for-line duplicate anchor-bound hosted-view registries (one for
GhosttySurfaceScrollView, one for WKWebView). This extracts the slice of
their lifecycle that is provably identical into a new shared base class,
HostedViewPortalRegistry, and leaves everything else untouched in each
subclass.

Unified into the base (byte-identical or identical modulo a per-class debug
log token, verified line-by-line before moving):
- Stored bookkeeping: window, installedContainerView, installedReferenceView,
  geometryObservers
- removeGeometryObservers()
- effectiveAnchorFrameInWindow(for:)
- synchronizeHostFrameToReference() (per-class dlog kept verbatim via an
  overridable logHostFrameUpdate(_:) hook)
- debugHostedSubviewCount()
- static helpers: rectApproximatelyEqual, pixelSnappedRect,
  isHiddenOrAncestorHidden, isView(_:above:in:)

Kept divergent per subclass, on purpose, after comparing method-by-method:
- pruneDeadEntries: terminal prunes non-visible entries with a dead anchor;
  browser deliberately preserves them so a hidden WKWebView can be rebound
  without a full reload on workspace switch.
- resetTransientRecoveryRetryIfNeeded / scheduleTransientRecoveryRetryIfNeeded:
  terminal's retry budget is gated behind PROGRAMA_ISSUE_483_PORTAL_RECOVERY
  and resets only when the budget hits zero; browser's is always enabled and
  resets whenever the recovery reason changes (tracked per-entry). Different
  coalescing semantics, left untouched on each side.
- installGeometryObservers(for:) / ensureInstalled(): terminal uses Auto
  Layout constraints and orders itself below the last WindowBrowserHostView;
  browser is frame-based and orders itself above the last
  WindowTerminalHostView/reference. This is the actual z-order coordination
  between the two portal types, not incidental duplication.
- bind / detachHostedView<->detachWebView / hideEntry<->hideWebView /
  updateEntryVisibility / synchronizeHostedView<->synchronizeWebView: browser
  wraps each WKWebView in a WindowBrowserSlotView container (drop-zone,
  search-overlay, hosted-inspector-divider, WebKit lifecycle notifications);
  terminal parents the hosted view directly. Two-level vs one-level
  containment, not safely generalizable.
- tearDown(): terminal additionally deactivates/clears Auto Layout
  constraints; kept as two small separate implementations rather than adding
  an abstract-method hook for a 2-line difference.
- scheduleDeferredFullSynchronizeAll() / debugEntryCount(): trivial-bodied
  but reference different backing dictionaries and (for the schedule method)
  different debug logging; left as near-duplicate one-liners rather than
  adding indirection for marginal benefit.

WindowTerminalHostView, WindowBrowserHostView, and their hitTest()
implementations are untouched, as are all WebKit-specific,
drop-zone/search-overlay/hosted-inspector-divider, and terminal-specific
attach/refresh code paths.

Line counts: TerminalWindowPortal.swift 2148 -> 2045 (-103),
BrowserWindowPortal.swift 4177 -> 4075 (-102), new
HostedViewPortalRegistry.swift +147.
@arzafran arzafran merged commit 4bbbd1b into main Jul 8, 2026
8 checks passed
@arzafran arzafran deleted the refactor/n4-unify-portal-registries branch July 10, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant