@mmerterden/multi-agent-pipeline 20.35.0 → 20.37.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/CHANGELOG.md +26 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/manifest.json +210 -136
- package/package.json +5 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/lib/qr-code.mjs +26 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/office-pair.mjs +40 -9
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/skills/.skill-manifest.json +69 -65
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +8 -71
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +11 -73
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +75 -219
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- package/pipeline/skills/skills-index.md +2 -1
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -57,186 +57,15 @@ keyboard operability, screen reader support, and sufficient color contrast.
|
|
|
57
57
|
|
|
58
58
|
## ARIA Roles, States, and Properties
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
<!-- Dialog / Modal -->
|
|
62
|
-
<div
|
|
63
|
-
role="dialog"
|
|
64
|
-
aria-modal="true"
|
|
65
|
-
aria-labelledby="dialog-title"
|
|
66
|
-
aria-describedby="dialog-desc"
|
|
67
|
-
>
|
|
68
|
-
<h2 id="dialog-title">Confirm Delete</h2>
|
|
69
|
-
<p id="dialog-desc">This action cannot be undone.</p>
|
|
70
|
-
<button>Cancel</button>
|
|
71
|
-
<button>Delete</button>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<!-- Tabs -->
|
|
75
|
-
<div role="tablist" aria-label="Product info">
|
|
76
|
-
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
|
|
77
|
-
Description
|
|
78
|
-
</button>
|
|
79
|
-
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">
|
|
80
|
-
Reviews
|
|
81
|
-
</button>
|
|
82
|
-
</div>
|
|
83
|
-
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
|
|
84
|
-
<p>Product description...</p>
|
|
85
|
-
</div>
|
|
86
|
-
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
|
|
87
|
-
<p>Product reviews...</p>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<!-- Live region for dynamic content -->
|
|
91
|
-
<div aria-live="polite" aria-atomic="true">
|
|
92
|
-
<!-- Screen reader announces changes here -->
|
|
93
|
-
<p>3 items in your cart</p>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<!-- Status messages -->
|
|
97
|
-
<div role="status" aria-live="polite">
|
|
98
|
-
Form submitted successfully.
|
|
99
|
-
</div>
|
|
100
|
-
<div role="alert" aria-live="assertive">
|
|
101
|
-
Error: Please correct the highlighted fields.
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<!-- Loading state -->
|
|
105
|
-
<div aria-busy="true" aria-live="polite">
|
|
106
|
-
Loading results...
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<!-- Expandable content -->
|
|
110
|
-
<button aria-expanded="false" aria-controls="details-section">
|
|
111
|
-
Show Details
|
|
112
|
-
</button>
|
|
113
|
-
<div id="details-section" hidden>
|
|
114
|
-
<p>Additional information...</p>
|
|
115
|
-
</div>
|
|
116
|
-
```
|
|
60
|
+
Code for this section: [Web accessibility examples](references/a11y-examples.md).
|
|
117
61
|
|
|
118
62
|
## Form Accessibility
|
|
119
63
|
|
|
120
|
-
|
|
121
|
-
<form aria-label="Contact form">
|
|
122
|
-
<!-- Visible label (preferred) -->
|
|
123
|
-
<div>
|
|
124
|
-
<label for="name">Full Name <span aria-hidden="true">*</span></label>
|
|
125
|
-
<input
|
|
126
|
-
type="text"
|
|
127
|
-
id="name"
|
|
128
|
-
name="name"
|
|
129
|
-
required
|
|
130
|
-
aria-required="true"
|
|
131
|
-
autocomplete="name"
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<!-- Error state -->
|
|
136
|
-
<div>
|
|
137
|
-
<label for="email">Email <span aria-hidden="true">*</span></label>
|
|
138
|
-
<input
|
|
139
|
-
type="email"
|
|
140
|
-
id="email"
|
|
141
|
-
name="email"
|
|
142
|
-
required
|
|
143
|
-
aria-required="true"
|
|
144
|
-
aria-invalid="true"
|
|
145
|
-
aria-describedby="email-error email-hint"
|
|
146
|
-
autocomplete="email"
|
|
147
|
-
/>
|
|
148
|
-
<p id="email-hint" class="hint">We'll never share your email.</p>
|
|
149
|
-
<p id="email-error" class="error" role="alert">
|
|
150
|
-
Please enter a valid email address.
|
|
151
|
-
</p>
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
<!-- Fieldset for related inputs -->
|
|
155
|
-
<fieldset>
|
|
156
|
-
<legend>Preferred contact method</legend>
|
|
157
|
-
<label>
|
|
158
|
-
<input type="radio" name="contact" value="email" /> Email
|
|
159
|
-
</label>
|
|
160
|
-
<label>
|
|
161
|
-
<input type="radio" name="contact" value="phone" /> Phone
|
|
162
|
-
</label>
|
|
163
|
-
</fieldset>
|
|
164
|
-
|
|
165
|
-
<button type="submit">Send Message</button>
|
|
166
|
-
</form>
|
|
167
|
-
```
|
|
64
|
+
Code for this section: [Web accessibility examples](references/a11y-examples.md).
|
|
168
65
|
|
|
169
66
|
## Keyboard Navigation
|
|
170
67
|
|
|
171
|
-
|
|
172
|
-
// Focus trap for modals
|
|
173
|
-
function trapFocus(element: HTMLElement) {
|
|
174
|
-
const focusableSelectors = [
|
|
175
|
-
'a[href]', 'button:not([disabled])', 'input:not([disabled])',
|
|
176
|
-
'select:not([disabled])', 'textarea:not([disabled])',
|
|
177
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
178
|
-
];
|
|
179
|
-
const focusableElements = element.querySelectorAll<HTMLElement>(
|
|
180
|
-
focusableSelectors.join(',')
|
|
181
|
-
);
|
|
182
|
-
const firstFocusable = focusableElements[0];
|
|
183
|
-
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
184
|
-
|
|
185
|
-
element.addEventListener('keydown', (e) => {
|
|
186
|
-
if (e.key !== 'Tab') return;
|
|
187
|
-
|
|
188
|
-
if (e.shiftKey) {
|
|
189
|
-
if (document.activeElement === firstFocusable) {
|
|
190
|
-
e.preventDefault();
|
|
191
|
-
lastFocusable.focus();
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
if (document.activeElement === lastFocusable) {
|
|
195
|
-
e.preventDefault();
|
|
196
|
-
firstFocusable.focus();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
firstFocusable?.focus();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// React: accessible keyboard handler
|
|
205
|
-
function handleKeyDown(event: React.KeyboardEvent, action: () => void) {
|
|
206
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
207
|
-
event.preventDefault();
|
|
208
|
-
action();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Roving tabindex for arrow key navigation (toolbar, menu, listbox)
|
|
213
|
-
function useRovingTabindex(items: HTMLElement[]) {
|
|
214
|
-
let currentIndex = 0;
|
|
215
|
-
|
|
216
|
-
items.forEach((item, index) => {
|
|
217
|
-
item.tabIndex = index === 0 ? 0 : -1;
|
|
218
|
-
item.addEventListener('keydown', (e) => {
|
|
219
|
-
let newIndex = currentIndex;
|
|
220
|
-
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
221
|
-
newIndex = (currentIndex + 1) % items.length;
|
|
222
|
-
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
223
|
-
newIndex = (currentIndex - 1 + items.length) % items.length;
|
|
224
|
-
} else if (e.key === 'Home') {
|
|
225
|
-
newIndex = 0;
|
|
226
|
-
} else if (e.key === 'End') {
|
|
227
|
-
newIndex = items.length - 1;
|
|
228
|
-
} else {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
e.preventDefault();
|
|
232
|
-
items[currentIndex].tabIndex = -1;
|
|
233
|
-
items[newIndex].tabIndex = 0;
|
|
234
|
-
items[newIndex].focus();
|
|
235
|
-
currentIndex = newIndex;
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
```
|
|
68
|
+
Code for this section: [Web accessibility examples](references/a11y-examples.md).
|
|
240
69
|
|
|
241
70
|
## Color Contrast and Visual Design
|
|
242
71
|
|
|
@@ -374,3 +203,7 @@ test('homepage has no a11y violations', async ({ page }) => {
|
|
|
374
203
|
| Tab order is wrong | Unstructured DOM or tabindex misuse | Restructure DOM to match visual order; remove tabindex > 0 |
|
|
375
204
|
| Modal doesn't trap focus | No focus management | Implement focus trap and restore focus on close |
|
|
376
205
|
| Live region not announced | Missing `aria-live` or wrong timing | Add `aria-live="polite"` and inject content after region exists in DOM |
|
|
206
|
+
|
|
207
|
+
## References
|
|
208
|
+
|
|
209
|
+
- [Web accessibility examples](references/a11y-examples.md): open when building ARIA widgets, accessible forms, focus traps or roving tab index.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Web accessibility examples
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [ARIA Roles, States, and Properties](#aria-roles-states-and-properties)
|
|
5
|
+
- [Form Accessibility](#form-accessibility)
|
|
6
|
+
- [Keyboard Navigation](#keyboard-navigation)
|
|
7
|
+
<!-- /toc -->
|
|
8
|
+
|
|
9
|
+
Markup and script for ARIA widgets, accessible forms and keyboard navigation. SKILL.md keeps the principles, contrast rules, testing and troubleshooting.
|
|
10
|
+
|
|
11
|
+
## ARIA Roles, States, and Properties
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!-- Dialog / Modal -->
|
|
15
|
+
<div
|
|
16
|
+
role="dialog"
|
|
17
|
+
aria-modal="true"
|
|
18
|
+
aria-labelledby="dialog-title"
|
|
19
|
+
aria-describedby="dialog-desc"
|
|
20
|
+
>
|
|
21
|
+
<h2 id="dialog-title">Confirm Delete</h2>
|
|
22
|
+
<p id="dialog-desc">This action cannot be undone.</p>
|
|
23
|
+
<button>Cancel</button>
|
|
24
|
+
<button>Delete</button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Tabs -->
|
|
28
|
+
<div role="tablist" aria-label="Product info">
|
|
29
|
+
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
|
|
30
|
+
Description
|
|
31
|
+
</button>
|
|
32
|
+
<button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1">
|
|
33
|
+
Reviews
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
|
|
37
|
+
<p>Product description...</p>
|
|
38
|
+
</div>
|
|
39
|
+
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
|
|
40
|
+
<p>Product reviews...</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Live region for dynamic content -->
|
|
44
|
+
<div aria-live="polite" aria-atomic="true">
|
|
45
|
+
<!-- Screen reader announces changes here -->
|
|
46
|
+
<p>3 items in your cart</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Status messages -->
|
|
50
|
+
<div role="status" aria-live="polite">
|
|
51
|
+
Form submitted successfully.
|
|
52
|
+
</div>
|
|
53
|
+
<div role="alert" aria-live="assertive">
|
|
54
|
+
Error: Please correct the highlighted fields.
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Loading state -->
|
|
58
|
+
<div aria-busy="true" aria-live="polite">
|
|
59
|
+
Loading results...
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Expandable content -->
|
|
63
|
+
<button aria-expanded="false" aria-controls="details-section">
|
|
64
|
+
Show Details
|
|
65
|
+
</button>
|
|
66
|
+
<div id="details-section" hidden>
|
|
67
|
+
<p>Additional information...</p>
|
|
68
|
+
</div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Form Accessibility
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<form aria-label="Contact form">
|
|
75
|
+
<!-- Visible label (preferred) -->
|
|
76
|
+
<div>
|
|
77
|
+
<label for="name">Full Name <span aria-hidden="true">*</span></label>
|
|
78
|
+
<input
|
|
79
|
+
type="text"
|
|
80
|
+
id="name"
|
|
81
|
+
name="name"
|
|
82
|
+
required
|
|
83
|
+
aria-required="true"
|
|
84
|
+
autocomplete="name"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Error state -->
|
|
89
|
+
<div>
|
|
90
|
+
<label for="email">Email <span aria-hidden="true">*</span></label>
|
|
91
|
+
<input
|
|
92
|
+
type="email"
|
|
93
|
+
id="email"
|
|
94
|
+
name="email"
|
|
95
|
+
required
|
|
96
|
+
aria-required="true"
|
|
97
|
+
aria-invalid="true"
|
|
98
|
+
aria-describedby="email-error email-hint"
|
|
99
|
+
autocomplete="email"
|
|
100
|
+
/>
|
|
101
|
+
<p id="email-hint" class="hint">We'll never share your email.</p>
|
|
102
|
+
<p id="email-error" class="error" role="alert">
|
|
103
|
+
Please enter a valid email address.
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Fieldset for related inputs -->
|
|
108
|
+
<fieldset>
|
|
109
|
+
<legend>Preferred contact method</legend>
|
|
110
|
+
<label>
|
|
111
|
+
<input type="radio" name="contact" value="email" /> Email
|
|
112
|
+
</label>
|
|
113
|
+
<label>
|
|
114
|
+
<input type="radio" name="contact" value="phone" /> Phone
|
|
115
|
+
</label>
|
|
116
|
+
</fieldset>
|
|
117
|
+
|
|
118
|
+
<button type="submit">Send Message</button>
|
|
119
|
+
</form>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Keyboard Navigation
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Focus trap for modals
|
|
126
|
+
function trapFocus(element: HTMLElement) {
|
|
127
|
+
const focusableSelectors = [
|
|
128
|
+
'a[href]', 'button:not([disabled])', 'input:not([disabled])',
|
|
129
|
+
'select:not([disabled])', 'textarea:not([disabled])',
|
|
130
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
131
|
+
];
|
|
132
|
+
const focusableElements = element.querySelectorAll<HTMLElement>(
|
|
133
|
+
focusableSelectors.join(',')
|
|
134
|
+
);
|
|
135
|
+
const firstFocusable = focusableElements[0];
|
|
136
|
+
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
137
|
+
|
|
138
|
+
element.addEventListener('keydown', (e) => {
|
|
139
|
+
if (e.key !== 'Tab') return;
|
|
140
|
+
|
|
141
|
+
if (e.shiftKey) {
|
|
142
|
+
if (document.activeElement === firstFocusable) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
lastFocusable.focus();
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
if (document.activeElement === lastFocusable) {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
firstFocusable.focus();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
firstFocusable?.focus();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// React: accessible keyboard handler
|
|
158
|
+
function handleKeyDown(event: React.KeyboardEvent, action: () => void) {
|
|
159
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
action();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Roving tabindex for arrow key navigation (toolbar, menu, listbox)
|
|
166
|
+
function useRovingTabindex(items: HTMLElement[]) {
|
|
167
|
+
let currentIndex = 0;
|
|
168
|
+
|
|
169
|
+
items.forEach((item, index) => {
|
|
170
|
+
item.tabIndex = index === 0 ? 0 : -1;
|
|
171
|
+
item.addEventListener('keydown', (e) => {
|
|
172
|
+
let newIndex = currentIndex;
|
|
173
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
174
|
+
newIndex = (currentIndex + 1) % items.length;
|
|
175
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
176
|
+
newIndex = (currentIndex - 1 + items.length) % items.length;
|
|
177
|
+
} else if (e.key === 'Home') {
|
|
178
|
+
newIndex = 0;
|
|
179
|
+
} else if (e.key === 'End') {
|
|
180
|
+
newIndex = items.length - 1;
|
|
181
|
+
} else {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
items[currentIndex].tabIndex = -1;
|
|
186
|
+
items[newIndex].tabIndex = 0;
|
|
187
|
+
items[newIndex].focus();
|
|
188
|
+
currentIndex = newIndex;
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
```
|
|
@@ -159,42 +159,9 @@ module.exports = withBundleAnalyzer({
|
|
|
159
159
|
|
|
160
160
|
## Caching Strategies
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
// Service Worker caching with Workbox
|
|
164
|
-
import { precacheAndRoute } from 'workbox-precaching';
|
|
165
|
-
import { registerRoute } from 'workbox-routing';
|
|
166
|
-
import { StaleWhileRevalidate, CacheFirst, NetworkFirst } from 'workbox-strategies';
|
|
167
|
-
import { ExpirationPlugin } from 'workbox-expiration';
|
|
168
|
-
|
|
169
|
-
// Precache static assets
|
|
170
|
-
precacheAndRoute(self.__WB_MANIFEST);
|
|
171
|
-
|
|
172
|
-
// Cache-first for images (long-lived)
|
|
173
|
-
registerRoute(
|
|
174
|
-
({ request }) => request.destination === 'image',
|
|
175
|
-
new CacheFirst({
|
|
176
|
-
cacheName: 'images',
|
|
177
|
-
plugins: [
|
|
178
|
-
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }),
|
|
179
|
-
],
|
|
180
|
-
})
|
|
181
|
-
);
|
|
162
|
+
With a service worker (Workbox): precache static assets, cache-first for long-lived images, stale-while-revalidate for CSS and JS, network-first for API calls.
|
|
182
163
|
|
|
183
|
-
|
|
184
|
-
registerRoute(
|
|
185
|
-
({ request }) => request.destination === 'style' || request.destination === 'script',
|
|
186
|
-
new StaleWhileRevalidate({ cacheName: 'static-resources' })
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Network-first for API calls
|
|
190
|
-
registerRoute(
|
|
191
|
-
({ url }) => url.pathname.startsWith('/api/'),
|
|
192
|
-
new NetworkFirst({
|
|
193
|
-
cacheName: 'api-cache',
|
|
194
|
-
networkTimeoutSeconds: 3,
|
|
195
|
-
})
|
|
196
|
-
);
|
|
197
|
-
```
|
|
164
|
+
Code: [Caching Strategies](references/code-examples.md#caching-strategies).
|
|
198
165
|
|
|
199
166
|
### HTTP Caching Headers
|
|
200
167
|
|
|
@@ -254,38 +221,9 @@ export async function GET() {
|
|
|
254
221
|
|
|
255
222
|
## Reducing INP (Interaction to Next Paint)
|
|
256
223
|
|
|
257
|
-
|
|
258
|
-
// Break up long tasks with scheduler.yield() or setTimeout
|
|
259
|
-
async function processLargeList(items: Item[]) {
|
|
260
|
-
const CHUNK_SIZE = 50;
|
|
261
|
-
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
|
|
262
|
-
const chunk = items.slice(i, i + CHUNK_SIZE);
|
|
263
|
-
processChunk(chunk);
|
|
264
|
-
|
|
265
|
-
// Yield to the browser between chunks
|
|
266
|
-
if ('scheduler' in globalThis && 'yield' in scheduler) {
|
|
267
|
-
await scheduler.yield();
|
|
268
|
-
} else {
|
|
269
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Debounce expensive input handlers
|
|
275
|
-
import { useDebouncedCallback } from 'use-debounce';
|
|
276
|
-
|
|
277
|
-
function SearchInput() {
|
|
278
|
-
const handleSearch = useDebouncedCallback((term: string) => {
|
|
279
|
-
performSearch(term); // Expensive operation
|
|
280
|
-
}, 300);
|
|
281
|
-
|
|
282
|
-
return <input onChange={(e) => handleSearch(e.target.value)} />;
|
|
283
|
-
}
|
|
224
|
+
Break long tasks into chunks and yield to the browser between them, debounce expensive input handlers, and use CSS containment on complex layouts.
|
|
284
225
|
|
|
285
|
-
|
|
286
|
-
// .card { contain: content; }
|
|
287
|
-
// .sidebar { contain: layout style; }
|
|
288
|
-
```
|
|
226
|
+
Code: [Reducing INP (Interaction to Next Paint)](references/code-examples.md#reducing-inp-interaction-to-next-paint).
|
|
289
227
|
|
|
290
228
|
## Lighthouse Audit Checklist
|
|
291
229
|
|
|
@@ -346,3 +284,7 @@ npx lhci autorun --config=lighthouserc.json
|
|
|
346
284
|
| Large bundle | Importing entire libraries | Use tree-shakeable imports, analyze with bundle visualizer |
|
|
347
285
|
| Slow TTFB | No server caching, slow DB | Add CDN, implement server-side caching, optimize queries |
|
|
348
286
|
| Flash of unstyled text | Web font loading | Use `font-display: swap`, preload font files |
|
|
287
|
+
|
|
288
|
+
## References
|
|
289
|
+
|
|
290
|
+
- [Code examples](references/code-examples.md): open for the full code behind Caching Strategies, Reducing INP (Interaction to Next Paint).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Web Performance Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## Caching Strategies
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Service Worker caching with Workbox
|
|
9
|
+
import { precacheAndRoute } from 'workbox-precaching';
|
|
10
|
+
import { registerRoute } from 'workbox-routing';
|
|
11
|
+
import { StaleWhileRevalidate, CacheFirst, NetworkFirst } from 'workbox-strategies';
|
|
12
|
+
import { ExpirationPlugin } from 'workbox-expiration';
|
|
13
|
+
|
|
14
|
+
// Precache static assets
|
|
15
|
+
precacheAndRoute(self.__WB_MANIFEST);
|
|
16
|
+
|
|
17
|
+
// Cache-first for images (long-lived)
|
|
18
|
+
registerRoute(
|
|
19
|
+
({ request }) => request.destination === 'image',
|
|
20
|
+
new CacheFirst({
|
|
21
|
+
cacheName: 'images',
|
|
22
|
+
plugins: [
|
|
23
|
+
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }),
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Stale-while-revalidate for CSS/JS
|
|
29
|
+
registerRoute(
|
|
30
|
+
({ request }) => request.destination === 'style' || request.destination === 'script',
|
|
31
|
+
new StaleWhileRevalidate({ cacheName: 'static-resources' })
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Network-first for API calls
|
|
35
|
+
registerRoute(
|
|
36
|
+
({ url }) => url.pathname.startsWith('/api/'),
|
|
37
|
+
new NetworkFirst({
|
|
38
|
+
cacheName: 'api-cache',
|
|
39
|
+
networkTimeoutSeconds: 3,
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Reducing INP (Interaction to Next Paint)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Break up long tasks with scheduler.yield() or setTimeout
|
|
48
|
+
async function processLargeList(items: Item[]) {
|
|
49
|
+
const CHUNK_SIZE = 50;
|
|
50
|
+
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
|
|
51
|
+
const chunk = items.slice(i, i + CHUNK_SIZE);
|
|
52
|
+
processChunk(chunk);
|
|
53
|
+
|
|
54
|
+
// Yield to the browser between chunks
|
|
55
|
+
if ('scheduler' in globalThis && 'yield' in scheduler) {
|
|
56
|
+
await scheduler.yield();
|
|
57
|
+
} else {
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Debounce expensive input handlers
|
|
64
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
65
|
+
|
|
66
|
+
function SearchInput() {
|
|
67
|
+
const handleSearch = useDebouncedCallback((term: string) => {
|
|
68
|
+
performSearch(term); // Expensive operation
|
|
69
|
+
}, 300);
|
|
70
|
+
|
|
71
|
+
return <input onChange={(e) => handleSearch(e.target.value)} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Use CSS containment for complex layouts
|
|
75
|
+
// .card { contain: content; }
|
|
76
|
+
// .sidebar { contain: layout style; }
|
|
77
|
+
```
|