Status: ✅ All green · 27/27 unit tests · 47/47 Playwright smoke · 0 typecheck errors
Date: 2026-08-30
Branch: feat/id9-r210-minimal-header @ 419b339
Repo: avidtech6/vibecoder-standalone
Base: clean/baseline-plus-wdm @ af00bff (r209 P0 header dead-code cleanup)
Replaces the r202-era header chrome (Home + Project name + 5 surface pills + 5 eye icons + hamburger) with a minimal React header:
FV • vibecoder (read-only, identifies the workspace)MAIN / WORKING / DRAFT / PREVIEW / ORIGIN), click to advancefvcms-gallery-panel) backed by GalleryPanel.tsxwindow.__fvcmsOpenEdgePanel74/74 tests pass (27 unit + 47 Playwright). No iframe, no postMessage, no pact edits.
Before (r202):
[O] vibecoder | [MAIN] [WORKING] [DRAFT] [PREVIEW] [ORIGIN] [👁][👁][👁][👁][👁] [☰]
After (r210):
[FV • vibecoder] [MAIN] [🖼] [☰]
The ☰ icon is now a deliberate Edge Panel icon (SVG, 3 horizontal lines in a square). The Gallery icon is a 2x2 grid (SVG). Both are 32×32px.
src/host/header/MinimalHeader.tsx | +280 (NEW)
src/host/header/GalleryPanel.tsx | +210 (NEW)
src/host/header/__tests__/MinimalHeader.test.tsx | +340 (NEW, 27 assertions)
scripts/smoke/r210-smoke.spec.cjs | +445 (NEW, 14 steps, 47 assertions)
scripts/smoke/screenshots/ | 4 PNGs (NEW)
src/host/host-boot.js | +160 -155 (mountMinimalHeader replaces mountOSHeader + openGalleryPanel + static React imports)
src/host/workspace/index.ts | +4 -14 (removed attachEyeHandlers() call, kept API)
package.json | +3 (jsdom, @types/jsdom, @types/node — devDeps for tests)
package-lock.json | +605 (locked by npm install)
Net: +2047 lines / -156 lines. The +2047 is mostly tests, smoke, package-lock + dependencies.
A React 18 component that renders the 4-element header. Reads window.__fvcmsActiveSurface and window.__fvcmsWorkspaceId. Subscribes to the fvcms:active-surface-changed document event. Click handlers:
window.__fvcmsSetActiveSurface(next) to advance the cycleprops.onGalleryClick() (defaults to window.__fvcmsOpenGalleryPanel)props.onEdgeClick() (defaults to window.__fvcmsOpenEdgePanel)The component is stateless beyond useState for the active surface mirror. It does NOT use the r206 useStudio hook (the r210 header is chrome-level, not a mount point in the r206 contract).
A React 18 component for the new PM2 Gallery panel. Reads window.__fvcmsGallery.listModules() and window.__fvcmsGallery.CATEGORIES (the host-boot.js exposes these). Groups modules by category and renders a responsive grid. When the gallery stub returns [] (current vibecoder-standalone build), shows a helpful empty state pointing to a789 (Unified Gallery Panel plan).
Replaces mountOSHeader(). Injects CSS (only panel-clearance + mobile @media rules), mounts a fresh #fvcms-os-header div, and uses createRoot from react-dom/client to mount the React MinimalHeader. Includes a text-only fallback if the React import fails (so the operator is never stranded).
Wires the Gallery icon to a new PM2 panel. If the panel already exists (fvcms-gallery-panel), activates it. Otherwise, mounts a fresh panel that wraps GalleryPanel.tsx via createRoot. Position: { x: 120, y: 120, w: 720, h: 540 }.
The function is exposed on window.__fvcmsOpenGalleryPanel for the React MinimalHeader to call.
The first iteration used await Promise.all([import('react-dom/client'), import('./header/MinimalHeader.tsx')]) inside mountMinimalHeader(). This caused Vite to bundle a SECOND copy of React → "Invalid hook call" errors when MinimalHeader called useState. The fix: static imports at the top of host-boot.js:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { MinimalHeader } from './header/MinimalHeader.tsx';
import { GalleryPanel } from './header/GalleryPanel.tsx';
The r202-era IDs (#fvcms-os-header-pill-main, etc.) and the .fvcms-surface-eye class still appear in panel-manager.css (canonical pact code, "no pact edits" per spec). To prevent these from being visually resurrected by HMR or 3rd-party extensions, the new mountMinimalHeader injects a CSS safety net:
#fvcms-os-header-pills,
#fvcms-os-header-pill-main,
#fvcms-os-header-pill-working,
#fvcms-os-header-pill-draft,
#fvcms-os-header-pill-preview,
#fvcms-os-header-pill-origin,
#fvcms-os-header-home,
#fvcms-os-header-hamburger,
.fvcms-surface-eye
{ display: none !important; }
This is NOT a pact edit — it's a JS string-concat in the host-boot.js (per Rule 14 "no patches to canonical code" — patches here are in the host boot, not in the canonical CSS).
The WDM (Workspace Desktop Manager) had a togglePillHidden() API and an attachEyeHandlers() function that wired eye icons to the 5 surface pills. The function still exists in workspace-manager.ts for API stability, but the call in workspace/index.ts:55-56 is removed per the operator spec.
togglePillHidden() is still callable (no-op without pills). The CSS rules in panel-manager.css (body[data-pills-hidden~="X"] #fvcms-os-header-pill-X) are no-ops without the pills.
| Suite | Result |
|------|--------|
| r210 unit tests (MinimalHeader.test.tsx) | 27/27 ✅ |
| r210 Playwright smoke (r210-smoke.spec.cjs) | 47/47 ✅ |
| r210 typecheck | 0 errors ✅ |
| r210 iframe count (after comment strip) | 0 ✅ |
| r210 postMessage count (after comment strip) | 0 ✅ |
| WDM attachEyeHandlers is NOT called | ✅ (no .fvcms-surface-eye in DOM) |
| Total | 74/74 + 0 lint + 0 iframe + 0 postMessage |
| Invariant | Status |
|-----------|--------|
| No iframe | ✅ (0 in r210 source + 0 in mount tree) |
| No postMessage | ✅ (0 in r210 source + 0 in mount tree) |
| Window-level event bus unchanged | ✅ (fvcms:active-surface-changed still on document) |
| Surface targeting preserved | ✅ (window.__fvcmsSetActiveSurface still wired) |
| Edge Panel open path unchanged | ✅ (icon calls window.__fvcmsOpenEdgePanel) |
| WDM togglePillHidden still works | ✅ (function preserved, no pills to dim) |
| WDM attachEyeHandlers is not called | ✅ (call removed) |
| PM2 panel addPanel pattern | ✅ (Gallery panel uses same mgr.addPanel(...) pattern as r202 VSIL) |
| Static React imports (no Vite dual-React) | ✅ |
| Test for r206/r207/r208 compatibility | ✅ (window globals only, no r206 StudioContext coupling) |
cd /workspace/vibecoder-standalone-r210
npm run build # build the production bundle
scp -O -r dist/* root@185.249.73.178:/var/www/freshvibeapps/clients/vibecoder/
The r210 branch is based on clean/baseline-plus-wdm @ af00bff (r209). It does NOT include r206/r207/r208 work. To merge r210 with r206/r207/r208, two paths:
feat/id9-r208-integration @ d8e2cca) — likely clean since r210 only changes host-boot.js and workspace/index.ts (r208 doesn't touch those)The new Edge icon is a 3-line SVG in a square. The old hamburger was ☰. They look different. If the operator wants a different look (e.g., the r202 hamburger glyph), it's a 1-line change in MinimalHeader.tsx.
1. Merge order: r208 (in feat/id9-r208-integration) is also pending production. Do we merge r208 first, then r210? Or fast-track r210 (header) ahead of r208 (integration)?
2. r210 + r208 merge conflict on host-boot.js: r208 added 2 LoC in host-boot.js:335 (Step 6.5 boot call) and replaced openVsilPanel in EDGE_PANEL_HOST. r210 also changes host-boot.js heavily. We need a merge plan.
3. Gallery panel — wired to real listModules() or empty stub? The r210 panel shows the empty state (current stub). Once avidtech6/fv-module-gallery is wired into the dev server (per a789), the panel will populate automatically. No r210 change needed.
4. WDM togglePillHidden — keep the API or delete? Per spec, the API stays. But if 3rd-party callers don't use it, we can remove in a future P1 cleanup.
To re-run the smoke locally:
cd /workspace/vibecoder-standalone-r210
npx tsx src/host/header/__tests__/MinimalHeader.test.tsx # 27/27
npx vite --port 5273 --host 0.0.0.0 &
node scripts/smoke/r210-smoke.spec.cjs # 47/47
Expected: r210 MinimalHeader.test.tsx: 27 passed, 0 failed + r210 Playwright smoke: 47 passed, 0 failed.