Skip to content

Fix terminal first render for Cmd+N workspaces - #3264

Closed
austinywang wants to merge 11 commits into
mainfrom
issue-3068-terminal-blank-content
Closed

austinywang wants to merge 11 commits into
mainfrom
issue-3068-terminal-blank-content

Conversation

@austinywang

@austinywang austinywang commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression test for the real Cmd+N workspace-creation path that requires the newly selected workspace to present a terminal frame without any follow-up tab switch
  • add a regression test for switching back to an already-existing workspace, requiring the first return to present a frame without a second switch
  • refresh a visible terminal again when its runtime Ghostty surface reports ready, reusing the same redraw nudge that already exists for visible=false -> true portal visibility restores
  • start the workspace-level portal/layout follow-up loop when a workspace is reselected so remounted terminal and browser portals get a first-return reconciliation pass immediately

References:

Root cause

There are two closely related first-visible-frame gaps here.

  1. Brand-new visible terminal path
    GhosttySurfaceScrollView.setVisibleInUI(_:) already calls refreshSurfaceNow(...) when a terminal becomes visible again after being hidden, which is why switching away and back immediately recovers the blank view. But a brand-new selected workspace starts out visible on its first SwiftUI render, so it never passes through that false -> true visibility transition.

    On that path, the runtime Ghostty surface becomes ready, terminalSurfaceDidBecomeReady fires, and the code only re-ran first-responder focus handling. There was no equivalent visible redraw nudge at surface-ready time, so the first Metal frame could remain blank until a later tab/workspace switch toggled visibility and finally hit the existing refresh path.

  2. Existing workspace remount / first-return path
    Several user reports are broader than brand-new workspaces: an already-existing workspace comes back blank on the first switch and only recovers after another switch. The shared workspace-level follow-up loop is the path that reconciles terminal and browser portal visibility after attach/remount, but workspace selection did not start that loop at all.

    That meant a reselected workspace could remount portal-hosted views and restore focus without ever running the shared post-attach reconciliation pass until some later interaction caused a second visibility/layout churn.

Reproduction

  1. Cold-launch cmux.
  2. Open multiple workspaces with Cmd+N in the main window.
  3. Observe that one of the newly selected workspaces can stay blank even though the workspace is selected and the terminal runtime is alive.
  4. Wait for time-to-first-render: affected workspaces can stay blank indefinitely, or in the 3068 report roughly a minute, until another selection change happens.
  5. Click or switch to another workspace/tab and then switch back.
  6. The terminal content immediately appears, showing that the runtime surface existed and only the visible first-frame refresh / remount reconciliation was missing.

The same family also explains broader reports where returning to an existing workspace blanks on the first switch but recovers on a second switch, and why browser panes have shown a similar “white until another selection/layout churn” symptom.

Regression tests

  • tests_v2/test_workspace_shortcut_initial_render.py
  • tests_v2/test_workspace_switch_initial_render.py

The first test drives the real Cmd+N shortcut path instead of socket background workspace.create, creates a burst of workspaces to amplify the attach timing churn, and asserts that the final selected workspace presents at least one terminal frame without any manual tab switch, typing, or refresh-surfaces workaround.

The second test warms two existing workspaces, switches between them repeatedly, and requires each first return to present a new frame without a second switch. That locks the broader remount/reattach path covered by user reports outside the brand-new-workspace case.

The commits keep the repository’s failure-before-fix structure:

  • 8f030400 test only for new visible workspaces
  • 3eb2c00f fix for runtime-surface-ready visible redraw
  • a8d3b6b5 test only for existing-workspace first return
  • 5737b45b fix for workspace-selection follow-up reconciliation

Issue relationship

Nightly status

At the time of this update, current origin/main / nightly does not contain this fix stack. The branch includes commits not present on origin/main, so reproducing on current nightly is expected unless another unrelated fix lands separately.

Testing

  • Not run locally per repository policy.
  • Verified git diff --check.
  • Verified Python syntax with python3 -m py_compile tests_v2/test_workspace_shortcut_initial_render.py tests_v2/test_workspace_switch_initial_render.py.

Note

Medium Risk
Touches workspace selection and portal/layout follow-up scheduling, which can affect focus/visibility timing across terminals and browser panels, but is limited to UI rendering behavior with added regression coverage.

Overview
Fixes a blank-first-frame regression where newly created or reselected workspaces could show mounted terminal/browser portals without presenting an initial frame until a second workspace/tab switch.

When a terminalSurfaceDidBecomeReady notification arrives for a visible terminal, GhosttyTerminalView now triggers an immediate refreshSurfaceNow to nudge the first Metal frame. Workspace selection also now starts a dedicated selection render follow-up (scheduleSelectionRenderFollowUp) so remounted portal-hosted views get an immediate post-attach reconciliation pass.

Adds two tests_v2 regressions that drive the real Cmd+N creation path and repeated workspace switching, asserting presentCount advances without any extra user interaction.

Reviewed by Cursor Bugbot for commit 51ab429. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes

    • Improved rendering performance when switching between workspaces and creating new workspaces
    • Terminal content now displays more promptly during initial workspace selection
  • Tests

    • Added regression tests for workspace creation and switching rendering behavior

Closes #3068

The current blank-terminal reports are centered on the user shortcut path, not the background socket-create path, so the regression test drives Cmd+N and requires the newly selected workspace to present a frame without any follow-up tab switch.

Constraint: Repository policy requires a failing regression test commit before the fix commit
Rejected: Reuse workspace.create background tests | they bypass the focused shortcut/selection handoff that reproduces issue 3068
Confidence: medium
Scope-risk: narrow
Directive: Keep this test on the real shortcut path; socket-driven workspace.create does not cover the same rendering lifecycle
Tested: Not run locally per repository policy
Not-tested: Whether current main fails deterministically in every local environment
… ready

New workspaces and restored launch-selected workspaces start visible from their first SwiftUI render, so they never pass through the existing visible=false -> true portal refresh path. Reusing that redraw nudge when the Ghostty runtime surface reports ready forces the first frame without waiting for a later tab switch.

Constraint: Must avoid sleep-based app timing fixes and keep the change off the typing hot path
Rejected: Re-sequence TabManager.addWorkspace publishes | broader behavior risk without proving the publish burst is the only missing signal
Rejected: Force portal sync from addWorkspace with delayed async hops | less direct than using the runtime-surface-ready callback that already marks attach readiness
Confidence: medium-high
Scope-risk: narrow
Directive: Keep first-frame refresh keyed to real lifecycle signals; do not add timer-based retries here
Tested: git diff --check; python3 -m py_compile tests_v2/test_workspace_shortcut_initial_render.py
Not-tested: Local app run and UI regression test execution per repository policy
@vercel

vercel Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 12, 2026 9:37pm
cmux-staging Building Building Preview, Comment May 12, 2026 9:37pm

@coderabbitai

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds immediate-render triggers so terminal surfaces render their first Metal frame when they become ready or when a workspace/tab is reselected; also adds two end-to-end regression tests that verify initial render after workspace creation and after workspace switching.

Changes

Cohort / File(s) Summary
Terminal surface refresh
Sources/GhosttyTerminalView.swift
On terminalSurfaceDidBecomeReady, if the portal is visible and attached to a window (not hidden), call refreshSurfaceNow immediately to trigger the first Metal frame rather than waiting for later visibility changes.
Selection follow-up scheduling
Sources/TabManager.swift, Sources/Workspace.swift
When focusing a panel (focusSelectedTabPanel), schedule scheduleSelectionRenderFollowUp(panelId:); Workspace gains scheduleSelectionRenderFollowUp(panelId:) which maps panel IDs and calls beginEventDrivenLayoutFollowUp(reason: "workspace.selection", includeGeometry: true).
Regression tests (e2e)
tests_v2/test_workspace_shortcut_initial_render.py, tests_v2/test_workspace_switch_initial_render.py
Adds two end-to-end tests: one that simulates repeated workspace creation via shortcut and waits for first present, and one that cycles workspace switches to assert render_stats(...).presentCount advances on first return. Includes helpers to query workspace IDs, focused surface, surface health, and render stats.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TabManager
    participant Workspace
    participant GhosttyTerminalView
    participant WindowPortal
    participant MetalLayer

    User->>TabManager: create/select workspace (cmd+n / select)
    TabManager->>Workspace: set selectedTabId / focusSelectedTabPanel
    Workspace->>Workspace: scheduleSelectionRenderFollowUp(panelId)
    Workspace->>GhosttyTerminalView: notify selection / layout follow-up
    GhosttyTerminalView->>WindowPortal: terminalSurfaceDidBecomeReady
    alt portal visible & attached
        GhosttyTerminalView->>WindowPortal: refreshSurfaceNow
        WindowPortal->>MetalLayer: present first frame
        MetalLayer-->>GhosttyTerminalView: presentCount++
    else portal not attached
        GhosttyTerminalView-->>Workspace: await attach/visibility
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped to the portal, ears all a-flutter,

The surface was ready — no longer a shutter.
Cmd+n we summoned a workspace so spry,
Metal frames danced and brightened my eye.
Hooray — no more blankness, the first draw is nigh! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing terminal first render for Cmd+N workspaces.
Linked Issues check ✅ Passed The PR directly addresses issue #3068 by ensuring newly created workspaces present terminal frames without manual switches, and adds regression tests covering both brand-new and existing workspace scenarios from #2555.
Out of Scope Changes check ✅ Passed All changes are focused on fixing terminal first-render issues: two new regression tests, one visible-terminal refresh on surface-ready, and one workspace-selection follow-up loop trigger.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all required template sections with detailed context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3068-terminal-blank-content

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes two closely related blank-first-frame gaps for terminal workspaces: a brand-new Cmd+N workspace that never transitions through false → true visibility (fixed by calling refreshSurfaceNow on terminalSurfaceDidBecomeReady), and an existing workspace that remounts without a reconciliation pass on reselection (fixed by adding scheduleSelectionRenderFollowUp in TabManager).

  • GhosttyTerminalView.swift: Adds a refreshSurfaceNow("surfaceDidBecomeReady") nudge inside the terminalSurfaceDidBecomeReady observer when the surface view is already visible, reusing the same redraw pattern as the existing setVisibleInUI path.
  • Workspace.swift: Refactors portalRenderingEnabled from a Bool (default true) to a two-case PortalRenderingPhase enum (default .unmounted); adds LayoutFollowUpTargetUpdate with a replaceSelectionTargets case; adds scheduleSelectionRenderFollowUp(panelId:) to kick a panel-targeted layout follow-up on workspace selection.
  • TabManager.swift: Calls scheduleSelectionRenderFollowUp after focusPanel on workspace reactivation so remounted portal-hosted views get a reconciliation pass on the first return.

Confidence Score: 5/5

The changes are well-scoped, reuse existing patterns, and both fix paths are covered by the new regression tests.

The two production Swift changes are conservative: the refreshSurfaceNow addition reuses the same redraw nudge already used in setVisibleInUI, and scheduleSelectionRenderFollowUp delegates entirely to the established beginEventDrivenLayoutFollowUp machinery. The PortalRenderingPhase enum refactor changes the initial state from enabled to disabled intentionally, causing the first setPortalRenderingEnabled(true) call to always trigger a layout follow-up instead of silently skipping it. No new timing primitives, sleeps, or actor-isolation issues were introduced.

Sources/Workspace.swift — the silent guard drop in scheduleSelectionRenderFollowUp for unmounted workspaces is worth a second read, though behavior is still an improvement over pre-PR.

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Adds refreshSurfaceNow call in terminalSurfaceDidBecomeReady when the surface is already visible; reuses existing setVisibleInUI redraw pattern and is guarded by window/visibility checks.
Sources/TabManager.swift Adds scheduleSelectionRenderFollowUp after focusPanel on workspace reactivation; the call is safe when portal rendering is enabled and is a no-op (silently dropped) when the workspace is unmounted.
Sources/Workspace.swift Refactors portalRenderingEnabled from a Bool (default true) to a PortalRenderingPhase enum (default .unmounted/false); adds LayoutFollowUpTargetUpdate enum and scheduleSelectionRenderFollowUp. Initial-state change means the first setPortalRenderingEnabled(true) call now always fires a follow-up, which is an improvement; scheduleSelectionRenderFollowUp silently drops for unmounted workspaces.
tests_v2/test_workspace_shortcut_initial_render.py New regression test exercising the Cmd+N burst path; drives real shortcut, waits for surface mount and first present frame without any manual tab switch.
tests_v2/test_workspace_switch_initial_render.py New regression test for existing-workspace first-return rendering; switches between two warmed workspaces repeatedly and requires a present-count advance on each first return.

Sequence Diagram

sequenceDiagram
    participant User
    participant TabManager
    participant Workspace
    participant GhosttyTerminalView
    participant TerminalSurface

    Note over User,TerminalSurface: Path 1 — Brand-new Cmd+N workspace
    User->>TabManager: Cmd+N shortcut
    TabManager->>Workspace: focusPanel(panelId)
    TabManager->>Workspace: "scheduleSelectionRenderFollowUp(panelId) [no-op: portalRenderingEnabled=false]"
    Workspace-->>ContentView: SwiftUI renders new tab
    ContentView->>Workspace: setPortalRenderingEnabled(true) → beginEventDrivenLayoutFollowUp (geometry)
    TerminalSurface-->>GhosttyTerminalView: terminalSurfaceDidBecomeReady notification
    GhosttyTerminalView->>GhosttyTerminalView: "isVisibleInUI && window!=nil && !isHidden"
    GhosttyTerminalView->>TerminalSurface: refreshSurfaceNow(surfaceDidBecomeReady) ✅

    Note over User,TerminalSurface: Path 2 — Reselecting an existing (mounted) workspace
    User->>TabManager: select workspace
    TabManager->>Workspace: focusPanel(panelId)
    TabManager->>Workspace: "scheduleSelectionRenderFollowUp(panelId) [portalRenderingEnabled=true]"
    Workspace->>Workspace: beginEventDrivenLayoutFollowUp replaceSelectionTargets + terminalFocusPanelId
    Workspace->>GhosttyTerminalView: setVisibleInUI(true)
    GhosttyTerminalView->>TerminalSurface: refreshSurfaceNow(setVisibleInUI) ✅
Loading

Reviews (7): Last reviewed commit: "Fix workspace portal mount ownership" | Re-trigger Greptile

Comment thread tests_v2/test_workspace_shortcut_initial_render.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests_v2/test_workspace_shortcut_initial_render.py`:
- Around line 67-89: The readiness predicate in _wait_for_surface_mount is too
strict and the short first-present timeout causes CI flakes: update the ready()
checks in _wait_for_surface_mount to stop requiring surface_focused and relax
the hosted_view_frame size check (use a smaller threshold, e.g. width and height
>= 40.0 instead of 80.0), and increase the first-present/initial timeout used
elsewhere from 2s to a more forgiving value (e.g. 5s) so loaded runners are not
marked failed prematurely; touch the readiness boolean expressions referencing
surface_focused, hosted_view_frame width/height, and the code that defines the
initial timeout to apply these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a631a5c3-f75b-4034-88c4-7159090a05f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3af6447 and 3eb2c00.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • tests_v2/test_workspace_shortcut_initial_render.py

Comment thread tests_v2/test_workspace_shortcut_initial_render.py
…rame

The remaining blank-terminal reports are not limited to brand-new workspaces; several reports say an already-existing workspace comes back blank on the first switch and only recovers after another switch. This regression test warms two workspaces, alternates between them, and requires each first return to present a new frame without manual recovery.

Constraint: Repository policy requires a failing regression test commit before the fix commit
Rejected: Extend the Cmd+N-only test | it does not cover already-created surfaces returning from an inactive workspace
Confidence: medium
Scope-risk: narrow
Directive: Keep this test on workspace selection/remount, not background creation, because the bug depends on returning an existing portal-hosted surface
Tested: Not run locally per repository policy; python3 -m py_compile tests_v2/test_workspace_switch_initial_render.py
Not-tested: Deterministic failure rate on every local machine before the follow-up fix
Brand-new visible terminals were only part of the blank-screen family. Existing workspaces could also return blank on the first switch because workspace selection remounted portal-hosted views without starting the workspace-level follow-up loop that reconciles terminal and browser portal visibility after attach.

Routing workspace selection through the existing follow-up machinery gives remounted panels a post-attach geometry/visibility pass on the first return instead of waiting for a second switch, and it does so through the shared workspace path that already knows how to reconcile both terminal and browser portals.

Constraint: Must preserve the existing portal/lifecycle model and avoid adding timer-only redraw hacks
Rejected: Add another terminal-only refresh callback | too narrow for the broader existing-workspace and browser-pane symptom family
Rejected: Rework workspace mount policy or published selection sequencing | much broader risk surface than starting the existing reconciliation loop at selection time
Confidence: medium-high
Scope-risk: narrow
Directive: Keep workspace selection fixes on the shared follow-up path so terminal and browser portal behavior stays aligned
Tested: git diff --check; python3 -m py_compile tests_v2/test_workspace_shortcut_initial_render.py tests_v2/test_workspace_switch_initial_render.py
Not-tested: Local app run and UI regression execution per repository policy

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/Workspace.swift`:
- Around line 11641-11650: The follow-up targets can linger because
beginEventDrivenLayoutFollowUp only updates targets when non-nil; fix by adding
a way to force-clearing stale targets and call it from
scheduleSelectionRenderFollowUp: change beginEventDrivenLayoutFollowUp to accept
a flag like forceResetTargets: Bool (or an explicit resetTargets parameter) and
implement logic that clears stored browser/terminal targets when
forceResetTargets is true; then call beginEventDrivenLayoutFollowUp(reason:
"workspace.selection", browserPanelId: browserPanelId, terminalFocusPanelId:
terminalPanelId, includeGeometry: true, forceResetTargets: true) from
scheduleSelectionRenderFollowUp (referencing scheduleSelectionRenderFollowUp,
browserPanel(for:), terminalPanel(for:), and beginEventDrivenLayoutFollowUp).

In `@tests_v2/test_workspace_switch_initial_render.py`:
- Line 24: The default SOCKET_PATH assignment uses a fixed path which can
collide; change the fallback for os.environ.get("CMUX_SOCKET",
"/tmp/cmux-debug.sock") to a user-scoped or unique path (e.g., include the
current UID or username or use tempfile.gettempdir() plus a per-user identifier)
or make CMUX_SOCKET required; update the SOCKET_PATH variable initialization to
compute a fallback such as
f"{tempfile.gettempdir()}/cmux-debug-{os.getuid()}.sock" (or similar) so
concurrent users/sessions don't collide.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1bdf2442-1422-4044-85d6-860530b9ed34

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb2c00 and 5737b45.

📒 Files selected for processing (3)
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • tests_v2/test_workspace_switch_initial_render.py

Comment thread Sources/Workspace.swift
Comment thread tests_v2/test_workspace_switch_initial_render.py Outdated
Selection-driven layout follow-up now replaces stale terminal/browser targets instead of inheriting pending work from a previous selected panel. The new regression harnesses also wait for the render condition they actually assert and use the tagged socket path exported by the runner.

Constraint: PR feedback flagged stale follow-up targets and CI-flaky readiness gates.

Rejected: Adding another timing repair in Workspace | would preserve split ownership of pending layout targets.

Confidence: high

Scope-risk: narrow

Directive: Selection changes should derive follow-up targets from the newly selected panel and clear prior target IDs.

Tested: python3 -m py_compile tests_v2/test_workspace_shortcut_initial_render.py tests_v2/test_workspace_switch_initial_render.py; git diff --check

Not-tested: Local app/E2E tests; repo policy runs tests in CI/VM only.
Selection render follow-up should replace the targets derived from the selected panel, not unrelated pending focus-restoration work. The target update policy now preserves the split-zoom browser-exit focus target while still clearing stale terminal/browser selection targets.

Constraint: Greptile flagged that replace-all target semantics could drop split-zoom-exit focus restoration in the same run-loop cycle.

Rejected: Clearing every pending target on selection change | selection does not own split-zoom-exit focus restoration.

Confidence: high

Scope-risk: narrow

Directive: Keep layout follow-up target replacement scoped to the source that owns the target.

Tested: git diff --check

Not-tested: Local app/E2E tests; repo policy runs tests in CI/VM only.
Comment thread Sources/Workspace.swift Outdated
Selection-driven layout follow-up now carries the selected panel ID, replacing selection-owned targets while preserving only same-panel split-zoom exit focus intent. This prevents stale browser-exit focus work from overriding a new selection without dropping legitimate same-panel exit recovery.

Constraint: Review feedback identified both over-clearing and under-clearing risks for browser split-zoom exit focus restoration.

Rejected: Always preserve browser-exit focus intent | stale intent can refocus the wrong panel after selection changes.

Rejected: Always clear browser-exit focus intent | same-panel split-zoom exit recovery can be lost.

Confidence: high

Scope-risk: narrow

Directive: Layout follow-up target replacement must compare ownership against the selected panel before clearing cross-purpose pending work.

Tested: git diff --check

Not-tested: Local app/E2E tests; repo policy runs tests in CI/VM only.
Start workspace portal rendering from an explicit unmounted phase so the first selected mount always transitions through the shared layout follow-up owner. This removes the implicit enabled-at-init state that could let initial render reconciliation depend on selection/focus callback ordering.

Constraint: Do not run reload, xcodebuild, or local tests during the bulk run.

Rethink: make the Workspace MainActor model own portal mount phase instead of treating first mount as an already-enabled boolean state.

Swift guidance: keep lifecycle state synchronous in the model and avoid adding timer/sleep repair paths.

Tested: git diff --check

Not-tested: local app build, xcodebuild, reload, and local tests per user constraints.
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 51ab4299 Deployed May 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

3 participants