Files
headquarter/openspec/changes/mobile-terminal-ux/design.md
T
Fusion b6bda3d692 feat: implement mobile terminal UX
- Add mobile viewport detection hook
- Add virtual keyboard detection with fallback
- Add auto-hide hook for header/keys strip
- Add special keys mapping hook
- Create MobileTerminalHeader, SpecialKeysStrip, SpecialKeysPanel components
- Create MobileTerminalWrapper component
- Update TerminalComponent with mobile support, font scaling, copy/paste, reconnection
- Update AppShell to hide chrome on mobile terminal pages
- Update TerminalPage to use MobileTerminalWrapper
- Add comprehensive mobile terminal styles
- TypeScript check passes
- Build succeeds
2026-05-24 11:30:04 +02:00

109 lines
5.7 KiB
Markdown

## Context
The current terminal implementation (`apps/web/src/components/terminal.tsx`) uses xterm.js with a fixed header and minimal mobile considerations. The terminal page (`apps/web/src/pages/terminal.tsx`) renders inside the standard AppShell layout (`apps/web/src/components/app-shell.tsx`), which consumes significant viewport space on mobile devices.
On mobile devices (viewport < 768px):
- The virtual keyboard covers 40-50% of the screen
- xterm.js touch events conflict with browser touch behavior
- Special keys (Ctrl, Esc, Tab, Arrows) are not available on mobile keyboards
- The AppShell header and sidebar waste precious screen real estate
- No mechanism exists to handle virtual keyboard appearance/disappearance
## Goals / Non-Goals
**Goals:**
- Make terminal sessions practical on mobile devices for occasional use
- Provide access to special terminal keys without external keyboard
- Maximize terminal screen real estate on mobile
- Handle virtual keyboard gracefully
- Support full terminal functionality (vim, tmux, etc.)
**Non-Goals:**
- Native mobile app (stays web-based)
- Command palette / quick commands (future enhancement)
- Offline terminal access
- Mobile-first redesign of the entire application (terminal pages only)
- Gesture-based text selection (use xterm.js native)
## Decisions
### Decision: Collapsible AppShell, Not Hidden
**Choice**: Collapse AppShell to a minimal auto-hiding header instead of completely hiding it.
**Rationale**: Users need a way to navigate back and access the menu. Complete removal would trap users in the terminal page.
**Alternative considered**: Fullscreen mode with swipe-from-edge to reveal nav. Rejected because it's not discoverable and conflicts with browser gestures.
### Decision: Auto-Hide Header and Keys Strip
**Choice**: Both header and special keys strip auto-hide after 3 seconds of inactivity.
**Rationale**: Maximizes terminal space while keeping controls accessible. Tap to toggle visibility is intuitive.
**Alternative considered**: Always-visible fixed bars. Rejected because they permanently reduce terminal height by ~20%.
### Decision: Hidden Input for Keyboard Management
**Choice**: Use a hidden/transparent input element to maintain virtual keyboard focus.
**Rationale**: xterm.js handles keyboard input directly, but mobile browsers need a focused input to show the virtual keyboard. A hidden input bridges this gap without interfering with xterm.js rendering.
**Alternative considered**: Custom on-screen keyboard. Rejected because native virtual keyboards provide better UX (autocorrect, swipe typing, user's preferred keyboard layout).
### Decision: Special Keys as Bottom Strip, Not Floating
**Choice**: Fixed bottom strip that slides up, not floating action buttons.
**Rationale**: Bottom placement is thumb-friendly and doesn't obscure terminal content. Fixed position makes it always accessible.
**Alternative considered**: Floating action button that expands to a menu. Rejected because it requires two taps for every special key.
### Decision: Debounced Resize (250ms)
**Choice**: 250ms debounce for resize events.
**Rationale**: Mobile keyboard animation is slow and produces multiple resize events. 250ms catches the final state without being sluggish.
**Alternative considered**: No debounce (immediate resize). Rejected because it causes excessive xterm.js refits and WebSocket resize messages.
### Decision: No New Dependencies
**Choice**: Implement using existing React, xterm.js, and browser APIs.
**Rationale**: All required functionality (touch events, viewport API, clipboard) is available natively. Adding libraries increases bundle size for a feature used occasionally.
**Alternative considered**: `react-use` hooks, `xterm-addon-webgl`. Rejected to keep bundle size down.
## Risks / Trade-offs
**[Risk] Visual Viewport API unreliability** → **Mitigation**: Implement fallback using `window.innerHeight` comparison and focus-based detection. Accept imperfect behavior on older browsers.
**[Risk] xterm.js touch conflicts** → **Mitigation**: Use `touch-action: none` on terminal container. Let xterm.js handle its own touch events. Disable browser zoom to prevent pinch conflicts.
**[Risk] WebSocket drops on network change/backgrounding** → **Mitigation**: Implement reconnect logic with exponential backoff. Show clear status to user. Document that mobile networks may cause disconnections.
**[Risk] Clipboard API restrictions on mobile Safari** → **Mitigation**: Use both modern Clipboard API and `document.execCommand('copy')` fallback. Show user feedback on failure.
**[Risk] Screen rotation causes layout flicker** → **Mitigation**: Debounced resize. CSS transitions on layout changes. Consider `orientation` lock prompt for landscape preference.
**[Trade-off] Touch targets vs terminal density** → Larger touch targets mean fewer terminal cells visible. Compromise: 16px minimum font size provides readable text while keeping reasonable cell count.
## Migration Plan
No migration needed. This is a purely additive frontend change that doesn't affect data models, APIs, or existing desktop behavior. Desktop terminal experience remains unchanged.
**Deployment:**
1. Merge changes to dev branch
2. Verify on actual mobile devices (iOS Safari, Android Chrome)
3. Monitor for any desktop regressions
**Rollback:** Revert frontend commit. No database or API changes involved.
## Open Questions
1. Should we implement a landscape orientation prompt? ("Rotate for better experience")
2. Should font size preference sync across devices (via backend user config) or stay local?
3. What's the maximum number of special keys to show in the primary strip before requiring "More"?
4. Should the header show connection status, or is the terminal's own status dot sufficient?