Skip to content

WIP#4652

Merged
saddlepaddle merged 1 commit into
mainfrom
web-mobile-terminal-followups
May 16, 2026
Merged

WIP#4652
saddlepaddle merged 1 commit into
mainfrom
web-mobile-terminal-followups

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes


Open in Stage

Summary by cubic

Introduce a shared MobileTerminalInput to improve typing and key controls on mobile for both Remote and Web terminals. Replaces the old toolbar, adds IME-safe input, paste support, and better focus behavior.

  • New Features

    • Added MobileTerminalInput used by RemoteTerminal and WebTerminal.
    • Hidden textarea captures IME, paste, Backspace/Enter, arrows, Tab/Esc, and Ctrl- combos.
    • Key toolbar included; mobile-only by default, always visible in the workspace terminal.
    • Tap-to-focus on touch; mouse keeps the xterm focused. RemoteTerminal wraps string input to bytes.
  • Dependencies

    • Updated internal versions in bun.lock: @superset/desktop to 1.9.6, @superset/cli to 0.2.19, @superset/host-service to 0.8.6.

Written for commit 9aa5eb5. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Refactor
    • Improved mobile terminal input experience with enhanced virtual keyboard support and touch interaction handling.
    • Consolidated keyboard input functionality across remote and web terminals for a more consistent mobile user experience.

Review Change Stack

@saddlepaddle saddlepaddle merged commit 8dea9a6 into main May 16, 2026
9 of 10 checks passed
@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8f58f5e-e280-4835-8dc5-7146cf8ce932

📥 Commits

Reviewing files that changed from the base of the PR and between 09dc459 and 9aa5eb5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx
  • apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/components/MobileToolbar/MobileToolbar.tsx
  • apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/components/MobileToolbar/index.ts
  • apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/WebTerminal.tsx
  • apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx
  • apps/web/src/components/MobileTerminalInput/index.ts

📝 Walkthrough

Walkthrough

This PR replaces the MobileToolbar component with a new MobileTerminalInput component that provides improved terminal input handling for mobile/virtual keyboard users. The new component uses a hidden textarea to capture text and keyboard input, converts it into terminal escape sequences, and renders a configurable toolbar. Two terminal consumers—RemoteTerminal and WebTerminal—are updated to use the new component, with WebTerminal also gaining ref-based focus management for the xterm instance.

Changes

Mobile Terminal Input Migration

Layer / File(s) Summary
MobileTerminalInput Component
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx, apps/web/src/components/MobileTerminalInput/index.ts
New client component with hidden textarea for terminal input. Maps keyboard and toolbar buttons to escape/control sequences, handles IME composition, distinguishes touch from mouse focus, and renders a visibility-controlled toolbar.
RemoteTerminal Integration
apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx
Imports MobileTerminalInput and replaces MobileToolbar in full-access mode. Adds sendInputText callback to encode text strings as bytes and wires it to the component's onSend handler; focuses xterm instance on terminal interaction.
WebTerminal Integration and Ref Management
apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/WebTerminal.tsx
Removes inline KEY_BUTTONS toolbar and imports MobileTerminalInput. Introduces terminalRef to track the xterm instance and enable focus management; replaces button UI with the new component wired to existing sendSequence callback.
MobileToolbar Cleanup
apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/components/MobileToolbar/index.ts
Removes barrel export for deprecated MobileToolbar component.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 A new terminal input blooms,
Textarea whispers where buttons once ruled,
Focus flows pure through touch and through keys,
Two paths now converge on a single refrain—
The toolbar evolves, and the UI ascends! 🎹✨

✨ 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 web-mobile-terminal-followups

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@stage-review

stage-review Bot commented May 16, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 4 individual chapters for you:

Title
1 Create shared MobileTerminalInput component
2 Refactor RemoteTerminal to use shared input
3 Integrate shared input into WebTerminal
4 Other changes
Open in Stage

Chapters generated by Stage for commit 9aa5eb5 on May 16, 2026 11:32pm UTC.

@capy-ai

capy-ai Bot commented May 16, 2026

Copy link
Copy Markdown

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

@greptile-apps

greptile-apps Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts duplicate mobile-keyboard-toolbar code from RemoteTerminal (and its deleted MobileToolbar sub-component) and WebTerminal into a single shared MobileTerminalInput component, while also upgrading the input handling from a simple button strip to a full hidden-textarea approach that properly captures IME composition, paste, and Ctrl-key sequences on mobile.

  • New MobileTerminalInput provides a hidden <textarea> (positioned off-screen) that intercepts virtual-keyboard input via onBeforeInput, onInput, onCompositionStart/End, onKeyDown, and onPaste, plus the original key-button toolbar controlled by a toolbarVisibility prop ("mobile" | "always").
  • RemoteTerminal replaces the deleted MobileToolbar with MobileTerminalInput; adds a sendInputText wrapper to adapt the new string-based onSend contract to the existing sendInputBytes(Uint8Array) path.
  • WebTerminal adds a terminalRef to expose the xterm instance for programmatic focus and replaces the inline button strip with MobileTerminalInput toolbarVisibility="always".

Confidence Score: 4/5

Safe to merge — the refactor is a clean extraction with no functional regressions; the two open issues are non-blocking style/performance concerns.

The inline onFocusTerminal callbacks in both consuming components create a new function reference every render, causing MobileTerminalInput's useEffect to teardown and re-register its pointerdown/touchstart listeners on every parent state change. During active terminal use this is frequent, though behavior remains correct. The ref-null guard issue is similarly low-risk given current usage. Neither concern blocks shipping.

Both RemoteTerminal.tsx and WebTerminal.tsx need stable useCallback-wrapped onFocusTerminal props.

Important Files Changed

Filename Overview
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx New shared mobile terminal input component with hidden textarea for keyboard capture, key button toolbar, and composition/paste handling — logic is sound with one dependency-stability concern
apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx Switches from deleted MobileToolbar to shared MobileTerminalInput; adds sendInputText wrapper and adapts onSend signature from Uint8Array to string — functionally equivalent
apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/WebTerminal.tsx Replaces inline key-button strip with MobileTerminalInput; adds terminalRef for programmatic focus; MobileTerminalInput is always rendered with toolbarVisibility="always"
apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/components/MobileToolbar/MobileToolbar.tsx Deleted — superseded by the new shared MobileTerminalInput component
apps/web/src/components/MobileTerminalInput/index.ts New barrel export for MobileTerminalInput

Sequence Diagram

sequenceDiagram
    participant User as User (mobile touch)
    participant CT as containerRef div
    participant MIT as MobileTerminalInput
    participant TA as Hidden Textarea
    participant OS as onSend callback
    participant WS as WebSocket / tRPC

    User->>CT: touchstart / pointerdown (touch)
    CT->>MIT: event listener fires
    MIT->>TA: "focus({ preventScroll: true })"
    Note over TA: Virtual keyboard opens

    User->>TA: types character (onInput)
    TA->>MIT: flushTextareaValue()
    MIT->>OS: onSend(string)
    OS->>WS: send input message

    User->>MIT: taps toolbar button (Tab / Esc / Arrow / etc.)
    MIT->>TA: focus (via sendButtonSequence)
    MIT->>OS: onSend(sequence)
    OS->>WS: send input message

    User->>CT: pointerdown (mouse)
    CT->>MIT: event listener fires
    MIT->>OS: onFocusTerminal()
    Note over OS: xterm Terminal.focus()
Loading

Comments Outside Diff (1)

  1. apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx, line 376-378 (link)

    P2 The onFocusTerminal prop receives a new inline arrow function on every render of RemoteTerminal. Because MobileTerminalInput lists onFocusTerminal in its useEffect dependency array, this causes the effect to tear down and re-add the pointerdown/touchstart listeners on every parent render — during active use (state changes, pings, viewer-count updates) that can be frequent. Wrapping it in useCallback makes the reference stable.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx
    Line: 376-378
    
    Comment:
    The `onFocusTerminal` prop receives a new inline arrow function on every render of `RemoteTerminal`. Because `MobileTerminalInput` lists `onFocusTerminal` in its `useEffect` dependency array, this causes the effect to tear down and re-add the `pointerdown`/`touchstart` listeners on every parent render — during active use (state changes, pings, viewer-count updates) that can be frequent. Wrapping it in `useCallback` makes the reference stable.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/web/src/app/(public)/agents/remote-control/[sessionId]/components/RemoteTerminal/RemoteTerminal.tsx:376-378
The `onFocusTerminal` prop receives a new inline arrow function on every render of `RemoteTerminal`. Because `MobileTerminalInput` lists `onFocusTerminal` in its `useEffect` dependency array, this causes the effect to tear down and re-add the `pointerdown`/`touchstart` listeners on every parent render — during active use (state changes, pings, viewer-count updates) that can be frequent. Wrapping it in `useCallback` makes the reference stable.

```suggestion
	const isFull = meta?.mode === "full" && state === "open";

	const focusTerminal = useCallback(() => {
		termRef.current?.focus();
	}, []);

	return (
```

### Issue 2 of 3
apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/WebTerminal.tsx:243-248
Same pattern as `RemoteTerminal`: an inline `() => terminalRef.current?.focus()` is passed as `onFocusTerminal`, so `MobileTerminalInput`'s event-listener effect tears down and re-adds `pointerdown`/`touchstart` listeners on every render of `WebTerminal` (connection-state changes, etc.). Define a stable callback with `useCallback` — e.g. `const focusTerminal = useCallback(() => { terminalRef.current?.focus(); }, [])` — and pass that reference instead.

### Issue 3 of 3
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx:39-60
**Stale ref if target is null on first mount**

`focusTargetRef.current` is read once when the effect runs. If it is `null` at that point (e.g., the container mounts asynchronously after `MobileTerminalInput`), the effect exits early without registering any listeners, and because `focusTargetRef` itself is a stable object its identity never changes, so the effect never re-runs to retry. In practice the current callers always mount the container before or simultaneously with `MobileTerminalInput`, but the component would silently do nothing if consumed in a context where the ref is populated later.

Reviews (1): Last reviewed commit: "WIP" | Re-trigger Greptile

Comment on lines +243 to +248
<MobileTerminalInput
focusTargetRef={containerRef}
onFocusTerminal={() => terminalRef.current?.focus()}
onSend={sendSequence}
toolbarVisibility="always"
/>

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.

P2 Same pattern as RemoteTerminal: an inline () => terminalRef.current?.focus() is passed as onFocusTerminal, so MobileTerminalInput's event-listener effect tears down and re-adds pointerdown/touchstart listeners on every render of WebTerminal (connection-state changes, etc.). Define a stable callback with useCallback — e.g. const focusTerminal = useCallback(() => { terminalRef.current?.focus(); }, []) — and pass that reference instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/src/app/workspaces/[workspaceId]/components/WebTerminal/WebTerminal.tsx
Line: 243-248

Comment:
Same pattern as `RemoteTerminal`: an inline `() => terminalRef.current?.focus()` is passed as `onFocusTerminal`, so `MobileTerminalInput`'s event-listener effect tears down and re-adds `pointerdown`/`touchstart` listeners on every render of `WebTerminal` (connection-state changes, etc.). Define a stable callback with `useCallback` — e.g. `const focusTerminal = useCallback(() => { terminalRef.current?.focus(); }, [])` — and pass that reference instead.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +39 to +60
useEffect(() => {
const target = focusTargetRef.current;
if (!target) return;

const onPointerDown = (event: PointerEvent) => {
if (event.pointerType === "mouse") {
onFocusTerminal?.();
return;
}
focusKeyboardInput();
};
const onTouchStart = () => {
focusKeyboardInput();
};

target.addEventListener("pointerdown", onPointerDown, { passive: true });
target.addEventListener("touchstart", onTouchStart, { passive: true });
return () => {
target.removeEventListener("pointerdown", onPointerDown);
target.removeEventListener("touchstart", onTouchStart);
};
}, [focusKeyboardInput, focusTargetRef, onFocusTerminal]);

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.

P2 Stale ref if target is null on first mount

focusTargetRef.current is read once when the effect runs. If it is null at that point (e.g., the container mounts asynchronously after MobileTerminalInput), the effect exits early without registering any listeners, and because focusTargetRef itself is a stable object its identity never changes, so the effect never re-runs to retry. In practice the current callers always mount the container before or simultaneously with MobileTerminalInput, but the component would silently do nothing if consumed in a context where the ref is populated later.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx
Line: 39-60

Comment:
**Stale ref if target is null on first mount**

`focusTargetRef.current` is read once when the effect runs. If it is `null` at that point (e.g., the container mounts asynchronously after `MobileTerminalInput`), the effect exits early without registering any listeners, and because `focusTargetRef` itself is a stable object its identity never changes, so the effect never re-runs to retry. In practice the current callers always mount the container before or simultaneously with `MobileTerminalInput`, but the component would silently do nothing if consumed in a context where the ref is populated later.

How can I resolve this? If you propose a fix, please make it concise.

sazabi Bot pushed a commit that referenced this pull request May 20, 2026
MocA-Love added a commit to MocA-Love/superset that referenced this pull request May 29, 2026
…4652)

No code change: apps/web WebTerminal and MobileTerminalInput are byte-aligned with upstream/main for the superset-sh#4652-relevant files; remote-control viewer paths from that WIP are not present because the remote-control feature remains deferred.
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