Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03a5b31
test: reproduce overlapping Cloud list and stats HTTP reads
austinywang Sep 14, 2026
807b1b8
fix: own and bound Cloud read refreshes across callers
austinywang Sep 14, 2026
8b6aee8
test(web): complete CodeRouter authentication module mocks
austinywang Sep 14, 2026
cafb8b0
ci: recognize the existing mapped Dock resize gate
austinywang Sep 14, 2026
08c05cd
test: drive delayed panel polling with an injected clock
austinywang Sep 14, 2026
e73fc7e
fix: queue replacement reads and expire stale admission
austinywang Sep 14, 2026
035c0dc
test(irx): join the retired watcher before checking redial state
austinywang Sep 14, 2026
d9a4ff6
fix: share network admission with team usage reads
austinywang Sep 14, 2026
87860c8
fix: retain oversized Retry-After without duration overflow
austinywang Sep 14, 2026
1944015
test: reproduce cross-waiter Cloud deadline cancellation
austinywang Sep 15, 2026
919c11a
fix: preserve each Cloud reader deadline and remove fixture sleeps
austinywang Sep 15, 2026
2552c38
fix: restore renderer fixture callbacks and clear Swift warnings
austinywang Sep 15, 2026
6750cec
fix: keep Cloud integration within file budgets and compile C callbac…
austinywang Sep 15, 2026
acebfb2
test: present the initial frame before reclaiming the renderer
austinywang Sep 15, 2026
2ff5c14
docs: cover the private SCP socket in the Cloud CLI skill
austinywang Sep 15, 2026
eb57883
test: reproduce local PTY escape from pending Cloud shortcuts
austinywang Sep 15, 2026
96a8624
fix: retain Cloud execution ownership through pending terminal creation
austinywang Sep 15, 2026
f996f1a
fix: keep empty Cloud pane replacements on their machine
austinywang Sep 15, 2026
30f0fc0
test: pass split insertion policy in cloud routing regression
austinywang Sep 15, 2026
cfcdc5b
fix: remove stale deleted Cloud VPN source from project wiring
austinywang Sep 15, 2026
574833f
fix: recover cloud terminal rendering after reconnect
austinywang Sep 15, 2026
66a1383
style: keep reconnect recovery readable
austinywang Sep 15, 2026
3373222
fix: unblock synced CI test targets
austinywang Sep 16, 2026
734bdcd
fix: wire Cloud refresh coalescing tests
lawrencecchen Sep 19, 2026
7a4627a
docs: retain Cloud transfer failure socket coverage
lawrencecchen Sep 19, 2026
d873d53
style: remove trailing blank lines from Cloud models
lawrencecchen Sep 19, 2026
ec3f5bb
Merge origin/main into issue-12625-cloud-refresh-pressure
lawrencecchen Sep 20, 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
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public actor IrxPeerEngine {
/// clear or adopt anything.
private var dialGeneration: UInt64 = 0
private var redialTimer: Task<Void, Never>?
private var terminationWatcher: Task<Void, Never>?
private(set) var terminationWatcher: Task<Void, Never>?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test widens production state

This changes terminationWatcher from private state to an internally readable property solely so a test can await it. That violates the repository directive against adding test-observability seams or widening members in production Sources/ files for tests. Tests must instead observe behavior or access an appropriate internal declaration through @testable import. This repository requirement must be satisfied before merging.

Rule Used: Do not add new test/debug seams (ForTesting-style members, properties, or methods) to production source files under Sources/. Tests must reach internal state via @testable import instead. Existing occurrences are grandfathered but new ones are ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

private var foregroundTask: Task<Void, Never>?
private var activityGeneration: UInt64 = 0
private var applicationActive: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,21 +685,19 @@ struct IrxLiveQUICTests {
#expect(await registry.activeSessionCount == 1)

let retired = try #require(recovered)
let dialStartsBeforeRetirement =
journal.counterSnapshot()["dial-started"] ?? 0
#expect(
await engine.retire(
connection: retired.connection,
code: .explicitRedial
)
)
// Capture the watcher before retirement clears its slot, then join its completion.
let terminationWatcher = try #require(await engine.terminationWatcher)
let dialStartsBeforeRetirement = journal.counterSnapshot()["dial-started"] ?? 0
let autoRedialsBeforeRetirement = journal.counterSnapshot()["auto-redial"] ?? 0
#expect(await engine.retire(connection: retired.connection, code: .explicitRedial))
await retired.connection.close(code: .explicitRedial, origin: .local)
_ = await retired.connection.termination()
await terminationWatcher.value
#expect(await engine.currentSession() == nil)
#expect(
journal.counterSnapshot()["dial-started"] ?? 0
== dialStartsBeforeRetirement
)
#expect(journal.counterSnapshot()["auto-redial"] ?? 0 == autoRedialsBeforeRetirement)

await engine.stop()
serverLoop.cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ extension TerminalSurface {
public func setRenderHealthChangeHandler(_ handler: (@Sendable (TerminalSurfaceRenderHealth) -> Void)?) {
onRenderHealthChanged = handler
}

/// Re-applies the active window background through the surface view.
@MainActor
public func applyWindowBackgroundIfActive() {
surfaceView.applyWindowBackgroundIfActive()
}

/// Keep `desiredFocusState` in sync when the hosted view's responder chain
/// calls `ghostty_surface_set_focus` directly (bypassing `setFocus`).
/// Without this, `createSurface` would replay a stale state on recreation.
Expand Down Expand Up @@ -368,6 +366,10 @@ extension TerminalSurface {
guard rendererPortalVisible,
hasLiveSurface,
(rendererPresentationPhase != .presented || renderHealth != .rendering) else { return }
// Later activity is a new opportunity after a failed probe.
if renderHealth == .notRendering {
rendererPresentationState.recoveryAttempted = false
}
ensureRendererPresented(presentationReady: presentationReady)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import GhosttyKit
import GhosttyRuntimeTestStubs
import Testing
@testable import CmuxTerminal

extension TerminalSurface {
/// Synthetic runtimes still use the native token/callback handshake. The
/// fixture owns the surface until releaseSurfaceForTesting clears callbacks.
@MainActor
func installRendererTestCallbacks(_ runtime: UnsafeMutableRawPointer) {
let userdata = Unmanaged.passUnretained(self).toOpaque()
let presented = ghostty_surface_set_render_presented_callback(runtime, { userdata, token in
guard let userdata else { return }
MainActor.assumeIsolated {
Unmanaged<TerminalSurface>.fromOpaque(userdata).takeUnretainedValue().rendererFrameDidPresent(token: token)
}
}, userdata)
#expect(presented)
let failed = ghostty_surface_set_render_failed_callback(runtime, { userdata, token, status in
guard let userdata else { return }
MainActor.assumeIsolated {
Unmanaged<TerminalSurface>.fromOpaque(userdata).takeUnretainedValue().rendererFrameDidFail(
token: token, status: status
)
}
}, userdata)
#expect(failed)
}

@MainActor
func acknowledgeRendererTestPresentation() {
guard rendererPresentationState.inFlightToken != nil, let surface else { return }
#expect(cmux_test_ghostty_renderer_present(surface))
}

@MainActor
func failRendererTestPresentation() {
guard let surface else { Issue.record("expected a runtime surface"); return }
#expect(cmux_test_ghostty_renderer_fail(surface, Int32(GHOSTTY_RENDER_PRESENTATION_BACKEND_FAILED.rawValue)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Testing
@MainActor
@Suite(.serialized) struct TerminalSurfaceRendererCallbackTests {
@Test func registeredPresentationCallbackAcknowledgesThePendingToken() {
let fixture = PresentedSurfaceFixture()
let fixture = PresentedSurfaceFixture(configureRendererCallbacks: false)
defer { fixture.tearDown() }
let surface = fixture.surface

Expand All @@ -23,7 +23,7 @@ import Testing
}

@Test func registeredFailureCallbackForwardsTokenAndTriggersOneRecoveryProbe() {
let fixture = PresentedSurfaceFixture()
let fixture = PresentedSurfaceFixture(configureRendererCallbacks: false)
defer { fixture.tearDown() }
let surface = fixture.surface

Expand All @@ -40,8 +40,44 @@ import Testing
#expect(surface.renderHealth == .notRendering)
}

@Test func laterRendererActivityRetriesAfterRecoveryProbeExhaustion() {
let fixture = PresentedSurfaceFixture(configureRendererCallbacks: false)
defer { fixture.tearDown() }
let surface = fixture.surface
let context = installCallbackContext(on: surface)
#expect(ghostty_surface_set_render_presented_callback(
fixture.runtimeSurface,
terminalRendererPresentedCallback,
context.toOpaque()
))
#expect(ghostty_surface_set_render_failed_callback(
fixture.runtimeSurface,
terminalRendererFailedCallback,
context.toOpaque()
))

surface.rendererRuntimeSurfaceDidCreate(presentationReady: true)
#expect(cmux_test_ghostty_renderer_fail(
fixture.runtimeSurface,
Int32(GHOSTTY_RENDER_PRESENTATION_BACKEND_FAILED.rawValue)
))
#expect(cmux_test_ghostty_renderer_fail(
fixture.runtimeSurface,
Int32(GHOSTTY_RENDER_PRESENTATION_DISCARDED.rawValue)
))
#expect(surface.renderHealth == .notRendering)

// A later activity edge must be allowed to arm a fresh probe. Before
// this regression fix, the one-shot recovery latch made this a no-op.
surface.retryRendererPresentationAfterActivity(presentationReady: true)
#expect(surface.renderHealth == .awaitingFrame)
#expect(cmux_test_ghostty_renderer_present(fixture.runtimeSurface))
#expect(surface.renderHealth == .rendering)
#expect(surface.isRendererPresented)
}

@Test func shellExitHealthSurvivesRendererRebuildAndPresentation() {
let fixture = PresentedSurfaceFixture()
let fixture = PresentedSurfaceFixture(configureRendererCallbacks: false)
defer { fixture.tearDown() }
let surface = fixture.surface

Expand Down
Loading
Loading