diff --git a/packages/client/src/components/hermes/chat/DrawerPanel.vue b/packages/client/src/components/hermes/chat/DrawerPanel.vue
index a25e63f..42a146f 100644
--- a/packages/client/src/components/hermes/chat/DrawerPanel.vue
+++ b/packages/client/src/components/hermes/chat/DrawerPanel.vue
@@ -86,9 +86,10 @@ function handleClose() {
.drawer-panel {
position: fixed;
top: 0;
- right: -900px;
- width: 900px;
- height: 100vh;
+ right: min(-1180px, -88vw);
+ width: min(1180px, 88vw);
+ height: calc(100 * var(--vh));
+ max-height: calc(100 * var(--vh));
background: $bg-card;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
display: flex;
@@ -180,5 +181,3 @@ function handleClose() {
overflow: auto;
}
-
-
diff --git a/packages/client/src/components/hermes/chat/TerminalPanel.vue b/packages/client/src/components/hermes/chat/TerminalPanel.vue
index c64a09c..cfe83a3 100644
--- a/packages/client/src/components/hermes/chat/TerminalPanel.vue
+++ b/packages/client/src/components/hermes/chat/TerminalPanel.vue
@@ -103,6 +103,9 @@ let activeFitAddon: FitAddon | null = null;
let resizeObserver: ResizeObserver | null = null;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 3;
+let touchScrollLastY: number | null = null;
+let touchScrollRemainder = 0;
+const TOUCH_SCROLL_LINE_PX = 18;
// ─── Computed ──────────────────────────────────────────────────
@@ -356,6 +359,35 @@ function sendResize() {
} catch {}
}
+function handleTerminalTouchStart(event: TouchEvent) {
+ if (event.touches.length !== 1) {
+ touchScrollLastY = null;
+ touchScrollRemainder = 0;
+ return;
+ }
+ touchScrollLastY = event.touches[0].clientY;
+ touchScrollRemainder = 0;
+}
+
+function handleTerminalTouchMove(event: TouchEvent) {
+ if (!activeTerm || event.touches.length !== 1 || touchScrollLastY === null) return;
+ const nextY = event.touches[0].clientY;
+ touchScrollRemainder += touchScrollLastY - nextY;
+ touchScrollLastY = nextY;
+
+ const lines = Math.trunc(touchScrollRemainder / TOUCH_SCROLL_LINE_PX);
+ if (lines === 0) return;
+
+ activeTerm.scrollLines(lines);
+ touchScrollRemainder -= lines * TOUCH_SCROLL_LINE_PX;
+ event.preventDefault();
+}
+
+function handleTerminalTouchEnd() {
+ touchScrollLastY = null;
+ touchScrollRemainder = 0;
+}
+
// ─── Theme ───────────────────────────────────────────────────────
function applyTheme(themeName: string) {
@@ -517,7 +549,15 @@ onUnmounted(() => {
@@ -798,4 +838,35 @@ onUnmounted(() => {
display: none !important;
}
}
+
+@media (max-width: $breakpoint-mobile) {
+ .terminal-panel-drawer {
+ height: calc(100 * var(--vh));
+ max-height: calc(100 * var(--vh));
+ }
+
+ .terminal-main {
+ min-height: 0;
+ }
+
+ .terminal-container {
+ margin-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
+ }
+
+ .terminal-xterm {
+ :deep(.xterm-viewport),
+ :deep(.xterm-scrollable-element) {
+ touch-action: pan-y;
+ -webkit-overflow-scrolling: touch;
+ overscroll-behavior: contain;
+ scrollbar-width: thin !important;
+ }
+
+ :deep(.xterm-viewport::-webkit-scrollbar),
+ :deep(.xterm-scrollable-element::-webkit-scrollbar) {
+ display: block !important;
+ width: 6px !important;
+ }
+ }
+}
diff --git a/packages/client/src/views/hermes/TerminalView.vue b/packages/client/src/views/hermes/TerminalView.vue
index 4a51fc3..f00b00e 100644
--- a/packages/client/src/views/hermes/TerminalView.vue
+++ b/packages/client/src/views/hermes/TerminalView.vue
@@ -245,6 +245,9 @@ let activeTerm: Terminal | null = null;
let activeFitAddon: FitAddon | null = null;
let resizeObserver: ResizeObserver | null = null;
let mobileQuery: MediaQueryList | null = null;
+let touchScrollLastY: number | null = null;
+let touchScrollRemainder = 0;
+const TOUCH_SCROLL_LINE_PX = 18;
// ─── Computed ──────────────────────────────────────────────────
@@ -495,6 +498,35 @@ function sendResize() {
} catch {}
}
+function handleTerminalTouchStart(event: TouchEvent) {
+ if (event.touches.length !== 1) {
+ touchScrollLastY = null;
+ touchScrollRemainder = 0;
+ return;
+ }
+ touchScrollLastY = event.touches[0].clientY;
+ touchScrollRemainder = 0;
+}
+
+function handleTerminalTouchMove(event: TouchEvent) {
+ if (!activeTerm || event.touches.length !== 1 || touchScrollLastY === null) return;
+ const nextY = event.touches[0].clientY;
+ touchScrollRemainder += touchScrollLastY - nextY;
+ touchScrollLastY = nextY;
+
+ const lines = Math.trunc(touchScrollRemainder / TOUCH_SCROLL_LINE_PX);
+ if (lines === 0) return;
+
+ activeTerm.scrollLines(lines);
+ touchScrollRemainder -= lines * TOUCH_SCROLL_LINE_PX;
+ event.preventDefault();
+}
+
+function handleTerminalTouchEnd() {
+ touchScrollLastY = null;
+ touchScrollRemainder = 0;
+}
+
// ─── Theme ───────────────────────────────────────────────────────
function applyTheme(themeName: string) {
@@ -689,7 +721,15 @@ onUnmounted(() => {
@@ -980,6 +1020,19 @@ onUnmounted(() => {
// ─── Mobile ─────────────────────────────────────────────────────
@media (max-width: $breakpoint-mobile) {
+ .terminal-panel {
+ height: calc(100 * var(--vh));
+ max-height: calc(100 * var(--vh));
+ }
+
+ .terminal-main {
+ min-height: 0;
+ }
+
+ .terminal-container {
+ margin-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
+ }
+
.session-close-btn {
display: flex;
}
@@ -1011,6 +1064,20 @@ onUnmounted(() => {
left: 0;
right: 0;
bottom: 0;
+
+ :deep(.xterm-viewport),
+ :deep(.xterm-scrollable-element) {
+ touch-action: pan-y;
+ -webkit-overflow-scrolling: touch;
+ overscroll-behavior: contain;
+ scrollbar-width: thin !important;
+ }
+
+ :deep(.xterm-viewport::-webkit-scrollbar),
+ :deep(.xterm-scrollable-element::-webkit-scrollbar) {
+ display: block !important;
+ width: 6px !important;
+ }
}
}