diff --git a/.gitignore b/.gitignore
index 58f1d9f..7f0a081 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ node_modules/
dist/
*.vsix
.DS_Store
-settings.json
\ No newline at end of file
+settings.json
+.superpowers/
\ No newline at end of file
diff --git a/docs/superpowers/plans/2026-07-19-modern-panel-redesign.md b/docs/superpowers/plans/2026-07-19-modern-panel-redesign.md
new file mode 100644
index 0000000..31f436c
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-19-modern-panel-redesign.md
@@ -0,0 +1,831 @@
+# Modern Panel Redesign Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Give the request/response split in the request editor webview a VS Code "modern UI" look — matching rounded-card panels with no separator border, a dot-indicator drag handle between them that turns into a bold gradient line on hover — and fix the sidebar's whole-row grab cursor by scoping drag to a dedicated grip handle.
+
+**Architecture:** All panel/divider changes are CSS + markup only inside `src/webview/request/` (no changes to `RequestContext.tsx`'s resize state/logic or the `ResizeObserver`-driven layout switch). The sidebar fix moves `draggable` off the row `
` onto a new grip ``, which also requires extracting `FolderItem` out of `Sidebar.tsx` into its own file to keep `Sidebar.tsx` under the project's 500-line component limit (it's 678 lines before this change).
+
+**Tech Stack:** TypeScript (strict), React 18, plain CSS custom properties (no CSS-in-JS, no Tailwind in these two bundles' relevant files).
+
+## Global Constraints
+
+- Strict TypeScript — never `@ts-nocheck`, `@ts-ignore`, eslint-disable.
+- No test suite/lint script in this repo — verification is `npx tsc --noEmit` (run it yourself) plus a manual step for the developer (do not run `npm run build`/`npm run watch` yourself).
+- Keep components under 500 lines; extract a sub-component rather than growing a file further (project rule — this is why Task 3 extracts `FolderItem`).
+- Keep spacing/padding on the smaller end of the existing `--rl-sp*` scale — confirmed with the user that generous demo-style margins must not be used, so the layout stays usable on small screens and at 150% editor zoom.
+- No new CSS custom properties — consolidate onto tokens that already exist in `src/webview/request/styles.css`'s `:root` block (`--rl-sp0`…`--rl-sp5`, `--rl-r1`/`--rl-r2`/`--rl-r3`, `--restlab-gradient*`, `--glass-bg`, `--glass-border`).
+- Spec: `docs/superpowers/specs/2026-07-19-modern-panel-redesign-design.md`.
+
+---
+
+### Task 1: Symmetric rounded panels
+
+**Files:**
+- Modify: `src/webview/request/styles.css:834-841` (`.request-content`)
+- Modify: `src/webview/request/styles.css:1463-1486` (`.response-section`)
+
+**Interfaces:** None — pure CSS, no new classes consumed elsewhere.
+
+- [ ] **Step 1: Give `.request-content` the same card treatment `.response-section` already has**
+
+Change:
+
+```css
+/* Request Content */
+.request-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ overflow: hidden;
+}
+```
+
+to:
+
+```css
+/* Request Content */
+.request-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ overflow: hidden;
+ padding: var(--rl-sp0) var(--rl-sp2) var(--rl-sp2);
+ border: 1px solid var(--glass-border);
+ border-radius: var(--rl-r3);
+ background: var(--glass-bg);
+ backdrop-filter: blur(8px);
+ position: relative;
+}
+
+.request-content::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 24px;
+ right: 24px;
+ height: 1px;
+ background: var(--restlab-gradient);
+ opacity: 0.5;
+}
+```
+
+This mirrors `.response-section` and `.response-section::before` exactly (same padding/border/radius/background/accent-line pattern), giving the request side the same card look. `overflow: hidden` is kept (unlike `.response-section`'s `overflow: visible`) because `.request-content` relies on it to clip the scrolling tab content — this is an intentional, pre-existing difference, not an inconsistency to fix.
+
+- [ ] **Step 2: Move `.response-section` off its hardcoded radius onto the shared token**
+
+Change:
+
+```css
+/* Response Section */
+.response-section {
+ padding: var(--rl-sp0) var(--rl-sp2) var(--rl-sp2);
+ border: 1px solid var(--glass-border);
+ border-radius: 12px;
+ background: var(--glass-bg);
+```
+
+to:
+
+```css
+/* Response Section */
+.response-section {
+ padding: var(--rl-sp0) var(--rl-sp2) var(--rl-sp2);
+ border: 1px solid var(--glass-border);
+ border-radius: var(--rl-r3);
+ background: var(--glass-bg);
+```
+
+- [ ] **Step 3: Type-check**
+
+Run: `npx tsc --noEmit`
+Expected: no errors (this task is CSS-only, but confirms nothing else broke).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/webview/request/styles.css
+git commit -m "style: give the request panel the same rounded-card treatment as the response panel"
+```
+
+---
+
+### Task 2: Divider dots + bold hover line
+
+**Files:**
+- Modify: `src/webview/request/styles.css:852-933` (`.resize-handle` block)
+- Modify: `src/webview/request/ResponsePanel.tsx:64-69`
+
+**Interfaces:** None — the `resize-handle`/`isResizing`/`splitLayout`/`handleResizeStart` wiring from `RequestContext.tsx` is unchanged; only the handle's inner markup and its CSS change.
+
+- [ ] **Step 1: Replace the resize-handle CSS block**
+
+The existing block includes a `.resize-handle-bar` element that is *always* `display: none` regardless of orientation (dead code — both the `.horizontal` and `.vertical` variants hide it), so its hover-only grow/glow rules never fire today. Replace the whole block (lines 852-933) — from:
+
+```css
+/* Resize Handle */
+.resize-handle {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ user-select: none;
+}
+
+/* Horizontal resize handle (for top/bottom split) */
+.resize-handle.horizontal {
+ height: 5px;
+ cursor: row-resize;
+ margin: 0;
+}
+
+.resize-handle.horizontal::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 50%;
+ height: 1px;
+ background: var(--glass-border);
+ transition: background 0.15s ease;
+}
+
+.resize-handle.horizontal .resize-handle-bar {
+ display: none;
+}
+
+/* Vertical resize handle (for left/right split) */
+.resize-handle.vertical {
+ width: 5px;
+ cursor: col-resize;
+ margin: 0;
+}
+
+.resize-handle.vertical::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 50%;
+ width: 1px;
+ background: var(--glass-border);
+ transition: background 0.15s ease;
+}
+
+.resize-handle.vertical .resize-handle-bar {
+ display: none;
+}
+
+.resize-handle:hover::before,
+.resize-handle.active::before {
+ background: var(--restlab-gradient);
+}
+
+.resize-handle-bar {
+ border-radius: 2px;
+ background: var(--vscode-descriptionForeground);
+ opacity: 0.3;
+ transition: all 0.15s ease;
+ z-index: 1;
+}
+
+.resize-handle:hover .resize-handle-bar,
+.resize-handle.active .resize-handle-bar {
+ background: var(--restlab-gradient);
+ opacity: 1;
+}
+
+.resize-handle.horizontal:hover .resize-handle-bar,
+.resize-handle.horizontal.active .resize-handle-bar {
+ width: 64px;
+}
+
+.resize-handle.vertical:hover .resize-handle-bar,
+.resize-handle.vertical.active .resize-handle-bar {
+ height: 64px;
+}
+```
+
+to:
+
+```css
+/* Resize Handle */
+.resize-handle {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ user-select: none;
+}
+
+/* Horizontal resize handle (for top/bottom split) */
+.resize-handle.horizontal {
+ height: 8px;
+ cursor: row-resize;
+ margin: 0;
+}
+
+.resize-handle.horizontal::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 50%;
+ height: 2px;
+ transform: translateY(-50%);
+ border-radius: 1px;
+ background: var(--restlab-gradient);
+ opacity: 0;
+ transition: opacity 0.15s ease;
+}
+
+/* Vertical resize handle (for left/right split) */
+.resize-handle.vertical {
+ width: 8px;
+ cursor: col-resize;
+ margin: 0;
+}
+
+.resize-handle.vertical::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 50%;
+ width: 2px;
+ transform: translateX(-50%);
+ border-radius: 1px;
+ background: var(--restlab-gradient);
+ opacity: 0;
+ transition: opacity 0.15s ease;
+}
+
+.resize-handle:hover::before,
+.resize-handle.active::before {
+ opacity: 1;
+}
+
+/* Dot indicator — visible at rest, fades out for the bold line on hover/drag */
+.resize-handle-dots {
+ display: flex;
+ gap: 3px;
+ z-index: 1;
+ transition: opacity 0.15s ease;
+}
+
+.resize-handle.horizontal .resize-handle-dots {
+ flex-direction: row;
+}
+
+.resize-handle.vertical .resize-handle-dots {
+ flex-direction: column;
+}
+
+.resize-handle-dots span {
+ width: 3px;
+ height: 3px;
+ border-radius: 50%;
+ background: var(--vscode-descriptionForeground);
+ opacity: 0.5;
+}
+
+.resize-handle:hover .resize-handle-dots,
+.resize-handle.active .resize-handle-dots {
+ opacity: 0;
+}
+```
+
+The handle grows from 5px to 8px (still a slim gap) to give the three dots room without clipping.
+
+- [ ] **Step 2: Swap the resize-handle markup in `ResponsePanel.tsx`**
+
+Change:
+
+```tsx
+
+
+
+```
+
+to:
+
+```tsx
+
+
+
+
+
+
+
+```
+
+- [ ] **Step 3: Type-check**
+
+Run: `npx tsc --noEmit`
+Expected: no errors.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/webview/request/styles.css src/webview/request/ResponsePanel.tsx
+git commit -m "style: replace the resize handle's dead bar element with a dot indicator and hover line"
+```
+
+---
+
+### Task 3: Sidebar drag handle — icon, `FolderItem` extraction, row wiring
+
+**Files:**
+- Create: `src/webview/components/icons/DragHandleIcon.tsx`
+- Create: `src/webview/sidebar/FolderItem.tsx`
+- Modify: `src/webview/sidebar/Sidebar.tsx:1-267`
+
+**Interfaces:**
+- Produces: `DragHandleIcon` (no props, matches the existing icon-component pattern e.g. `PlusIcon`). `FolderItem` — same `FolderItemProps` shape as today, default-exported from its own file instead of being defined inline in `Sidebar.tsx`.
+- Consumes (in `FolderItem.tsx`): `Folder`/`Request` from `../types/internal.types`, `FolderActionsDropdown`/`RequestActionsDropdown` from `./`, `Tooltip` from `../components/Tooltip`, existing icon components.
+
+`Sidebar.tsx` is 678 lines before this change, over the project's 500-line component limit — `FolderItem` (currently lines 51-266, ~216 lines) is a self-contained unit with its own props interface and is the exact code this task needs to edit anyway, so extracting it is the natural way to do this task's edit rather than an unrelated refactor.
+
+- [ ] **Step 1: Create the grip icon**
+
+```tsx
+import React from "react";
+const DragHandleIcon = () => (
+
+);
+
+export default DragHandleIcon;
+```
+
+Save as `src/webview/components/icons/DragHandleIcon.tsx`.
+
+- [ ] **Step 2: Create `FolderItem.tsx`**, moving `getMethodColor`, `FolderItemProps`, and the `FolderItem` component out of `Sidebar.tsx`, with the grip wired onto both the folder row and the request row:
+
+```tsx
+import React from "react";
+import Tooltip from "../components/Tooltip";
+import ChevronIcon from "../components/icons/ChevronIcon";
+import CollectionIcon from "../components/icons/CollectionIcon";
+import DragHandleIcon from "../components/icons/DragHandleIcon";
+import FolderIcon from "../components/icons/FolderIcon";
+import PlusIcon from "../components/icons/PlusIcon";
+import { Folder, Request } from "../types/internal.types";
+import FolderActionsDropdown from "./FolderActionsDropdown";
+import RequestActionsDropdown from "./RequestActionsDropdown";
+
+const getMethodColor = (method: string) => {
+ switch (method) {
+ case "GET":
+ return "method-get";
+ case "POST":
+ return "method-post";
+ case "PUT":
+ return "method-put";
+ case "PATCH":
+ return "method-patch";
+ case "DELETE":
+ return "method-delete";
+ default:
+ return "";
+ }
+};
+
+interface FolderItemProps {
+ folder: Folder;
+ depth?: number;
+ isDragging: boolean;
+ dragOverFolderId: string | null;
+ expandedFolders: Set;
+ activeRequestId: string | null;
+ onToggleFolder: (folderId: string) => void;
+ onDragStart: (
+ e: React.DragEvent,
+ type: "request" | "folder",
+ id: string,
+ name: string,
+ sourceFolderId?: string,
+ ) => void;
+ onDragEnd: () => void;
+ onDragOver: (e: React.DragEvent, folderId: string) => void;
+ onDragLeave: (e: React.DragEvent, folderId: string) => void;
+ onDrop: (e: React.DragEvent, targetFolderId: string) => void;
+ onAddRequest: (e: React.MouseEvent, folderId: string) => void;
+ onAddRequestFromCurl: (e: React.MouseEvent, folderId: string) => void;
+ onAddSubfolder: (e: React.MouseEvent, parentFolderId: string) => void;
+ onOpenFolder: (e: React.MouseEvent, folder: Folder) => void;
+ onExportCollection: (folderId: string, format: string) => void;
+ onDuplicateFolder: (e: React.MouseEvent, folderId: string) => void;
+ onRenameFolder: (e: React.MouseEvent, folderId: string) => void;
+ onDeleteFolder: (e: React.MouseEvent, folderId: string) => void;
+ onOpenRequest: (request: Request) => void;
+ onRenameRequest: (
+ e: React.MouseEvent,
+ requestId: string,
+ folderId: string,
+ ) => void;
+ onDuplicateRequest: (
+ e: React.MouseEvent,
+ requestId: string,
+ folderId: string,
+ ) => void;
+ onDeleteRequest: (
+ e: React.MouseEvent,
+ requestId: string,
+ folderId: string,
+ ) => void;
+}
+
+const FolderItem: React.FC = ({
+ folder,
+ depth = 0,
+ isDragging,
+ dragOverFolderId,
+ expandedFolders,
+ activeRequestId,
+ onToggleFolder,
+ onDragStart,
+ onDragEnd,
+ onDragOver,
+ onDragLeave,
+ onDrop,
+ onAddRequest,
+ onAddRequestFromCurl,
+ onAddSubfolder,
+ onOpenFolder,
+ onExportCollection,
+ onDuplicateFolder,
+ onRenameFolder,
+ onDeleteFolder,
+ onOpenRequest,
+ onRenameRequest,
+ onDuplicateRequest,
+ onDeleteRequest,
+}) => {
+ const isDropTarget = dragOverFolderId === folder.id;
+
+ return (
+
+ );
+};
+
+export default FolderItem;
+```
+
+Save as `src/webview/sidebar/FolderItem.tsx`.
+
+Two behavioral changes versus the original inline version, both intentional:
+- `draggable`/`onDragStart`/`onDragEnd` moved from the row `
` onto the new `.row-grip` `` — only the grip starts a drag now. The row's own `onDragOver`/`onDragLeave`/`onDrop` (it accepting drops as a folder target) stay on the row, since those aren't about the row being draggable.
+- Each grip has `onClick={(e) => e.stopPropagation()}` so a plain click on the grip doesn't also bubble up and trigger the row's `onClick` (folder toggle / request open).
+
+- [ ] **Step 3: Trim `Sidebar.tsx`'s head down to the `Sidebar` component, importing `FolderItem`**
+
+Replace lines 1-267 of `src/webview/sidebar/Sidebar.tsx` (everything from the top of the file through the blank line after the old inline `FolderItem` component) with:
+
+```tsx
+import React, { useEffect, useRef, useState } from "react";
+import CollectionAddIcon from "../components/icons/CollectionAddIcon";
+import HistoryIcon from "../components/icons/HistoryIcon";
+import NoItemsIcon from "../components/icons/NoItemsIcon";
+import Tooltip from "../components/Tooltip";
+import { Folder, Request } from "../types/internal.types";
+import FolderItem from "./FolderItem";
+import ImportDropdown from "./ImportDropdown";
+
+declare function acquireVsCodeApi(): {
+ postMessage: (message: unknown) => void;
+ getState: () => unknown;
+ setState: (state: unknown) => void;
+};
+
+export const vscode = acquireVsCodeApi();
+
+// Drag data type constants
+const DRAG_TYPE_REQUEST = "application/x-restlab-request";
+const DRAG_TYPE_FOLDER = "application/x-restlab-folder";
+
+interface DragData {
+ type: "request" | "folder";
+ id: string;
+ sourceFolderId?: string;
+ name: string;
+}
+
+```
+
+Everything from `export const Sidebar: React.FC = () => {` onward (the rest of the original file, previously starting at line 268) is unchanged — it already only references `FolderItem` by name in JSX, which now resolves via the new import instead of the old inline definition.
+
+- [ ] **Step 4: Type-check**
+
+Run: `npx tsc --noEmit`
+Expected: no errors. This catches any leftover unused import or missing prop from the extraction.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/webview/components/icons/DragHandleIcon.tsx src/webview/sidebar/FolderItem.tsx src/webview/sidebar/Sidebar.tsx
+git commit -m "refactor: extract FolderItem from Sidebar.tsx and scope row dragging to a grip handle"
+```
+
+---
+
+### Task 4: Sidebar grip styling, cursor scoping fix, final QA
+
+**Files:**
+- Modify: `src/webview/sidebar/sidebar.css:195` (insert after)
+- Modify: `src/webview/sidebar/drag-drop.css:76-79`
+- Modify: `src/webview/sidebar/drag-drop.css:105-112`
+
+**Interfaces:** None — CSS only, closing out Task 3's `.row-grip` markup.
+
+- [ ] **Step 1: Add `.row-grip` styling to `sidebar.css`**
+
+Insert directly after the existing rule (currently at line 195):
+
+```css
+.tree-row:hover .tree-icon { color: var(--restlab-accent); }
+```
+
+add:
+
+```css
+
+.row-grip {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--rl-icon);
+ height: var(--rl-icon);
+ color: var(--vscode-descriptionForeground);
+ cursor: grab;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+}
+.tree-row:hover .row-grip,
+.req-row:hover .row-grip {
+ opacity: 0.6;
+}
+.row-grip:active {
+ cursor: grabbing;
+}
+```
+
+This follows the same reserved-space opacity-fade pattern already used for `.action-btn` in this file (`.tree-row:hover .action-btn, .req-row:hover .action-btn { opacity: 0.6; }`), so there's no layout shift when the grip fades in on hover.
+
+- [ ] **Step 2: Scope the cursor rules in `drag-drop.css` to the grip instead of any `[draggable]` element**
+
+Change:
+
+```css
+/* Cursor styles during drag */
+[draggable="true"] {
+ cursor: grab;
+}
+
+[draggable="true"]:active {
+ cursor: grabbing;
+}
+```
+
+to:
+
+```css
+/* Cursor styles during drag — scoped to the grip handle, not the whole row */
+.row-grip {
+ cursor: grab;
+}
+
+.row-grip:active {
+ cursor: grabbing;
+}
+```
+
+(This duplicates the `.row-grip` cursor rules from Step 1 — that's fine; `drag-drop.css` is the file that owns drag-specific behavior, `sidebar.css` owns the row/grip layout. Keep both.)
+
+- [ ] **Step 3: Fix the now-orphaned `dragging-active[draggable="true"]` selector**
+
+`draggable` no longer lives on the same element as the `.dragging-active` class (it's on the row's child grip now), so this compound selector would stop matching. Change:
+
+```css
+/* Make items slightly transparent while dragging */
+.dragging-active[draggable="true"]:active {
+ opacity: 0.5;
+}
+```
+
+to:
+
+```css
+/* Make items slightly transparent while dragging */
+.dragging-active:active {
+ opacity: 0.5;
+}
+```
+
+`:active` still applies to the row while the mouse is pressed on its grip descendant (native CSS `:active`-state bubbling), so this preserves the original visual behavior without requiring `draggable` on the row itself.
+
+- [ ] **Step 4: Type-check**
+
+Run: `npx tsc --noEmit`
+Expected: no errors (CSS-only task, but confirms the whole branch still compiles clean).
+
+- [ ] **Step 5: Manual verification (developer runs this)**
+
+Run `npm run watch`, launch the Extension Development Host, and check:
+
+*Panels (Tasks 1-2):*
+- Request and response panels both show a rounded border and glass background, no hard line between them.
+- At rest, three small dots appear in the gap between the panels; hovering the gap fades the dots out and fades in a bold gradient line along the full length of the gap.
+- Drag the handle — the resize still works exactly as before (layout doesn't jump, min/max clamp still applies).
+- Toggle the layout button (`SplitIcon`) to switch between side-by-side and stacked — confirm the dots/line orient correctly in both (vertical stack for horizontal dots, horizontal stack for vertical dots).
+- Resize the panel narrow enough to hit the `680px` small-screen breakpoint and confirm the panels still look reasonable, not cramped or overlapping.
+- Test at 150% editor zoom (`Cmd/Ctrl` + `+` a few times or the zoom setting) — padding inside the panels should stay usable, not overflow.
+
+*Sidebar (Tasks 3-4):*
+- Hovering a folder or request row: cursor is a normal pointer over the label/icon/action buttons; only hovering the grip (fades in on the left) shows the grab cursor.
+- Clicking the row label still opens the request / toggles the folder, exactly as before.
+- Dragging from the grip still lets you reorder requests, move a request into a different folder, reorder folders, and drop at root — all existing drag-and-drop behavior is unchanged, just the initiation point moved.
+- Dragging still shows the reduced-opacity effect on the row being dragged.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/webview/sidebar/sidebar.css src/webview/sidebar/drag-drop.css
+git commit -m "style: fade in the sidebar drag grip on row hover, scope grab cursor to it"
+```
+
+---
+
+## Self-Review Notes
+
+- **Spec coverage:** shared radius token + symmetric request/response cards (Task 1); dot indicator + bold hover line, both orientations (Task 2); sidebar grip icon + scoped draggable + click-vs-drag fix (Tasks 3-4); compact spacing per the user's small-screen/150%-zoom feedback (Task 1's token choice + Task 2's 8px handle) — all covered. `FolderItem` extraction wasn't in the spec but is required by the project's 500-line component rule given Task 3 has to edit exactly that code.
+- **Placeholder scan:** no TBD/TODO; every step shows exact code or exact before/after diffs.
+- **Type consistency:** `DragHandleIcon` (no props) is used identically in `FolderItem.tsx`'s two call sites; `FolderItemProps` is unchanged in shape, just relocated; `.row-grip` class name is consistent across `FolderItem.tsx`, `sidebar.css`, and `drag-drop.css`.
diff --git a/docs/superpowers/specs/2026-07-19-modern-panel-redesign-design.md b/docs/superpowers/specs/2026-07-19-modern-panel-redesign-design.md
new file mode 100644
index 0000000..1f17a3c
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-19-modern-panel-redesign-design.md
@@ -0,0 +1,60 @@
+# Modern Panel Redesign (Rounded Sections + Dotted Divider) Design
+
+**Date:** 2026-07-19
+**Branch:** enh/design-change
+**Status:** Approved
+
+## Goal
+
+Redesign the request/response split in the request editor webview to match VS Code's newer "experimental modern UI" language: rounded-card sections with no hard separator border between them, a draggable gap between sections marked by a small dot indicator, and a bold highlight line on hover of that gap. Fix the underlying inconsistency that currently makes spacing/borders look mismatched, and separately fix a sidebar UX bug where the drag-cursor covers an entire row instead of a dedicated handle.
+
+## Problem
+
+- `src/webview/request/styles.css` has no shared `Panel`/`Section` primitive. Border-radius values are set ad hoc per element (`.response-section` L1463-1486 uses `12px`; `.tab-section` L936-949 uses `8px`; other elements in the same file use `4px`–`7px` or `var(--rl-r3)`), and spacing between sibling sections is a mix of independently-set `margin-bottom`, `gap`, and `padding`. This is the direct cause of the "spaces are not maintained" complaint.
+- `.request-panel` (L781-786) has no border/background at all, while `.response-section` does — the two sides of the split look asymmetric.
+- The existing resize handle (`ResponsePanel.tsx` L64-69, styled in `styles.css` L764-933) is a thin 5px strip with a `::before` line that only appears on hover — functional, but has no idle-state affordance (nothing tells the user the gap is draggable until they're already hovering it), and no dot indicator.
+- `src/webview/sidebar/Sidebar.tsx` puts `draggable="true"` directly on the entire row `
` for both folder rows (L126-137) and request rows (L225-241). `drag-drop.css` L105-112 then applies `cursor: grab` / `grabbing` via a blanket `[draggable="true"]` selector, so the grab cursor — and drag-start behavior — covers the label, icon, and action-button area of the row, not just an intended handle. This reads as "click here to do something" and confuses users, and also means any click on the row can be mistaken for a drag-start.
+
+## Design
+
+### 1. Shared spacing/radius tokens
+
+`src/webview/request/styles.css` already defines a token scale (`--rl-sp0`…`--rl-sp5` spacing, `--rl-r1`/`--rl-r2`/`--rl-r3` border-radius, `--restlab-gradient*`, `--glass-bg`, `--glass-border`) at L3-69, but existing rules don't consistently use them. Standardize:
+- All top-level panel corners use a single radius token (`--rl-r3`) instead of the current hardcoded `12px`/`8px` mix.
+- Panel padding and the gap between panels use the existing `--rl-sp*` scale, biased toward the smaller end (`--rl-sp1`/`--rl-sp2`) rather than the roomier values used in the exploratory mockups — this keeps the layout usable on small screens and at high editor zoom (verified against 150% zoom during review).
+- No new tokens are introduced; this is a consolidation of existing ones.
+
+### 2. Symmetric rounded panels (shallow scope)
+
+- `.request-panel` gets the same treatment `.response-section` already has: `border-radius: var(--rl-r3)`, `border: 1px solid var(--glass-border)`, `background: var(--glass-bg)` — reusing `.response-section`'s existing rule as the shared pattern rather than inventing a new one.
+- Scope is **shallow**: only the two top-level panels (request side, response side) get the card treatment. Nested content — Headers/Params/Body tab content, header/param rows — stays flat, no per-row cards. This matches how VS Code's own editor groups look (one border per group, flat rows inside) and avoids visual noise from many small bordered rows.
+- `.tab-section` (L936-949) and similar nested elements keep flat backgrounds; only their padding/margins move onto the shared spacing tokens for consistency.
+
+### 3. Divider / resize handle
+
+Replace the current always-thin `.resize-handle` idle state with a dot indicator, keeping the existing hover-line behavior:
+- **Idle state:** three small dots centered in the gap between panels (vertically stacked for the side-by-side/horizontal-split layout, horizontally arranged for the stacked/vertical-split layout — mirroring the existing `splitLayout` class toggle already read in `ResponsePanel.tsx`'s `resize-handle` className).
+- **Hover/drag state:** dots fade out, a bold line fades in along the full height (or width, for stacked layout) of the gap, using the existing `--restlab-gradient*` tokens already used elsewhere for accents — no new colors introduced.
+- No separator border is drawn between the two panels; the gap itself (dots at rest, line on hover/drag) is the only visual divider.
+- Implementation stays inside the existing files: markup in `ResponsePanel.tsx` (L64-69 today), styles in `styles.css` (L764-933 today), state/handlers unchanged in `RequestContext.tsx` (`splitLayout`, `isResizing`, `handleResizeStart`, L192-386) — this is a styling/markup change to the existing resize-handle element, not a new resize mechanism.
+
+### 4. Sidebar drag handle
+
+- Add a small grip glyph (⋮⋮) as its own element at the start of each row's markup, in both the folder row (`Sidebar.tsx` L126-137) and request row (L225-241) — hidden by default, faded in on row hover, matching the fade-in pattern already used for the row's `.actions` buttons.
+- Move `draggable="true"` off the row `
` and onto the grip element only.
+- `drag-drop.css` L105-112's blanket `[draggable="true"]` cursor rule is replaced with a rule scoped to the grip element/class; the row itself uses a normal pointer cursor.
+- The row's existing `onDragStart`/`onDragOver`/`onDrop` handlers stay attached where they are today (on the row) — native `dragstart` fires on the draggable descendant (the grip) and bubbles up through the row, so handlers reading `e.currentTarget`/row dataset continue to work unchanged. Only the `draggable` attribute placement and the CSS cursor selector move; the row's drop-target handlers (`onDragOver`/`onDrop`) are unaffected since those don't require the element itself to be draggable.
+- Net effect: hovering/clicking the label, icon, or action buttons behaves like a normal row (select/open); only pressing on the grip starts a drag. This also removes the current click-vs-drag ambiguity, not just the visual cursor mismatch.
+
+## Out of Scope
+
+- The sidebar-vs-request-editor-panel split (the VS Code-native webview boundary) is not part of this redesign.
+- The folder config editor (`src/webview/editor/`) is not part of this redesign.
+- No changes to `RequestContext.tsx`'s resize state/logic or the `ResizeObserver`-driven horizontal/vertical layout switch — only the handle's visual treatment changes.
+- No new shared `Panel`/`Section` React component is introduced across webview bundles; this pass consolidates CSS tokens within `src/webview/request/styles.css` only. A cross-bundle shared component (given each webview is a separately-built, isolated Vite IIFE bundle with its own duplicated token block) is a larger refactor left for a future pass if the same treatment is extended to the sidebar or folder editor.
+
+## Verification
+
+- Visual check in-browser (via `npm run watch` + Extension Development Host) at default zoom and at 150% editor zoom, at a narrow window width to confirm the stacked (vertical) split layout also gets dots/line correctly, and that panel padding stays usable.
+- Confirm sidebar drag-and-drop (folder reorder, request reorder, request-into-folder) still works with `draggable` moved to the grip, and that clicking the row label/icon still selects/opens as before, not just from the grip.
+- `npx tsc --noEmit` clean (no test suite/lint script in this repo per project conventions).
diff --git a/package-lock.json b/package-lock.json
index bad3a58..eb9f658 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2609,13 +2609,16 @@
}
},
"node_modules/baseline-browser-mapping": {
- "version": "2.9.18",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz",
- "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==",
+ "version": "2.10.43",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
+ "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
- "baseline-browser-mapping": "dist/cli.js"
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
}
},
"node_modules/before-after-hook": {
@@ -2703,9 +2706,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001766",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
- "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
+ "version": "1.0.30001806",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
"dev": true,
"funding": [
{
diff --git a/src/webview/components/HistoryEntryList.tsx b/src/webview/components/HistoryEntryList.tsx
index 540c9d7..0fbb238 100644
--- a/src/webview/components/HistoryEntryList.tsx
+++ b/src/webview/components/HistoryEntryList.tsx
@@ -124,7 +124,7 @@ const HistoryEntryList: React.FC = ({
>
Restore
-
+