create-surf-app 0.1.5 → 0.1.7
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/dist/chunk-AMAMASIV.js +1016 -0
- package/dist/cli.js +51 -0
- package/dist/index.js +5 -387
- package/dist/templates/default/CLAUDE.md +44 -0
- package/dist/templates/default/backend/db/index.js +23 -0
- package/dist/templates/default/backend/db/schema.js +20 -0
- package/dist/templates/{backend → default/backend}/eslint.config.mjs +1 -1
- package/dist/templates/default/backend/lib/db.js +67 -0
- package/dist/templates/default/backend/package.json +25 -0
- package/dist/templates/default/backend/routes/proxy.js +66 -0
- package/dist/templates/default/backend/server.js +444 -0
- package/dist/templates/default/frontend/components.json +21 -0
- package/{templates → dist/templates/default}/frontend/eslint.config.js +1 -1
- package/dist/templates/default/frontend/package.json +82 -0
- package/dist/templates/default/frontend/src/App.tsx +23 -0
- package/dist/templates/default/frontend/src/ErrorBoundary.tsx +106 -0
- package/dist/templates/default/frontend/src/components/ui/accordion.tsx +55 -0
- package/dist/templates/default/frontend/src/components/ui/alert.tsx +59 -0
- package/dist/templates/default/frontend/src/components/ui/aspect-ratio.tsx +5 -0
- package/dist/templates/default/frontend/src/components/ui/avatar.tsx +48 -0
- package/dist/templates/default/frontend/src/components/ui/badge.tsx +36 -0
- package/dist/templates/default/frontend/src/components/ui/breadcrumb.tsx +115 -0
- package/dist/templates/default/frontend/src/components/ui/button.tsx +57 -0
- package/dist/templates/default/frontend/src/components/ui/calendar.tsx +211 -0
- package/dist/templates/default/frontend/src/components/ui/card.tsx +76 -0
- package/dist/templates/default/frontend/src/components/ui/carousel.tsx +262 -0
- package/dist/templates/default/frontend/src/components/ui/checkbox.tsx +30 -0
- package/dist/templates/default/frontend/src/components/ui/collapsible.tsx +9 -0
- package/dist/templates/default/frontend/src/components/ui/command.tsx +153 -0
- package/dist/templates/default/frontend/src/components/ui/context-menu.tsx +200 -0
- package/dist/templates/default/frontend/src/components/ui/dialog.tsx +120 -0
- package/dist/templates/default/frontend/src/components/ui/drawer.tsx +118 -0
- package/dist/templates/default/frontend/src/components/ui/dropdown-menu.tsx +201 -0
- package/dist/templates/default/frontend/src/components/ui/form.tsx +176 -0
- package/dist/templates/default/frontend/src/components/ui/hover-card.tsx +29 -0
- package/dist/templates/default/frontend/src/components/ui/input.tsx +22 -0
- package/dist/templates/default/frontend/src/components/ui/label.tsx +26 -0
- package/dist/templates/default/frontend/src/components/ui/menubar.tsx +256 -0
- package/dist/templates/default/frontend/src/components/ui/navigation-menu.tsx +128 -0
- package/dist/templates/default/frontend/src/components/ui/popover.tsx +33 -0
- package/dist/templates/default/frontend/src/components/ui/progress.tsx +26 -0
- package/dist/templates/default/frontend/src/components/ui/radio-group.tsx +42 -0
- package/dist/templates/default/frontend/src/components/ui/resizable.tsx +43 -0
- package/dist/templates/default/frontend/src/components/ui/scroll-area.tsx +46 -0
- package/dist/templates/default/frontend/src/components/ui/select.tsx +157 -0
- package/dist/templates/default/frontend/src/components/ui/separator.tsx +31 -0
- package/dist/templates/default/frontend/src/components/ui/sheet.tsx +140 -0
- package/dist/templates/default/frontend/src/components/ui/skeleton.tsx +15 -0
- package/dist/templates/default/frontend/src/components/ui/slider.tsx +26 -0
- package/dist/templates/default/frontend/src/components/ui/sonner.tsx +29 -0
- package/dist/templates/default/frontend/src/components/ui/switch.tsx +29 -0
- package/dist/templates/default/frontend/src/components/ui/table.tsx +120 -0
- package/dist/templates/default/frontend/src/components/ui/tabs.tsx +53 -0
- package/dist/templates/default/frontend/src/components/ui/textarea.tsx +22 -0
- package/dist/templates/default/frontend/src/components/ui/toast.tsx +129 -0
- package/dist/templates/default/frontend/src/components/ui/toaster.tsx +35 -0
- package/dist/templates/default/frontend/src/components/ui/toggle-group.tsx +59 -0
- package/dist/templates/default/frontend/src/components/ui/toggle.tsx +43 -0
- package/dist/templates/default/frontend/src/components/ui/tooltip.tsx +30 -0
- package/dist/templates/default/frontend/src/db/schema.ts +16 -0
- package/{templates → dist/templates/default}/frontend/src/entry-client.tsx +11 -8
- package/dist/templates/default/frontend/src/hooks/use-toast.ts +95 -0
- package/dist/templates/default/frontend/src/index.css +314 -0
- package/dist/templates/default/frontend/src/lib/api.ts +31 -0
- package/dist/templates/default/frontend/src/lib/fetch.ts +38 -0
- package/dist/templates/default/frontend/src/lib/utils.ts +6 -0
- package/dist/templates/default/frontend/src/vite-env.d.ts +11 -0
- package/dist/templates/default/frontend/tsconfig.json +22 -0
- package/dist/templates/default/frontend/vite.config.ts +162 -0
- package/package.json +7 -7
- package/dist/templates/frontend/eslint.config.js +0 -42
- package/dist/templates/frontend/src/entry-client.tsx +0 -109
- package/templates/backend/eslint.config.mjs +0 -21
- package/templates/frontend/index.html +0 -43
- package/templates/frontend/src/entry-server.tsx +0 -13
- /package/dist/templates/{frontend → default/frontend}/index.html +0 -0
- /package/dist/templates/{frontend → default/frontend}/src/entry-server.tsx +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;600;700;900&family=Roboto+Mono:wght@400;500&display=swap');
|
|
2
|
+
|
|
3
|
+
@import "tailwindcss";
|
|
4
|
+
@import "tw-animate-css";
|
|
5
|
+
|
|
6
|
+
/* ===== Tailwind CSS 4 Theme — maps CSS vars to Tailwind tokens ===== */
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--color-card: var(--card);
|
|
11
|
+
--color-card-foreground: var(--card-foreground);
|
|
12
|
+
--color-popover: var(--popover);
|
|
13
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
14
|
+
--color-primary: var(--primary);
|
|
15
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
16
|
+
--color-secondary: var(--secondary);
|
|
17
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
18
|
+
--color-muted: var(--muted);
|
|
19
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
20
|
+
--color-accent: var(--accent);
|
|
21
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
22
|
+
--color-destructive: var(--destructive);
|
|
23
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
24
|
+
--color-border: var(--border);
|
|
25
|
+
--color-input: var(--input);
|
|
26
|
+
--color-ring: var(--ring);
|
|
27
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
28
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
29
|
+
--radius-lg: var(--radius);
|
|
30
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
31
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
32
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
33
|
+
|
|
34
|
+
/* Surf Semantic — Background */
|
|
35
|
+
--color-bg-subtle: var(--bg-subtle);
|
|
36
|
+
--color-bg-subtle-hover: var(--bg-subtle-hover);
|
|
37
|
+
--color-bg-base: var(--bg-base);
|
|
38
|
+
--color-bg-base-opaque: var(--bg-base-opaque);
|
|
39
|
+
--color-bg-menu: var(--bg-menu);
|
|
40
|
+
--color-bg-chat: var(--bg-chat);
|
|
41
|
+
--color-bg-chat-nav: var(--bg-chat-nav);
|
|
42
|
+
|
|
43
|
+
/* Surf Semantic — Foreground */
|
|
44
|
+
--color-fg-base: var(--fg-base);
|
|
45
|
+
--color-fg-subtle: var(--fg-subtle);
|
|
46
|
+
--color-fg-muted: var(--fg-muted);
|
|
47
|
+
--color-fg-disabled: var(--fg-disabled);
|
|
48
|
+
|
|
49
|
+
/* Surf Semantic — Border */
|
|
50
|
+
--color-border-base: var(--border-base);
|
|
51
|
+
--color-border-strong: var(--border-strong);
|
|
52
|
+
--color-border-contrast: var(--border-contrast);
|
|
53
|
+
--color-border-focus: var(--border-focus);
|
|
54
|
+
|
|
55
|
+
/* Surf Brand */
|
|
56
|
+
--color-brand-100: var(--brand-100);
|
|
57
|
+
--color-brand-60: var(--brand-60);
|
|
58
|
+
--color-brand-30: var(--brand-30);
|
|
59
|
+
--color-brand-10: var(--brand-10);
|
|
60
|
+
|
|
61
|
+
/* Surf Visualizer (Chart/Status) */
|
|
62
|
+
--color-visualizer-rose-pop: var(--visualizer-rose-pop);
|
|
63
|
+
--color-visualizer-indigo-breeze: var(--visualizer-indigo-breeze);
|
|
64
|
+
--color-visualizer-emerald-mint: var(--visualizer-emerald-mint);
|
|
65
|
+
--color-visualizer-golden-amber: var(--visualizer-golden-amber);
|
|
66
|
+
--color-visualizer-royal-blue: var(--visualizer-royal-blue);
|
|
67
|
+
--color-visualizer-crimson-spark: var(--visualizer-crimson-spark);
|
|
68
|
+
--color-visualizer-aqua-glow: var(--visualizer-aqua-glow);
|
|
69
|
+
--color-visualizer-sunbeam-yellow: var(--visualizer-sunbeam-yellow);
|
|
70
|
+
|
|
71
|
+
/* Surf Tags */
|
|
72
|
+
--color-tag-blue-10: var(--tag-blue-10);
|
|
73
|
+
--color-tag-blue-100: var(--tag-blue-100);
|
|
74
|
+
--color-tag-yellow-10: var(--tag-yellow-10);
|
|
75
|
+
--color-tag-yellow-100: var(--tag-yellow-100);
|
|
76
|
+
--color-tag-purple-10: var(--tag-purple-10);
|
|
77
|
+
--color-tag-purple-100: var(--tag-purple-100);
|
|
78
|
+
--color-tag-cyan-10: var(--tag-cyan-10);
|
|
79
|
+
--color-tag-cyan-100: var(--tag-cyan-100);
|
|
80
|
+
--color-tag-pink-10: var(--tag-pink-10);
|
|
81
|
+
--color-tag-pink-100: var(--tag-pink-100);
|
|
82
|
+
--color-tag-orange-10: var(--tag-orange-10);
|
|
83
|
+
--color-tag-orange-100: var(--tag-orange-100);
|
|
84
|
+
|
|
85
|
+
/* Neutral */
|
|
86
|
+
--color-neutral-0: var(--neutral-0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes accordion-down {
|
|
90
|
+
from { height: 0; }
|
|
91
|
+
to { height: var(--radix-accordion-content-height); }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@keyframes accordion-up {
|
|
95
|
+
from { height: var(--radix-accordion-content-height); }
|
|
96
|
+
to { height: 0; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ===== Light Mode (default) ===== */
|
|
100
|
+
:root {
|
|
101
|
+
/* shadcn semantic tokens (Surf Light) */
|
|
102
|
+
--background: hsl(0 0% 100%);
|
|
103
|
+
--foreground: hsl(0 0% 13%);
|
|
104
|
+
--card: hsl(0 0% 100%);
|
|
105
|
+
--card-foreground: hsl(0 0% 13%);
|
|
106
|
+
--popover: hsl(0 0% 100%);
|
|
107
|
+
--popover-foreground: hsl(0 0% 13%);
|
|
108
|
+
--primary: hsl(339 100% 58%);
|
|
109
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
110
|
+
--secondary: hsl(0 3% 95%);
|
|
111
|
+
--secondary-foreground: hsl(0 0% 13%);
|
|
112
|
+
--muted: hsl(0 3% 95%);
|
|
113
|
+
--muted-foreground: hsl(0 0% 48%);
|
|
114
|
+
--accent: hsl(0 3% 95%);
|
|
115
|
+
--accent-foreground: hsl(0 0% 13%);
|
|
116
|
+
--destructive: hsl(357 91% 55%);
|
|
117
|
+
--destructive-foreground: hsl(0 0% 100%);
|
|
118
|
+
--border: hsl(0 0% 91%);
|
|
119
|
+
--input: hsl(0 0% 91%);
|
|
120
|
+
--ring: #212121;
|
|
121
|
+
--radius: 0.5rem;
|
|
122
|
+
|
|
123
|
+
/* Surf Neutral Palette */
|
|
124
|
+
--neutral-0: #ffffff;
|
|
125
|
+
--neutral-50: #fafafa;
|
|
126
|
+
--neutral-100: #f5f4f4;
|
|
127
|
+
--neutral-200: #e7e7e7;
|
|
128
|
+
--neutral-300: #d8d8d8;
|
|
129
|
+
--neutral-400: #aaaaaa;
|
|
130
|
+
--neutral-500: #7a7a7a;
|
|
131
|
+
--neutral-600: #5b5b5b;
|
|
132
|
+
--neutral-700: #464646;
|
|
133
|
+
--neutral-800: #2a2a2a;
|
|
134
|
+
--neutral-900: #212121;
|
|
135
|
+
--neutral-950: #1b1b1b;
|
|
136
|
+
--neutral-1000: #101010;
|
|
137
|
+
|
|
138
|
+
/* Surf Brand */
|
|
139
|
+
--brand-100: #ff2882;
|
|
140
|
+
--brand-60: rgba(255, 40, 130, 0.6);
|
|
141
|
+
--brand-30: rgba(255, 40, 130, 0.3);
|
|
142
|
+
--brand-10: rgba(255, 40, 130, 0.1);
|
|
143
|
+
|
|
144
|
+
/* Surf Brand Extended */
|
|
145
|
+
--brand-seafoam: #b6cfd0;
|
|
146
|
+
--brand-dusty-teal: #6ba4b8;
|
|
147
|
+
--brand-black-aqua: #002f38;
|
|
148
|
+
--brand-deep-teal-blue: #07272d;
|
|
149
|
+
|
|
150
|
+
/* Surf Visualizer (Chart) Colors */
|
|
151
|
+
--visualizer-rose-pop: #fd4b96;
|
|
152
|
+
--visualizer-indigo-breeze: #6366f1;
|
|
153
|
+
--visualizer-emerald-mint: #10b981;
|
|
154
|
+
--visualizer-golden-amber: #f59e0b;
|
|
155
|
+
--visualizer-royal-blue: #1d4ed8;
|
|
156
|
+
--visualizer-crimson-spark: #ef4444;
|
|
157
|
+
--visualizer-aqua-glow: #06b6d4;
|
|
158
|
+
--visualizer-sunbeam-yellow: #facc15;
|
|
159
|
+
|
|
160
|
+
/* Surf Semantic Tokens (Light) */
|
|
161
|
+
--bg-subtle: rgba(42, 42, 42, 0.04);
|
|
162
|
+
--bg-subtle-hover: rgba(42, 42, 42, 0.06);
|
|
163
|
+
--bg-base: rgba(255, 255, 255, 0.88);
|
|
164
|
+
--bg-base-opaque: #ffffff;
|
|
165
|
+
--bg-menu: #ffffff;
|
|
166
|
+
--bg-chat: #fcfcfc;
|
|
167
|
+
--bg-chat-nav: #f4f4f4;
|
|
168
|
+
--fg-base: #212121;
|
|
169
|
+
--fg-subtle: #7a7a7a;
|
|
170
|
+
--fg-muted: #aaaaaa;
|
|
171
|
+
--fg-disabled: #d8d8d8;
|
|
172
|
+
--border-base: rgba(42, 42, 42, 0.04);
|
|
173
|
+
--border-strong: rgba(42, 42, 42, 0.08);
|
|
174
|
+
--border-contrast: rgba(42, 42, 42, 0.12);
|
|
175
|
+
--border-focus: rgba(42, 42, 42, 0.4);
|
|
176
|
+
|
|
177
|
+
/* Tag Colors */
|
|
178
|
+
--tag-blue-10: rgba(91, 181, 255, 0.1);
|
|
179
|
+
--tag-blue-100: #5bb5ff;
|
|
180
|
+
--tag-yellow-10: rgba(222, 195, 120, 0.1);
|
|
181
|
+
--tag-yellow-100: #dec378;
|
|
182
|
+
--tag-purple-10: rgba(144, 142, 184, 0.1);
|
|
183
|
+
--tag-purple-100: #908eb8;
|
|
184
|
+
--tag-cyan-10: rgba(116, 173, 164, 0.1);
|
|
185
|
+
--tag-cyan-100: #74ada4;
|
|
186
|
+
--tag-pink-10: rgba(184, 142, 167, 0.1);
|
|
187
|
+
--tag-pink-100: #b88ea7;
|
|
188
|
+
--tag-orange-10: rgba(184, 160, 142, 0.1);
|
|
189
|
+
--tag-orange-100: #b8a08e;
|
|
190
|
+
|
|
191
|
+
/* Surf Gradient Definitions (raw, mode-independent) */
|
|
192
|
+
--gradient-max-dark: linear-gradient(213deg, #8c421d 2%, #fbe67b 31%, #fcfbe7 53%, #f7d14e 77%, #d4a041 100%);
|
|
193
|
+
--gradient-max-light: linear-gradient(125deg, #8c421d 22%, #d2af00 47%, #cd9124 67%, #d4a041 88%);
|
|
194
|
+
--gradient-pro-dark: linear-gradient(213deg, #7a96ac 1%, #c5d6e2 27%, #eaeff3 47%, #c5d6e2 65%, #a3bccf 89%);
|
|
195
|
+
--gradient-pro-light: linear-gradient(129deg, #7a96ac 4%, #6d7f8d 27%, #9fb9ce 55%, #6d7f8d 90%);
|
|
196
|
+
--gradient-plus-dark: linear-gradient(214deg, #986732 0%, #a7825b 23%, #f6d0ab 45%, #a07043 66%, #9d774e 100%);
|
|
197
|
+
--gradient-plus-light: linear-gradient(129deg, #9e8976 5%, #7a5e50 19%, #f6d0ab 35%, #9d774e 49%, #c99b70 65%, #795f52 78%);
|
|
198
|
+
--gradient-upgrade-dark: linear-gradient(214deg, #ffedf3 0%, #ff9fbf 30%, #fd5d92 59%, #ffdab2 100%);
|
|
199
|
+
--gradient-upgrade-light: linear-gradient(32deg, #fc3bb2 6%, #f7906e 79%);
|
|
200
|
+
--gradient-new-function: linear-gradient(33deg, #6e86ff 17%, #ff2882 55%, #ff98a4 103%);
|
|
201
|
+
--gradient-suggestion: linear-gradient(32deg, #fd538c 4%, #5aa6e0 47%, #ffdab2 98%);
|
|
202
|
+
--gradient-g1: linear-gradient(189deg, #ffacc6 6%, #b6e0f5 99%);
|
|
203
|
+
--gradient-g2: linear-gradient(34deg, #9796f0 9%, #fbc7d4 84%);
|
|
204
|
+
--gradient-g3: linear-gradient(to right, #ed4264, #ffedbc);
|
|
205
|
+
--gradient-g4: linear-gradient(232deg, #fedc2a 4%, #dd5789 56%, #7a2c9e 104%);
|
|
206
|
+
--gradient-g5: linear-gradient(32deg, #fc3bb2 6%, #f7906e 79%);
|
|
207
|
+
|
|
208
|
+
/* Surf Gradients (semantic, auto-switch by mode) */
|
|
209
|
+
--gradient-max: var(--gradient-max-light);
|
|
210
|
+
--gradient-pro: var(--gradient-pro-light);
|
|
211
|
+
--gradient-plus: var(--gradient-plus-light);
|
|
212
|
+
--gradient-upgrade: var(--gradient-upgrade-light);
|
|
213
|
+
|
|
214
|
+
/* Spacing */
|
|
215
|
+
--spacing-2: 2px;
|
|
216
|
+
--spacing-4: 4px;
|
|
217
|
+
--spacing-6: 6px;
|
|
218
|
+
--spacing-8: 8px;
|
|
219
|
+
--spacing-10: 10px;
|
|
220
|
+
--spacing-12: 12px;
|
|
221
|
+
--spacing-16: 16px;
|
|
222
|
+
--spacing-24: 24px;
|
|
223
|
+
--spacing-32: 32px;
|
|
224
|
+
--spacing-40: 40px;
|
|
225
|
+
--spacing-64: 64px;
|
|
226
|
+
--spacing-96: 96px;
|
|
227
|
+
--spacing-128: 128px;
|
|
228
|
+
|
|
229
|
+
/* Border Radius */
|
|
230
|
+
--radius-2: 2px;
|
|
231
|
+
--radius-4: 4px;
|
|
232
|
+
--radius-6: 6px;
|
|
233
|
+
--radius-8: 8px;
|
|
234
|
+
--radius-10: 10px;
|
|
235
|
+
--radius-12: 12px;
|
|
236
|
+
--radius-16: 16px;
|
|
237
|
+
--radius-20: 20px;
|
|
238
|
+
--radius-24: 24px;
|
|
239
|
+
--radius-full: 999px;
|
|
240
|
+
|
|
241
|
+
/* Typography */
|
|
242
|
+
--font-family-header: "Lato", "PingFang SC", sans-serif;
|
|
243
|
+
--font-family-body: "Lato", "PingFang SC", sans-serif;
|
|
244
|
+
--font-family-code: "Roboto Mono", monospace;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* ===== Dark Mode ===== */
|
|
248
|
+
.dark {
|
|
249
|
+
/* shadcn semantic tokens (Surf Dark) */
|
|
250
|
+
--background: hsl(0 0% 9%);
|
|
251
|
+
--foreground: hsl(0 0% 91%);
|
|
252
|
+
--card: hsl(0 0% 9%);
|
|
253
|
+
--card-foreground: hsl(0 0% 91%);
|
|
254
|
+
--popover: hsl(0 0% 15%);
|
|
255
|
+
--popover-foreground: hsl(0 0% 91%);
|
|
256
|
+
--primary: hsl(339 100% 58%);
|
|
257
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
258
|
+
--secondary: hsl(0 0% 16%);
|
|
259
|
+
--secondary-foreground: hsl(0 0% 91%);
|
|
260
|
+
--muted: hsl(0 0% 16%);
|
|
261
|
+
--muted-foreground: hsl(0 0% 67%);
|
|
262
|
+
--accent: hsl(0 0% 16%);
|
|
263
|
+
--accent-foreground: hsl(0 0% 91%);
|
|
264
|
+
--destructive: hsl(359 100% 65%);
|
|
265
|
+
--destructive-foreground: hsl(0 0% 100%);
|
|
266
|
+
--border: hsl(0 0% 20%);
|
|
267
|
+
--input: hsl(0 0% 20%);
|
|
268
|
+
--ring: #e7e7e7;
|
|
269
|
+
|
|
270
|
+
/* Surf Gradients (Dark) */
|
|
271
|
+
--gradient-max: var(--gradient-max-dark);
|
|
272
|
+
--gradient-pro: var(--gradient-pro-dark);
|
|
273
|
+
--gradient-plus: var(--gradient-plus-dark);
|
|
274
|
+
--gradient-upgrade: var(--gradient-upgrade-dark);
|
|
275
|
+
|
|
276
|
+
/* Surf Semantic Tokens (Dark) */
|
|
277
|
+
--bg-subtle: rgba(255, 255, 255, 0.04);
|
|
278
|
+
--bg-subtle-hover: rgba(255, 255, 255, 0.12);
|
|
279
|
+
--bg-base: rgba(42, 42, 42, 0.8);
|
|
280
|
+
--bg-base-opaque: #171717;
|
|
281
|
+
--bg-menu: #252525;
|
|
282
|
+
--bg-chat: #101010;
|
|
283
|
+
--bg-chat-nav: #171717;
|
|
284
|
+
--fg-base: #e7e7e7;
|
|
285
|
+
--fg-subtle: #aaaaaa;
|
|
286
|
+
--fg-muted: #7a7a7a;
|
|
287
|
+
--fg-disabled: #5b5b5b;
|
|
288
|
+
--border-base: rgba(255, 255, 255, 0.04);
|
|
289
|
+
--border-strong: rgba(255, 255, 255, 0.08);
|
|
290
|
+
--border-contrast: rgba(255, 255, 255, 0.12);
|
|
291
|
+
--border-focus: rgba(255, 255, 255, 0.4);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@layer base {
|
|
295
|
+
*,
|
|
296
|
+
::after,
|
|
297
|
+
::before {
|
|
298
|
+
border-color: var(--color-border);
|
|
299
|
+
}
|
|
300
|
+
body {
|
|
301
|
+
background-color: var(--color-background);
|
|
302
|
+
color: var(--color-foreground);
|
|
303
|
+
font-family: "Lato", "PingFang SC", sans-serif;
|
|
304
|
+
-webkit-font-smoothing: antialiased;
|
|
305
|
+
-moz-osx-font-smoothing: grayscale;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@media (max-width: 768px) {
|
|
310
|
+
:root {
|
|
311
|
+
--spacing-96: 64px;
|
|
312
|
+
--spacing-128: 64px;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto API client helpers (fallback — swagger fetch failed).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { API_BASE, normalizeProxyPath, fetchWithRetry, proxyGet, proxyPost } from './fetch'
|
|
6
|
+
|
|
7
|
+
export interface ApiResponse<T> {
|
|
8
|
+
data: T[]
|
|
9
|
+
meta?: { total?: number; limit?: number; offset?: number }
|
|
10
|
+
error?: { code: string; message: string }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ApiObjectResponse<T> {
|
|
14
|
+
data: T
|
|
15
|
+
meta?: { total?: number; limit?: number; offset?: number }
|
|
16
|
+
error?: { code: string; message: string }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CursorMeta {
|
|
20
|
+
has_more: boolean
|
|
21
|
+
next_cursor?: string
|
|
22
|
+
limit?: number
|
|
23
|
+
cached?: boolean
|
|
24
|
+
credits_used?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ApiCursorResponse<T> {
|
|
28
|
+
data: T[]
|
|
29
|
+
meta: CursorMeta
|
|
30
|
+
error?: { code: string; message: string }
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core fetch utilities for proxy API access.
|
|
3
|
+
* This file is part of the scaffold — do NOT modify manually.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const API_BASE = import.meta.env.VITE_API_URL || import.meta.env.BASE_URL.replace(/\/$/, '')
|
|
7
|
+
|
|
8
|
+
/** Normalize logical proxy paths; strips leading slashes and proxy/ prefix. */
|
|
9
|
+
export function normalizeProxyPath(path: string): string {
|
|
10
|
+
const trimmed = String(path || '').replace(/^\/+/, '')
|
|
11
|
+
return trimmed.replace(/^(?:proxy\/)+/, '')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Fetch with retry on empty response. */
|
|
15
|
+
export async function fetchWithRetry<T = any>(url: string, init?: RequestInit, retries = 1): Promise<T> {
|
|
16
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
17
|
+
const res = await fetch(url, init)
|
|
18
|
+
const text = await res.text()
|
|
19
|
+
if (text) return JSON.parse(text)
|
|
20
|
+
if (attempt < retries) await new Promise(r => setTimeout(r, 1000))
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Empty response from ${url.replace(API_BASE, '')}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Generic proxy GET \u2014 use for any /proxy/{category}/{endpoint} route. */
|
|
26
|
+
export async function proxyGet<T = any>(path: string, params?: Record<string, string>): Promise<T> {
|
|
27
|
+
const qs = params ? '?' + new URLSearchParams(params).toString() : ''
|
|
28
|
+
return fetchWithRetry<T>(`${API_BASE}/proxy/${normalizeProxyPath(path)}${qs}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Generic proxy POST \u2014 use for any /proxy/{category}/{endpoint} route. */
|
|
32
|
+
export async function proxyPost<T = any>(path: string, body?: any): Promise<T> {
|
|
33
|
+
return fetchWithRetry<T>(`${API_BASE}/proxy/${normalizeProxyPath(path)}`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": false,
|
|
15
|
+
"noUnusedParameters": false,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./src/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { defineConfig, loadEnv } from 'vite'
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
5
|
+
|
|
6
|
+
// Use loadEnv to read .env before config evaluation.
|
|
7
|
+
// Vite does NOT load .env files until after the config is resolved,
|
|
8
|
+
// so process.env.VITE_* is unavailable here without this.
|
|
9
|
+
const env = loadEnv('development', process.cwd())
|
|
10
|
+
|
|
11
|
+
function readRequiredPort(name: 'VITE_BACKEND_PORT' | 'VITE_PORT') {
|
|
12
|
+
const value = env[name]
|
|
13
|
+
const port = Number.parseInt(value || '', 10)
|
|
14
|
+
if (!Number.isInteger(port)) {
|
|
15
|
+
throw new Error(`Missing required ${name} in frontend/.env`)
|
|
16
|
+
}
|
|
17
|
+
return port
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const BACKEND_PORT = readRequiredPort('VITE_BACKEND_PORT')
|
|
21
|
+
const BASE = env.VITE_BASE || './'
|
|
22
|
+
|
|
23
|
+
// Vite proxy middleware runs BEFORE base-path stripping. When base is
|
|
24
|
+
// non-relative (e.g. /preview/staging/{sid}/), proxy keys must be prefixed
|
|
25
|
+
// with base to match incoming requests.
|
|
26
|
+
// See: https://vite.dev/config/server-options#server-proxy
|
|
27
|
+
const hasAbsBase = BASE.startsWith('/')
|
|
28
|
+
const proxyKey = hasAbsBase ? `${BASE}proxy` : '/proxy'
|
|
29
|
+
const apiProxyKey = hasAbsBase ? `${BASE}api` : '/api'
|
|
30
|
+
|
|
31
|
+
const backendProxy = {
|
|
32
|
+
target: `http://127.0.0.1:${BACKEND_PORT}`,
|
|
33
|
+
changeOrigin: true,
|
|
34
|
+
// Strip base prefix so Express receives clean paths
|
|
35
|
+
...(hasAbsBase && {
|
|
36
|
+
rewrite: (path: string) => path.replace(BASE, '/'),
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
plugins: [react(), tailwindcss()],
|
|
42
|
+
server: {
|
|
43
|
+
port: readRequiredPort('VITE_PORT'),
|
|
44
|
+
host: '0.0.0.0',
|
|
45
|
+
proxy: {
|
|
46
|
+
// /proxy/* → Express → OutboundProxy (crypto data APIs)
|
|
47
|
+
[proxyKey]: backendProxy,
|
|
48
|
+
// /api/* → Express (custom backend routes: auth, CRUD, etc.)
|
|
49
|
+
[apiProxyKey]: backendProxy,
|
|
50
|
+
},
|
|
51
|
+
hmr: {
|
|
52
|
+
// Relative path so Vite prepends the base (VITE_BASE) to construct
|
|
53
|
+
// the full WebSocket URL: wss://host/{base}/ws/vite-hmr
|
|
54
|
+
// This ensures HMR goes through the same preview proxy chain
|
|
55
|
+
// (frontend rewrite → Muninn → Urania → Vite dev server).
|
|
56
|
+
// An absolute path (starting with /) would bypass the proxy entirely.
|
|
57
|
+
path: 'ws/vite-hmr',
|
|
58
|
+
},
|
|
59
|
+
// Pre-transform entry files at startup so deps are optimized before the
|
|
60
|
+
// first browser request. Reduces the window where Vite serves dep stubs
|
|
61
|
+
// (partially-optimized modules) during cold start after session respawn.
|
|
62
|
+
warmup: {
|
|
63
|
+
clientFiles: ['./src/entry-client.tsx', './src/App.tsx'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
// Force Vite to always resolve a single copy of React.
|
|
67
|
+
// Without this, dep re-optimization after `npm install <new-pkg>` can create
|
|
68
|
+
// a new React bundle (different hash) while dynamic imports still reference
|
|
69
|
+
// the old one → two React instances → "Invalid hook call" errors.
|
|
70
|
+
resolve: {
|
|
71
|
+
alias: {
|
|
72
|
+
"@": path.resolve(__dirname, "./src"),
|
|
73
|
+
},
|
|
74
|
+
dedupe: ['react', 'react-dom'],
|
|
75
|
+
// CRITICAL: node_modules is a tmpfs mount for performance.
|
|
76
|
+
// With preserveSymlinks=false (default), esbuild and Vite may resolve
|
|
77
|
+
// paths through the tmpfs layer differently after npm install modifies
|
|
78
|
+
// the directory — old dep bundles reference the previous real path
|
|
79
|
+
// → two React instances → "Cannot read properties of null
|
|
80
|
+
// (reading 'useState')".
|
|
81
|
+
// With preserveSymlinks=true, module identity uses the logical path
|
|
82
|
+
// (frontend/node_modules/react) which stays constant regardless of
|
|
83
|
+
// underlying mount changes → single React instance always.
|
|
84
|
+
preserveSymlinks: true,
|
|
85
|
+
},
|
|
86
|
+
// Eagerly pre-bundle ALL scaffold deps at startup.
|
|
87
|
+
// Without this, Vite discovers deps lazily by crawling imports. When the agent
|
|
88
|
+
// rewrites code between turns, new imports trigger dep re-optimization which
|
|
89
|
+
// serves "stub" modules (all exports undefined) → "Cannot read properties of
|
|
90
|
+
// null (reading 'useEffect')" and "Loading dependencies..." reload loops.
|
|
91
|
+
// Pre-bundling everything eliminates lazy discovery entirely.
|
|
92
|
+
optimizeDeps: {
|
|
93
|
+
include: [
|
|
94
|
+
// React core
|
|
95
|
+
'react',
|
|
96
|
+
'react-dom',
|
|
97
|
+
'react-dom/client',
|
|
98
|
+
'react/jsx-dev-runtime',
|
|
99
|
+
'react/jsx-runtime',
|
|
100
|
+
// Data fetching
|
|
101
|
+
'@tanstack/react-query',
|
|
102
|
+
'@tanstack/query-core',
|
|
103
|
+
// Charts
|
|
104
|
+
'echarts',
|
|
105
|
+
'echarts-for-react',
|
|
106
|
+
'echarts/core',
|
|
107
|
+
'echarts/charts',
|
|
108
|
+
'echarts/components',
|
|
109
|
+
'echarts/renderers',
|
|
110
|
+
// UI primitives (Radix)
|
|
111
|
+
'@radix-ui/react-accordion',
|
|
112
|
+
'@radix-ui/react-aspect-ratio',
|
|
113
|
+
'@radix-ui/react-avatar',
|
|
114
|
+
'@radix-ui/react-checkbox',
|
|
115
|
+
'@radix-ui/react-collapsible',
|
|
116
|
+
'@radix-ui/react-context-menu',
|
|
117
|
+
'@radix-ui/react-dialog',
|
|
118
|
+
'@radix-ui/react-dropdown-menu',
|
|
119
|
+
'@radix-ui/react-hover-card',
|
|
120
|
+
'@radix-ui/react-label',
|
|
121
|
+
'@radix-ui/react-menubar',
|
|
122
|
+
'@radix-ui/react-navigation-menu',
|
|
123
|
+
'@radix-ui/react-popover',
|
|
124
|
+
'@radix-ui/react-progress',
|
|
125
|
+
'@radix-ui/react-radio-group',
|
|
126
|
+
'@radix-ui/react-scroll-area',
|
|
127
|
+
'@radix-ui/react-select',
|
|
128
|
+
'@radix-ui/react-separator',
|
|
129
|
+
'@radix-ui/react-slider',
|
|
130
|
+
'@radix-ui/react-slot',
|
|
131
|
+
'@radix-ui/react-switch',
|
|
132
|
+
'@radix-ui/react-tabs',
|
|
133
|
+
'@radix-ui/react-toast',
|
|
134
|
+
'@radix-ui/react-toggle',
|
|
135
|
+
'@radix-ui/react-toggle-group',
|
|
136
|
+
'@radix-ui/react-tooltip',
|
|
137
|
+
// UI components
|
|
138
|
+
'sonner',
|
|
139
|
+
'cmdk',
|
|
140
|
+
'vaul',
|
|
141
|
+
'embla-carousel-react',
|
|
142
|
+
'react-day-picker',
|
|
143
|
+
'react-resizable-panels',
|
|
144
|
+
// Forms & validation
|
|
145
|
+
'react-hook-form',
|
|
146
|
+
'@hookform/resolvers',
|
|
147
|
+
'zod',
|
|
148
|
+
// Utilities
|
|
149
|
+
'lucide-react',
|
|
150
|
+
'next-themes',
|
|
151
|
+
'class-variance-authority',
|
|
152
|
+
'clsx',
|
|
153
|
+
'tailwind-merge',
|
|
154
|
+
'date-fns',
|
|
155
|
+
'scheduler',
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
// Base path set dynamically to match external preview URL.
|
|
159
|
+
// This ensures Vite's internal modules (/@vite/client, /@react-refresh, etc.)
|
|
160
|
+
// are served under the correct path through the multi-layer proxy chain.
|
|
161
|
+
base: BASE,
|
|
162
|
+
})
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-surf-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Scaffold a Surf app — Vite + React + Express + @surf-ai/sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-surf-app": "./dist/
|
|
7
|
+
"create-surf-app": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
"templates"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"scripts": {
|
|
14
|
-
"build": "tsup src/index.ts --format esm --clean && rm -rf dist/templates && cp -r templates dist/templates",
|
|
15
|
-
"dev": "tsup src/index.ts --format esm --watch",
|
|
16
|
-
"test": "
|
|
13
|
+
"build": "tsup src/index.ts src/cli.ts --format esm --clean && rm -rf dist/templates && cp -r templates dist/templates",
|
|
14
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm --watch",
|
|
15
|
+
"test": "tsx --test tests/no-hardcoded-port.test.ts"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
18
|
+
"tsx": "^4.20.6",
|
|
19
19
|
"tsup": "^8.0.0",
|
|
20
20
|
"typescript": "^5.9.0"
|
|
21
21
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import globals from 'globals'
|
|
3
|
-
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
-
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
-
import tseslint from 'typescript-eslint'
|
|
6
|
-
|
|
7
|
-
export default tseslint.config(
|
|
8
|
-
{ ignores: ['dist', 'node_modules'] },
|
|
9
|
-
{
|
|
10
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
-
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
12
|
-
languageOptions: {
|
|
13
|
-
ecmaVersion: 2020,
|
|
14
|
-
globals: {
|
|
15
|
-
...globals.browser,
|
|
16
|
-
...globals.node,
|
|
17
|
-
},
|
|
18
|
-
parserOptions: {
|
|
19
|
-
ecmaFeatures: { jsx: true },
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
plugins: {
|
|
23
|
-
'react-hooks': reactHooks,
|
|
24
|
-
'react-refresh': reactRefresh,
|
|
25
|
-
},
|
|
26
|
-
rules: {
|
|
27
|
-
...reactHooks.configs.recommended.rules,
|
|
28
|
-
'react-refresh/only-export-components': [
|
|
29
|
-
'warn',
|
|
30
|
-
{ allowConstantExport: true },
|
|
31
|
-
],
|
|
32
|
-
'no-unused-vars': 'off',
|
|
33
|
-
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
34
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
35
|
-
'@typescript-eslint/no-empty-object-type': 'warn',
|
|
36
|
-
'@typescript-eslint/ban-ts-comment': 'warn',
|
|
37
|
-
'@typescript-eslint/no-require-imports': 'off',
|
|
38
|
-
'@typescript-eslint/no-unused-expressions': 'warn',
|
|
39
|
-
'@typescript-eslint/no-unsafe-function-type': 'warn',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
)
|