fix: improve mobile terminal alternate-screen detection and scrolling

- Revert touch-action: none on .xterm-viewport so xterm.js can fall back
  to its own viewport scrolling when the custom handler doesn't take over.
- Detect alternate screen via reference equality
  (term.buffer.active === term.buffer.alternate) instead of the
  string, which could report normal buffer incorrectly.
- Lower vertical-scroll activation threshold from 4px to 2px and only
  prevent default once a vertical gesture is recognized.
- In normal buffer use term.scrollLines() so xterm.js handles the buffer
  scroll consistently; in alternate screen continue sending SGR 1006
  mouse-wheel sequences to tmux/vim.

Quality gates: typecheck, lint clean, npm test -- --run 87 passed.
This commit is contained in:
Developer
2026-06-13 20:42:52 +00:00
parent c7ab2de25e
commit 2ece7074f6
18 changed files with 59 additions and 61 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps
## role
Contains the main deployable application entry points and top-level executable modules for the project.
Contains the main deployable application modules or entry points for the project.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps
index: apps/.pi-map.index.md
## role
Contains the main deployable application entry points and top-level executable modules for the project.
Contains the main deployable application modules or entry points for the project.
## files
## arch
Monorepo-style directory with separate app folders, each typically following a layered architecture with domain, application, and infrastructure separation.
Modular monolith or microservices architecture with separate application boundaries, each potentially having its own configuration, dependencies, and lifecycle.
## tags
-
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web
## role
Frontend web application providing a browser-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
## parent
index: apps/.pi-map.index.md
map: apps/.pi-map.md
+2 -2
View File
@@ -4,7 +4,7 @@ dir: apps/web
index: apps/web/.pi-map.index.md
## role
Frontend web application providing a browser-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
## files
- .env.example | Template file defining example environment variables for frontend API and application URL configuration
- .eslintrc.cjs | Configures ESLint for a TypeScript browser project with modern ECMAScript module support | dep: @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint
@@ -16,7 +16,7 @@ Frontend web application providing a browser-based UI with code editing, termina
- tsconfig.json | TypeScript configuration file for a React project using Vite with modern ES2020 target and bundler module resolution | dep: typescript, react, vite
- vite.config.ts | Configures Vite build tool for a React project with custom dev server port and Vitest test settings. | dep: vite, @vitejs/plugin-react
## arch
Modern React SPA built with Vite and TypeScript, containerized via multi-stage Docker with nginx serving, featuring client-side routing and optimized static asset delivery.
Modern React SPA built with Vite and TypeScript, using nginx for production serving with client-side routing, multi-stage Docker deployment, and Vitest for testing.
## tags
react, eslint, vite, typescript, dom, application, nginx, web
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src
## role
Frontend web application entry point and core routing infrastructure for a React-based project management or developer workspace platform.
Frontend web application entry point and core infrastructure for a React-based collaborative development platform.
## parent
index: apps/web/.pi-map.index.md
map: apps/web/.pi-map.md
+2 -2
View File
@@ -4,13 +4,13 @@ dir: apps/web/src
index: apps/web/src/.pi-map.index.md
## role
Frontend web application entry point and core routing infrastructure for a React-based project management or developer workspace platform.
Frontend web application entry point and core infrastructure for a React-based collaborative development platform.
## files
- main.tsx | Bootstraps a React application with routing, authentication, and session management providers. | dep: react, react-dom/client, react-router-dom, ./router, ./state/auth, ./state/sessions, ./styles/tokens.css, ./styles/global.css, ./styles/utilities.css, ./styles/syntax-highlight.css, ./styles/pages/git-history.css, ./styles/pages/projects.css, ./styles/pages/sessions.css, ./styles/pages/ssh-keys.css, ./styles/pages/workspace-detail.css, ./styles/pages/workspaces.css, react-dom
- router.tsx | Defines the React Router configuration for a web application with protected routes, nested layouts, and redirects. | exp: AppRouter | dep: react-router-dom, ./components/app-shell, ./components/protected-route, ./pages/DashboardPage, ./pages/PlaceholderPage, ./pages/ProfilePage, ./pages/ProjectsPage, ./pages/GitRepositoriesPage, ./pages/GitHistoryPage, ./pages/ProjectSettingsPage, ./pages/SettingsPage, ./pages/TerminalPage, ./pages/ToolWorkshopPage, ./pages/SshKeysPage, ./pages/ConfigProfilesPage, ./pages/SessionsPage, ./pages/WorkspacesPage, ./pages/WorkspaceDetailPage
- types.ts | Defines TypeScript type definitions for user sessions, projects, repositories, and workspaces in an application. | exp: SessionUser, SessionPayload, Project, WorkspaceSummary, RepositorySummary, ProjectWithRepos
## arch
Modern React SPA with declarative routing (React Router v6), protected route guards via authentication/session context providers, nested layout composition, and centralized TypeScript domain models for multi-tenant workspace/project data structures.
Modular React SPA using React Router v6 with nested route layouts, protected route guards via authentication context, and centralized TypeScript domain models for session/workspace/project entities.
## tags
pages, styles, css, router, react, session, dom, project
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/components
## role
Provides reusable, accessible UI components for the web application including layout shell, data states, icons, code display, routing guards, and toast notifications.
Provides reusable, accessible UI components and utilities for rendering the application shell, data states, icons, code display, notifications, and route protection in a React web application.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+2 -2
View File
@@ -4,7 +4,7 @@ dir: apps/web/src/components
index: apps/web/src/components/.pi-map.index.md
## role
Provides reusable, accessible UI components for the web application including layout shell, data states, icons, code display, routing guards, and toast notifications.
Provides reusable, accessible UI components and utilities for rendering the application shell, data states, icons, code display, notifications, and route protection in a React web application.
## files
- app-shell.tsx | Renders the main application shell layout with navigation, header, session management, and mobile-responsive behavior for a React Router-based SPA. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- code-editor.tsx | A React component that renders a syntax-highlighted code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
@@ -17,7 +17,7 @@ Provides reusable, accessible UI components for the web application including la
- toast-rules.test.ts | Unit tests for mapping instance events to toast notification categories and severities | dep: vitest, ./toast-rules, ../types/events
- toast-rules.ts | Maps instance events to toast notifications with deduplication logic to prevent spam | exp: func:mapEventToCategory(event: InstanceEventPayload) → string, call:event.event.startsWith, func:mapEventToSeverity(event: InstanceEventPayload) → "info" | "warning" | "error" | "success", func:handleEventToast(event: InstanceEventPayload) → void, call:shouldShowToast, call:toast.info, call:toast.success, call:toast.warning, call:toast.error, func:clearToastDedup() → void, call:lastToastTime.clear | dep: ../state/toast, ../types/events, toast state module, InstanceEventPayload type
## arch
Component-based React architecture with functional components, custom hooks for state management, centralized icon mapping, route guards with auth integration, and utility modules for cross-cutting concerns like notifications and data state handling.
Component-based React architecture with functional components, composition patterns, separation of concerns (presentation vs logic), and test coverage for critical utilities; integrates third-party libraries (Phosphor icons, react-simple-code-editor) and React Router for SPA navigation.
## tags
toast, icon, react, state, code, loading, event, editor
## symbols
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features
## role
Houses reusable React components that implement specific product features and business logic for the web application.
Contains reusable React components that implement specific user-facing features and functionality across the web application.
## parent
index: apps/web/src/components/.pi-map.index.md
map: apps/web/src/components/.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps/web/src/components/features
index: apps/web/src/components/features/.pi-map.index.md
## role
Houses reusable React components that implement specific product features and business logic for the web application.
Contains reusable React components that implement specific user-facing features and functionality across the web application.
## files
## arch
Feature-based component organization with co-located UI pieces, likely composed of smaller design-system elements and connected to domain hooks/stores for stateful functionality.
Feature-based component organization with domain-specific UI building blocks following React composition patterns, likely co-located with related hooks, utilities, or sub-components for each feature area.
## tags
-
## symbols
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/terminal
## role
Provides cross-platform terminal UI components with session management, special key input, and WebSocket-backed xterm.js integration for web-based terminal emulation.
Provides a cross-platform terminal emulator interface with WebSocket backend connectivity, supporting multiple sessions, special key input, and responsive desktop/mobile layouts.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -4,7 +4,7 @@ dir: apps/web/src/components/features/terminal
index: apps/web/src/components/features/terminal/.pi-map.index.md
## role
Provides cross-platform terminal UI components with session management, special key input, and WebSocket-backed xterm.js integration for web-based terminal emulation.
Provides a cross-platform terminal emulator interface with WebSocket backend connectivity, supporting multiple sessions, special key input, and responsive desktop/mobile layouts.
## files
- DesktopTerminalView.tsx | Renders a desktop-optimized terminal view with session tabs, fullscreen mode, font controls, and reset confirmation dialog | exp: DesktopTerminalView | dep: react, ./terminal, ./terminal-session-tabs, ../../../api/terminal, React, TerminalComponent, TerminalSessionTabs, TerminalSession type
- MobileTerminalView.tsx | Renders a mobile-optimized terminal interface with toolbar, session tabs, terminal output, and special keys input controls. | exp: MobileTerminalView | dep: react, ./terminal, ./terminal-session-tabs, ../../icon, ./special-keys-strip, ./special-keys-panel, ../../../hooks/use-special-keys, ../../../api/terminal
@@ -12,9 +12,9 @@ Provides cross-platform terminal UI components with session management, special
- special-keys-strip.tsx | Renders a strip of buttons for sending special keyboard keys (Esc, Tab, arrows, etc.) with optional modifier support for a terminal interface. | exp: SpecialKeysStrip | dep: react, ../../../hooks/use-special-keys, React, use-special-keys hook
- terminal-session-tabs.test.tsx | Tests a React component that renders interactive terminal session tabs with selection, close confirmation, renaming, creation limits, and status indicators. | dep: @testing-library/react, vitest, ./terminal-session-tabs
- terminal-session-tabs.tsx | Renders a tabbed interface for managing multiple terminal sessions with selection, creation, renaming, and close confirmation features. | exp: TerminalSessionInfo, TerminalSessionTabsProps, TerminalSessionTabs | dep: react, React
- terminal.tsx | A React component that renders an interactive xterm.js terminal connected to a WebSocket backend, with mobile touch support, flow control, heartbeat monitoring, and automatic reconnection. | exp: TerminalProps, TerminalRef, TerminalComponent | dep: react, xterm, xterm-addon-fit, xterm-addon-web-links, xterm/css/xterm.css, ../../../hooks/use-special-keys
- terminal.tsx | A React component that renders an interactive terminal emulator using xterm.js, connecting to a WebSocket backend with features like mobile touch scrolling, font resizing, flow control, heartbeat monitoring, and automatic reconnection. | exp: TerminalProps, TerminalRef, TerminalComponent | dep: react, xterm, xterm-addon-fit, xterm-addon-web-links, xterm/css/xterm.css, ../../../hooks/use-special-keys
## arch
Feature-based component architecture with platform-specific view splitting (desktop/mobile), compound component pattern for terminal sessions, and adapter pattern for special key input variants (panel/strip).
Component-based architecture with platform-specific view variants (Desktop/Mobile), compound component pattern for terminal subsystems (tabs, special keys, xterm.js integration), and WebSocket-based real-time communication with reconnection and heartbeat monitoring.
## tags
terminal, special, keys, session, react, tabs, view, renders
## symbols
@@ -355,10 +355,9 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// In normal mode xterm.js has a scrollable viewport; in alternate
// screen (tmux/vim) there is no scrollback and the only way to
// scroll is to send mouse-wheel protocol sequences to the
// application. We detect which situation we're in by checking
// the active buffer type. We also lock the browser into the
// terminal area: touchstart prevents the browser from starting a
// page-scroll gesture, so swipes always go to the terminal.
// application. We detect the active buffer by reference equality
// (term.buffer.active === term.buffer.alternate) because the
// `type` string can be unreliable in some xterm.js versions.
let touchCleanup: (() => void) | undefined;
if (isMobile) {
let startY = 0;
@@ -388,8 +387,10 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
scrollPending = 0;
};
const isNormalBuffer = () => {
return termRef.current?.buffer.active.type === "normal";
const isAlternateScreen = () => {
const term = termRef.current;
if (!term) return false;
return term.buffer.active === term.buffer.alternate;
};
const onTouchStart = (e: TouchEvent) => {
@@ -405,35 +406,36 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
const touch = e.touches[0];
const deltaY = startY - touch.clientY;
const deltaX = Math.abs(startX - touch.clientX);
// Decide early whether this is a vertical scroll gesture.
if (!isScrolling) {
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 4) {
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 2) {
isScrolling = true;
}
}
if (isScrolling) {
// Always stop the browser from treating this as a page scroll.
e.preventDefault();
if (!isScrolling) return;
const normalBuffer = isNormalBuffer();
const viewport = container.querySelector(
".xterm-viewport",
) as HTMLElement | null;
// Take over the gesture so the page/toolbar never scrolls.
e.preventDefault();
if (normalBuffer && viewport) {
// Normal buffer: scroll the xterm viewport directly.
viewport.scrollTop += deltaY;
} else {
// Alternate screen (tmux/vim): accumulate the swipe and
// send SGR 1006 mouse-wheel events in steps.
scrollPending += deltaY;
flushScroll();
if (isAlternateScreen()) {
// Alternate screen (tmux/vim): accumulate the swipe and
// send SGR 1006 mouse-wheel events in steps.
scrollPending += deltaY;
flushScroll();
} else if (termRef.current) {
// Normal buffer: let xterm.js scroll its own viewport by
// the number of lines corresponding to the swipe distance.
const lineHeight =
termRef.current.options.fontSize != null
? (termRef.current.options.fontSize as number) * 1.2
: 10;
const lines = Math.round(deltaY / lineHeight);
if (lines !== 0) {
termRef.current.scrollLines(lines);
}
startY = touch.clientY;
} else if (Math.abs(deltaY) > 4 || Math.abs(deltaX) > 4) {
// Once the user has moved far enough to be considered a
// gesture, prevent any default page scroll/pinch behavior.
e.preventDefault();
}
startY = touch.clientY;
};
const onTouchEnd = () => {
if (isScrolling) {
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/styles
## role
Provides the complete visual design system and styling foundation for the web application, including themes, tokens, utilities, and component-specific styles.
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+4 -4
View File
@@ -4,16 +4,16 @@ dir: apps/web/src/styles
index: apps/web/src/styles/.pi-map.index.md
## role
Provides the complete visual design system and styling foundation for the web application, including themes, tokens, utilities, and component-specific styles.
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles.
## files
- global.css | Defines global CSS styles for a web application shell layout, navigation, cards, forms, dialogs, settings pages, and responsive design patterns. | dep: CSS custom properties (CSS variables: --border, --panel, --brand, --muted, --ink, --bg, --danger, --success, --warning, --font-size-xs, --font-size-sm, --space-2, --space-3, --space-4, --space-5)
- syntax-highlight.css | Stylesheet for a syntax highlighting component with toolbar, line numbers, code display, and Prism.js theme integration | dep: Prism.js
- tokens.css | Defines a comprehensive CSS design token system with light/dark themes, spacing scales, breakpoints, and fluid typography for a web application.
- utilities.css | Provides responsive CSS utility classes and component-specific styles for a web application including layout systems, terminal styling, session management, and mobile-first adaptations. | dep: CSS custom properties (var(--space-*), var(--border), var(--bg), var(--text), var(--brand), var(--success), var(--danger), var(--muted)), xterm.js (terminal library)
- utilities.css | Provides responsive CSS utility classes and component-specific styles for a web application featuring terminals, dialogs, tables, forms, and navigation with mobile-first breakpoints | dep: CSS custom properties (variables like --space-*, --border, --bg, --brand, --muted, --success, --danger, --text-xs, --text-sm), xterm.js (terminal library)
## arch
Token-driven CSS architecture using CSS custom properties for light/dark theming, utility-first responsive classes, and modular separation of concerns across global styles, syntax highlighting, design tokens, and utility helpers.
CSS custom properties (variables) based theming system with light/dark mode support, mobile-first responsive breakpoints, fluid typography scales, utility-first class patterns, and modular separation of concerns across tokens, utilities, global layouts, and component-specific styles.
## tags
var(, space, global, css, web, application, syntax, defines
space, global, css, web, application, syntax, defines, styles
## symbols
-
## workflows
+3 -7
View File
@@ -551,14 +551,12 @@ a.nav-item,
height: 100%;
padding: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Ensure xterm's internal viewport also disables browser gestures on mobile */
/* Let xterm.js's own viewport handle touch; we only lock the outer page */
.terminal-container .xterm-viewport {
touch-action: none;
overscroll-behavior: none;
touch-action: auto;
overscroll-behavior: auto;
}
/* xterm.js manages its own positioning and sizing */
@@ -572,8 +570,6 @@ a.nav-item,
/* Mobile terminal container - no padding to maximize space */
.terminal-wrapper.mobile .terminal-container {
padding: 0;
touch-action: none;
overscroll-behavior: none;
}
.terminal-wrapper.mobile {