Skip to content

fix(ui): anchor the host cursor to modal name inputs - #2569

Merged
ogulcancelik merged 5 commits into
herdrdev:masterfrom
kataokatsuki:fix/ime-anchor-modal-name-inputs
Aug 10, 2026
Merged

fix(ui): anchor the host cursor to modal name inputs#2569
ogulcancelik merged 5 commits into
herdrdev:masterfrom
kataokatsuki:fix/ime-anchor-modal-name-inputs

Conversation

@kataokatsuki

Copy link
Copy Markdown
Contributor

Problem

With a rename dialog or the new-worktree dialog open, typing Japanese with an
IME shows the composition preview behind the dialog, at the cursor the focused
pane last reported. The text still lands in the field on Enter, so only the
composition phase is affected — CJK users type blind.

Cause

render_rename_overlay and render_new_linked_worktree_overlay painted the
caret as a glyph and never called frame.set_cursor_position. The focused
pane does not supply one either — tab_surface_cursor returns None outside
Mode::Terminal — so the frame left the server with no cursor at all
(Terminal::draw hides the cursor in that case, which clears the tracking
backend's rendered_cursor). resolve_host_cursor_state then falls back to
last_visible_cursor and emits CUP to where the pane last had it with
visible: false: the position still moves, so the IME composes behind the
dialog.

Fix

  • extract render_name_input_field, shared by both dialogs
  • set the cursor to the caret column instead of drawing the block glyph; the
    column is input_rect.x + 1 + display_width_u16(&name_input), so wide
    characters advance it two cells
  • clamp the column to the field so a name wider than the popup keeps the cursor
    inside it

The goes away because the dialog now shows the host terminal's own cursor.
A terminal draws that cursor by inverting the cell under it, so leaving a full
block there would flatten it into a solid cell. One consequence worth naming:
the emitted CursorState carries shape: 0, which resets DECSCUSR, so the
caret takes the terminal's default shape, blink and colour instead of the old
steady palette.text block. Clients that paint the cursor into frame cells
(draw_host_cursor, the Windows default) still get a steady block through
frame_with_drawn_cursor.

The issue names the rename and new-tab dialogs. The new-worktree dialog reads
the same name_input through the same missing call, so it gets the same fix.

Same approach as #2468, arrived at independently; that one kept the and
did not cover the new-worktree dialog.

One edge is left alone: render_stream.rs prefers a visible popup pane's cursor
over the frame cursor, so a modal rendered over a popup would still lose its
caret. That state is currently unreachable — opening a popup forces
Mode::Terminal, and while it is open every key and mouse event is routed to
the popup.

Not fixed here, and visible to the same users: for halfwidth voiced kana
(ガ, ギ) the field text itself renders wrong. Paragraph packs the grapheme
into one cell while cell_width treats it as two, so write_all_cells skips
the following cell. That predates this change and no caret arithmetic can line
up while it stands.

Docs

No public docs change needed; no documented command, config key, or keybinding
changes.

Verification

  • checked by hand on macOS 26.6.1 with Ghostty 2dd79f3bc and the macOS Japanese
    IME: with the rename dialog open, composition now appears at the input caret
    instead of behind the dialog
  • six new tests: the caret's absolute column for an empty name, ASCII and wide
    characters, the clamp boundary at 51 / 53 / 200 columns, the cell under the
    caret being blank, all three rename modes, and the resolved CursorState the
    server would send. All six fail when the set_cursor_position call is removed
  • just lint (cargo fmt --check, cargo clippy --all-targets --locked -- -D warnings)
  • cargo nextest run --locked --no-fail-fast -E 'all()': 3,188 run, 3,185
    passed, 3 failed — pane_spawn_cwd_fallback_in_server,
    live_handoff_keeps_unmanaged_agent_name_bound_to_saved_session, and
    multi_client_client_crash_sigkill_does_not_affect_server fail identically on
    a clean master checkout on this machine
  • just integration-assets-test (4 pass) and just plugin-marketplace-test
    (31 pass)

refs #1755

IMEs draw their composition preview at the host terminal cursor. The rename
and new-worktree dialogs drew a block glyph instead of setting one, so the
frame carried no cursor and the client kept the position the focused pane
last reported. Japanese composition appeared behind the dialog and only
reached the field on Enter.

Set the cursor to the caret column instead, counting wide characters as two
cells.

refs herdrdev#1755
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bacc04f-bce5-4b95-85f5-58763f5da60e

📥 Commits

Reviewing files that changed from the base of the PR and between b5b1f3f and 8ff47a8.

📒 Files selected for processing (1)
  • src/ui/dialogs.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ui/dialogs.rs

📝 Walkthrough

Walkthrough

The change adds shared cursor-aware rendering for name inputs. Rename and linked-worktree overlays use the renderer. Tests cover cursor placement, display widths, bounds clamping, and cursor propagation.

Changes

Name input cursor rendering

Layer / File(s) Summary
Shared cursor-aware renderer
src/ui/dialogs.rs
The shared renderer draws name input text and explicitly positions the terminal cursor. Rename and linked-worktree overlays use it.
Cursor rendering validation
src/ui/dialogs.rs
Tests cover rename modes, empty and populated input, wide characters, overflow clamping, blank caret cells, server cursor propagation, and worktree input.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main UI fix: anchoring the host cursor to modal name inputs.
Description check ✅ Passed The description explains the IME positioning problem, its cause, the shared renderer fix, tests, and verification results.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests

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

@kangal-bot kangal-bot added the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 9, 2026

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ebdc681f-8ac5-47c8-a942-fccb4530a93e

📥 Commits

Reviewing files that changed from the base of the PR and between ca1af38 and b5b1f3f.

📒 Files selected for processing (1)
  • src/ui/dialogs.rs

Comment thread src/ui/dialogs.rs
A name that fills the field left the clamped caret sitting on the last
rendered glyph. A terminal inverts the cell under its cursor and an IME
composes there, so the glyph was hidden and composition overlapped it.

Render the text one column short of the field. The field is still cleared
in full, so the clamped caret always lands on a blank cell.

refs herdrdev#1755
@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR replaces block-glyph carets in rename and new-worktree name fields with an explicit host-terminal cursor, positioning it by display width and clamping it within the field.

  • Extracts shared name-input rendering for both modal types.
  • Keeps the clamped cursor cell blank for terminal cursor inversion.
  • Adds coverage for empty, ASCII, wide-character, overflow, modal-mode, worktree, and server cursor-resolution cases.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/ui/dialogs.rs Centralizes modal name-field rendering and anchors the host cursor to the width-aware, bounded input caret with focused regression tests.

Reviews (4): Last reviewed commit: "Merge branch 'master' into fix/ime-ancho..." | Re-trigger Greptile

@kouyichi

kouyichi commented Aug 9, 2026

Copy link
Copy Markdown

This is exactly the fix Chinese/Japanese users are waiting for — on Linux with herdr 0.8.0 stable, typing with an IME in the tab/workspace rename dialog shows no composition preview and no cursor, so CJK users type blind. Our team hit this immediately.

Would love to see this merged and shipped. Happy to test the fix once it lands in a release (or on a preview build).

@ogulcancelik
ogulcancelik merged commit 2e8cc90 into herdrdev:master Aug 10, 2026
7 checks passed
@ogulcancelik

Copy link
Copy Markdown
Collaborator

ty!

@kangal-bot kangal-bot removed the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 10, 2026
@kataokatsuki

Copy link
Copy Markdown
Contributor Author

thanks for the quick merge!

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.

4 participants