start-vibing-stacks 2.2.0 → 2.3.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.
@@ -0,0 +1,267 @@
1
+ # React 19+ Standards (with Inertia.js)
2
+
3
+ ## Version Requirements
4
+
5
+ - **ReactJS >= 19** — MANDATORY
6
+ - **TailwindCSS >= 4** — MANDATORY
7
+ - **Inertia.js >= 2** — MANDATORY
8
+
9
+ ## Translation Pattern (via Inertia shared props)
10
+
11
+ ```tsx
12
+ import __ from '@/Utils/translate';
13
+
14
+ // CORRECT: Translations as CONST at the top, BEFORE hooks
15
+ const LABELS = {
16
+ title: __('dashboard.title'),
17
+ save: __('common.save'),
18
+ cancel: __('common.cancel'),
19
+ errorRequired: __('errors.field_required'),
20
+ welcome: __('dashboard.welcome', { name: 'User' }),
21
+ };
22
+
23
+ export default function Dashboard() {
24
+ const [data, setData] = useState(null);
25
+
26
+ return <h1>{LABELS.title}</h1>;
27
+ }
28
+
29
+ // WRONG: __() inside JSX (Hook violation — usePage() is called internally)
30
+ return <h1>{__('dashboard.title')}</h1>; // NEVER
31
+ ```
32
+
33
+ **Rules:**
34
+ - Translations in `CONST` variables before state hooks
35
+ - New strings must be added to `lang/en/*.php` AND `lang/pt/*.php`
36
+ - Error strings centralized in `lang/*/errors.php`
37
+ - Use replacements for dynamic values: `__('key', { name: value })`
38
+
39
+ ## Debug Logging
40
+
41
+ ```tsx
42
+ const ENABLE_DASHBOARD_DEBUG = false;
43
+
44
+ const debugLog = (...args: unknown[]) => {
45
+ if (ENABLE_DASHBOARD_DEBUG) console.log('[Dashboard]', ...args);
46
+ };
47
+
48
+ export default function Dashboard() {
49
+ debugLog('Rendering with data:', data);
50
+ }
51
+ ```
52
+
53
+ **Rule:** Never leave raw `console.log`. Always use controlled debug pattern.
54
+
55
+ ## TailwindCSS Class Organization
56
+
57
+ ```tsx
58
+ // CORRECT: Classes as CONST — clean JSX
59
+ const STYLES = {
60
+ container: 'flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm',
61
+ title: 'text-2xl font-bold text-gray-900',
62
+ button: 'px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition',
63
+ grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
64
+ };
65
+
66
+ export default function Dashboard() {
67
+ return (
68
+ <div className={STYLES.container}>
69
+ <h1 className={STYLES.title}>{LABELS.title}</h1>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ // WRONG: Inline class soup
75
+ <div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm"> // NEVER
76
+ ```
77
+
78
+ ## SVG Icons
79
+
80
+ ```tsx
81
+ // CORRECT: Separate files, import with ?react
82
+ import CheckIcon from '@/Icons/CheckIcon.svg?react';
83
+ import { CheckIcon, AlertIcon } from '@/Icons';
84
+
85
+ // WRONG: Inline SVG (bloats JSX)
86
+ <svg viewBox="0 0 24 24">...</svg> // NEVER
87
+ ```
88
+
89
+ **Structure:**
90
+ ```
91
+ resources/js/Icons/
92
+ ├── index.js # Barrel export
93
+ ├── CheckIcon.svg
94
+ ├── AlertIcon.svg
95
+ └── SpinnerIcon.svg
96
+ ```
97
+
98
+ ## Inertia.js Hooks & Navigation
99
+
100
+ ### Accessing Shared Props
101
+
102
+ ```tsx
103
+ import { usePage } from '@inertiajs/react';
104
+
105
+ export default function Header() {
106
+ const { auth, locale, flash } = usePage().props;
107
+
108
+ return (
109
+ <nav>
110
+ <span>{auth.user?.name}</span>
111
+ {flash.success && <Alert>{flash.success}</Alert>}
112
+ </nav>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ### Forms with useForm
118
+
119
+ ```tsx
120
+ import { useForm } from '@inertiajs/react';
121
+
122
+ export default function CreateOrder() {
123
+ const { data, setData, post, processing, errors } = useForm({
124
+ product_id: '',
125
+ quantity: 1,
126
+ notes: '',
127
+ });
128
+
129
+ const handleSubmit = (e) => {
130
+ e.preventDefault();
131
+ post(route('orders.store'));
132
+ };
133
+
134
+ return (
135
+ <form onSubmit={handleSubmit}>
136
+ <Input
137
+ value={data.product_id}
138
+ onChange={(e) => setData('product_id', e.target.value)}
139
+ error={errors.product_id}
140
+ />
141
+ <Button type="submit" loading={processing}>
142
+ {processing ? <LoadingSpinner /> : LABELS.save}
143
+ </Button>
144
+ </form>
145
+ );
146
+ }
147
+ ```
148
+
149
+ **Rules:**
150
+ - Use `useForm` for ALL form submissions (handles CSRF, errors, loading)
151
+ - `processing` boolean for button loading states
152
+ - `errors` object maps to Form Request validation errors
153
+ - Never use `fetch()` or `axios` for form submissions
154
+
155
+ ### Navigation
156
+
157
+ ```tsx
158
+ import { Link, router } from '@inertiajs/react';
159
+
160
+ // SPA links (no full page reload)
161
+ <Link href={route('orders.index')}>Orders</Link>
162
+
163
+ // Programmatic navigation
164
+ router.visit(route('dashboard'));
165
+
166
+ // Partial reload (only refresh specific props)
167
+ router.reload({ only: ['stats', 'recentOrders'] });
168
+ ```
169
+
170
+ ## Loading States
171
+
172
+ ```tsx
173
+ // CORRECT: Always show loading feedback
174
+ export default function DataTable() {
175
+ const [loading, setLoading] = useState(true);
176
+
177
+ if (loading) {
178
+ return <SectionLoader />;
179
+ }
180
+
181
+ return <Table data={data} />;
182
+ }
183
+
184
+ // Button loading (from useForm)
185
+ <Button onClick={handleSave} disabled={processing}>
186
+ {processing ? <LoadingSpinner /> : LABELS.save}
187
+ </Button>
188
+ ```
189
+
190
+ **Rule:** Every data-heavy section needs a loading state.
191
+
192
+ ## Modal Data Flow
193
+
194
+ ```tsx
195
+ interface EditModalProps {
196
+ item: Item;
197
+ isOpen: boolean;
198
+ onClose: () => void;
199
+ onUpdated: () => void;
200
+ }
201
+
202
+ function EditModal({ item, isOpen, onClose, onUpdated }: EditModalProps) {
203
+ const { data, setData, put, processing } = useForm({
204
+ name: item.name,
205
+ });
206
+
207
+ const handleSave = (e) => {
208
+ e.preventDefault();
209
+ put(route('items.update', item.id), {
210
+ onSuccess: () => {
211
+ onUpdated();
212
+ onClose();
213
+ },
214
+ });
215
+ };
216
+ }
217
+
218
+ // Parent: use router.reload for refresh after modal mutation
219
+ <EditModal
220
+ item={selectedItem}
221
+ isOpen={showModal}
222
+ onClose={() => setShowModal(false)}
223
+ onUpdated={() => router.reload({ only: ['items'] })}
224
+ />
225
+ ```
226
+
227
+ ## Third-Party Libraries (Charts)
228
+
229
+ ```tsx
230
+ // CORRECT: Let React handle re-rendering
231
+ {chartData && (
232
+ <ApexChart
233
+ key={JSON.stringify(chartData)}
234
+ options={chartOptions}
235
+ series={chartData}
236
+ type="area"
237
+ />
238
+ )}
239
+
240
+ // WRONG: Manual DOM manipulation
241
+ const chartRef = useRef(null);
242
+ chartRef.current.updateSeries(newData); // NEVER
243
+
244
+ // Memoize expensive computations
245
+ const processedData = useMemo(() => {
246
+ return heavyTransform(rawData);
247
+ }, [rawData]);
248
+ ```
249
+
250
+ **Rules:**
251
+ - No `useRef` for updating third-party components
252
+ - Conditional rendering: `data && <Component />`
253
+ - `useMemo` for expensive computations
254
+ - Loading states before rendering charts
255
+
256
+ ## Forbidden Patterns
257
+
258
+ | Pattern | Reason | Use Instead |
259
+ |---------|--------|-------------|
260
+ | `fetch()` / `axios` for pages | Bypasses Inertia | `Inertia::render()` props |
261
+ | `__()` inside JSX | Hook violation | CONST at top |
262
+ | Inline SVGs | Bloats components | SVG files + `?react` |
263
+ | `<a href>` for internal links | Full reload | `<Link href>` |
264
+ | `window.location` | Full reload | `router.visit()` |
265
+ | Raw `console.log` | Uncontrolled | Debug constant pattern |
266
+ | Inline Tailwind soup | Unreadable | STYLES const object |
267
+ | `axios.post()` for forms | No CSRF/errors | `useForm().post()` |
@@ -1,92 +1,170 @@
1
1
  # Laravel Octane (RoadRunner) Patterns
2
2
 
3
- ## Critical Rules
3
+ ## How Octane Works
4
4
 
5
- Octane runs your app in a **long-lived process** different from traditional PHP.
5
+ Octane runs your app in a **long-lived worker process**. The application boots ONCE and handles many requests without restarting. This is fundamentally different from traditional PHP where each request boots a fresh process.
6
6
 
7
- ### DO
7
+ **Consequence:** Any state stored in static properties, globals, or singletons persists across requests and can leak between users.
8
8
 
9
- ```php
10
- // Dependency Injection over resolve()
11
- public function __construct(
12
- private readonly UserService $userService,
13
- ) {}
9
+ ## Critical Rules
14
10
 
15
- // Use Request object
16
- public function store(Request $request): JsonResponse
11
+ ### DO: Dependency Injection
12
+
13
+ ```php
14
+ // CORRECT: Constructor injection (resolved fresh per request scope)
15
+ class OrderController extends Controller
17
16
  {
18
- $name = $request->input('name');
17
+ public function __construct(
18
+ private readonly OrderService $orderService,
19
+ private readonly CacheManager $cache,
20
+ ) {}
19
21
  }
20
22
 
21
- // Persistent DB connections with flush
22
- // config/database.php
23
- 'options' => [PDO::ATTR_PERSISTENT => true]
24
- // Octane::prepare to flush connections between requests
23
+ // CORRECT: Method injection in controller actions
24
+ public function store(StoreOrderRequest $request, PaymentGateway $gateway): JsonResponse
25
+ {
26
+ $result = $gateway->charge($request->validated());
27
+ return response()->json($result, 201);
28
+ }
25
29
  ```
26
30
 
27
- ### NEVER
31
+ ### DO: Use the Request Object
28
32
 
29
33
  ```php
30
- // Static state (leaks between requests!)
31
- class Service {
32
- private static array $cache = []; // ❌ LEAKS
34
+ public function store(Request $request): JsonResponse
35
+ {
36
+ $name = $request->input('name');
37
+ $token = $request->bearerToken();
38
+ $ip = $request->ip();
39
+ $user = $request->user();
33
40
  }
34
-
35
- // Global variables
36
- global $user; // ❌
37
-
38
- // Modifying config at runtime
39
- config(['app.name' => 'New']); // ❌ Affects ALL requests
40
-
41
- // die() or exit()
42
- die('error'); // ❌ Kills the worker
43
-
44
- // Superglobals
45
- $_GET['id']; // ❌ Stale between requests
46
- $_POST['name']; // ❌
47
- $_SESSION['user']; // ❌
48
41
  ```
49
42
 
50
- ### AppServiceProvider Connection Flush
43
+ ### DO: Persistent DB Connections with Flush
51
44
 
52
45
  ```php
53
- // app/Providers/AppServiceProvider.php
54
- use Laravel\Octane\Facades\Octane;
55
-
56
- public function boot(): void
57
- {
58
- // MANDATORY: flush DB connections between requests
59
- Octane::prepare(fn ($sandbox) => $sandbox->flushDatabaseConnections());
60
- }
46
+ // config/database.php
47
+ 'mysql' => [
48
+ 'options' => [
49
+ PDO::ATTR_PERSISTENT => true,
50
+ ],
51
+ ],
52
+
53
+ // Octane automatically flushes connections between requests
54
+ // via Octane::prepare() — no manual work needed
61
55
  ```
62
56
 
63
- **Rule:** ALWAYS pair persistent connections with `Octane::prepare` flush in AppServiceProvider.
64
-
65
- ### Memoization in Workers
57
+ ### DO: Instance-scoped Memoization
66
58
 
67
59
  ```php
68
60
  // ✅ Scoped to instance, cleared per request
69
61
  class ProcessLeadsJob implements ShouldQueue
70
62
  {
71
63
  private array $lookupCache = [];
72
-
64
+
73
65
  public function handle(): void
74
66
  {
75
67
  foreach ($this->leads as $lead) {
76
- $result = $this->lookupCache[$lead->key]
68
+ $result = $this->lookupCache[$lead->key]
77
69
  ??= $this->expensiveLookup($lead->key);
78
70
  }
79
- // Cache is GC'd when job instance is destroyed
71
+ // Cache is garbage collected when job instance is destroyed
80
72
  }
81
73
  }
74
+ ```
75
+
76
+ ## NEVER Do These in Octane
77
+
78
+ ### No Static State
79
+
80
+ ```php
81
+ // WRONG: Static properties persist across ALL requests
82
+ class AuthService {
83
+ private static ?User $currentUser = null; // LEAKS between users!
84
+ private static array $cache = []; // Grows forever!
85
+ }
82
86
 
83
- // Static cache (persists across jobs in Octane!)
84
- private static array $cache = [];
87
+ // CORRECT: Instance properties (scoped to request)
88
+ class AuthService {
89
+ private ?User $currentUser = null;
90
+ private array $cache = [];
91
+ }
92
+ ```
93
+
94
+ ### No Global Variables
95
+
96
+ ```php
97
+ // WRONG
98
+ global $config;
99
+ $GLOBALS['user'] = $user;
100
+
101
+ // CORRECT: Use DI or config()
102
+ $value = config('app.name');
103
+ ```
104
+
105
+ ### No Runtime Config Mutation
106
+
107
+ ```php
108
+ // WRONG: Affects ALL concurrent requests in the worker
109
+ config(['app.timezone' => 'America/Sao_Paulo']);
110
+ config(['services.stripe.key' => $newKey]);
111
+
112
+ // CORRECT: Pass values explicitly
113
+ $this->service->processWithTimezone('America/Sao_Paulo');
114
+ ```
115
+
116
+ ### No Process Termination
117
+
118
+ ```php
119
+ // WRONG: Kills the entire worker process
120
+ die('Something went wrong');
121
+ exit(1);
122
+ dd($variable);
123
+
124
+ // CORRECT: Throw exceptions (handled by Laravel)
125
+ throw new RuntimeException('Something went wrong');
126
+ abort(500, 'Internal error');
127
+
128
+ // For debugging: use dump() without die
129
+ dump($variable); // Outputs without killing worker
130
+ Log::debug('Debug info', ['var' => $variable]);
131
+ ```
132
+
133
+ ### No Superglobals
134
+
135
+ ```php
136
+ // WRONG: Stale data from previous requests
137
+ $_GET['id'];
138
+ $_POST['name'];
139
+ $_SESSION['user'];
140
+ $_SERVER['HTTP_AUTHORIZATION'];
141
+ $_COOKIE['token'];
142
+
143
+ // CORRECT: Use Request object
144
+ $request->input('id');
145
+ $request->post('name');
146
+ $request->session()->get('user');
147
+ $request->header('Authorization');
148
+ $request->cookie('token');
149
+ ```
150
+
151
+ ### No Singleton Abuse
152
+
153
+ ```php
154
+ // WRONG: Singleton state leaks
155
+ app()->singleton('cart', fn () => new Cart());
156
+ // The same Cart instance serves ALL users!
157
+
158
+ // CORRECT: Use scoped bindings
159
+ app()->scoped('cart', fn () => new Cart());
160
+ // Fresh instance per request, cleared between requests
85
161
  ```
86
162
 
87
163
  ## RoadRunner Configuration (rr.yaml)
88
164
 
89
165
  ```yaml
166
+ version: '3'
167
+
90
168
  server:
91
169
  command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000"
92
170
 
@@ -94,17 +172,41 @@ http:
94
172
  address: 0.0.0.0:8000
95
173
  pool:
96
174
  num_workers: 4
97
- max_jobs: 500 # Restart worker after 500 requests (memory safety)
175
+ max_jobs: 500
98
176
  supervisor:
99
- max_worker_memory: 128 # MB
177
+ max_worker_memory: 128
100
178
 
101
179
  logs:
102
180
  level: info
181
+ encoding: json
103
182
  ```
104
183
 
184
+ ### Worker Tuning
185
+
186
+ | Setting | Default | Description |
187
+ |---------|---------|-------------|
188
+ | `num_workers` | CPU cores | Number of worker processes |
189
+ | `max_jobs` | 500 | Restart worker after N requests (memory safety) |
190
+ | `max_worker_memory` | 128 MB | Kill worker if exceeds memory |
191
+
192
+ **Rule:** Always set `max_jobs` to prevent memory leaks from accumulating.
193
+
105
194
  ## Database Safety
106
195
 
107
196
  - **NEVER** execute `db:wipe`, `migrate:fresh`, `migrate:refresh`, `db:reset`
108
197
  - **ALWAYS** use incremental migrations (`make:migration`)
109
- - If migration fails fix the file or create a new one
198
+ - If migration fails, fix the file or create a new one
110
199
  - Assume **all environments contain critical data**
200
+
201
+ ## Octane Checklist
202
+
203
+ - [ ] No `static` properties on service classes
204
+ - [ ] No global variables or `$GLOBALS`
205
+ - [ ] No `config()` mutations at runtime
206
+ - [ ] No `die()`, `exit()`, or `dd()` in production code
207
+ - [ ] No superglobals (`$_GET`, `$_POST`, `$_SESSION`, `$_SERVER`)
208
+ - [ ] No `app()->singleton()` for request-scoped data (use `scoped`)
209
+ - [ ] All services use constructor DI (not `app()` or `resolve()`)
210
+ - [ ] Memoization scoped to instance, not static
211
+ - [ ] `max_jobs` configured in `rr.yaml` for memory safety
212
+ - [ ] Persistent DB connections enabled with Octane flush