@hustle-together/api-dev-tools 3.6.4 → 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,262 @@
1
+ 'use client';
2
+
3
+ import { useState, useMemo } from 'react';
4
+ import { HeroHeader } from '../shared/HeroHeader';
5
+ import { PreviewCard } from './PreviewCard';
6
+ import { PreviewModal } from './PreviewModal';
7
+
8
+ // Import registry - this will be updated by the CLI when components are created
9
+ // Note: In production, this could be fetched from an API route
10
+ import registry from '@/../.claude/registry.json';
11
+
12
+ type FilterType = 'all' | 'components' | 'pages';
13
+
14
+ interface RegistryItem {
15
+ name: string;
16
+ description?: string;
17
+ file?: string;
18
+ route?: string;
19
+ story?: string;
20
+ tests?: string;
21
+ variants?: string[];
22
+ status?: string;
23
+ created_at?: string;
24
+ uses_components?: string[];
25
+ }
26
+
27
+ interface Registry {
28
+ version: string;
29
+ apis: Record<string, any>;
30
+ components: Record<string, RegistryItem>;
31
+ pages: Record<string, RegistryItem>;
32
+ combined: Record<string, any>;
33
+ }
34
+
35
+ /**
36
+ * UI Showcase Component
37
+ *
38
+ * Displays a grid of all components and pages from the registry.
39
+ * Click any card to open a modal with live preview.
40
+ *
41
+ * Features:
42
+ * - Animated 3D grid hero header
43
+ * - Grid layout (like website portfolio)
44
+ * - Filter tabs: All, Components, Pages
45
+ * - Search functionality
46
+ * - Modal preview with Sandpack (components) or iframe (pages)
47
+ *
48
+ * Created with Hustle API Dev Tools (v3.9.2)
49
+ */
50
+ export function UIShowcase() {
51
+ const [filter, setFilter] = useState<FilterType>('all');
52
+ const [searchQuery, setSearchQuery] = useState('');
53
+ const [selectedItem, setSelectedItem] = useState<{
54
+ id: string;
55
+ type: 'component' | 'page';
56
+ data: RegistryItem;
57
+ } | null>(null);
58
+
59
+ // Type the registry
60
+ const typedRegistry = registry as Registry;
61
+
62
+ // Combine components and pages into a single list
63
+ const allItems = useMemo(() => {
64
+ const items: Array<{
65
+ id: string;
66
+ type: 'component' | 'page';
67
+ data: RegistryItem;
68
+ }> = [];
69
+
70
+ // Add components
71
+ Object.entries(typedRegistry.components || {}).forEach(([id, data]) => {
72
+ items.push({ id, type: 'component', data });
73
+ });
74
+
75
+ // Add pages
76
+ Object.entries(typedRegistry.pages || {}).forEach(([id, data]) => {
77
+ items.push({ id, type: 'page', data });
78
+ });
79
+
80
+ return items;
81
+ }, [typedRegistry]);
82
+
83
+ // Filter items based on type and search
84
+ const filteredItems = useMemo(() => {
85
+ return allItems.filter((item) => {
86
+ // Type filter
87
+ if (filter !== 'all' && filter !== `${item.type}s`) {
88
+ return false;
89
+ }
90
+
91
+ // Search filter
92
+ if (searchQuery) {
93
+ const query = searchQuery.toLowerCase();
94
+ const matchesName = item.data.name?.toLowerCase().includes(query);
95
+ const matchesDescription = item.data.description?.toLowerCase().includes(query);
96
+ return matchesName || matchesDescription;
97
+ }
98
+
99
+ return true;
100
+ });
101
+ }, [allItems, filter, searchQuery]);
102
+
103
+ // Count by type
104
+ const componentCount = Object.keys(typedRegistry.components || {}).length;
105
+ const pageCount = Object.keys(typedRegistry.pages || {}).length;
106
+
107
+ return (
108
+ <div className="min-h-screen bg-white dark:bg-[#050505]">
109
+ {/* Hero Header */}
110
+ <HeroHeader
111
+ title="UI Showcase"
112
+ badge="Component Library"
113
+ description={
114
+ <>
115
+ Live preview and testing for all{' '}
116
+ <strong>Hustle</strong> components and pages.
117
+ </>
118
+ }
119
+ />
120
+
121
+ {/* Filter Bar */}
122
+ <div className="sticky top-0 z-10 border-b-2 border-black bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/60 dark:border-gray-600 dark:bg-[#050505]/95">
123
+ <div className="container mx-auto px-4 py-4">
124
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
125
+ {/* Stats */}
126
+ <div className="flex items-center gap-4">
127
+ <span className="text-sm text-gray-600 dark:text-gray-400">
128
+ <strong className="text-black dark:text-white">{componentCount}</strong> components
129
+ </span>
130
+ <span className="h-4 w-px bg-black dark:bg-gray-600" />
131
+ <span className="text-sm text-gray-600 dark:text-gray-400">
132
+ <strong className="text-black dark:text-white">{pageCount}</strong> pages
133
+ </span>
134
+ </div>
135
+
136
+ {/* Search */}
137
+ <div className="flex items-center gap-4">
138
+ <input
139
+ type="search"
140
+ placeholder="Search..."
141
+ value={searchQuery}
142
+ onChange={(e) => setSearchQuery(e.target.value)}
143
+ className="h-9 w-full border-2 border-black bg-white px-3 text-sm placeholder:text-gray-500 focus:border-[#BA0C2F] focus:outline-none dark:border-gray-600 dark:bg-gray-800 dark:text-white sm:w-64"
144
+ />
145
+ </div>
146
+ </div>
147
+
148
+ {/* Filter Tabs */}
149
+ <div className="mt-4 flex gap-2">
150
+ <button
151
+ onClick={() => setFilter('all')}
152
+ className={`border-2 px-3 py-1.5 text-sm font-bold transition-colors ${
153
+ filter === 'all'
154
+ ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
155
+ : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
156
+ }`}
157
+ >
158
+ All ({allItems.length})
159
+ </button>
160
+ <button
161
+ onClick={() => setFilter('components')}
162
+ className={`border-2 px-3 py-1.5 text-sm font-bold transition-colors ${
163
+ filter === 'components'
164
+ ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
165
+ : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
166
+ }`}
167
+ >
168
+ Components ({componentCount})
169
+ </button>
170
+ <button
171
+ onClick={() => setFilter('pages')}
172
+ className={`border-2 px-3 py-1.5 text-sm font-bold transition-colors ${
173
+ filter === 'pages'
174
+ ? 'border-[#BA0C2F] bg-[#BA0C2F] text-white'
175
+ : 'border-black bg-white text-black hover:border-[#BA0C2F] dark:border-gray-600 dark:bg-gray-800 dark:text-white'
176
+ }`}
177
+ >
178
+ Pages ({pageCount})
179
+ </button>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ {/* Grid */}
185
+ <main className="container mx-auto px-4 py-8">
186
+ {filteredItems.length === 0 ? (
187
+ <div className="flex flex-col items-center justify-center border-2 border-dashed border-black py-16 text-center dark:border-gray-600">
188
+ <div className="mb-4 border-2 border-black bg-gray-100 p-4 dark:border-gray-600 dark:bg-gray-800">
189
+ <svg
190
+ xmlns="http://www.w3.org/2000/svg"
191
+ width="32"
192
+ height="32"
193
+ viewBox="0 0 24 24"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ strokeWidth="2"
197
+ strokeLinecap="round"
198
+ strokeLinejoin="round"
199
+ className="text-gray-500"
200
+ >
201
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
202
+ <circle cx="9" cy="9" r="2" />
203
+ <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
204
+ </svg>
205
+ </div>
206
+ <h2 className="text-xl font-bold text-black dark:text-white">
207
+ {searchQuery ? 'No results found' : 'No items yet'}
208
+ </h2>
209
+ <p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
210
+ {searchQuery
211
+ ? `No components or pages match "${searchQuery}"`
212
+ : 'Run /ui-create to add components and pages'}
213
+ </p>
214
+ </div>
215
+ ) : (
216
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
217
+ {filteredItems.map((item) => (
218
+ <PreviewCard
219
+ key={`${item.type}-${item.id}`}
220
+ id={item.id}
221
+ type={item.type}
222
+ name={item.data.name || item.id}
223
+ description={item.data.description}
224
+ variants={item.data.variants}
225
+ usesComponents={item.data.uses_components}
226
+ route={item.data.route}
227
+ file={item.data.file}
228
+ onClick={() => setSelectedItem(item)}
229
+ />
230
+ ))}
231
+ </div>
232
+ )}
233
+ </main>
234
+
235
+ {/* Modal */}
236
+ {selectedItem && (
237
+ <PreviewModal
238
+ id={selectedItem.id}
239
+ type={selectedItem.type}
240
+ data={selectedItem.data}
241
+ onClose={() => setSelectedItem(null)}
242
+ />
243
+ )}
244
+
245
+ {/* Footer */}
246
+ <footer className="border-t-2 border-black py-6 text-center text-sm text-gray-600 dark:border-gray-600 dark:text-gray-400">
247
+ <p>
248
+ Created with{' '}
249
+ <a
250
+ href="https://github.com/hustle-together/api-dev-tools"
251
+ target="_blank"
252
+ rel="noopener noreferrer"
253
+ className="font-bold text-black hover:text-[#BA0C2F] dark:text-white"
254
+ >
255
+ Hustle API Dev Tools
256
+ </a>{' '}
257
+ v3.9.2
258
+ </p>
259
+ </footer>
260
+ </div>
261
+ );
262
+ }
@@ -0,0 +1,26 @@
1
+ import type { Metadata } from 'next';
2
+ import { UIShowcase } from './UIShowcase';
3
+
4
+ export const metadata: Metadata = {
5
+ title: 'UI Showcase',
6
+ description: 'Preview all components and pages created with Hustle UI Create',
7
+ };
8
+
9
+ /**
10
+ * UI Showcase Page
11
+ *
12
+ * Auto-generated page that displays all components and pages from the registry.
13
+ * Grid layout with modal preview for each element.
14
+ *
15
+ * Features:
16
+ * - Animated 3D grid hero header
17
+ * - Grid layout showing all registered components and pages
18
+ * - Interactive preview with Sandpack
19
+ * - Variant switching
20
+ * - Responsive viewport testing
21
+ *
22
+ * Created with Hustle API Dev Tools (v3.9.2)
23
+ */
24
+ export default function UIShowcasePage() {
25
+ return <UIShowcase />;
26
+ }