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
20 changes: 16 additions & 4 deletions .github/skills/vscode-dev-workbench/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@ If your paths differ, check `server/` in `vscode-dev` for the source root resolu

## Start the dev server

**Critical:** Run `npm run dev` from the **`vscode-dev`** folder, NOT from `vscode`. The `vscode` repo has no `dev` script and will fail with `npm error Missing script: "dev"`. Terminal tools that simplify/strip leading `cd` into separate commands will silently keep the cwd of a previous terminal — always use an absolute `pushd` or verify with `pwd` before `npm run dev`.

```bash
cd vscode-dev
npm run dev # runs watch + nodemon; serves https://127.0.0.1:3000
cd /path/to/vscode-dev # NOT /path/to/vscode
npm run dev # runs watch + nodemon; serves https://127.0.0.1:3000
```

On first start you may see one crash like `Cannot find module './indexes'` — it's the watcher racing the first build. nodemon restarts automatically once `out/` finishes compiling.
If you're driving this through an agent/terminal tool, prefer:

```bash
pushd /absolute/path/to/vscode-dev >/dev/null && pwd && npm run dev
```

On first start you may see one crash like `Cannot find module './indexes'` — it's the watcher racing the first build. nodemon restarts automatically once `out/` finishes compiling. The server is ready when `curl -sk -o /dev/null -w "%{http_code}" https://127.0.0.1:3000/` returns `200`.

## URLs

Expand Down Expand Up @@ -97,15 +105,19 @@ For a true mobile viewport, drive a standalone Playwright script with `devices['

## Testing the Agents window against a local mock agent host

If the scenario touches the Agents window (`/agents` route), you almost always need the mock agent host running. Without it, the Agents window will sit on the sign-in / tunnel-discovery screen and block any real interaction. Start it **in addition to** the dev server — it's a second terminal, not a replacement.

`vscode-dev` supports a `?mock-agent-host=ws://…` URL parameter that short-circuits tunnel discovery and wires the Agents window to a raw WebSocket. Pair it with the mock agent host binary from `microsoft/vscode`:

```bash
cd vscode
cd /path/to/vscode
node out/vs/platform/agentHost/node/agentHostServerMain.js \
--enable-mock-agent --quiet --without-connection-token --port 8765
# Listens on ws://localhost:8765
```

Prerequisite: `out/` in the `vscode` repo must be populated by the `VS Code - Build` task (or `npm run watch`). If `out/vs/platform/agentHost/node/agentHostServerMain.js` is missing, start that task first.

`--enable-mock-agent` registers the `ScriptedMockAgent` from `src/vs/platform/agentHost/test/node/mockAgent.ts` with one pre-existing session. Seed additional sessions via the `VSCODE_AGENT_HOST_MOCK_SEED_SESSIONS` env var, using a comma-separated list of session URIs (for example, `VSCODE_AGENT_HOST_MOCK_SEED_SESSIONS=mock://pre-1,mock://pre-2`). Scripted prompts include `hello`, `use-tool`, `error`, `permission`, `write-file`, `run-safe-command`, `slow`, `client-tool`, `subagent`, etc. (see `mockAgent.ts` for the full list).

Then open:
Expand Down
60 changes: 32 additions & 28 deletions src/vs/sessions/browser/parts/media/sidebarPart.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,42 @@

/* ---- Phone Layout: Sidebar Drawer Overlay ---- */

/* On phone, the sidebar is a drawer that slides over the chat.
It takes 85% width (max 360px) and sits on top of everything. */
/* On phone, the sidebar is a full-width drawer that slides over the chat.
It covers the full viewport below the mobile top bar; the top bar's
sidebar toggle button remains visible and is used to dismiss it.

The drawer slides in/out with a transition (not a keyframe animation) so
that interrupted toggles retarget smoothly from the current position
rather than restarting. The split-view wrapper toggles `display: none`
via a `.visible` class; `transition-behavior: allow-discrete` defers
the discrete `display` change until the slide-out completes, and
`@starting-style` provides the offscreen origin for the slide-in. */
.agent-sessions-workbench.phone-layout .split-view-view:has(> .part.sidebar) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
bottom: 0 !important;
width: 85% !important;
max-width: 360px !important;
width: 100% !important;
height: 100% !important;
z-index: 250;
animation: sidebar-slide-in 200ms ease-out;
transform: translateX(0);
transition:
transform 260ms cubic-bezier(0.32, 0.72, 0, 1),
display 260ms allow-discrete;
}

/* Slide-in starting state (applies on each transition into .visible) */
@starting-style {
.agent-sessions-workbench.phone-layout .split-view-view.visible:has(> .part.sidebar) {
transform: translateX(-100%);
}
}

/* Slide-out target: when `.visible` is removed, splitview's own rule sets
`display: none`. With `allow-discrete` above, the transform animates first
and the discrete `display` swap happens at the end of the transition. */
.agent-sessions-workbench.phone-layout .split-view-view:not(.visible):has(> .part.sidebar) {
transform: translateX(-100%);
}

/* The sidebar Part inside fills its container */
Expand All @@ -90,32 +114,12 @@
height: 100%;
}

@keyframes sidebar-slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
@media (prefers-reduced-motion: reduce) {
.agent-sessions-workbench.phone-layout .split-view-view:has(> .part.sidebar) {
transition: none;
}
}

/* Sidebar backdrop — applied via JS when sidebar is open on phone */
.mobile-sidebar-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 240;
animation: backdrop-fade-in 200ms ease-out;
}

@keyframes backdrop-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}

/* Increase sidebar footer action button height for touch */
.agent-sessions-workbench.phone-layout .part.sidebar > .sidebar-footer .sidebar-action-button {
min-height: 44px;
Expand Down
138 changes: 66 additions & 72 deletions src/vs/sessions/browser/parts/mobile/mobileChatShell.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/*
* `!important` policy for this file:
*
* Only use `!important` when fighting one of:
* 1. SplitView.View.layoutContainer — sets inline position/top/left/width/height
* on every `.split-view-view` (src/vs/base/browser/ui/splitview/splitview.ts).
* 2. Part.layoutContents — inlines width/height on `.part > .content` via size().
* 3. An equal-specificity desktop rule in the main workbench stylesheet.
*
* Rules that only face lower-specificity desktop CSS (e.g. a single `.part.X`
* selector) do NOT need `!important` — the `.phone-layout` class on the
* workbench root already wins. When adding a new rule, omit `!important`
* first and only add it if DevTools shows an actual override.
*/

/* ---- Mobile Top Bar ---- */

.mobile-top-bar {
Expand Down Expand Up @@ -114,95 +129,74 @@

/* On phone, stack the mobile top bar and grid vertically */
.agent-sessions-workbench.phone-layout {
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}

/* On phone, split-view-views that directly contain a Part fill the full
grid area. Uses :has(> .part) to target only part containers — NOT
nested split-views inside parts' own content. */
.agent-sessions-workbench.phone-layout .split-view-view:has(> .part) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}

/* The grid's own branch nodes (NOT those inside parts) need full sizing.
Target only direct children of the grid root. */
.agent-sessions-workbench.phone-layout > .monaco-grid-view > .monaco-grid-branch-node {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}

/* Split-view-views inside the grid root that contain branch nodes */
.agent-sessions-workbench.phone-layout > .monaco-grid-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view:has(> .monaco-grid-branch-node) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}

/* Second-level grid branch nodes */
.agent-sessions-workbench.phone-layout > .monaco-grid-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view > .monaco-grid-branch-node {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
display: flex;
flex-direction: column;
overflow: hidden;
}

/* Third-level (top-right section) */
.agent-sessions-workbench.phone-layout > .monaco-grid-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view:has(> .monaco-grid-branch-node) {
/* On phone, all split-view-view wrappers inside the grid — both those
wrapping parts AND those wrapping nested grid branch nodes — fill the
full grid area. This collapses the multi-axis grid into a single
full-screen viewport so parts overlap rather than share horizontal
space. The sidebar uses its own z-index + transform to slide over
the chat (see sidebarPart.css). The :has() conditions scope strictly
to grid wrappers so splitviews used inside individual parts' content
(e.g. a sidebar's view list) are not affected. */
.agent-sessions-workbench.phone-layout .split-view-view:has(> .part),
.agent-sessions-workbench.phone-layout .split-view-view:has(> .monaco-grid-branch-node) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}

.agent-sessions-workbench.phone-layout > .monaco-grid-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view > .monaco-grid-branch-node > .monaco-split-view2 > .split-view-container > .split-view-view > .monaco-grid-branch-node {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
/* All grid branch nodes fill their parent. `.monaco-grid-branch-node` is
specific to the grid widget, so this descendant selector won't hit
splitviews used inside individual parts' content. The grid widget
does not write inline geometry to branch nodes (only to the wrapping
`.split-view-view`), so plain CSS suffices here. */
.agent-sessions-workbench.phone-layout .monaco-grid-branch-node {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

/* Remove card appearance from ALL parts on phone */
/* Remove card appearance from ALL parts on phone.
Specificity wins over the desktop card rule in style.css without !important;
width/height match what the mobile Part.layout() already inlines. */
.agent-sessions-workbench.phone-layout .part.chatbar,
.agent-sessions-workbench.phone-layout .part.sidebar,
.agent-sessions-workbench.phone-layout .part.auxiliarybar,
.agent-sessions-workbench.phone-layout .part.panel {
margin: 0 !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
--part-border-color: transparent !important;
width: 100% !important;
height: 100% !important;
margin: 0;
border: none;
border-radius: 0;
box-shadow: none;
--part-border-color: transparent;
width: 100%;
height: 100%;
}

/* Force content div inside parts to fill the part on phone.
Part.layoutContents() sets inline width/height via size(), which
may use the grid-allocated dimensions rather than the CSS-overridden
100% dimensions. Override with !important. */
/* Pin Part content to the wrapper's full width on phone.
`!important` is required because `Part.layoutContents()` inlines the
width on `.part > .content` based on the splitview size (rule #2 in the
policy above). Without this, opening the sidebar — which makes the
splitview share space between sidebar and chatbar — would shrink the
chatbar's content during the drawer slide animation. */
.agent-sessions-workbench.phone-layout .part.chatbar > .content,
.agent-sessions-workbench.phone-layout .part.sidebar > .content,
.agent-sessions-workbench.phone-layout .part.auxiliarybar > .content,
.agent-sessions-workbench.phone-layout .part.panel > .content {
width: 100% !important;
height: 100% !important;
}

/* Hide the session composite bar (Copilot CLI / Approvals / Branch) on phone */
.agent-sessions-workbench.phone-layout .session-composite-bar {
display: none !important;
display: none;
}

/* Ensure the grid view element doesn't overflow — flex child must shrink */
Expand All @@ -224,8 +218,8 @@
}

.agent-sessions-workbench.phone-layout .interactive-session .interactive-input-part {
max-width: none !important;
padding-bottom: calc(10px + env(safe-area-inset-bottom)) !important;
max-width: none !important; /* fights equal-specificity rule in style.css */
padding-bottom: calc(10px + env(safe-area-inset-bottom));
}

/* Chat input minimum font size to prevent iOS auto-zoom */
Expand All @@ -247,7 +241,7 @@
}

.agent-sessions-workbench.phone-layout .part.sidebar > .composite.title {
display: none !important;
display: none;
}

.agent-sessions-workbench.phone-layout .part.sidebar > .content {
Expand All @@ -260,7 +254,7 @@

/* Customization toolbar: hidden on phone (opens editors, not mobile-compatible) */
.agent-sessions-workbench.phone-layout .part.sidebar .ai-customization-toolbar {
display: none !important;
display: none;
}

/* Make sidebar footer touch-friendly */
Expand All @@ -271,7 +265,7 @@

/* Hide the "+ Session" button in the sidebar on phone — replaced by top bar + button */
.agent-sessions-workbench.phone-layout .agent-sessions-new-button-container {
display: none !important;
display: none;
}

/* Hide sashes on phone */
Expand All @@ -291,7 +285,7 @@

/* On phone, push the chat input to the bottom of the chat area */
.agent-sessions-workbench.phone-layout .interactive-session .interactive-input-and-execute-toolbar {
margin-top: auto !important;
margin-top: auto;
}

/* ---- Phone Layout: Chat Welcome Page ---- */
Expand Down Expand Up @@ -351,7 +345,7 @@
}

.agent-sessions-workbench.phone-layout .session-workspace-picker-label {
font-size: 18px !important;
font-size: 18px;
opacity: 0.6;
}

Expand All @@ -370,12 +364,12 @@

/* Hide the local mode bar (Copilot CLI / Default Approvals / Branch) on phone */
.agent-sessions-workbench.phone-layout .new-chat-bottom-container {
display: none !important;
display: none;
}

/* Also hide the sessions-chat-widget's DnD overlay on phone */
.agent-sessions-workbench.phone-layout .sessions-chat-dnd-overlay {
display: none !important;
display: none;
}

/* Chat widget fills full width on phone */
Expand Down
Loading
Loading