Skip to content

fix(terminal): prevent redundant focus events causing Powerlevel10k redraws - #1579

Merged
lawrencecchen merged 1 commit into
manaflow-ai:mainfrom
BillionClaw:clawoss/fix/1566-zsh-powerlevel10k-focus-events
Mar 18, 2026
Merged

lawrencecchen merged 1 commit into
manaflow-ai:mainfrom
BillionClaw:clawoss/fix/1566-zsh-powerlevel10k-focus-events

Conversation

@BillionClaw

@BillionClaw BillionClaw commented Mar 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1566

Problem

When using zsh with Powerlevel10k in cmux, there was a double prompt / extra redraw issue when switching workspaces or using right-aligned elements on the prompt.

Root Cause

The setFocus method in TerminalSurface unconditionally called ghostty_surface_set_focus even when the focus state hadn't changed. This caused focus events (CSI I/O sequences) to be sent to the terminal on every workspace switch, triggering Powerlevel10k to redraw its prompt unnecessarily.

Solution

Add lastFocusState tracking to TerminalSurface to avoid sending duplicate focus events to the terminal surface.

Changes

  • Added private var lastFocusState: Bool = false to track focus state
  • Modified setFocus(_ focused: Bool) to only send focus events when the state actually changes

Testing

  • Build completed successfully with swift build
  • Diff: 7 lines added to Sources/GhosttyTerminalView.swift

Summary by cubic

Prevent redundant focus events in TerminalSurface that triggered zsh/Powerlevel10k prompt redraws and double prompts on workspace switches. Track the last focus state and only call ghostty_surface_set_focus when it changes (fixes #1566).

Written for commit 7ee57c3. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed redundant focus state changes that caused unnecessary terminal prompt redraws with certain terminal themes (e.g., Powerlevel10k).

…edraws

Add lastFocusState tracking to TerminalSurface to avoid sending duplicate
focus events to the terminal surface. This fixes double prompt / extra redraw
issues when using zsh with Powerlevel10k and switching workspaces.

Fixes manaflow-ai#1566
@vercel

vercel Bot commented Mar 17, 2026

Copy link
Copy Markdown

@BillionClaw is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps greptile-apps Bot left a comment

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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 17, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A state tracking mechanism was added to prevent redundant focus events in TerminalSurface. The setFocus(_:) method now compares the requested focus state against a stored previous state and skips sending events when no change has occurred, reducing unintended terminal redraws.

Changes

Cohort / File(s) Summary
Focus State Optimization
Sources/GhosttyTerminalView.swift
Added lastFocusState property to track previous focus state; updated setFocus(_:) to suppress redundant focus events when the requested state matches the stored state, preventing duplicate prompt redraws with multi-line zsh themes.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

Poem

🐰 A prompt that danced and blinked with glee,
Now settles down, as calm can be,
No double draws on focus lost,
Powerlevel's peace regained, not crossed!
One state to track, redundancy gone—
Clean redraws for zsh, all day long. ✨

🚥 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: preventing redundant focus events that cause Powerlevel10k redraws, which is the primary objective of this pull request.
Description check ✅ Passed The description includes a comprehensive summary of the problem, root cause, and solution. However, the Testing section lacks details on manual verification and verification steps, and the Demo Video section is not applicable here.
Linked Issues check ✅ Passed The changes directly address issue #1566 by preventing redundant focus events that were causing Powerlevel10k redraws on workspace switches, implementing the exact solution of tracking last focus state.
Out of Scope Changes check ✅ Passed All changes are focused solely on preventing redundant focus events in TerminalSurface through state tracking; no unrelated modifications are present.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@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 `@Sources/GhosttyTerminalView.swift`:
- Around line 3297-3301: The focus-deduplication boolean lastFocusState can get
out of sync because other code paths call ghostty_surface_set_focus(...)
directly; update TerminalSurface.setFocus(_:) to be the single canonical setter
for focus changes and ensure all first-responder / direct focus transitions
route through it (or update lastFocusState there) so the cache and libghostty
state stay consistent: find usages of ghostty_surface_set_focus(...) outside
TerminalSurface.setFocus(_:) and either replace those calls to invoke
TerminalSurface.setFocus(_:) or add logic to update lastFocusState whenever
ghostty_surface_set_focus(...) is called, keeping lastFocusState,
TerminalSurface.setFocus(_:), and ghostty_surface_set_focus(...) behavior
coherent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a7ff4e6-e1b9-450b-a843-7c7350d99201

📥 Commits

Reviewing files that changed from the base of the PR and between 746fb8e and 7ee57c3.

📒 Files selected for processing (1)
  • Sources/GhosttyTerminalView.swift

Comment on lines +3297 to 3301
// Only send focus events when the state changes to avoid redundant
// prompt redraws with zsh themes like Powerlevel10k.
guard focused != lastFocusState else { return }
lastFocusState = focused
ghostty_surface_set_focus(surface, focused)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Focus dedupe state can desync from actual runtime focus.

Line 3299 relies on lastFocusState, but runtime focus is also changed through direct ghostty_surface_set_focus(...) paths outside TerminalSurface.setFocus(_:). That can leave this cache stale and still allow duplicate focus events (or skip needed ones).

Suggested fix
-    private var lastFocusState: Bool = false
+    private var lastFocusState: Bool?

     func setFocus(_ focused: Bool) {
         guard let surface = surface else { return }
-        guard focused != lastFocusState else { return }
+        if let lastFocusState, focused == lastFocusState { return }
         lastFocusState = focused
         ghostty_surface_set_focus(surface, focused)

Also route first-responder focus transitions through TerminalSurface.setFocus(_:) (or update lastFocusState in those direct call sites) so the dedupe cache and libghostty focus never diverge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 3297 - 3301, The
focus-deduplication boolean lastFocusState can get out of sync because other
code paths call ghostty_surface_set_focus(...) directly; update
TerminalSurface.setFocus(_:) to be the single canonical setter for focus changes
and ensure all first-responder / direct focus transitions route through it (or
update lastFocusState there) so the cache and libghostty state stay consistent:
find usages of ghostty_surface_set_focus(...) outside
TerminalSurface.setFocus(_:) and either replace those calls to invoke
TerminalSurface.setFocus(_:) or add logic to update lastFocusState whenever
ghostty_surface_set_focus(...) is called, keeping lastFocusState,
TerminalSurface.setFocus(_:), and ghostty_surface_set_focus(...) behavior
coherent.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/GhosttyTerminalView.swift">

<violation number="1" location="Sources/GhosttyTerminalView.swift:3299">
P2: lastFocusState persists across surface teardown/recreation, so a newly created ghostty_surface_t can miss its initial focus update when focused state is unchanged, desynchronizing focus state.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

guard let surface = surface else { return }
// Only send focus events when the state changes to avoid redundant
// prompt redraws with zsh themes like Powerlevel10k.
guard focused != lastFocusState else { return }

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: lastFocusState persists across surface teardown/recreation, so a newly created ghostty_surface_t can miss its initial focus update when focused state is unchanged, desynchronizing focus state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/GhosttyTerminalView.swift, line 3299:

<comment>lastFocusState persists across surface teardown/recreation, so a newly created ghostty_surface_t can miss its initial focus update when focused state is unchanged, desynchronizing focus state.</comment>

<file context>
@@ -3291,6 +3294,10 @@ final class TerminalSurface: Identifiable, ObservableObject {
         guard let surface = surface else { return }
+        // Only send focus events when the state changes to avoid redundant
+        // prompt redraws with zsh themes like Powerlevel10k.
+        guard focused != lastFocusState else { return }
+        lastFocusState = focused
         ghostty_surface_set_focus(surface, focused)
</file context>
Fix with Cubic

@lawrencecchen
lawrencecchen merged commit c0fd15c into manaflow-ai:main Mar 18, 2026
3 of 4 checks passed
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…edraws (manaflow-ai#1579)

Add lastFocusState tracking to TerminalSurface to avoid sending duplicate
focus events to the terminal surface. This fixes double prompt / extra redraw
issues when using zsh with Powerlevel10k and switching workspaces.

Fixes manaflow-ai#1566

Co-authored-by: BillionClaw <267901332+BillionClaw@users.noreply.github.com>
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.

Zsh and Powerlevel10k

2 participants