create-caspian-app 1.5.8 → 1.6.0

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.
@@ -130,7 +130,7 @@ async function cleanupStaleWatcher(): Promise<void> {
130
130
  }
131
131
 
132
132
  console.warn(
133
- `[tailwind] Found stale PostCSS watcher (PID ${pid}), stopping it before restart.`,
133
+ `[css] Found stale PostCSS watcher (PID ${pid}), stopping it before restart.`,
134
134
  );
135
135
  await killProcessTree(pid);
136
136
  await rmAsync(watcherPidFile, { force: true });
@@ -185,31 +185,35 @@ function createWatchers(rebuildWorker: DebouncedWorker): ClosableWatcher[] {
185
185
  rebuildWorker.schedule(relPath);
186
186
  };
187
187
 
188
+ // Tailwind scans markup for class names, so any authored file can change the
189
+ // generated CSS. Plain CSS has no such scan: only stylesheets matter.
188
190
  const watchers: ClosableWatcher[] = [
189
191
  createSrcWatcher(join(process.cwd(), "src", "**", "*"), {
190
- exts: [".css", ".html", ".js", ".py"],
192
+ exts: caspianConfig.tailwindcss
193
+ ? [".css", ".html", ".js", ".py"]
194
+ : [".css"],
191
195
  ignored: WATCH_IGNORES,
192
196
  awaitWriteFinish: DEFAULT_AWF,
193
- logPrefix: "tailwind:src",
197
+ logPrefix: "css:src",
194
198
  onEvent: scheduleRebuild,
195
199
  }),
196
200
  createSrcWatcher(join(process.cwd(), "postcss.config.js"), {
197
201
  exts: [".js"],
198
202
  ignored: WATCH_IGNORES,
199
203
  awaitWriteFinish: DEFAULT_AWF,
200
- logPrefix: "tailwind:config",
204
+ logPrefix: "css:config",
201
205
  onEvent: scheduleRebuild,
202
206
  }),
203
207
  ];
204
208
 
205
209
  const tsRoot = join(process.cwd(), "ts");
206
- if (caspianConfig.typescript && existsSync(tsRoot)) {
210
+ if (caspianConfig.tailwindcss && caspianConfig.typescript && existsSync(tsRoot)) {
207
211
  watchers.push(
208
212
  createSrcWatcher(join(tsRoot, "**", "*"), {
209
213
  exts: [".js", ".jsx", ".ts", ".tsx"],
210
214
  ignored: WATCH_IGNORES,
211
215
  awaitWriteFinish: DEFAULT_AWF,
212
- logPrefix: "tailwind:ts",
216
+ logPrefix: "css:ts",
213
217
  onEvent: scheduleRebuild,
214
218
  }),
215
219
  );
@@ -235,13 +239,13 @@ async function runWatchMode(): Promise<void> {
235
239
 
236
240
  if (exitCode !== 0) {
237
241
  console.error(
238
- `[tailwind] PostCSS exited with code ${exitCode}. Watching for the next change...`,
242
+ `[css] PostCSS exited with code ${exitCode}. Watching for the next change...`,
239
243
  );
240
244
  }
241
245
  } catch (error) {
242
246
  console.error(error);
243
247
  }
244
- }, 150, "tailwind");
248
+ }, 150, "css");
245
249
 
246
250
  sourceWatchers.push(...createWatchers(rebuildWorker));
247
251
 
@@ -250,7 +254,7 @@ async function runWatchMode(): Promise<void> {
250
254
 
251
255
  if (exitCode !== 0) {
252
256
  console.error(
253
- `[tailwind] Initial PostCSS build exited with code ${exitCode}. Watching for the next change...`,
257
+ `[css] Initial PostCSS build exited with code ${exitCode}. Watching for the next change...`,
254
258
  );
255
259
  }
256
260
  } catch (error) {
@@ -143,17 +143,29 @@ export class DebouncedWorker {
143
143
  * period has elapsed. `onSettled` only runs when no newer work is pending, so
144
144
  * callers can safely perform one user-visible action (such as a browser
145
145
  * reload) after the final batch instead of after every intermediate batch.
146
+ *
147
+ * An optional `shouldDefer` predicate suspends draining entirely without losing
148
+ * work: pending items keep accumulating and the quiet period re-arms, so once
149
+ * the predicate clears, everything collected in the meantime drains as a single
150
+ * batch with a single settle. That is what lets an agent's whole editing run
151
+ * cost one restart and one reload instead of one of each per edit.
146
152
  */
147
153
  export class SettledBatchWorker<T> {
148
154
  private timer: NodeJS.Timeout | null = null;
149
155
  private pending = new Set<T>();
150
156
  private running = false;
157
+ private deferring = false;
151
158
 
152
159
  constructor(
153
160
  private work: (items: ReadonlySet<T>) => Promise<void> | void,
154
161
  private onSettled: () => Promise<void> | void,
155
162
  private quietMs = 1200,
156
163
  private name = "batch",
164
+ private shouldDefer?: (pending: ReadonlySet<T>) => boolean,
165
+ private onDeferStateChange?: (
166
+ deferring: boolean,
167
+ pending: ReadonlySet<T>,
168
+ ) => void,
157
169
  ) {}
158
170
 
159
171
  schedule(item: T) {
@@ -173,6 +185,15 @@ export class SettledBatchWorker<T> {
173
185
  if (this.running) return;
174
186
  if (this.pending.size === 0) return;
175
187
 
188
+ // Checked before anything is consumed, so a deferred batch keeps every
189
+ // queued item and simply waits for another quiet period to re-test.
190
+ if (this.shouldDefer?.(this.pending)) {
191
+ this.setDeferring(true);
192
+ this.armQuietPeriod();
193
+ return;
194
+ }
195
+ this.setDeferring(false);
196
+
176
197
  const batch = new Set(this.pending);
177
198
  this.pending.clear();
178
199
  this.running = true;
@@ -196,6 +217,13 @@ export class SettledBatchWorker<T> {
196
217
  console.error(`[${this.name}] settle error:`, err);
197
218
  }
198
219
  }
220
+
221
+ /** Reports edges only, so a long hold logs once instead of every quiet period. */
222
+ private setDeferring(deferring: boolean) {
223
+ if (this.deferring === deferring) return;
224
+ this.deferring = deferring;
225
+ this.onDeferStateChange?.(deferring, this.pending);
226
+ }
199
227
  }
200
228
 
201
229
  export function createRestartableProcess(spec: {
@@ -5,17 +5,26 @@
5
5
  :root {
6
6
  --background: #ffffff;
7
7
  --foreground: #171717;
8
+ --border: oklch(92.8% 0.006 264.531);
9
+ --muted: oklch(96.7% 0.003 264.542);
10
+ --muted-foreground: oklch(55.1% 0.027 264.364);
8
11
  }
9
12
 
10
13
  @theme inline {
11
14
  --color-background: var(--background);
12
15
  --color-foreground: var(--foreground);
16
+ --color-border: var(--border);
17
+ --color-muted: var(--muted);
18
+ --color-muted-foreground: var(--muted-foreground);
13
19
  }
14
20
 
15
21
  @media (prefers-color-scheme: dark) {
16
22
  :root {
17
23
  --background: #0a0a0a;
18
24
  --foreground: #ededed;
25
+ --border: oklch(100% 0 0 / 12%);
26
+ --muted: oklch(26.9% 0 0);
27
+ --muted-foreground: oklch(70.8% 0 0);
19
28
  }
20
29
  }
21
30
 
@@ -0,0 +1,502 @@
1
+ /*
2
+ * globals.css - the only stylesheet you edit.
3
+ *
4
+ * `npm run dev` and `npm run build` compile this file to public/css/styles.css,
5
+ * which the layout links. Never edit public/css/styles.css: it is generated
6
+ * and overwritten on every build.
7
+ *
8
+ * This project was created without Tailwind CSS, so the rules below are plain
9
+ * CSS that you own. They cover the starter pages; add, rename or delete
10
+ * whatever you like.
11
+ */
12
+
13
+ /* ---------------------------------------------------------------- tokens */
14
+
15
+ :root {
16
+ --background: #ffffff;
17
+ --foreground: #171717;
18
+ --border: oklch(92.8% 0.006 264.531);
19
+ --muted: oklch(96.7% 0.003 264.542);
20
+ --muted-foreground: oklch(55.1% 0.027 264.364);
21
+
22
+ --gray-50: oklch(98.5% 0.002 247.839);
23
+ --gray-100: oklch(96.7% 0.003 264.542);
24
+ --gray-200: oklch(92.8% 0.006 264.531);
25
+ --gray-500: oklch(55.1% 0.027 264.364);
26
+ --gray-600: oklch(44.6% 0.03 256.802);
27
+ --gray-700: oklch(37.3% 0.034 259.733);
28
+ --gray-800: oklch(27.8% 0.033 256.848);
29
+ --gray-900: oklch(21% 0.034 264.665);
30
+ --red-600: oklch(57.7% 0.245 27.325);
31
+ --blue-600: oklch(54.6% 0.245 262.881);
32
+ --indigo-600: oklch(51.1% 0.262 276.966);
33
+ --indigo-700: oklch(45.7% 0.24 277.023);
34
+
35
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
36
+ "Liberation Mono", "Courier New", monospace;
37
+ --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
38
+ }
39
+
40
+ @media (prefers-color-scheme: dark) {
41
+ :root {
42
+ --background: #0a0a0a;
43
+ --foreground: #ededed;
44
+ --border: oklch(100% 0 0 / 12%);
45
+ --muted: oklch(26.9% 0 0);
46
+ --muted-foreground: oklch(70.8% 0 0);
47
+ }
48
+ }
49
+
50
+ /* ------------------------------------------------------------- base reset */
51
+
52
+ *,
53
+ ::before,
54
+ ::after {
55
+ box-sizing: border-box;
56
+ border: 0 solid var(--border);
57
+ }
58
+
59
+ html {
60
+ -webkit-text-size-adjust: 100%;
61
+ line-height: 1.5;
62
+ }
63
+
64
+ body {
65
+ margin: 0;
66
+ background: var(--background);
67
+ color: var(--foreground);
68
+ font-family: Arial, Helvetica, sans-serif;
69
+ }
70
+
71
+ h1,
72
+ h2,
73
+ h3,
74
+ p,
75
+ figure,
76
+ pre {
77
+ margin: 0;
78
+ font-size: inherit;
79
+ font-weight: inherit;
80
+ }
81
+
82
+ code,
83
+ pre {
84
+ font-family: var(--font-mono);
85
+ font-size: 1em;
86
+ }
87
+
88
+ a {
89
+ color: inherit;
90
+ text-decoration: inherit;
91
+ }
92
+
93
+ button {
94
+ margin: 0;
95
+ padding: 0;
96
+ background: transparent;
97
+ color: inherit;
98
+ font: inherit;
99
+ cursor: pointer;
100
+ }
101
+
102
+ img,
103
+ svg {
104
+ display: block;
105
+ vertical-align: middle;
106
+ }
107
+
108
+ img {
109
+ max-width: 100%;
110
+ height: auto;
111
+ }
112
+
113
+ /* ---------------------------------------------------------------- layout */
114
+
115
+ .container {
116
+ width: 100%;
117
+ }
118
+ .flex {
119
+ display: flex;
120
+ }
121
+ .grid {
122
+ display: grid;
123
+ }
124
+ .hidden {
125
+ display: none;
126
+ }
127
+ .inline-block {
128
+ display: inline-block;
129
+ }
130
+
131
+ .flex-col {
132
+ flex-direction: column;
133
+ }
134
+ .flex-wrap {
135
+ flex-wrap: wrap;
136
+ }
137
+ .flex-1 {
138
+ flex: 1 1 0%;
139
+ }
140
+
141
+ .items-start {
142
+ align-items: flex-start;
143
+ }
144
+ .items-center {
145
+ align-items: center;
146
+ }
147
+ .justify-center {
148
+ justify-content: center;
149
+ }
150
+ .justify-between {
151
+ justify-content: space-between;
152
+ }
153
+
154
+ .gap-px {
155
+ gap: 1px;
156
+ }
157
+ .gap-2 {
158
+ gap: 0.5rem;
159
+ }
160
+ .gap-4 {
161
+ gap: 1rem;
162
+ }
163
+ .gap-16 {
164
+ gap: 4rem;
165
+ }
166
+
167
+ .min-h-screen {
168
+ min-height: 100vh;
169
+ }
170
+ .mx-auto {
171
+ margin-inline: auto;
172
+ }
173
+ .overflow-auto {
174
+ overflow: auto;
175
+ }
176
+ .object-contain {
177
+ object-fit: contain;
178
+ }
179
+
180
+ /* --------------------------------------------------------------- spacing */
181
+
182
+ .mt-2 {
183
+ margin-top: 0.5rem;
184
+ }
185
+ .mt-3 {
186
+ margin-top: 0.75rem;
187
+ }
188
+ .mt-4 {
189
+ margin-top: 1rem;
190
+ }
191
+ .mt-5 {
192
+ margin-top: 1.25rem;
193
+ }
194
+ .mt-6 {
195
+ margin-top: 1.5rem;
196
+ }
197
+ .mt-8 {
198
+ margin-top: 2rem;
199
+ }
200
+ .mb-2 {
201
+ margin-bottom: 0.5rem;
202
+ }
203
+
204
+ .p-4 {
205
+ padding: 1rem;
206
+ }
207
+ .p-6 {
208
+ padding: 1.5rem;
209
+ }
210
+
211
+ .px-2 {
212
+ padding-inline: 0.5rem;
213
+ }
214
+ .px-3 {
215
+ padding-inline: 0.75rem;
216
+ }
217
+ .px-4 {
218
+ padding-inline: 1rem;
219
+ }
220
+ .px-5 {
221
+ padding-inline: 1.25rem;
222
+ }
223
+ .px-6 {
224
+ padding-inline: 1.5rem;
225
+ }
226
+
227
+ .py-1 {
228
+ padding-block: 0.25rem;
229
+ }
230
+ .py-1\.5 {
231
+ padding-block: 0.375rem;
232
+ }
233
+ .py-2 {
234
+ padding-block: 0.5rem;
235
+ }
236
+ .py-3 {
237
+ padding-block: 0.75rem;
238
+ }
239
+ .py-8 {
240
+ padding-block: 2rem;
241
+ }
242
+ .py-10 {
243
+ padding-block: 2.5rem;
244
+ }
245
+
246
+ /* ---------------------------------------------------------------- sizing */
247
+
248
+ .size-5 {
249
+ width: 1.25rem;
250
+ height: 1.25rem;
251
+ }
252
+ .size-20 {
253
+ width: 5rem;
254
+ height: 5rem;
255
+ }
256
+ .h-10 {
257
+ height: 2.5rem;
258
+ }
259
+ .w-10 {
260
+ width: 2.5rem;
261
+ }
262
+ .max-w-md {
263
+ max-width: 28rem;
264
+ }
265
+ .max-w-2xl {
266
+ max-width: 42rem;
267
+ }
268
+
269
+ /* ------------------------------------------------------------ typography */
270
+
271
+ .font-mono {
272
+ font-family: var(--font-mono);
273
+ }
274
+ .font-medium {
275
+ font-weight: 500;
276
+ }
277
+ .font-semibold {
278
+ font-weight: 600;
279
+ }
280
+ .font-bold {
281
+ font-weight: 700;
282
+ }
283
+ .font-black {
284
+ font-weight: 900;
285
+ }
286
+
287
+ .text-xs {
288
+ font-size: 0.75rem;
289
+ line-height: 1.33333;
290
+ }
291
+ .text-\[11px\] {
292
+ font-size: 11px;
293
+ }
294
+ .text-sm {
295
+ font-size: 0.875rem;
296
+ line-height: 1.42857;
297
+ }
298
+ .text-2xl {
299
+ font-size: 1.5rem;
300
+ line-height: 1.33333;
301
+ }
302
+ .text-5xl {
303
+ font-size: 3rem;
304
+ line-height: 1;
305
+ }
306
+ .text-9xl {
307
+ font-size: 8rem;
308
+ line-height: 1;
309
+ }
310
+
311
+ .leading-relaxed {
312
+ line-height: 1.625;
313
+ }
314
+ .tracking-tight {
315
+ letter-spacing: -0.025em;
316
+ }
317
+ .tracking-\[0\.18em\] {
318
+ letter-spacing: 0.18em;
319
+ }
320
+ .tracking-\[-0\.04em\] {
321
+ letter-spacing: -0.04em;
322
+ }
323
+ .uppercase {
324
+ text-transform: uppercase;
325
+ }
326
+ .text-center {
327
+ text-align: center;
328
+ }
329
+ .text-pretty {
330
+ text-wrap: pretty;
331
+ }
332
+
333
+ /* ---------------------------------------------------------------- colors */
334
+
335
+ .bg-background {
336
+ background-color: var(--background);
337
+ }
338
+ .bg-border {
339
+ background-color: var(--border);
340
+ }
341
+ .bg-transparent {
342
+ background-color: transparent;
343
+ }
344
+ .bg-white {
345
+ background-color: #fff;
346
+ }
347
+ .bg-gray-50 {
348
+ background-color: var(--gray-50);
349
+ }
350
+ .bg-gray-100 {
351
+ background-color: var(--gray-100);
352
+ }
353
+ .bg-blue-600 {
354
+ background-color: var(--blue-600);
355
+ }
356
+ .bg-indigo-600 {
357
+ background-color: var(--indigo-600);
358
+ }
359
+
360
+ .text-foreground {
361
+ color: var(--foreground);
362
+ }
363
+ .text-muted-foreground {
364
+ color: var(--muted-foreground);
365
+ }
366
+ .text-black {
367
+ color: #000;
368
+ }
369
+ .text-white {
370
+ color: #fff;
371
+ }
372
+ .text-red-600 {
373
+ color: var(--red-600);
374
+ }
375
+ .text-gray-200 {
376
+ color: var(--gray-200);
377
+ }
378
+ .text-gray-500 {
379
+ color: var(--gray-500);
380
+ }
381
+ .text-gray-600 {
382
+ color: var(--gray-600);
383
+ }
384
+ .text-gray-700 {
385
+ color: var(--gray-700);
386
+ }
387
+ .text-gray-800 {
388
+ color: var(--gray-800);
389
+ }
390
+ .text-gray-900 {
391
+ color: var(--gray-900);
392
+ }
393
+
394
+ /* --------------------------------------------------------------- borders */
395
+
396
+ .border {
397
+ border-width: 1px;
398
+ }
399
+ .border-border {
400
+ border-color: var(--border);
401
+ }
402
+ .border-gray-200 {
403
+ border-color: var(--gray-200);
404
+ }
405
+ .rounded {
406
+ border-radius: 0.25rem;
407
+ }
408
+ .shadow-sm {
409
+ box-shadow:
410
+ 0 1px 3px 0 rgb(0 0 0 / 0.1),
411
+ 0 1px 2px -1px rgb(0 0 0 / 0.1);
412
+ }
413
+
414
+ /* ------------------------------------------------------ motion and focus */
415
+
416
+ .transition-colors {
417
+ transition:
418
+ color var(--transition),
419
+ background-color var(--transition),
420
+ border-color var(--transition);
421
+ }
422
+
423
+ .transition-transform {
424
+ transition: transform var(--transition);
425
+ }
426
+
427
+ .focus\:outline-none:focus {
428
+ outline: none;
429
+ }
430
+ .focus\:ring:focus {
431
+ box-shadow: 0 0 0 3px rgb(59 130 246 / 0.5);
432
+ }
433
+
434
+ .hover\:text-foreground:hover {
435
+ color: var(--foreground);
436
+ }
437
+ .hover\:bg-gray-200:hover {
438
+ background-color: var(--gray-200);
439
+ }
440
+ .hover\:bg-indigo-700:hover {
441
+ background-color: var(--indigo-700);
442
+ }
443
+ .hover\:bg-muted\/50:hover {
444
+ background-color: color-mix(in oklab, var(--muted) 50%, transparent);
445
+ }
446
+
447
+ .group:hover .group-hover\:translate-x-1 {
448
+ transform: translateX(0.25rem);
449
+ }
450
+
451
+ @media (prefers-reduced-motion: reduce) {
452
+ .motion-reduce\:transform-none {
453
+ transform: none;
454
+ }
455
+ }
456
+
457
+ /* ----------------------------------------------------------- breakpoints */
458
+
459
+ @media (width >= 40rem) {
460
+ .container {
461
+ max-width: 40rem;
462
+ }
463
+ .sm\:inline {
464
+ display: inline;
465
+ }
466
+ .sm\:grid-cols-2 {
467
+ grid-template-columns: repeat(2, minmax(0, 1fr));
468
+ }
469
+ .sm\:text-4xl {
470
+ font-size: 2.25rem;
471
+ line-height: 1.11111;
472
+ }
473
+ }
474
+
475
+ @media (width >= 48rem) {
476
+ .container {
477
+ max-width: 48rem;
478
+ }
479
+ .md\:size-24 {
480
+ width: 6rem;
481
+ height: 6rem;
482
+ }
483
+ .md\:px-12 {
484
+ padding-inline: 3rem;
485
+ }
486
+ .md\:py-12 {
487
+ padding-block: 3rem;
488
+ }
489
+ .md\:text-7xl {
490
+ font-size: 4.5rem;
491
+ line-height: 1;
492
+ }
493
+ }
494
+
495
+ @media (width >= 64rem) {
496
+ .container {
497
+ max-width: 64rem;
498
+ }
499
+ .lg\:grid-cols-4 {
500
+ grid-template-columns: repeat(4, minmax(0, 1fr));
501
+ }
502
+ }