@hustle-together/api-dev-tools 3.12.3 → 4.5.1

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.
Files changed (159) hide show
  1. package/.claude/adr-requests/.gitkeep +10 -0
  2. package/.claude/agents/adr-researcher.md +109 -0
  3. package/.claude/agents/visual-analyzer.md +183 -0
  4. package/.claude/api-dev-state.json +7 -463
  5. package/.claude/documentation-audit.json +114 -0
  6. package/.claude/registry.json +289 -0
  7. package/.claude/settings.json +45 -1
  8. package/.claude/workflow-logs/None.json +49 -0
  9. package/.claude/workflow-logs/session-20251230-143727.json +106 -0
  10. package/.skills/adr-deep-research/SKILL.md +351 -0
  11. package/.skills/api-create/SKILL.md +116 -17
  12. package/.skills/api-research/SKILL.md +130 -0
  13. package/.skills/docs-sync/SKILL.md +260 -0
  14. package/.skills/docs-update/SKILL.md +205 -0
  15. package/.skills/hustle-brand/SKILL.md +368 -0
  16. package/.skills/hustle-build/SKILL.md +786 -0
  17. package/.skills/hustle-build-review/SKILL.md +518 -0
  18. package/.skills/parallel-spawn/SKILL.md +212 -0
  19. package/.skills/ralph-continue/SKILL.md +151 -0
  20. package/.skills/ralph-loop/SKILL.md +341 -0
  21. package/.skills/ralph-status/SKILL.md +87 -0
  22. package/.skills/refactor/SKILL.md +59 -0
  23. package/.skills/shadcn/SKILL.md +522 -0
  24. package/.skills/test-all/SKILL.md +210 -0
  25. package/.skills/test-builds/SKILL.md +208 -0
  26. package/.skills/test-debug/SKILL.md +212 -0
  27. package/.skills/test-e2e/SKILL.md +168 -0
  28. package/.skills/test-review/SKILL.md +707 -0
  29. package/.skills/test-unit/SKILL.md +143 -0
  30. package/.skills/test-visual/SKILL.md +301 -0
  31. package/.skills/token-report/SKILL.md +132 -0
  32. package/CHANGELOG.md +575 -0
  33. package/README.md +426 -56
  34. package/bin/cli.js +1538 -88
  35. package/commands/hustle-api-create.md +22 -0
  36. package/commands/hustle-build.md +259 -0
  37. package/commands/hustle-combine.md +81 -2
  38. package/commands/hustle-ui-create-page.md +84 -2
  39. package/commands/hustle-ui-create.md +82 -2
  40. package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
  41. package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
  42. package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
  43. package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
  44. package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
  45. package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
  46. package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
  47. package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
  48. package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
  49. package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
  50. package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
  51. package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
  52. package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
  53. package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
  54. package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
  55. package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
  56. package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
  57. package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
  58. package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
  59. package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
  60. package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
  61. package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
  62. package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
  63. package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
  64. package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
  65. package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
  66. package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
  67. package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
  68. package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
  69. package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
  70. package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
  71. package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
  72. package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
  73. package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
  74. package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
  75. package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
  76. package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
  77. package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
  78. package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
  79. package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
  80. package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
  81. package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
  82. package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
  83. package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
  84. package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
  85. package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
  86. package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
  87. package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
  88. package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
  89. package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
  90. package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
  91. package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
  92. package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
  93. package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
  94. package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
  95. package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
  96. package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
  97. package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
  98. package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
  99. package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
  100. package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
  101. package/hooks/api-workflow-check.py +34 -0
  102. package/hooks/auto-answer.py +305 -0
  103. package/hooks/check-update.py +132 -0
  104. package/hooks/completion-promise-detector.py +293 -0
  105. package/hooks/context-capacity-warning.py +171 -0
  106. package/hooks/docs-update-check.py +120 -0
  107. package/hooks/enforce-dry-run.py +134 -0
  108. package/hooks/enforce-external-research.py +25 -0
  109. package/hooks/enforce-interview.py +20 -0
  110. package/hooks/generate-adr-options.py +282 -0
  111. package/hooks/hook_utils.py +609 -0
  112. package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  113. package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  114. package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  115. package/hooks/ntfy-on-question.py +240 -0
  116. package/hooks/orchestrator-completion.py +313 -0
  117. package/hooks/orchestrator-handoff.py +267 -0
  118. package/hooks/orchestrator-session-startup.py +146 -0
  119. package/hooks/parallel-orchestrator.py +451 -0
  120. package/hooks/periodic-reground.py +270 -67
  121. package/hooks/project-document-prompt.py +302 -0
  122. package/hooks/remote-question-proxy.py +284 -0
  123. package/hooks/remote-question-server.py +1224 -0
  124. package/hooks/run-code-review.py +176 -29
  125. package/hooks/run-visual-qa.py +338 -0
  126. package/hooks/session-logger.py +27 -1
  127. package/hooks/session-startup.py +113 -0
  128. package/hooks/update-adr-decision.py +236 -0
  129. package/hooks/update-api-showcase.py +13 -1
  130. package/hooks/update-testing-checklist.py +195 -0
  131. package/hooks/update-ui-showcase.py +13 -1
  132. package/package.json +7 -3
  133. package/scripts/extract-schema-docs.cjs +322 -0
  134. package/templates/.skills/hustle-interview/SKILL.md +174 -0
  135. package/templates/CLAUDE-SECTION.md +89 -64
  136. package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
  137. package/templates/api-dev-state.json +33 -1
  138. package/templates/api-showcase/_components/APIModal.tsx +100 -8
  139. package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
  140. package/templates/api-showcase/_components/APITester.tsx +367 -58
  141. package/templates/brand-page/page.tsx +645 -0
  142. package/templates/component/Component.visual.spec.ts +30 -24
  143. package/templates/docs/page.tsx +230 -0
  144. package/templates/eslint-plugin-zod-schema/index.js +446 -0
  145. package/templates/eslint-plugin-zod-schema/package.json +26 -0
  146. package/templates/github-workflows/security.yml +274 -0
  147. package/templates/hustle-build-defaults.json +136 -0
  148. package/templates/hustle-dev-dashboard/page.tsx +365 -0
  149. package/templates/page/page.e2e.test.ts +30 -26
  150. package/templates/performance-budgets.json +63 -5
  151. package/templates/playwright-report/page.tsx +258 -0
  152. package/templates/registry.json +279 -3
  153. package/templates/review-dashboard/page.tsx +510 -0
  154. package/templates/settings.json +155 -7
  155. package/templates/test-results/page.tsx +237 -0
  156. package/templates/typedoc.json +19 -0
  157. package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
  158. package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
  159. package/templates/ui-showcase/page.tsx +1 -1
@@ -0,0 +1,365 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useMemo } from "react";
5
+
6
+ // Import registry for stats
7
+ import registryData from "@/../.claude/registry.json";
8
+
9
+ interface Registry {
10
+ version: string;
11
+ apis: Record<string, unknown>;
12
+ components?: Record<string, unknown>;
13
+ pages?: Record<string, unknown>;
14
+ combined?: Record<string, unknown>;
15
+ }
16
+
17
+ /**
18
+ * Hustle Dev Dashboard
19
+ *
20
+ * Central hub linking to all development tools and showcases.
21
+ * Provides quick access to:
22
+ * - API Showcase & Documentation
23
+ * - UI Showcase & Storybook
24
+ * - Test Results
25
+ * - TypeDoc API Documentation
26
+ *
27
+ * Created with Hustle API Dev Tools (v3.12.11)
28
+ */
29
+ export default function HustleDevDashboard() {
30
+ const registry: Registry = registryData || {
31
+ version: "1.0.0",
32
+ apis: {},
33
+ components: {},
34
+ pages: {},
35
+ combined: {},
36
+ };
37
+
38
+ const stats = useMemo(() => {
39
+ return {
40
+ apis: Object.keys(registry.apis || {}).length,
41
+ combined: Object.keys(registry.combined || {}).length,
42
+ components: Object.keys(registry.components || {}).length,
43
+ pages: Object.keys(registry.pages || {}).length,
44
+ };
45
+ }, [registry]);
46
+
47
+ const totalAPIs = stats.apis + stats.combined;
48
+ const totalUI = stats.components + stats.pages;
49
+
50
+ return (
51
+ <div className="min-h-screen bg-white dark:bg-gray-950">
52
+ {/* Header */}
53
+ <header className="border-b-4 border-black bg-[#BA0C2F] px-6 py-8 dark:border-gray-600">
54
+ <div className="mx-auto max-w-6xl">
55
+ <h1 className="text-3xl font-black text-white">
56
+ HUSTLE DEV DASHBOARD
57
+ </h1>
58
+ <p className="mt-2 text-white/80">
59
+ Central hub for all development tools and showcases
60
+ </p>
61
+ </div>
62
+ </header>
63
+
64
+ {/* Main Content */}
65
+ <main className="mx-auto max-w-6xl px-6 py-8">
66
+ {/* Stats Overview */}
67
+ <div className="mb-8 grid gap-4 sm:grid-cols-4">
68
+ <StatCard label="APIs" value={stats.apis} />
69
+ <StatCard label="Combined" value={stats.combined} />
70
+ <StatCard label="Components" value={stats.components} />
71
+ <StatCard label="Pages" value={stats.pages} />
72
+ </div>
73
+
74
+ {/* Links Grid */}
75
+ <div className="grid gap-6 md:grid-cols-2">
76
+ {/* APIs Section */}
77
+ <DashboardSection
78
+ icon="📡"
79
+ title="APIs"
80
+ description={`${totalAPIs} endpoint${totalAPIs !== 1 ? "s" : ""} registered`}
81
+ >
82
+ <DashboardLink
83
+ href="/api-showcase"
84
+ title="API Showcase"
85
+ description="Interactive API testing and documentation"
86
+ primary
87
+ />
88
+ <DashboardLink
89
+ href="/docs/api"
90
+ title="API Documentation"
91
+ description="TypeDoc-generated API reference"
92
+ />
93
+ </DashboardSection>
94
+
95
+ {/* UI Section */}
96
+ <DashboardSection
97
+ icon="🧩"
98
+ title="Components & Pages"
99
+ description={`${totalUI} item${totalUI !== 1 ? "s" : ""} registered`}
100
+ >
101
+ <DashboardLink
102
+ href="/ui-showcase"
103
+ title="UI Showcase"
104
+ description="Component gallery with live previews"
105
+ primary
106
+ />
107
+ <DashboardLink
108
+ href="http://localhost:6006"
109
+ title="Storybook"
110
+ description="Component development environment"
111
+ external
112
+ />
113
+ </DashboardSection>
114
+
115
+ {/* Testing Section */}
116
+ <DashboardSection
117
+ icon="🧪"
118
+ title="Testing"
119
+ description="Test results and coverage reports"
120
+ >
121
+ <DashboardLink
122
+ href="/test-results"
123
+ title="Test Results"
124
+ description="Vitest unit test results"
125
+ />
126
+ <DashboardLink
127
+ href="/playwright-report"
128
+ title="Playwright Reports"
129
+ description="E2E test results and screenshots"
130
+ />
131
+ </DashboardSection>
132
+
133
+ {/* Documentation Section */}
134
+ <DashboardSection
135
+ icon="📚"
136
+ title="Documentation"
137
+ description="Project documentation and guides"
138
+ >
139
+ <DashboardLink
140
+ href="/docs"
141
+ title="TypeDoc Documentation"
142
+ description="Auto-generated code documentation"
143
+ />
144
+ <DashboardLink
145
+ href="https://github.com/hustle-together/api-dev-tools"
146
+ title="GitHub Repository"
147
+ description="Source code and README"
148
+ external
149
+ />
150
+ </DashboardSection>
151
+ </div>
152
+
153
+ {/* Workflow Status */}
154
+ <div className="mt-8 border-2 border-black bg-gray-50 p-6 dark:border-gray-600 dark:bg-gray-900">
155
+ <h2 className="mb-4 text-lg font-bold text-black dark:text-white">
156
+ Workflow Status
157
+ </h2>
158
+ <div className="grid gap-3 sm:grid-cols-2">
159
+ <StatusItem
160
+ label="TypeDoc"
161
+ status="ready"
162
+ command="pnpm typedoc"
163
+ description="Auto-generated on build"
164
+ />
165
+ <StatusItem
166
+ label="Unit Tests"
167
+ status="ready"
168
+ command="pnpm test"
169
+ description="Run during TDD phases"
170
+ />
171
+ <StatusItem
172
+ label="Storybook"
173
+ status="manual"
174
+ command="pnpm storybook"
175
+ description="Start server manually"
176
+ />
177
+ <StatusItem
178
+ label="E2E Tests"
179
+ status="ready"
180
+ command="pnpm test:e2e"
181
+ description="Run during verification"
182
+ />
183
+ </div>
184
+ <p className="mt-4 text-xs text-gray-500 dark:text-gray-400">
185
+ Most commands run automatically during workflows. Manual items require server startup.
186
+ </p>
187
+ </div>
188
+ </main>
189
+
190
+ {/* Footer */}
191
+ <footer className="border-t-2 border-black px-6 py-4 dark:border-gray-600">
192
+ <div className="mx-auto max-w-6xl text-center text-sm text-gray-600 dark:text-gray-400">
193
+ Built with{" "}
194
+ <a
195
+ href="https://github.com/hustle-together/api-dev-tools"
196
+ className="font-bold text-[#BA0C2F] hover:underline"
197
+ >
198
+ Hustle API Dev Tools
199
+ </a>
200
+ </div>
201
+ </footer>
202
+ </div>
203
+ );
204
+ }
205
+
206
+ function StatCard({ label, value }: { label: string; value: number }) {
207
+ return (
208
+ <div className="border-2 border-black bg-white p-4 dark:border-gray-600 dark:bg-gray-900">
209
+ <p className="text-3xl font-black text-[#BA0C2F]">{value}</p>
210
+ <p className="text-sm text-gray-600 dark:text-gray-400">{label}</p>
211
+ </div>
212
+ );
213
+ }
214
+
215
+ function DashboardSection({
216
+ icon,
217
+ title,
218
+ description,
219
+ children,
220
+ }: {
221
+ icon: string;
222
+ title: string;
223
+ description: string;
224
+ children: React.ReactNode;
225
+ }) {
226
+ return (
227
+ <div className="border-2 border-black dark:border-gray-600">
228
+ <div className="border-b-2 border-black bg-gray-50 px-4 py-3 dark:border-gray-600 dark:bg-gray-800">
229
+ <div className="flex items-center gap-2">
230
+ <span className="text-xl">{icon}</span>
231
+ <div>
232
+ <h2 className="font-bold text-black dark:text-white">{title}</h2>
233
+ <p className="text-xs text-gray-600 dark:text-gray-400">
234
+ {description}
235
+ </p>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ <div className="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-900">
240
+ {children}
241
+ </div>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ function DashboardLink({
247
+ href,
248
+ title,
249
+ description,
250
+ primary,
251
+ external,
252
+ }: {
253
+ href: string;
254
+ title: string;
255
+ description: string;
256
+ primary?: boolean;
257
+ external?: boolean;
258
+ }) {
259
+ const LinkComponent = external ? "a" : Link;
260
+ const externalProps = external
261
+ ? { target: "_blank", rel: "noopener noreferrer" }
262
+ : {};
263
+
264
+ return (
265
+ <LinkComponent
266
+ href={href}
267
+ className={`block px-4 py-3 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800 ${
268
+ primary ? "bg-[#BA0C2F]/5" : ""
269
+ }`}
270
+ {...externalProps}
271
+ >
272
+ <div className="flex items-center justify-between">
273
+ <div>
274
+ <p
275
+ className={`font-medium ${
276
+ primary
277
+ ? "text-[#BA0C2F]"
278
+ : "text-black dark:text-white"
279
+ }`}
280
+ >
281
+ {title}
282
+ {external && (
283
+ <span className="ml-1 text-xs text-gray-400">↗</span>
284
+ )}
285
+ </p>
286
+ <p className="text-xs text-gray-600 dark:text-gray-400">
287
+ {description}
288
+ </p>
289
+ </div>
290
+ <svg
291
+ xmlns="http://www.w3.org/2000/svg"
292
+ width="16"
293
+ height="16"
294
+ viewBox="0 0 24 24"
295
+ fill="none"
296
+ stroke="currentColor"
297
+ strokeWidth="2"
298
+ strokeLinecap="round"
299
+ strokeLinejoin="round"
300
+ className="text-gray-400"
301
+ >
302
+ <polyline points="9 18 15 12 9 6" />
303
+ </svg>
304
+ </div>
305
+ </LinkComponent>
306
+ );
307
+ }
308
+
309
+ function StatusItem({
310
+ label,
311
+ status,
312
+ command,
313
+ description,
314
+ }: {
315
+ label: string;
316
+ status: "ready" | "manual" | "running" | "error";
317
+ command: string;
318
+ description: string;
319
+ }) {
320
+ const statusConfig = {
321
+ ready: {
322
+ bg: "bg-green-100 dark:bg-green-900/30",
323
+ text: "text-green-700 dark:text-green-400",
324
+ label: "Auto",
325
+ icon: "✓",
326
+ },
327
+ manual: {
328
+ bg: "bg-yellow-100 dark:bg-yellow-900/30",
329
+ text: "text-yellow-700 dark:text-yellow-400",
330
+ label: "Manual",
331
+ icon: "⚡",
332
+ },
333
+ running: {
334
+ bg: "bg-blue-100 dark:bg-blue-900/30",
335
+ text: "text-blue-700 dark:text-blue-400",
336
+ label: "Running",
337
+ icon: "◐",
338
+ },
339
+ error: {
340
+ bg: "bg-red-100 dark:bg-red-900/30",
341
+ text: "text-red-700 dark:text-red-400",
342
+ label: "Error",
343
+ icon: "✗",
344
+ },
345
+ };
346
+
347
+ const config = statusConfig[status];
348
+
349
+ return (
350
+ <div className="rounded border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
351
+ <div className="flex items-center justify-between">
352
+ <span className="font-medium text-black dark:text-white">{label}</span>
353
+ <span
354
+ className={`rounded px-2 py-0.5 text-xs font-medium ${config.bg} ${config.text}`}
355
+ >
356
+ {config.icon} {config.label}
357
+ </span>
358
+ </div>
359
+ <code className="mt-1 block text-xs text-[#BA0C2F]">{command}</code>
360
+ <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
361
+ {description}
362
+ </p>
363
+ </div>
364
+ );
365
+ }
@@ -37,36 +37,40 @@ test.describe("__PAGE_NAME__ Page", () => {
37
37
  });
38
38
 
39
39
  // ===================================
40
- // Responsive Tests
40
+ // Responsive Tests (7 Viewports)
41
41
  // ===================================
42
42
 
43
- test("should be responsive on mobile", async ({ page }) => {
44
- await page.setViewportSize({ width: 375, height: 667 });
45
- await page.goto("/__PAGE_ROUTE__");
46
-
47
- // Verify main content is visible
48
- await expect(page.getByRole("main")).toBeVisible();
49
-
50
- // Verify no horizontal scroll
51
- const body = await page.locator("body");
52
- const scrollWidth = await body.evaluate((el) => el.scrollWidth);
53
- const clientWidth = await body.evaluate((el) => el.clientWidth);
54
- expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1); // +1 for rounding
55
- });
56
-
57
- test("should be responsive on tablet", async ({ page }) => {
58
- await page.setViewportSize({ width: 768, height: 1024 });
59
- await page.goto("/__PAGE_ROUTE__");
60
-
61
- await expect(page.getByRole("main")).toBeVisible();
62
- });
43
+ // All 7 viewports from performance-budgets.json
44
+ const viewports = [
45
+ { name: "mobile-portrait", width: 375, height: 667 },
46
+ { name: "mobile-notch", width: 393, height: 852 },
47
+ { name: "mobile-landscape", width: 667, height: 375 },
48
+ { name: "tablet-portrait", width: 768, height: 1024 },
49
+ { name: "tablet-landscape", width: 1024, height: 768 },
50
+ { name: "small-desktop", width: 1280, height: 720 },
51
+ { name: "desktop", width: 1920, height: 1080 },
52
+ ];
53
+
54
+ for (const viewport of viewports) {
55
+ test(`should be responsive on ${viewport.name} (${viewport.width}x${viewport.height})`, async ({
56
+ page,
57
+ }) => {
58
+ await page.setViewportSize({
59
+ width: viewport.width,
60
+ height: viewport.height,
61
+ });
62
+ await page.goto("/__PAGE_ROUTE__");
63
63
 
64
- test("should be responsive on desktop", async ({ page }) => {
65
- await page.setViewportSize({ width: 1920, height: 1080 });
66
- await page.goto("/__PAGE_ROUTE__");
64
+ // Verify main content is visible
65
+ await expect(page.getByRole("main")).toBeVisible();
67
66
 
68
- await expect(page.getByRole("main")).toBeVisible();
69
- });
67
+ // Verify no horizontal scroll
68
+ const body = await page.locator("body");
69
+ const scrollWidth = await body.evaluate((el) => el.scrollWidth);
70
+ const clientWidth = await body.evaluate((el) => el.clientWidth);
71
+ expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1);
72
+ });
73
+ }
70
74
 
71
75
  // ===================================
72
76
  // Accessibility Tests
@@ -48,11 +48,69 @@
48
48
  },
49
49
 
50
50
  "responsive": {
51
- "description": "Responsive breakpoints to test",
51
+ "description": "Responsive breakpoints to test (7 comprehensive viewports)",
52
52
  "breakpoints": [
53
- { "name": "mobile", "width": 375, "height": 667 },
54
- { "name": "tablet", "width": 768, "height": 1024 },
55
- { "name": "desktop", "width": 1920, "height": 1080 }
56
- ]
53
+ {
54
+ "name": "mobile-portrait",
55
+ "width": 375,
56
+ "height": 667,
57
+ "description": "iPhone SE baseline",
58
+ "safeArea": null
59
+ },
60
+ {
61
+ "name": "mobile-notch",
62
+ "width": 393,
63
+ "height": 852,
64
+ "description": "iPhone 14 Pro with notch and home indicator",
65
+ "safeArea": {
66
+ "top": 47,
67
+ "bottom": 34,
68
+ "left": 0,
69
+ "right": 0
70
+ }
71
+ },
72
+ {
73
+ "name": "mobile-landscape",
74
+ "width": 667,
75
+ "height": 375,
76
+ "description": "iPhone rotated landscape",
77
+ "safeArea": null
78
+ },
79
+ {
80
+ "name": "tablet-portrait",
81
+ "width": 768,
82
+ "height": 1024,
83
+ "description": "iPad Mini baseline",
84
+ "safeArea": null
85
+ },
86
+ {
87
+ "name": "tablet-landscape",
88
+ "width": 1024,
89
+ "height": 768,
90
+ "description": "iPad Mini rotated",
91
+ "safeArea": null
92
+ },
93
+ {
94
+ "name": "small-desktop",
95
+ "width": 1280,
96
+ "height": 720,
97
+ "description": "Laptop screens, 720p displays",
98
+ "safeArea": null
99
+ },
100
+ {
101
+ "name": "desktop",
102
+ "width": 1920,
103
+ "height": 1080,
104
+ "description": "Standard desktop 1080p",
105
+ "safeArea": null
106
+ }
107
+ ],
108
+ "safeAreaCSS": {
109
+ "description": "CSS environment variables for safe area insets",
110
+ "top": "env(safe-area-inset-top, 0px)",
111
+ "bottom": "env(safe-area-inset-bottom, 0px)",
112
+ "left": "env(safe-area-inset-left, 0px)",
113
+ "right": "env(safe-area-inset-right, 0px)"
114
+ }
57
115
  }
58
116
  }