@coralai/sps-cli 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -3
- package/dist/commands/projectInit.d.ts.map +1 -1
- package/dist/commands/projectInit.js +40 -53
- package/dist/commands/projectInit.js.map +1 -1
- package/dist/commands/skillCommand.d.ts +2 -0
- package/dist/commands/skillCommand.d.ts.map +1 -0
- package/dist/commands/skillCommand.js +235 -0
- package/dist/commands/skillCommand.js.map +1 -0
- package/dist/core/skillStore.d.ts +46 -0
- package/dist/core/skillStore.d.ts.map +1 -0
- package/dist/core/skillStore.js +197 -0
- package/dist/core/skillStore.js.map +1 -0
- package/dist/core/skillStore.test.d.ts +2 -0
- package/dist/core/skillStore.test.d.ts.map +1 -0
- package/dist/core/skillStore.test.js +190 -0
- package/dist/core/skillStore.test.js.map +1 -0
- package/dist/main.js +19 -17
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/skills/architecture-decision-records/SKILL.md +207 -0
- package/skills/backend/SKILL.md +62 -0
- package/skills/backend/references/api-design.md +168 -0
- package/skills/backend/references/caching.md +181 -0
- package/skills/backend/references/data-access.md +173 -0
- package/skills/backend/references/layering.md +181 -0
- package/skills/backend/references/observability.md +190 -0
- package/skills/backend/references/resilience.md +201 -0
- package/skills/backend/references/security.md +186 -0
- package/skills/backend-architect/SKILL.md +119 -0
- package/skills/code-reviewer/SKILL.md +143 -0
- package/skills/coding-standards/SKILL.md +60 -0
- package/skills/coding-standards/references/clean-code.md +258 -0
- package/skills/coding-standards/references/code-review.md +192 -0
- package/skills/coding-standards/references/commits-and-prs.md +226 -0
- package/skills/coding-standards/references/error-strategy.md +193 -0
- package/skills/coding-standards/references/naming.md +185 -0
- package/skills/coding-standards/references/tdd.md +171 -0
- package/skills/database/SKILL.md +53 -0
- package/skills/database/references/indexing.md +190 -0
- package/skills/database/references/migrations.md +199 -0
- package/skills/database/references/nosql.md +185 -0
- package/skills/database/references/queries.md +295 -0
- package/skills/database/references/scaling.md +203 -0
- package/skills/database/references/schema.md +191 -0
- package/skills/database-optimizer/SKILL.md +168 -0
- package/skills/debugging-workflow/SKILL.md +244 -0
- package/skills/devops/SKILL.md +55 -0
- package/skills/devops/references/ci-cd.md +204 -0
- package/skills/devops/references/containers.md +272 -0
- package/skills/devops/references/deploy.md +201 -0
- package/skills/devops/references/iac.md +252 -0
- package/skills/devops/references/observability.md +228 -0
- package/skills/devops/references/secrets.md +178 -0
- package/skills/devops-automator/SKILL.md +164 -0
- package/skills/frontend/SKILL.md +52 -0
- package/skills/frontend/references/accessibility.md +222 -0
- package/skills/frontend/references/components.md +206 -0
- package/skills/frontend/references/performance.md +219 -0
- package/skills/frontend/references/routing.md +209 -0
- package/skills/frontend/references/state.md +190 -0
- package/skills/frontend/references/testing.md +216 -0
- package/skills/frontend-developer/SKILL.md +115 -0
- package/skills/git-workflow/SKILL.md +355 -0
- package/skills/golang/SKILL.md +49 -0
- package/skills/golang/references/concurrency.md +284 -0
- package/skills/golang/references/errors.md +241 -0
- package/skills/golang/references/idioms.md +285 -0
- package/skills/golang/references/testing.md +238 -0
- package/skills/java/SKILL.md +50 -0
- package/skills/java/references/concurrency.md +194 -0
- package/skills/java/references/idioms.md +283 -0
- package/skills/java/references/testing.md +228 -0
- package/skills/kotlin/SKILL.md +47 -0
- package/skills/kotlin/references/coroutines.md +240 -0
- package/skills/kotlin/references/idioms.md +268 -0
- package/skills/kotlin/references/testing.md +219 -0
- package/skills/mobile/SKILL.md +50 -0
- package/skills/mobile/references/architecture.md +204 -0
- package/skills/mobile/references/navigation.md +158 -0
- package/skills/mobile/references/performance.md +152 -0
- package/skills/mobile/references/platform.md +166 -0
- package/skills/mobile/references/state-and-data.md +174 -0
- package/skills/python/SKILL.md +51 -0
- package/skills/python/THIRD_PARTY.md +14 -0
- package/skills/python/references/async.md +218 -0
- package/skills/python/references/error-handling.md +254 -0
- package/skills/python/references/idioms.md +279 -0
- package/skills/python/references/packaging.md +233 -0
- package/skills/python/references/testing.md +269 -0
- package/skills/python/references/typing.md +292 -0
- package/skills/qa-tester/SKILL.md +186 -0
- package/skills/rust/SKILL.md +50 -0
- package/skills/rust/references/async.md +224 -0
- package/skills/rust/references/errors.md +240 -0
- package/skills/rust/references/ownership.md +263 -0
- package/skills/rust/references/testing.md +274 -0
- package/skills/rust/references/traits.md +250 -0
- package/skills/security-engineer/SKILL.md +157 -0
- package/skills/swift/SKILL.md +48 -0
- package/skills/swift/references/concurrency.md +280 -0
- package/skills/swift/references/idioms.md +334 -0
- package/skills/swift/references/testing.md +229 -0
- package/skills/typescript/SKILL.md +51 -0
- package/skills/typescript/references/async.md +241 -0
- package/skills/typescript/references/errors.md +208 -0
- package/skills/typescript/references/idioms.md +246 -0
- package/skills/typescript/references/testing.md +225 -0
- package/skills/typescript/references/tooling.md +208 -0
- package/skills/typescript/references/types.md +259 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devops-automator
|
|
3
|
+
description: Persona skill — automate everything repeatable, measure everything deployable, rehearse every rollback. Overlay on top of `devops`. For the patterns, load `devops`.
|
|
4
|
+
origin: agency-agents-fork + original (https://github.com/msitarzewski/agency-agents, MIT)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DevOps Automator
|
|
8
|
+
|
|
9
|
+
Think in pipelines, not heroics. Every manual step is a future incident.
|
|
10
|
+
|
|
11
|
+
## When to load
|
|
12
|
+
|
|
13
|
+
- Setting up / reviewing CI/CD
|
|
14
|
+
- Building a new deploy topology
|
|
15
|
+
- Writing a runbook
|
|
16
|
+
- Triaging a deploy or infra issue
|
|
17
|
+
- Deciding "should this be a manual step or automated?"
|
|
18
|
+
|
|
19
|
+
## The posture
|
|
20
|
+
|
|
21
|
+
1. **Automate the boring.** Manual steps are breeding grounds for incidents. If it's done twice, it's a script.
|
|
22
|
+
2. **Same artifact dev → prod.** Build once; promote. Building per-environment hides bugs.
|
|
23
|
+
3. **Rollback is a command, not a procedure.** If rollback takes a checklist, the checklist is the bug.
|
|
24
|
+
4. **Observability is pre-launch.** Dashboards + alerts + runbooks exist before the first deploy.
|
|
25
|
+
5. **Plan for the 3am self.** Will you remember at 3am, with dogs barking? Write it down.
|
|
26
|
+
6. **Infrastructure is code.** Clicks in consoles are for exploration, not for prod.
|
|
27
|
+
7. **Least privilege, always.** CI keys, service accounts, human access — scoped.
|
|
28
|
+
|
|
29
|
+
## The questions you always ask
|
|
30
|
+
|
|
31
|
+
- **What happens if this step fails partway?** Idempotent? Rerunnable?
|
|
32
|
+
- **What does rollback look like?** One command. Tested.
|
|
33
|
+
- **What signals say "go ahead" to promote?** Metrics + alerts, not vibes.
|
|
34
|
+
- **Who sees this alert, and what do they do?** Runbook.
|
|
35
|
+
- **What's the blast radius of a bad deploy?** Pod / service / cluster / region?
|
|
36
|
+
- **How do we know what's running in prod right now?** SHA / tag should be a keystroke away.
|
|
37
|
+
- **Who has access to prod?** Named humans, not shared creds.
|
|
38
|
+
- **Is this reproducible on a fresh environment?** Disaster recovery test.
|
|
39
|
+
|
|
40
|
+
## The checklist — shipping a new service
|
|
41
|
+
|
|
42
|
+
### CI / CD
|
|
43
|
+
- [ ] Lint, typecheck, unit tests, integration tests in pipeline.
|
|
44
|
+
- [ ] Cache deps; pipeline < 10 min on typical change.
|
|
45
|
+
- [ ] Build artifact once; immutable tag.
|
|
46
|
+
- [ ] Gate prod on explicit approval.
|
|
47
|
+
- [ ] Pipeline secrets scoped per env, rotated on a schedule.
|
|
48
|
+
- [ ] Dependency scanning + image scanning.
|
|
49
|
+
|
|
50
|
+
### Infra
|
|
51
|
+
- [ ] Every resource in IaC (Terraform / Pulumi / CDK / etc.).
|
|
52
|
+
- [ ] State remote + locked + encrypted.
|
|
53
|
+
- [ ] Env-specific variables, not code forks.
|
|
54
|
+
- [ ] Tagging strategy applied everywhere.
|
|
55
|
+
- [ ] Cost allocation visible.
|
|
56
|
+
|
|
57
|
+
### Deploy
|
|
58
|
+
- [ ] Rolling / canary / blue-green chosen with reason.
|
|
59
|
+
- [ ] Health checks: live vs. ready.
|
|
60
|
+
- [ ] Graceful shutdown on SIGTERM.
|
|
61
|
+
- [ ] Resource limits (CPU, mem, replicas) sized, not defaults.
|
|
62
|
+
- [ ] Feature flags for behaviour-changing releases.
|
|
63
|
+
- [ ] Rollback command documented and tested.
|
|
64
|
+
|
|
65
|
+
### Secrets
|
|
66
|
+
- [ ] All secrets in a secret manager.
|
|
67
|
+
- [ ] Runtime fetch via workload identity, not long-lived keys.
|
|
68
|
+
- [ ] Rotation schedule defined.
|
|
69
|
+
- [ ] Pre-commit / CI scanning for leaked secrets.
|
|
70
|
+
|
|
71
|
+
### Observability
|
|
72
|
+
- [ ] Structured logs to stdout.
|
|
73
|
+
- [ ] Four golden signals exposed.
|
|
74
|
+
- [ ] Traces propagated through service boundaries.
|
|
75
|
+
- [ ] Dashboards on first-day launch.
|
|
76
|
+
- [ ] Alerts on symptoms (user impact), not infrastructure noise.
|
|
77
|
+
- [ ] Runbook linked from every alert.
|
|
78
|
+
|
|
79
|
+
### Backup & DR
|
|
80
|
+
- [ ] Automated backups + tested restore.
|
|
81
|
+
- [ ] Cross-region replication if applicable.
|
|
82
|
+
- [ ] Recovery time objective (RTO) and recovery point objective (RPO) documented.
|
|
83
|
+
- [ ] Disaster recovery drill at least yearly.
|
|
84
|
+
|
|
85
|
+
## What you push back on
|
|
86
|
+
|
|
87
|
+
- **"Just SSH in and run this"** as a solution. That's a bug you haven't fixed.
|
|
88
|
+
- **Secrets in env vars set by hand.** Use the secret manager.
|
|
89
|
+
- **Deploys without metrics backing "looks fine"** at the end.
|
|
90
|
+
- **Manual gates that could be automated.** Automated + logged is more trustworthy than "Alice always checks".
|
|
91
|
+
- **Latest-tag deploys.** Never rollback-able.
|
|
92
|
+
- **Dashboards that nobody watches.** Prune.
|
|
93
|
+
- **Alerts that fire and "auto-resolve".** Either it's actionable or it's noise.
|
|
94
|
+
|
|
95
|
+
## Tradeoffs you name
|
|
96
|
+
|
|
97
|
+
- **Deploy speed vs. safety gates.** Small deploys can be fast; big ones need gates.
|
|
98
|
+
- **Monolith vs. microservices ops cost.** Each service is its own pipeline, dashboard, alert set.
|
|
99
|
+
- **Own vs. managed.** Self-hosted is cheaper until you count the oncall load.
|
|
100
|
+
- **Auto-healing vs. alert-first.** Orchestrator restarts hide problems. Strike a balance.
|
|
101
|
+
|
|
102
|
+
## Standard runbook skeleton
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
# Runbook: <alert name>
|
|
106
|
+
|
|
107
|
+
## What this means
|
|
108
|
+
One sentence. Who is affected, what's failing.
|
|
109
|
+
|
|
110
|
+
## Immediate checks
|
|
111
|
+
1. Dashboard link.
|
|
112
|
+
2. Recent deploys link.
|
|
113
|
+
3. Dependency status page.
|
|
114
|
+
|
|
115
|
+
## Common causes
|
|
116
|
+
- Cause A → check X.
|
|
117
|
+
- Cause B → check Y.
|
|
118
|
+
|
|
119
|
+
## Mitigation (in order)
|
|
120
|
+
1. Fast: roll back if recent deploy.
|
|
121
|
+
2. Medium: scale up / restart pool.
|
|
122
|
+
3. Slow: escalate to service owner.
|
|
123
|
+
|
|
124
|
+
## Escalation
|
|
125
|
+
- Primary: @service-owner
|
|
126
|
+
- Secondary: @infra
|
|
127
|
+
- SLA: respond within 15 min
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Every alert has one. Update as you learn.
|
|
131
|
+
|
|
132
|
+
## What a good postmortem looks like
|
|
133
|
+
|
|
134
|
+
Blameless. Focused on the system.
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Summary: One paragraph.
|
|
138
|
+
Impact: Users affected × time × severity.
|
|
139
|
+
Timeline: Minute-by-minute from detection to resolution.
|
|
140
|
+
Root cause: Technical (what broke) AND process (why we didn't catch it).
|
|
141
|
+
Action items: Specific, owned, dated. Tracked.
|
|
142
|
+
Lessons: What surprised us.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Track action items to completion. The same incident should never happen twice for the same reason.
|
|
146
|
+
|
|
147
|
+
## Forbidden patterns
|
|
148
|
+
|
|
149
|
+
- Prod secrets on a developer laptop
|
|
150
|
+
- Deploy requires "call John"
|
|
151
|
+
- Untagged, unversioned images in prod
|
|
152
|
+
- "It worked in staging" without staging mirroring prod
|
|
153
|
+
- Alerts without runbooks
|
|
154
|
+
- IaC changes that were really cloud-console changes
|
|
155
|
+
- Long-lived API keys / tokens
|
|
156
|
+
- Pipelines that mask real test failures with `|| true`
|
|
157
|
+
- Single point of failure: one person knows how to deploy
|
|
158
|
+
- Deploys without a recorded event (can't correlate with incidents)
|
|
159
|
+
|
|
160
|
+
## Pair with
|
|
161
|
+
|
|
162
|
+
- [`devops`](../devops/SKILL.md) — the patterns and recipes.
|
|
163
|
+
- [`backend/references/observability.md`](../backend/references/observability.md) — app-level signal definitions.
|
|
164
|
+
- [`coding-standards`](../coding-standards/SKILL.md) — how the pipelines enforce quality.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend
|
|
3
|
+
description: Frontend end skill — components, state, routing, performance, accessibility, testing. Language-neutral (React / Vue / Svelte / SolidJS patterns described conceptually). Pair with a language skill and `coding-standards`.
|
|
4
|
+
origin: ecc-fork + original (https://github.com/affaan-m/everything-claude-code, MIT)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend
|
|
8
|
+
|
|
9
|
+
Client-side architecture. **Framework-agnostic by design** — examples use pseudocode or a generic component model. Pair with a language skill (`typescript`) and a framework choice.
|
|
10
|
+
|
|
11
|
+
## When to load
|
|
12
|
+
|
|
13
|
+
- Building or reviewing UI in the browser (web / PWA / Electron)
|
|
14
|
+
- State management: local, server, global
|
|
15
|
+
- Routing, navigation, deep linking
|
|
16
|
+
- Rendering performance, bundle size
|
|
17
|
+
- Accessibility, i18n, SEO
|
|
18
|
+
- Frontend testing (component, E2E)
|
|
19
|
+
|
|
20
|
+
## Core principles
|
|
21
|
+
|
|
22
|
+
1. **State is the hard part. Components are easy.** Get state shape right and most code writes itself.
|
|
23
|
+
2. **Server state ≠ client state.** Use a dedicated tool (TanStack Query, SWR, Apollo) for server state; don't cram it into your global store.
|
|
24
|
+
3. **Derive, don't duplicate.** If two pieces of state must stay in sync, at least one is derived.
|
|
25
|
+
4. **Optimistic UI for responsiveness, reconciliation for correctness.** Show the expected result immediately; update on server response.
|
|
26
|
+
5. **Measure before optimizing.** Bundle analyzer, Lighthouse, React Profiler / equivalent.
|
|
27
|
+
6. **Accessibility is not optional.** Keyboard navigation, semantic HTML, color contrast, screen-reader labels.
|
|
28
|
+
7. **Keep components dumb.** Props in, events out. Move side effects to hooks / composables / stores.
|
|
29
|
+
|
|
30
|
+
## How to use references
|
|
31
|
+
|
|
32
|
+
| Reference | When to load |
|
|
33
|
+
|---|---|
|
|
34
|
+
| [`references/components.md`](references/components.md) | Component shape, props, composition, slots / children, separation of concerns |
|
|
35
|
+
| [`references/state.md`](references/state.md) | Local vs. shared, server state, derived, stores, forms |
|
|
36
|
+
| [`references/routing.md`](references/routing.md) | Client routing, navigation, deep linking, query params, guards |
|
|
37
|
+
| [`references/performance.md`](references/performance.md) | Rendering cost, code-splitting, virtualization, images, fonts |
|
|
38
|
+
| [`references/accessibility.md`](references/accessibility.md) | Semantic HTML, keyboard, ARIA, focus management, contrast |
|
|
39
|
+
| [`references/testing.md`](references/testing.md) | Component tests, snapshot tests, E2E (Playwright), visual regression |
|
|
40
|
+
|
|
41
|
+
## Forbidden patterns (auto-reject)
|
|
42
|
+
|
|
43
|
+
- Business logic inside presentational components
|
|
44
|
+
- Global mutable state for server data (keep server data in a cache, not in Redux/Zustand directly)
|
|
45
|
+
- Fetching in a loop of unrelated components (N+1 on the client)
|
|
46
|
+
- Mutating props
|
|
47
|
+
- `any` / untyped props (in typed frontends)
|
|
48
|
+
- Accessibility via `role="button"` on a `<div>` when `<button>` works
|
|
49
|
+
- Inline styles driven by state when a class + CSS toggle would do
|
|
50
|
+
- `dangerouslySetInnerHTML` / `v-html` without a sanitizer
|
|
51
|
+
- `useEffect` / `watchEffect` for derivations — use computed values
|
|
52
|
+
- Animation / loading state toggled by timeouts instead of by actual completion events
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Semantic HTML, keyboard, ARIA, focus, contrast. Not optional.
|
|
4
|
+
|
|
5
|
+
## Who benefits
|
|
6
|
+
|
|
7
|
+
- 15–20% of users have a disability (motor, visual, cognitive, temporary).
|
|
8
|
+
- Everyone benefits from good a11y: keyboard shortcuts, screen readers, screen-reader-like voice assistants, low-light contrast, slow networks.
|
|
9
|
+
|
|
10
|
+
Treat it as a correctness requirement, not a "nice to have".
|
|
11
|
+
|
|
12
|
+
## Semantic HTML first
|
|
13
|
+
|
|
14
|
+
Every control you use should be the right element for the job.
|
|
15
|
+
|
|
16
|
+
| Use | For |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `<button>` | Action |
|
|
19
|
+
| `<a href>` | Navigation |
|
|
20
|
+
| `<form>` | Group inputs, submit behaviour |
|
|
21
|
+
| `<input type="checkbox">` / `<input type="radio">` | Single / exclusive choice |
|
|
22
|
+
| `<select>` / `<option>` | Enumerated choice from list |
|
|
23
|
+
| `<label>` | Associate text with an input |
|
|
24
|
+
| `<fieldset>` / `<legend>` | Group related inputs |
|
|
25
|
+
| `<nav>`, `<main>`, `<aside>`, `<header>`, `<footer>` | Landmarks |
|
|
26
|
+
| `<h1>` … `<h6>` | Document outline |
|
|
27
|
+
|
|
28
|
+
**Rule**: if the right element exists, use it. `<div onClick>` with `role="button"` is twice the work and half the behaviour.
|
|
29
|
+
|
|
30
|
+
## Keyboard navigation
|
|
31
|
+
|
|
32
|
+
Every interactive control must be operable with keyboard only:
|
|
33
|
+
|
|
34
|
+
- **Tab** / **Shift+Tab** — move focus.
|
|
35
|
+
- **Enter** / **Space** — activate (varies: buttons on both, links on Enter).
|
|
36
|
+
- **Arrows** — move within composite widgets (menus, tablists, listboxes, grids).
|
|
37
|
+
- **Escape** — dismiss modal, popover, typeahead.
|
|
38
|
+
|
|
39
|
+
Test periodically: unplug the mouse.
|
|
40
|
+
|
|
41
|
+
### Visible focus
|
|
42
|
+
|
|
43
|
+
Don't remove the focus outline.
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
/* ❌ */
|
|
47
|
+
*:focus { outline: none; }
|
|
48
|
+
|
|
49
|
+
/* ✅ style it, don't remove it */
|
|
50
|
+
:focus-visible {
|
|
51
|
+
outline: 2px solid var(--color-focus);
|
|
52
|
+
outline-offset: 2px;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`:focus-visible` shows on keyboard focus but not mouse click — best of both.
|
|
57
|
+
|
|
58
|
+
### Focus management
|
|
59
|
+
|
|
60
|
+
Modal opens → move focus into the modal. Modal closes → return focus to the trigger.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
function Modal() {
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const prev = document.activeElement;
|
|
66
|
+
firstInput.focus();
|
|
67
|
+
return () => prev.focus(); // restore on unmount
|
|
68
|
+
}, []);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Route changes → announce and move focus to the new page's `<h1>` or main landmark (framework routers handle some of this; verify).
|
|
73
|
+
|
|
74
|
+
## ARIA — the rule
|
|
75
|
+
|
|
76
|
+
First rule of ARIA: **don't use ARIA.** Use semantic HTML. Every ARIA attribute is a hand-maintained contract with assistive tech; native elements get it for free.
|
|
77
|
+
|
|
78
|
+
When you must use ARIA (custom widgets), follow the [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) patterns — they are the source of truth.
|
|
79
|
+
|
|
80
|
+
Common attributes:
|
|
81
|
+
|
|
82
|
+
| Attribute | Meaning |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `aria-label` | Override accessible name (use sparingly) |
|
|
85
|
+
| `aria-labelledby` | Name by reference to another element |
|
|
86
|
+
| `aria-describedby` | Extra hint / help text |
|
|
87
|
+
| `aria-hidden="true"` | Hide purely decorative element from AT |
|
|
88
|
+
| `aria-live="polite"` / `"assertive"` | Announce changes |
|
|
89
|
+
| `aria-expanded` | Disclosure state on buttons |
|
|
90
|
+
| `aria-controls` | Associates a control with the thing it controls |
|
|
91
|
+
| `aria-current` | The active item in a set (e.g. nav) |
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
- Don't set `aria-hidden` on a focusable element — screen readers skip, but keyboard doesn't.
|
|
95
|
+
- Don't name duplicates: `<button aria-label="Close">Close</button>` — pick one.
|
|
96
|
+
|
|
97
|
+
## Labels and descriptions
|
|
98
|
+
|
|
99
|
+
Every form control has a label. Either:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
<label for="email">Email</label>
|
|
103
|
+
<input id="email" name="email">
|
|
104
|
+
|
|
105
|
+
<!-- or wrap -->
|
|
106
|
+
<label>
|
|
107
|
+
Email
|
|
108
|
+
<input name="email">
|
|
109
|
+
</label>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Placeholder is NOT a label. It disappears on focus and has poor contrast.
|
|
113
|
+
|
|
114
|
+
For help text / errors:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
<label for="pw">Password</label>
|
|
118
|
+
<input id="pw" type="password" aria-describedby="pw-help pw-err">
|
|
119
|
+
<span id="pw-help">At least 12 chars.</span>
|
|
120
|
+
<span id="pw-err" role="alert">Too short.</span>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Color contrast
|
|
124
|
+
|
|
125
|
+
Minimum WCAG AA:
|
|
126
|
+
- Normal text vs. background: **4.5 : 1**
|
|
127
|
+
- Large text (18 pt+ or 14 pt bold+): **3 : 1**
|
|
128
|
+
- UI components (border, icon vs. background): **3 : 1**
|
|
129
|
+
|
|
130
|
+
Don't rely on color alone. "Red = error" + icon + text is accessible; red alone isn't for the color-blind.
|
|
131
|
+
|
|
132
|
+
Tools: Lighthouse flags most contrast issues; for design systems, contrast-aware token generators (radix colors, Open Props).
|
|
133
|
+
|
|
134
|
+
## Live regions — announce changes
|
|
135
|
+
|
|
136
|
+
Status updates that aren't caused by the user's last action (notifications, background sync) need an ARIA live region.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
<div aria-live="polite" aria-atomic="true">
|
|
140
|
+
{status}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- For urgent errors -->
|
|
144
|
+
<div role="alert">
|
|
145
|
+
{errorMessage}
|
|
146
|
+
</div>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`polite` queues the announcement; `assertive` / `role="alert"` interrupts. Use `assertive` sparingly.
|
|
150
|
+
|
|
151
|
+
## Images and alt text
|
|
152
|
+
|
|
153
|
+
- Content image: `<img alt="screen reader text">` describing what it shows.
|
|
154
|
+
- Decorative image: `<img alt="">` (empty alt, NOT no alt).
|
|
155
|
+
- SVG icon with text next to it: `<svg aria-hidden="true">`.
|
|
156
|
+
- Standalone SVG icon that means something: provide an accessible name.
|
|
157
|
+
|
|
158
|
+
## Motion and animation
|
|
159
|
+
|
|
160
|
+
- Respect `prefers-reduced-motion`:
|
|
161
|
+
```css
|
|
162
|
+
@media (prefers-reduced-motion: reduce) {
|
|
163
|
+
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
- Don't autoplay videos with audio.
|
|
167
|
+
- Avoid content flashing more than 3 times per second (seizure risk).
|
|
168
|
+
|
|
169
|
+
## Screen reader testing
|
|
170
|
+
|
|
171
|
+
Two minutes in a screen reader beats an hour of reading spec.
|
|
172
|
+
|
|
173
|
+
- macOS: **VoiceOver** (Cmd+F5).
|
|
174
|
+
- Windows: **NVDA** (free) or **JAWS**.
|
|
175
|
+
- iOS: **VoiceOver**. Android: **TalkBack**.
|
|
176
|
+
|
|
177
|
+
Walk through key flows. If you can't complete a task with only voice output, neither can your users.
|
|
178
|
+
|
|
179
|
+
## Automated testing
|
|
180
|
+
|
|
181
|
+
Tools that catch a lot cheaply:
|
|
182
|
+
|
|
183
|
+
- **axe-core** (or `@axe-core/react`, `vitest-axe`) — unit-test with it.
|
|
184
|
+
- **Lighthouse a11y score** — smoke test in CI.
|
|
185
|
+
- **Playwright / Cypress + axe** — E2E a11y checks.
|
|
186
|
+
- **ESLint `jsx-a11y`** — catches basics at write time.
|
|
187
|
+
|
|
188
|
+
Automated tools find roughly 30% of issues. The rest needs a human.
|
|
189
|
+
|
|
190
|
+
## Common patterns — the right way
|
|
191
|
+
|
|
192
|
+
| Widget | Key rules |
|
|
193
|
+
|---|---|
|
|
194
|
+
| Modal | Trap focus, restore on close, `aria-modal`, visible backdrop |
|
|
195
|
+
| Menu | Button with `aria-expanded`, arrow-key navigation, Escape to close |
|
|
196
|
+
| Tabs | Arrow keys move focus; Tab exits; `aria-selected` on active |
|
|
197
|
+
| Combobox | WAI-ARIA APG pattern — complex; use a library |
|
|
198
|
+
| Tooltip | `aria-describedby`, hover + focus triggers, escape to dismiss |
|
|
199
|
+
| Toast | `role="status"` or `aria-live="polite"`, don't auto-dismiss critical messages |
|
|
200
|
+
| Accordion | Button with `aria-expanded`, content with `role="region"` |
|
|
201
|
+
|
|
202
|
+
## i18n hooks
|
|
203
|
+
|
|
204
|
+
Accessibility overlaps with i18n:
|
|
205
|
+
- `lang` attribute on `<html>` and on sections that differ.
|
|
206
|
+
- `dir="rtl"` / `dir="ltr"` for text direction.
|
|
207
|
+
- Locale-aware number / date formatting.
|
|
208
|
+
|
|
209
|
+
## Anti-patterns
|
|
210
|
+
|
|
211
|
+
| Anti-pattern | Fix |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `<div onClick>` acting as a button | `<button>` |
|
|
214
|
+
| `outline: none` with no replacement | Style `:focus-visible` |
|
|
215
|
+
| Placeholder-as-label | Visible `<label>` |
|
|
216
|
+
| Tab order that skips around visually | Match DOM order to visual order |
|
|
217
|
+
| Modal without focus trap | Library (`focus-trap-react`) or correct hand-rolled |
|
|
218
|
+
| Color-only error state | Add icon + text |
|
|
219
|
+
| Auto-focusing unexpected things | Focus jumps break the user's place |
|
|
220
|
+
| `aria-hidden` on a focusable control | Either hide visually too, or remove aria-hidden |
|
|
221
|
+
| Missing `lang` | Set on `<html>` |
|
|
222
|
+
| "Click here" link text | Descriptive link text: "Read the pricing guide" |
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
Shape, composition, props, children / slots.
|
|
4
|
+
|
|
5
|
+
## One responsibility per component
|
|
6
|
+
|
|
7
|
+
A component that renders a user profile should not fetch the user. A component that fetches the user shouldn't render it.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<UserProfile data={user} onEdit={handleEdit} /> # presentational
|
|
11
|
+
<UserProfileContainer userId={id} /> # loads + renders
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This split (presentational / container, or dumb / smart) keeps each piece testable in isolation. The container deals with data; the presentational piece with pixels.
|
|
15
|
+
|
|
16
|
+
## Props in, events out
|
|
17
|
+
|
|
18
|
+
Data flows down through props. Changes flow up through callbacks / emitted events.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
<Button
|
|
22
|
+
label="Save"
|
|
23
|
+
disabled={!form.valid}
|
|
24
|
+
onClick={() => submit()}
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Avoid:
|
|
29
|
+
- Parents reaching into children via refs for non-imperative purposes.
|
|
30
|
+
- Children calling setters on state that belongs to the parent.
|
|
31
|
+
- Two-way binding magic that hides data flow.
|
|
32
|
+
|
|
33
|
+
## Composition over configuration
|
|
34
|
+
|
|
35
|
+
Accept children / slots, don't try to anticipate every variant with boolean props.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
# ❌ exploding configuration
|
|
39
|
+
<Card
|
|
40
|
+
hasHeader
|
|
41
|
+
hasFooter
|
|
42
|
+
headerText="Hi"
|
|
43
|
+
footerAction="Dismiss"
|
|
44
|
+
iconLeft
|
|
45
|
+
iconRight
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
# ✅ composition
|
|
49
|
+
<Card>
|
|
50
|
+
<CardHeader>Hi</CardHeader>
|
|
51
|
+
<CardBody>...</CardBody>
|
|
52
|
+
<CardFooter>
|
|
53
|
+
<Button onClick={dismiss}>Dismiss</Button>
|
|
54
|
+
</CardFooter>
|
|
55
|
+
</Card>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Fewer props, more flexibility. The boundary between "configure a primitive" and "compose primitives" is roughly when boolean props start bumping past 3–4 that interact with each other.
|
|
59
|
+
|
|
60
|
+
## Slots / children
|
|
61
|
+
|
|
62
|
+
Every modern framework has a way to inject arbitrary content into a component:
|
|
63
|
+
|
|
64
|
+
| Framework | Name |
|
|
65
|
+
|---|---|
|
|
66
|
+
| React | `children` (and render props) |
|
|
67
|
+
| Vue | `<slot>` (named + scoped) |
|
|
68
|
+
| Svelte | `<slot>` + named slots |
|
|
69
|
+
| Solid | `children` |
|
|
70
|
+
| Web Components | `<slot>` |
|
|
71
|
+
|
|
72
|
+
Prefer these over passing JSX / VNodes through props. Slots / children are the idiomatic "here's your content".
|
|
73
|
+
|
|
74
|
+
## Smart vs. dumb — the rough divide
|
|
75
|
+
|
|
76
|
+
| Smart (container, hook, composable) | Dumb (presentational) |
|
|
77
|
+
|---|---|
|
|
78
|
+
| Fetches data | Renders what it's given |
|
|
79
|
+
| Owns mutable state | Stateless (or local UI state only) |
|
|
80
|
+
| Wires up events to side effects | Emits events, doesn't handle them |
|
|
81
|
+
| Tests: usually via integration | Tests: snapshot / visual |
|
|
82
|
+
|
|
83
|
+
The boundary is not rigid. Don't create a container for a `<Button>`. But for anything with data or non-trivial behaviour, having the data part extractable is a win.
|
|
84
|
+
|
|
85
|
+
## Prop typing (typed frameworks)
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
type UserCardProps = {
|
|
89
|
+
user: User;
|
|
90
|
+
variant?: 'compact' | 'full';
|
|
91
|
+
onEdit?: (user: User) => void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function UserCard({ user, variant = 'full', onEdit }: UserCardProps) { ... }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Rules:
|
|
98
|
+
- Default values inline at destructure.
|
|
99
|
+
- Optional callbacks are fine; treat them as optional behaviour.
|
|
100
|
+
- Don't type `user: any` — if the shape is unclear, define the type.
|
|
101
|
+
|
|
102
|
+
## Render props vs. hooks / composables
|
|
103
|
+
|
|
104
|
+
Modern frameworks prefer hooks / composables for reusing logic. Render props / slot props are still useful for reusing *markup+logic* bundles (a `<Downshift>` combobox, a headless UI primitive).
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
# Hook — reuse logic
|
|
108
|
+
function useDebouncedSearch(value, ms) {
|
|
109
|
+
const [debounced, setDebounced] = useState(value);
|
|
110
|
+
useEffect(() => { const t = setTimeout(() => setDebounced(value), ms); return () => clearTimeout(t); }, [value, ms]);
|
|
111
|
+
return debounced;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Render prop / slot — reuse logic + markup hooks
|
|
115
|
+
<Combobox items={items}>
|
|
116
|
+
{({ input, menu, option }) => (
|
|
117
|
+
<div>
|
|
118
|
+
<input {...input} />
|
|
119
|
+
<ul {...menu}>{items.map(i => <li {...option(i)}>{i.label}</li>)}</ul>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</Combobox>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Controlled vs. uncontrolled inputs
|
|
126
|
+
|
|
127
|
+
| Controlled | State in the parent; parent dictates the value |
|
|
128
|
+
| Uncontrolled | State in the DOM; parent reads via ref on submit |
|
|
129
|
+
|
|
130
|
+
Use controlled for forms with validation, dynamic enablement, real-time feedback. Use uncontrolled for simple forms where only the submit value matters — less re-render noise.
|
|
131
|
+
|
|
132
|
+
## Forwarded refs — the escape hatch
|
|
133
|
+
|
|
134
|
+
Let a parent attach a ref through a wrapping component to reach an underlying DOM node (focus a text input, scroll into view). Use sparingly — most needs are better solved with props.
|
|
135
|
+
|
|
136
|
+
## Error boundaries
|
|
137
|
+
|
|
138
|
+
Every app root should have a boundary that catches render errors and shows a graceful fallback. Otherwise one bad prop crashes the whole tree to white.
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
<ErrorBoundary fallback={<Error500 />}>
|
|
142
|
+
<App />
|
|
143
|
+
</ErrorBoundary>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Don't wrap every component in its own boundary — a handful of strategic ones (app shell, each route, expensive widgets) is plenty.
|
|
147
|
+
|
|
148
|
+
## Suspense / loading states
|
|
149
|
+
|
|
150
|
+
Where the framework supports it, declarative loading UX beats imperative flags.
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
# Declarative
|
|
154
|
+
<Suspense fallback={<Skeleton />}>
|
|
155
|
+
<UserCard id={id} />
|
|
156
|
+
</Suspense>
|
|
157
|
+
|
|
158
|
+
# Imperative — works too, more verbose
|
|
159
|
+
{loading ? <Skeleton /> : <UserCard user={user} />}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Folder structure — organize by feature
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
src/
|
|
166
|
+
├── features/
|
|
167
|
+
│ ├── user/
|
|
168
|
+
│ │ ├── UserCard.tsx
|
|
169
|
+
│ │ ├── UserCard.test.tsx
|
|
170
|
+
│ │ ├── useUser.ts
|
|
171
|
+
│ │ └── api.ts
|
|
172
|
+
│ └── billing/
|
|
173
|
+
│ └── ...
|
|
174
|
+
├── shared/
|
|
175
|
+
│ ├── components/ # truly shared primitives (Button, Input)
|
|
176
|
+
│ ├── hooks/
|
|
177
|
+
│ └── utils/
|
|
178
|
+
└── app/
|
|
179
|
+
├── routes.tsx
|
|
180
|
+
└── root.tsx
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Organize by feature first, by type (components, hooks, utils) second. Kitchen-sink `components/` folders become dumping grounds.
|
|
184
|
+
|
|
185
|
+
## Styling — pick one, consistent
|
|
186
|
+
|
|
187
|
+
- **CSS modules / scoped styles** — simple, portable.
|
|
188
|
+
- **Utility-first (Tailwind)** — fast to write, mature ecosystem, needs team buy-in.
|
|
189
|
+
- **CSS-in-JS** — component-local; runtime cost varies.
|
|
190
|
+
- **Design tokens** — common layer regardless of above choice (`--color-primary`, `var(...)`).
|
|
191
|
+
|
|
192
|
+
Mixing strategies across the codebase is the antipattern, not the choice itself.
|
|
193
|
+
|
|
194
|
+
## Anti-patterns
|
|
195
|
+
|
|
196
|
+
| Anti-pattern | Fix |
|
|
197
|
+
|---|---|
|
|
198
|
+
| Huge props object passed through many layers | Composition / context / slots |
|
|
199
|
+
| `useEffect` / `watchEffect` to "sync two states" | Derive one from the other |
|
|
200
|
+
| Logic in JSX (`{users.filter(...).map(...).length > 0 && ...}`) | Extract to a named variable or helper |
|
|
201
|
+
| Deep prop drilling | Context / store, or restructure component tree |
|
|
202
|
+
| Rendering a component for each row of thousands | Virtualize (see `performance.md`) |
|
|
203
|
+
| Side effects during render | Side effects belong in effects / handlers |
|
|
204
|
+
| Mutating props or items inside `.map` | Make new objects / arrays |
|
|
205
|
+
| Callback prop recreated on every render causing child re-renders | Memoize or lift |
|
|
206
|
+
| Inline objects / arrays in deps arrays | Memoize or use stable refs |
|