Skip to content

Restore inactive-window glass tint (revert #180) - #182

Merged
thdxg merged 1 commit into
mainfrom
claude/restore-glass-tint
Jul 22, 2026
Merged

Restore inactive-window glass tint (revert #180)#182
thdxg merged 1 commit into
mainfrom
claude/restore-glass-tint

Conversation

@thdxg

@thdxg thdxg commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What this reverts

PR #180 removed the MactermGlassView tint overlay, believing it only ever made unfocused windows more opaque. That was a misread. This PR restores it.

Why #180 was wrong

NSGlassEffectView (macOS 26 liquid glass) desaturates itself when its window is not key — native OS behavior, and the macOS 26 SDK header exposes no property to opt out (only tintColor/style/cornerRadius/effectIsInteractive). The tint overlay's real job was to mask that system dimming with a fade-in tint.

With the overlay gone, the raw system dimming showed through: on the glass path a translucent window read as markedly more translucent when unfocused. This only affected the liquid-glass appearance (glass enabled + opacity < 1.0), which is why it slipped through — the plain backgroundColor+blur path never varies with focus, and a glass-off build looks fine.

Ghostty itself keeps this overlay for the same reason (its TerminalGlassView fades the tint 0 → tint.opacity on resign-key); it does not — and cannot cleanly — suppress the system dim.

What's restored

  • tintOverlay + backgroundOpacity + updateKeyStatus in MactermGlassView
  • WindowAppearance.syncKeyStatus and its windowDidBecomeKey/windowDidResignKey call sites
  • The NSColor.luminance/isLightColor/adjustingSaturation helpers

Behavior

Functionally identical to the pre-#180 code, with improved doc comments explaining why the overlay is load-bearing (so it isn't removed again).

  • Focused: tint alpha 0 — focused glass matches the user's chosen opacity exactly (no over-darkening).
  • Unfocused: tint fades in at (0.35 light / 0.85 dark) * backgroundOpacity. The * backgroundOpacity scaling is a deliberate Macterm divergence from Ghostty: Macterm has a full-range opacity slider, and the raw tint would jump a very-translucent window to a near-opaque unfocused state, ignoring the slider. Scaling keeps the inactive look proportional to the setting.

Testing

  • mise run test green; format/lint clean.
  • Verified in a debug build with glass enabled: unfocused tint now scales with the opacity slider, focused window matches the set opacity.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added visual feedback for active and inactive windows.
    • Inactive windows now display a subtle tint that fades based on window focus and configured opacity.
    • Window appearance updates immediately when focus changes.

#180 removed the tint overlay on the assumption it only ever *added*
opacity on unfocus. That was wrong: the overlay masks NSGlassEffectView's
own inactive-window desaturation, which is native liquid-glass behavior
with no API to disable. Without the overlay the raw system dimming makes a
translucent window read as markedly *more* translucent when unfocused.

Restore the fade-in tint. It keeps the Macterm-specific divergence from
Ghostty of scaling the inactive tint by the window opacity, so a very
translucent window doesn't jump to a near-opaque unfocused state and the
inactive appearance stays proportional to the opacity slider. Focused, the
tint is at alpha 0, so the focused glass still matches the chosen opacity
exactly. Doc comments now record why the overlay is load-bearing so it
isn't removed again.
@github-actions github-actions Bot added the area:ui Views, Settings UI label Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The window glass appearance now includes an opacity-scaled inactive tint derived from background color properties. Glass configuration receives the initial key-window state, and window delegate callbacks synchronize tint changes when key status changes.

Inactive tint rendering

Layer / File(s) Summary
Tint rendering and opacity
Macterm/Views/WindowAppearance.swift
Adds luminance and saturation helpers, a tint overlay, and key-status-based tint color and opacity calculations.
Key-status synchronization
Macterm/Views/WindowAppearance.swift, Macterm/Views/MainWindow.swift
Passes initial key status during glass configuration and updates the installed glass view when windows become or cease to be key.

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

Sequence Diagram(s)

sequenceDiagram
  participant MainWindowCoordinator
  participant WindowAppearance
  participant MactermGlassView
  MainWindowCoordinator->>WindowAppearance: window key status changes
  WindowAppearance->>MactermGlassView: sync key status
  MactermGlassView->>MactermGlassView: update inactive tint overlay
Loading

Possibly related PRs

  • thdxg/macterm#178: Modifies the same glass and inactive-tint behavior using MactermTheme.nsBg.
  • thdxg/macterm#180: Conflicts with this PR’s tint overlay and key-status synchronization paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 summarizes the main change: restoring the inactive-window glass tint as a revert of PR #180.
Description check ✅ Passed The description covers What, Why, How, and Verified with sufficient detail; only the optional Notes/Closes line is missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 claude/restore-glass-tint

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

@thdxg
thdxg enabled auto-merge (squash) July 22, 2026 06:31
@thdxg
thdxg disabled auto-merge July 22, 2026 06:31
@thdxg
thdxg merged commit 5388ea6 into main Jul 22, 2026
8 of 9 checks passed
@thdxg
thdxg deleted the claude/restore-glass-tint branch July 22, 2026 06:31

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Macterm/Views/WindowAppearance.swift`:
- Around line 163-170: Update updateKeyStatus so key-status changes animate
tintOverlay opacity through NSAnimationContext and
tintOverlay.animator().alphaValue, while preserving a direct alphaValue
assignment during the initial configuration path.
🪄 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 Plus

Run ID: 59398735-f28e-428a-b2c2-8a4b7af974be

📥 Commits

Reviewing files that changed from the base of the PR and between 36930ec and 2b86815.

📒 Files selected for processing (2)
  • Macterm/Views/MainWindow.swift
  • Macterm/Views/WindowAppearance.swift

Comment on lines +163 to +170
func updateKeyStatus(_ isKeyWindow: Bool, backgroundColor: NSColor) {
let tint = tintProperties(for: backgroundColor)
tintOverlay.layer?.backgroundColor = tint.color.cgColor
// Scale by the window opacity so the inactive tint stays within the
// translucency the user chose — otherwise an unfocused window reads as
// near-opaque regardless of the opacity slider.
tintOverlay.alphaValue = isKeyWindow ? 0 : tint.opacity * backgroundOpacity
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file, then inspect the relevant section and nearby call sites.
ast-grep outline Macterm/Views/WindowAppearance.swift --view expanded || true
printf '\n--- lines 120-210 ---\n'
sed -n '120,210p' Macterm/Views/WindowAppearance.swift
printf '\n--- call sites of updateKeyStatus ---\n'
rg -n "updateKeyStatus\(" Macterm/Views/WindowAppearance.swift Macterm -g '*.swift'

Repository: thdxg/macterm

Length of output: 6453


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation and any surrounding animation helpers.
sed -n '1,220p' Macterm/Views/WindowAppearance.swift

Repository: thdxg/macterm

Length of output: 10588


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file outline ---\n'
ast-grep outline Macterm/Views/WindowAppearance.swift --view expanded || true

printf '\n--- relevant lines ---\n'
nl -ba Macterm/Views/WindowAppearance.swift | sed -n '140,190p'

printf '\n--- call sites ---\n'
rg -n "updateKeyStatus\(" Macterm/Views/WindowAppearance.swift Macterm -g '*.swift' || true

Repository: thdxg/macterm

Length of output: 242


🌐 Web query:

AppKit NSView alphaValue animator animation context documentation

💡 Result:

To animate an NSView's alphaValue in AppKit, you must use the view's animator proxy [1][2] within an NSAnimationContext [3]. Crucially, the view must be layer-backed for this animation to function [4][5][6]. Key Requirements and Usage 1. Layer-Backing: You must enable layer-backing for the view (or its superview if appropriate) by setting its wantsLayer property to true [5][6]. Without this, alphaValue changes will typically occur instantly rather than animating [5]. 2. Animator Proxy: Use the animator method to obtain a proxy object [1][2]. Any property changes made to this proxy are automatically animated if they are animatable [1]. 3. Animation Context: Wrap the property change in an NSAnimationContext to control parameters such as duration and timing functions [3][7]. Example (Swift) NSAnimationContext.runAnimationGroup({ context in context.duration = 1.0 // Set animation duration myView.animator.alphaValue = 0.0 // Animate to transparent }) { // Optional: Actions to perform after animation completes myView.isHidden = true } When executing these animations, ensure the view's wantsLayer property is set beforehand [6]: myView.wantsLayer = true // Then proceed with the animation block as shown above. Behavioral Notes - The NSAnimatablePropertyContainer protocol defines this proxy-based animation behavior [1][2]. - If you set a new value while an animation is in progress, the system will animate from the current interpolated value to the new target [1][2]. - You can nest NSAnimationContexts to apply different durations or configurations to specific groups of animations [3].

Citations:


Animate the key-status opacity change. alphaValue is assigned directly here, so the inactive tint snaps instead of fading. Wrap this in NSAnimationContext and set tintOverlay.animator().alphaValue for key changes; keep the initial configure path direct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Macterm/Views/WindowAppearance.swift` around lines 163 - 170, Update
updateKeyStatus so key-status changes animate tintOverlay opacity through
NSAnimationContext and tintOverlay.animator().alphaValue, while preserving a
direct alphaValue assignment during the initial configuration path.

@github-actions github-actions Bot added benchmark:regression CI benchmark: significant resource regression vs main benchmark:improvement CI benchmark: significant resource improvement vs main labels Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Window-state benchmark

State Metric main@36930ec77 this branch Δ
focused CPU % 1.10 1.20 +9%
Memory (RSS MB) 108.7 107.7 -1%
CPU ms/s (powermetrics) 11.3 11.2 -0%
Wakeups/s (powermetrics) 116.6 182.7 +57% 🔺
unfocused CPU % 1.30 1.00 -23%
Memory (RSS MB) 113.3 112.2 -1%
CPU ms/s (powermetrics) 12.9 9.5 -26%
Wakeups/s (powermetrics) 145.2 185.6 +28%
minimized CPU % 0.30 0.20 -33%
Memory (RSS MB) 106.7 112.5 +5%
CPU ms/s (powermetrics) 2.3 1.8 -25%
Wakeups/s (powermetrics) 58.0 77.2 +33%
workload-focused CPU % 3.00 2.20 -27% 🔻
Memory (RSS MB) 156.1 163.6 +5%
CPU ms/s (powermetrics) 28.4 21.2 -25% 🔻
Wakeups/s (powermetrics) 249.7 248.4 -1%
workload-unfocused CPU % 2.60 2.20 -15%
Memory (RSS MB) 159.1 163.9 +3%
CPU ms/s (powermetrics) 24.9 21.8 -13%
Wakeups/s (powermetrics) 232.7 267.7 +15%
workload-minimized CPU % 0.40 0.30 -25%
Memory (RSS MB) 159.1 163.9 +3%
CPU ms/s (powermetrics) 3.6 3.3 -10%
Wakeups/s (powermetrics) 146.9 132.6 -10%

⚠️ Labeled benchmark:regression

This PR is labeled benchmark:regression because these metrics regressed by ≥25% vs main@36930ec77 (beyond each metric's absolute noise floor):

  • focused — Wakeups/s (powermetrics): 116.6 → 182.7 (+57%)

🎉 Labeled benchmark:improvement

This PR is labeled benchmark:improvement because these metrics improved by ≥25% vs main@36930ec77 (beyond each metric's absolute noise floor):

  • workload-focused — CPU %: 3.00 → 2.20 (-27%)
  • workload-focused — CPU ms/s (powermetrics): 28.4 → 21.2 (-25%)

Reported value is the median of 3×10s windows per state (splitting the window and taking the median keeps one co-scheduled spike from skewing a state); CPU % is the process CPU-time delta over a window. Runs land on different shared runners, so treat small deltas as noise — 🔺/🔻 marks changes ≥25% that also clear the metric's absolute noise floor (CPU % ≥0.5, Memory (RSS MB) ≥25, CPU ms/s ≥5, Wakeups/s ≥50); CPU deltas off a noise-dominated baseline aren't flagged (CPU % baseline ≥1.5, CPU ms/s baseline ≥15). Flagged changes add the benchmark:regression / benchmark:improvement label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ui Views, Settings UI benchmark:improvement CI benchmark: significant resource improvement vs main benchmark:regression CI benchmark: significant resource regression vs main

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant