|
| 1 | +# Mobile Agent Sessions — Architecture |
| 2 | + |
| 3 | +## Core Principle |
| 4 | + |
| 5 | +**Every feature accessible in the desktop window must be accessible on mobile — same functionality, different presentation.** Mobile is NOT "desktop minus stuff." It is a parallel UI layer where the same services, views, and actions are rendered through mobile-native interaction patterns. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +### Mobile Part Subclasses |
| 10 | + |
| 11 | +Desktop Parts (`ChatBarPart`, `SidebarPart`, `PanelPart`, `AuxiliaryBarPart`) remain unchanged. Each has a **mobile subclass** that extends it and overrides only `layout()` and/or `updateStyles()` to remove card margins, border insets, and inline theme styles. `AgenticPaneCompositePartService` conditionally instantiates the mobile or desktop variant at startup based on viewport width (`< 640px` → phone). |
| 12 | + |
| 13 | +This means: |
| 14 | +- Desktop code has **zero** phone-layout checks — all mobile logic lives in mobile subclasses, `MobileTopBar`, and CSS. |
| 15 | + |
| 16 | +**Known limitation:** Part classes are chosen once at construction and never swapped at runtime. If the viewport changes class (e.g., device rotation from portrait to landscape), the original Part implementations remain. This is acceptable because real mobile devices don't switch between phone and desktop — the scenario only occurs in DevTools emulation. |
| 17 | + |
| 18 | +### View & Action Gating |
| 19 | + |
| 20 | +Views, menu items, and actions use `when` clauses with the `sessionsIsMobileLayout` context key to control visibility per viewport class. This follows a **default-deny** approach for mobile: |
| 21 | + |
| 22 | +- **Desktop-only features** add `when: IsMobileLayoutContext.negate()` to their view descriptors and menu registrations. They simply don't appear on mobile. |
| 23 | +- **Mobile-compatible features** (chat, sessions list) have no mobile gate — they render on all viewports. |
| 24 | +- **Mobile-specific replacements** (when ready) register with `when: IsMobileLayoutContext` and live in separate files under `parts/mobile/contributions/`. |
| 25 | + |
| 26 | +Two registrations can target the same slot with opposite `when` clauses, pointing to different view classes in different files — giving full file separation with no internal branching. |
| 27 | + |
| 28 | +#### Current Gating Status |
| 29 | + |
| 30 | +| Feature | Mobile Status | Mechanism | |
| 31 | +|---------|--------------|-----------| |
| 32 | +| Sessions list (sidebar) | ✅ Compatible | No gate | |
| 33 | +| Chat views (ChatBar) | ✅ Compatible | No gate | |
| 34 | +| Changes view (AuxiliaryBar) | ❌ Gated | `when: !sessionsIsMobileLayout` on view descriptor | |
| 35 | +| Files view (AuxiliaryBar) | ❌ Gated | `when: !sessionsIsMobileLayout` on view descriptor | |
| 36 | +| Logs view (Panel) | ❌ Gated | `when: !sessionsIsMobileLayout` on view descriptor | |
| 37 | +| Terminal actions | ❌ Gated | `when: !sessionsIsMobileLayout` on menu item | |
| 38 | +| "Open in VS Code" action | ❌ Gated | `when: !sessionsIsMobileLayout` on menu item | |
| 39 | +| Code review toolbar | ❌ Gated | `when: !sessionsIsMobileLayout` on menu item | |
| 40 | +| Customizations toolbar | ❌ Hidden | CSS `display: none` on phone | |
| 41 | +| Titlebar | ❌ Hidden | Grid `visible: false` + CSS + MobileTopBar replacement | |
| 42 | + |
| 43 | +### Phone Layout |
| 44 | + |
| 45 | +On phone-sized viewports (`< 640px` width): |
| 46 | + |
| 47 | +``` |
| 48 | +┌──────────────────────────────────┐ |
| 49 | +│ [☰] Session Title [+] │ ← MobileTopBar (prepended before grid) |
| 50 | +├──────────────────────────────────┤ |
| 51 | +│ │ |
| 52 | +│ Chat (edge-to-edge) │ ← Grid: ChatBarPart fills 100% |
| 53 | +│ │ |
| 54 | +│ │ |
| 55 | +│ │ |
| 56 | +│ ┌──────────────────────────┐ │ |
| 57 | +│ │ Chat input │ │ ← Pinned to bottom |
| 58 | +│ └──────────────────────────┘ │ |
| 59 | +└──────────────────────────────────┘ |
| 60 | +``` |
| 61 | + |
| 62 | +- **MobileTopBar** is a DOM element prepended above the grid. It has a hamburger (☰), session title, and new session (+) button. |
| 63 | +- **Sidebar** is hidden by default and opens as an **85% width drawer overlay** with a backdrop when the hamburger is tapped. CSS makes its `split-view-view` absolutely positioned with `z-index: 250`. The workbench manually calls `sidebarPart.layout()` with drawer dimensions after opening. Closing the drawer clears the navigation stack. |
| 64 | +- **Titlebar** is hidden in the grid (`visible: false`) and via CSS — replaced by MobileTopBar. |
| 65 | +- **SessionCompositeBar** (chat tabs) is hidden via CSS. |
| 66 | +- The grid uses `display: flex; flex-direction: column` and all `split-view-view:has(> .part)` containers are positioned absolutely at `100% width/height`. |
| 67 | + |
| 68 | +### Viewport Classification |
| 69 | + |
| 70 | +`SessionsLayoutPolicy` classifies the viewport: |
| 71 | +- **phone**: `width < 640px` |
| 72 | +- **tablet**: `640px ≤ width < 1024px` |
| 73 | +- **desktop**: `width ≥ 1024px` |
| 74 | + |
| 75 | +The workbench toggles CSS classes (`phone-layout`, `mobile-layout`) on `layout()` and creates/destroys mobile components when the viewport class changes at runtime (e.g., DevTools device emulation). MobileTopBar lifecycle is managed via a `DisposableStore` that is cleared on viewport transitions to prevent leaks. |
| 76 | + |
| 77 | +### Context Keys |
| 78 | + |
| 79 | +| Key | Type | Purpose | |
| 80 | +|-----|------|---------| |
| 81 | +| `sessionsViewportClass` | `string` | `'phone'`, `'tablet'`, or `'desktop'` | |
| 82 | +| `sessionsIsMobileLayout` | `boolean` | `true` when phone or tablet | |
| 83 | +| `sessionsKeyboardVisible` | `boolean` | `true` when virtual keyboard is visible | |
| 84 | + |
| 85 | +### Desktop → Mobile Component Mapping |
| 86 | + |
| 87 | +| Desktop Component | Mobile Equivalent | How Accessed | |
| 88 | +|---|---|---| |
| 89 | +| **Titlebar** (3-section toolbar) | **MobileTopBar** (☰ / title / +) | Always visible at top | |
| 90 | +| **Sidebar** (sessions list) | Drawer overlay (85% width) | Hamburger button (☰) | |
| 91 | +| **ChatBar** (chat widget) | Same Part, edge-to-edge, no card chrome | Default view (always visible) | |
| 92 | +| **AuxiliaryBar** (files, changes) | Gated — not shown on mobile | Planned: mobile-specific view | |
| 93 | +| **Panel** (terminal, output) | Gated — not shown on mobile | Planned: mobile-specific view | |
| 94 | +| **SessionCompositeBar** (chat tabs) | Hidden on phone | — | |
| 95 | +| **New Session** (sidebar button) | + button in MobileTopBar | Always visible in top bar | |
| 96 | + |
| 97 | +## File Map |
| 98 | + |
| 99 | +### Mobile Part Subclasses |
| 100 | + |
| 101 | +| File | Purpose | |
| 102 | +|------|---------| |
| 103 | +| `src/vs/sessions/browser/parts/mobile/mobileChatBarPart.ts` | Extends `ChatBarPart`. Overrides `layout()` (no card margins) and `updateStyles()` (no inline card styles). | |
| 104 | +| `src/vs/sessions/browser/parts/mobile/mobileSidebarPart.ts` | Extends `SidebarPart`. Overrides `updateStyles()` (no inline card/title styles). | |
| 105 | +| `src/vs/sessions/browser/parts/mobile/mobileAuxiliaryBarPart.ts` | Extends `AuxiliaryBarPart`. Overrides `layout()` and `updateStyles()` (no card margins or inline styles). | |
| 106 | +| `src/vs/sessions/browser/parts/mobile/mobilePanelPart.ts` | Extends `PanelPart`. Overrides `layout()` and `updateStyles()` (no card margins or inline styles). | |
| 107 | + |
| 108 | +### Mobile Chrome Components |
| 109 | + |
| 110 | +| File | Purpose | |
| 111 | +|------|---------| |
| 112 | +| `src/vs/sessions/browser/parts/mobile/mobileTopBar.ts` | Phone top bar: hamburger (☰), session title, new session (+). Emits `onDidClickHamburger`, `onDidClickNewSession`, `onDidClickTitle`. | |
| 113 | +| `src/vs/sessions/browser/parts/mobile/mobileChatShell.css` | **Single source of truth** for all phone-layout CSS: flex column layout, split-view-view absolute positioning, card chrome removal, part/content width overrides, sidebar title hiding, composite bar hiding, welcome page layout, sash hiding, button focus overrides, mobile pickers. | |
| 114 | + |
| 115 | +### Layout & Navigation |
| 116 | + |
| 117 | +| File | Purpose | |
| 118 | +|------|---------| |
| 119 | +| `src/vs/sessions/browser/layoutPolicy.ts` | `SessionsLayoutPolicy`: observable viewport classification (phone/tablet/desktop), platform flags (isIOS, isAndroid, isTouchDevice), part visibility and size defaults. | |
| 120 | +| `src/vs/sessions/browser/mobileNavigationStack.ts` | `MobileNavigationStack`: Android back button integration via `history.pushState` / `popstate`. Supports `push()`, `pop()`, and `clear()`. | |
| 121 | +| `src/vs/sessions/common/contextkeys.ts` | Mobile context keys: `ViewportClassContext`, `IsMobileLayoutContext`, `KeyboardVisibleContext`. | |
| 122 | + |
| 123 | +### Part Instantiation |
| 124 | + |
| 125 | +| File | Purpose | |
| 126 | +|------|---------| |
| 127 | +| `src/vs/sessions/browser/paneCompositePartService.ts` | `AgenticPaneCompositePartService`: checks viewport width at construction time and instantiates `Mobile*Part` vs desktop `*Part` classes accordingly. | |
| 128 | + |
| 129 | +### Workbench Integration |
| 130 | + |
| 131 | +| File | Key Changes | |
| 132 | +|------|-------------| |
| 133 | +| `src/vs/sessions/browser/workbench.ts` | Layout policy integration, MobileTopBar creation/destruction (via `DisposableStore`), sidebar drawer open/close with backdrop, viewport-class-change detection, window resize listener, grid height calculation (subtracts MobileTopBar height), titlebar grid visibility toggle, `ISessionsManagementService` for new session button. | |
| 134 | +| `src/vs/sessions/browser/parts/chatBarPart.ts` | `_lastLayout` changed from `private` to `protected` for mobile subclass access. | |
| 135 | + |
| 136 | +### Styling |
| 137 | + |
| 138 | +| File | Purpose | |
| 139 | +|------|---------| |
| 140 | +| `src/vs/sessions/browser/parts/mobile/mobileChatShell.css` | All phone-layout CSS (see above). | |
| 141 | +| `src/vs/sessions/browser/parts/media/sidebarPart.css` | Sidebar drawer overlay CSS: 85% width, z-index 250, slide-in animation, backdrop. | |
| 142 | +| `src/vs/sessions/browser/media/style.css` | Mobile overscroll containment, 44px touch targets, quick pick bottom sheets, context menu action sheets, dialog sizing, notification positioning, hover card suppression, editor modal full-screen. | |
| 143 | + |
| 144 | +### PWA & Viewport |
| 145 | + |
| 146 | +| File | Purpose | |
| 147 | +|------|---------| |
| 148 | +| `src/vs/code/browser/workbench/workbench.html` | `viewport-fit=cover` meta tag, `theme-color` meta tag. | |
| 149 | +| `resources/server/manifest.json` | PWA manifest: `background_color`, `theme_color`, `orientation`. | |
| 150 | + |
| 151 | +## Remaining Work |
| 152 | + |
| 153 | +- **Session title sync**: MobileTopBar shows hardcoded "New Session" — needs to subscribe to `sessionsManagementService.activeSession` and update title when session changes. |
| 154 | +- **Files & Terminal access**: Should become mobile-specific views gated with `when: IsMobileLayoutContext`. |
| 155 | +- **iOS keyboard handling**: Adjust layout when virtual keyboard appears (context key exists, but no layout response yet). |
| 156 | +- **Session list inline actions**: Make always-visible on touch devices (no hover-to-reveal). |
| 157 | +- **Customizations on mobile**: Currently hidden — needs a mobile-friendly alternative. |
0 commit comments