feat(core): wire MouseLevel through setMouseMode + CliRendererConfig#1039
Open
namastex888 wants to merge 1 commit into
Open
feat(core): wire MouseLevel through setMouseMode + CliRendererConfig#1039namastex888 wants to merge 1 commit into
namastex888 wants to merge 1 commit into
Conversation
Replace `setMouseMode(enable: bool, enable_movement: bool)` with `setMouseMode(level: MouseLevel)` so consumers can subscribe to a precise slice of the xterm mouse protocol instead of being locked into the historical `clicks + drag + motion` default. The dormant `MouseLevel` enum in terminal.zig (none / basic / drag / motion / pixels) is now actually consumed: - terminal.zig: `setMouseMode` takes a MouseLevel and emits the matching DECSET sequences. `state.mouse_level` records the canonical level; `state.mouse` and `state.mouse_movement` remain a back-compat projection so `restoreTerminalModes`, `resetState`, and any external callers reading those fields keep working unchanged. A new `setMouseModeLegacy(enable, movement)` shim preserves the prior two-bool contract for callers that haven't migrated. - renderer.zig: `enableMouse(bool)` now routes through the legacy shim; new `setMouseLevel(level)` exposes the canonical API. `disableMouse` calls `setMouseMode(.none)` directly. - lib.zig: new `setMouseLevel(rendererPtr, u8)` FFI export. The u8 matches MouseLevel ordinals; out-of-range values fall back to .none. - zig.ts: matching FFI binding + RenderLib method. - renderer.ts: `MouseLevel` enum exported; `mouseLevel?: MouseLevel | \"none\" | \"basic\" | \"drag\" | \"motion\"` added to `CliRendererConfig`; `resolveMouseLevel()` helper centralizes the legacy back-compat shim (false => None; true,false => Drag; true,true => Motion). CliRenderer gets a public `mouseLevel` getter/setter that drives the new FFI. Tests: - Replace the two existing `setMouseMode(true, ...)` tests with level-based equivalents that also assert `state.mouse_level`. - Add `.basic`, `.none`, and same-level-no-op coverage. - Add `setMouseModeLegacy` round-trip tests for all three legacy combinations to guarantee the shim preserves prior behavior. Use case: callers that want native terminal drag-to-select (e.g. clipboard-aware terminals like Warp on macOS) can set \`mouseLevel: \"basic\"\` to leave drag events with the host terminal while still receiving click events for navigation. Today this requires a manual ?1002l write after createCliRenderer() returns. Refs: https://github.com/automagik-dev/genie wish tui-native-selection
namastex888
added a commit
to automagik-dev/genie
that referenced
this pull request
May 9, 2026
Group 1 / Jaw A — Local mouse override (drag terminal-owned):
- bump @opentui/{core,keymap,react} 0.2.0 → 0.2.6
- src/tui/render.tsx: disableDragTracking() helper emits \e[?1002l
after createCliRenderer() and re-emits on lifecycle events
(suspend/resume + useMouse setter) so OpenTUI's hardcoded ?1002h
drag-tracking subscription is overridden, restoring native
terminal selection in the OpenTUI Nav pane
- src/tui/render.test.ts: +73 LOC test suite covering override
emission + suspend/resume re-emit
- src/tui/components/Nav.tsx: audit comment confirming no
onMouseDrag/onMouseDragEnd registrations (drag intentionally
terminal-owned in v5)
Group 2 / Jaw B — Strip OSC 52 plumbing:
- scripts/tmux/genie.tmux.conf: set-clipboard external → off,
drop terminal-overrides Ms= cap, replace copy-pipe-and-cancel
~/.genie/scripts/osc52-copy.sh with copy-selection-and-cancel
- scripts/tmux/tui-tmux.conf: same three edits
- src/__tests__/tmux-config.test.ts: invariants inverted (assert
set-clipboard off, no Ms=, copy-selection-and-cancel)
- CHANGELOG.md: v5-launch entry naming the user-visible contract
(drag highlights, Cmd+C copies, no auto OSC 52 emit, GENIE_TUI_MOUSE=0
is the env-var escape hatch)
- .docs-vendor pointer bump: includes 5a1f1715 docs(genie): TUI
clipboard semantics in v5 — terminal-native selection
- scripts/tmux/osc52-copy.sh kept on disk per D7 for ad-hoc operator
use; just no longer invoked from tmux config
Group 3 / Jaw C — Upstream PR (non-blocking):
- Issue: anomalyco/opentui#1038 "Surface MouseLevel through
setMouseMode for click-only mouse use cases"
- Draft PR: anomalyco/opentui#1039 "feat(core): wire MouseLevel
through setMouseMode + CliRendererConfig" (commit 0594866d,
+297/-34 across 6 files: terminal.zig, renderer.zig, lib.zig,
zig.ts, renderer.ts, terminal_test.zig)
- Once accepted and 0.2.7+ ships, follow-up minor in genie will
set mouseLevel: 'basic' natively and delete the Jaw A local
override
Group 4 (QA smoke gate on Warp + Terminal.app on macOS) is
manual and pending operator hardware.
Wish: .genie/wishes/tui-native-selection/WISH.md
Sister: v5-major-cutover-handoff (same v4-final / v5-launch boundary)
Co-Authored-By: Claude <noreply@anthropic.com>
namastex888
added a commit
to automagik-dev/genie
that referenced
this pull request
May 9, 2026
Reframe Jaw A from "transitional workaround" to permanent v5 solution. Stop pretending Jaw C (upstream PR anomalyco/opentui#1039) is on the v5 critical path — it's now goodwill only, not blocking v5, and genie does NOT adopt it even if it merges. Rationale (Felipe pushback 2026-05-09): relying on anomalyco's merge cadence is not acceptable. Pinning a fork via git URL would cost more in supply-chain risk (Zig toolchain on install hosts, no cosign provenance, fork-rebase maintenance burden) than the 30 LOC override itself. The override is small, well-commented, fully reversible, and depends on nothing outside the genie repo + the npm-published @opentui/core@0.2.6. Changes: - D3: "ships immediately, regardless of upstream PR status" → "**permanent** v5 solution, not transitional" - D5: "Jaw C is part of the wish but not blocking" + "use it natively when upstream lands" → "**goodwill only**, not on v5 critical path. Genie ships v5 with the local override regardless of #1039's status. Even if anomalyco merges, we do NOT restructure genie's code to depend on it." - D6: amended to clarify it's a courtesy decision for the upstream PR shape, not a genie-affecting choice - IN scope (Jaw C): "non-blocking" + "follow-up minor adopts it" → "goodwill only, not v5-blocking. Genie does not adopt this in v5 even if it merges." - Success criterion: "PR opened" → "PR opened + flipped to ready-for-review" with goodwill-only annotation Side action: anomalyco/opentui#1039 flipped from draft to ready-for-review. Wish: .genie/wishes/tui-native-selection/WISH.md Co-Authored-By: Claude <noreply@anthropic.com>
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.
Closes #1038.
What
Wires the existing-but-dormant
MouseLevelenum (packages/core/src/zig/terminal.zig:33-39) throughsetMouseModeand surfaces it onCliRendererConfigasmouseLevel?: MouseLevel | "none" | "basic" | "drag" | "motion".useMouse+enableMouseMovementkeep working as a back-compat shim so every existing consumer continues to produce identical output bytes.Why
MouseLevel.basic("click only") is declared in source but unreachable today —setMouseModealways emits?1000h ?1002h ?1006hwhenever mouse is enabled. There's no way to subscribe to clicks-only and let the host terminal own drag events natively.The motivating use case is downstream in
automagik-dev/genie: when the TUI runs over SSH from terminals that don't implement OSC 52 (notably Warp on macOS), the active?1002hsubscription captures drag events that the user wanted to use for native text selection. A genie-side override (\e[?1002laftercreateCliRenderer()) ships in v5 day-one as a workaround. The long-term plan is to drop that override oncemouseLevel: "basic"lands here.Full discussion in #1038.
Changes
Zig
terminal.zig—setMouseModenow takes(level: MouseLevel). Emits per level:.none→ disable all four (?1000l ?1002l ?1003l ?1006l).basic→?1002l(downgrade) +?1000h + ?1006h.drag→?1003l(downgrade) +?1000h + ?1002h + ?1006h(matches the prior(true, false)output).motion→?1000h + ?1002h + ?1003h + ?1006h(matches the prior(true, true)output).pixels→ currently treated as.drag; reserved for future pixel-coordinate decodingstate.mouse_levelfield records the canonical level. Existingstate.mouseandstate.mouse_movementremain a derived projection sorestoreTerminalModes,resetState, and any external callers reading those fields keep working.setMouseModeLegacy(enable, movement)— new shim mapping the prior two-bool contract onto aMouseLevel.renderer.zig—enableMouse(bool)routes through the legacy shim (zero behavior change). NewsetMouseLevel(level)exposes the canonical API.disableMousecallssetMouseMode(.none)directly.lib.zig— newsetMouseLevel(rendererPtr, u8)FFI export.TypeScript
zig.ts— matching FFI binding + RenderLib method.renderer.ts—MouseLevelenum exported (numeric values match the Zig enum order).mouseLeveladded toCliRendererConfigwith aresolveMouseLevel()helper centralizing the legacy back-compat shim. CliRenderer gains a publicmouseLevelgetter/setter that drives the new FFI; the privateenableMouse()falls through to the level-aware path only when the resolved level isn't.dragor.motion, so legacy callers stay on the original code path bit-for-bit.Tests (Zig)
setMouseMode(true, ...)tests with level-based equivalents; both also assertstate.mouse_level..basic,.none(drives through.dragfirst), and same-level no-op.setMouseModeLegacyround-trip tests for all three legacy combinations to guarantee behavior preservation.Back-compat / API surface
setMouseMode(true, false)setMouseMode(true, true)setMouseMode(false, _)setMouseModeLegacy(true, false)setMouseMode(.drag)setMouseModeLegacy(true, true)setMouseMode(.motion)setMouseModeLegacy(false, _)setMouseMode(.none)setMouseMode(.basic)The TypeScript surface is fully back-compat: existing
useMouse/enableMouseMovementcallers see no behavior change.Validation
bun test(TS) — N/A in this draft until reviewer confirms preferred test wiring; happy to addrenderer.mouse.test.tscoveringresolveMouseLevel()if useful.tests/terminal_test.zigcover all five level transitions plus the legacy shim.Open questions for reviewers
setMouseMode(level)reuses the old name; alternative is to keep oldsetMouseMode(enable, movement)and add a freshsetMouseLevel(level). I went with the former because the wish from the downstream user explicitly asked for a signature change, but I'm happy to flip if you prefer additive-only..pixels: reserved but treated as.dragfor now (matches the existing TODO at line 572). Should I add a?1016hemit path now or leave it for a follow-up?useMousedeprecation: I left bothuseMouseandenableMouseMovementas?: booleaninCliRendererConfigwith a comment pointing atmouseLevel. Want a JSDoc@deprecatedannotation, or keep them first-class?Refs
automagik-dev/genie— wishtui-native-selection(filed by @namastex888 / namastex.ai)namastex888:feat/mouse-level-config-surfaceMarking draft pending #1038 discussion.