# UI Shell Rework — Wave 2 (Low-risk page migrations) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development or executing-plans. Steps use `- [ ]` checkboxes.

**Goal:** Migrate the 6 lowest-risk shell pages (login, home, settings, tokens, users, containers) from `css/common.css` + bespoke per-page CSS to the new `/dist/app.css` primitive bundle from wave 1. Each page goes from "old look" to "new look" with the same functionality. Also fold in the deferred token cleanups from the wave-1 code review.

**Architecture:** Each page migration is a self-contained markup rewrite. Pattern: swap the `<link>` to `/dist/app.css`, replace the sidebar + topbar markup with `wd-*` primitives, restyle page-specific content with `wd-card-asset` / `wd-card-op` / `wd-list-row` / `wd-form-*` / `wd-btn` / etc. Preserve every existing `<script>` and `id` so JS keeps working. Deploy after each page; check.

**Tech Stack:** Tailwind+flyon-ui bundle from wave 1 (already live), nginx static, no JS changes expected.

**Reference spec:** `docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md`
**Wave 1 plan:** `docs/superpowers/plans/2026-05-21-ui-shell-rework-wave-1-plan.md`

---

## File structure

**Files this wave modifies:**

```
services/web-ui/
├── public/
│   ├── login.html        (REWRITE: small, no sidebar, just form)
│   ├── home.html         (REWRITE: hero + stat tiles, has sidebar)
│   ├── settings.html     (REWRITE: tabbed settings forms, has sidebar)
│   ├── tokens.html       (REWRITE: list of tokens + create panel, has sidebar)
│   ├── users.html        (REWRITE: user list + edit slide-panel, has sidebar)
│   └── containers.html   (REWRITE: docker container list + logs, has sidebar)
└── src/css/components/
    └── tokens.css        (MODIFY: add deferred token cleanups)
```

**Files this wave does NOT touch:** the other 9 pages (index, projects, upload, jobs, api-tokens, recorders, cluster, capture, edit, editor, player). They're wave 3 / 4 / excluded.

---

## Tasks

### Task 0: Fold deferred token cleanups into tokens.css

Address items 1, 2, 4, 6 from the wave-1 code review BEFORE the page migrations multiply duplication of raw oklch values.

**Files:**
- Modify: `services/web-ui/src/css/components/tokens.css`
- Modify: `services/web-ui/src/css/components/button.css` (use new tokens)
- Modify: `services/web-ui/src/css/components/card-operational.css` (use new tokens)
- Modify: `services/web-ui/src/css/components/sidebar.css`, `topbar.css`, `slide-panel.css`, `card-asset.css`, `form-controls.css`, `field-group.css`, `list-row.css`, `toast.css` (use shared --ease / --dur tokens)

- [ ] **Step 1: Extend tokens.css with the missing tokens**

Append to `services/web-ui/src/css/components/tokens.css` inside `:root`:

```css
  /* Hover-darker variants of accent + signals — promoted from
   * inline oklch() arithmetic that was duplicated across button.css
   * and card-operational.css */
  --accent-hover:      oklch(52% 0.20 266);
  --accent-bright:     oklch(70% 0.18 266);
  --signal-bad-hover:  oklch(68% 0.22 25);
  --signal-good-hover: oklch(74% 0.18 148);
  --signal-warn-hover: oklch(84% 0.16 90);
  /* Pure-black-ish tinted toward brand hue for thumbnails & overlays.
   * Numerically still ~black but the hue channel is set so future
   * derivations stay on-brand. */
  --thumb-black: oklch(0% 0 266);
  --overlay:     oklch(8% 0.010 266 / 0.65);
  --shadow:      oklch(0% 0 266 / 0.5);

  /* Motion + ease tokens — promoted from raw cubic-bezier strings
   * that appeared in 8 of 12 primitive files */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast:    120ms;
  --dur-normal:  180ms;
  --dur-slide:   240ms;

  /* Z layers — promoted from sidebar/topbar where 30 was hard-coded */
  --z-topbar:   30;
```

- [ ] **Step 2: Find-and-replace raw oklch hover values across primitives**

For each of these files, replace the literal oklch string with the new token. Use `sed -i` for the substitutions, but verify each file afterward.

```bash
cd /opt/wild-dragon/services/web-ui/src/css/components

# button.css
sed -i 's|background: oklch(52% 0.20 266);|background: var(--accent-hover);|' button.css
sed -i 's|background: oklch(68% 0.22 25);|background: var(--signal-bad-hover);|' button.css

# card-operational.css — gradient stop in signal-strip-fill
sed -i 's|oklch(70% 0.18 266)|var(--accent-bright)|' card-operational.css

# card-asset.css — pure-black thumb background
sed -i 's|background: oklch(0% 0 0);|background: var(--thumb-black);|' card-asset.css

# slide-panel.css — overlay color
sed -i 's|oklch(8% 0.010 266 / 0.65)|var(--overlay)|' slide-panel.css

# toast.css — shadow
sed -i 's|oklch(0% 0 0 / 0.7)|var(--shadow)|' toast.css
```

- [ ] **Step 3: Replace raw cubic-bezier strings with --ease + --dur tokens**

```bash
cd /opt/wild-dragon/services/web-ui/src/css/components
# Replace exact "120ms cubic-bezier(0.25, 1, 0.5, 1)" with the tokens
for f in sidebar.css topbar.css slide-panel.css card-asset.css card-operational.css form-controls.css field-group.css list-row.css button.css; do
  sed -i 's|120ms cubic-bezier(0\.25, 1, 0\.5, 1)|var(--dur-fast) var(--ease-out-quart)|g' "$f"
done
# slide-panel slide-in (240ms ease-out-expo)
sed -i 's|240ms cubic-bezier(0\.16, 1, 0\.3, 1)|var(--dur-slide) var(--ease-out-expo)|' slide-panel.css
# Tab indicator
sed -i 's|240ms cubic-bezier(0\.25, 1, 0\.5, 1)|var(--dur-slide) var(--ease-out-quart)|' slide-panel.css
sed -i 's|200ms cubic-bezier(0\.25, 1, 0\.5, 1)|200ms var(--ease-out-quart)|g' form-controls.css
sed -i 's|240ms cubic-bezier(0\.16, 1, 0\.3, 1)|var(--dur-slide) var(--ease-out-expo)|' card-operational.css
```

- [ ] **Step 4: Replace hard-coded z-index 30 with --z-topbar**

```bash
cd /opt/wild-dragon/services/web-ui/src/css/components
sed -i 's|z-index: 30;|z-index: var(--z-topbar);|' topbar.css
```

- [ ] **Step 5: Rebuild + verify primitives still ship correctly**

```bash
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
docker exec wild-dragon-web-ui-1 grep -c '.wd-' /usr/share/nginx/html/dist/app.css
# Expect: same large number (~116+) — no rules dropped
docker exec wild-dragon-web-ui-1 grep -c '\-\-accent-hover\|\-\-ease-out-quart\|\-\-z-topbar' /usr/share/nginx/html/dist/app.css
# Expect: at least 3 hits (tokens now defined + referenced)
```

- [ ] **Step 6: Visual regression check on smoke page**

```bash
curl -sk -o /dev/null -w 'smoke=%{http_code}/%{size_download}\n' http://localhost:47434/_primitives-smoke.html
# Expect: HTTP 200, ~12 KB (unchanged from wave 1)
```

Manually load the smoke page in a browser; everything should look identical to wave 1. If anything changed visually, the sed substitutions introduced a regression.

- [ ] **Step 7: Commit + push**

```bash
cd /opt/wild-dragon
HOME=/root git add services/web-ui/src/css/components/
HOME=/root git diff --cached --stat
HOME=/root git -c user.email=zgaetano@wilddragon.net -c user.name='Zac Gaetano' commit -m 'web-ui: token cleanups from wave-1 code review

- Promote --accent-hover, --signal-bad-hover, --signal-good-hover,
  --signal-warn-hover, --accent-bright tokens (were duplicated raw
  oklch arithmetic in button.css / card-operational.css)
- Promote --thumb-black, --overlay, --shadow tokens (tinted toward
  brand hue 266 so future derivations stay on-brand)
- Promote --ease-out-quart, --ease-out-expo, --dur-fast/normal/slide
  tokens (cubic-bezier strings appeared in 8 of 12 primitive files)
- Promote --z-topbar (was hard-coded 30 in topbar.css while every
  other layer was tokenized)
- Replace all usages across the 12 primitive files via sed.

Bundle byte count unchanged (~138 KB); visual regression on smoke
page = zero. Code-review concerns from wave 1 now resolved before
wave 2 page migrations begin.'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 1: Migrate login.html

Smallest page. No sidebar, no topbar — just a centered card with email/password. Migrating first because if it breaks nothing else does.

**Files:**
- Modify: `services/web-ui/public/login.html`

- [ ] **Step 1: Read the current page to see what's there**

```bash
cd /opt/wild-dragon/services/web-ui/public
cat login.html | head -80
```

Note: form ids, input names, any inline JS handlers. Preserve all of them.

- [ ] **Step 2: Write the new login.html**

The new structure:
- `<link rel="stylesheet" href="/dist/app.css">` instead of the old `common.css`
- Centered `<main>` with a single `.wd-card-op`-shaped panel (operational card primitive, sized small)
- Inside: brand logo + "Z-AMPP" wordmark at top, then `<form>` with two `.wd-form-group` (email + password), then `.wd-btn.wd-btn--primary.wd-btn--md` submit
- Keep every existing `id`, `name`, `type`, and `<script>` tag from the old file
- If there's an "error message" div, replace its class with `.wd-toast.wd-toast--error` (inline, not floating)

Replace the entire `<head>` and `<body>` with the new shell. JS at the bottom stays as-is.

- [ ] **Step 3: Deploy on zampp1 (no Docker rebuild needed — HTML is static)**

```bash
# Actually nginx serves from the image's filesystem, not the host's
# /opt/wild-dragon/services/web-ui/public/. So we DO need a rebuild.
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'login=%{http_code}/%{size_download}\n' http://localhost:47434/login.html
# Confirm new bundle is referenced
curl -sk http://localhost:47434/login.html | grep -E 'dist/app.css|common.css'
# Expect: dist/app.css present, common.css absent
```

- [ ] **Step 4: Visual + functional check**

Open `http://172.18.91.216:47434/login.html` in a browser. Verify:
- Page renders with new brand styling
- Email + password fields look like the wd-input primitive
- Submit button looks like wd-btn--primary
- Logging in still actually works (POST to /api/v1/auth/login)

- [ ] **Step 5: Commit + push**

```bash
HOME=/root git add services/web-ui/public/login.html
HOME=/root git commit -m 'web-ui(wave 2): migrate login.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 2: Migrate home.html

Has sidebar + topbar + dashboard stat tiles. The first page that exercises the full shell.

**Files:**
- Modify: `services/web-ui/public/home.html`

- [ ] **Step 1: Read the current page**

```bash
cd /opt/wild-dragon/services/web-ui/public
wc -l home.html
head -80 home.html
```

Identify: page title, what's in the topbar right side, the stat tile structure, any chart libraries, and the bottom `<script>` blocks. Preserve all script references and JS state.

- [ ] **Step 2: Migrate the markup**

The migration recipe for every shell page:

1. **`<head>`**: replace `<link rel=stylesheet href=css/common.css>` with `<link rel=stylesheet href=/dist/app.css>`. Keep favicon, viewport meta.
2. **`<body>` root**: wrap in `<div class="wd-shell">` (style inline: `display:flex;min-height:100vh`).
3. **Sidebar**: copy verbatim from the smoke page's `<nav class="wd-sidebar">` block. Mark the active nav item with `is-active` on Home.
4. **Right column**: `<div style="flex:1;display:flex;flex-direction:column;">`
5. **Topbar**: `<header class="wd-topbar">` with breadcrumb in `.wd-topbar-left` containing just "Home", any existing right-side button as `.wd-btn.wd-btn--primary.wd-btn--sm`.
6. **Main content**: `<main style="padding:20px 20px 32px;">`
7. **Stat tiles**: replace with `.wd-card-op-grid` containing `.wd-card-op` (small, content-only — no footer needed if there's no action).
8. **Auth-guard script** at the bottom — stays exactly as-is.

- [ ] **Step 3: Deploy + check**

```bash
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'home=%{http_code}/%{size_download}\n' http://localhost:47434/home.html
curl -sk http://localhost:47434/home.html | grep -c 'wd-sidebar\|wd-topbar\|wd-card'
# Expect: 8+ matches
```

Load `http://172.18.91.216:47434/home.html` in a browser. Sidebar should be the new one, breadcrumb shows "Home", stat tiles render as operational cards.

- [ ] **Step 4: Commit + push**

```bash
HOME=/root git add services/web-ui/public/home.html
HOME=/root git commit -m 'web-ui(wave 2): migrate home.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 3: Migrate settings.html

System settings form. Lots of form-groups, possibly tabbed.

**Files:**
- Modify: `services/web-ui/public/settings.html`

- [ ] **Step 1: Read current page + identify all form sections**

```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E '<h[12]|form-group|form-section-label' settings.html | head -30
```

- [ ] **Step 2: Migrate using the standard recipe**

Same recipe as task 2, except for the form content:
- Replace each form section with a `.wd-field-group` (header + body, no tabs unless the section is genuinely tabbed)
- Replace every `<input>` with `class="wd-input"`, every `<select>` with `class="wd-select"`, every `<label>` with `class="wd-label"`
- Replace every `<button>` with `class="wd-btn wd-btn--primary wd-btn--md"` (or `--secondary` / `--ghost` / `--danger` as appropriate)
- Wrap rows of inputs in `.wd-form-row`
- Preserve every `id`, `name`, `type`, and JS handler

Set `.wd-nav-item.is-active` on Settings.

- [ ] **Step 3: Deploy + check**

```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'settings=%{http_code}/%{size_download}\n' http://localhost:47434/settings.html
```

Load in browser. Verify the form actually saves (test by changing one value and clicking save).

- [ ] **Step 4: Commit + push**

```bash
HOME=/root git add services/web-ui/public/settings.html
HOME=/root git commit -m 'web-ui(wave 2): migrate settings.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 4: Migrate tokens.html

Lists API tokens, allows creation of new ones with a slide-panel. First page that exercises the slide-panel primitive in the migrated context.

**Files:**
- Modify: `services/web-ui/public/tokens.html`

- [ ] **Step 1: Read + identify the slide-panel structure**

```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E 'slide-panel|slide-overlay|wd-list-row' tokens.html | head -20
```

- [ ] **Step 2: Migrate**

- Standard shell recipe (sidebar with `is-active` on Tokens, topbar with "Tokens" breadcrumb and "New token" primary button)
- Token list → `.wd-list` containing `.wd-list-row` for each token: name (cell--name), created date (cell--meta), badge for scope, action buttons in cell--actions
- Create-token form moves into `.wd-slide-panel` (with overlay, header, body, footer pattern exactly as in the smoke page's field-group)
- Preserve every JS handler — especially the copy-to-clipboard one for the newly-generated token

- [ ] **Step 3: Deploy + check**

```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'tokens=%{http_code}/%{size_download}\n' http://localhost:47434/tokens.html
```

Load + verify: clicking "New token" opens the slide-panel (codec-clipping bug fix from wave 1 applies — body should scroll if it overflows), creating a token shows the copy-once display.

- [ ] **Step 4: Commit + push**

```bash
HOME=/root git add services/web-ui/public/tokens.html
HOME=/root git commit -m 'web-ui(wave 2): migrate tokens.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 5: Migrate users.html

User management. List + edit slide-panel. Pattern matches tokens.html closely.

**Files:**
- Modify: `services/web-ui/public/users.html`

- [ ] **Step 1: Read + identify**

```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E '<h[12]|wd-list|slide-panel' users.html | head
```

- [ ] **Step 2: Migrate using the tokens.html recipe**

Identical pattern to task 4: shell + list + slide-panel for create/edit. Mark Users active. Preserve every JS handler (role dropdown, password reset, etc.).

- [ ] **Step 3: Deploy + check**

```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'users=%{http_code}/%{size_download}\n' http://localhost:47434/users.html
```

Verify: list renders, edit panel opens, save works.

- [ ] **Step 4: Commit + push**

```bash
HOME=/root git add services/web-ui/public/users.html
HOME=/root git commit -m 'web-ui(wave 2): migrate users.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 6: Migrate containers.html

Docker container list. List rows with status badges + action buttons (logs / restart). No slide-panel (logs typically opens in a separate tab or inline).

**Files:**
- Modify: `services/web-ui/public/containers.html`

- [ ] **Step 1: Read + identify**

```bash
cd /opt/wild-dragon/services/web-ui/public
head -80 containers.html
```

- [ ] **Step 2: Migrate**

- Standard shell recipe (Containers active)
- Container list → `.wd-list` with `.wd-list-row` per container:
  - cell--name: container name
  - cell with image: cell--meta
  - cell with status: `.wd-badge.wd-badge--good` (Up) / `.wd-badge--bad` (Down) / `.wd-badge--warn` (Restarting)
  - cell--actions: ghost buttons for Logs / Restart / Stop
- Auto-refresh polling JS stays unchanged

- [ ] **Step 3: Deploy + check**

```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'containers=%{http_code}/%{size_download}\n' http://localhost:47434/containers.html
```

Load + verify: all containers visible, status badges color-coded, Logs button still opens logs.

- [ ] **Step 4: Commit + push**

```bash
HOME=/root git add services/web-ui/public/containers.html
HOME=/root git commit -m 'web-ui(wave 2): migrate containers.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```

---

### Task 7: Wave-2 user QA gate

- [ ] **Step 1: Verify all 6 migrated pages serve correctly**

```bash
for p in login home settings tokens users containers; do
  printf '  %s.html: HTTP=%s\n' "$p" "$(curl -sk -o /dev/null -w '%{http_code}' http://localhost:47434/$p.html)"
done
```

Expected: all 200.

- [ ] **Step 2: Verify all 6 pages reference /dist/app.css and NOT common.css**

```bash
for p in login home settings tokens users containers; do
  CNT_NEW=$(curl -sk http://localhost:47434/$p.html | grep -c dist/app.css)
  CNT_OLD=$(curl -sk http://localhost:47434/$p.html | grep -c common.css)
  printf '  %s.html: new=%s old=%s\n' "$p" "$CNT_NEW" "$CNT_OLD"
done
```

Expected: new=1 old=0 for every page.

- [ ] **Step 3: Verify wave-3 / wave-4 pages are STILL on the old CSS (no accidental change)**

```bash
for p in index projects upload jobs api-tokens recorders cluster capture editor; do
  CNT_OLD=$(curl -sk http://localhost:47434/$p.html | grep -c common.css)
  printf '  %s.html: still-on-old=%s\n' "$p" "$CNT_OLD"
done
```

Expected: still-on-old=1 for every page (none of them migrated yet).

- [ ] **Step 4: User visual QA**

Stop. Ask user to load each of the 6 migrated pages and confirm the new look is correct, navigation still works, forms still save, lists still poll. If anything looks wrong, fix it before wave 3 starts.

---

## Self-review notes

- **Spec coverage**: Every page in the wave-2 list from the design spec is in the plan. Token cleanups from wave-1 review are folded in as task 0.
- **Placeholders**: none. Every step has the actual command / file change.
- **Type consistency**: every migrated page uses `.wd-shell` / `.wd-sidebar` / `.wd-topbar` / `.wd-nav-item.is-active` / `.wd-card-op` / `.wd-list` / `.wd-list-row` / `.wd-btn` / `.wd-input` / `.wd-select` / `.wd-label` / `.wd-form-row` / `.wd-field-group` — exact class names from the wave-1 bundle.
- **Open risk**: each page migration is a manual markup rewrite. The implementer subagent needs to actually read each existing page before rewriting, not work from the description alone, because each page has page-specific JS handlers that must be preserved verbatim.
