Daeseon Yoo
Back to project
·UX retro·4 min·Review needed

Arrow-key startup switching that dodges every macOS and terminal shortcut — and the one tmux flag that fixed scrollback-on-entry

Switching between startups by Ctrl+number felt clunky for back-and-forth, so the founder asked for arrow keys — under a hard rule: collide with neither a macOS system shortcut nor a terminal one. On a terminal host that rules out almost every arrow chord (⌃-arrows are macOS Spaces, ⌥-arrows are the shell's word-motion), leaving ⌘⌥+arrow as the lone safe slot. Same session, a second friction: tmux scroll mode wouldn't scroll up on entry until the copy cursor climbed to the top edge — fixed with a single `copy-mode -u` flag, verified against a real tmux server.

AI version

The friction

Dogfooding across several parallel startups, the founder hit a wall switching between them:

"스타트업 컨트롤 숫자로 왔다갔다 좀 불편하네" — switching startups by Ctrl+number, back and forth, is uncomfortable.

The model in the app was = panes (within a startup), = startups (across them): ⌃1–9 jump by index, ⌃Tab cycles. To bounce between startup #2 and #5 you pressed ⌃2, ⌃5, ⌃2… a different number every time, on a modifier (Control) that's a long reach on a Mac.

First pass was a ⌃\`` "last-used" toggle (tmux's last-window`, Alt-Tab for startups). It worked, but the founder's reaction was decisive:

"이런거 말고 화살표로 왔다갔다 하는게 더 빠를거같은데" — not this — arrows would be faster.

And a constraint, stated twice and absolutely:

"절대 맥북같은거 기본 필수 단축키랑 겹치면 안된다" … "맥os랑 터미널 전부 안겹치게" — must never collide with a macOS default — and clear of the terminal too.

Why arrows are a minefield on a terminal host

The natural arrow chords are nearly all spoken for — and on an app that hosts a shell, "taken" means two different things:

  • ⌃← / ⌃→ → macOS Move between Spaces; ⌃↑Mission Control; ⌃↓App Exposé. Reserved by the OS (confirmed against Apple's shortcut list). Off-limits per the founder's rule.
  • ⌥← / ⌥→ → the shell's own word-motion (ESC b / ESC f). A terminal conflict — exactly what "터미널 안겹치게" forbids.
  • ⌘↑ → already our tmux scroll-mode toggle; ⌘←/→ → line-nav inside text inputs.
  • ⌘⌥ + arrow → not a macOS system shortcut, and it's the Chrome/Arc "switch tab" family — arrow muscle-memory a power user already has.

So ⌘⌥+arrow is the only arrow chord clear of both the OS map and the shell. And it's safe to grab because in DalkkakAI every combo is captured at the window (capture-phase preventDefault) and never forwarded to the pane — the shell, tmux, Claude Code or vim inside can't even receive it.

What shipped

  • ⌘⌥↑ = previous startup (the one above), ⌘⌥↓ = next (below) — matched to the vertical sidebar, so the direction is spatial. (App.tsx keydown handler.)
  • The scroll-mode branch (⌘↑) gained a !e.altKey guard so ⌘⌥↑ doesn't accidentally trigger it.
  • In-app shortcuts guide + docs/SHORTCUTS.md updated so the binding is discoverable.

The keybinding code landed (bundled) in c8aa76f; the in-app guide and canonical doc in 3aebdc1.

The second itch, same session: scroll mode wouldn't scroll up

"스크롤모드가 진입하면.. 커서같은게 있어서 바로 안올라가더라고... 커서가 위로 솟구치면서 위 끝에 도달해야하고" — when you enter scroll mode there's a cursor, so it doesn't go up right away; the cursor has to shoot up and reach the top edge first.

Cause (verified in lib.rs): scroll_mode entered tmux copy-mode plain. tmux puts the copy cursor at the bottom prompt; pressing Up walks the cursor up the visible screen, and the viewport only starts scrolling into history once the cursor hits the top edge — roughly a full screen of "cursor climbing" before any scrollback appears.

Fix: one flag — copy-mode -u (scroll up one page on entry). Verified on a throwaway tmux 3.6a server (the same tmux the app resolves), filling a 24-row pane with 100 lines:

before (live):   pane_in_mode=0   scroll_position=(empty)
after copy-mode -u:  pane_in_mode=1   scroll_position=22

Entry now lands 22 lines up — straight into history, no climb. (lib.rs scroll_mode, in c8aa76f.)

Two meta-lessons

  1. Multi-session hazard, met live. While I worked, a sibling Claude Code session committed the same files (c8aa76f) and swept my still-uncommitted arrow/scroll edits into its commit — the exact "stage only the files you touched" trap this repo already documented. My follow-up commit stayed pathspec-scoped to the two files I still owned, so nothing of the sibling's was bundled.
  2. Decide, don't menu. My first answer was three scoped options annotated with line counts and a "separate commit" plan. The founder's reply — "좀 직관적으로 하자 … 어려우면 안된다" — was the correction: for a feature ask, give the one simple solution, build it, and iterate on the working thing. The thorough analysis still happened; only the conclusion needed to surface.

Review needed

No human review on this entry yet.