Skip to content

Keep Swift file length budget current - #6016

Closed
austinywang wants to merge 7 commits into
mainfrom
issue-5764-sidebar-layout-loop
Closed

austinywang wants to merge 7 commits into
mainfrom
issue-5764-sidebar-layout-loop

Conversation

@austinywang

@austinywang austinywang commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5764

Related same-root-cause reports: #5845, #2586, #5570.

Summary

  • Resolved the PR branch conflict with current origin/main.
  • Kept .github/swift-file-length-budget.tsv aligned to the current measured file sizes.
  • This PR does not increase any Swift file-length budget; it lowers stale allowances for files that already shrank.

Diagnosis

The captured hang was a main-thread SwiftUI layout livelock over the sidebar workspace list. The representative stack is:

NSHostingView.beginTransaction -> GraphHost.flushTransactions
  -> LazySubviewPlacements.placeSubviews -> LazyStack.place
    -> ForEachList.applyNodes (recurses hundreds deep)
      -> cmux: SidebarWorkspaceRenderItem / TabItemView / SidebarWorkspaceSnapshotBuilder

The runtime sidebar convergence fix is already present on the current base branch: the default workspace sidebar no longer measures the full LazyVStack into @State through a rows-height PreferenceKey. It uses SidebarRowsFillLayout(viewportHeight:) to size the blank drop/tap area from the explicit scroll viewport, so there is no same-value preference write available to re-trigger the layout pass.

File length budget

Current guard output on this branch:

All scanned cmux-owned Swift files: 655636 line(s) across 2158 Swift file(s)
Tracked Swift files >= 500 lines: 427638 line(s) across 199 Swift file(s)
Allowed Swift file length budget: 427638 line(s) across 199 Swift file(s)
Swift file length budget respected.

The PR diff only reduces stale allowances:

  • Packages/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift: 4726 -> 4706
  • Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift: 3665 -> 3663

Testing

  • Not run locally per task instruction.
  • Verified locally only with python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv.
  • CI runs the required checks.

Localization

No user-facing strings changed.

@vercel

vercel Bot commented Jun 12, 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 Jun 14, 2026 1:19am
cmux-staging Building Building Preview, Comment Jun 14, 2026 1:19am

@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds normalization and an update-gating helper to sidebar workspace row-height measurement handling; preference-key reduction and ContentView preference-change handling now avoid storing equivalent measurements. Includes a unit test verifying idempotent behavior across equivalent and different inputs.

Changes

Measurement idempotence and preference update gating

Layer / File(s) Summary
Measurement normalization and update decision helpers
Sources/SidebarWorkspaceRowsMeasurement.swift
normalizedForStorage clamps rowsHeight to non-negative values, and shouldStorePreferenceUpdate(current:preference:) gates writes based on nil-handling and equivalence comparison to prevent no-op republishes.
Preference key reduction with normalization and short-circuiting
Sources/SidebarWorkspaceRowsHeightPreferenceKey.swift
reduce now normalizes incoming nextValue() for storage, returns early if normalization yields no storable value, initializes from the normalized next when current is nil, and skips updates when current and next are equivalent.
Content view preference update handler with idempotent gating
Sources/ContentView.swift
ContentView .onPreferenceChange uses shouldStorePreferenceUpdate to decide persistence and stores measurement?.normalizedForStorage; adds an early-return guard when the stored measurement is nil.
Idempotence test coverage
cmuxTests/SidebarWorkspaceScrollLayoutTests.swift
Adds rowsMeasurementPreferenceUpdateIsIdempotentForEquivalentValues to assert that equivalent measurements (including sub-pixel jitter and reordered workspace IDs) are not re-stored while meaningful differences trigger updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through prefs with careful paws,
Normalized heights to respect the laws,
A gate I built to stop the spin,
Equivalent whispers stay tucked in—
Now the sidebar naps, no noisy cause.

🚥 Pre-merge checks | ✅ 19 | ❌ 2

❌ Failed checks (2 warnings)

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.
Title check ⚠️ Warning The PR title 'Keep Swift file length budget current' describes only a minor housekeeping task, not the main objective of fixing a sidebar layout livelock bug. Update the title to reflect the primary fix: e.g., 'Fix sidebar rows height preference idempotency to prevent layout livelock' or similar.
✅ Passed checks (19 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully addresses the requirements from #5764: gates writes via shouldStorePreferenceUpdate, normalizes values via normalizedForStorage, updates onPreferenceChange, and adds comprehensive test coverage for idempotency.
Out of Scope Changes check ✅ Passed All changes directly relate to fixing the idempotency issue: SidebarWorkspaceRowsMeasurement helpers, PreferenceKey normalization, preference change handling, and new focused tests are all necessary for convergence.
Cmux Swift Actor Isolation ✅ Passed Checked PR-updated Swift files: changes only add nonisolated value-type normalization/idempotency and gate logic in SwiftUI onPreferenceChange; no new async/Task/actor/sendable/protocol or backgrou...
Cmux Swift Blocking Runtime ✅ Passed Checked git diff (main..HEAD) for all modified Swift files; no new blocking/timing primitives (semaphores, sleeps, main sync, locks) were introduced—changes are preference normalization/idempotency...
Cmux Expensive Synchronous Load ✅ Passed In the PR’s Swift files, RestorableAgentSessionIndex.load() (sync) appears 0 times; only async loadIncluding... is used, which delegates to Task.detached (off-main).
Cmux Cache Substitution Correctness ✅ Passed Updates are limited to SwiftUI sidebar rows-height preference idempotency (normalizedForStorage + shouldStorePreferenceUpdate gating); no evidence of persistence/history/undo/snapshot cache substit...
Cmux No Hacky Sleeps ✅ Passed PR #6016 only changes Swift files plus .github/swift-file-length-budget.tsv; no TS/JS/shell/build runtime scripts were modified, so no hacky sleeps rule can be violated.
Cmux Algorithmic Complexity ✅ Passed Changes add linear workspaceIds equality + constant-time clamping in idempotency helpers; no nested scans, sorts/filters, or per-target rescans in the hot sidebar rows-height preference path.
Cmux Swift Concurrency ✅ Passed PASS: Updated sidebar rows-height preference handling uses shouldStorePreferenceUpdate/normalizedForStorage with synchronous logic; inspected modified Swift hunks/tests show no new DispatchQueue/Ta...
Cmux Swift @Concurrent ✅ Passed PR #6016 only changes ContentView.swift, SidebarWorkspaceRowsHeightPreferenceKey.swift, SidebarWorkspaceRowsMeasurement.swift, and a test; searches show no @concurrent or nonisolated async adde...
Cmux Swift File And Package Boundaries ✅ Passed PR only makes small, cohesive preference/measurement idempotency edits (max +19 lines) across 3 Swift files + adds 1 focused test; no new oversized files or large growth/rule violations (tsv budget...
Cmux Swift Logging ✅ Passed No prohibited logging added in the PR files: no print/debugPrint/dump/Logger constants; the only NSLog is inside #if DEBUG in Sources/ContentView.swift.
Cmux User-Facing Error Privacy ✅ Passed Verified production UI alert/recovery-related added lines in the touched Swift files for forbidden vendor/provider/token/upstream content; none found.
Cmux Full Internationalization ✅ Passed Updated sidebar rows-height preference logic/tests only; inspected touched sections in ContentView.swift and the other modified Swift files—no new user-facing/localized Swift text or i18n catalog/w...
Cmux Swiftui State Layout ✅ Passed Diff only normalizes/idempotently gates workspaceRowsMeasurement preference updates (ContentView + PreferenceKey + helper + test); no new ObservableObject/@published, GeometryReader, or lazy-row st...
Cmux Architecture Rethink ✅ Passed PR changes add normalizedForStorage + shouldStorePreferenceUpdate gating for Sidebar rows-height preference writes; inspected files show only guard/equivalence logic—no sleeps, polling, locks, obse...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR #6016 only updates sidebar rows-height preference/idempotency (ContentView + preference types + tests); no NSWindow/NSPanel/WindowGroup/Window/identifiers changed, so auxiliary window close-shor...
Cmux Source Artifacts ✅ Passed Inspected current Swift/test files referenced in the PR for embedded logs/snapshots/recordings and scanned repo for common artifact dirs/files (DerivedData/build/tmp/logs/recordings); none found.
Description check ✅ Passed The PR description covers the summary (what changed and why), testing approach, file budget details, and diagnosis; it aligns well with the repository template requirements.

✏️ 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-5764-sidebar-layout-loop

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 Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a SwiftUI layout livelock in the sidebar workspace list by making the workspaceRowsMeasurement @State writes fully idempotent. It consolidates normalization and equivalence-gating into a single shared shouldStorePreferenceUpdate helper and extends that guard into the PreferenceKey.reduce path.

  • SidebarWorkspaceRowsMeasurement: adds normalizedForStorage (clamps rowsHeight to ≥ 0) and the shouldStorePreferenceUpdate static gate, which short-circuits on nil/nil, same-value, and sub-pixel-jitter cases — the three inputs that previously could re-trigger the layout cycle.
  • SidebarWorkspaceRowsHeightPreferenceKey.reduce: now normalizes each incoming measurement and skips the max-height accumulation step when the new value is within tolerance of the current one, preventing re-emission of equivalent preferences before the @State write gate even sees them.
  • ContentView.onPreferenceChange / onChange: delegates to the shared helper and adds a nil guard on the clear path so a repeated onChange with an already-nil state does not dirty the graph.

Confidence Score: 5/5

Safe to merge. The idempotency fix is tightly scoped to the preference-update gate, all writes go through the same normalization helper, and the new tests cover all branches.

Every changed code path has direct test coverage, the normalization logic is pure and value-typed, the reduce tolerance guard is intentional and bounded by the existing 0.5 pt threshold, and no actor boundaries or mutable shared state are touched.

No files require special attention.

Important Files Changed

Filename Overview
Sources/SidebarWorkspaceRowsMeasurement.swift Adds normalizedForStorage computed property and shouldStorePreferenceUpdate static gate; logic is correct and fully tested.
Sources/SidebarWorkspaceRowsHeightPreferenceKey.swift Normalizes incoming measurements in reduce and short-circuits on equivalent values; change is intentional and bounded by the 0.5pt tolerance.
Sources/ContentView.swift Adds nil guard on clear path and delegates onPreferenceChange logic to the shared helper; clean idempotency fix.
cmuxTests/SidebarWorkspaceScrollLayoutTests.swift New rowsMeasurementPreferenceUpdateIsIdempotentForEquivalentValues test covers all branches of shouldStorePreferenceUpdate, including nil/nil, nil→value, value→nil, equivalent, jittered, moved, and different-order cases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["SwiftUI layout pass\n(LazyVStack measures rows)"] --> B["SidebarWorkspaceRowsHeightPreferenceKey.reduce"]
    B --> C{"nextValue?.normalizedForStorage\nis nil?"}
    C -- "yes" --> D["return — keep current"]
    C -- "no" --> E{"current == nil?"}
    E -- "yes" --> F["value = next"]
    E -- "no" --> G{"current.isEquivalent(to: next)\n(same IDs + ≤0.5pt)"}
    G -- "yes" --> H["return — keep current\n(suppress churn)"]
    G -- "no" --> I["value = max-height winner"]
    F & I & H --> J["onPreferenceChange(measurement)"]
    J --> K{"shouldStorePreferenceUpdate\n(current, preference)"}
    K -- "preference nil & current nil" --> L["skip @State write"]
    K -- "preference nil & current != nil" --> M["workspaceRowsMeasurement = nil"]
    K -- "equivalent / jitter" --> L
    K -- "real change" --> N["workspaceRowsMeasurement =\nmeasurement?.normalizedForStorage"]
    M & N --> O["empty-area sizing recomputes"]
    L --> P["layout converges — no re-trigger"]
Loading

Reviews (2): Last reviewed commit: "Clarify sidebar measurement reduction" | Re-trigger Greptile

Comment thread Sources/SidebarWorkspaceRowsHeightPreferenceKey.swift Outdated
…yout-loop

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Sources/ContentView.swift
#	Sources/SidebarWorkspaceRowsHeightPreferenceKey.swift
#	Sources/SidebarWorkspaceRowsMeasurement.swift
#	cmuxTests/SidebarWorkspaceScrollLayoutTests.swift
@austinywang austinywang changed the title Fix sidebar rows height preference idempotency Fix sidebar workspace layout loop Jun 13, 2026
@austinywang austinywang changed the title Fix sidebar workspace layout loop Keep Swift file length budget current Jun 14, 2026
@austinywang

Copy link
Copy Markdown
Contributor Author

Closing this out.

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

1 participant