Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions desktop/frontend/src/mobile/MobileTerminal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ onBeforeUnmount(() => {

<template>
<div class="mobile-term">
<div ref="container" class="term"></div>
<div ref="container" class="term" :class="{ inert: !isDriver }"></div>
<div v-if="!isDriver" class="viewer-overlay">
<div class="viewer-card">
<div class="viewer-title">{{ t('terminal.remoteHasControl') }}</div>
<button v-if="canControl" class="take-control" data-testid="mobile-take-control" @click="takeControl">{{ t('terminal.takeControl') }}</button>
<button v-if="canControl" type="button" class="take-control" data-testid="mobile-take-control" @click.stop="takeControl">{{ t('terminal.takeControl') }}</button>
<div v-else class="view-only-copy" data-testid="mobile-view-only-overlay">{{ t('mobile.viewOnly') }}</div>
</div>
</div>
Expand Down Expand Up @@ -210,6 +210,11 @@ onBeforeUnmount(() => {
.take-control { padding: 8px 16px; border: none; border-radius: 8px; background: #3b82f6; color: #fff; font-weight: 600; }
.view-only-copy { color: #fbbf24; font-size: 0.82rem; }
.term { flex: 1; min-height: 0; }
/* While the viewer overlay is up, swallow pointer events on the terminal so an
iOS tap on the "Take control" button can't fall through to xterm's hidden
<textarea> and open the on-screen keyboard. The button itself sits on the
overlay (a sibling) and stays interactive. */
.term.inert { pointer-events: none; }
/* Smooth, inertial scrollback on iOS: pan-y keeps the fling momentum (and
disables double-tap/pinch zoom over the terminal), -webkit-overflow-scrolling
is the legacy momentum flag, and overscroll-behavior stops the scroll from
Expand Down
16 changes: 16 additions & 0 deletions desktop/frontend/src/mobile/__tests__/MobileTerminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ describe('MobileTerminal', () => {
expect(w.find('[data-testid="mobile-take-control"]').exists()).toBe(false)
})

it('blocks pointer events on .term while viewing so iOS taps cannot fall through to xterm', async () => {
const w = mount(MobileTerminal, { props: { endpoint: { url: 'wss://r', token: 'atk_t' }, sessionId: 's1', info, active: true } })
// Driver by default → no blocking.
expect(w.find('.term').classes()).not.toContain('inert')

lastHandlers.onDriverChange?.('owner-A', false, 'mac-mini')
await w.vm.$nextTick()
// Viewing now → .term must be pointer-inert so taps land on the overlay button.
expect(w.find('.term').classes()).toContain('inert')

lastHandlers.onDriverChange?.('me', true, '')
await w.vm.$nextTick()
// Back to driver → interactive again.
expect(w.find('.term').classes()).not.toContain('inert')
})

it('renders a mobile control panel with required keys and quick text buttons', () => {
const w = mount(MobileTerminal, { props: { endpoint: { url: 'wss://r', token: 'atk_t' }, sessionId: 's1', info, active: true } })
expect(w.find('[data-testid="mobile-control-panel"]').exists()).toBe(true)
Expand Down
Loading