Skip to content

Update ghostty to latest upstream - #2167

Closed
lawrencecchen wants to merge 4 commits into
mainfrom
task-ghostty-upstream-sync
Closed

lawrencecchen wants to merge 4 commits into
mainfrom
task-ghostty-upstream-sync

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Merges 1043 new commits from ghostty-org/ghostty into the manaflow-ai/ghostty fork, then updates the submodule pointer.

All cmux-specific patches preserved:

  • OSC 99 notification parser
  • cursor-click-to-move for OSC133
  • macOS resize frame fixes (display link restart, stale-frame gravity)
  • Pure prompt redraw markers
  • Theme picker helper hooks

Testing


Summary by cubic

Syncs ghostty to the latest upstream (1043 commits), preserves all cmux patches, and adapts to upstream API changes. Adds a debug-only Niri/PaperWM-style terminal canvas with per-panel tabs and drag-and-drop.

  • New Features

    • Debug-only Niri canvas (Cmd+Ctrl+N): per-panel tabs with cross-panel drag-drop and blue drop indicators.
    • Keyboard controls: Cmd+Opt+Arrows / Cmd+Ctrl+H/L navigate, Cmd+Ctrl+R resize, Cmd+T add tab, Cmd+N add panel, Cmd+W/Ctrl+D close, Ctrl+1–9 switch tabs.
    • Smooth 120fps animations, auto-scroll to reveal focused panels.
  • Upstream/API Changes

    • Clipboard read callback now returns Bool; we return true when scheduled and false on missing context.
    • Handle new GHOSTTY_ACTION_SET_TAB_TITLE (ignored; cmux manages titles).
    • Update ghostty.h to upstream: IO mode/write callback, fullscreen enum updates, renderer health rename, new actions/functions; cmux-specific selection APIs retained.

Written for commit 007569f. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Terminal can set tab titles and copy titles to the clipboard.
    • Experimental multi-panel "Niri Canvas" terminal canvas with keyboard controls (debug/experimental).
    • Demo "PaperWM"-style strip and a standalone tab-bar test app added.
  • Bug Fixes

    • Clipboard-read flow now reliably reports success when a read is scheduled/performed.
  • Chores

    • Updated the embedded Ghostty component to a newer checked-in version.

@vercel

vercel Bot commented Mar 25, 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 Mar 26, 2026 10:59pm

@coderabbitai

coderabbitai Bot commented Mar 25, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c00d36b0-bd6c-4c38-ae86-9e839b7e8a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 4576358 and 007569f.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/NiriCanvasView.swift
  • cmuxUITests/NiriCanvasUITests.swift
  • scripts/test-tabbar.swift

📝 Walkthrough

Walkthrough

Updates the Ghostty submodule pointer and corresponding host integration: C API additions (IO enums, io_write callback, new fullscreen and action enums, clipboard callback now returns bool, new surface output API) and Swift-side adaptations plus a new DEBUG-only Niri canvas UI, tests, and demo scripts.

Changes

Cohort / File(s) Summary
Submodule Reference
ghostty
Updated the Git submodule commit pointer to a newer upstream commit.
Public C API
ghostty.h
Added ghostty_surface_io_mode_e, ghostty_io_write_cb and userdata fields in ghostty_surface_config_s; added ghostty_config_fullscreen_e; renamed macOS fullscreen enum variants and renderer health member; added GHOSTTY_ACTION_SET_TAB_TITLE and GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD; changed ghostty_runtime_read_clipboard_cb to return bool; added ghostty_surface_process_output(...).
Swift Ghostty integration
Sources/GhosttyTerminalView.swift
Adjusted clipboard read callback handling to return/propagate Bool and explicitly handle/ignore GHOSTTY_ACTION_SET_TAB_TITLE; updated early-return/true return paths for clipboard scheduling.
Debug canvas UI & wiring
Sources/NiriCanvasView.swift, Sources/AppDelegate.swift, GhosttyTabs.xcodeproj/...
Added DEBUG-only Niri canvas implementation (view, window, controller), debug-only AppDelegate hooks and shortcut (Cmd+Ctrl+N), and project file entries to compile the new source.
UI Tests
cmuxUITests/NiriCanvasUITests.swift
Added UI test that opens the canvas, creates tabs, and performs synthetic raw mouse drag to exercise tab-drag/reorder behavior.
Demos / Scripts
scripts/demo-paperwm.swift, scripts/test-tabbar.swift
Added executable demo scripts: PaperWM-style animated strip demo and a Tab Bar test app demonstrating tab drag/reorder interactions.

Sequence Diagram(s)

sequenceDiagram
    participant UI as App UI
    participant Surface as Ghostty Surface
    participant Runtime as Ghostty Runtime
    participant Clipboard as System Clipboard

    UI->>Surface: provide input / schedule IO (io_mode / io_write_cb)
    Surface->>Runtime: ghostty_surface_process_output(data)
    Runtime->>Surface: emit action (e.g., COPY_TITLE_TO_CLIPBOARD / SET_TAB_TITLE)
    alt COPY_TITLE_TO_CLIPBOARD
        Surface->>Runtime: runtime_read_clipboard_cb(... ) -> bool
        alt returns true
            Runtime->>Clipboard: schedule/read clipboard
            Clipboard-->>Runtime: result / completion
            Surface-->>UI: acknowledge action handled
        else returns false
            Surface-->>UI: treat as unhandled
        end
    else SET_TAB_TITLE
        Surface-->>UI: ignore terminal title override (handled elsewhere)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped to a newer SHA today,
New enums and callbacks paved my way,
Titles politely waved aside,
Clipboard asks now answer with pride,
I nap, then hop again—hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: updating the ghostty submodule to the latest upstream commit (from bc9be90a to 2c530daf). While secondary changes (NiriCanvasView prototype) are present, the primary modification is the submodule update.
Description check ✅ Passed The PR description provides a clear summary and testing approach aligned with the template, though it lacks explicit mention of a demo video and the checklist is incomplete.

✏️ 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 task-ghostty-upstream-sync

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 Mar 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates the ghostty submodule pointer from bc9be90a to f41d302a, incorporating 1043 new upstream commits from ghostty-org/ghostty into the manaflow-ai/ghostty fork while preserving all cmux-specific patches (OSC 99 notification parser, macOS resize frame fixes, prompt redraw markers, and theme picker hooks).

Key concerns:

  • Submodule commit not on manaflow-ai/ghostty:main: The PR description shows the fork work lives on task-ghostty-upstream-sync, not main. CLAUDE.md explicitly requires the submodule commit to be reachable from the fork's main branch before updating the parent-repo pointer — otherwise git submodule update will break for all developers and CI once the branch is deleted.
  • docs/ghostty-fork.md not updated: CLAUDE.md requires this file to be updated with new change summaries and conflict notes whenever the fork changes. The document still records "March 12, 2026" as its last sync date and does not reflect the new upstream base or any conflicts resolved during the 1043-commit rebase.
  • Zig build cannot be tested locally (macOS 26 linker issue, pre-existing), so CI is the only verification gate.

Confidence Score: 3/5

  • Hold until the fork commit is verified on manaflow-ai/ghostty:main and docs/ghostty-fork.md is updated.
  • The submodule pointer currently references a commit that, per the PR description, lives on the task-ghostty-upstream-sync branch rather than main. CLAUDE.md is explicit that this must be resolved before the parent-repo pointer lands — an orphaned commit breaks git submodule update for all developers and CI. The docs/ghostty-fork.md is also stale (still dated March 12, 2026) after a 1043-commit rebase. Both are straightforward to fix before merge.
  • ghostty (submodule pointer) — verify f41d302a is reachable from manaflow-ai/ghostty:main. docs/ghostty-fork.md — needs update to reflect new upstream base and any resolved conflicts.

Important Files Changed

Filename Overview
ghostty Submodule pointer bumped from bc9be90a to f41d302a (1043 upstream commits). New commit appears to be on a feature branch rather than manaflow-ai/ghostty:main, which risks orphaning the pointer.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["ghostty-org/ghostty (upstream)"] -->|"1043 new commits merged"| B["manaflow-ai/ghostty\ntask-ghostty-upstream-sync branch"]
    B -->|"cmux patches preserved\n(OSC 99, resize fixes, prompt markers,\ntheme picker hooks)"| B
    B -->|"⚠️ Should merge to main FIRST"| C["manaflow-ai/ghostty:main"]
    C -->|"submodule pointer update\nbc9be90a → f41d302a"| D["manaflow-ai/cmux\nghostty submodule"]
    
    style B fill:#ffcc00,stroke:#cc9900
    style C fill:#90EE90,stroke:#228B22
    style D fill:#87CEEB,stroke:#4682B4
Loading

Reviews (1): Last reviewed commit: "Update ghostty submodule to latest upstr..." | Re-trigger Greptile

Comment thread ghostty Outdated
@@ -1 +1 @@
Subproject commit bc9be90a21997a4e5f06bf15ae2ec0f937c2dc42
Subproject commit f41d302a378277d76c3736f9ad5f6d04523bce9e

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.

P1 Submodule commit not on manaflow-ai/ghostty:main

The PR description states the Ghostty fork work lives on the task-ghostty-upstream-sync branch (https://github.com/manaflow-ai/ghostty/tree/task-ghostty-upstream-sync), but CLAUDE.md mandates:

Submodule safety: When modifying a submodule (ghostty, vendor/bonsplit, etc.), always push the submodule commit to its remote main branch BEFORE committing the updated pointer in the parent repo. Never commit on a detached HEAD or temporary branch — the commit will be orphaned and lost. Verify with: cd <submodule> && git merge-base --is-ancestor HEAD origin/main.

The new submodule pointer (f41d302a) must be reachable from manaflow-ai/ghostty:main before this parent-repo commit lands. If the task-ghostty-upstream-sync branch is ever deleted or not merged, the pointer will be orphaned and git submodule update will fail for all developers and CI.

Please merge (or push) the fork branch to manaflow-ai/ghostty:main and verify:

cd ghostty
git merge-base --is-ancestor f41d302a378277d76c3736f9ad5f6d04523bce9e origin/main

before merging this PR.

Context Used: CLAUDE.md (source)

@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.

No issues found across 1 file

@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 `@ghostty`:
- Line 1: Add a new entry to the ghostty-fork.md sync history documenting the
submodule pointer bump to origin/main: include the exact commit hash that the
submodule now points to, a short bullet-list of upstream changes that were
brought in by that commit (features/bugfixes/other notable diffs), any conflict
resolution steps taken (files changed, manual edits made, or "no conflicts"),
and a brief timestamp/author line; place this entry under the existing "Update
this file with the new change summary + conflict notes" section so the sync
history stays up to date and clearly indicates the new pointer bump to
origin/main.
- Line 1: Push the ghostty submodule commit
f41d302a378277d76c3736f9ad5f6d04523bce9e to the manaflow-ai/ghostty repo and
ensure origin/main contains that commit (so the submodule HEAD is an ancestor of
origin/main); then update docs/ghostty-fork.md by adding a new "Current fork
changes" entry summarizing the sync and any conflicts for this commit; finally
compute the sha256 of the built GhosttyKit artifact for commit f41d302a...
(f41d302a378277d76c3736f9ad5f6d04523bce9e) and append a line with that checksum
and commit id to scripts/ghosttykit-checksums.txt so CI can validate it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77ac0cbb-d805-4eac-b1a9-7bf688171f32

📥 Commits

Reviewing files that changed from the base of the PR and between 99ca3c9 and 4ed8698.

📒 Files selected for processing (1)
  • ghostty

Comment thread ghostty Outdated
@@ -1 +1 @@
Subproject commit bc9be90a21997a4e5f06bf15ae2ec0f937c2dc42
Subproject commit f41d302a378277d76c3736f9ad5f6d04523bce9e

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Verify submodule commit ancestry against remote main"
git -C ghostty fetch origin main
git -C ghostty rev-parse HEAD
git -C ghostty merge-base --is-ancestor HEAD origin/main && echo "OK: HEAD is on origin/main ancestry"

echo
echo "Check whether docs mention this sync commit/context"
rg -n 'f41d302a378277d76c3736f9ad5f6d04523bce9e|upstream sync|conflict' docs/ghostty-fork.md || true

Repository: manaflow-ai/cmux

Length of output: 574


Update docs/ghostty-fork.md with this submodule commit sync details.

The ghostty submodule pointer is correctly positioned on origin/main. Add an entry to the fork documentation documenting this pointer bump, including the commit hash, any upstream changes synced, and any conflict resolution notes (if applicable). Keep the sync history in the doc up to date as specified in the "Update this file with the new change summary + conflict notes" section (line 10 of the doc).

🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: GhosttyKit checksum missing for ghostty f41d302a378277d76c3736f9ad5f6d04523bce9e in scripts/ghosttykit-checksums.txt; step failed (exit code 1).

🪛 GitHub Actions: macOS Compatibility

[error] 1-1: Missing pinned GhosttyKit checksum for ghostty f41d302a378277d76c3736f9ad5f6d04523bce9e in /Users/runner/work/cmux/cmux/scripts/ghosttykit-checksums.txt

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

In `@ghostty` at line 1, Add a new entry to the ghostty-fork.md sync history
documenting the submodule pointer bump to origin/main: include the exact commit
hash that the submodule now points to, a short bullet-list of upstream changes
that were brought in by that commit (features/bugfixes/other notable diffs), any
conflict resolution steps taken (files changed, manual edits made, or "no
conflicts"), and a brief timestamp/author line; place this entry under the
existing "Update this file with the new change summary + conflict notes" section
so the sync history stays up to date and clearly indicates the new pointer bump
to origin/main.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate checksum file and confirm missing/new entries"
fd ghosttykit-checksums.txt
rg -n 'bc9be90a21997a4e5f06bf15ae2ec0f937c2dc42|f41d302a378277d76c3736f9ad5f6d04523bce9e' scripts/ghosttykit-checksums.txt || true

echo
echo "Show checksum file format (use this to add the new line in correct format)"
sed -n '1,120p' scripts/ghosttykit-checksums.txt

Repository: manaflow-ai/cmux

Length of output: 1266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify ghostty submodule commit is on origin/main ==="
cd ghostty 2>/dev/null && {
  echo "Current HEAD: $(git rev-parse HEAD)"
  echo "Checking ancestry against origin/main..."
  if git merge-base --is-ancestor HEAD origin/main 2>/dev/null; then
    echo "✓ Current HEAD is ancestor of origin/main (correct)"
  else
    echo "✗ WARNING: Current HEAD is NOT on the main branch path"
  fi
  git log --oneline -5
  cd - > /dev/null
} || echo "ghostty directory not found or not a git repo"

echo
echo "=== Check docs/ghostty-fork.md for sync notes ==="
if [ -f docs/ghostty-fork.md ]; then
  echo "Found docs/ghostty-fork.md. Checking content:"
  head -50 docs/ghostty-fork.md
  echo "..."
  echo "[File exists; checking for any TODOs or outdated notes]"
  rg -i 'todo|fixme|outdated|needs update|sync' docs/ghostty-fork.md || echo "No update markers found"
else
  echo "docs/ghostty-fork.md not found"
fi

echo
echo "=== Verify no other missing checksum entries exist ==="
echo "Total entries in checksums file:"
wc -l scripts/ghosttykit-checksums.txt
echo "Checking for duplicate or malformed entries:"
rg '^\w{40}\s+\w{64}$' scripts/ghosttykit-checksums.txt | wc -l

Repository: manaflow-ai/cmux

Length of output: 2944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Get the sha256 for the f41d302 commit ==="
# Check if there's any documentation or recent build that might have this checksum
rg -B2 -A2 'f41d302|4ed8698' scripts/ || echo "No cached checksums found for these commits"

echo
echo "=== Check git history of checksums file for format examples ==="
git log --oneline scripts/ghosttykit-checksums.txt | head -10

echo
echo "=== Identify the exact commit being referenced in the PR ==="
cd ghostty 2>/dev/null && git show f41d302a378277d76c3736f9ad5f6d04523bce9e --oneline || echo "Commit not in ghostty repo"

Repository: manaflow-ai/cmux

Length of output: 559


Follow the documented submodule update process before committing this pointer.

This PR violates three required steps from the docs/ghostty-fork.md checklist:

  1. Submodule not pushed to origin/main: The current ghostty HEAD is not an ancestor of origin/main. Per the checklist, changes must be committed and pushed to manaflow-ai/ghostty before updating the parent submodule pointer.

  2. Missing fork change documentation: docs/ghostty-fork.md has not been updated with a new entry documenting this fork sync (no new section under "Current fork changes"). The checklist requires: "Update this file with the new change summary + conflict notes."

  3. Missing GhosttyKit checksum: scripts/ghosttykit-checksums.txt has no entry for commit f41d302a378277d76c3736f9ad5f6d04523bce9e. Add the sha256 checksum for the built GhosttyKit artifact on this commit (required for CI).

Resolve all three in order:

  • Push the ghostty submodule commit to manaflow-ai/ghostty main and verify it is reachable from origin/main
  • Update docs/ghostty-fork.md with the fork change summary
  • Add the corresponding checksum entry to scripts/ghosttykit-checksums.txt
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: GhosttyKit checksum missing for ghostty f41d302a378277d76c3736f9ad5f6d04523bce9e in scripts/ghosttykit-checksums.txt; step failed (exit code 1).

🪛 GitHub Actions: macOS Compatibility

[error] 1-1: Missing pinned GhosttyKit checksum for ghostty f41d302a378277d76c3736f9ad5f6d04523bce9e in /Users/runner/work/cmux/cmux/scripts/ghosttykit-checksums.txt

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

In `@ghostty` at line 1, Push the ghostty submodule commit
f41d302a378277d76c3736f9ad5f6d04523bce9e to the manaflow-ai/ghostty repo and
ensure origin/main contains that commit (so the submodule HEAD is an ancestor of
origin/main); then update docs/ghostty-fork.md by adding a new "Current fork
changes" entry summarizing the sync and any conflicts for this commit; finally
compute the sha256 of the built GhosttyKit artifact for commit f41d302a...
(f41d302a378277d76c3736f9ad5f6d04523bce9e) and append a line with that checksum
and commit id to scripts/ghosttykit-checksums.txt so CI can validate it.

Merges ghostty-org/ghostty main into manaflow-ai/ghostty fork,
preserving all cmux-specific patches (OSC 99, cursor-click-to-move,
resize fixes, Pure prompt markers, theme picker).
- read_clipboard_cb now returns Bool (was Void)
- Handle new GHOSTTY_ACTION_SET_TAB_TITLE action
- Update ghostty.h to match new upstream header, preserving cmux-specific
  declarations (select_cursor_cell, clear_selection)

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9aa71fd006

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2455 to +2458
case GHOSTTY_ACTION_SET_TAB_TITLE:
// Tab title override from the terminal (e.g. OSC escape).
// cmux handles tab titles through its own tab manager, so we ignore this.
return true

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 Badge Forward tab-title actions to title update path

GHOSTTY_ACTION_SET_TAB_TITLE is now marked handled but ignored, which drops terminal-provided title changes (e.g. OSC title updates) before they reach cmux’s title pipeline. TabManager updates panel/window titles from .ghosttyDidSetTitle, and this branch never posts that notification, so users can end up with stale tab/toolbar titles after the Ghostty upgrade.

Useful? React with 👍 / 👎.

@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 `@ghostty.h`:
- Around line 907-908: Add a new switch case for
GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD in the same Swift action handler switch
that already handles GHOSTTY_ACTION_SET_TAB_TITLE; implement it to read the
provided title payload (same source used by GHOSTTY_ACTION_SET_TAB_TITLE) and
copy that string to the system clipboard using the existing project clipboard
utility or NSPasteboard, matching error handling/logging style of the
surrounding cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e712da17-6269-488e-90c1-327d315d93e6

📥 Commits

Reviewing files that changed from the base of the PR and between 72e846c and 9aa71fd.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • ghostty.h

Comment thread ghostty.h
Comment on lines +907 to 908
GHOSTTY_ACTION_SET_TAB_TITLE,
GHOSTTY_ACTION_PROMPT_TITLE,

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Swift handles the new action tags
rg -n 'SET_TAB_TITLE|COPY_TITLE_TO_CLIPBOARD' --type swift -C3

# Look at action handler implementation
rg -n 'action_cb|ghostty_action_tag_e' --type swift -A10 -B2

Repository: manaflow-ai/cmux

Length of output: 1753


🏁 Script executed:

# Search more broadly for any mention of COPY_TITLE or copy_title
rg -n 'COPY_TITLE|copy_title' --type swift

# Get the full action handler switch statement to see all cases
rg -n 'case GHOSTTY_ACTION' --type swift | head -50

Repository: manaflow-ai/cmux

Length of output: 2073


🏁 Script executed:

# Check ghostty.h around line 938 to confirm the action is defined
sed -n '930,945p' ghostty.h

# Search entire repo for COPY_TITLE_TO_CLIPBOARD to see if it's defined anywhere
rg -n 'COPY_TITLE_TO_CLIPBOARD' --type c --type h

Repository: manaflow-ai/cmux

Length of output: 632


Add Swift case for GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD.

The action is defined in ghostty.h (line 938) but missing from the Swift action handler. GHOSTTY_ACTION_SET_TAB_TITLE is correctly handled at line 2455, but GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD requires a case statement in the same switch block.

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

In `@ghostty.h` around lines 907 - 908, Add a new switch case for
GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD in the same Swift action handler switch
that already handles GHOSTTY_ACTION_SET_TAB_TITLE; implement it to read the
provided title payload (same source used by GHOSTTY_ACTION_SET_TAB_TITLE) and
copy that string to the system clipboard using the existing project clipboard
utility or NSPasteboard, matching error handling/logging style of the
surrounding cases.

Horizontal strip layout with real ghostty terminal surfaces. Each
terminal renders independently via ghostty's Metal renderer. Features:
- Cmd+Opt+Arrow / Cmd+Ctrl+H/L to navigate between panels
- Cmd+Ctrl+R to cycle per-panel resize presets (33%/67%/100%)
- Cmd+W / Ctrl+D to close, Cmd+T to add new terminal
- Smooth 120fps animation via CVDisplayLink
- Smart scroll: only moves when panel is off-screen
- Animated resize with left-edge pinning and visibility clamping

Triggered with Cmd+Ctrl+N in debug builds.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4576358bdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)
}
}
return true

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 Badge Return failure when clipboard read is dropped

read_clipboard_cb now always returns true after enqueueing work, but the main-queue block can still bail out at callbackContext.runtimeSurface == nil without ever calling ghostty_surface_complete_clipboard_request. In that close-race path (for example, paste requested and surface closes immediately), Ghostty is told the request was handled even though no completion is possible, which can leave the read unresolved. Return false when the request cannot be completed (or guarantee completion even on teardown).

Useful? React with 👍 / 👎.

styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false
)
win.title = "Terminal Canvas"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Localize the new Niri canvas window title

The new debug window title is hardcoded as "Terminal Canvas", but this repo’s policy requires all user-visible UI strings to go through String(localized:..., defaultValue:...). Shipping this as-is introduces an untranslated UI string in non-English builds and bypasses the localization pipeline.

Useful? React with 👍 / 👎.

@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: 5

🧹 Nitpick comments (3)
scripts/demo-paperwm.swift (1)

299-312: Consider simplifying the interpolation logic.

The current animation applies two consecutive blends per tick (lines 304-305), which is unconventional. Line 305 alone would produce smooth interpolation toward targetOffset:

scrollOffset = scrollOffset * damping + targetOffset * (1 - damping)

The current approach works for this demo but could be simplified if this pattern is later adapted for production use.

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

In `@scripts/demo-paperwm.swift` around lines 299 - 312, The tick() function
currently applies two blends to scrollOffset (first adding diff * stiffness then
applying a damping blend), which is redundant; simplify by removing the initial
stiffness step (the calculation that uses diff * stiffness) and use only the
damping blend to interpolate scrollOffset toward targetOffset (i.e., set
scrollOffset using the damping/targetOffset blend), keep the proximity snap
check (abs(diff) < 0.5) and the updateLayout(animated: false) call intact so
behavior remains unchanged aside from the simplified interpolation.
Sources/AppDelegate.swift (1)

9086-9091: Log this new debug key event path.

Please add a dlog(...) line when this shortcut fires so the DEBUG key-routing trail is complete.

Proposed tweak
         if normalizedFlags == [.command, .control] && (chars.lowercased() == "n" || event.keyCode == 45) {
+            dlog("shortcut.action name=openNiriCanvas keyCode=\(event.keyCode)")
             openNiriCanvas()
             return true
         }

Based on learnings: Applies to **/*.swift : All debug events (keys, mouse, focus, splits, tabs) in DEBUG builds go to the debug event log. Use free function dlog("message") to log events.

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

In `@Sources/AppDelegate.swift` around lines 9086 - 9091, Add a debug-log call
when the DEBUG-only key shortcut is handled: inside the conditional that checks
hasCommand && hasControl && !flags.contains(.shift) && (chars.lowercased() ==
"n" || event.keyCode == 45), call dlog(...) with a clear message (e.g. "DEBUG
key event: openNiriCanvas shortcut triggered") immediately before invoking
openNiriCanvas() so the event appears in the debug event log; keep the dlog call
inside the same `#if` DEBUG block and include identifying info such as chars or
event.keyCode if desired.
Sources/NiriCanvasView.swift (1)

34-38: Consider idling and coalescing the display-link ticks.

The link starts in setup() and enqueues a main-thread tick() every refresh for the full window lifetime. With no idle gate or pending-tick coalescing, this keeps relayout running at display refresh even when nothing is moving, and a busy main thread can accumulate stale ticks. A small “is animating?” / “tick already queued?” gate would make this much cheaper.

Also applies to: 280-289, 291-340

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

In `@Sources/NiriCanvasView.swift` around lines 34 - 38, The display-link created
in setup() via startDisplayLink() currently enqueues tick() on every refresh
regardless of activity; add a simple boolean gate (e.g., isAnimating) and a
"tickQueued"/pendingTick flag to coalesce/skip redundant ticks so that
startDisplayLink()/display callback only schedules a tick when isAnimating is
true and if no tick is already queued; ensure tick() clears tickQueued when it
begins and that stopDisplayLink() (or when isAnimating becomes false) prevents
further scheduling. Update references in setup(), startDisplayLink(), the
display callback, and tick() to use these flags so idle canvases do not
continuously enqueue main-thread work.
🤖 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/AppDelegate.swift`:
- Around line 9086-9091: The current shortcut condition (using hasCommand &&
hasControl && !flags.contains(.shift)) still matches extra modifiers (e.g.,
Option) and should require an exact modifier set; replace the loose checks with
an exact comparison using the event's modifier flags (e.g., compare
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command,
.control]) so the branch that calls openNiriCanvas() only runs when modifiers
are exactly Command+Control and no other modifiers are present; update the
condition that references hasCommand, hasControl, flags, chars.lowercased(), and
event.keyCode accordingly.

In `@Sources/NiriCanvasView.swift`:
- Around line 177-197: The current ensureVisible implementation only clamps
targetOffset to a minimum of 0, which allows it to exceed the scrollable extent;
compute a maxScrollOffset from the current animated content widths and close
progress (the same value used to render the animated content width) and clamp
both targetOffset and scrollOffset to the range [0, maxScrollOffset]; update the
ensureVisible function (reference: ensureVisible(panelStart:panelWidth:),
variables targetOffset and viewport/maxW) to use maxScrollOffset and also apply
the same [0, maxScrollOffset] clamping in the other places that adjust offsets
(the other offset-update code paths referenced in the review where
scrollOffset/targetOffset are modified) so no offset can go past the animated
content width.
- Line 378: Replace the bare UI string assigned to win.title ("Terminal Canvas")
with a localized lookup using String(localized: "window.terminalCanvas.title",
defaultValue: "Terminal Canvas") and add the key "window.terminalCanvas.title"
with the English value "Terminal Canvas" to Resources/Localizable.xcstrings (and
translations for other languages); update the assignment at the win.title site
in NiriCanvasView (where win.title is set) so the window title uses that
localized string helper.
- Around line 265-273: nearestLiveIndex(forOffset:) currently compares panel
centers to the viewport left edge (offset) which biases selection; compute the
viewport midpoint instead and use that for distance comparisons. Replace uses of
offset in nearestLiveIndex(forOffset:) with a midpoint value (e.g. let
viewportMid = offset + self.bounds.width / 2 or your visibleWidth property) and
compare abs(mid - viewportMid) when computing bestDist; keep the rest of the
loop (pw(for:), panelGap, slots, li) unchanged so you still return the index of
the nearest live panel.
- Around line 213-218: In closeFocusedTerminal(), the code currently checks only
if let s = slots[si].surface.surface before calling
ghostty_surface_request_close(s), which can pass a stale pointer; change the
guard so you check the TerminalSurface live flag first (use the
TerminalSurface.hasLiveSurface / portalLifecycleState check on
slots[si].surface) and only call ghostty_surface_request_close(s) when
hasLiveSurface is true and surface.surface is non-nil (i.e., ensure
slots[si].surface.hasLiveSurface && slots[si].surface.surface != nil before
invoking ghostty_surface_request_close).

---

Nitpick comments:
In `@scripts/demo-paperwm.swift`:
- Around line 299-312: The tick() function currently applies two blends to
scrollOffset (first adding diff * stiffness then applying a damping blend),
which is redundant; simplify by removing the initial stiffness step (the
calculation that uses diff * stiffness) and use only the damping blend to
interpolate scrollOffset toward targetOffset (i.e., set scrollOffset using the
damping/targetOffset blend), keep the proximity snap check (abs(diff) < 0.5) and
the updateLayout(animated: false) call intact so behavior remains unchanged
aside from the simplified interpolation.

In `@Sources/AppDelegate.swift`:
- Around line 9086-9091: Add a debug-log call when the DEBUG-only key shortcut
is handled: inside the conditional that checks hasCommand && hasControl &&
!flags.contains(.shift) && (chars.lowercased() == "n" || event.keyCode == 45),
call dlog(...) with a clear message (e.g. "DEBUG key event: openNiriCanvas
shortcut triggered") immediately before invoking openNiriCanvas() so the event
appears in the debug event log; keep the dlog call inside the same `#if` DEBUG
block and include identifying info such as chars or event.keyCode if desired.

In `@Sources/NiriCanvasView.swift`:
- Around line 34-38: The display-link created in setup() via startDisplayLink()
currently enqueues tick() on every refresh regardless of activity; add a simple
boolean gate (e.g., isAnimating) and a "tickQueued"/pendingTick flag to
coalesce/skip redundant ticks so that startDisplayLink()/display callback only
schedules a tick when isAnimating is true and if no tick is already queued;
ensure tick() clears tickQueued when it begins and that stopDisplayLink() (or
when isAnimating becomes false) prevents further scheduling. Update references
in setup(), startDisplayLink(), the display callback, and tick() to use these
flags so idle canvases do not continuously enqueue main-thread work.
🪄 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: dab98a3c-2bda-4f3e-a861-41af94a0af07

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa71fd and 4576358.

📒 Files selected for processing (4)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/AppDelegate.swift
  • Sources/NiriCanvasView.swift
  • scripts/demo-paperwm.swift

Comment thread Sources/AppDelegate.swift
Comment on lines +9086 to +9091
#if DEBUG
// Cmd+Ctrl+N: Open niri canvas demo
if hasCommand && hasControl && !flags.contains(.shift) && (chars.lowercased() == "n" || event.keyCode == 45) {
openNiriCanvas()
return true
}

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 | 🟡 Minor

Require an exact modifier match for the Cmd+Ctrl+N shortcut.

Line 9088 currently also matches combinations like Cmd+Ctrl+Option+N. That can unintentionally steal unrelated shortcuts.

Proposed fix
-        if hasCommand && hasControl && !flags.contains(.shift) && (chars.lowercased() == "n" || event.keyCode == 45) {
+        if normalizedFlags == [.command, .control] && (chars.lowercased() == "n" || event.keyCode == 45) {
             openNiriCanvas()
             return true
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 9086 - 9091, The current shortcut
condition (using hasCommand && hasControl && !flags.contains(.shift)) still
matches extra modifiers (e.g., Option) and should require an exact modifier set;
replace the loose checks with an exact comparison using the event's modifier
flags (e.g., compare
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == [.command,
.control]) so the branch that calls openNiriCanvas() only runs when modifiers
are exactly Command+Control and no other modifiers are present; update the
condition that references hasCommand, hasControl, flags, chars.lowercased(), and
event.keyCode accordingly.

Comment thread Sources/NiriCanvasView.swift
Comment thread Sources/NiriCanvasView.swift Outdated
Comment on lines +213 to +218
func closeFocusedTerminal() {
let live = liveIndices
guard !live.isEmpty, focusedIndex < live.count else { return }
let si = live[focusedIndex].slot
slots[si].closing = true
if let s = slots[si].surface.surface { ghostty_surface_request_close(s) }

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, read the current code at NiriCanvasView.swift around lines 213-218
cat -n Sources/NiriCanvasView.swift | sed -n '210,225p'

Repository: manaflow-ai/cmux

Length of output: 659


🏁 Script executed:

# Search for hasLiveSurface in GhosttyTerminalView.swift
rg "hasLiveSurface" Sources/GhosttyTerminalView.swift -B 2 -A 2

Repository: manaflow-ai/cmux

Length of output: 348


🏁 Script executed:

# Check the comments around lines 2831-2840 in GhosttyTerminalView.swift
cat -n Sources/GhosttyTerminalView.swift | sed -n '2825,2845p'

Repository: manaflow-ai/cmux

Length of output: 1156


🏁 Script executed:

# Search for TerminalSurface class definition and related safety properties
ast-grep --pattern 'class TerminalSurface {
  $$$
}'

Repository: manaflow-ai/cmux

Length of output: 42


🏁 Script executed:

# Check the slots property definition in NiriCanvasView.swift
rg "slots.*:" Sources/NiriCanvasView.swift | head -5

Repository: manaflow-ai/cmux

Length of output: 330


🏁 Script executed:

# Search for TerminalSurface type definition to confirm it's what we're dealing with
rg "class TerminalSurface" Sources/GhosttyTerminalView.swift -A 5

Repository: manaflow-ai/cmux

Length of output: 472


🏁 Script executed:

# Check how ghostty_surface_request_close is used elsewhere in the codebase
rg "ghostty_surface_request_close" --type swift -B 2 -A 2

Repository: manaflow-ai/cmux

Length of output: 447


🏁 Script executed:

# Find the Slot definition in NiriCanvasView.swift
rg "struct Slot|class Slot" Sources/NiriCanvasView.swift -A 5

Repository: manaflow-ai/cmux

Length of output: 297


Guard ghostty_surface_request_close with hasLiveSurface.

Line 218 passes a stale pointer to a Ghostty C API call. TerminalSurface explicitly documents at GhosttyTerminalView.swift:2834–2839 that hasLiveSurface must be checked before calling C APIs that dereference surface, since a non-nil pointer is insufficient when portal teardown or reparenting is in progress. The current code only checks if let s = slots[si].surface.surface, which omits the required portalLifecycleState check and can race close/reparent paths and crash.

Suggested fix
 func closeFocusedTerminal() {
     let live = liveIndices
     guard !live.isEmpty, focusedIndex < live.count else { return }
     let si = live[focusedIndex].slot
-    slots[si].closing = true
-    if let s = slots[si].surface.surface { ghostty_surface_request_close(s) }
+    let terminalSurface = slots[si].surface
+    slots[si].closing = true
+    if terminalSurface.hasLiveSurface, let s = terminalSurface.surface {
+        ghostty_surface_request_close(s)
+    }
     if liveCount > 0 {
         focusedIndex = min(focusedIndex, liveCount - 1)
         focusCurrentTerminal()
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func closeFocusedTerminal() {
let live = liveIndices
guard !live.isEmpty, focusedIndex < live.count else { return }
let si = live[focusedIndex].slot
slots[si].closing = true
if let s = slots[si].surface.surface { ghostty_surface_request_close(s) }
func closeFocusedTerminal() {
let live = liveIndices
guard !live.isEmpty, focusedIndex < live.count else { return }
let si = live[focusedIndex].slot
let terminalSurface = slots[si].surface
slots[si].closing = true
if terminalSurface.hasLiveSurface, let s = terminalSurface.surface {
ghostty_surface_request_close(s)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/NiriCanvasView.swift` around lines 213 - 218, In
closeFocusedTerminal(), the code currently checks only if let s =
slots[si].surface.surface before calling ghostty_surface_request_close(s), which
can pass a stale pointer; change the guard so you check the TerminalSurface live
flag first (use the TerminalSurface.hasLiveSurface / portalLifecycleState check
on slots[si].surface) and only call ghostty_surface_request_close(s) when
hasLiveSurface is true and surface.surface is non-nil (i.e., ensure
slots[si].surface.hasLiveSurface && slots[si].surface.surface != nil before
invoking ghostty_surface_request_close).

Comment thread Sources/NiriCanvasView.swift Outdated
styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false
)
win.title = "Terminal Canvas"

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 | 🟡 Minor

Localize the window title.

Line 378 introduces a bare UI string. This should go through String(localized:..., defaultValue: ...), and the key should be added to Resources/Localizable.xcstrings.

Suggested fix
-        win.title = "Terminal Canvas"
+        win.title = String(localized: "niriCanvas.windowTitle", defaultValue: "Terminal Canvas")

As per coding guidelines, "All user-facing strings must be localized using String(localized: "key.name", defaultValue: "English text") for every string shown in the UI. Keys must go in Resources/Localizable.xcstrings with translations for all supported languages. Never use bare string literals in SwiftUI Text(), Button(), alert titles, or other UI elements".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
win.title = "Terminal Canvas"
win.title = String(localized: "niriCanvas.windowTitle", defaultValue: "Terminal Canvas")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/NiriCanvasView.swift` at line 378, Replace the bare UI string
assigned to win.title ("Terminal Canvas") with a localized lookup using
String(localized: "window.terminalCanvas.title", defaultValue: "Terminal
Canvas") and add the key "window.terminalCanvas.title" with the English value
"Terminal Canvas" to Resources/Localizable.xcstrings (and translations for other
languages); update the assignment at the win.title site in NiriCanvasView (where
win.title is set) so the window title uses that localized string helper.

@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.

3 issues found across 4 files (changes from recent commits).

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="scripts/demo-paperwm.swift">

<violation number="1" location="scripts/demo-paperwm.swift:248">
P2: Do not conditionally negate scroll deltas based on `isDirectionInvertedFromDevice`; doing so breaks macOS Natural Scrolling.</violation>
</file>

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

<violation number="1" location="Sources/NiriCanvasView.swift:196">
P2: Clamp `targetOffset`/`scrollOffset` to the strip’s max scroll extent, not only zero, so repeated wheel input cannot scroll the entire panel strip out of view.</violation>

<violation number="2" location="Sources/NiriCanvasView.swift:281">
P2: Avoid adding an app-level display link; the project guidelines warn that display links can introduce typing lag. Consider driving these animations through Ghostty wakeups or a scoped timer that only runs while resizing/closing instead of a global CVDisplayLink.</violation>
</file>

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


override func scrollWheel(with event: NSEvent) {
// Horizontal scroll (trackpad or shift+scroll)
var dx = event.scrollingDeltaX

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 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: Do not conditionally negate scroll deltas based on isDirectionInvertedFromDevice; doing so breaks macOS Natural Scrolling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/demo-paperwm.swift, line 248:

<comment>Do not conditionally negate scroll deltas based on `isDirectionInvertedFromDevice`; doing so breaks macOS Natural Scrolling.</comment>

<file context>
@@ -0,0 +1,368 @@
+
+    override func scrollWheel(with event: NSEvent) {
+        // Horizontal scroll (trackpad or shift+scroll)
+        var dx = event.scrollingDeltaX
+        if event.isDirectionInvertedFromDevice { dx = -dx }
+
</file context>
Fix with Cubic

Comment thread Sources/NiriCanvasView.swift
}
// else: already fully visible, don't move
}
targetOffset = max(0, targetOffset)

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 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: Clamp targetOffset/scrollOffset to the strip’s max scroll extent, not only zero, so repeated wheel input cannot scroll the entire panel strip out of view.

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

<comment>Clamp `targetOffset`/`scrollOffset` to the strip’s max scroll extent, not only zero, so repeated wheel input cannot scroll the entire panel strip out of view.</comment>

<file context>
@@ -0,0 +1,415 @@
+            }
+            // else: already fully visible, don't move
+        }
+        targetOffset = max(0, targetOffset)
+    }
+
</file context>
Fix with Cubic

- Per-panel tab system: each panel has its own tab bar with tabs
- Cmd+T adds tab, Cmd+N adds panel, Ctrl+1-9 switches tabs
- Cross-panel tab drag with floating drag image and blue drop indicators
- Drop onto another panel's tab bar moves the tab there
- Drop in gap between panels creates a new panel (inherits source width)
- Escape cancels drag
- Auto-scroll: hovering over a panel during drag scrolls it into view
- Click on terminal/tab focuses that panel and scrolls it into view
- Fixed focus-after-drop: recomputes live index after panel changes
- Fixed hitTest focus stealing: removed side-effect from hitTest,
  focus-on-click handled in NiriCanvasWindow.sendEvent instead
- All operations logged via dlog for debugging
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@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 4 files (changes from recent commits).

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="cmuxUITests/NiriCanvasUITests.swift">

<violation number="1" location="cmuxUITests/NiriCanvasUITests.swift:158">
P3: This adds substantial duplicate raw-mouse drag helper code that already exists in another UI test file; extract a shared test utility to avoid drift.</violation>
</file>

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

return false
}

private struct RawMouseDragSession {

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 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.

P3: This adds substantial duplicate raw-mouse drag helper code that already exists in another UI test file; extract a shared test utility to avoid drift.

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

<comment>This adds substantial duplicate raw-mouse drag helper code that already exists in another UI test file; extract a shared test utility to avoid drift.</comment>

<file context>
@@ -0,0 +1,236 @@
+        return false
+    }
+
+    private struct RawMouseDragSession {
+        let source: CGEventSource
+    }
</file context>
Fix with Cubic

This branch was successfully deployed

1 active deployment
Preview — 007569f5 Deployed Mar 26, 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

Development

Successfully merging this pull request may close these issues.

1 participant