# Multi-Device Federation Phase 3: Remote Auth + Terminal + Unreachable States > **Execution:** Use the subagent-driven-development workflow to implement this plan. **Goal:** Make remote sessions fully interactive — detect auth requirements, present login flow, show unreachable placeholders, and connect terminals to remote instances. **Architecture:** Phase 2 left sources in three status states (`authenticated | auth_required | unreachable`) but only rendered sessions for `authenticated` sources. Phase 3 adds rendering for the other two states (auth-required tiles with login buttons, offline placeholder tiles with last-seen timestamps), implements the popup login flow, makes `terminal.js` connect WebSockets to remote origins, and updates `openSession`/`closeSession` to route API calls to the correct instance. **Tech Stack:** Vanilla JS (no framework), CSS custom properties, Node.js `--test` runner with `assert`, `window.open()` for login popups, cross-origin `fetch` with `credentials: "include"`. --- ## Conventions (read this before you start) **File paths are relative to the repo root** (`muxplex/`). **How to run tests:** ```bash cd muxplex && node --test muxplex/frontend/tests/test_app.mjs cd muxplex && node --test muxplex/frontend/tests/test_terminal.mjs ``` **Test pattern:** Every test file uses `.mjs` extension, imports via `createRequire`, and mocks `globalThis.document`, `globalThis.fetch`, `globalThis.window`, etc. before requiring the module. See the top of `muxplex/frontend/tests/test_app.mjs` for the full mock setup. **CommonJS export shim:** `app.js` ends with a `module.exports = { ... }` block wrapped in `if (typeof module !== 'undefined' && module.exports)`. Any new function you want to test **must** be added to that exports object at the bottom of `app.js` (around line 1660). **CSS design tokens:** The file `muxplex/frontend/style.css` defines tokens on `:root`. Key ones: `--bg: #0D1117`, `--accent: #00D9F5`, `--text-dim: #4A5060`, `--text-muted: #8E95A3`, `--border: #2A3040`, `--err: #f85149`, `--warn: #d29922`, `--ok: #3fb950`. **Phase 2 assumptions:** The plan assumes Phase 2 left these in place: - `_sources` array with `{ url, name, type, status, lastSeenAt, backoff }` per source - `pollSessions()` does `Promise.all` across sources, sets `source.status` to `authenticated | auth_required | unreachable` - Each session object has `deviceName`, `sourceUrl`, `sessionKey` fields - Device badge HTML in `buildTileHTML` (something like `Laptop`) - `_sources` is accessible from tests via a `_setSources` or `_getSources` test helper If any of these are named slightly differently in the actual Phase 2 code, adjust accordingly — the logic is the same. --- ### Task 1: CSS for auth-required and offline tile states **Files:** - Modify: `muxplex/frontend/style.css` Phase 3 tiles need two new visual states: a greyed-out "offline" tile and an "auth required" tile with a login button. Add these styles to the end of `style.css`, before the `@media (prefers-reduced-motion)` block. **Step 1: Add the CSS** Append these rules to `muxplex/frontend/style.css`, just before the `/* Reduced Motion */` media query (currently around line 798): ```css /* ============================================================ Federation: source status tiles (auth-required + offline) ============================================================ */ .source-tile { height: var(--tile-height); background: var(--bg-tile); border: 1px solid var(--border); border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; text-align: center; padding: 24px; } /* Offline / unreachable device tile */ .source-tile--offline { opacity: 0.45; border-style: dashed; } .source-tile--offline .source-tile__name { color: var(--text-dim); } .source-tile--offline .source-tile__badge { background: var(--err); color: #fff; font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: 0.05em; } .source-tile--offline .source-tile__last-seen { font-size: 11px; color: var(--text-dim); } /* Auth-required device tile */ .source-tile--auth { border-color: var(--warn); border-style: dashed; } .source-tile__name { font-size: 15px; font-weight: 600; color: var(--text); } .source-tile__login-btn { background: var(--accent); color: var(--bg); border: none; border-radius: 4px; padding: 8px 20px; font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity var(--t-fast); } .source-tile__login-btn:hover { opacity: 0.85; } .source-tile__login-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .source-tile__hint { font-size: 11px; color: var(--text-muted); } ``` **Step 2: Verify the CSS file is valid** Open the file and visually confirm the new classes are syntactically correct (no unclosed braces): ```bash cd muxplex && grep -c 'source-tile' muxplex/frontend/style.css ``` Expected: a count of ~20+ (the lines we just added). **Step 3: Commit** ```bash cd muxplex && git add muxplex/frontend/style.css && git commit -m "feat(federation): add CSS for auth-required and offline source tiles" ``` --- ### Task 2: `buildAuthTileHTML` — render an auth-required tile **Files:** - Modify: `muxplex/frontend/app.js` (add function + export) - Modify: `muxplex/frontend/tests/test_app.mjs` (add tests) This function builds the HTML for a single auth-required source tile. It shows the device name and a "Log in" button. **Step 1: Write the failing tests** Add these tests to the end of `muxplex/frontend/tests/test_app.mjs` (before the final blank lines): ```javascript // --- buildAuthTileHTML --- test('buildAuthTileHTML is exported as a function', () => { assert.strictEqual(typeof app.buildAuthTileHTML, 'function'); }); test('buildAuthTileHTML returns article with source-tile--auth class', () => { const html = app.buildAuthTileHTML({ name: 'Workstation', url: 'http://work:8088' }); assert.ok(html.includes('source-tile--auth'), 'must have source-tile--auth class'); assert.ok(html.startsWith(' element'); }); test('buildAuthTileHTML includes device name', () => { const html = app.buildAuthTileHTML({ name: 'Dev Server', url: 'http://dev:8088' }); assert.ok(html.includes('Dev Server'), 'must include the device name'); }); test('buildAuthTileHTML includes login button with data-url attribute', () => { const html = app.buildAuthTileHTML({ name: 'Dev', url: 'http://dev:8088' }); assert.ok(html.includes('source-tile__login-btn'), 'must have login button'); assert.ok(html.includes('data-url="http://dev:8088"'), 'button must have data-url'); }); test('buildAuthTileHTML escapes HTML in device name', () => { const html = app.buildAuthTileHTML({ name: '', url: 'http://x' }); assert.ok(!html.includes('