Repository navigation
CPLAT-12781: TUI robustness - move blocking tmux/git work off the UI loop, fix CJK input wrap - #175
Merged
Merged
Conversation
kittyTickMsg ran tmux display-message synchronously inside Update every 250ms and forced a View() each time even when idle. A tea.Cmd now polls tmux and only returns a message when the visibility actually changes.
Hangul/CJK runes are two cells wide. The modal truncated by rune count and never wrapped the cursor line, so Korean input overflowed the inner width, lipgloss re-wrapped it, and the modal grew and misplaced rows. Soft-wrap each line into visual rows by cell width and scroll by visual row so the cursor row is always the one drawn.
git worktree add, tmux.FindPane and send-keys ran synchronously inside Update, freezing input for up to seconds on large repos. They now run in tea.Cmds that return worktreeDoneMsg / sessionsKilledMsg / liveInputPaneMsg. The live-input lookup carries a generation so a stale result cannot open input for the wrong pane. send-keys C-c errors were discarded, so a failed kill still read "Killed"; they are now collected and shown in the status line, and the bulk count only includes panes that actually received the interrupt.
renderSessionSplit resized the list, wrote sessSplit.Preview.Width/Height and re-wrapped preview content while rendering, so View was not pure and two renders could differ. The mutating half moves to syncSessionSplitLayout, which Update runs after every message; View only reads.
Kairo-Kim
approved these changes
Oct 14, 2026
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JIRA: https://sendbird.atlassian.net/browse/CPLAT-12781
Summary
Fixes UI-thread blocking, a CJK layout bug, swallowed errors and a render-time state write in the TUI, found in a robustness audit. One commit per fix.
kittyTickMsgrantmux display-messagesynchronously inUpdateevery 250ms and forced aView()each time. Atea.Cmdnow polls tmux and only returnskittyVisibilityMsgwhen visibility changes, so an idle app receives no messages. (internal/tui/kitty_watch.go,app.go)inputmodal.gotruncated and positioned by rune count; Hangul is 2 cells per rune, so lines overflowed, lipgloss re-wrapped them and the modal grew. Lines now soft-wrap into visual rows by cell width, and scrolling and the cursor follow visual rows. (internal/tui/inputmodal.go)git worktree add,tmux.FindPaneandsend-keysran inside Update.executeNewWorktreeSession,executeCmdWorktreeNew,executeWorktree,bulkKill,killLiveSessionandopenLiveInputnow do their git/tmux work intea.Cmds that returnworktreeDoneMsg/sessionsKilledMsg/liveInputPaneMsg. The live-input lookup carries a generation, so a stale result cannot open input for the wrong pane. (internal/tui/worktree_async.go,internal/tui/pane_actions.go)send-keys C-cerrors were discarded. A failed kill still reported "Killed". Errors now appear in the status line (Kill failed: …/Killed N, M failed: …), and the bulk count only includes panes that actually got the interrupt. (internal/tui/pane_actions.go)renderSessionSplitwrote state during render. It resized the list andsessSplit.Preview.Width/Heightand re-wrapped content insideView. That work moves tosyncSessionSplitLayout, which runs after everyUpdate.Viewnow only reads state. (internal/tui/app.go)app.goshrinks by ~230 lines; the moved code lives in focused files.Not in this PR
Other synchronous tmux calls in Update remain:
bulkInput(FindPane per selected session),FindPaneatapp.gojump/preview paths,share_ref.go, and thesend-keyspair in the\-continuation path whose.Run()errors are still ignored.Tests
kitty_watch_test.go,inputmodal_test.go(Hangul wrap + fixed modal height; the height test fails on master with 29 vs 26 lines),pane_actions_test.go(no tmux call on the Update path, send-keys error surfaced, stale live-input result dropped, worktree created by the Cmd and not in Update, using a temp git repo),session_split_layout_test.go(View does not resize the preview; fails on master).outputs_actions_test.gofixture now settles the layout before it injects digest rows, as the first real Update would.go vet ./...clean;go test ./...passes (10 packages);-raceon the new tests passes.