# UX Audit — 2026-04-21

**Scope**: Single exhaustive pass, focus on recent changes (skills page + slash, theme editor + JSON, chat fixes). Dogfood as a logged-in Jezweb user (jeremy@jezweb.net) on deployed app.

**URL**: https://vite-flare-starter.webfonts.workers.dev

**Tools**: Chrome MCP (Jez's session), 1440×900 viewport.

**Previous audit**: 2026-04-20 (skills-focused). Yesterday's 9 fixes (H1, M1-M3, L1-L5) shipped live but STILL UNCOMMITTED in working tree — SkillActivationBlock.tsx, MessageRenderer.tsx, SkillsPage.tsx, registry.ts, routes.ts. **Commit these before next audit so findings map cleanly to revisions.**

---

## Severity key
- **Critical** — blocks user from completing core task
- **High** — significant confusion or friction, most users hit it
- **Medium** — suboptimal but workable
- **Low** — polish / minor

---

## Findings

### H1 — Conversation title leaks raw `<skill_content>` wrapper *(HIGH)*

**What**: After activating a skill via `/plan-task I want to ship a file upload feature`, the conversation title everywhere in the UI shows:
```
<skill_content name="plan-task" directory="bundled:plan-task">...
```
This appears in: breadcrumb header, conversation sidebar list, document `<title>` (tab name is fine, it's `AI Chat · Vite Flare Starter`, but the in-app title rendered as H1 is the wrapper text).

**Why it matters**: First thing a user sees if they open their conversation list is a wall of verbose XML-looking strings. Unrecognisable, not scannable, embarrassing if anyone else sees the screen.

**Fix direction**: The auto-title logic (usually a heuristic on first user message) must strip the `<skill_content>` wrapper before sampling. Use the *trailing* user text as the title, or fall back to a friendly "Plan task — file upload feature".

**Repro**: New chat → type `/plan-task a short prompt` → send → look at breadcrumb/sidebar.

---

### H2 — SkillActivationBlock pill does not render during initial streaming *(HIGH)*

**What**: After yesterday's SkillActivationBlock fix, the user message SHOULD render as a compact `⚡ /plan-task ›` pill + trailing user text. On a fresh `/slash` send, the user message and assistant text are not visible at all while the response streams — only the `ask_questions` widget at the bottom is shown. Empty state "Good evening, Jeremy" re-appears at the top. After a full page refresh (Cmd-R), the pill renders correctly.

**Repro**:
1. New chat
2. Type `/plan-task I want to ship a small file upload feature`
3. Hit send
4. Observe: only the UI widget + empty state greeting, no user message
5. Cmd-R
6. Observe: pill now renders correctly, with "Thought for a few seconds" and "Waiting for your response below…"

**Why it matters**: Users lose sight of their own message during the single most confusing moment — the first slash activation. Looks broken.

**Fix direction**: MessageRenderer likely sees the message as empty/transient during streaming. Confirm the parseSkillActivation path runs during streaming, not just on persisted message load. Possibly the streaming state has a different shape than loaded state.

---

### M1 — No slash-command autocomplete in composer *(MEDIUM)*

**What**: Typing `/` in the chat composer does not trigger a picker of available skills. Users need to remember the exact skill name + type it in full.

**Why it matters**: 18 bundled skills + user's own uploads = need for discovery. Without autocomplete, a user types `/` and sees nothing to guide them; the Skills page is a separate navigation. Every mature chat app (claude.ai, Cursor, t3.chat) shows a picker.

**Fix direction**: On `/` keypress with preceding whitespace/start-of-input, open a dropdown listing `name + short description` of enabled skills. Enter / click to insert `/<name> ` at the cursor.

---

### M2 — Empty state briefly visible inside an active conversation *(MEDIUM)*

**What**: During the first skill activation (before page refresh), the chat area simultaneously shows:
1. "Good evening, Jeremy" empty state + 4 chip presets + 4 example prompts (top)
2. `ask_questions` card (bottom)
3. Nothing in between (no user message, no assistant thinking, no response)

After refresh this disappears — only the conversation content shows. Seems related to H2 above.

**Fix direction**: Gate the empty state on "zero messages AND not in a streaming state", not just "zero rendered messages".

---

### L1 — `/skill-name` inline code pill wraps at hyphen on Skills page *(LOW)*

**What**: On `/dashboard/skills` the description reads:
> Use `/skill-` (break) `name` in chat to activate one explicitly.

The inline `<code>/skill-name</code>` has `white-space: normal`, so it wraps mid-phrase at the hyphen. Visually ugly — looks like `/skill` is a command and `name` is a word.

**Fix direction**: Add `white-space: nowrap` (or `word-break: keep-all`) to the code pill. Optionally shrink the whole description to a single line at desktop widths.

---

### L2 — Sidebar toggle button has no visible label in collapsed state *(LOW)*

**What**: The two icons at the top-left of the chat area (sidebar toggle + AI Chat breadcrumb) are both small square icons. The first one — the conversations sidebar toggle — has only a `sr-only` accessible label. No tooltip on hover.

**Fix direction**: Add a `title` / `aria-label`-driven tooltip so hovering gives "Conversations".

---

## Verified-working from yesterday's fixes

| Tag | Fix | Status |
|-----|-----|--------|
| M2 | Title + 3 buttons on same flex row on Skills page | ✓ held |
| L5 | `agentskills.io spec` link with period outside | ✓ held |
| M3 | Zip picker hint "Uploads automatically when you pick a file" + renamed submit to "Upload pasted SKILL.md" | ✓ held |
| L4 | Install-from-GitHub collision warning "A skill named /pdf already exists (r2)…" shown inline when typing existing skill URL | ✓ held |
| L1 | Resources in preview dialog are clickable — chevron rotates, content expands inline | ✓ held (tested with LICENSE.txt) |
| L2 | Body preview no longer capped at 40vh | ✓ held |

## Ancillary positives

- `bundled` vs `r2` chip differentiation on cards is clear
- Delete (red trash) only appears for non-bundled skills — safety by design
- `/meeting-notes`, `/csv-analyse`, `/git-diff-summariser` example skills all present and enabled by default
- Preview dialog `Resources — click to view file contents` header explicitly invites interaction — great copy
- Install-from-GitHub dialog has helpful inline guidance text explaining both URL formats

---

---

## Theme editor (Preferences → Custom)

### T1 — HSL inputs have no H/S/L labels *(LOW)*

**What**: Each token input shows raw HSL like `240 10% 3.9%` — no column headers or labels telling the user which number is hue, which is saturation, which is lightness. First-time users will guess.

**Fix**: Add a tooltip on focus, or split into three small inputs with H/S/L headers. Or at minimum a placeholder hint above the list.

### T2 — Token names aren't self-descriptive *(LOW)*

**What**: Tokens like Muted, Accent, Secondary, Popover, Card, Ring are industry-standard shadcn tokens but the user editing them doesn't know which piece of the UI they affect. Someone tweaks "Muted" hoping to change button hover, gets something else.

**Fix**: Hover tooltip per token row: "Muted — used for button hover backgrounds, disabled text, secondary borders". Or a tiny swatch + label preview column showing the token in action.

### T3 — Paste CSS tab placeholder looks real *(LOW)*

**What**: The Paste CSS textarea is pre-filled with a full example `:root {...} .dark {...}` block. Not greyed-out, not styled as a placeholder — reads like actual theme content. Users will wonder if they need to edit it or clear it first.

**Fix**: Show the example as dim placeholder text that disappears on focus, or render above the textarea as a "Format expected" example card.

### Verified-working

- Visual editor ↔ Paste CSS tab switching preserves state
- "Auto-saves 0.5s after your last edit" copy is reassuring
- Per-token reset icon + global "Reset to default"
- Collapsible Base / Surfaces / Accent / Actions groups + Form / Charts (optional) groups
- 3 inline generator links (tweakcn, shadcn/ui Themes, 10000+ Themes) — excellent power-user touch
- Light/Dark variant toggle inside the editor
- Import JSON / Export JSON / Copy share link — full round-trip

---

## Dashboard pages

### A1 — Activity feed leaks raw `<skill_content>` wrapper *(HIGH)*

**What**: Same root cause as H1 — the activity log for every skill-activated conversation shows `<skill_content name="plan-task" directory="bundled:plan-task"> # Plan Task ## W` instead of a readable description. Two out of two items visible on the Activity page are unreadable wrappers.

**Fix**: When logging an activity row for "created conversation", strip the skill wrapper the same way we should fix the conversation title (H1). Ideally do it once in the storage layer so every surface benefits (titles, activity, notifications, search).

### A2 — `/dashboard/notifications` is a 404 *(LOW)*

**What**: The notification bell dropdown shows a `No notifications` empty state, but there is no dedicated page for viewing all notifications (archived, read, grouped). The 404 page itself is well-designed (icon + "Back to Dashboard" + "Start a new chat"), but landing there via guessed URL shows the missing route.

**Fix**: Either add a dedicated `/dashboard/notifications` page with the full list + filters + mark-all-read controls, or keep the dropdown-only pattern but surface a "View all" link inside the dropdown that points to a future page. Right now the bell has no overflow and no way to review history.

### ADM1 — Feature flags admin only shows 3 of the real feature flags *(MEDIUM)*

**What**: Admin Panel → Features shows `apiTokens`, `styleGuide`, `components` (3 DB-backed flags). But CLAUDE.md lists `chat`, `files`, `activity`, `notifications`, `themePicker`, `devTools` as flags too. Those are env-var driven (`VITE_FEATURE_CHAT=false` in `.dev.vars`), so they legitimately aren't in the DB — but the admin panel gives no hint that "runtime flag" vs "deploy-time flag" is a thing.

**Why it matters**: Admin opens the page expecting to toggle modules at runtime. They don't see Chat, Files, Activity in the list, assume it's a bug or data issue. They also don't realise there's a whole other class of flags controlled at deploy time.

**Fix**: Either add a read-only section titled "Deploy-time flags (set via env vars)" showing the env-var flags and whether they're currently enabled; or add a small inline hint below the Feature Flags heading: "Module visibility (chat, files, activity, notifications) is controlled at deploy time via `.dev.vars`. See CLAUDE.md for details."

### ADM2 — Active Sessions count (8) exceeds Total Users (4) suggesting stale sessions *(LOW)*

**What**: The admin stats cards show 4 Total Users but 8 Active Sessions. Ratio of 2:1 for a dev app with only one real tester (you) is high. Either users log in from many devices, or old sessions aren't being cleaned up.

**Fix direction**: Check the session cleanup policy — sessions should expire on logout AND on 7d inactivity. A cron/scheduled worker that purges expired sessions would keep the count honest. Also the admin UI could distinguish "Active" (last 24h) from "Valid" (not expired but idle).

---

## Verified-working from yesterday's fixes

| Tag | Fix | Status |
|-----|-----|--------|
| M2 | Title + 3 buttons on same flex row on Skills page | ✓ held |
| L5 | `agentskills.io spec` link with period outside | ✓ held |
| M3 | Zip picker hint "Uploads automatically when you pick a file" + renamed submit to "Upload pasted SKILL.md" | ✓ held |
| L4 | Install-from-GitHub collision warning "A skill named /pdf already exists (r2)…" shown inline when typing existing skill URL | ✓ held |
| L1 | Resources in preview dialog are clickable — chevron rotates, content expands inline | ✓ held |
| L2 | Body preview no longer capped at 40vh | ✓ held |

---

## Summary

### Findings by severity

| Severity | Count | Tags |
|---------:|------:|------|
| Critical | 0 | — |
| High     | 3 | H1, H2, A1 |
| Medium   | 3 | M1, M2, ADM1 |
| Low      | 8 | L1, L2, T1, T2, T3, A2, ADM2 |

### The big one

**H1 + A1 are the same bug in two surfaces.** The skill `<skill_content name="..." directory="...">...` wrapper leaks into conversation titles, activity log items, and almost certainly into notifications + conversation search once those have data. Fix once at the storage layer (or at the point of first persistence) and every surface benefits. This is the single most visible polish issue across the whole app.

### What yesterday's session shipped and what's still outstanding

Yesterday's audit closed 9 issues (H1, M1–M3, L1–L5 on the skills surface). Today's audit confirms all of them held in live deployment. The remaining gaps are on chat, activity, theme, and admin — none blocking, but the skill-wrapper leak (H1/A1) is the highest-ROI fix by far because it sits on screens users look at *every* time they return to the app.

### Recommended next-session phases

**Phase A — Fix the skill wrapper leak (~30 min)**
- Strip `<skill_content name=...>` at save time in `src/server/modules/conversations/storage.ts` (title derivation) and in activity logging
- Backfill existing rows with a migration or on-read stripping for safety
- Covers H1 + A1 + the likely-same bug in notifications/search

**Phase B — Streaming-state rendering (~1 hr)**
- H2: SkillActivationBlock pill and assistant output must render during streaming, not just after reload
- M2: Empty-state must gate on streaming too, not just `messages.length === 0`

**Phase C — Slash-command autocomplete (~2–3 hrs)**
- M1: Typing `/` opens a picker of enabled skills with name + description
- Arrow-key navigation + Enter to insert
- Reuses the R2 + bundled skill list already powering `/dashboard/skills`

**Phase D — Polish (~1 hr total)**
- L1 nowrap on `/skill-name` inline code
- L2 sidebar toggle tooltip
- T1/T2/T3 theme editor affordances
- A2 notifications page (or "View all" link)
- ADM1 feature flags legend

### Positives worth keeping

- Yesterday's 9 fixes all held in deployment
- Skills page is in great shape overall
- Theme editor's 3 inline generator links (tweakcn, shadcn/ui Themes, 10000+) are a standout power-user touch
- 404 page is well-designed
- `bundled` vs `r2` chip system on skills is clear and safe by design (delete only on non-bundled)
- Auto-saves 0.5s after edit is exactly the right polish on the theme editor

---

**Audit complete.** 14 findings total. Full repro steps + fix directions per finding above. Plan file suggested at `.jez/artifacts/ux-audit-2026-04-21-plan.md` when the next session starts executing.
