# Design sweep dossier — agent-tools

Files: `routes/agent/{Playground,Models,ChannelsTab,FacebookChannelCard,
InstagramChannelCard,ThresholdEditor,Triggers}.tsx`. None are in
`LEGACY_BASELINE` (styles/conformance.test.ts) — all seven, plus their CSS,
carry **0 legacy markers** (no `so-*` class, no bare navy/gold var, no raw
hex/rgb, no off-scale font-size). Triggers' post `<select>` is being replaced
by another seat — excluded from scoring/forks per group note.

## Playground

- IA: 3 concept groups (channel×case picker, chat+trace, trigger tester) plus
  session-spend and sandbox notices. Dense but every piece is load-bearing.
- Interaction: pickers use `role="radio"`+`aria-checked`, disable (not hide)
  impossible combos with an explaining `title`. Enter-to-send, Clear chat,
  per-turn "Open trace". No dead ends.
- Content: plain throughout ("Every turn is a real completion and really
  spends"), no engineering vocabulary.
- Visual/tokens: 100% `--cm-*`.
- a11y/responsive: composer `<input>` (357) and trigger-tester `<input>`
  (438) have only a `placeholder`, no label — real gap, placeholder is not a
  substitute. Grid collapses to one column under 1180px.

**Verdict: polish** — label the two bare inputs; nothing else needs redesign.

## Models

- IA: 4 Card blocks (health, role picker, cost meters, comparison table) —
  well chunked.
- Interaction: per-role model choice is free text, not a picker over known
  models — a typo only surfaces later via the health line's "Last failure".
- Content: clear, honest ("There is no paid test here").
- Visual/tokens: **bug** — `models.css` styles `.cm-models__filters`,
  `.cm-models__filters select`, `.cm-models__muted`, `.cm-models__caveat`
  with `var(--cm-text)`, `var(--cm-text-muted)`, `var(--cm-border)` — none of
  these tokens exist (`tokens.css` has `--cm-ink`/`--cm-muted`/`--cm-line`),
  so they resolve to nothing: the Period filter row and caveat/muted text
  lose their color and dark-mode adaptation. The scanner only flags *legacy*
  bare names or raw hex, not an undefined `--cm-*` typo, so this slipped the
  ratchet silently — the standout defect of the group.
- a11y/responsive: Period `<select>` has an implicit label; table uses
  `<th scope>` correctly.

**Verdict: polish** — fix the three dead token refs (mechanical); IA/copy fine.

## ChannelsTab (Facebook + Instagram cards)

- IA: each card stacks 2–3 independent three-state gates (SegmentedControl
  Off/Shadow/Live), each with a plain hint and a `ConfirmDialog` on "Live".
  Consistent pattern keeps the second card near-free to learn.
- Interaction: `ConfirmDialog` correctly gates the one irreversible step
  (going live / permanent delete) with channel-specific, factual warnings
  (ManyChat double-reply, Meta's undocumented hide permission). No dead ends.
- Content: strongest copy in the group — plain, specific, honest about
  consequences ("a hidden comment is not gone"). Comment *behaviour* text
  lives only in Behaviour, linked from here (one concept, one surface).
- Visual/tokens: 0 legacy markers, but both cards (and sibling
  `WhatsAppChannelCard`, out of scope) are built entirely from inline
  `style={s.xxx}` objects (`routes/agent/styles.ts`), not CSS classes — the
  only screens here built this way. Inline styles can't carry
  `:hover`/`:focus-visible`/media queries, so future interactive polish needs
  a rewrite to classes first.
- a11y/responsive: `SegmentedControl` takes `ariaLabel`. In-paragraph links
  get one-off inline color styles instead of a shared link class — minor.

**Verdict: keep** — content/interaction are the model for the section; the
inline-style architecture is consistency debt, not a user-facing problem.

## Triggers

- IA: one CRUD screen (form + table), single concept end to end.
- Interaction: create/toggle/delete all present. `dm_flow`/`silent` show a
  caveat via a `⚠` glyph with a native `title` tooltip (286) — mouse-hover
  only, unreachable by keyboard/touch.
- Content: plain, specific per-action hints.
- Visual/tokens: fully `Field`/`Input`/`Table`/`Button`, 0 legacy markers.
  Scope and Action selects already carry `cm-input`; only the excluded Post
  select is bare.
- a11y/responsive: `Field`/`Input` pair labels correctly; rows wrap at
  220px min-width per control.

**Verdict: polish** — replace the `title`-only caveat with something
keyboard/touch-reachable; otherwise clean CRUD.

## ThresholdEditor

- IA: 3 sliders + 1 toggle + save/reset, `<details>` for per-knob "why".
  Lowest concept load in the group.
- Interaction: live readout, Save disabled until dirty, explicit
  focus-visible rings. "Reset to defaults" hardcodes `0.8` for all three
  knobs locally rather than an API-read default — matches this file's own
  `?? 0.8` fallback, plausibly intentional but worth confirming.
- Content: plain, thorough, terms explained inline.
- Visual/tokens: fully `--cm-*`, but defines its own `.cm-thresholds__btn`
  classes instead of the shared `Button` component used elsewhere in this
  group — a duplicated primitive.
- a11y/responsive: ranges carry `aria-label`; focus-visible explicit;
  `<details>` used correctly.

**Verdict: keep** — well executed; fold the button classes into `Button` on
a pass through.

## Forks

1. **Models — free-text model field.** Keep free text (flexible, matches how
   the connection's model is entered) or become a picker constrained to
   known models? *Recommend: keep free text — the health line already
   surfaces a bad value fast, and a picker needs a live model list not every
   provider exposes.*
2. **Channel cards — inline `styles.ts` vs CSS classes.** Fold into classes
   now (touches 3 files, no visual change) or defer? *Recommend: defer — no
   user-facing defect today, and bundling it risks scope creep.*
3. **ThresholdEditor — "Reset to defaults" source of truth.** Confirm `0.8`
   is the real server default for all three knobs, not just the client's own
   fallback — if it ever diverges per-knob, Reset silently goes wrong.
   *Recommend: read the default from the API response instead of hardcoding.*
