# Review panel — design, UX copy, code
**WP BaseStation · 2026-08-25 · four reviewers on v0.79.0/0.80.0 · fixes shipped in 0.80.0 + 0.81.0**

Four specialists ran in parallel: two security/correctness passes (the second scoped to files the first couldn't see), a UX-copy pass over every customer-facing string, and a design/IA/accessibility pass over the stylesheet and rendered markup. Everything below was verified against the source before it was acted on — two agent findings turned out to be artifacts of scope and are marked as such.

---

## The headline: panels were buried, and now measured

The burial wasn't a matter of taste. Measured on live staging:

| Screen | Panels stacked | Rendered size | Consequence |
|---|---|---|---|
| **Diagnostics** | 3 | **58,934 chars** | The self-test — the reason anyone opens it — was the **third** panel, under preflight and licensing |
| **History** | 4 | 15,372px = **18.1 viewport-heights** | The last panel was an 18-screen scroll |
| **Settings** | 6 cards, no sub-nav | — | Private mode shipped **buried fifth** |
| Design | 2 | — | Module index below the fold |

**Fixed (0.80.0):** every panel is now a named pill in a rail under the screen head, addressable as `?tab=X&panel=Y`, one panel rendered at a time. Screens open on the reason you came — Diagnostics opens on the self-test. Settings' six cards became six panels, split on boundaries that already existed. Engine-room panels keep a clickable pill with an "adv" tag rather than vanishing in Simple view: hiding a whole panel was only ever a defence against scroll length, and the rail removes that at the root.

---

## Security — 6 real holes, all fixed

| # | Finding | Who could exploit it | Status |
|---|---|---|---|
| 1 | **`DVC_Fixer::undo()` had no capability check** — the only write in the class that asked nothing of the caller | A token that could merely *edit posts* could reverse an admin's site-option fix: re-hide the site from search engines, revert the permalink structure (breaking every URL), repoint attachments, rewrite content site-wide | ✅ carries `fix()`'s exact per-kind ladder |
| 2 | **Theme Builder had no capability check anywhere** — `guard()` tested whether the *feature* existed, never who was asking | An **Editor** could permanently delete the site's global header/footer — site-wide chrome with no snapshot and no revision path. Divi gates this behind admin in its own UI; we didn't | ✅ writes need `edit_theme_options`; `list_templates()` explicitly read-mode |
| 3 | **Site-wide rewrite authorised by a one-page check** — `rewrite_references()` rewrote every post mentioning a file after checking capability on a single page | Anyone who could edit one draft containing a heavy image | ✅ per-post re-check, with a `denied` count reported — a fix must never turn a known number into an unknown one |
| 4 | **The live connector key was written into access logs** — the preflight probe put it in a *query string*, so every Diagnostics load logged the credential to this site's webserver, its CDN and its WAF | Anyone with log access, backups, or a pasted ticket — two screens from our own warning that the URL *is* the password | ✅ `Authorization: Bearer` header |
| 5 | **Unbounded gzip decompression** — gzip reaches ~1032:1, so an envelope inside the byte cap could expand to gigabytes | Whoever steers the agent to a hostile URL | ✅ decompressed size capped |
| 6 | **Three unguarded mbstring calls** — WordPress polyfills only `mb_substr`/`mb_strlen`, and we declare no mbstring requirement | Any host without ext-mbstring: fatal in the SEO fixer, the H1 check, and token decoding | ✅ all `function_exists`-guarded |

**Verified clean** (auditable): OAuth 2.1 is genuinely solid — mandatory S256 PKCE with `plain` rejected, exact-match `redirect_uri` (no prefix matching), single-use codes burned *before* verification, `hash_equals` on every secret comparison, tokens stored as SHA-256 only, ~381 bits of entropy. Media sideload is hardened against SSRF with A/AAAA resolution and private-range refusal. Every `$wpdb` query uses `prepare()`. Only two unescaped echoes in 3,600 lines, both correct. `uninstall.php` is multisite-correct.

**Known and deferred** (documented, not launch-blocking): the "one-step undo slot" the plugin header promises for revisions-disabled sites is not wired up (`prior_undo()` is never called) — either implement `revision_id=0` or drop the claim; `/handoff` currently fails closed (the feature doesn't work rather than leaks); saved memories are a persistent prompt-injection channel worth an owner-approval step.

---

## Design & IA — including three defects I shipped hours earlier

1. **The panel rail was shipping invisible.** It styled `border-bottom: 1px solid var(--border)` — and `--border` is not a token in this system, so the declaration was dropped entirely. Separately, `.dap-subnav`, declared later at equal specificity, overrode its padding. Net visual difference from the Audit sub-nav: 20px of margin. **Fixed**, and given its own grammar: an enclosed segmented track, because a panel switch is a control *within* a screen while a free-floating pill means "go somewhere else."
2. **`?panel=` was emitted from exactly one place — the rail itself.** The capability tile badged "in Settings" landed you on Safety switches, with scheduling a further unsignposted click away. **Fixed** with a `screen_url('tab|panel')` helper; five tiles retargeted; an unknown panel slug degrades to the screen rather than printing a dead link.
3. **Five self-test assertions were green on navigation the product never rendered.** `NAV_GROUPS` + `nav_groups()` described a grouped rail; `render()` hand-listed a *third* source of truth. **Fixed** by deriving the rail from the constant — verified offline to reproduce the previous eight-pill row byte-for-byte, so the assertions now describe the shipped product.

**Our own dashboard's accessibility** — we ship an accessibility auditor, so this is a credibility issue:

- `.dap-fix-msg` is `role="status"` and was `display:none` while empty. Text written into a hidden live region is dropped by assistive tech — the exact trap the toast's own code comment documents. Fix results may never have been announced. **Fixed.**
- `.dvc-faq summary` and `.dap-connect-how summary` hide their markers and had **no focus style at all** — the primary onboarding disclosure was keyboard-focusable with no visible indicator (WCAG 2.4.7). **Fixed.**
- The working spinner was white on light-mode tinted buttons: **1.18:1**. The loading state was a blank button. **Fixed** to `currentColor`.
- Light-theme contrast, computed with real alpha compositing: `--success` **3.35:1** in a PASS chip (the single most repeated element in an audit product), `--text-muted` **4.22:1**, `--danger-text` **4.36:1**. The toast keeps a dark ground in both themes but read theme-scoped tokens, so light mode painted a **3.08:1** error glyph. **All fixed**, plus theme-independent toast tokens and the 9px "adv" tag raised to 11px.

**Deferred, worth doing before 1.0:** the type scale has 21 sizes with 60 of ~96 declarations inside an 11–13.5px band, so nothing below 14px reads as ranked; 24 ad-hoc spacing values against one spacing token; 15 distinct card-padding pairs; six micro-badge components doing one job; `.dap-btn-accent` is provably identical to the base button in both themes. The heading outline needs work (47 `<h3>` against 2 `<h2>`; Overview skips h1→h3) — pointed, in a plugin shipping `station_fix_headings`. And `<main>` has no max-width, so tables run 2,400px on a large monitor.

---

## UX copy — the standouts

**Caught a real bug via copy review:** all three lower History panel labels were attached to the **wrong renderers** — the pill saying "Tool calls" rendered a screen headed "Site history", and so on. Three labels, none matching its content. Fixed to Snapshots / Page revisions / Site history / Tool calls.

**Highest-impact remaining copy fixes** (queued, not yet shipped):

- `"BUG #20 class"` — internal bug-tracker shorthand shipped to paying customers.
- `Fix all (14 pages)` — the only bulk-write button in the product, with no confirmation and no statement that it writes to live pages. Meanwhile *Restore revision* does confirm. Severity signalling is inverted.
- "Clear site history" / "Clear activity" — irreversible, no confirmation.
- `"Could not fix this one — "` — trailing em dash concatenated with a server message; if that message is empty, users see a dangling dash and no reason.
- The "Fix problems automatically" tile is badged **"via Claude"** — but those fixes have one-click Fix buttons in the dashboard itself. The badge contradicts the product.
- `Node 0.2.1:` is the most-repeated prefix in the product and is unusable to a site owner — it isn't a link and can't be searched. Lead with the human fact, put the coordinate last.
- Terminology: **token** means both the credential and a design token, two screens apart. Recommend **key** for the credential. Also colour/color is inconsistent, and audit/check/scan/self-test/QA/diagnostics are used interchangeably.

**Voice worth protecting** — the reviewer singled these out as the product's differentiator, and they should be the reference for any new copy:

> "Honest limit: an administrator with server or file access can always find installed code — this is a workspace boundary, not encryption."

> "Clearing is itself recorded — a history that can be emptied without trace is not a history."

> "That is usually the host blocking loopback requests, not a broken connector — many managed hosts do this. Add the connector in Claude and see whether it attaches; if it does, ignore this warning."

---

## Two findings that were *not* real

Recorded so they don't get re-raised: the first security reviewer reported 16 missing files and "the plugin fatals on load." That was an artifact of the path it was given — my working copy held 27 of 43 files. The full tree is intact and gates pass. I re-ran a second reviewer against the complete tree, which is where the OAuth review above came from.

---

## Launch status

**Cleared:** CI green · self-test green on two sites · audit false-positive classes eliminated · 6 security holes closed · panel rail · dashboard a11y defects.

**Remaining blocker:** tier enforcement is still OFF — every install runs all 79 tools free.

**Then:** distribution decision (Freemius-only vs wp.org), the copy batch above, the design-system consolidation, and the v1.0.0 tag.
