# Round-4 render audit — every screen, looked at

> Produced 2026-08-01 in the screens lane, after the operator's order: *"I have
> not seen a single screen rendered."* Sixteen screens were rendered over
> `vite preview` with Playwright driving the installed Chrome; fixtures were
> derived from the types in `apps/web/src/lib/*-api.ts`, never guessed.
>
> **This is a findings list, not a spec.** Nothing here has been ruled. Each item
> is a measurement with the file and line that produces it, so the next session
> can fix or strike it without re-deriving anything.
>
> The PNGs lived in a session scratchpad and are gone; the measurements are here
> because they are what mattered. Re-shooting: serve `apps/web/dist` with
> `vite preview --port 4174`, stub `**/api/**`, unlock the route guard with a
> SYNTHETIC `whatapp_token` in localStorage.

## Rendered and looked at

login · home · inbox (6 widths) · contacts · templates · campaigns · analytics ·
automation · agent-overview · agent-activity · agent-posts · agent-playground ·
knowledge-doc · settings · docs-api · not-found

**Not yet rendered** (each needs a fixture): agent-behaviour · agent-keywords ·
analytics-agent-outcomes · contacts-segments · campaign-new · contact-detail ·
template-detail · template-editor · campaign-detail · campaign-builder ·
flow-canvas · csv-broadcast · qr-campaigns.

Clean, nothing owed: Home, Analytics, Contacts, Automation, Playground, Login.

## Defects, each measured in a browser

1. **The stat-tile inversion never landed.** `ui.css` sets `order: -1` on
   `.cm-stat__l`, but `.cm-stat` (`ui.css:230`) is a plain block and `order`
   only applies to flex/grid children. Measured on Agent overview: all three
   tiles report label-below. **Fix:** `display: flex; flex-direction: column` on
   `.cm-stat`. The 2026-08-01 sweep commit claims this shipped; it did not.
2. **Analytics' failure-class bars never render.** `.an-reason .rf`
   (`analytics.css:259`) is a `<span>` with `height: 100%` and no `display`, so
   width and height are ignored: measured 0x0 inside a 620px track. **Fix:**
   `display: block`. Pre-existing, invisible to jsdom.
3. **Inbox loses the contact's name below ~1237px.** `.cm-thread__acts` is 313px
   and unshrinkable; `.cm-thread__id` (`inbox.css:402`) is the only shrinkable
   child and collapses to 0 while the text stays in the DOM. Between 768 and
   1180 the "Hide details" button inside it toggles `.cm-inbox__ctx`, which
   `inbox.css:368` has already set to `display: none` — a dead control eating
   ~110px. **Fix shape:** drop the dead toggle in that band, and take the header
   buttons to icons below `--cm-bp-rail`.
4. **The Inbox panes do not fit their own ruled breakpoints.** nav 216 + list 324
   (`inbox.css:29`) + thread floor 340 (`Inbox.tsx:861`) + rail 320 = **1200
   needed**, but the rail is shown from the ruled 1180, so it overlaps the thread
   header. Two panes need **880**, but the desktop layout runs from the ruled
   768, so at 768 the thread's right edge is 880 and is clipped. Fixing it means
   one of the panes gives: a fluid list (`flex: 0 1 324px; min-width: 216px`)
   plus a 320 thread floor makes both bands fit **without touching either ruled
   token**. Not applied — it changes an approved screen, so it is the operator's.
   This also contradicts `HANDOFF.md`, which says the layout wraps rather than
   starving the thread. It starves and clips it.

## The sweep's blind spot

5. **Two whole screens were never swept**, because their type is inline-styled
   and a `.css` sweep cannot see them: **Settings** (`PLATFORM` / `CONTACTS` /
   `CONNECTIONS` / `ACCESS` rail groups, `EMAIL ALERTS`, `DEFAULTS`) and **API
   Documentation** (10 card headers plus `FIELD` / `REQUIRED` / `DESCRIPTION`).
6. **52 inline `textTransform: "uppercase"` across 35 `.tsx` files.** "47 → 2" is
   true of stylesheets only. Visible today: `OPTED IN`
   (`ContactSidebar.tsx:170`) and `NOT DELIVERED` (`MessageBubble.tsx:316,339`)
   on the approved Inbox, `BROADCAST` on Campaigns, `UTILITY` / `MARKETING` on
   Templates.

`design/craft.md` says uppercase is "kept appropriately, no enforcement with or
against", so **whether these earn it is a judgment the operator owns** — they are
listed as unswept, not as wrong.

## Copy, against `design/craft.md`

7. **Em dashes in rendered prose** (the glyph is the null placeholder and nothing
   else): Agent activity's page description, Agent posts' empty state.
8. **A question-phrased heading**: Agent overview's "Is the bot okay?".
9. **Codes where words belong**: Analytics renders "Connected (CONNECTED)" and
   raw ISO dates ("since 2026-07-18") on a screen that writes "26 Jul" elsewhere.
10. **"Whatsapp"** for WhatsApp, twice on Analytics (volume and cost rows).
11. **Title Case column header** "Created At" on Campaigns.

## Watch, not a defect

Analytics is built almost entirely from bars, split bars and meters — the four
forms a round-3 fork listed as banned. That fork is **provisional**, so nothing
here is actionable until it is ruled.

## Method notes worth keeping

- **The overflow assertion is blind to this class of defect.**
  `scrollWidth <= innerWidth` passed clean on every screen while panes were
  clipping inside `overflow: hidden`. Measure the element, not the document.
- **A wrong fixture and a real defect look identical.** Agent overview appeared
  to render "9300%" until the type said otherwise: `overview.service.ts:173`
  returns a 0–1 fraction. Read the producer before believing the screen.
