mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-18 15:06:28 +03:00
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:
@@ -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
@@ -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.
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
Executable
+76
@@ -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())
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user