# VPN Proxy Client Route Console 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:** Replace the current macOS client overview with a route-first console that makes the active traffic path, local proxy address, selected mode, and next action obvious at a glance. **Architecture:** Keep `resolveClientRoute()` as the single source of truth and keep `ClientOverviewPage` as the orchestrator. Split the screen into small presentational components inside `src/web/components/ClientOverviewPage.jsx`, then replace only the client-mode CSS block in `src/web/styles.css` so gateway and Windows work stay untouched. **Tech Stack:** React 19, Vite, Node.js `node:test`, existing CSS variables, Open Design static HTML artifact. **Design Artifact:** `docs/design/open-design/vpn-proxy-route-console-redesign.html` --- ## Current Findings - `src/web/components/ClientOverviewPage.jsx` already has the right model: one overview screen, mutually exclusive `Gateway`, `VPN`, and `Direct` modes, and route state from `resolveClientRoute()`. - `src/web/styles.css` makes the client screen visually separate, but it uses a dark blue-green palette that reads as a monitoring dashboard rather than a macOS setup tool. - The current status panel, route line, mode grid, and proxy panel have similar visual weight. The user must scan several boxes to answer the primary question: where does my traffic go right now? - Copyable proxy addresses sit in the side panel. They are useful, but they are visually separated from the route story. - The three mode buttons look like cards. They work, but they do not communicate that mode selection changes the middle segment of the route. ## Target Design Use a light, restrained operational UI for a normal macOS desktop context: a user has Docker running, a browser open, and is checking why an app uses a certain proxy path. The interface should feel closer to a compact network control console than a server dashboard. The first viewport should show: - top status: service running, restart, apply route; - left mode rail: Gateway, Local VPN, Direct; - main route strip: `Mac apps > local proxy > selected route > Internet`; - right utility panel: copy proxy addresses, proxy port, recent activity; - settings below route: only the form for the selected mode. ## File Structure - Modify `src/web/components/ClientOverviewPage.jsx`: reorganize render structure into route console subcomponents while preserving props and handlers. - Modify `src/web/styles.css`: replace `.client-*` layout styles from `.client-mode .app-main` through the final client media query. - Test `test/web/client-route.test.js`: extend route state coverage so UI changes do not hide incorrect mode/status combinations. - Keep `docs/design/open-design/vpn-proxy-route-console-redesign.html`: reference artifact for visual decisions. --- ### Task 1: Lock Route Contract Before UI Changes **Files:** - Modify: `test/web/client-route.test.js` - [ ] **Step 1: Add tests for all user-visible route statuses** Add these cases to `test/web/client-route.test.js`: ```js test('resolves running local VPN route', () => { const route = resolveClientRoute({ state: { singboxRunning: true, configExists: true, proxyPort: 8082, selectedTag: 'finland-02', clientSettings: { homeBypassEnabled: false, sharedProxyEnabled: false }, }, activeServer: { tag: 'finland-02' }, }); assert.equal(route.mode, 'vpn'); assert.equal(route.status, 'connected'); assert.equal(route.localProxy, '127.0.0.1:8082'); assert.deepEqual(route.path, ['Mac apps', '127.0.0.1:8082', 'VPN finland-02', 'Internet']); }); test('resolves gateway route when shared proxy is enabled', () => { const route = resolveClientRoute({ state: { singboxRunning: true, configExists: true, proxyPort: 8082, clientSettings: { sharedProxyEnabled: true, sharedProxy: { host: '192.168.50.111', port: 8080 }, }, }, }); assert.equal(route.mode, 'gateway'); assert.equal(route.status, 'connected'); assert.equal(route.target, '192.168.50.111:8080'); assert.deepEqual(route.path, ['Mac apps', '127.0.0.1:8082', 'Gateway 192.168.50.111:8080', 'Internet']); }); test('resolves direct route when home bypass is enabled', () => { const route = resolveClientRoute({ state: { singboxRunning: true, configExists: true, clientSettings: { homeBypassEnabled: true, sharedProxyEnabled: false, proxyPort: 8084 }, }, }); assert.equal(route.mode, 'direct'); assert.equal(route.status, 'connected'); assert.equal(route.localProxy, '127.0.0.1:8084'); assert.deepEqual(route.path, ['Mac apps', '127.0.0.1:8084', 'Direct', 'Internet']); }); ``` - [ ] **Step 2: Run the route tests** Run: ```bash npm test -- test/web/client-route.test.js ``` Expected: all existing and new route tests pass. - [ ] **Step 3: Commit** ```bash git add test/web/client-route.test.js git commit -m "test: lock client route display contract" ``` --- ### Task 2: Restructure Client Overview Markup **Files:** - Modify: `src/web/components/ClientOverviewPage.jsx` - [ ] **Step 1: Replace the route line with route nodes** Replace `RouteLine` with: ```jsx function RouteStrip({ route }) { const nodes = [ { label: 'Источник', value: route.path[0], detail: 'приложения Mac' }, { label: 'Локальный proxy', value: route.localProxy, detail: 'HTTP и SOCKS5' }, { label: 'Режим', value: route.target, detail: route.targetDetail, active: route.status === 'connected' }, { label: 'Выход', value: 'Internet', detail: route.status === 'connected' ? 'маршрут активен' : 'ожидает запуска' }, ]; return (
{nodes.map((node) => (
{node.label} {node.value} {node.detail}
))}
); } function RoutePath({ route }) { return (
{route.path.map((item, index) => ( {item} {index < route.path.length - 1 && {'>'}} ))}
); } ``` - [ ] **Step 2: Add a mode rail component** Add: ```jsx function ModeRail({ route, setupMode, clientSettings, state, busy, onGateway, onVpn, onDirect }) { const modes = [ { id: 'gateway', title: 'Общий gateway', subtitle: clientSettings?.sharedProxy ? `${clientSettings.sharedProxy.host}:${clientSettings.sharedProxy.port}` : 'серверная proxy', onClick: onGateway, }, { id: 'vpn', title: 'Локальный VPN', subtitle: state?.selectedTag || 'выбрать сервер', onClick: onVpn, }, { id: 'direct', title: 'Напрямую', subtitle: 'без VPN', onClick: onDirect, }, ]; return ( ); } ``` - [ ] **Step 3: Replace the top-level JSX** Use this layout in `ClientOverviewPage`: ```jsx return (
{ setSetupMode('direct'); enableDirect(); }} />
{setupMode === 'gateway' && ( )} {setupMode === 'vpn' && ( )} {setupMode === 'direct' && }
); ``` - [ ] **Step 4: Run build** Run: ```bash npm run build ``` Expected: Vite build succeeds. - [ ] **Step 5: Commit** ```bash git add src/web/components/ClientOverviewPage.jsx git commit -m "refactor: reshape client overview around route console" ``` --- ### Task 3: Replace Client Visual System **Files:** - Modify: `src/web/styles.css` - [ ] **Step 1: Replace only the client CSS block** Replace the CSS from `.client-mode .app-main` through the client media query with the style direction from `docs/design/open-design/vpn-proxy-route-console-redesign.html`. Keep selectors scoped to `.client-*` so gateway screens keep the existing palette. Use these token values for the client block: ```css .app-body.client-mode { grid-template-columns: 1fr; background: oklch(0.965 0.008 232); } .client-mode .topbar { background: oklch(0.978 0.007 232); border-bottom-color: oklch(0.835 0.018 232); } .client-mode .app-main { max-width: 1320px; width: 100%; margin: 0 auto; padding: 18px; color: oklch(0.238 0.028 238); } .client-console { min-height: calc(100vh - var(--topbar-h) - 36px); display: grid; grid-template-columns: 264px minmax(0, 1fr) 312px; overflow: hidden; background: oklch(0.986 0.006 232); border: 1px solid oklch(0.835 0.018 232); border-radius: 8px; box-shadow: 0 18px 42px oklch(0.36 0.035 238 / 0.13); } ``` - [ ] **Step 2: Add responsive behavior** Add: ```css @media (max-width: 1080px) { .client-console { grid-template-columns: 220px minmax(0, 1fr); } .client-side-panel { grid-column: 1 / -1; border-left: 0; border-top: 1px solid oklch(0.835 0.018 232); } .client-route-strip { grid-template-columns: 1fr 1fr; } } @media (max-width: 760px) { .client-console, .client-route-strip, .client-inline-form, .client-port-row { grid-template-columns: 1fr; } .client-mode-rail { border-right: 0; border-bottom: 1px solid oklch(0.835 0.018 232); } } ``` - [ ] **Step 3: Verify no banned patterns were introduced** Run: ```bash rg -n "background-clip:\\s*text|border-left:\\s*[2-9]|border-right:\\s*[2-9]|backdrop-filter|letter-spacing:\\s*-" src/web/styles.css ``` Expected: no matches. - [ ] **Step 4: Run build** Run: ```bash npm run build ``` Expected: Vite build succeeds. - [ ] **Step 5: Commit** ```bash git add src/web/styles.css git commit -m "style: apply light route console client theme" ``` --- ### Task 4: Browser Verification **Files:** - No file changes expected. - [ ] **Step 1: Start the dev server** Run: ```bash npm run dev -- --host 127.0.0.1 --port 4567 ``` Expected: Vite listens on `http://127.0.0.1:4567`. - [ ] **Step 2: Open client mode with representative state** Use the browser to open: ```text http://127.0.0.1:4567 ``` Expected: the first viewport shows the mode rail, route strip, route path, selected-mode form, and copyable proxy addresses without overlap at desktop width. - [ ] **Step 3: Check mobile width** Resize to 390px wide. Expected: rail, route workspace, and proxy panel stack vertically; long proxy URLs truncate inside their containers; action buttons remain readable. - [ ] **Step 4: Run final verification** Run: ```bash npm test npm run build git diff --check ``` Expected: all commands pass. - [ ] **Step 5: Commit** ```bash git add src/web/components/ClientOverviewPage.jsx src/web/styles.css test/web/client-route.test.js git commit -m "feat: redesign client overview as route console" ``` --- ## Self-Review Spec coverage: - Current UX assessment is captured in `Current Findings`. - New design direction is captured in `Target Design`. - Open Design artifact is referenced explicitly. - Implementation tasks cover route contract, markup, scoped CSS, and browser verification. Placeholder scan: - No `TBD`, `TODO`, or unspecified validation steps remain. Type consistency: - Route fields match `resolveClientRoute()`: `mode`, `status`, `localProxy`, `target`, `targetDetail`, `path`.