diff --git a/.changeset/fn-8633-tablet-terminal-drag.md b/.changeset/fn-8633-tablet-terminal-drag.md new file mode 100644 index 0000000000..4f0f38f2a8 --- /dev/null +++ b/.changeset/fn-8633-tablet-terminal-drag.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Make floating terminal windows reliably draggable on touch tablets. +category: fix +dev: Adds a reserved tablet-only terminal header drag grip and touch-sized pop-out target. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index cb3dd1e612..5c84d51257 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -817,7 +817,7 @@ Mailbox view shows inbox/outbox communication threads and unread state. When an ## Interactive Terminal -Fusion embeds a terminal using xterm.js. Desktop and tablet use the footer status bar as the terminal launcher; mobile keeps the full-screen terminal path. Known touch tablets, including at the 768px responsive boundary, retain docked/floating presentation rather than falling back to the phone sheet. Their saved floating size and position and touch drag/edge-or-corner resize controls stay available when a software keyboard shortens the visual viewport. True narrow phones, including folded panes and short phone landscapes, intentionally remain full-screen. +Fusion embeds a terminal using xterm.js. Desktop and tablet use the footer status bar as the terminal launcher; mobile keeps the full-screen terminal path. Known touch tablets, including at the 768px responsive boundary, retain docked/floating presentation rather than falling back to the phone sheet. Their saved floating size and position and touch drag/edge-or-corner resize controls stay available when a software keyboard shortens the visual viewport. In tablet floating mode, use the reserved grip at the left side of the terminal header to move the window; it stays available even when the tab strip overflows. True narrow phones, including folded panes and short phone landscapes, intentionally remain full-screen. On first launch or first open, the terminal keeps reconnecting automatically until its initial WebSocket opens; it should show **Reconnecting...** during that cold-start recovery rather than requiring a manual **Reconnect** click. If an already-connected terminal drops and exhausts its bounded reconnect budget, Fusion then parks it as **Disconnected** and surfaces the manual **Reconnect** control. @@ -838,7 +838,7 @@ Use the terminal on desktop/tablet: 3. Drag the top edge of the docked or pinned panel. Expected outcome: the panel height changes within its viewport-safe bounds and persists per project, with pinned mode clamped shorter so the application remains usable. 4. Select **Pop out** from the bottom action-control footer. - Expected outcome: the terminal switches to a floating window that can be dragged and freely resized; size, position, and display mode are saved per project. + Expected outcome: the terminal switches to a floating window that can be dragged and freely resized; size, position, and display mode are saved per project. On touch tablets, drag the reserved header grip rather than the horizontally scrollable tab strip. 5. Select **Dock** in the floating terminal. Expected outcome: the terminal returns to the bottom docked overlay panel using the saved docked height. 6. Select the scripts chevron beside the footer **Terminal** button. diff --git a/packages/dashboard/app/components/TerminalModal.css b/packages/dashboard/app/components/TerminalModal.css index 41599d2aba..8e1b728475 100644 --- a/packages/dashboard/app/components/TerminalModal.css +++ b/packages/dashboard/app/components/TerminalModal.css @@ -241,6 +241,67 @@ FN-7897: reserve space for the fixed ExecutorStatusBar footer when it is rendere border-radius: 2px; } +/* +FNXC:TerminalModalControls 2026-07-31-22:41: +The tablet classifier, rather than the legacy 769px media query, owns this grip so a known +768px touch tablet remains a floating window instead of receiving phone-sheet chrome. Only the +reserved grip disables browser gesture handling: applying `touch-action: none` to the panel or +header prevents native horizontal panning in the descendant overflowing tab strip. +*/ +.modal.terminal-modal.terminal-modal--tablet.terminal-modal--floating .terminal-header__drag-grip { + position: relative; + display: flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + min-inline-size: var(--modal-resize-touch-target); + min-block-size: var(--modal-resize-touch-target); + touch-action: none; + cursor: grab; + user-select: none; +} + +.modal.terminal-modal.terminal-modal--tablet.terminal-modal--floating .terminal-header__drag-grip::before { + content: ""; + inline-size: var(--space-lg); + block-size: var(--space-xs); + border-radius: var(--radius-pill); + background: color-mix(in srgb, var(--text-muted) 55%, transparent); +} + +.modal.terminal-modal.terminal-modal--tablet.terminal-modal--floating .terminal-header__drag-grip:active { + cursor: grabbing; +} + +/* +FNXC:TerminalModalControls 2026-07-31-22:41: +The tab strip must retain native horizontal touch panning while the sibling grip owns window +movement. This explicit contract prevents future tablet drag rules from suppressing tab scrolling. +*/ +.modal.terminal-modal.terminal-modal--tablet.terminal-modal--floating .terminal-tabs { + touch-action: pan-x; +} + +/* +FNXC:TerminalModalControls 2026-07-31-22:19: +Tablets start docked, so the footer pop-out control needs the same touch-sized reach without +changing its established 14px icon or footer layout. Its pseudo-element only enlarges the button +hit area; it is not a second terminal control. +*/ +.modal.terminal-modal.terminal-modal--tablet .terminal-clear-btn.terminal-clear-btn--icon[data-testid="terminal-popout-toggle"] { + position: relative; +} + +.modal.terminal-modal.terminal-modal--tablet .terminal-clear-btn.terminal-clear-btn--icon[data-testid="terminal-popout-toggle"]::before { + content: ""; + position: absolute; + inline-size: var(--modal-resize-touch-target); + block-size: var(--modal-resize-touch-target); + inset-inline-start: 50%; + inset-block-start: 50%; + transform: translate(-50%, -50%); +} + .terminal-tab { display: flex; align-items: center; diff --git a/packages/dashboard/app/components/TerminalModal.tsx b/packages/dashboard/app/components/TerminalModal.tsx index 4ff0737d8b..04ce09583f 100644 --- a/packages/dashboard/app/components/TerminalModal.tsx +++ b/packages/dashboard/app/components/TerminalModal.tsx @@ -2426,12 +2426,18 @@ export function TerminalModal({ isOpen, onClose, initialCommand, initialCommandG > ); + /* + FNXC:TerminalModalControls 2026-07-31-22:19: + The tab scroller owns tab presses and horizontal panning; floating-window drags begin only on + the reserved grip so a tab interaction cannot leave a captured header drag behind. + */ const renderTerminalTabStrip = (measuring = false) => (