references/implementation-patterns.md
# Implementation Patterns
Use this file during the Develop phase when deciding how to build the frontend.
## Existing Projects
Follow local conventions first.
- Reuse existing layout shells, route wrappers, providers, and component primitives.
- Match existing naming, folder structure, import aliases, and styling conventions.
- Use the installed icon library instead of adding hand-written SVGs for common actions.
- Keep data fetching and mutation patterns aligned with the app's current client/server boundary.
- Keep state local unless multiple routes or distant components need to share it.
- Prefer existing form, table, modal, toast, tooltip, and menu patterns.
- Add tests in the style already present in the repo.
Avoid introducing:
- A new component library for one screen
- A new state manager for local interaction state
- A new CSS framework where the app already has one
- Global CSS changes for a local component unless tokens or resets are actually needed
## Greenfield Defaults
Pick the simplest stack that satisfies the request.
- Use plain HTML/CSS/JS for small static pages, simple prototypes, and single-file artifacts.
- Use React + TypeScript + Vite for interactive single-page apps when no stack is specified.
- Use Next.js only when routing, server rendering, file-based routes, or backend integration is part of the request.
- Use Tailwind only when the project already uses it or rapid component styling is valuable.
- Use shadcn/ui only for component-rich apps where its primitives reduce real implementation work.
## Component Design
Build semantic structure before visual polish.
- Use headings in logical order.
- Use `main`, `nav`, `section`, `aside`, `form`, `table`, `button`, and `a` appropriately.
- Use buttons for commands and links for navigation.
- Keep components cohesive: split when a subpart has its own state, behavior, reuse, or complexity.
- Do not split purely to create many files.
For fixed-format UI such as grids, dashboards, boards, control bars, metric cards, and tool palettes:
- Use stable dimensions.
- Set predictable min/max widths.
- Ensure labels and dynamic values do not resize the whole layout.
- Ensure hover and focus states do not shift neighboring elements.
## Visual Quality
Choose a visual direction that fits the domain.
- Operational tools should feel dense, clear, and work-focused.
- Consumer apps can be more expressive.
- Editorial or portfolio pages can use larger type and stronger art direction.
- Games and creative tools can be more animated and playful.
Avoid common weak defaults:
- Purple-blue gradients as the main visual idea
- Oversized centered hero sections for tools and dashboards
- Decorative cards around every section
- Cards nested inside cards
- Orbs, bokeh blobs, and generic abstract decorations
- One-note palettes where every surface is a tint of the same hue
- Text that scales directly with viewport width
Use:
- Clear hierarchy
- Intentional typography
- Consistent spacing
- Strong contrast
- Real icons for common tools
- Meaningful empty, loading, error, disabled, hover, focus, and active states
## Responsive Behavior
Define breakpoints by content, not by arbitrary device names.
- Verify narrow mobile, standard desktop, and any layout-specific breakpoint.
- Avoid horizontal scrolling on mobile unless the interface is intentionally a data grid.
- Wrap long controls or move secondary actions into menus.
- Keep tap targets comfortably sized.
- Ensure sticky headers, sidebars, and bottom bars do not cover content.
## Accessibility Baseline
Include:
- Programmatic labels for inputs and icon-only buttons
- Visible focus states
- Keyboard access for core interactions
- Dialog focus management when dialogs are used
- Sufficient color contrast
- Reduced-motion handling for large animations where appropriate
- Alt text for meaningful images and empty alt for decorative images
## Mock Data
If mock data is needed:
- Keep it isolated in a fixture, local constant, or mock module.
- Make it realistic enough to exercise long labels, empty values, and edge cases.
- Do not hardcode throwaway examples throughout component markup.
- Mention in the final response when mock data was added.
references/planning-checklist.md
# Planning Checklist
Use this file during the Plan and Orchestrate phases for non-trivial frontend work.
## Project Recon
Read enough code to answer:
- Which framework owns the UI: React, Next.js, Vue, Svelte, Astro, plain HTML, or another stack?
- How is routing handled?
- Where are reusable components stored?
- Where are layout shells, nav, providers, and global styles defined?
- Which styling system is used: Tailwind, CSS modules, Sass, vanilla CSS, CSS-in-JS, component library tokens, or custom tokens?
- Which icon library is already installed?
- Which commands exist for dev, build, lint, typecheck, unit tests, and browser tests?
- Is there an existing Playwright, Cypress, Storybook, or visual testing setup?
Prefer these sources:
- `package.json`
- framework config files
- app route or page directories
- component directories
- global CSS/theme/token files
- existing tests near the target surface
## User and Experience
Identify:
- Primary user
- Primary job the interface must support
- First screen or route the user will see
- Critical user path
- Required inputs, outputs, and state changes
- Required empty, loading, error, disabled, and success states
- Data source or mock data boundary
- Responsive needs: mobile-first, desktop-first, dashboard desktop, kiosk, embedded widget, etc.
- Accessibility needs: keyboard flow, labels, focus management, contrast, reduced motion
If the user did not specify details, infer conservative defaults from the product type and existing app.
## Scope Control
Before editing, decide:
- Which files must change?
- Which existing components can be reused?
- Which new components are justified?
- Which behavior can be mocked for this task?
- Which behavior must be real?
- Which checks prove the work is done?
Avoid:
- Broad refactors unrelated to the requested UI
- Introducing a new design system into an existing app
- Replacing routing or state management to solve a local UI task
- Building a landing page when the user asked for an app or tool
## Plan Shape
For substantial work, keep an internal checklist:
1. Identify target files and route.
2. Confirm layout and component structure.
3. Implement data/state and interactions.
4. Implement responsive behavior.
5. Add accessibility and edge states.
6. Run static checks.
7. Run browser verification.
8. Fix issues and repeat affected checks.
Expose the plan to the user only when the work is large, risky, or the user asked for it.
references/verification-checklist.md
# Verification Checklist
Use this file during the Verify phase.
## Static Checks
Prefer repo scripts. Inspect `package.json` and use the commands already available.
Common checks:
- Type check
- Lint
- Unit tests
- Component tests
- End-to-end tests
- Production build
Do not invent a new toolchain if the repo already has one.
If dependencies are missing or commands fail for environment reasons, continue with checks that are still possible and report the limitation.
## Browser Checks
When the app can run locally:
1. Start the dev server with the repo's existing script.
2. Open the relevant route.
3. Wait for the app to finish rendering.
4. Check browser console errors.
5. Check failed network requests.
6. Capture desktop and mobile screenshots.
7. Exercise the primary user path.
8. Inspect layout at narrow and desktop widths.
9. Fix issues and re-run affected checks.
Use `scripts/verify_frontend.py` when a generic page health check is enough. Use custom Playwright automation when the core interaction path needs task-specific clicks, typing, or assertions.
## Generic Visual QA
Assume the first render has problems. Look for:
- Text clipped inside buttons, cards, inputs, nav items, charts, tables, or badges
- Headings wrapping into controls or decorative lines
- Low contrast text or icons
- Disabled elements that look active
- Interactive elements without hover or focus states
- Layout shift when counts, labels, or loading text changes
- Repeated items with inconsistent spacing
- Cards or panels too close to each other
- Elements too close to viewport edges
- Modal, popover, dropdown, or tooltip clipping
- Sticky headers, sidebars, or bottom bars covering content
- Accidental horizontal scroll on mobile
- Empty states missing for empty data
- Loading states that leave a blank page
- Error states that are not recoverable
- Placeholder copy left in the UI
## Responsive QA
Check at minimum:
- Narrow mobile: about 375px wide
- Standard desktop: about 1440px wide
Also check any breakpoint where the layout changes.
Verify:
- Navigation remains usable
- Primary actions stay visible
- Text wraps cleanly
- Tables, charts, and grids remain readable
- Touch targets are large enough
- No horizontal scroll unless intentional
- Media is not cropped in a way that hides important content
## Interaction QA
Exercise the primary path:
- Click primary and secondary actions
- Submit forms with valid and invalid input
- Navigate between tabs or views
- Open and close menus, dialogs, popovers, and drawers
- Confirm keyboard access for critical controls
- Confirm loading, success, and error feedback
## Console and Network QA
Treat these as issues unless clearly expected:
- JavaScript exceptions
- React hydration errors
- Failed static asset loads
- Failed API requests
- CORS errors
- Source map errors that obscure real failures
## Completion Bar
Do not declare success until:
- The target route renders.
- The main interaction works.
- Desktop and mobile layouts have been checked.
- Console errors have been reviewed.
- Any static checks that can run have run.
- Known limitations are reported.
scripts/verify_frontend.py
#!/usr/bin/env python3
"""Generic browser health check for frontend work.
This script opens a URL with Playwright, records console and network failures,
captures desktop and mobile screenshots, and runs lightweight DOM checks for
common frontend regressions.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
DEFAULT_VIEWPORTS = {
"desktop": {"width": 1440, "height": 1000},
"mobile": {"width": 375, "height": 812},
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Verify a frontend route with browser screenshots, console checks, and DOM health checks."
)
parser.add_argument("url", help="URL to verify, for example http://localhost:5173/dashboard")
parser.add_argument(
"--out-dir",
default="frontend-verification",
help="Directory for screenshots and report.json. Default: frontend-verification",
)
parser.add_argument(
"--timeout",
type=int,
default=30000,
help="Navigation timeout in milliseconds. Default: 30000",
)
parser.add_argument(
"--wait",
default="networkidle",
choices=["load", "domcontentloaded", "networkidle"],
help="Load state to wait for. Default: networkidle",
)
parser.add_argument(
"--viewport",
action="append",
default=[],
metavar="NAME:WIDTHxHEIGHT",
help="Extra viewport to check, for example tablet:768x1024. Can be repeated.",
)
parser.add_argument(
"--full-page",
action="store_true",
help="Capture full-page screenshots instead of viewport screenshots.",
)
return parser.parse_args()
def parse_viewports(values: list[str]) -> dict[str, dict[str, int]]:
viewports = dict(DEFAULT_VIEWPORTS)
for raw in values:
try:
name, size = raw.split(":", 1)
width, height = size.lower().split("x", 1)
viewports[name] = {"width": int(width), "height": int(height)}
except ValueError as exc:
raise SystemExit(f"Invalid --viewport value: {raw!r}. Expected NAME:WIDTHxHEIGHT.") from exc
return viewports
def run_dom_checks(page: Any) -> dict[str, Any]:
return page.evaluate(
"""
() => {
const result = {
title: document.title || "",
bodyTextLength: (document.body && document.body.innerText || "").trim().length,
documentWidth: document.documentElement.scrollWidth,
viewportWidth: window.innerWidth,
horizontalOverflow: document.documentElement.scrollWidth > window.innerWidth + 1,
clippedElements: [],
unnamedButtons: [],
imagesMissingAlt: [],
fixedElements: [],
};
const isVisible = (el) => {
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
return style.display !== "none" &&
style.visibility !== "hidden" &&
rect.width > 0 &&
rect.height > 0;
};
for (const el of Array.from(document.querySelectorAll("body *"))) {
if (!isVisible(el)) continue;
const style = window.getComputedStyle(el);
const tag = el.tagName.toLowerCase();
if (
result.clippedElements.length < 25 &&
(el.scrollWidth > el.clientWidth + 2 || el.scrollHeight > el.clientHeight + 2) &&
["hidden", "clip", "auto", "scroll"].includes(style.overflow)
) {
const text = (el.innerText || el.getAttribute("aria-label") || "").trim().slice(0, 80);
result.clippedElements.push({
tag,
className: String(el.className || "").slice(0, 120),
id: el.id || "",
text,
clientWidth: el.clientWidth,
scrollWidth: el.scrollWidth,
clientHeight: el.clientHeight,
scrollHeight: el.scrollHeight,
});
}
if (result.fixedElements.length < 20 && ["fixed", "sticky"].includes(style.position)) {
const rect = el.getBoundingClientRect();
result.fixedElements.push({
tag,
className: String(el.className || "").slice(0, 120),
id: el.id || "",
position: style.position,
rect: {
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
},
});
}
}
for (const button of Array.from(document.querySelectorAll("button, [role='button']"))) {
if (!isVisible(button)) continue;
const name = (
button.innerText ||
button.getAttribute("aria-label") ||
button.getAttribute("title") ||
""
).trim();
if (!name && result.unnamedButtons.length < 25) {
result.unnamedButtons.push({
tag: button.tagName.toLowerCase(),
className: String(button.className || "").slice(0, 120),
id: button.id || "",
});
}
}
for (const img of Array.from(document.images)) {
if (!isVisible(img)) continue;
if (!img.hasAttribute("alt") && result.imagesMissingAlt.length < 25) {
result.imagesMissingAlt.push({
src: img.currentSrc || img.src || "",
className: String(img.className || "").slice(0, 120),
id: img.id || "",
});
}
}
return result;
}
"""
)
def main() -> int:
args = parse_args()
viewports = parse_viewports(args.viewport)
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
try:
from playwright.sync_api import Error as PlaywrightError
from playwright.sync_api import sync_playwright
except ImportError:
print(
"Playwright is not installed. Install it with `pip install playwright` and `python -m playwright install chromium`.",
file=sys.stderr,
)
return 2
report: dict[str, Any] = {
"url": args.url,
"viewports": {},
"console": [],
"failedRequests": [],
"errors": [],
"status": "success",
}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
def on_console(msg: Any) -> None:
if msg.type in {"error", "warning"}:
report["console"].append({"type": msg.type, "text": msg.text})
def on_request_failed(request: Any) -> None:
failure = request.failure
report["failedRequests"].append(
{
"url": request.url,
"method": request.method,
"errorText": failure.get("errorText") if failure else "unknown",
}
)
page.on("console", on_console)
page.on("requestfailed", on_request_failed)
for name, viewport in viewports.items():
page.set_viewport_size(viewport)
viewport_report: dict[str, Any] = {"viewport": viewport}
try:
response = page.goto(args.url, wait_until=args.wait, timeout=args.timeout)
viewport_report["httpStatus"] = response.status if response else None
screenshot_path = out_dir / f"{name}.png"
page.screenshot(path=str(screenshot_path), full_page=args.full_page)
viewport_report["screenshot"] = str(screenshot_path)
viewport_report["dom"] = run_dom_checks(page)
except PlaywrightError as exc:
viewport_report["error"] = str(exc)
report["errors"].append({"viewport": name, "error": str(exc)})
report["viewports"][name] = viewport_report
browser.close()
issue_count = (
len(report["console"])
+ len(report["failedRequests"])
+ len(report["errors"])
+ sum(
1
for viewport in report["viewports"].values()
if viewport.get("dom", {}).get("horizontalOverflow")
or viewport.get("dom", {}).get("clippedElements")
or viewport.get("dom", {}).get("unnamedButtons")
or viewport.get("dom", {}).get("imagesMissingAlt")
)
)
if issue_count:
report["status"] = "issues_found"
report_path = out_dir / "report.json"
report_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
print(json.dumps({"status": report["status"], "report": str(report_path)}, indent=2))
return 1 if report["status"] == "issues_found" else 0
if __name__ == "__main__":
raise SystemExit(main())
SKILL.md
---
name: frontend-development-workflow
description: "End-to-end frontend development workflow for building or modifying web apps, pages, components, dashboards, HTML/CSS/JS interfaces, React/Vue/Svelte projects, or UI artifacts. Use when the user asks to create, redesign, implement, fix, or polish frontend work and expects the model to plan first, orchestrate the implementation, develop the code, then verify it in a browser. Trigger for requests about frontend development, page development, component development, dashboards, web applications, UI implementation, responsive styling, or visual polish. Do NOT use when the user only asks for backend/API/database work, a design critique with no code changes, or a high-level explanation with no implementation."
---
# Frontend Development Workflow
## Repository planning gate
When this repository's `CLAUDE.md` classifies the request as complex or high-impact, the primary agent must invoke `plan-tasks` before entering this workflow's Plan phase. A subagent must return control to the primary agent rather than create a competing plan or edit target files. Resume this workflow only after an approved `docs/plans/PLAN_<descriptive-name>.md` maps the relevant `A-*` to `frontend-development-workflow`, its assigned role, worktree isolation, and verification. This skill's own planning phase refines the approved action; it never replaces user approval.
Use this skill to complete frontend work through four phases:
1. Plan
2. Orchestrate
3. Develop
4. Verify
For new apps, pages, dashboards, redesigns, components, and non-trivial UI fixes, follow the full workflow. For tiny edits, keep the plan brief but still inspect the existing code and verify the affected UI.
## Resource Guide
Load these files only when needed:
- `references/planning-checklist.md`: Use during Plan and Orchestrate for project recon, scope control, and implementation sequencing.
- `references/implementation-patterns.md`: Use during Develop for component structure, styling choices, responsive behavior, accessibility, and mock data handling.
- `references/verification-checklist.md`: Use during Verify for static checks, browser checks, visual QA, responsive QA, and completion criteria.
- `scripts/verify_frontend.py`: Use as a black-box generic browser health check for local frontend routes. Run `python scripts/verify_frontend.py --help` before using it.
## Phase 1: Plan
Inspect the existing project before making architectural or styling decisions.
Read enough context to identify:
- Framework, router, package manager, and dev server command
- Target route, page, component, or artifact
- Styling system, design tokens, component library, and icon library
- Existing UI conventions and test commands
- Primary user, user task, required states, and responsive needs
For substantial work, read `references/planning-checklist.md`.
Ask the user only when a missing decision would cause meaningful rework. Otherwise make a conservative assumption, proceed, and mention the assumption in the final response.
## Phase 2: Orchestrate
Convert the plan into a short implementation sequence before editing code.
Prefer:
- Existing components and local patterns
- Existing routing and state management
- Existing styling conventions
- Focused files and scoped changes
- The smallest architecture that satisfies the request
For greenfield work, choose a simple stack that matches the request. Use plain HTML/CSS/JS for small standalone pages. Use React + TypeScript + Vite for interactive single-page apps unless the user or repo indicates otherwise.
For detailed orchestration guidance, read `references/planning-checklist.md`.
## Phase 3: Develop
Build the actual usable interface, not a placeholder or marketing explanation.
During implementation:
- Use semantic structure before visual polish.
- Match the repo's component, styling, import, and naming patterns.
- Include real expected states: loading, empty, error, disabled, success, hover, focus, and active where relevant.
- Use stable responsive constraints so labels, counts, loading text, and hover states do not shift the layout.
- Use icons for familiar actions when an icon library exists.
- Avoid unrelated refactors.
Avoid generic AI-looking UI:
- Default purple gradients
- Decorative blobs or orbs
- Unnecessary card-heavy page sections
- Cards nested inside cards
- Oversized centered hero layouts for dashboards or tools
- One-note palettes unless required by brand
For detailed implementation guidance, read `references/implementation-patterns.md`.
## Phase 4: Verify
Do not finish after coding. Verify the frontend unless the environment cannot run it.
Run available static checks from the repo:
- Type check
- Lint
- Unit or component tests
- Build
- Existing browser tests
Then run browser verification when the app can launch locally:
1. Start the dev server with the repo's script.
2. Open the relevant route.
3. Wait for the app to render.
4. Check console errors and failed requests.
5. Check desktop and mobile layouts.
6. Exercise the primary interaction.
7. Fix issues and re-run affected checks.
For a generic page health check, use:
```bash
python scripts/verify_frontend.py --help
python scripts/verify_frontend.py http://localhost:5173
```
For task-specific interactions, write focused Playwright automation instead of relying only on the generic script.
For detailed QA guidance, read `references/verification-checklist.md`.
## Final Response
When complete, report:
- What changed
- Key files touched
- Static checks run
- Browser checks run
- Any verification that could not run and why
- The local URL if a dev server is running for user inspection
Keep the final response concise and factual.