@hustle-together/api-dev-tools 3.6.5 → 3.9.2

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 (61) hide show
  1. package/README.md +5307 -258
  2. package/bin/cli.js +348 -20
  3. package/commands/README.md +459 -71
  4. package/commands/hustle-api-continue.md +158 -0
  5. package/commands/{api-create.md → hustle-api-create.md} +22 -2
  6. package/commands/{api-env.md → hustle-api-env.md} +4 -4
  7. package/commands/{api-interview.md → hustle-api-interview.md} +1 -1
  8. package/commands/{api-research.md → hustle-api-research.md} +3 -3
  9. package/commands/hustle-api-sessions.md +149 -0
  10. package/commands/{api-status.md → hustle-api-status.md} +16 -16
  11. package/commands/{api-verify.md → hustle-api-verify.md} +2 -2
  12. package/commands/hustle-combine.md +763 -0
  13. package/commands/hustle-ui-create.md +825 -0
  14. package/hooks/api-workflow-check.py +385 -19
  15. package/hooks/cache-research.py +337 -0
  16. package/hooks/check-playwright-setup.py +103 -0
  17. package/hooks/check-storybook-setup.py +81 -0
  18. package/hooks/detect-interruption.py +165 -0
  19. package/hooks/enforce-brand-guide.py +131 -0
  20. package/hooks/enforce-documentation.py +60 -8
  21. package/hooks/enforce-freshness.py +184 -0
  22. package/hooks/enforce-questions-sourced.py +146 -0
  23. package/hooks/enforce-schema-from-interview.py +248 -0
  24. package/hooks/enforce-ui-disambiguation.py +108 -0
  25. package/hooks/enforce-ui-interview.py +130 -0
  26. package/hooks/generate-manifest-entry.py +981 -0
  27. package/hooks/session-logger.py +297 -0
  28. package/hooks/session-startup.py +65 -10
  29. package/hooks/track-scope-coverage.py +220 -0
  30. package/hooks/track-tool-use.py +81 -1
  31. package/hooks/update-api-showcase.py +149 -0
  32. package/hooks/update-registry.py +352 -0
  33. package/hooks/update-ui-showcase.py +148 -0
  34. package/package.json +8 -2
  35. package/templates/BRAND_GUIDE.md +299 -0
  36. package/templates/CLAUDE-SECTION.md +56 -24
  37. package/templates/SPEC.json +640 -0
  38. package/templates/api-dev-state.json +179 -161
  39. package/templates/api-showcase/APICard.tsx +153 -0
  40. package/templates/api-showcase/APIModal.tsx +375 -0
  41. package/templates/api-showcase/APIShowcase.tsx +231 -0
  42. package/templates/api-showcase/APITester.tsx +522 -0
  43. package/templates/api-showcase/page.tsx +41 -0
  44. package/templates/component/Component.stories.tsx +172 -0
  45. package/templates/component/Component.test.tsx +237 -0
  46. package/templates/component/Component.tsx +86 -0
  47. package/templates/component/Component.types.ts +55 -0
  48. package/templates/component/index.ts +15 -0
  49. package/templates/dev-tools/_components/DevToolsLanding.tsx +320 -0
  50. package/templates/dev-tools/page.tsx +10 -0
  51. package/templates/page/page.e2e.test.ts +218 -0
  52. package/templates/page/page.tsx +42 -0
  53. package/templates/performance-budgets.json +58 -0
  54. package/templates/registry.json +13 -0
  55. package/templates/settings.json +74 -0
  56. package/templates/shared/HeroHeader.tsx +261 -0
  57. package/templates/shared/index.ts +1 -0
  58. package/templates/ui-showcase/PreviewCard.tsx +315 -0
  59. package/templates/ui-showcase/PreviewModal.tsx +676 -0
  60. package/templates/ui-showcase/UIShowcase.tsx +262 -0
  61. package/templates/ui-showcase/page.tsx +26 -0
@@ -0,0 +1,320 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import Link from 'next/link';
5
+ import { HeroHeader } from '../../shared/HeroHeader';
6
+
7
+ interface Registry {
8
+ version?: string;
9
+ apis?: Record<string, unknown>;
10
+ components?: Record<string, unknown>;
11
+ pages?: Record<string, unknown>;
12
+ combined?: Record<string, unknown>;
13
+ }
14
+
15
+ /**
16
+ * DevToolsLanding Component
17
+ *
18
+ * Central hub for all Hustle Developer Tools.
19
+ * Links to API Showcase, UI Showcase, and displays registry stats.
20
+ *
21
+ * Created with Hustle Dev Tools (v3.9.2)
22
+ */
23
+ export function DevToolsLanding() {
24
+ const [registry, setRegistry] = useState<Registry>({});
25
+ const [loading, setLoading] = useState(true);
26
+
27
+ useEffect(() => {
28
+ fetch('/api/registry')
29
+ .then((res) => res.json())
30
+ .then((data) => {
31
+ setRegistry(data);
32
+ setLoading(false);
33
+ })
34
+ .catch(() => {
35
+ setLoading(false);
36
+ });
37
+ }, []);
38
+
39
+ const stats = {
40
+ apis: Object.keys(registry.apis || {}).length,
41
+ components: Object.keys(registry.components || {}).length,
42
+ pages: Object.keys(registry.pages || {}).length,
43
+ combined: Object.keys(registry.combined || {}).length,
44
+ };
45
+
46
+ const total = stats.apis + stats.components + stats.pages + stats.combined;
47
+
48
+ return (
49
+ <div className="min-h-screen bg-white dark:bg-gray-950">
50
+ <HeroHeader
51
+ title="Hustle Dev Tools"
52
+ badge="Developer Portal"
53
+ description={
54
+ <>
55
+ Central hub for <strong>API development</strong>,{' '}
56
+ <strong>UI components</strong>, and documentation. Built with the
57
+ Hustle Together workflow.
58
+ </>
59
+ }
60
+ />
61
+
62
+ <main className="mx-auto max-w-6xl px-8 py-12 md:px-16">
63
+ {/* Stats Bar */}
64
+ <div className="mb-8 flex flex-wrap items-center gap-6 border-2 border-black bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-900">
65
+ <div className="flex items-center gap-2">
66
+ <span className="text-3xl font-bold text-black dark:text-white">
67
+ {loading ? '...' : total}
68
+ </span>
69
+ <span className="text-gray-600 dark:text-gray-400">
70
+ Total Items
71
+ </span>
72
+ </div>
73
+ <div className="h-8 w-px bg-gray-300 dark:bg-gray-600" />
74
+ <div className="flex items-center gap-2">
75
+ <span className="font-bold text-black dark:text-white">
76
+ {stats.apis}
77
+ </span>
78
+ <span className="text-gray-600 dark:text-gray-400">APIs</span>
79
+ </div>
80
+ <div className="flex items-center gap-2">
81
+ <span className="font-bold text-black dark:text-white">
82
+ {stats.components}
83
+ </span>
84
+ <span className="text-gray-600 dark:text-gray-400">Components</span>
85
+ </div>
86
+ <div className="flex items-center gap-2">
87
+ <span className="font-bold text-black dark:text-white">
88
+ {stats.pages}
89
+ </span>
90
+ <span className="text-gray-600 dark:text-gray-400">Pages</span>
91
+ </div>
92
+ <div className="flex items-center gap-2">
93
+ <span className="font-bold text-black dark:text-white">
94
+ {stats.combined}
95
+ </span>
96
+ <span className="text-gray-600 dark:text-gray-400">Combined</span>
97
+ </div>
98
+ </div>
99
+
100
+ {/* Main Cards Grid */}
101
+ <div className="mb-12 grid gap-6 md:grid-cols-3">
102
+ {/* API Showcase Card */}
103
+ <Link
104
+ href="/api-showcase"
105
+ className="group flex flex-col border-2 border-black bg-white p-6 transition-all hover:border-[#BA0C2F] hover:shadow-[4px_4px_0px_0px_rgba(186,12,47,0.2)] dark:border-gray-700 dark:bg-gray-900"
106
+ >
107
+ <div className="mb-4 flex h-12 w-12 items-center justify-center border-2 border-[#BA0C2F] bg-[#BA0C2F]/10">
108
+ <svg
109
+ xmlns="http://www.w3.org/2000/svg"
110
+ width="24"
111
+ height="24"
112
+ viewBox="0 0 24 24"
113
+ fill="none"
114
+ stroke="currentColor"
115
+ strokeWidth="2"
116
+ strokeLinecap="round"
117
+ strokeLinejoin="round"
118
+ className="text-[#BA0C2F]"
119
+ >
120
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
121
+ <polyline points="22,6 12,13 2,6" />
122
+ </svg>
123
+ </div>
124
+ <h2 className="mb-2 text-xl font-bold text-black group-hover:text-[#BA0C2F] dark:text-white">
125
+ API Showcase
126
+ </h2>
127
+ <p className="mb-4 flex-1 text-sm text-gray-600 dark:text-gray-400">
128
+ Interactive testing and documentation for all API endpoints. Try
129
+ requests, view schemas, and copy curl commands.
130
+ </p>
131
+ <div className="flex items-center gap-2 text-sm font-bold text-[#BA0C2F]">
132
+ <span>{stats.apis} APIs</span>
133
+ <span>+</span>
134
+ <span>{stats.combined} Combined</span>
135
+ </div>
136
+ </Link>
137
+
138
+ {/* UI Showcase Card */}
139
+ <Link
140
+ href="/ui-showcase"
141
+ className="group flex flex-col border-2 border-black bg-white p-6 transition-all hover:border-[#BA0C2F] hover:shadow-[4px_4px_0px_0px_rgba(186,12,47,0.2)] dark:border-gray-700 dark:bg-gray-900"
142
+ >
143
+ <div className="mb-4 flex h-12 w-12 items-center justify-center border-2 border-[#BA0C2F] bg-[#BA0C2F]/10">
144
+ <svg
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ width="24"
147
+ height="24"
148
+ viewBox="0 0 24 24"
149
+ fill="none"
150
+ stroke="currentColor"
151
+ strokeWidth="2"
152
+ strokeLinecap="round"
153
+ strokeLinejoin="round"
154
+ className="text-[#BA0C2F]"
155
+ >
156
+ <rect width="7" height="7" x="3" y="3" rx="1" />
157
+ <rect width="7" height="7" x="14" y="3" rx="1" />
158
+ <rect width="7" height="7" x="14" y="14" rx="1" />
159
+ <rect width="7" height="7" x="3" y="14" rx="1" />
160
+ </svg>
161
+ </div>
162
+ <h2 className="mb-2 text-xl font-bold text-black group-hover:text-[#BA0C2F] dark:text-white">
163
+ UI Showcase
164
+ </h2>
165
+ <p className="mb-4 flex-1 text-sm text-gray-600 dark:text-gray-400">
166
+ Live component previews with Sandpack. Edit code in real-time,
167
+ switch variants, and test responsive layouts.
168
+ </p>
169
+ <div className="flex items-center gap-2 text-sm font-bold text-[#BA0C2F]">
170
+ <span>{stats.components} Components</span>
171
+ <span>+</span>
172
+ <span>{stats.pages} Pages</span>
173
+ </div>
174
+ </Link>
175
+
176
+ {/* Registry Card */}
177
+ <Link
178
+ href="/api/registry"
179
+ target="_blank"
180
+ className="group flex flex-col border-2 border-black bg-white p-6 transition-all hover:border-[#BA0C2F] hover:shadow-[4px_4px_0px_0px_rgba(186,12,47,0.2)] dark:border-gray-700 dark:bg-gray-900"
181
+ >
182
+ <div className="mb-4 flex h-12 w-12 items-center justify-center border-2 border-[#BA0C2F] bg-[#BA0C2F]/10">
183
+ <svg
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ width="24"
186
+ height="24"
187
+ viewBox="0 0 24 24"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ strokeWidth="2"
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ className="text-[#BA0C2F]"
194
+ >
195
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
196
+ <polyline points="14 2 14 8 20 8" />
197
+ <line x1="16" x2="8" y1="13" y2="13" />
198
+ <line x1="16" x2="8" y1="17" y2="17" />
199
+ <line x1="10" x2="8" y1="9" y2="9" />
200
+ </svg>
201
+ </div>
202
+ <h2 className="mb-2 text-xl font-bold text-black group-hover:text-[#BA0C2F] dark:text-white">
203
+ Registry JSON
204
+ </h2>
205
+ <p className="mb-4 flex-1 text-sm text-gray-600 dark:text-gray-400">
206
+ Raw JSON registry of all APIs, components, and pages. Central
207
+ source of truth for the showcase pages.
208
+ </p>
209
+ <div className="text-sm font-bold text-[#BA0C2F]">
210
+ Version: {registry.version || '1.0.0'}
211
+ </div>
212
+ </Link>
213
+ </div>
214
+
215
+ {/* Quick Actions */}
216
+ <div className="border-2 border-black bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-900">
217
+ <h3 className="mb-4 text-lg font-bold text-black dark:text-white">
218
+ Quick Actions
219
+ </h3>
220
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
221
+ <QuickAction
222
+ title="Run Storybook"
223
+ command="pnpm storybook"
224
+ description="Component development server"
225
+ />
226
+ <QuickAction
227
+ title="Run Tests"
228
+ command="pnpm test"
229
+ description="Execute test suite"
230
+ />
231
+ <QuickAction
232
+ title="Create API"
233
+ command="/hustle-api-create"
234
+ description="Start new API workflow"
235
+ />
236
+ <QuickAction
237
+ title="Create Component"
238
+ command="/hustle-ui-create"
239
+ description="Start new UI workflow"
240
+ />
241
+ </div>
242
+ </div>
243
+
244
+ {/* Setup Instructions */}
245
+ <div className="mt-8 border-2 border-black bg-white p-6 dark:border-gray-700 dark:bg-gray-900">
246
+ <h3 className="mb-4 text-lg font-bold text-black dark:text-white">
247
+ Optional Setup
248
+ </h3>
249
+ <div className="grid gap-4 md:grid-cols-3">
250
+ <SetupCard
251
+ title="Storybook"
252
+ command="npx storybook@latest init"
253
+ description="Interactive component development and visual testing"
254
+ />
255
+ <SetupCard
256
+ title="Playwright"
257
+ command="npm init playwright@latest"
258
+ description="E2E testing and accessibility verification"
259
+ />
260
+ <SetupCard
261
+ title="Sandpack"
262
+ command="pnpm add @codesandbox/sandpack-react"
263
+ description="Live code previews in UI Showcase"
264
+ />
265
+ </div>
266
+ </div>
267
+ </main>
268
+ </div>
269
+ );
270
+ }
271
+
272
+ function QuickAction({
273
+ title,
274
+ command,
275
+ description,
276
+ }: {
277
+ title: string;
278
+ command: string;
279
+ description: string;
280
+ }) {
281
+ return (
282
+ <div className="border-2 border-black bg-white p-4 dark:border-gray-600 dark:bg-gray-800">
283
+ <h4 className="font-bold text-black dark:text-white">{title}</h4>
284
+ <code className="mt-2 block border border-gray-300 bg-gray-100 px-2 py-1 font-mono text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
285
+ {command}
286
+ </code>
287
+ <p className="mt-2 text-xs text-gray-600 dark:text-gray-400">
288
+ {description}
289
+ </p>
290
+ </div>
291
+ );
292
+ }
293
+
294
+ function SetupCard({
295
+ title,
296
+ command,
297
+ description,
298
+ }: {
299
+ title: string;
300
+ command: string;
301
+ description: string;
302
+ }) {
303
+ return (
304
+ <div className="flex flex-col border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800">
305
+ <h4 className="font-bold text-black dark:text-white">{title}</h4>
306
+ <p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
307
+ {description}
308
+ </p>
309
+ <button
310
+ onClick={() => navigator.clipboard.writeText(command)}
311
+ className="mt-3 border-2 border-black bg-white px-3 py-1.5 text-left font-mono text-sm text-gray-700 transition-colors hover:border-[#BA0C2F] hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
312
+ >
313
+ {command}
314
+ <span className="float-right text-gray-400">Copy</span>
315
+ </button>
316
+ </div>
317
+ );
318
+ }
319
+
320
+ export default DevToolsLanding;
@@ -0,0 +1,10 @@
1
+ import { DevToolsLanding } from './_components/DevToolsLanding';
2
+
3
+ export const metadata = {
4
+ title: 'Hustle Dev Tools',
5
+ description: 'Developer tools for API and UI development with Hustle Together',
6
+ };
7
+
8
+ export default function DevToolsPage() {
9
+ return <DevToolsLanding />;
10
+ }
@@ -0,0 +1,218 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ /**
4
+ * E2E Tests for __PAGE_NAME__ Page
5
+ *
6
+ * Created with Hustle UI Create workflow (v3.9.0)
7
+ *
8
+ * Run with: pnpm playwright test __PAGE_ROUTE__.spec.ts
9
+ */
10
+ test.describe('__PAGE_NAME__ Page', () => {
11
+ test.beforeEach(async ({ page }) => {
12
+ // Navigate to the page before each test
13
+ await page.goto('/__PAGE_ROUTE__');
14
+ });
15
+
16
+ // ===================================
17
+ // Basic Rendering Tests
18
+ // ===================================
19
+
20
+ test('should load successfully', async ({ page }) => {
21
+ // Wait for page to be fully loaded
22
+ await page.waitForLoadState('networkidle');
23
+
24
+ // Check page title
25
+ await expect(page).toHaveTitle(/__PAGE_TITLE__/);
26
+ });
27
+
28
+ test('should display page heading', async ({ page }) => {
29
+ const heading = page.getByRole('heading', { level: 1 });
30
+ await expect(heading).toBeVisible();
31
+ await expect(heading).toContainText('__PAGE_TITLE__');
32
+ });
33
+
34
+ test('should display page description', async ({ page }) => {
35
+ const description = page.getByText('__PAGE_DESCRIPTION__');
36
+ await expect(description).toBeVisible();
37
+ });
38
+
39
+ // ===================================
40
+ // Responsive Tests
41
+ // ===================================
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
+ });
63
+
64
+ test('should be responsive on desktop', async ({ page }) => {
65
+ await page.setViewportSize({ width: 1920, height: 1080 });
66
+ await page.goto('/__PAGE_ROUTE__');
67
+
68
+ await expect(page.getByRole('main')).toBeVisible();
69
+ });
70
+
71
+ // ===================================
72
+ // Accessibility Tests
73
+ // ===================================
74
+
75
+ test('should have no accessibility violations', async ({ page }) => {
76
+ // Note: Requires @axe-core/playwright
77
+ // const results = await new AxeBuilder({ page }).analyze();
78
+ // expect(results.violations).toEqual([]);
79
+
80
+ // Basic accessibility checks
81
+ // All images should have alt text
82
+ const images = await page.getByRole('img').all();
83
+ for (const img of images) {
84
+ await expect(img).toHaveAttribute('alt');
85
+ }
86
+
87
+ // All buttons should have accessible names
88
+ const buttons = await page.getByRole('button').all();
89
+ for (const button of buttons) {
90
+ const name = await button.getAttribute('aria-label') || await button.textContent();
91
+ expect(name?.trim()).toBeTruthy();
92
+ }
93
+ });
94
+
95
+ test('should be keyboard navigable', async ({ page }) => {
96
+ // Tab through interactive elements
97
+ await page.keyboard.press('Tab');
98
+
99
+ // Verify focus is visible
100
+ const focusedElement = page.locator(':focus');
101
+ await expect(focusedElement).toBeVisible();
102
+ });
103
+
104
+ // ===================================
105
+ // Performance Tests (TDD GATES)
106
+ // These thresholds match .claude/performance-budgets.json
107
+ // Tests FAIL if exceeded, triggering TDD loop-back
108
+ // ===================================
109
+
110
+ test('should load within performance budget', async ({ page }) => {
111
+ const startTime = Date.now();
112
+ await page.goto('/__PAGE_ROUTE__', { waitUntil: 'networkidle' });
113
+ const loadTime = Date.now() - startTime;
114
+
115
+ // THRESHOLD: Page load max 3000ms
116
+ // If this fails, optimize: code splitting, lazy loading, reduce bundle
117
+ expect(loadTime).toBeLessThan(3000);
118
+ });
119
+
120
+ test('should have acceptable memory usage', async ({ page }) => {
121
+ await page.goto('/__PAGE_ROUTE__');
122
+
123
+ // Get Chromium-specific metrics
124
+ const client = await page.context().newCDPSession(page);
125
+ const metrics = await client.send('Performance.getMetrics');
126
+
127
+ const jsHeapSize = metrics.metrics.find(m => m.name === 'JSHeapUsedSize')?.value || 0;
128
+ const domNodes = metrics.metrics.find(m => m.name === 'Nodes')?.value || 0;
129
+
130
+ // THRESHOLD: Memory max 50MB
131
+ // If this fails, check for: memory leaks, large state, unbounded lists
132
+ expect(jsHeapSize).toBeLessThan(50 * 1024 * 1024);
133
+
134
+ // THRESHOLD: DOM nodes max 1500
135
+ // If this fails, check for: unnecessary renders, infinite lists without virtualization
136
+ expect(domNodes).toBeLessThan(1500);
137
+ });
138
+
139
+ test('should not have layout thrashing', async ({ page }) => {
140
+ await page.goto('/__PAGE_ROUTE__');
141
+
142
+ const client = await page.context().newCDPSession(page);
143
+ const metrics = await client.send('Performance.getMetrics');
144
+
145
+ const layoutCount = metrics.metrics.find(m => m.name === 'LayoutCount')?.value || 0;
146
+ const layoutDuration = metrics.metrics.find(m => m.name === 'LayoutDuration')?.value || 0;
147
+
148
+ // THRESHOLD: Layout count max 10
149
+ // If this fails, batch DOM updates, use CSS transforms instead of layout properties
150
+ expect(layoutCount).toBeLessThan(10);
151
+
152
+ // THRESHOLD: Layout duration max 100ms
153
+ expect(layoutDuration * 1000).toBeLessThan(100);
154
+ });
155
+
156
+ test('should meet Core Web Vitals', async ({ page }) => {
157
+ await page.goto('/__PAGE_ROUTE__');
158
+
159
+ // Get paint timing
160
+ const paintTiming = await page.evaluate(() => {
161
+ const entries = performance.getEntriesByType('paint');
162
+ return {
163
+ fcp: entries.find(e => e.name === 'first-contentful-paint')?.startTime || 0,
164
+ };
165
+ });
166
+
167
+ // THRESHOLD: First Contentful Paint max 1500ms
168
+ // If this fails, optimize critical rendering path
169
+ expect(paintTiming.fcp).toBeLessThan(1500);
170
+
171
+ // Get LCP via PerformanceObserver result (if available)
172
+ const lcp = await page.evaluate(() => {
173
+ return new Promise<number>((resolve) => {
174
+ new PerformanceObserver((list) => {
175
+ const entries = list.getEntries();
176
+ const lastEntry = entries[entries.length - 1];
177
+ resolve(lastEntry?.startTime || 0);
178
+ }).observe({ type: 'largest-contentful-paint', buffered: true });
179
+
180
+ // Timeout fallback
181
+ setTimeout(() => resolve(0), 3000);
182
+ });
183
+ });
184
+
185
+ // THRESHOLD: Largest Contentful Paint max 2500ms
186
+ if (lcp > 0) {
187
+ expect(lcp).toBeLessThan(2500);
188
+ }
189
+ });
190
+
191
+ // ===================================
192
+ // Feature-Specific Tests
193
+ // ===================================
194
+
195
+ // TODO: Add tests for specific page features
196
+ // Example:
197
+ // test('should display user data when logged in', async ({ page }) => {
198
+ // // Setup auth state
199
+ // // await page.context().addCookies([...]);
200
+ //
201
+ // await page.goto('/__PAGE_ROUTE__');
202
+ // await expect(page.getByText('Welcome')).toBeVisible();
203
+ // });
204
+
205
+ // ===================================
206
+ // Error Handling Tests
207
+ // ===================================
208
+
209
+ test('should handle errors gracefully', async ({ page }) => {
210
+ // Intercept API calls to simulate errors (if applicable)
211
+ // await page.route('/api/*', route => route.fulfill({ status: 500 }));
212
+
213
+ await page.goto('/__PAGE_ROUTE__');
214
+
215
+ // Page should still render without crashing
216
+ await expect(page.getByRole('main')).toBeVisible();
217
+ });
218
+ });
@@ -0,0 +1,42 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ /**
4
+ * Page metadata for SEO
5
+ */
6
+ export const metadata: Metadata = {
7
+ title: '__PAGE_TITLE__',
8
+ description: '__PAGE_DESCRIPTION__',
9
+ };
10
+
11
+ /**
12
+ * __PAGE_NAME__ Page
13
+ *
14
+ * @description __PAGE_DESCRIPTION__
15
+ *
16
+ * Created with Hustle UI Create workflow (v3.9.0)
17
+ */
18
+ export default async function __PAGE_NAME__Page() {
19
+ // Server-side data fetching (if needed)
20
+ // const data = await fetchData();
21
+
22
+ return (
23
+ <main className="container mx-auto px-4 py-8">
24
+ {/* Page Header */}
25
+ <header className="mb-8">
26
+ <h1 className="text-3xl font-bold tracking-tight">__PAGE_TITLE__</h1>
27
+ <p className="mt-2 text-muted-foreground">__PAGE_DESCRIPTION__</p>
28
+ </header>
29
+
30
+ {/* Page Content */}
31
+ <section className="space-y-6">
32
+ {/* Add your components here */}
33
+ <div className="rounded-lg border bg-card p-6">
34
+ <h2 className="text-xl font-semibold">Getting Started</h2>
35
+ <p className="mt-2 text-muted-foreground">
36
+ Replace this content with your page implementation.
37
+ </p>
38
+ </div>
39
+ </section>
40
+ </main>
41
+ );
42
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "description": "Performance budgets for UI components and pages. Tests will FAIL if these thresholds are exceeded.",
4
+ "version": "1.0.0",
5
+
6
+ "memory": {
7
+ "description": "Memory usage thresholds (in MB)",
8
+ "page_max_mb": 50,
9
+ "component_max_mb": 10,
10
+ "heap_growth_max_mb": 5
11
+ },
12
+
13
+ "renders": {
14
+ "description": "React render count thresholds",
15
+ "mount_max": 1,
16
+ "prop_change_max": 1,
17
+ "state_change_max": 1,
18
+ "unnecessary_renders_max": 0
19
+ },
20
+
21
+ "layout": {
22
+ "description": "Layout performance thresholds",
23
+ "count_max": 10,
24
+ "duration_max_ms": 100,
25
+ "style_recalc_max": 20
26
+ },
27
+
28
+ "timing": {
29
+ "description": "Load and interaction timing thresholds (in ms)",
30
+ "page_load_max_ms": 3000,
31
+ "first_contentful_paint_max_ms": 1500,
32
+ "time_to_interactive_max_ms": 3500,
33
+ "largest_contentful_paint_max_ms": 2500
34
+ },
35
+
36
+ "bundle": {
37
+ "description": "Bundle size thresholds (in KB)",
38
+ "component_max_kb": 50,
39
+ "page_max_kb": 200,
40
+ "chunk_max_kb": 100
41
+ },
42
+
43
+ "accessibility": {
44
+ "description": "Accessibility requirements",
45
+ "wcag_level": "AA",
46
+ "violations_max": 0,
47
+ "color_contrast_min": 4.5
48
+ },
49
+
50
+ "responsive": {
51
+ "description": "Responsive breakpoints to test",
52
+ "breakpoints": [
53
+ { "name": "mobile", "width": 375, "height": 667 },
54
+ { "name": "tablet", "width": 768, "height": 1024 },
55
+ { "name": "desktop", "width": 1920, "height": 1080 }
56
+ ]
57
+ }
58
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "updated_at": "",
4
+ "description": "Central registry tracking all APIs, components, and pages created through Hustle Dev Tools",
5
+
6
+ "apis": {},
7
+
8
+ "components": {},
9
+
10
+ "pages": {},
11
+
12
+ "combined": {}
13
+ }