Skip to content

Render panel header glyphs through the resolved-icon path (#8558) - #10272

Closed
BorisLoveDev wants to merge 1 commit into
manaflow-ai:mainfrom
BorisLoveDev:fix/issue-8558-header-icons
Closed

BorisLoveDev wants to merge 1 commit into
manaflow-ai:mainfrom
BorisLoveDev:fix/issue-8558-header-icons

Conversation

@BorisLoveDev

@BorisLoveDev BorisLoveDev commented Aug 17, 2026 •

Copy link
Copy Markdown

Summary

  • What changed? The markdown viewer and file preview headers (PanelFilePathHeader) now draw their leading file icon and every trailing action glyph through CmuxResolvedIconImage instead of the SwiftUI symbol path. The glyph tint becomes an explicit NSColor carried by a new panelHeaderIconTint environment value, so .foregroundColor(.secondary) is dropped from PanelHeaderIconButton, MarkdownTypographyControl, and FileExternalOpenHeaderMenuButton.
  • Why? Fixes Markdown viewer: entire upper-right action button group disappears after switching back to cmux #8558. On macOS 15 the SwiftUI symbol path rasterizes these glyphs fully transparent while the buttons keep their frames and hit areas, so the entire upper-right action group is invisible but still responds to clicks.

Root cause

Reproduced deterministically on macOS 15.7.4 (Apple Silicon), Xcode 16.4. In a markdown panel the whole header action group and the leading doc.richtext icon render blank, in every state — before and after the app-activation cycle from the report.

Three instrumented builds narrowed it down:

  1. The controls exist. Giving PanelHeaderIconGlyph a colored background shows all six button frames laid out and empty. The 20×20 frame and contentShape(Rectangle()) survive whether or not a glyph draws, which is exactly the "invisible but clickable" symptom.
  2. It is not the tint. Forcing .foregroundStyle(.red) on the glyph changes nothing, so this is not a tint resolving to the background color.
  3. It is not CmuxSystemSymbolImage's image resolution. Painting the nil-image fallback branch green shows it is never taken — the NSImage resolves fine. Setting cacheMode = .never plus recache() on the cached image does not help either, so a poisoned shared raster is not the cause.

A side-by-side render of all three paths in the live header settles it:

path result
Image(systemName:) blank
CmuxSystemSymbolImage (shared template NSImage) blank
CmuxResolvedIconImage glyph renders

So the SwiftUI symbol path itself fails to rasterize in this header context, and the AppKit renderer introduced in #7729 does not. CmuxResolvedIconRenderer draws into an explicit bitmap context under appearance.performAsCurrentDrawingAppearance, verifies the output contains visible pixels, and re-renders on window attachment and effective-appearance changes.

The tint has to be explicit rather than a hierarchical style: the AppKit-backed icon resolves the window appearance, not the panel's SwiftUI colorScheme override, so .secondary would no longer track the panel theme. PanelFilePathHeader publishes the panel's theme foreground at 0.55 alpha; disabled glyphs keep the color at reduced alpha.

Relationship to existing work

This is the same surface and the same approach as #9144, which has been open since 29 July and is currently conflicting with main. That PR's analysis and its choice of the resolved-icon path informed this one; close whichever you prefer. Related reports of the same class: #8352, #7725, #4476.

Scope is deliberately limited to the panel header. Other CmuxSystemSymbolImage call sites are untouched.

Testing

  • Local dogfood. Tagged debug build, markdown panel opened via the debug CLI. Before: leading icon and all six action glyphs blank. After: all render, and they survive deactivating and reactivating the app.
  • Added cmuxTests/PanelHeaderIconGlyphTests.swift (Swift Testing, wired into the cmuxTests target; scripts/lint-pbxproj-test-wiring.sh passes): request construction (symbol source, size, explicit tint, disabled alpha, fallback tint), theme-tint derivation, and a parameterized pass rendering every header symbol under both aqua and darkAqua, asserting the renderer reports visible pixels.
  • I could not execute the unit tests locally. xcodebuild test -scheme cmux-unit aborts in swift-frontend while deserializing the cmux_DEV module (While finishing conformance for protocol conformance GhosttyNSView: TerminalRenderedFrameReceiving → While cross-referencing conformance for 'NSResponder' → abort). I verified this is not caused by this change: the identical crash reproduces on the unmodified base commit, on a clean derivedDataPath, in a compile batch that contains none of my files. It is Xcode 16.4 / Swift 6.1.2 on my machine; CI pins its own Xcode. Please let CI run the suite.
  • No red/green regression commit pair. The failure is an OS-level rasterization failure in the SwiftUI symbol path that a unit test cannot observe — the same symbols render fine in an isolated SwiftUI harness on this machine. The renderer-level visibility assertions are the meaningful guard.
  • Localization audit: icon rendering only. No user-facing strings added, changed, or removed; all String(localized:) labels, help text, and accessibility labels are untouched, so no Resources/Localizable.xcstrings or web/messages/*.json changes are needed.

Note on the base commit

This branch is based on 5734a451c rather than current main because main has not compiled since 04ff18eea (14 Aug):

  • Sources/Workspace+PanelLifecycle.swift:455 passes workspaceID: to TerminalController.cleanupSurfaceState, which takes (surfaceIds:paneIds:).
  • Sources/TerminalController+MobileSurfaces.swift references panelArtifactAuthorizationStore, which is not declared anywhere in the repository, plus three non-exhaustive switches over the new MobileSurfaceKind cases.

5734a451c is an ancestor of main, so this PR's diff is just the one commit. Happy to rebase once main builds again.

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed (none needed)
  • I requested bot reviews after my latest commit
  • All code review bot comments are resolved
  • All human review comments are resolved

Fixes #8558.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Render panel header icons through the resolved AppKit path with an explicit tint to fix invisible-but-clickable glyphs on macOS 15. Old: SwiftUI symbol path produced transparent glyphs; new: CmuxResolvedIconImage draws visible pixels; side effect: tint now comes from a new environment value.

  • Switches the leading file icon and all trailing header actions in PanelFilePathHeader to CmuxResolvedIconImage (16×16 lead, 13×13 glyphs) and publishes panelHeaderIconTint.
  • Drops .foregroundColor(.secondary) from header buttons; derives tint from the header theme foreground at 0.55 alpha; disabled state keeps color, reduces alpha by 0.45; falls back to NSColor.secondaryLabelColor.
  • Adds PanelHeaderIconGlyphTests covering request construction, tint derivation, and visible-pixel rendering for all header symbols in aqua and darkAqua.
  • Scope limited to panel headers; other CmuxSystemSymbolImage call sites unchanged.

Fixes #8558.

Written for commit 40824fb. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Style

    • Improved panel header icon appearance across light and dark themes.
    • Applied consistent tinting, sizing, disabled-state transparency, and secondary emphasis to header icons.
    • Updated file preview and typography controls to use the resolved icon appearance.
  • Tests

    • Added coverage for icon tinting, sizing, disabled states, theme changes, and visible rendering.

…i#8558)

The markdown viewer and file preview headers draw their leading file icon
and every trailing action glyph through the SwiftUI symbol path. On macOS
15 that path rasterizes fully transparent in this header while the buttons
keep their frames and hit areas, so the whole action group is invisible yet
still responds to clicks.

Verified on a local build: instrumenting the glyphs with a colored
background shows all six button frames laid out and empty, and a
side-by-side render proves both `Image(systemName:)` and the shared
template `NSImage` behind `CmuxSystemSymbolImage` draw nothing there while
`CmuxResolvedIconImage` draws the glyph. Forcing an explicit foreground
color does not bring the symbols back, so this is a rasterization failure
rather than a tint that resolves to the background.

Draw the header glyphs through `CmuxResolvedIconRenderer`, which
rasterizes into an explicit bitmap context under the resolved appearance,
verifies the output has visible pixels, and re-renders when the window or
effective appearance changes. The tint becomes an explicit color carried
by a new `panelHeaderIconTint` environment value, because the
AppKit-backed icon resolves the window appearance rather than the panel's
SwiftUI `colorScheme` override; disabled glyphs keep the color at reduced
alpha.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7e67d623-44fd-483f-8700-2006f53c66b0

📥 Commits

Reviewing files that changed from the base of the PR and between 7f9af0f and 40824fb.

📒 Files selected for processing (5)
  • Sources/Panels/FilePreviewPanel.swift
  • Sources/Panels/MarkdownTypographyControl.swift
  • Sources/Panels/PanelContentView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/PanelHeaderIconGlyphTests.swift
💤 Files with no reviewable changes (2)
  • Sources/Panels/MarkdownTypographyControl.swift
  • Sources/Panels/FilePreviewPanel.swift

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Panel header icons now use appearance-resolved rendering with explicit tint propagation. Header controls remove secondary foreground modifiers. New tests validate tinting, disabled states, glyph sizes, and light/dark rendering.

Changes

Panel Header Icon Rendering

Layer / File(s) Summary
Resolve panel header icon appearance
Sources/Panels/PanelContentView.swift
Panel header icons use explicit tint values, enabled-state alpha, and appearance-resolved rendering.
Integrate resolved icons into headers
Sources/Panels/PanelContentView.swift, Sources/Panels/FilePreviewPanel.swift, Sources/Panels/MarkdownTypographyControl.swift
Header controls delegate tinting to the resolved glyph renderer and remove explicit secondary styling.
Validate icon rendering
cmuxTests/PanelHeaderIconGlyphTests.swift, cmux.xcodeproj/project.pbxproj
Tests cover tint propagation, glyph size, disabled alpha, appearance rendering, and test-target integration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 40824

This localized change updates panel header icons to use the resolved rendering path to restore visibility on affected macOS versions; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: austinywang, azooz2003-bit, lawrencecchen

🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: rendering panel header glyphs through the resolved-icon path.
Description check ✅ Passed The description provides a detailed summary, root cause, testing information, scope, and checklist relevant to the UI fix.
Linked Issues check ✅ Passed The changes directly address issue #8558 by preserving visible Markdown header glyphs after application reactivation.
Out of Scope Changes check ✅ Passed The changes remain within panel header icon rendering, tinting, and related regression tests described by issue #8558.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cmux Swift Actor Isolation ✅ Passed The diff adds only SwiftUI header rendering and tests; the new request helper is @MainActor, with no new Sendable models, service protocols, shared mutable references, or background store access.
Cmux Swift Blocking Runtime ✅ Passed The PR adds no semaphore, wait, sleep, timer, polling, sync, or lock primitive. Existing NSLock and asyncAfter code is unchanged; the new test is deterministic.
Cmux Browser Automation Off-Main ✅ Passed The diff only changes panel icon rendering and tests; TerminalController.swift and ControlCommandExecutionPolicy.swift are unchanged, with no browser/WebKit wait or worker-router changes.
Cmux Expensive Synchronous Load ✅ Passed The production diff only changes panel icon rendering and tinting; added-line scans found no agent-history loader, JSON/JSONL read, directory scan, or synchronous load on an interactive path.
Cmux Cache Substitution Correctness ✅ Passed The diff only changes transient panel-header icon rendering and tinting; it does not replace an authoritative read in persistence, history, undo, or snapshot code.
Cmux No Hacky Sleeps ✅ Passed The diff changes only Swift sources, Swift tests, and Xcode wiring; no covered sleep, timer, polling, or wall-clock synchronization was added. Swift timing is out of this rule's scope.
Cmux Algorithmic Complexity ✅ Passed The diff adds only fixed-size panel glyph construction and rendering; it introduces no nested collection scans, batch rescans, repeated sorting/filtering, joins, or unbenchmarked large-record algor...
Cmux Swift Concurrency ✅ Passed The commit diff adds no Dispatch, Combine, completion-handler, or fire-and-forget Task patterns. Existing async code is unchanged, and the new tests use allowed main-actor testing.
Cmux Swift @Concurrent ✅ Passed The diff adds no async or nonisolated work and no @concurrent annotation; the new request helper and renderer calls are synchronous, intentionally UI-bound MainActor work.
Cmux Swift Package Boundaries ✅ Passed The diff adds panel-specific SwiftUI/AppKit glue and tests; it reuses existing CmuxAppKitSupportUI renderer APIs and introduces no reusable, lifecycle-independent domain logic needing a new package.
Cmux Swiftpm Lockfiles ✅ Passed The diff changes Swift sources and test wiring only; it adds no Package.swift/.gitignore or package-reference changes, and the root Xcode Package.resolved is unchanged.
Cmux Swift Logging ✅ Passed The PR diff adds no print, debugPrint, dump, NSLog, Logger, or ad hoc diagnostic logging; changed Swift code only updates icon rendering and tests.
Cmux User-Facing Error Privacy ✅ Passed The diff changes icon rendering and adds tests; it adds no user-facing errors, alerts, command output, or sensitive error text. Test diagnostics and developer comments are allowed.
Cmux Full Internationalization ✅ Passed The diff changes icon rendering and tint only; it adds no production user-facing text, and no string catalogs or web locale files change. Added literals are in tests/comments.
Cmux Swiftui State Layout ✅ Passed The PR diff adds no ObservableObject/@published state, GeometryReader measurement, lazy/list store reference, or render-time state mutation; it only adds icon tinting and renderer calls.
Cmux Architecture Rethink ✅ Passed The diff uses the pre-existing resolved-icon AppKit bridge and an immutable tint environment value; it adds no sleeps, polling, locks, observers, mutable lifecycle state, or duplicate action wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The diff changes panel icon views and a test fixture only; it adds no standalone window code. The auxiliary-window lint passes and checks 35 existing identifiers.
Cmux Source Artifacts ✅ Passed The diff adds only Swift source/tests and Xcode project wiring; no logs, media, caches, temp folders, build output, or artifact directories are present.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The production diff adds no DEBUG/test guard or seam-named member; request and tint are production helpers called by PanelHeaderIconGlyph/PanelFilePathHeader, while test scaffolding stays in cmuxTe...
Cmux No Ambient Global State ✅ Passed The diff adds no top-level function, mutable global, or singleton. PanelHeaderIconGlyph remains a constructable View; its new helpers are pure statics, and PanelHeaderIconTintKey holds only a...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Thank you for this, @BorisLoveDev! Same story as #9144: the panel header glyph fix landed on main in #12126 and #12145 after you opened this, so this one's covered. Really appreciate it :)

@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 25, 2026
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.

Markdown viewer: entire upper-right action button group disappears after switching back to cmux

2 participants