# 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 (