start-vibing-stacks 2.0.0 → 2.0.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.
package/dist/ui.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Start Vibing Stacks — Terminal UI
3
3
  */
4
4
  import chalk from 'chalk';
5
- const VERSION = '2.0.0';
5
+ const VERSION = '2.0.2';
6
6
  const gradient = (text) => {
7
7
  const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
8
8
  return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,31 +12,49 @@ Preline is a **semantic token-based design system** built on TailwindCSS. It pro
12
12
 
13
13
  ## Installation (Laravel + Inertia)
14
14
 
15
+ ### Step 1: Install
16
+
15
17
  ```bash
16
- npm install preline
18
+ npm install preline @tailwindcss/forms
19
+ ```
20
+
21
+ ### Step 2: CSS Config
22
+
23
+ ```css
24
+ /* resources/css/app.css */
25
+ @import "tailwindcss";
26
+
27
+ /* Preline — MUST be in this order */
28
+ @source "./node_modules/preline/dist/*.js"; /* JS component scanning */
29
+ @import "./node_modules/preline/variants.css"; /* CSS variants */
30
+ @plugin "@tailwindcss/forms"; /* Forms plugin */
31
+ @import "./node_modules/preline/themes/theme.css"; /* Base theme */
17
32
  ```
18
33
 
34
+ ### Step 3: Vite Config
35
+
19
36
  ```js
20
37
  // vite.config.js
38
+ import { defineConfig } from 'vite';
39
+ import laravel from 'laravel-vite-plugin';
40
+ import react from '@vitejs/plugin-react';
41
+
21
42
  export default defineConfig({
22
43
  plugins: [
23
- laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'] }),
44
+ laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'], refresh: true }),
45
+ react(),
24
46
  ],
25
47
  });
26
48
  ```
27
49
 
28
- ```css
29
- /* resources/css/app.css */
30
- @import "tailwindcss";
31
- @import "preline/themes/theme.css";
32
- ```
50
+ ### Step 4: Init Preline in Inertia (MANDATORY)
33
51
 
34
52
  ```tsx
35
- // resources/js/app.tsx — init Preline after Inertia navigation
53
+ // resources/js/app.tsx
36
54
  import { router } from '@inertiajs/react';
37
55
 
56
+ // Re-init Preline components after every SPA navigation
38
57
  router.on('navigate', () => {
39
- // Re-init Preline components after SPA navigation
40
58
  setTimeout(() => {
41
59
  import('preline/preline').then(({ HSStaticMethods }) => {
42
60
  HSStaticMethods.autoInit();
@@ -45,6 +63,77 @@ router.on('navigate', () => {
45
63
  });
46
64
  ```
47
65
 
66
+ **Rule:** Without `HSStaticMethods.autoInit()`, dropdowns, modals, and accordions will NOT work after Inertia navigation.
67
+
68
+ ## Templates & Components (840+ free)
69
+
70
+ ### Where to Find
71
+
72
+ | Source | URL | What |
73
+ |---|---|---|
74
+ | **Docs (components)** | https://preline.co/docs | Buttons, modals, forms, tables, navs |
75
+ | **Examples (blocks)** | https://preline.co/examples.html | 220+ UI blocks (hero, testimonials, pricing, etc.) |
76
+ | **Pro templates** | https://preline.co/pro/templates.html | 21 dashboard/app templates (paid) |
77
+ | **GitHub** | https://github.com/htmlstreamofficial/preline | Source + examples |
78
+
79
+ ### How to Use Templates
80
+
81
+ 1. Browse https://preline.co/examples.html
82
+ 2. Click a block → copy the HTML/JSX
83
+ 3. Adapt to React + Inertia:
84
+ - Replace `<a href>` with `<Link href>` (Inertia)
85
+ - Replace `class=` with `className=`
86
+ - Add Preline `data-*` attributes for interactive components
87
+ - Use `usePage().props` for dynamic data
88
+
89
+ ### Example: Copy a Hero Block
90
+
91
+ ```tsx
92
+ // From preline.co/examples.html → Hero sections
93
+ // Adapt HTML to React component:
94
+ export default function HeroSection() {
95
+ return (
96
+ <div className="relative overflow-hidden">
97
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24">
98
+ <div className="text-center">
99
+ <h1 className="text-4xl sm:text-6xl font-bold text-foreground">
100
+ Build your next idea
101
+ </h1>
102
+ <p className="mt-4 text-lg text-muted-foreground max-w-2xl mx-auto">
103
+ Preline UI is an open-source set of prebuilt UI components.
104
+ </p>
105
+ <div className="mt-8 flex justify-center gap-3">
106
+ <Link href="/register"
107
+ className="px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover font-medium transition-colors">
108
+ Get Started
109
+ </Link>
110
+ <Link href="/docs"
111
+ className="px-6 py-3 bg-layer border border-layer-line text-layer-foreground rounded-lg hover:bg-layer-hover font-medium transition-colors">
112
+ Documentation
113
+ </Link>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ );
119
+ }
120
+ ```
121
+
122
+ ### Key Component Categories (free)
123
+
124
+ | Category | Count | Examples |
125
+ |---|---|---|
126
+ | **Navigation** | 20+ | Navbar, sidebar, breadcrumb, pagination |
127
+ | **Hero** | 11 | Landing page headers |
128
+ | **Cards** | 15+ | Product, blog, profile, pricing |
129
+ | **Forms** | 20+ | Login, register, contact, checkout |
130
+ | **Tables** | 10+ | Sortable, paginated, striped |
131
+ | **Modals** | 8+ | Confirmation, form, full-screen |
132
+ | **Dropdowns** | 10+ | Menu, select, multi-select |
133
+ | **Testimonials** | 10+ | Quotes, carousel, grid |
134
+ | **Pricing** | 8+ | Monthly/yearly toggle, comparison |
135
+ | **Dashboard** | 5+ | Stats, charts, activity feed |
136
+
48
137
  ## Token Architecture
49
138
 
50
139
  ```
@@ -0,0 +1,298 @@
1
+ # React UI Patterns — Loading, Errors, Empty States & Forms
2
+
3
+ **ALWAYS invoke when handling async UI states, forms, or user feedback.**
4
+
5
+ ## Core Principles
6
+
7
+ 1. **Never show stale UI** — loading only when actually loading
8
+ 2. **Always surface errors** — users must KNOW when something fails
9
+ 3. **Optimistic updates** — make UI feel instant
10
+ 4. **Progressive disclosure** — show content as it becomes available
11
+ 5. **Disable during operations** — prevent double-submit
12
+
13
+ ## Loading State Decision Tree
14
+
15
+ ```
16
+ Error?
17
+ → Yes: Show ErrorState with retry
18
+ → No ↓
19
+
20
+ Loading AND no data?
21
+ → Yes: Show Skeleton or Spinner
22
+ → No ↓
23
+
24
+ Has data?
25
+ → Yes + items: Render data
26
+ → Yes + empty: Show EmptyState
27
+ → No: Show Spinner (fallback)
28
+ ```
29
+
30
+ ```tsx
31
+ // ✅ CORRECT — only loading when no data
32
+ const { data, isLoading, error, refetch } = useQuery(...);
33
+
34
+ if (error) return <ErrorState error={error} onRetry={refetch} />;
35
+ if (isLoading && !data) return <Skeleton />;
36
+ if (!data?.items.length) return <EmptyState />;
37
+ return <ItemList items={data.items} />;
38
+
39
+ // ❌ WRONG — flashes spinner on refetch when cached data exists
40
+ if (isLoading) return <Spinner />;
41
+ ```
42
+
43
+ ### Skeleton vs Spinner
44
+
45
+ | Use Skeleton | Use Spinner |
46
+ |---|---|
47
+ | Known content shape (cards, tables, lists) | Unknown shape (modals, inline actions) |
48
+ | Initial page load | Button submissions |
49
+ | Content placeholders | Small inline operations |
50
+
51
+ ```tsx
52
+ // Skeleton component
53
+ function CardSkeleton() {
54
+ return (
55
+ <div className="bg-card border border-card-line rounded-xl p-6 animate-pulse">
56
+ <div className="h-4 w-3/4 bg-muted rounded" />
57
+ <div className="mt-3 h-3 w-1/2 bg-muted rounded" />
58
+ <div className="mt-6 h-10 w-full bg-muted rounded" />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ // Skeleton grid
64
+ function ListSkeleton({ count = 6 }: { count?: number }) {
65
+ return (
66
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
67
+ {Array.from({ length: count }, (_, i) => <CardSkeleton key={i} />)}
68
+ </div>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## Error Handling Hierarchy
74
+
75
+ ```
76
+ Level 1 — Inline error → Field validation (under input)
77
+ Level 2 — Toast notification → Recoverable, user can retry
78
+ Level 3 — Error banner → Page-level, data partially usable
79
+ Level 4 — Full error screen → Unrecoverable, needs user action
80
+ ```
81
+
82
+ ```tsx
83
+ // Reusable ErrorState
84
+ interface ErrorStateProps {
85
+ error: Error | string;
86
+ onRetry?: () => void;
87
+ title?: string;
88
+ }
89
+
90
+ function ErrorState({ error, onRetry, title }: ErrorStateProps) {
91
+ const message = typeof error === 'string' ? error : error.message;
92
+ return (
93
+ <div className="flex flex-col items-center justify-center py-12 text-center">
94
+ <div className="h-12 w-12 rounded-full bg-destructive/10 flex items-center justify-center mb-4">
95
+ <AlertCircle className="h-6 w-6 text-destructive" />
96
+ </div>
97
+ <h3 className="text-lg font-semibold text-foreground">{title ?? 'Something went wrong'}</h3>
98
+ <p className="mt-1 text-sm text-muted-foreground max-w-md">{message}</p>
99
+ {onRetry && (
100
+ <button onClick={onRetry}
101
+ className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover transition-colors">
102
+ Try Again
103
+ </button>
104
+ )}
105
+ </div>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ### NEVER Swallow Errors
111
+
112
+ ```tsx
113
+ // ✅ CORRECT — error surfaced to user
114
+ const mutation = useMutation({
115
+ mutationFn: createItem,
116
+ onSuccess: () => toast.success('Item created!'),
117
+ onError: (error) => {
118
+ console.error('createItem failed:', error);
119
+ toast.error('Failed to create item');
120
+ },
121
+ });
122
+
123
+ // ❌ WRONG — user sees nothing
124
+ try { await createItem(data); }
125
+ catch (e) { console.log(e); } // Silent failure!
126
+ ```
127
+
128
+ ## Empty States
129
+
130
+ **Every list/collection MUST have an empty state.**
131
+
132
+ ```tsx
133
+ // ✅ With empty state
134
+ function UserList({ users }: { users: User[] }) {
135
+ if (!users.length) {
136
+ return (
137
+ <EmptyState
138
+ icon={<Users className="h-8 w-8" />}
139
+ title="No users yet"
140
+ description="Invite your first team member"
141
+ action={{ label: 'Invite User', onClick: () => router.visit('/users/invite') }}
142
+ />
143
+ );
144
+ }
145
+ return <div className="space-y-2">{users.map(u => <UserCard key={u.id} user={u} />)}</div>;
146
+ }
147
+
148
+ // Reusable EmptyState
149
+ function EmptyState({ icon, title, description, action }: {
150
+ icon: ReactNode;
151
+ title: string;
152
+ description: string;
153
+ action?: { label: string; onClick: () => void };
154
+ }) {
155
+ return (
156
+ <div className="flex flex-col items-center justify-center py-16 text-center">
157
+ <div className="text-muted-foreground mb-4">{icon}</div>
158
+ <h3 className="text-lg font-semibold text-foreground">{title}</h3>
159
+ <p className="mt-1 text-sm text-muted-foreground max-w-sm">{description}</p>
160
+ {action && (
161
+ <button onClick={action.onClick}
162
+ className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover transition-colors">
163
+ {action.label}
164
+ </button>
165
+ )}
166
+ </div>
167
+ );
168
+ }
169
+ ```
170
+
171
+ ## Button States
172
+
173
+ ```tsx
174
+ // ✅ CORRECT — disabled + loading indicator
175
+ <button
176
+ onClick={handleSubmit}
177
+ disabled={!isValid || isSubmitting}
178
+ className="bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-primary-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
179
+ >
180
+ {isSubmitting ? (
181
+ <span className="flex items-center gap-2">
182
+ <Loader className="h-4 w-4 animate-spin" />
183
+ Saving...
184
+ </span>
185
+ ) : 'Save'}
186
+ </button>
187
+
188
+ // ❌ WRONG — user can click multiple times
189
+ <button onClick={handleSubmit}>
190
+ {isSubmitting ? 'Submitting...' : 'Submit'}
191
+ </button>
192
+ ```
193
+
194
+ ## Form Pattern (Inertia.js)
195
+
196
+ ```tsx
197
+ import { useForm } from '@inertiajs/react';
198
+
199
+ export default function CreateUser() {
200
+ const { data, setData, post, processing, errors, reset } = useForm({
201
+ name: '',
202
+ email: '',
203
+ });
204
+
205
+ const submit = (e: React.FormEvent) => {
206
+ e.preventDefault();
207
+ post('/users', {
208
+ onSuccess: () => {
209
+ toast.success('User created!');
210
+ reset();
211
+ },
212
+ onError: () => toast.error('Failed to create user'),
213
+ });
214
+ };
215
+
216
+ return (
217
+ <form onSubmit={submit} className="space-y-4">
218
+ <div>
219
+ <label className="block text-sm font-medium text-foreground mb-1">Name</label>
220
+ <input
221
+ value={data.name}
222
+ onChange={e => setData('name', e.target.value)}
223
+ className="w-full h-10 px-3 rounded-md border border-border bg-background text-foreground"
224
+ />
225
+ {errors.name && <p className="mt-1 text-sm text-destructive">{errors.name}</p>}
226
+ </div>
227
+
228
+ <div>
229
+ <label className="block text-sm font-medium text-foreground mb-1">Email</label>
230
+ <input
231
+ type="email"
232
+ value={data.email}
233
+ onChange={e => setData('email', e.target.value)}
234
+ className="w-full h-10 px-3 rounded-md border border-border bg-background text-foreground"
235
+ />
236
+ {errors.email && <p className="mt-1 text-sm text-destructive">{errors.email}</p>}
237
+ </div>
238
+
239
+ <button
240
+ type="submit"
241
+ disabled={processing}
242
+ className="bg-primary text-primary-foreground px-6 py-2 rounded-lg hover:bg-primary-hover disabled:opacity-50 transition-colors"
243
+ >
244
+ {processing ? (
245
+ <span className="flex items-center gap-2">
246
+ <Loader className="h-4 w-4 animate-spin" /> Creating...
247
+ </span>
248
+ ) : 'Create User'}
249
+ </button>
250
+ </form>
251
+ );
252
+ }
253
+ ```
254
+
255
+ ## Optimistic Updates
256
+
257
+ ```tsx
258
+ // Show result immediately, rollback on error
259
+ function ToggleFavorite({ item }: { item: Item }) {
260
+ const [optimistic, setOptimistic] = useState(item.isFavorite);
261
+
262
+ const toggle = () => {
263
+ setOptimistic(!optimistic); // Instant UI
264
+ router.post(`/items/${item.id}/favorite`, {}, {
265
+ preserveState: true,
266
+ onError: () => {
267
+ setOptimistic(item.isFavorite); // Rollback
268
+ toast.error('Failed to update');
269
+ },
270
+ });
271
+ };
272
+
273
+ return (
274
+ <button onClick={toggle} className="text-xl">
275
+ {optimistic ? '❤️' : '🤍'}
276
+ </button>
277
+ );
278
+ }
279
+ ```
280
+
281
+ ## Checklist — Before Shipping Any UI Component
282
+
283
+ - [ ] Error state handled and shown to user
284
+ - [ ] Loading state only when no data exists (no flash on refetch)
285
+ - [ ] Empty state for every collection/list
286
+ - [ ] Buttons disabled during async operations
287
+ - [ ] Buttons show loading indicator
288
+ - [ ] Form errors shown inline under fields
289
+ - [ ] Mutations have onError with user feedback
290
+ - [ ] Skeleton matches content layout shape
291
+
292
+ ## FORBIDDEN
293
+
294
+ 1. **`if (loading) return <Spinner />`** — check `loading && !data` instead
295
+ 2. **Silent catch** — always toast/display errors to user
296
+ 3. **No empty state** — every list needs one
297
+ 4. **Clickable button during submit** — always `disabled={processing}`
298
+ 5. **Console.log-only errors** — user must see feedback