feat(skills): add React and Vite tool expertise

Squash-merge verified React and Vite expertise at exact head d4fd6cf70d. Required validate and paired evaluation checks passed; advisory droid review had no blocking findings.
This commit is contained in:
Magnus Hedemark
2026-09-01 20:05:58 -04:00
committed by GitHub
parent 035e58d3e3
commit befe2e26fc
22 changed files with 864 additions and 2 deletions
+38
View File
@@ -0,0 +1,38 @@
# react — Build and diagnose React applications
## Why Install This Skill
React projects accumulate framework-specific failure modes: hooks that resynchronize unnecessarily, stale requests that overwrite newer data, routes that break under a subpath, and Vite environment values accidentally shipped to browsers. This skill gives your agent a focused operating loop for those problems.
After installation, your agent can inspect a React/Vite project safely, make component and state changes that fit its existing conventions, protect accessible interaction states, and verify the result with the project's own checks. It also includes a read-only doctor for quick diagnostics without installing dependencies or exposing environment values.
## What You Get
| Directory | Purpose |
|---|---|
| `SKILL.md` | React-specific implementation workflow, guardrails, handoffs, and verification steps |
| `references/component-and-state.md` | Component boundaries, hooks, effects, async state, and forms |
| `references/vite-diagnostics.md` | Vite environment, build, asset-base, and deployment diagnostics |
| `scripts/react-doctor.py` | Bounded Python diagnostic with human-readable or JSON output |
| `scripts/test_react_doctor.py` | Offline tests for diagnostic behavior and safety guarantees |
| `evals/evals.json` | Six substantive output-quality cases for React and Vite work |
## Quick Start
```bash
python3 scripts/react-doctor.py --json .
python3 scripts/react-doctor.py .
```
The doctor reads project files only. It does not install packages, run scripts, contact the network, or print environment values.
## Triggers
Load this skill when working with React, JSX/TSX, hooks, React Router, Vite React configuration, React component/state implementation, or React build failures. Use it for a focused project diagnosis before editing.
## Requirements
- Python 3.8+ for the bundled diagnostic and tests.
- Node.js and the project's package manager for application builds and tests.
- Existing React/Vite project files; no API key is required.
- Use `frontend-engineering` for framework-agnostic frontend architecture, `playwright` for browser automation, and `web-accessibility` for dedicated accessibility audits.
+103
View File
@@ -0,0 +1,103 @@
---
name: react
description: >-
Operate React applications as a named tool: inspect and diagnose React/Vite
projects, design component boundaries and state flow, implement accessible
responsive UI, and verify behavior with the project's tests. Use when a task
explicitly involves React, JSX/TSX, React hooks, React Router, Vite React
configuration, or React build failures. Do not use for framework-agnostic
frontend strategy (route to frontend-engineering), browser automation (route
to playwright), accessibility policy or audits (route to web-accessibility),
or non-React mobile apps (route to mobile-development).
license: MIT
compatibility: >-
The bundled diagnostic script uses Python 3.8+ standard library only. Running
or building an application requires its repository package manager and Node.js.
metadata:
tags: react, jsx, tsx, vite, hooks, components, router, frontend
source: https://react.dev/
---
# React Application Engineering
Use this skill for the React-specific implementation layer. Preserve the
project's existing React version, package manager, build scripts, styling
conventions, and routing model unless the user asks for a migration.
## Operating loop
1. **Diagnose before editing.** Run `scripts/react-doctor.py --json [PROJECT]` and
inspect `package.json`, source entry points, Vite config, TypeScript config,
routes, and test scripts. The diagnostic is bounded and read-only.
2. **Define the component contract.** Identify the page/feature boundary,
inputs and outputs, owned state, server state, loading/empty/error/success
states, and side effects. Keep reusable components independent of route
globals and avoid passing state through unrelated layers.
3. **Implement with explicit data flow.** Prefer local state for local behavior,
context only for genuinely cross-cutting concerns, and the existing server
state/cache solution for remote data. Keep effects for synchronization with
external systems; derive values during render rather than storing duplicates.
4. **Keep UI resilient.** Render a useful loading, empty, error, and success
experience. Cancel or ignore stale async work, handle aborts, and avoid
setting state after an obsolete request. Preserve stable keys and avoid
mutating props or state.
5. **Build for the browser.** Use semantic HTML, keyboard-operable controls,
visible focus, responsive layout, and stable accessible names. For detailed
accessibility requirements, load [web-accessibility](../web-accessibility/SKILL.md).
6. **Verify in layers.** Run the narrowest existing unit/component test, then
lint/typecheck, then the production build. For browser-level flows use
[playwright](../playwright/SKILL.md), not ad hoc browser automation. Report
the exact commands and any environment-dependent checks that were skipped.
## React-specific rules
- Hooks run unconditionally and in the same order on every render; never call
them in branches, loops, event handlers, or nested functions.
- Effects synchronize with external systems. Do not use an effect to calculate
a value that can be derived from props/state, or to mirror props into state
without a clear user-editing requirement.
- Use functional updates when the next state depends on the previous state.
Give list items stable keys from domain identity, not array indexes when the
list can reorder, insert, or delete.
- Treat event handlers as user intent and keep them separate from render-time
computation. Disable or guard duplicate submissions and expose pending state.
- Keep API response validation and transformation at the integration boundary;
components should consume a typed, predictable view model.
- Do not add a state library or router solely because it is popular. First map
ownership and use the project's existing choices.
- In Vite, expose only intentionally public variables using the project's
documented prefix (normally `VITE_`); never put secrets in client bundles.
Read [references/vite-diagnostics.md](references/vite-diagnostics.md) for
environment, build, and deployment checks.
## Routing and handoffs
- Component architecture, responsive implementation, performance budgets, and
general frontend testing: [frontend-engineering](../frontend-engineering/SKILL.md).
- Browser E2E authoring, locator choice, network interception, and Playwright
runs: [playwright](../playwright/SKILL.md).
- Semantic structure, keyboard/focus behavior, WCAG acceptance evidence, and
accessibility audits: [web-accessibility](../web-accessibility/SKILL.md).
- React Native, Expo, Android, or iOS implementation: [mobile-development](../mobile-development/SKILL.md).
## Reference routing
| Load when | Reference |
|---|---|
| Choosing component boundaries, state ownership, effects, or async UI behavior | `references/component-and-state.md` |
| Diagnosing Vite env exposure, dependency versions, build output, or deployment paths | `references/vite-diagnostics.md` |
## Included script
`scripts/react-doctor.py` is a read-only, dependency-free diagnostic. Run
`scripts/react-doctor.py --help` for options. It accepts a project directory,
checks common React/Vite signals, and emits human-readable or bounded JSON
output. It does not install packages, execute project scripts, access the
network, or print environment values.
## Completion boundary
Stop when the requested React change is implemented, the project's relevant
checks have run, and remaining failures are reported with their command and
root-cause evidence. Do not broaden a component task into a framework migration
or an accessibility audit without explicit scope.
+42
View File
@@ -0,0 +1,42 @@
{
"schema_version": 1,
"skill_name": "react",
"evals": [
{
"id": "component-state-boundary",
"prompt": "Design a React dashboard filter component that fetches results and has loading, empty, error, and success states.",
"expected_output": "A component/state plan that assigns ownership, avoids duplicated derived state, and describes each async UI state.",
"assertions": ["Identifies local, URL, and server state separately", "Includes loading, empty, error, and success behavior", "Avoids an unnecessary effect for derived values"]
},
{
"id": "stale-request-protection",
"prompt": "Fix a React search hook where a slower response for the previous query overwrites the current query results.",
"expected_output": "A hook implementation or patch using abort or an active-request guard, with cleanup and intentional cancellation handling.",
"assertions": ["Prevents stale responses from committing state", "Cleans up the obsolete request", "Does not present abort cancellation as a user error"]
},
{
"id": "vite-secret-boundary",
"prompt": "Diagnose why a Vite app exposes an API token in its browser bundle and propose a safe fix.",
"expected_output": "A diagnosis that treats client env values as public, removes the secret from client code, and routes privileged access through a server boundary.",
"assertions": ["Explains compile-time Vite env substitution", "Never recommends shipping a secret under a public prefix", "Includes a way to rotate the exposed credential"]
},
{
"id": "subpath-routing-build",
"prompt": "A React Router app works at localhost root but its JS and CSS 404 when deployed under /portal/. Debug the Vite deployment.",
"expected_output": "A bounded diagnostic covering Vite base, generated asset URLs, and host-side SPA history fallback, followed by a preview verification.",
"assertions": ["Checks the Vite base setting", "Checks SPA fallback for deep links", "Verifies the built output at the deployed subpath"]
},
{
"id": "accessible-react-control",
"prompt": "Implement an expandable React disclosure with keyboard support and a focus-safe error message.",
"expected_output": "A semantic button/disclosure implementation with an accessible name, aria-expanded linkage, visible focus, and an announced error path.",
"assertions": ["Uses native button semantics", "Keeps aria-expanded and controlled-region linkage synchronized", "Routes detailed WCAG review to web-accessibility"]
},
{
"id": "verification-handoff",
"prompt": "Add a React form feature and verify it without installing new dependencies or using browser automation unnecessarily.",
"expected_output": "A verification sequence using existing project scripts, then build, with Playwright reserved for browser-level flows and failures reported exactly.",
"assertions": ["Inspects package scripts and existing conventions first", "Runs focused checks before the production build", "Routes browser E2E work to playwright rather than inventing ad hoc automation"]
}
]
}
+54
View File
@@ -0,0 +1,54 @@
# React Component And State Patterns
## Start with ownership
Describe each value as one of local UI state, server/cache state, URL state, or
cross-cutting application state. Keep state at the lowest common owner. If a
value is derived from other values, compute it during render or in a memoized
calculation when measurement proves the calculation costly; do not create a
second source of truth.
A practical component boundary usually owns one interaction or visual contract.
Split when a component has unrelated state machines, repeated markup, or an
API that requires consumers to understand implementation details. Keep domain
transformations outside presentational components when they can be tested
without a browser.
## Effects and asynchronous work
Before adding `useEffect`, name the external system it synchronizes with:
network, subscription, timer, browser API, or imperative widget. If none exists,
prefer render derivation or an event handler. Every effect should have a cleanup
when it creates a subscription, timer, listener, or request that can outlive the
render.
For a request keyed by an input, use an abort signal or an active-request guard,
handle abort as non-error cancellation, and ensure a late response cannot replace
newer data. Model `status` explicitly (`idle`, `pending`, `success`, `error`) and
render all meaningful states. Avoid catching an error only to log it and leave a
permanently pending screen.
## Interaction contracts
Use controlled inputs when validation, submission, or external reset is part of
the feature; otherwise an uncontrolled input with a ref may be simpler. Keep
submit handlers idempotent, disable or guard while pending, and preserve the
user's entered data on recoverable errors. Announce validation and server errors
through the accessible structure, not only a color or toast.
For lists, key rows with stable domain identity. If a row has local state, an
index key can transfer that state to another record after sorting or deletion.
Use functional updates for transitions based on prior state, especially when
multiple events may batch.
## Verification checklist
- Hooks are unconditional and dependencies reflect values read from the effect.
- No derived state or duplicated server state is introduced without a reason.
- Loading, empty, error, retry, and success states are represented where relevant.
- Async cleanup prevents stale writes and treats cancellation intentionally.
- Buttons and links use native semantics; keyboard and focus behavior is tested.
- Component tests cover user-visible behavior; browser flows are delegated to
[playwright](../../playwright/SKILL.md).
- Dedicated semantic and WCAG review is delegated to
[web-accessibility](../../web-accessibility/SKILL.md).
+51
View File
@@ -0,0 +1,51 @@
# Vite Diagnostics And Release Checks
## Environment values
Vite substitutes client-exposed variables at build time. Only variables with the
configured public prefix (commonly `VITE_`) should be read by browser code.
Treat every such value as public: it is not a secret merely because it lives in
`.env`. Keep credentials and server-only configuration outside the client
bundle. Check `.env.example`, Vite config, deployment configuration, and the
actual built assets for accidental exposure.
Vite loads mode-specific files with a defined precedence. Confirm the intended
mode (`development`, `production`, or a custom mode) and do not assume a local
`.env` matches CI. When diagnosing a value, inspect its name and source without
printing its value. Re-run the build after changing env configuration because
substitution is compile-time.
## Build and asset paths
Inspect `base` in `vite.config.*` when the app is served below `/`. A wrong base
usually appears as 404s for module, CSS, or asset URLs after deployment even
though the root-local dev server works. Validate the generated HTML and asset
references against the real deployment path. For SPA history fallback, confirm
the host serves the app entry point for non-root routes; Vite does not configure
that server rule for every deployment target.
## Dependency and output checks
Use the project's package manager lockfile and scripts. Check that `react` and
`react-dom` versions are compatible and that duplicate React copies are not
being pulled into the bundle, which can produce invalid hook call errors. Do not
blindly delete lockfiles or upgrade dependencies while diagnosing.
For release verification, run the existing typecheck/lint/test commands before
`vite build`, inspect warnings, and use a preview server for a smoke check at
the deployed base path. Keep source maps and reports out of user-facing output
unless the project intentionally publishes them.
## Safe diagnostic sequence
1. Record the package manager and available scripts from `package.json`.
2. Identify the active mode and public-prefix configuration without exposing
values.
3. Inspect `base`, route fallback, and generated asset URLs.
4. Check lockfile consistency and React package version alignment.
5. Run the narrowest reproducible check, then the production build.
6. Confirm the browser flow with [playwright](../../playwright/SKILL.md) when
route, asset, or navigation behavior is involved.
7. Ask [frontend-engineering](../../frontend-engineering/SKILL.md) for broader
performance/component strategy and [web-accessibility](../../web-accessibility/SKILL.md)
for a dedicated accessibility audit.
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Bounded, read-only diagnostics for React/Vite projects."""
import argparse
import json
import re
import sys
from pathlib import Path
MAX_BYTES = 512 * 1024
TEXT_FILES = ("package.json", "vite.config.js", "vite.config.ts", "vite.config.mjs", "vite.config.cjs", "tsconfig.json")
def read_text(root, name):
path = root / name
try:
if not path.is_file() or path.stat().st_size > MAX_BYTES:
return None
return path.read_text(encoding="utf-8")
except (OSError, UnicodeError):
return None
def diagnose(root):
result = {"project": str(root), "checks": [], "warnings": []}
package_text = read_text(root, "package.json")
package = None
if package_text is None:
result["warnings"].append("package.json is missing or exceeds the read limit")
else:
try:
package = json.loads(package_text)
if not isinstance(package, dict):
raise ValueError("not an object")
deps = {**package.get("dependencies", {}), **package.get("devDependencies", {})}
result["checks"].append({"name": "package-json", "status": "ok"})
result["checks"].append({"name": "react-dependencies", "status": "ok" if "react" in deps and "react-dom" in deps else "warning"})
except (ValueError, TypeError, json.JSONDecodeError):
result["warnings"].append("package.json is not valid JSON")
config_name = next((name for name in TEXT_FILES[1:5] if read_text(root, name) is not None), None)
result["checks"].append({"name": "vite-config", "status": "ok" if config_name else "info", "file": config_name})
source_files = []
for directory in (root / "src", root / "app"):
if directory.is_dir():
source_files.extend(p for p in directory.rglob("*") if p.is_file() and p.suffix in {".jsx", ".tsx", ".js", ".ts"})
result["checks"].append({"name": "source-entry", "status": "ok" if source_files else "warning", "file_count": len(source_files)})
env_names = set()
for path in root.glob(".env*"):
text = read_text(root, path.name) or ""
env_names.update(re.findall(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=", text, re.MULTILINE))
result["checks"].append({"name": "public-env-names", "status": "ok", "names": sorted(n for n in env_names if n.startswith("VITE_"))})
if any(not n.startswith("VITE_") for n in env_names):
result["warnings"].append(".env files contain non-public names; keep them server-side and never import secrets into client code")
lockfiles = [name for name in ("package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock") if (root / name).is_file()]
result["checks"].append({"name": "lockfile", "status": "ok" if lockfiles else "warning", "files": lockfiles})
return result
def main(argv=None):
parser = argparse.ArgumentParser(description="Read-only React/Vite project diagnostics")
parser.add_argument("project", nargs="?", default=".")
parser.add_argument("--json", action="store_true", dest="as_json")
args = parser.parse_args(argv)
root = Path(args.project).expanduser().resolve()
if not root.is_dir():
print("ERROR: project directory does not exist", file=sys.stderr)
return 2
result = diagnose(root)
if args.as_json:
print(json.dumps(result, sort_keys=True))
else:
print("React/Vite doctor: " + result["project"])
for check in result["checks"]:
print("- {name}: {status}".format(**check))
for warning in result["warnings"]:
print("WARNING: " + warning)
return 0
if __name__ == "__main__":
sys.exit(main())
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Offline tests for the read-only React doctor."""
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT = Path(__file__).with_name("react-doctor.py")
class DoctorTests(unittest.TestCase):
def run_doctor(self, root, *args):
return subprocess.run([sys.executable, str(SCRIPT), *args, str(root)], capture_output=True, text=True)
def test_json_reports_react_vite_signals_without_values(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "package.json").write_text(json.dumps({"dependencies": {"react": "18", "react-dom": "18"}}), encoding="utf-8")
(root / "vite.config.ts").write_text("export default {}", encoding="utf-8")
(root / "src").mkdir()
(root / "src/App.tsx").write_text("export default function App() {}", encoding="utf-8")
(root / ".env.local").write_text("VITE_PUBLIC=visible\nSECRET=do-not-print\n", encoding="utf-8")
output = self.run_doctor(root, "--json")
self.assertEqual(output.returncode, 0)
report = json.loads(output.stdout)
self.assertEqual(report["checks"][0]["status"], "ok")
env_check = next(check for check in report["checks"] if check["name"] == "public-env-names")
self.assertIn("VITE_PUBLIC", env_check["names"])
self.assertNotIn("do-not-print", output.stdout)
def test_missing_project_is_usage_error(self):
output = self.run_doctor(Path("/definitely/not/a/project"))
self.assertEqual(output.returncode, 2)
self.assertIn("does not exist", output.stderr)
def test_malformed_package_is_reported(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "package.json").write_text("{broken", encoding="utf-8")
output = self.run_doctor(root, "--json")
self.assertEqual(output.returncode, 0)
self.assertTrue(any("not valid JSON" in warning for warning in json.loads(output.stdout)["warnings"]))
def test_help_is_available(self):
output = subprocess.run([sys.executable, str(SCRIPT), "--help"], capture_output=True, text=True)
self.assertEqual(output.returncode, 0)
self.assertIn("read-only", output.stdout.lower())
if __name__ == "__main__":
unittest.main()