dragonflight/docs/superpowers/plans/2026-05-21-ui-shell-rework-wave-2-plan.md
Zac Gaetano 265f4174d5 docs: UI shell rework wave-2 implementation plan
7 tasks: token cleanups from wave-1 review (task 0), then migrate login/home/settings/tokens/users/containers.html one-by-one with deploy-and-verify between each. Smallest blast radius first.
2026-05-21 13:06:04 -04:00

20 KiB

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:

  /* 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.

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

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

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

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

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

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

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

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

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

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
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
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
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)
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.