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

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 (96) hide show
  1. package/.claude/commands/hustle-build.md +259 -0
  2. package/.claude/commands/hustle-combine.md +1089 -0
  3. package/.claude/commands/hustle-ui-create-page.md +1078 -0
  4. package/.claude/commands/hustle-ui-create.md +1058 -0
  5. package/.claude/hooks/auto-answer.py +305 -0
  6. package/.claude/hooks/cache-research.py +337 -0
  7. package/.claude/hooks/check-api-routes.py +168 -0
  8. package/.claude/hooks/check-playwright-setup.py +103 -0
  9. package/.claude/hooks/check-storybook-setup.py +81 -0
  10. package/.claude/hooks/check-update.py +132 -0
  11. package/.claude/hooks/completion-promise-detector.py +293 -0
  12. package/.claude/hooks/context-capacity-warning.py +171 -0
  13. package/.claude/hooks/detect-interruption.py +165 -0
  14. package/.claude/hooks/docs-update-check.py +120 -0
  15. package/.claude/hooks/enforce-a11y-audit.py +202 -0
  16. package/.claude/hooks/enforce-brand-guide.py +241 -0
  17. package/.claude/hooks/enforce-component-type-confirm.py +97 -0
  18. package/.claude/hooks/enforce-dry-run.py +134 -0
  19. package/.claude/hooks/enforce-freshness.py +184 -0
  20. package/.claude/hooks/enforce-page-components.py +186 -0
  21. package/.claude/hooks/enforce-page-data-schema.py +155 -0
  22. package/.claude/hooks/enforce-questions-sourced.py +146 -0
  23. package/.claude/hooks/enforce-schema-from-interview.py +248 -0
  24. package/.claude/hooks/enforce-ui-disambiguation.py +108 -0
  25. package/.claude/hooks/enforce-ui-interview.py +130 -0
  26. package/.claude/hooks/generate-adr-options.py +282 -0
  27. package/.claude/hooks/generate-manifest-entry.py +1161 -0
  28. package/.claude/hooks/hook_utils.py +609 -0
  29. package/.claude/hooks/lib/__init__.py +1 -0
  30. package/.claude/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  31. package/.claude/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  32. package/.claude/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  33. package/.claude/hooks/lib/greptile.py +355 -0
  34. package/.claude/hooks/lib/ntfy.py +209 -0
  35. package/.claude/hooks/notify-input-needed.py +73 -0
  36. package/.claude/hooks/notify-phase-complete.py +90 -0
  37. package/.claude/hooks/ntfy-on-question.py +240 -0
  38. package/.claude/hooks/orchestrator-completion.py +313 -0
  39. package/.claude/hooks/orchestrator-handoff.py +267 -0
  40. package/.claude/hooks/orchestrator-session-startup.py +146 -0
  41. package/.claude/hooks/parallel-orchestrator.py +451 -0
  42. package/.claude/hooks/project-document-prompt.py +302 -0
  43. package/.claude/hooks/remote-question-proxy.py +284 -0
  44. package/.claude/hooks/remote-question-server.py +1224 -0
  45. package/.claude/hooks/run-code-review.py +393 -0
  46. package/.claude/hooks/run-visual-qa.py +338 -0
  47. package/.claude/hooks/session-logger.py +323 -0
  48. package/.claude/hooks/test-orchestrator-reground.py +248 -0
  49. package/.claude/hooks/track-scope-coverage.py +220 -0
  50. package/.claude/hooks/track-token-usage.py +121 -0
  51. package/.claude/hooks/update-adr-decision.py +236 -0
  52. package/.claude/hooks/update-api-showcase.py +161 -0
  53. package/.claude/hooks/update-registry.py +352 -0
  54. package/.claude/hooks/update-testing-checklist.py +195 -0
  55. package/.claude/hooks/update-ui-showcase.py +224 -0
  56. package/.claude/settings.local.json +7 -1
  57. package/.claude/test-auto-answer-bot.py +183 -0
  58. package/.claude/test-completion-detector.py +263 -0
  59. package/.claude/test-orchestrator-state.json +20 -0
  60. package/.claude/test-orchestrator.sh +271 -0
  61. package/.skills/api-create/SKILL.md +88 -3
  62. package/.skills/docs-sync/SKILL.md +260 -0
  63. package/.skills/hustle-build/SKILL.md +459 -0
  64. package/.skills/hustle-build-review/SKILL.md +518 -0
  65. package/CHANGELOG.md +87 -0
  66. package/README.md +86 -9
  67. package/bin/cli.js +1302 -88
  68. package/commands/hustle-api-create.md +22 -0
  69. package/commands/hustle-combine.md +81 -2
  70. package/commands/hustle-ui-create-page.md +84 -2
  71. package/commands/hustle-ui-create.md +82 -2
  72. package/hooks/auto-answer.py +228 -0
  73. package/hooks/check-update.py +132 -0
  74. package/hooks/ntfy-on-question.py +227 -0
  75. package/hooks/orchestrator-completion.py +313 -0
  76. package/hooks/orchestrator-handoff.py +189 -0
  77. package/hooks/orchestrator-session-startup.py +146 -0
  78. package/hooks/periodic-reground.py +230 -67
  79. package/hooks/update-api-showcase.py +13 -1
  80. package/hooks/update-ui-showcase.py +13 -1
  81. package/package.json +7 -3
  82. package/scripts/extract-schema-docs.cjs +322 -0
  83. package/templates/CLAUDE-SECTION.md +89 -64
  84. package/templates/api-showcase/_components/APIModal.tsx +100 -8
  85. package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
  86. package/templates/api-showcase/_components/APITester.tsx +367 -58
  87. package/templates/docs/page.tsx +230 -0
  88. package/templates/hustle-build-defaults.json +84 -0
  89. package/templates/hustle-dev-dashboard/page.tsx +365 -0
  90. package/templates/playwright-report/page.tsx +258 -0
  91. package/templates/settings.json +88 -7
  92. package/templates/test-results/page.tsx +237 -0
  93. package/templates/typedoc.json +19 -0
  94. package/templates/ui-showcase/_components/UIShowcase.tsx +1 -1
  95. package/templates/ui-showcase/page.tsx +1 -1
  96. package/.claude/api-dev-state.json +0 -466
@@ -0,0 +1,230 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useEffect, useState } from "react";
5
+
6
+ /**
7
+ * Documentation Page
8
+ *
9
+ * Shows TypeDoc-generated documentation or instructions to generate it.
10
+ * Links to the generated markdown files in docs/api/.
11
+ *
12
+ * Created with Hustle API Dev Tools (v3.12.12)
13
+ */
14
+ export default function DocsPage() {
15
+ const [hasContent, setHasContent] = useState<boolean | null>(null);
16
+
17
+ useEffect(() => {
18
+ // Check if docs exist by trying to fetch the index
19
+ fetch("/docs/api/README.md")
20
+ .then((res) => {
21
+ setHasContent(res.ok);
22
+ })
23
+ .catch(() => {
24
+ setHasContent(false);
25
+ });
26
+ }, []);
27
+
28
+ return (
29
+ <div className="min-h-screen bg-white dark:bg-gray-950">
30
+ {/* Header */}
31
+ <header className="border-b-4 border-black bg-[#BA0C2F] px-6 py-8 dark:border-gray-600">
32
+ <div className="mx-auto max-w-4xl">
33
+ <div className="flex items-center gap-2">
34
+ <Link
35
+ href="/hustle-dev-dashboard"
36
+ className="text-white/80 hover:text-white"
37
+ >
38
+ Dashboard
39
+ </Link>
40
+ <span className="text-white/60">/</span>
41
+ <span className="text-white">Documentation</span>
42
+ </div>
43
+ <h1 className="mt-2 text-3xl font-black text-white">
44
+ API DOCUMENTATION
45
+ </h1>
46
+ <p className="mt-2 text-white/80">
47
+ TypeDoc-generated code documentation
48
+ </p>
49
+ </div>
50
+ </header>
51
+
52
+ {/* Main Content */}
53
+ <main className="mx-auto max-w-4xl px-6 py-8">
54
+ {hasContent === null ? (
55
+ <div className="flex items-center justify-center py-16">
56
+ <div className="h-8 w-8 animate-spin rounded-full border-4 border-[#BA0C2F] border-t-transparent" />
57
+ </div>
58
+ ) : hasContent ? (
59
+ <DocsContent />
60
+ ) : (
61
+ <EmptyState />
62
+ )}
63
+ </main>
64
+
65
+ {/* Footer */}
66
+ <footer className="border-t-2 border-black px-6 py-4 dark:border-gray-600">
67
+ <div className="mx-auto max-w-4xl text-center text-sm text-gray-600 dark:text-gray-400">
68
+ Built with{" "}
69
+ <a
70
+ href="https://github.com/hustle-together/api-dev-tools"
71
+ className="font-bold text-[#BA0C2F] hover:underline"
72
+ >
73
+ Hustle API Dev Tools
74
+ </a>
75
+ </div>
76
+ </footer>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ function DocsContent() {
82
+ return (
83
+ <div className="space-y-6">
84
+ <div className="border-2 border-black p-6 dark:border-gray-600">
85
+ <h2 className="mb-4 text-xl font-bold text-black dark:text-white">
86
+ Generated Documentation
87
+ </h2>
88
+ <p className="mb-4 text-gray-600 dark:text-gray-400">
89
+ Documentation has been generated. Browse the API reference:
90
+ </p>
91
+ <div className="space-y-2">
92
+ <DocLink href="/docs/api" title="API Reference" description="Full API documentation" />
93
+ </div>
94
+ </div>
95
+
96
+ <div className="border-2 border-black bg-gray-50 p-6 dark:border-gray-600 dark:bg-gray-900">
97
+ <h3 className="mb-2 font-bold text-black dark:text-white">
98
+ Regenerate Documentation
99
+ </h3>
100
+ <p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
101
+ Run this command to update the documentation:
102
+ </p>
103
+ <code className="block rounded bg-white px-4 py-2 font-mono text-[#BA0C2F] dark:bg-gray-800">
104
+ pnpm typedoc
105
+ </code>
106
+ </div>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ function EmptyState() {
112
+ return (
113
+ <div className="space-y-6">
114
+ {/* Status Card */}
115
+ <div className="border-2 border-black p-6 dark:border-gray-600">
116
+ <div className="flex items-center gap-3">
117
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-gray-100 text-2xl dark:bg-gray-800">
118
+ 0
119
+ </div>
120
+ <div>
121
+ <h2 className="text-xl font-bold text-black dark:text-white">
122
+ No Documentation Generated
123
+ </h2>
124
+ <p className="text-gray-600 dark:text-gray-400">
125
+ Run TypeDoc to generate API documentation
126
+ </p>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ {/* Instructions */}
132
+ <div className="border-2 border-black bg-gray-50 p-6 dark:border-gray-600 dark:bg-gray-900">
133
+ <h3 className="mb-4 font-bold text-black dark:text-white">
134
+ Generate Documentation
135
+ </h3>
136
+ <div className="space-y-4">
137
+ <Step
138
+ number={1}
139
+ title="Run TypeDoc"
140
+ command="pnpm typedoc"
141
+ description="Generates Markdown documentation from TSDoc comments"
142
+ />
143
+ <Step
144
+ number={2}
145
+ title="View Output"
146
+ command="ls docs/api/"
147
+ description="Documentation files are generated in the docs/api/ folder"
148
+ />
149
+ <Step
150
+ number={3}
151
+ title="Refresh Page"
152
+ description="Refresh this page to see your documentation"
153
+ />
154
+ </div>
155
+ </div>
156
+
157
+ {/* Requirements */}
158
+ <div className="border-2 border-black p-6 dark:border-gray-600">
159
+ <h3 className="mb-4 font-bold text-black dark:text-white">
160
+ Requirements
161
+ </h3>
162
+ <ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
163
+ <li className="flex items-center gap-2">
164
+ <span className="text-[#BA0C2F]">*</span>
165
+ typedoc.json configuration file in project root
166
+ </li>
167
+ <li className="flex items-center gap-2">
168
+ <span className="text-[#BA0C2F]">*</span>
169
+ TSDoc comments in your TypeScript files
170
+ </li>
171
+ <li className="flex items-center gap-2">
172
+ <span className="text-[#BA0C2F]">*</span>
173
+ typedoc and typedoc-plugin-markdown installed
174
+ </li>
175
+ </ul>
176
+ </div>
177
+ </div>
178
+ );
179
+ }
180
+
181
+ function Step({
182
+ number,
183
+ title,
184
+ command,
185
+ description,
186
+ }: {
187
+ number: number;
188
+ title: string;
189
+ command?: string;
190
+ description: string;
191
+ }) {
192
+ return (
193
+ <div className="flex gap-4">
194
+ <div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-[#BA0C2F] text-sm font-bold text-white">
195
+ {number}
196
+ </div>
197
+ <div>
198
+ <p className="font-medium text-black dark:text-white">{title}</p>
199
+ {command && (
200
+ <code className="mt-1 block rounded bg-white px-3 py-1 font-mono text-sm text-[#BA0C2F] dark:bg-gray-800">
201
+ {command}
202
+ </code>
203
+ )}
204
+ <p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
205
+ {description}
206
+ </p>
207
+ </div>
208
+ </div>
209
+ );
210
+ }
211
+
212
+ function DocLink({
213
+ href,
214
+ title,
215
+ description,
216
+ }: {
217
+ href: string;
218
+ title: string;
219
+ description: string;
220
+ }) {
221
+ return (
222
+ <Link
223
+ href={href}
224
+ className="block rounded border border-gray-200 p-4 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
225
+ >
226
+ <p className="font-medium text-[#BA0C2F]">{title}</p>
227
+ <p className="text-sm text-gray-600 dark:text-gray-400">{description}</p>
228
+ </Link>
229
+ );
230
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "$schema": "hustle-build-defaults",
3
+ "description": "Pre-configured default answers for --auto mode. These are used when no explicit answer is provided.",
4
+
5
+ "orchestrator": {
6
+ "auth_required": true,
7
+ "error_handling": "partial-success",
8
+ "brand_guide": true,
9
+ "testing_level": "full",
10
+ "caching_strategy": "individual",
11
+ "documentation_level": "comprehensive"
12
+ },
13
+
14
+ "api": {
15
+ "include_all_params": true,
16
+ "rate_limiting": true,
17
+ "caching": "individual",
18
+ "error_format": "detailed",
19
+ "validation": "strict",
20
+ "logging": true,
21
+ "metrics": true
22
+ },
23
+
24
+ "component": {
25
+ "all_variants": true,
26
+ "accessibility": "wcag-aa",
27
+ "animations": true,
28
+ "responsive": true,
29
+ "dark_mode": true,
30
+ "loading_states": true,
31
+ "error_states": true,
32
+ "storybook": true
33
+ },
34
+
35
+ "page": {
36
+ "layout": "responsive-grid",
37
+ "seo": true,
38
+ "loading_states": true,
39
+ "error_boundary": true,
40
+ "suspense": true,
41
+ "prefetch": true,
42
+ "meta_tags": true
43
+ },
44
+
45
+ "combined": {
46
+ "execution_order": "parallel",
47
+ "caching": "unified",
48
+ "error_handling": "partial-success",
49
+ "retry_strategy": "exponential",
50
+ "timeout": 30000
51
+ },
52
+
53
+ "testing": {
54
+ "unit_tests": true,
55
+ "integration_tests": true,
56
+ "e2e_tests": true,
57
+ "visual_tests": true,
58
+ "coverage_threshold": 80,
59
+ "snapshot_tests": true
60
+ },
61
+
62
+ "documentation": {
63
+ "tsdoc": true,
64
+ "readme": true,
65
+ "changelog": true,
66
+ "storybook_docs": true,
67
+ "api_reference": true
68
+ },
69
+
70
+ "question_mappings": {
71
+ "auth": "auth_required",
72
+ "authentication": "auth_required",
73
+ "error": "error_handling",
74
+ "brand": "brand_guide",
75
+ "styling": "brand_guide",
76
+ "testing": "testing_level",
77
+ "cache": "caching_strategy",
78
+ "variant": "all_variants",
79
+ "a11y": "accessibility",
80
+ "accessibility": "accessibility",
81
+ "layout": "layout",
82
+ "seo": "seo"
83
+ }
84
+ }
@@ -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
+ }