start-vibing-stacks 2.8.0 → 2.9.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.
- package/dist/index.js +2 -1
- package/dist/setup.js +10 -2
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
- package/stacks/frontend/react-api/skills/axios-laravel-api/SKILL.md +466 -0
- package/stacks/frontend/react-api/skills/react-api-standards/SKILL.md +509 -0
- package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +11 -2
- package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +9 -2
- package/stacks/php/skills/api-design/SKILL.md +281 -47
- package/stacks/php/skills/api-security/SKILL.md +128 -49
- package/stacks/php/skills/inertia-react/SKILL.md +16 -3
- package/stacks/php/skills/laravel-api-architecture/SKILL.md +650 -0
- package/stacks/php/skills/laravel-inertia-i18n/SKILL.md +11 -3
- package/stacks/php/skills/laravel-patterns/SKILL.md +123 -61
- package/stacks/php/stack.json +19 -6
- package/templates/CLAUDE-php.md +202 -101
package/templates/CLAUDE-php.md
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
**Branch:** main
|
|
8
8
|
**Date:** {{DATE}}
|
|
9
|
-
**Summary:** Initial project setup with start-vibing-stacks (PHP)
|
|
9
|
+
**Summary:** Initial project setup with start-vibing-stacks (PHP — API-first React SPA)
|
|
10
10
|
|
|
11
11
|
## 30 Seconds Overview
|
|
12
12
|
|
|
13
|
-
{{PROJECT_NAME}} is a PHP 8.3+ project using {{FRAMEWORK}}
|
|
13
|
+
{{PROJECT_NAME}} is a PHP 8.3+ project using {{FRAMEWORK}}, serving a JSON API
|
|
14
|
+
consumed by a React 19 + Vite SPA via Axios. Authentication uses Laravel
|
|
15
|
+
Sanctum SPA (HttpOnly session cookie + CSRF). Pages render shell + skeleton
|
|
16
|
+
instantly; data is fetched async via the `api` Axios instance.
|
|
14
17
|
|
|
15
18
|
## Stack
|
|
16
19
|
|
|
@@ -19,65 +22,117 @@
|
|
|
19
22
|
| Language | PHP >= 8.3 |
|
|
20
23
|
| Framework | {{FRAMEWORK}} |
|
|
21
24
|
| Server | Octane + RoadRunner |
|
|
22
|
-
|
|
|
25
|
+
| Auth | Sanctum SPA (HttpOnly cookie + CSRF) |
|
|
26
|
+
| API contract | JSON Resource + paginate (data + meta + links) |
|
|
27
|
+
| Frontend | React 19 + Vite + TailwindCSS 4 + React Router |
|
|
28
|
+
| HTTP client | Axios (`withCredentials`, `withXSRFToken`, interceptors) |
|
|
29
|
+
| Server cache | TanStack Query (recommended) |
|
|
23
30
|
| Database | {{DATABASE}} |
|
|
24
|
-
| Package Manager | Composer |
|
|
31
|
+
| Package Manager | Composer / npm (or pnpm/bun) |
|
|
25
32
|
| Static Analysis | PHPStan (level 6) |
|
|
26
|
-
| Testing | PHPUnit |
|
|
33
|
+
| Testing | PHPUnit / Pest |
|
|
27
34
|
| Code Style | PHP-CS-Fixer (PSR-12) |
|
|
28
35
|
|
|
29
|
-
## Architecture
|
|
36
|
+
## Architecture (API-first — DEFAULT)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Browser (React Vite SPA) Laravel 12 + Octane (RoadRunner)
|
|
40
|
+
───────────────────────── ───────────────────────────────────────
|
|
41
|
+
GET / → routes/web.php catch-all → app.blade (Vite shell)
|
|
42
|
+
GET /sanctum/csrf-cookie → XSRF-TOKEN cookie set
|
|
43
|
+
POST /login → AuthController → session cookie set
|
|
44
|
+
GET /api/orders → Route → Controller → FormRequest (rules + Policy)
|
|
45
|
+
→ Service (DB / business)
|
|
46
|
+
→ Resource (JSON whitelist)
|
|
47
|
+
→ JsonResponse
|
|
48
|
+
↓ async via Axios api instance
|
|
49
|
+
React Page renders shell + skeleton instantly; data arrives → renders rows.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Pipeline — One Responsibility per Layer
|
|
53
|
+
|
|
54
|
+
| Layer | File | Responsibility |
|
|
55
|
+
|-------|------|------------------|
|
|
56
|
+
| React Page | `resources/js/Pages/*/Index.jsx` | Render shell + skeleton; `api.get(...)` |
|
|
57
|
+
| Axios | `resources/js/lib/api.js` | `withCredentials`, CSRF, 401/403/419/422/5xx interceptors |
|
|
58
|
+
| Route | `routes/api.php` | URL ↔ Controller; group by `auth:sanctum` + `throttle` |
|
|
59
|
+
| Controller | `app/Http/Controllers/Api/*Controller.php` | Receive Request, call Service, return Resource |
|
|
60
|
+
| FormRequest | `app/Http/Requests/*/...Request.php` | `rules()` validate + `authorize()` → Policy |
|
|
61
|
+
| Policy | `app/Policies/*Policy.php` | `before()` super-admin bypass; per-action checks |
|
|
62
|
+
| Service | `app/Services/*Service.php` | Business logic, transactions, scoping |
|
|
63
|
+
| Resource | `app/Http/Resources/*Resource.php` | Eloquent → safe JSON; whitelist fields |
|
|
30
64
|
|
|
31
65
|
```
|
|
32
66
|
project/
|
|
33
67
|
├── app/
|
|
34
|
-
│ ├── Console/
|
|
35
|
-
│ ├── Exceptions/
|
|
68
|
+
│ ├── Console/ # Artisan commands
|
|
69
|
+
│ ├── Exceptions/ # Exception handlers
|
|
36
70
|
│ ├── Http/
|
|
37
|
-
│ │ ├── Controllers/
|
|
38
|
-
│ │
|
|
39
|
-
│ │
|
|
40
|
-
│ ├──
|
|
41
|
-
│
|
|
42
|
-
│ ├──
|
|
43
|
-
│ │ └──
|
|
44
|
-
│
|
|
45
|
-
│ ├──
|
|
46
|
-
│
|
|
47
|
-
├──
|
|
48
|
-
├──
|
|
49
|
-
│
|
|
71
|
+
│ │ ├── Controllers/
|
|
72
|
+
│ │ │ └── Api/ # JSON-only controllers (DEFAULT)
|
|
73
|
+
│ │ ├── Middleware/ # SecurityHeaders, RequestId, throttle, ...
|
|
74
|
+
│ │ ├── Requests/ # FormRequests grouped by resource
|
|
75
|
+
│ │ │ └── Order/
|
|
76
|
+
│ │ │ ├── IndexOrderRequest.php
|
|
77
|
+
│ │ │ └── StoreOrderRequest.php
|
|
78
|
+
│ │ └── Resources/ # JsonResource classes (date trait, whitelist)
|
|
79
|
+
│ ├── Models/ # Eloquent models (UUIDs, $fillable)
|
|
80
|
+
│ ├── Policies/ # OrderPolicy, UserPolicy, ... (before() bypass)
|
|
81
|
+
│ ├── Providers/ # AppServiceProvider (RateLimiter, Octane prepare)
|
|
82
|
+
│ ├── Services/ # Business logic (single responsibility)
|
|
83
|
+
│ │ ├── External/ # 3rd-party API services (with DTOs)
|
|
84
|
+
│ │ └── Helpers/ # Extracted logic for complex services
|
|
85
|
+
│ ├── Traits/ # FormatsDatesForApi, Auditable, ApiResponse
|
|
86
|
+
│ └── Jobs/ # Idempotent queue jobs
|
|
87
|
+
├── bootstrap/
|
|
88
|
+
│ └── app.php # ->statefulApi() + middleware aliases
|
|
89
|
+
├── config/
|
|
90
|
+
│ ├── sanctum.php # SANCTUM_STATEFUL_DOMAINS
|
|
91
|
+
│ ├── cors.php # supports_credentials=true, specific origin
|
|
92
|
+
│ ├── session.php # http_only, secure, same_site=lax
|
|
93
|
+
│ └── services.php # bridge env() ↔ application
|
|
50
94
|
├── database/
|
|
51
|
-
│ ├── factories/
|
|
52
|
-
│ ├── migrations/
|
|
53
|
-
│ └── seeders/
|
|
95
|
+
│ ├── factories/
|
|
96
|
+
│ ├── migrations/ # incremental ONLY
|
|
97
|
+
│ └── seeders/
|
|
54
98
|
├── lang/
|
|
55
|
-
│ ├── en/
|
|
56
|
-
│ └── pt/
|
|
57
|
-
├── public/
|
|
99
|
+
│ ├── en/ # PHP arrays — single source for all i18n
|
|
100
|
+
│ └── pt/
|
|
101
|
+
├── public/
|
|
58
102
|
├── resources/
|
|
59
|
-
│ ├──
|
|
60
|
-
│ ├──
|
|
103
|
+
│ ├── css/ # TailwindCSS 4
|
|
104
|
+
│ ├── views/
|
|
105
|
+
│ │ └── app.blade.php # Single Vite shell (catch-all)
|
|
61
106
|
│ └── js/
|
|
62
|
-
│ ├──
|
|
63
|
-
│ ├──
|
|
64
|
-
│ ├──
|
|
65
|
-
│ ├──
|
|
66
|
-
│
|
|
67
|
-
│
|
|
107
|
+
│ ├── app.jsx # createRoot + BrowserRouter + QueryClientProvider
|
|
108
|
+
│ ├── App.jsx # <Routes> + <ProtectedRoute>
|
|
109
|
+
│ ├── lib/
|
|
110
|
+
│ │ ├── api.js # Axios instance + interceptors
|
|
111
|
+
│ │ ├── auth.js # login / logout / fetchCurrentUser
|
|
112
|
+
│ │ └── queryClient.ts # TanStack Query
|
|
113
|
+
│ ├── store/ # Zustand / Context (auth, ui)
|
|
114
|
+
│ ├── Pages/ # Pages own routing client-side
|
|
115
|
+
│ │ └── Orders/
|
|
116
|
+
│ │ ├── Index.tsx # api.get('/api/orders') + skeleton
|
|
117
|
+
│ │ └── _components/
|
|
118
|
+
│ ├── Components/ # Reusable (ErrorState, EmptyState, ...)
|
|
119
|
+
│ ├── Layouts/ # AuthenticatedLayout, GuestLayout
|
|
120
|
+
│ └── Icons/ # SVG files imported with ?react
|
|
68
121
|
├── routes/
|
|
69
|
-
│ ├── api.php
|
|
70
|
-
│
|
|
71
|
-
|
|
122
|
+
│ ├── api.php # auth:sanctum + throttle:api groups
|
|
123
|
+
│ ├── web.php # /login, /logout, catch-all → app.blade
|
|
124
|
+
│ └── console.php
|
|
125
|
+
├── storage/
|
|
72
126
|
├── tests/
|
|
73
|
-
│ ├── Unit/
|
|
74
|
-
│ └── Feature/
|
|
75
|
-
├── .claude/
|
|
76
|
-
├── artisan
|
|
77
|
-
├── rr.yaml
|
|
78
|
-
├──
|
|
79
|
-
├──
|
|
80
|
-
|
|
127
|
+
│ ├── Unit/
|
|
128
|
+
│ └── Feature/ # API endpoints: happy + 401 + 403 + 422
|
|
129
|
+
├── .claude/ # AI agent configuration
|
|
130
|
+
├── artisan
|
|
131
|
+
├── rr.yaml # RoadRunner / Octane
|
|
132
|
+
├── vite.config.js # @vitejs/plugin-react + laravel-vite-plugin
|
|
133
|
+
├── composer.json
|
|
134
|
+
├── phpstan.neon
|
|
135
|
+
└── CLAUDE.md
|
|
81
136
|
```
|
|
82
137
|
|
|
83
138
|
## CLAUDE.md Update Rules
|
|
@@ -100,20 +155,59 @@ project/
|
|
|
100
155
|
|
|
101
156
|
## Critical Rules
|
|
102
157
|
|
|
158
|
+
### PHP / Laravel
|
|
159
|
+
|
|
103
160
|
- **PHP >= 8.3** — readonly, enums, typed constants, match expressions
|
|
104
161
|
- **`declare(strict_types=1)`** in EVERY PHP file
|
|
105
162
|
- **Octane-safe code** — no static state, no globals, no `die()`/`exit()`
|
|
106
163
|
- **Dependency Injection** — use DI over `app()` or `resolve()`
|
|
107
|
-
- **Thin controllers** — delegate business logic to
|
|
108
|
-
- **
|
|
164
|
+
- **Thin controllers** — delegate business logic to Services
|
|
165
|
+
- **FormRequest + Policy** — every protected endpoint goes through both
|
|
166
|
+
- **API Resource** — every response wrapped in `JsonResource`
|
|
109
167
|
- **Type everything** — properties, params, returns (no `mixed` without justification)
|
|
110
168
|
- **UUIDs** — all new models use `HasUuids` trait as primary key
|
|
111
|
-
- **Mass assignment** — always define `$fillable
|
|
112
|
-
- **Auditing** — critical models use `Loggable` trait
|
|
169
|
+
- **Mass assignment** — always define `$fillable`; never `$guarded = []`
|
|
113
170
|
- **Eloquent only** — no raw SQL unless strictly justified with parameter binding
|
|
114
171
|
- **Config immutable** — never use `config()` to SET values at runtime
|
|
115
172
|
- **Request object** — use `$request->input()`, never `$_GET`/`$_POST`/`$_SESSION`
|
|
116
173
|
|
|
174
|
+
### React / Frontend
|
|
175
|
+
|
|
176
|
+
- **Page contract** — render shell + skeleton on first paint; fetch via `api.get()`
|
|
177
|
+
- **NO `Inertia::render()`** for new endpoints — pure JSON API + Axios
|
|
178
|
+
- **`api` instance only** — never raw `axios` or `fetch` in components
|
|
179
|
+
- **422 binding** — render validation errors inline under fields (not toast)
|
|
180
|
+
- **LABELS + STYLES const** above the component (stable refs, no Hook abuse)
|
|
181
|
+
- **No tokens in `localStorage`** — Sanctum SPA cookie only
|
|
182
|
+
- **Skeletons match shape** of the loaded content (per-component)
|
|
183
|
+
- **Filters in URL** via `useSearchParams` — bookmarkable views
|
|
184
|
+
|
|
185
|
+
### Sanctum SPA Auth (MANDATORY config)
|
|
186
|
+
|
|
187
|
+
```php
|
|
188
|
+
// bootstrap/app.php
|
|
189
|
+
$middleware->statefulApi();
|
|
190
|
+
|
|
191
|
+
// config/cors.php
|
|
192
|
+
'supports_credentials' => true,
|
|
193
|
+
'allowed_origins' => [env('FRONTEND_URL')], // SPECIFIC origin
|
|
194
|
+
|
|
195
|
+
// config/session.php (production)
|
|
196
|
+
'http_only' => true,
|
|
197
|
+
'secure' => true, // HTTPS only
|
|
198
|
+
'same_site' => 'lax', // 'none' only if cross-origin (also requires secure)
|
|
199
|
+
|
|
200
|
+
// .env
|
|
201
|
+
SANCTUM_STATEFUL_DOMAINS=app.example.com
|
|
202
|
+
SESSION_DOMAIN=.example.com // shared subdomains
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
// resources/js/lib/api.js (Axios)
|
|
207
|
+
axios.defaults.withCredentials = true;
|
|
208
|
+
axios.defaults.withXSRFToken = true;
|
|
209
|
+
```
|
|
210
|
+
|
|
117
211
|
### Environment Variables & Secrets (MANDATORY)
|
|
118
212
|
|
|
119
213
|
> **NEVER use `env()` outside config files.** After `config:cache`, `env()` returns null everywhere except config files.
|
|
@@ -122,17 +216,14 @@ project/
|
|
|
122
216
|
|----------|--------|----------|
|
|
123
217
|
| `.env` | `env()` inside `config/*.php` only | API keys, DB credentials, secrets |
|
|
124
218
|
| `config/*.php` | `config('services.stripe.key')` | Application code access |
|
|
125
|
-
| Frontend (
|
|
219
|
+
| Frontend (`VITE_*`) | `import.meta.env.VITE_API_URL` | PUBLIC values ONLY (bundled) |
|
|
126
220
|
|
|
127
221
|
```php
|
|
128
222
|
// config/services.php — Bridge between .env and application
|
|
129
223
|
return [
|
|
130
|
-
'openai' => [
|
|
131
|
-
'key' => env('OPENAI_KEY'),
|
|
132
|
-
],
|
|
133
224
|
'stripe' => [
|
|
134
|
-
'key'
|
|
135
|
-
'secret' => env('STRIPE_SECRET'),
|
|
225
|
+
'key' => env('STRIPE_KEY'), // publishable (public ok)
|
|
226
|
+
'secret' => env('STRIPE_SECRET'), // server-side ONLY
|
|
136
227
|
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
|
|
137
228
|
],
|
|
138
229
|
];
|
|
@@ -140,76 +231,84 @@ return [
|
|
|
140
231
|
// In code: ALWAYS use config()
|
|
141
232
|
$apiKey = config('services.openai.key');
|
|
142
233
|
|
|
143
|
-
// FORBIDDEN
|
|
144
|
-
$apiKey = env('OPENAI_KEY');
|
|
234
|
+
// FORBIDDEN
|
|
235
|
+
$apiKey = env('OPENAI_KEY'); // returns null when config is cached
|
|
145
236
|
```
|
|
146
237
|
|
|
147
238
|
### Frontend Secret Isolation (MANDATORY)
|
|
148
239
|
|
|
149
|
-
> **NEVER send API
|
|
240
|
+
> **NEVER send API secrets to the frontend bundle.** Anything in `VITE_*` or
|
|
241
|
+
> in JSON returned to the browser is PUBLIC.
|
|
150
242
|
|
|
151
|
-
```
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
'stripePublicKey' => config('services.stripe.key'), // pk_ only
|
|
161
|
-
]);
|
|
162
|
-
|
|
163
|
-
// For operations requiring secrets: use backend API routes
|
|
164
|
-
// Frontend calls /api/payment → backend uses secret server-side
|
|
243
|
+
```js
|
|
244
|
+
// FORBIDDEN — secret embedded in bundle
|
|
245
|
+
const stripeSecret = import.meta.env.VITE_STRIPE_SECRET;
|
|
246
|
+
|
|
247
|
+
// CORRECT — only publishable keys cross the wire
|
|
248
|
+
const stripePublishable = import.meta.env.VITE_STRIPE_KEY; // pk_...
|
|
249
|
+
|
|
250
|
+
// For operations needing the secret: call your Laravel API
|
|
251
|
+
// Browser → POST /api/payment → backend uses the SECRET server-side → JSON
|
|
165
252
|
```
|
|
166
253
|
|
|
167
254
|
## FORBIDDEN
|
|
168
255
|
|
|
169
|
-
###
|
|
256
|
+
### Architecture (CRITICAL)
|
|
257
|
+
|
|
258
|
+
| Action | Reason |
|
|
259
|
+
|--------|--------|
|
|
260
|
+
| `Inertia::render()` for new endpoints | Blocks first paint on DB query — use API + Axios |
|
|
261
|
+
| Eloquent query inside a Controller | Move it to a Service |
|
|
262
|
+
| FormRequest `authorize(): true` blindly | Must call Policy or be index w/ Service scope |
|
|
263
|
+
| Resource performing DB queries | Use `whenLoaded()` and pre-load in Service |
|
|
264
|
+
| `axios.get(...)` directly in components | Bypasses interceptors — use `@/lib/api` |
|
|
265
|
+
| `localStorage.setItem('token', ...)` | XSS-readable — use HttpOnly Sanctum cookie |
|
|
266
|
+
| Page that blocks render on first fetch | Defeats the API-first model |
|
|
267
|
+
|
|
268
|
+
### Security
|
|
170
269
|
|
|
171
270
|
| Action | Reason |
|
|
172
271
|
|--------|--------|
|
|
173
272
|
| `env()` outside config files | Returns null when config is cached — use `config()` |
|
|
174
|
-
| Send API
|
|
175
|
-
| `$guarded = []` on models | Allows mass assignment
|
|
176
|
-
| `DB::raw()` with user input | SQL injection — use Eloquent
|
|
273
|
+
| Send API secrets to the browser | Embedded in bundle — keep server-side |
|
|
274
|
+
| `$guarded = []` on models | Allows mass assignment — use `$fillable` |
|
|
275
|
+
| `DB::raw()` with user input | SQL injection — use Eloquent / parameterized |
|
|
177
276
|
| `{!! $userInput !!}` | XSS — use `{{ }}` (auto-escaped) |
|
|
178
277
|
| Dynamic code execution functions | Remote code execution risk |
|
|
179
278
|
| `md5()` / `sha1()` for passwords | Weak hashing — use `Hash::make()` |
|
|
180
|
-
| `unserialize()` on user data | Object injection — use JSON
|
|
181
|
-
|
|
|
279
|
+
| `unserialize()` on user data | Object injection — use JSON casts |
|
|
280
|
+
| `'allowed_origins' => ['*']` with credentials | Browser rejects — list specific origins |
|
|
182
281
|
| `createToken('x', ['*'])` | Over-privileged — use specific abilities |
|
|
183
282
|
| Trust `X-Forwarded-For` directly | Spoofable — use trusted proxies config |
|
|
184
283
|
| `$_GET` / `$_POST` / `$_SESSION` | Stale in Octane — use `$request->input()` |
|
|
284
|
+
| Login route on `/api/*` | Login goes on `routes/web.php` (session middleware) |
|
|
185
285
|
|
|
186
|
-
### Backend
|
|
286
|
+
### Backend (Octane safety)
|
|
187
287
|
|
|
188
288
|
| Action | Reason |
|
|
189
289
|
|--------|--------|
|
|
190
290
|
| Business logic in controllers | Move to Service classes |
|
|
191
|
-
| `static` properties on services | Memory leaks
|
|
291
|
+
| `static` properties on services | Memory leaks across requests |
|
|
192
292
|
| Global variables / superglobals | Stale state in Octane |
|
|
193
|
-
| `die()` / `exit()` | Kills the
|
|
293
|
+
| `die()` / `exit()` / `dd()` | Kills the worker process |
|
|
194
294
|
| `config(['key' => 'val'])` at runtime | Affects all concurrent requests |
|
|
195
295
|
| `migrate:fresh` / `db:wipe` / `db:reset` | Destroys production data |
|
|
196
|
-
| `app()` / `resolve()` in constructors | Use constructor DI
|
|
197
|
-
| `
|
|
198
|
-
| `dd()` / `dump()` in production code | Use structured `Log::info()` |
|
|
296
|
+
| `app()` / `resolve()` in constructors | Use constructor DI |
|
|
297
|
+
| `dump()` / `dd()` in production | Use structured `Log::info()` |
|
|
199
298
|
|
|
200
299
|
### Frontend (React)
|
|
201
300
|
|
|
202
301
|
| Action | Reason |
|
|
203
302
|
|--------|--------|
|
|
204
|
-
|
|
|
205
|
-
| `
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
| `window.location` for navigation | Full reload — use `router.visit()` |
|
|
303
|
+
| `Inertia::render()` / `useForm()` from Inertia | Use API + Axios (Inertia is LEGACY) |
|
|
304
|
+
| `fetch()` for page data | Bypasses interceptors — use `api.get()` |
|
|
305
|
+
| `<a href>` for internal links | Full page reload — use `<Link>` from react-router-dom |
|
|
306
|
+
| `window.location` for SPA navigation | Full reload — use `useNavigate()` |
|
|
209
307
|
| Inline SVGs in JSX | Bloats components — use SVG files with `?react` |
|
|
210
308
|
| Raw `console.log` | Uncontrolled — use debug constant pattern |
|
|
211
309
|
| Inline Tailwind class soup | Unreadable — use STYLES const object |
|
|
212
|
-
| `
|
|
310
|
+
| `useEffect` to derive state | Anti-pattern — use `useMemo` |
|
|
311
|
+
| Skipping skeleton/empty/error states | Bad UX — all three are mandatory per page |
|
|
213
312
|
|
|
214
313
|
## UI/UX Design Intelligence
|
|
215
314
|
|
|
@@ -219,9 +318,11 @@ return Inertia::render('Dashboard', [
|
|
|
219
318
|
|
|
220
319
|
```bash
|
|
221
320
|
vendor/bin/phpstan analyse --level=6 # Static analysis
|
|
222
|
-
vendor/bin/phpunit # Tests
|
|
321
|
+
vendor/bin/phpunit # Tests (or Pest)
|
|
223
322
|
vendor/bin/php-cs-fixer fix --dry-run # Code style
|
|
224
323
|
php artisan test # Laravel test runner
|
|
324
|
+
npx tsc --noEmit # Frontend type check
|
|
325
|
+
npx eslint resources/js/ # Frontend lint (optional)
|
|
225
326
|
```
|
|
226
327
|
|
|
227
328
|
## Database
|
|
@@ -249,15 +350,15 @@ $results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
|
|
|
249
350
|
## Workflow
|
|
250
351
|
|
|
251
352
|
```
|
|
252
|
-
0. TODO LIST →
|
|
253
|
-
1. BRANCH →
|
|
254
|
-
2. RESEARCH →
|
|
255
|
-
3. IMPLEMENT →
|
|
256
|
-
4. TEST →
|
|
257
|
-
5. DOCUMENT →
|
|
258
|
-
6. UPDATE → Update THIS FILE (CLAUDE.md)
|
|
259
|
-
7. QUALITY → PHPStan + PHPUnit + PHP-CS-Fixer
|
|
260
|
-
8. COMMIT → Conventional commits
|
|
353
|
+
0. TODO LIST → Detailed plan from prompt; identify affected layer(s)
|
|
354
|
+
1. BRANCH → feature/ | fix/ | refactor/ | test/
|
|
355
|
+
2. RESEARCH → research-web for new features (cite sources)
|
|
356
|
+
3. IMPLEMENT → Bottom-up: FormRequest+Policy → Service → Resource → Controller → Route → React page
|
|
357
|
+
4. TEST → PHPUnit feature tests (happy + 401 + 403 + 422); React: api.test.ts
|
|
358
|
+
5. DOCUMENT → documenter agent for modified files; update domain docs
|
|
359
|
+
6. UPDATE → Update THIS FILE (CLAUDE.md) — Last Change + relevant sections
|
|
360
|
+
7. QUALITY → PHPStan + PHPUnit + PHP-CS-Fixer + tsc + eslint
|
|
361
|
+
8. COMMIT → Conventional commits; merge to main
|
|
261
362
|
```
|
|
262
363
|
|
|
263
364
|
## Domain Documentation
|