start-vibing-stacks 2.0.2 → 2.0.4

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.2';
5
+ const VERSION = '2.0.4';
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.2",
3
+ "version": "2.0.4",
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": {
@@ -104,10 +104,121 @@ const Heavy = lazy(() => import('./HeavyComponent'));
104
104
  <Suspense fallback={<Loading />}><Heavy /></Suspense>
105
105
  ```
106
106
 
107
+ ## React 19 Hooks
108
+
109
+ ```tsx
110
+ // useActionState — form submission state
111
+ import { useActionState } from 'react';
112
+
113
+ function LoginForm() {
114
+ const [state, formAction, isPending] = useActionState(
115
+ async (prev, formData: FormData) => {
116
+ const result = await login(formData);
117
+ if (result.error) return { error: result.error };
118
+ redirect('/dashboard');
119
+ },
120
+ { error: null }
121
+ );
122
+
123
+ return (
124
+ <form action={formAction}>
125
+ <input name="email" type="email" />
126
+ {state.error && <p className="text-destructive">{state.error}</p>}
127
+ <button disabled={isPending}>
128
+ {isPending ? 'Signing in...' : 'Sign In'}
129
+ </button>
130
+ </form>
131
+ );
132
+ }
133
+
134
+ // useOptimistic — instant UI feedback
135
+ import { useOptimistic } from 'react';
136
+
137
+ function TodoList({ todos }: { todos: Todo[] }) {
138
+ const [optimisticTodos, addOptimistic] = useOptimistic(
139
+ todos,
140
+ (state, newTodo: Todo) => [...state, newTodo]
141
+ );
142
+
143
+ const addTodo = async (formData: FormData) => {
144
+ const todo = { id: crypto.randomUUID(), title: formData.get('title') as string, done: false };
145
+ addOptimistic(todo); // Instant UI
146
+ await saveTodo(todo); // Server sync
147
+ };
148
+
149
+ return <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
150
+ }
151
+ ```
152
+
153
+ ## State Management Selection
154
+
155
+ | Complexity | Solution | When |
156
+ |---|---|---|
157
+ | Simple local | `useState` | Single component, simple values |
158
+ | Complex local | `useReducer` | Multiple related state transitions |
159
+ | Parent-child | Lift state up | 1-2 levels |
160
+ | Subtree | Context + `useReducer` | Theme, auth, 3+ levels |
161
+ | Server state | React Query / SWR | API data, caching, refetch |
162
+ | Complex global | Zustand | Cross-feature, many consumers |
163
+
164
+ ## Error Boundaries
165
+
166
+ ```tsx
167
+ import { Component, type ReactNode } from 'react';
168
+
169
+ class ErrorBoundary extends Component<
170
+ { children: ReactNode; fallback?: ReactNode },
171
+ { hasError: boolean; error?: Error }
172
+ > {
173
+ state = { hasError: false, error: undefined as Error | undefined };
174
+
175
+ static getDerivedStateFromError(error: Error) {
176
+ return { hasError: true, error };
177
+ }
178
+
179
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
180
+ console.error('ErrorBoundary caught:', error, info);
181
+ // Send to Sentry/logging
182
+ }
183
+
184
+ render() {
185
+ if (this.state.hasError) {
186
+ return this.props.fallback ?? (
187
+ <div className="p-8 text-center">
188
+ <h2 className="text-lg font-semibold text-foreground">Something went wrong</h2>
189
+ <button onClick={() => this.setState({ hasError: false })}
190
+ className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-lg">
191
+ Try Again
192
+ </button>
193
+ </div>
194
+ );
195
+ }
196
+ return this.props.children;
197
+ }
198
+ }
199
+
200
+ // Usage: wrap routes/features
201
+ <ErrorBoundary fallback={<ErrorPage />}>
202
+ <DashboardFeature />
203
+ </ErrorBoundary>
204
+ ```
205
+
206
+ ## Component Types
207
+
208
+ | Type | Use | Example |
209
+ |---|---|---|
210
+ | **Server** (RSC) | Data fetching, static content | Page-level components |
211
+ | **Client** (`'use client'`) | Interactivity, hooks, browser APIs | Forms, modals, dropdowns |
212
+ | **Presentational** | Pure display, props only | `<Badge>`, `<Avatar>` |
213
+ | **Container** | Logic + state, renders presentational | `<UserListContainer>` |
214
+
107
215
  ## FORBIDDEN
108
216
 
109
- 1. **Class components** — function components only
217
+ 1. **Class components** — function components only (except ErrorBoundary)
110
218
  2. **Prop drilling** — use context or composition
111
219
  3. **Inline objects/functions in JSX** — causes re-renders
112
220
  4. **useEffect for derived state** — use useMemo
113
221
  5. **Mutating state directly** — always setState/dispatch
222
+ 6. **Index as key** — use stable unique ID (`uuid`, `id`)
223
+ 7. **Premature optimization** — profile first with React DevTools
224
+ 8. **useEffect for everything** — prefer server components for data
@@ -49,27 +49,139 @@ export default function Dashboard() {
49
49
 
50
50
  **Rule:** Never leave raw `console.log`. Always use controlled debug pattern.
51
51
 
52
- ## TailwindCSS Class Organization
52
+ ## TailwindCSS Class Organization (CONST Pattern)
53
+
54
+ **MANDATORY:** Define all CSS classes as constants at the TOP of the file, before the component.
55
+
56
+ ### Why
57
+
58
+ 1. **No re-renders** — string constants have stable references (no new object per render)
59
+ 2. **Single source of truth** — change style once, updates everywhere
60
+ 3. **Clean JSX** — readable templates, no class soup
61
+ 4. **Prevents React state warnings** — no inline objects/strings changing reference
62
+ 5. **Easy theming** — swap tokens in one place
63
+
64
+ ### Pattern
53
65
 
54
66
  ```tsx
55
- // ✅ Classes as CONST — clean JSX
56
- const STYLES = {
57
- container: 'flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm',
58
- title: 'text-2xl font-bold text-gray-900',
59
- button: 'px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition',
60
- grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
61
- };
67
+ // ═══════════════════════════════════════════
68
+ // 1. TRANSLATIONS (before hooks)
69
+ // ═══════════════════════════════════════════
70
+ const LABELS = {
71
+ title: __('dashboard.title'),
72
+ save: __('common.save'),
73
+ } as const;
62
74
 
75
+ // ═══════════════════════════════════════════
76
+ // 2. STYLES (semantic tokens, not raw colors)
77
+ // ═══════════════════════════════════════════
78
+ const STYLES = {
79
+ // Layout
80
+ page: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8',
81
+ section: 'space-y-6',
82
+ grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4',
83
+
84
+ // Cards
85
+ card: 'bg-card border border-card-line rounded-xl p-6 hover:shadow-md transition-shadow',
86
+ cardHeader: 'flex items-center justify-between border-b border-card-divider pb-4 mb-4',
87
+ cardTitle: 'text-lg font-semibold text-foreground',
88
+ cardDescription: 'text-sm text-muted-foreground mt-1',
89
+
90
+ // Table
91
+ table: 'w-full text-sm',
92
+ tableHeader: 'text-left text-muted-foreground font-medium border-b border-border',
93
+ tableRow: 'border-b border-border hover:bg-muted/50 transition-colors',
94
+ tableCell: 'px-4 py-3 text-foreground',
95
+
96
+ // Buttons
97
+ btnPrimary: 'px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
98
+ btnSecondary: 'px-4 py-2 bg-layer border border-layer-line text-layer-foreground rounded-lg hover:bg-layer-hover font-medium transition-colors',
99
+ btnDestructive: 'px-4 py-2 bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive-hover font-medium transition-colors',
100
+ btnGhost: 'px-4 py-2 text-muted-foreground hover:bg-muted rounded-lg transition-colors',
101
+
102
+ // Forms
103
+ input: 'w-full h-10 px-3 rounded-md border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-colors',
104
+ label: 'block text-sm font-medium text-foreground mb-1',
105
+ fieldError: 'mt-1 text-sm text-destructive',
106
+
107
+ // Status badges
108
+ badgeSuccess: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400',
109
+ badgeWarning: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400',
110
+ badgeDanger: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400',
111
+
112
+ // Typography
113
+ heading: 'text-2xl font-bold text-foreground',
114
+ subheading: 'text-lg font-semibold text-foreground',
115
+ body: 'text-sm text-foreground',
116
+ muted: 'text-sm text-muted-foreground',
117
+ } as const;
118
+
119
+ // ═══════════════════════════════════════════
120
+ // 3. COMPONENT
121
+ // ═══════════════════════════════════════════
63
122
  export default function Dashboard() {
123
+ const [data, setData] = useState(null);
124
+
64
125
  return (
65
- <div className={STYLES.container}>
66
- <h1 className={STYLES.title}>{LABELS.title}</h1>
126
+ <div className={STYLES.page}>
127
+ <h1 className={STYLES.heading}>{LABELS.title}</h1>
128
+ <div className={STYLES.grid}>
129
+ <div className={STYLES.card}>
130
+ <h2 className={STYLES.cardTitle}>Stats</h2>
131
+ <p className={STYLES.cardDescription}>Overview</p>
132
+ </div>
133
+ </div>
67
134
  </div>
68
135
  );
69
136
  }
137
+ ```
138
+
139
+ ### Composing Styles
70
140
 
71
- // ❌ Inline class soup
72
- <div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm"> // ❌
141
+ ```tsx
142
+ // Combine with template literal when conditional
143
+ <tr className={`${STYLES.tableRow} ${isSelected ? 'bg-primary/5' : ''}`}>
144
+
145
+ // ✅ clsx/cn for complex conditions
146
+ import { cn } from '@/lib/utils';
147
+ <button className={cn(STYLES.btnPrimary, isFullWidth && 'w-full', className)}>
148
+ ```
149
+
150
+ ### Rules
151
+
152
+ 1. **CONST at top** — before hooks, before component
153
+ 2. **`as const`** — TypeScript ensures immutability
154
+ 3. **Semantic tokens** — `bg-card` not `bg-white`, `text-foreground` not `text-gray-900`
155
+ 4. **No inline class strings > 3 utilities** — extract to STYLES
156
+ 5. **Shared styles** — create a `styles.ts` file for cross-component constants
157
+
158
+ ```tsx
159
+ // resources/js/styles.ts — shared across components
160
+ export const SHARED_STYLES = {
161
+ page: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8',
162
+ btnPrimary: 'px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover ...',
163
+ input: 'w-full h-10 px-3 rounded-md border border-border bg-background ...',
164
+ } as const;
165
+ ```
166
+
167
+ ### ❌ FORBIDDEN
168
+
169
+ ```tsx
170
+ // ❌ Inline class soup — unreadable, unstable reference
171
+ <div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm">
172
+
173
+ // ❌ Raw colors instead of tokens
174
+ const STYLES = { card: 'bg-white text-gray-900' }; // ❌ Breaks dark mode
175
+
176
+ // ❌ Dynamic class construction (breaks Tailwind purge)
177
+ const color = 'blue';
178
+ <div className={`bg-${color}-500`}> // ❌ Purged!
179
+
180
+ // ❌ Styles inside component (new object every render)
181
+ export default function Bad() {
182
+ const styles = { card: 'bg-card p-4' }; // ❌ Inside = new ref every render
183
+ return <div className={styles.card} />;
184
+ }
73
185
  ```
74
186
 
75
187
  ## SVG Icons