A dev-only inspection and annotation tool for reviewing UI element presentation. Reviewers can hover any UI element with data-ux-* attributes, annotate issues, and export structured JSON or Markdown for agent handoff.
- Activation
- Quick Start
- Adding UX Metadata to Components
- Annotation Workflow
- Export Formats
- Export Format for Agent Handoff
- Programmatic API
- Production Safety
- Troubleshooting
UX Review Mode activates automatically when both conditions are true:
| Gate | Check | How to set |
|---|---|---|
| 1. Dev build | import.meta.env.DEV |
Run npm run dev (not production build) |
| 2. Feature flag | VITE_ENABLE_UX_REVIEW === "true" |
Set in frontend/.env |
No URL parameter or keyboard shortcut is required — it activates on page load.
The keyboard shortcut (Ctrl+Shift+U) and ?uxReview=1 query param remain
available as secondary toggles.
The feature flag goes in frontend/.env:
VITE_ENABLE_UX_REVIEW=trueThis file is committed to the repo. It is safe because production builds compile with import.meta.env.DEV === false, which short-circuits the entire system regardless of the flag.
- Start the dev server:
npm run dev - Navigate to any page (e.g.,
http://localhost:5173/skills) - The browser console logs:
[UX Review] Mode activated. - A side panel opens when you click any element with
data-ux-*attributes.
To toggle the panel on/off after activation, press Ctrl+Shift+U
(or append ?uxReview=1 to the URL).
Press Ctrl+Shift+U (or Cmd+Shift+U on macOS) to toggle UX Review Mode on and off without reloading the page.
- Add UX metadata to a component (see below)
- Hover the component — a cyan highlight frame and tooltip appear
- Click the component — the annotation side panel opens
- Fill in the annotation form (issue type, severity, comment, expected change)
- Submit — the annotation appears in the History tab
- Export using the footer dropdown — JSON or Markdown, per page or all sessions
Every component that should be inspectable needs to spread uxAttributes() on its root DOM element.
import type { UxMetadata } from "../components/ui";
import { uxAttributes } from "../components/ui";interface MyComponentProps {
ux?: UxMetadata;
// ... other props
}function MyComponent({ ux, ...rest }: MyComponentProps) {
return (
<div {...(ux ? uxAttributes(ux) : {})} {...rest}>
{/* children */}
</div>
);
}<MyComponent
ux={{
uxId: "skills-hero",
uxComponent: "SummaryHero",
uxRole: "page-summary",
uxPage: "skills",
uxAttention: "medium",
uxActionKind: "view",
uxDensity: "compact",
}}
/>| Attribute | UxMetadata field |
Purpose | Required |
|---|---|---|---|
data-ux-id |
uxId |
Stable, human-readable identifier | Yes (without it, the element is invisible to review mode) |
data-ux-component |
uxComponent |
Component type label (e.g. PageHeader, DataTable) |
No |
data-ux-role |
uxRole |
Semantic role within the page layout | No |
data-ux-page |
uxPage |
Page or feature identifier | No |
data-ux-attention |
uxAttention |
Attention level for review prioritisation | No |
data-ux-action-kind |
uxActionKind |
Action category (view, mutate, navigate, etc.) | No |
data-ux-density |
uxDensity |
Display density hint (default, compact, etc.) | No |
data-ux-source |
uxSource |
Source file or area reference | No |
data-ux-*attributes must never contain secrets, tokens, PII, raw payloads, logs, evidence blobs, or sensitive backend values.- The
uxIdshould be stable across page re-renders. Use a string constant, not an auto-generated ID. - These attributes remain in production builds — they are inert but visible in the DOM. Ensure they carry no sensitive data.
For a complete list of components that already support the
uxprop, see the ForgeFrame UI primitive library atfrontend/src/components/ui/.
Move your cursor over any element with a data-ux-id attribute. A cyan border highlights the element, and a floating tooltip shows its metadata (ID, component, role, action, attention).
Click the element. The side panel opens showing the full metadata table and the annotation form.
Fill in the form:
- Issue type — one of: Too large, Too prominent, Wrong action hierarchy, Navigation looks like mutation, Empty-looking section, Duplicate information, Raw data too visible, Diagnostics too prominent, Needs compact representation, Label unclear, Other
- Severity — Critical, Major, Minor, Suggestion
- Comment — free-text description of the issue
- Expected change — optional description of what should change
- Screenshot note — optional reference to a screenshot file
Click Add Annotation.
Switch to the History tab to see all annotations for the current page.
Each annotation card has three controls:
- Resolve (
○/✓) — toggle resolved state. Resolved annotations appear dimmed with a green border. - Edit — opens an inline form to change the issue type, severity, comment, and expected change. Save or cancel.
- Delete (
✕) — permanently removes the annotation.
Use the Clear dropdown in the footer to remove annotations for the current page or all pages.
The Export dropdown in the footer offers six options:
| Option | Format | Scope | Action |
|---|---|---|---|
| Copy JSON (current page) | JSON | Current page only | Clipboard |
| Copy JSON (all pages) | JSON | All annotations | Clipboard |
| Copy Markdown (current page) | Markdown | Current page only | Clipboard |
| Download JSON (current page) | JSON | Current page only | File download |
| Download JSON (all pages) | JSON | All annotations | File download |
Annotations are automatically saved to localStorage under the key forgeframe-ux-review-annotations. They survive page navigation, tab closure, and browser restart within the same origin. Clearing browser storage or calling clearAnnotations() removes them.
{
"schemaVersion": "1.0",
"generatedAt": "2026-05-05T17:04:46.123Z",
"route": "/skills",
"pageTitle": "Skills — ForgeFrame",
"viewport": { "width": 1440, "height": 900 },
"annotations": [
{
"id": "ux-ann-1714832686123-a1b2c3d4",
"route": "/skills",
"pageTitle": "Skills — ForgeFrame",
"uxId": "skills-hero",
"uxComponent": "SummaryHero",
"uxRole": "page-summary",
"uxActionKind": "view",
"uxAttention": "medium",
"issueType": "too-large",
"severity": "major",
"comment": "This hero takes up too much vertical space on a 1440px viewport",
"expectedChange": "Reduce padding from 32px to 16px vertically, shrink title font from 24px to 18px",
"selectorFallback": "[data-ux-id=\"skills-hero\"]",
"textSnapshot": "Skills Overview · 12 active · 3 archived",
"boundingBox": { "x": 0, "y": 0, "width": 1200, "height": 300 },
"createdAt": "2026-05-05T17:03:12.456Z",
"isResolved": false
}
],
"_instructions": "Only change presentation, hierarchy, grouping, labeling, density, or default visibility. Do not remove data or settings. Keep full detail accessible through details, expansion, audit history, or AdvancedDiagnostics."
}<!--
UX REVIEW ANNOTATION EXPORT — AGENT INSTRUCTIONS
Only change presentation, hierarchy, grouping, labeling, density, or default visibility. Do not remove data or settings. Keep full detail accessible through details, expansion, audit history, or AdvancedDiagnostics.
-->
# UX Review Annotations
**Page:** /skills
**Title:** Skills — ForgeFrame
**Generated:** 2026-05-05T17:04:46.123Z
**Annotation count:** 1
## Instructions
- Only change presentation, hierarchy, grouping, labeling, density, or default visibility.
- Do not remove data or settings.
- Keep full detail accessible through details, expansion, audit history, or AdvancedDiagnostics.
---
## Annotation 1
### Page
/skills
### Element
- **UX ID:** skills-hero
- **Component:** SummaryHero
- **Role:** page-summary
- **Selector:** `[data-ux-id="skills-hero"]`
- **Text:** Skills Overview · 12 active · 3 archived
### Issue
- **Type:** Too large
- **Severity:** Major
- **Comment:** This hero takes up too much vertical space on a 1440px viewport
### Expected Change
Reduce padding from 32px to 16px vertically, shrink title font from 24px to 18px
### Metadata
- **Route:** /skills
- **Created:** 2026-05-05T17:03:12.456Z
- **Viewport:** 1440×900
---Exported annotations are designed to be consumed by coding or task-management agents. The key fields for agent consumption are:
| Field | Purpose for agent |
|---|---|
uxId |
Identifies the exact element in code (stable, searchable) |
selectorFallback |
CSS selector to locate the element in the DOM |
uxComponent |
Component type to narrow the search in the codebase |
route |
Page route where the issue was found |
issueType / severity |
Priority guidance for the agent |
comment |
Human-readable description of the problem |
expectedChange |
Specific actionable instruction for the fix |
textSnapshot |
Text content to confirm the element identity |
boundingBox |
Visual size and position reference |
_instructions |
Constraints on what the agent may change |
Agents receiving this export should:
- Locate the component by
uxIdoruxComponentin the codebase - Navigate to the page indicated by
route - Apply the
expectedChangefollowing the_instructionsconstraints - Preserve all data, settings, and full detail access (keep hidden detail expandable)
- Only change presentation, hierarchy, grouping, labeling, density, or default visibility
import { useUxReview } from "../components/ui";
function MyComponent() {
const { isEnabled, annotationCount } = useUxReview();
// ...
}The useUxReview() hook returns a UxReviewContextValue with:
| Property | Type | Description |
|---|---|---|
isEnabled |
boolean |
Whether review mode is active |
selectedElement |
CapturedElement | null |
Currently selected element |
hoveredElement |
CapturedElement | null |
Currently hovered element |
annotations |
UxAnnotation[] |
All annotations across all pages |
pageAnnotations |
UxAnnotation[] |
Annotations for current page only |
annotationCount |
number |
Total count across all pages |
pageAnnotationCount |
number |
Count for current page only |
addAnnotation(...) |
function |
Add a new annotation |
removeAnnotation(id) |
function |
Delete an annotation |
updateAnnotation(id, updates) |
function |
Edit an existing annotation |
clearAnnotations(route?) |
function |
Clear page or all annotations |
exportJson(route?) |
function |
Export as JSON string |
exportMarkdown(route?) |
function |
Export as Markdown string |
import {
buildAnnotationExport, // Build AnnotationExport object
formatAnnotationsJson, // Format annotations as JSON string
formatAnnotationsMarkdown, // Format annotations as Markdown string
copyToClipboard, // Copy text to clipboard
downloadAsFile, // Trigger file download
} from "../components/ux-review/export-utils";All public types are re-exported from the UI barrel:
import type {
UxReviewContextValue,
UxElementData,
CapturedElement,
UxAnnotation,
AnnotationRecord,
AnnotationExport,
AnnotationUpdate,
UxIssueType,
UxAnnotationSeverity,
} from "../components/ui";UX Review Mode is unavailable in production builds by design:
-
Compile-time elimination: The constant
UX_REVIEW_AVAILABLEis computed asimport.meta.env.DEV && import.meta.env.VITE_ENABLE_UX_REVIEW === "true". Whenimport.meta.env.DEVisfalse(production build), Vite's tree-shaker eliminates all code conditioned on this beingtrue, including the overlay, panel, event listeners, and localStorage access. -
No-op provider: In production, the provider renders children through without mounting any review tooling. The
NULL_CONTEXTobject has no-op stubs for all functions. -
No runtime gate: In development, the provider auto-activates when
VITE_ENABLE_UX_REVIEW=trueis set. The?uxReview=1URL parameter is still respected as an override for testing purposes. -
No routes registration: No review-only routes (
/__design,/dev/design) are registered. The pattern is development-only and URL-param activated. -
Data safety: UX metadata attributes (
data-ux-*) remain in the DOM in production, but they carry only structural/type information — never secrets, tokens, PII, raw payloads, or sensitive backend values.
| Symptom | Likely cause | Fix |
|---|---|---|
Console shows no [UX Review] banner |
VITE_ENABLE_UX_REVIEW not set |
Add VITE_ENABLE_UX_REVIEW=true to .env |
| UX Review does not appear | Production build | Run npm run dev |
| Overlay appears but no highlights | No elements with data-ux-id |
Add UX metadata to the component |
| Panel opens but no metadata shown | Selected element lacks data-ux-* attributes |
Add uxId and other fields |
| Annotations lost after browser close | Corrupted localStorage | Clear forgeframe-ux-review-annotations from localStorage dev tools |
| Export JSON is empty | No annotations for current page | Switch to the History tab to verify |
| "Copied!" toast but nothing on clipboard | Clipboard API blocked (HTTP vs HTTPS, permissions) | Use Download option instead |
frontend/src/components/ux-review/— source codefrontend/src/components/ui/types.ts—UxMetadatatype anduxAttributes()helperfrontend/reference/residual-ux-anti-pattern-catalog.md— common UI anti-patterns to annotatedocs/ux-review/agent-workflow.md— agent workflow for fixing exported annotationsdocs/ux-review/annotation-schema.json— JSON Schema for annotation export validationdeploy/scripts/validate-ux-annotation.mjs— validation script for exported annotation files- Generated TypeDoc:
npm run docsthen opendocs/index.html
Exported annotations are designed to be consumed by coding agents. A complete
agent workflow is defined in docs/ux-review/agent-workflow.md. It covers:
- File naming convention for exported annotation files
- Annotation status lifecycle (
open→in_progress→fixed|wont_fix|needs_design_decision) - How agents locate elements by
data-ux-id, component name, or route - Mandatory fixing rules (presentation only, preserve data/routes, prefer shared primitives)
- Resolved annotation report format for audit trail
- Validation script for checking annotation file correctness
Reviewers should export annotations using the prescribed naming convention
and place them in docs/ux-review/ for agent consumption.