create-win-project 1.4.0 → 2.0.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/LICENSE +1 -1
- package/README.md +54 -147
- package/checks/check-compatibility.js +37 -8
- package/checks/check-generated-project.js +27 -3
- package/checks/check-library.js +13 -3
- package/checks/check-package.js +15 -0
- package/checks/classify-changes.js +5 -15
- package/checks/run-compatibility-shard.js +21 -0
- package/docs/README.md +20 -0
- package/docs/capabilities.md +13 -0
- package/docs/compatibility.md +13 -0
- package/docs/generated-project.md +12 -0
- package/docs/getting-started.md +26 -0
- package/docs/{ARCHITECTURE.md → maintainers/architecture.md} +3 -3
- package/docs/maintainers/ci-strategy.md +46 -0
- package/docs/{CONTRIBUTING.md → maintainers/contributing.md} +7 -7
- package/docs/migration-v2.md +13 -0
- package/docs/production-contract.md +24 -0
- package/library/INDEX.md +1 -1
- package/library/compatibility-impact.json +14 -0
- package/library/development-tools/devops/makefile/commands.md +11 -0
- package/library/development-tools/devops/makefile/definition.json +2 -2
- package/library/development-tools/devops/makefile/docker.md +9 -0
- package/library/development-tools/devops/makefile/validation.md +5 -0
- package/library/features/sqlalchemy-alembic.md +17 -0
- package/library/optional-features/concerns/zod/errors.md +5 -0
- package/library/optional-features/concerns/zod/testing.md +5 -0
- package/library/optional-features/concerns/zod/validation.md +21 -0
- package/library/optional-features/styling/css-modules/definition.json +3 -2
- package/library/optional-features/styling/css-modules/responsive.md +5 -0
- package/library/optional-features/styling/css-modules/theme.md +7 -0
- package/library/stacks/expo/definition.json +2 -2
- package/library/stacks/fastapi/architecture.md +40 -0
- package/library/stacks/fastapi/definition.json +39 -0
- package/library/stacks/fastapi/runtime.md +25 -0
- package/library/stacks/fastapi/security.md +26 -0
- package/library/stacks/fastapi/structure.md +27 -0
- package/library/stacks/fastapi/testing.md +23 -0
- package/library/stacks/nextjs/definition.json +2 -2
- package/library/stacks/no-frontend/definition.json +1 -1
- package/library/stacks/react-vite/definition.json +2 -2
- package/library/tested-versions.json +42 -2
- package/library/universal/coding-rules/definition.json +3 -3
- package/library/universal/coding-rules/hygiene.md +9 -0
- package/library/universal/coding-rules/naming.md +17 -0
- package/library/universal/git-conventions/branches.md +5 -0
- package/library/universal/git-conventions/commits.md +7 -0
- package/library/universal/git-conventions/definition.json +4 -2
- package/library/universal/git-conventions/workflow.md +5 -0
- package/library/universal/typescript/boundaries.md +13 -0
- package/library/universal/typescript/definition.json +3 -3
- package/library/universal/typescript/errors.md +5 -0
- package/library/universal/typescript/patterns.md +7 -0
- package/package.json +4 -6
- package/src/cli/arguments.js +11 -0
- package/src/cli/main.js +18 -0
- package/src/cli/questions.js +16 -15
- package/src/cli/system-check.js +22 -2
- package/src/engine/load-library.js +3 -2
- package/src/engine/project-files.js +10 -1
- package/src/engine/project-guidance.js +2 -1
- package/src/engine/project-shapes.js +4 -4
- package/src/engine/render-templates.js +3 -0
- package/src/engine/tested-versions.js +21 -2
- package/src/engine/upgrade-report.js +20 -0
- package/src/stacks/available-stacks.js +2 -0
- package/src/stacks/backends/fastapi/ci.js +3 -0
- package/src/stacks/backends/fastapi/create-files.js +874 -0
- package/src/stacks/backends/fastapi/docker.js +75 -0
- package/src/stacks/backends/fastapi/environment.js +3 -0
- package/src/stacks/backends/fastapi/index.js +32 -0
- package/src/stacks/compose-files.js +19 -1
- package/src/stacks/create-project.js +128 -7
- package/src/stacks/frontends/nextjs/index.js +1 -1
- package/src/stacks/frontends/react-native/create-files.js +4 -1
- package/src/stacks/frontends/react-native/environment.js +1 -1
- package/src/stacks/frontends/react-native/index.js +1 -1
- package/src/stacks/frontends/react-vite/environment.js +1 -1
- package/src/stacks/frontends/react-vite/index.js +1 -1
- package/src/stacks/shared/capability-packs.js +31 -0
- package/src/stacks/shared/environment.js +12 -4
- package/src/stacks/shared/javascript-package.js +6 -0
- package/templates/ci/fastapi.yml +62 -0
- package/templates/docker/compose-prod/fastapi.yml +52 -0
- package/templates/docker/compose-prod/springboot.yml +19 -1
- package/templates/docker/dockerfile/fastapi.dev.dockerfile +9 -0
- package/templates/docker/dockerfile/fastapi.prod.dockerfile +11 -0
- package/templates/docker/dockerfile/nextjs.prod.dockerfile +1 -0
- package/templates/docker/dockerfile/springboot.prod.dockerfile +4 -1
- package/templates/docker/dockerfile/vite.prod.dockerfile +2 -1
- package/templates/makefile/fastapi.mk +97 -0
- package/library/development-tools/devops/makefile/makefile.md +0 -556
- package/library/optional-features/concerns/zod.md +0 -174
- package/library/optional-features/styling/css-modules/css-modules-extensions.md +0 -267
- package/library/universal/coding-rules/coding-rules.md +0 -281
- package/library/universal/git-conventions/git-conventions.md +0 -186
- package/library/universal/typescript/typescript.md +0 -272
- /package/docs/{CONTENT_MODEL.md → maintainers/content-model.md} +0 -0
- /package/docs/{DEPENDENCY_MAINTENANCE.md → maintainers/dependencies.md} +0 -0
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# Zod + React Hook Form — Validation
|
|
2
|
-
|
|
3
|
-
> **When to use:** Any project with forms or untrusted input (API responses, URL params, env vars).
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# 1. Schema First, Always
|
|
8
|
-
|
|
9
|
-
Write the Zod schema before writing the form component or service. The schema is the source of truth.
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
// features/auth/schemas/login.schema.ts
|
|
13
|
-
import { z } from 'zod'
|
|
14
|
-
|
|
15
|
-
export const loginSchema = z.object({
|
|
16
|
-
email: z.string().email('Invalid email address'),
|
|
17
|
-
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
export type LoginInput = z.infer<typeof loginSchema>
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Never write a separate TypeScript type for form data — infer it from the schema.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
# 2. Schema Placement
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
Feature-specific form? → features/[name]/schemas/[name].schema.ts
|
|
31
|
-
Shared across multiple features? → src/schemas/[name].schema.ts (web)
|
|
32
|
-
schemas/[name].schema.ts (RN)
|
|
33
|
-
API response shape? → features/[name]/types.ts (type only, no runtime parse needed)
|
|
34
|
-
OR parse with z.safeParse() at the API boundary if strict
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
# 3. Web Form (React Hook Form)
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
// features/auth/components/LoginForm.tsx
|
|
43
|
-
'use client' // Next.js only — omit for React Vite
|
|
44
|
-
import { useForm } from 'react-hook-form'
|
|
45
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
46
|
-
import { loginSchema, type LoginInput } from '../schemas/login.schema'
|
|
47
|
-
|
|
48
|
-
export function LoginForm({ onSubmit }: { onSubmit: (data: LoginInput) => void }) {
|
|
49
|
-
const {
|
|
50
|
-
register,
|
|
51
|
-
handleSubmit,
|
|
52
|
-
formState: { errors, isSubmitting },
|
|
53
|
-
} = useForm<LoginInput>({ resolver: zodResolver(loginSchema) })
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
57
|
-
<input {...register('email')} placeholder="Email" />
|
|
58
|
-
{errors.email && <span>{errors.email.message}</span>}
|
|
59
|
-
|
|
60
|
-
<input type="password" {...register('password')} placeholder="Password" />
|
|
61
|
-
{errors.password && <span>{errors.password.message}</span>}
|
|
62
|
-
|
|
63
|
-
<button type="submit" disabled={isSubmitting}>
|
|
64
|
-
{isSubmitting ? 'Submitting...' : 'Login'}
|
|
65
|
-
</button>
|
|
66
|
-
</form>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
# 4. React Native Form (React Hook Form + Controller)
|
|
74
|
-
|
|
75
|
-
React Native has no native `<input>` — use `Controller` to bridge RHF with RN's `TextInput`.
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
// features/auth/components/LoginForm.tsx (React Native)
|
|
79
|
-
import { Controller, useForm } from 'react-hook-form'
|
|
80
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
81
|
-
import { loginSchema, type LoginInput } from '../schemas/login.schema'
|
|
82
|
-
|
|
83
|
-
export function LoginForm({ onSubmit }: { onSubmit: (data: LoginInput) => void }) {
|
|
84
|
-
const {
|
|
85
|
-
control,
|
|
86
|
-
handleSubmit,
|
|
87
|
-
formState: { errors, isSubmitting },
|
|
88
|
-
} = useForm<LoginInput>({ resolver: zodResolver(loginSchema) })
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<View className="gap-4">
|
|
92
|
-
<Controller
|
|
93
|
-
control={control}
|
|
94
|
-
name="email"
|
|
95
|
-
render={({ field }) => (
|
|
96
|
-
<TextInput
|
|
97
|
-
value={field.value}
|
|
98
|
-
onChangeText={field.onChange}
|
|
99
|
-
placeholder="Email"
|
|
100
|
-
keyboardType="email-address"
|
|
101
|
-
autoCapitalize="none"
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
/>
|
|
105
|
-
{errors.email && <Text>{errors.email.message}</Text>}
|
|
106
|
-
|
|
107
|
-
<Controller
|
|
108
|
-
control={control}
|
|
109
|
-
name="password"
|
|
110
|
-
render={({ field }) => (
|
|
111
|
-
<TextInput
|
|
112
|
-
value={field.value}
|
|
113
|
-
onChangeText={field.onChange}
|
|
114
|
-
placeholder="Password"
|
|
115
|
-
secureTextEntry
|
|
116
|
-
/>
|
|
117
|
-
)}
|
|
118
|
-
/>
|
|
119
|
-
{errors.password && <Text>{errors.password.message}</Text>}
|
|
120
|
-
|
|
121
|
-
<PrimaryButton
|
|
122
|
-
title={isSubmitting ? 'Submitting...' : 'Login'}
|
|
123
|
-
onPress={handleSubmit(onSubmit)}
|
|
124
|
-
disabled={isSubmitting}
|
|
125
|
-
/>
|
|
126
|
-
</View>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
# 5. Validating API Responses
|
|
134
|
-
|
|
135
|
-
Parse untrusted data at the boundary, not deep in hooks or components.
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
// features/products/services/productService.ts
|
|
139
|
-
import { z } from 'zod'
|
|
140
|
-
|
|
141
|
-
const productSchema = z.object({
|
|
142
|
-
id: z.string(),
|
|
143
|
-
name: z.string(),
|
|
144
|
-
price: z.number(),
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
export async function getProduct(id: string) {
|
|
148
|
-
const raw = await apiClient.get(`/products/${id}`)
|
|
149
|
-
return productSchema.parse(raw) // throws if shape is wrong
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
# 6. Rules
|
|
156
|
-
|
|
157
|
-
- Schema first — always write the schema before the form.
|
|
158
|
-
- Never write a separate TypeScript type for form input — use `z.infer<typeof schema>`.
|
|
159
|
-
- Validate on the server too (or in the service) — never trust client-only validation.
|
|
160
|
-
- Use `z.string().min(1)` not `z.string().nonempty()` — better cross-platform compatibility.
|
|
161
|
-
- Route on `error.code` after submission, never on `error.message`.
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
# 7. Agent Quick Reference
|
|
166
|
-
|
|
167
|
-
```text
|
|
168
|
-
New form? → schema first in features/[name]/schemas/
|
|
169
|
-
→ useForm + zodResolver
|
|
170
|
-
→ web: register(); RN: Controller + TextInput
|
|
171
|
-
New type for form data? → z.infer<typeof schema> instead
|
|
172
|
-
Validate API response? → z.safeParse() or schema.parse() in service
|
|
173
|
-
Shared schema? → src/schemas/ or schemas/ (not in features/)
|
|
174
|
-
```
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
# Styling: CSS Modules — Extensions
|
|
2
|
-
|
|
3
|
-
Extends your existing CSS Modules playbook with missing patterns.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Dark Mode with CSS Variables
|
|
8
|
-
```css
|
|
9
|
-
/* styles/tokens.css — single source of truth */
|
|
10
|
-
:root {
|
|
11
|
-
/* Colors */
|
|
12
|
-
--color-background: #ffffff;
|
|
13
|
-
--color-foreground: #0a0a0a;
|
|
14
|
-
--color-primary: #1a1a2e;
|
|
15
|
-
--color-primary-foreground: #f0f0f0;
|
|
16
|
-
--color-muted: #f5f5f5;
|
|
17
|
-
--color-muted-foreground: #737373;
|
|
18
|
-
--color-border: #e5e5e5;
|
|
19
|
-
--color-error: #dc2626;
|
|
20
|
-
--color-success: #16a34a;
|
|
21
|
-
|
|
22
|
-
/* Typography */
|
|
23
|
-
--font-size-sm: 0.875rem;
|
|
24
|
-
--font-size-base: 1rem;
|
|
25
|
-
--font-size-lg: 1.125rem;
|
|
26
|
-
--font-size-xl: 1.25rem;
|
|
27
|
-
--font-size-2xl: 1.5rem;
|
|
28
|
-
|
|
29
|
-
/* Spacing */
|
|
30
|
-
--space-1: 0.25rem;
|
|
31
|
-
--space-2: 0.5rem;
|
|
32
|
-
--space-3: 0.75rem;
|
|
33
|
-
--space-4: 1rem;
|
|
34
|
-
--space-6: 1.5rem;
|
|
35
|
-
--space-8: 2rem;
|
|
36
|
-
|
|
37
|
-
/* Radius */
|
|
38
|
-
--radius-sm: 0.25rem;
|
|
39
|
-
--radius-md: 0.375rem;
|
|
40
|
-
--radius-lg: 0.5rem;
|
|
41
|
-
|
|
42
|
-
/* Transitions */
|
|
43
|
-
--transition-fast: 100ms ease;
|
|
44
|
-
--transition-base: 200ms ease;
|
|
45
|
-
--transition-slow: 500ms ease;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
[data-theme="dark"] {
|
|
49
|
-
--color-background: #0a0a0a;
|
|
50
|
-
--color-foreground: #f0f0f0;
|
|
51
|
-
--color-primary: #e0e0f0;
|
|
52
|
-
--color-primary-foreground: #1a1a2e;
|
|
53
|
-
--color-muted: #1a1a1a;
|
|
54
|
-
--color-muted-foreground: #a3a3a3;
|
|
55
|
-
--color-border: #262626;
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// app/providers.tsx — apply theme to root
|
|
61
|
-
import { useEffect, useState } from 'react'
|
|
62
|
-
|
|
63
|
-
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
64
|
-
const [theme, setTheme] = useState<'light' | 'dark'>('light')
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
document.documentElement.setAttribute('data-theme', theme)
|
|
68
|
-
}, [theme])
|
|
69
|
-
|
|
70
|
-
return <>{children}</>
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Component Pattern with Tokens
|
|
77
|
-
```css
|
|
78
|
-
/* components/ui/Button/Button.module.css */
|
|
79
|
-
.button {
|
|
80
|
-
display: inline-flex;
|
|
81
|
-
align-items: center;
|
|
82
|
-
justify-content: center;
|
|
83
|
-
padding: var(--space-2) var(--space-4);
|
|
84
|
-
font-size: var(--font-size-base);
|
|
85
|
-
border-radius: var(--radius-md);
|
|
86
|
-
transition: background-color var(--transition-base);
|
|
87
|
-
cursor: pointer;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.primary {
|
|
91
|
-
background-color: var(--color-primary);
|
|
92
|
-
color: var(--color-primary-foreground);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.primary:hover {
|
|
96
|
-
opacity: 0.9;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.ghost {
|
|
100
|
-
background-color: transparent;
|
|
101
|
-
color: var(--color-foreground);
|
|
102
|
-
border: 1px solid var(--color-border);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.ghost:hover {
|
|
106
|
-
background-color: var(--color-muted);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.disabled {
|
|
110
|
-
opacity: 0.5;
|
|
111
|
-
cursor: not-allowed;
|
|
112
|
-
pointer-events: none;
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## clsx for Class Composition
|
|
119
|
-
```typescript
|
|
120
|
-
// Install: npm install clsx
|
|
121
|
-
import clsx from 'clsx'
|
|
122
|
-
import styles from './Button.module.css'
|
|
123
|
-
|
|
124
|
-
export function Button({ variant = 'primary', disabled, className }: ButtonProps) {
|
|
125
|
-
return (
|
|
126
|
-
<button
|
|
127
|
-
className={clsx(
|
|
128
|
-
styles.button,
|
|
129
|
-
styles[variant],
|
|
130
|
-
disabled && styles.disabled,
|
|
131
|
-
className // allow external class override
|
|
132
|
-
)}
|
|
133
|
-
disabled={disabled}
|
|
134
|
-
>
|
|
135
|
-
{children}
|
|
136
|
-
</button>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Responsive in Module File
|
|
144
|
-
```css
|
|
145
|
-
/* UserCard.module.css */
|
|
146
|
-
.card {
|
|
147
|
-
display: flex;
|
|
148
|
-
flex-direction: column;
|
|
149
|
-
padding: var(--space-4);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* Responsive stays in the component's own module file */
|
|
153
|
-
@media (min-width: 768px) {
|
|
154
|
-
.card {
|
|
155
|
-
flex-direction: row;
|
|
156
|
-
padding: var(--space-6);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@media (min-width: 1024px) {
|
|
161
|
-
.card {
|
|
162
|
-
max-width: 800px;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Animation
|
|
170
|
-
```css
|
|
171
|
-
/* Define @keyframes in component module if component-specific */
|
|
172
|
-
/* Define in global.css if reused across components */
|
|
173
|
-
|
|
174
|
-
/* Component-specific */
|
|
175
|
-
@keyframes fadeIn {
|
|
176
|
-
from { opacity: 0; transform: translateY(-8px); }
|
|
177
|
-
to { opacity: 1; transform: translateY(0); }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.dropdown {
|
|
181
|
-
animation: fadeIn var(--transition-base);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/* Reused animation → globals.css */
|
|
185
|
-
@keyframes spin {
|
|
186
|
-
to { transform: rotate(360deg); }
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## Never Mix Tailwind + CSS Modules on Same Element
|
|
193
|
-
```typescript
|
|
194
|
-
// ❌ mixing both on same element
|
|
195
|
-
<div className={clsx(styles.card, 'flex items-center')} />
|
|
196
|
-
|
|
197
|
-
// ✅ pick one strategy per project (defined at init)
|
|
198
|
-
// CSS Modules project → all styling via modules
|
|
199
|
-
<div className={styles.card} />
|
|
200
|
-
|
|
201
|
-
// Tailwind project → all styling via Tailwind
|
|
202
|
-
<div className="flex items-center gap-4 rounded-md" />
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Global Styles Structure
|
|
208
|
-
```
|
|
209
|
-
src/styles/
|
|
210
|
-
tokens.css → CSS variables (colors, space, type, radius)
|
|
211
|
-
globals.css → reset, base elements, @import tokens
|
|
212
|
-
animations.css → reusable @keyframes (optional)
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
```css
|
|
216
|
-
/* globals.css */
|
|
217
|
-
@import './tokens.css';
|
|
218
|
-
|
|
219
|
-
*, *::before, *::after {
|
|
220
|
-
box-sizing: border-box;
|
|
221
|
-
margin: 0;
|
|
222
|
-
padding: 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
body {
|
|
226
|
-
background-color: var(--color-background);
|
|
227
|
-
color: var(--color-foreground);
|
|
228
|
-
font-size: var(--font-size-base);
|
|
229
|
-
line-height: 1.5;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
a {
|
|
233
|
-
color: inherit;
|
|
234
|
-
text-decoration: none;
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## Agent Quick Reference (Extended)
|
|
241
|
-
```
|
|
242
|
-
Dark mode?
|
|
243
|
-
→ [data-theme="dark"] on html element
|
|
244
|
-
→ CSS variables in tokens.css, overrides in [data-theme="dark"]
|
|
245
|
-
→ Toggle via data-theme attribute, not class swap
|
|
246
|
-
|
|
247
|
-
Conditional classes?
|
|
248
|
-
→ clsx(styles.base, condition && styles.variant)
|
|
249
|
-
→ Never string template literals
|
|
250
|
-
|
|
251
|
-
Responsive?
|
|
252
|
-
→ @media queries in component's own .module.css
|
|
253
|
-
→ Mobile styles first (no query), then @media (min-width: ...)
|
|
254
|
-
|
|
255
|
-
Animation?
|
|
256
|
-
→ Component-specific: @keyframes in component module
|
|
257
|
-
→ Reused: @keyframes in globals.css or animations.css
|
|
258
|
-
→ Duration: always use --transition-* tokens
|
|
259
|
-
|
|
260
|
-
New token needed?
|
|
261
|
-
→ Add to tokens.css under correct section
|
|
262
|
-
→ Never hardcode values in component modules
|
|
263
|
-
|
|
264
|
-
External class override allowed?
|
|
265
|
-
→ Accept className prop, pass to clsx as last arg
|
|
266
|
-
→ Allows parent to extend without modifying component
|
|
267
|
-
```
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# Coding Rules (Universal)
|
|
2
|
-
|
|
3
|
-
Applies to every project regardless of stack.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Core Principle
|
|
8
|
-
```
|
|
9
|
-
simple + explicit + consistent
|
|
10
|
-
over
|
|
11
|
-
complex + abstract + theoretically pure
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Complexity must earn abstraction. Start simple. Add layers only when justified.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Naming
|
|
19
|
-
|
|
20
|
-
### Files
|
|
21
|
-
```
|
|
22
|
-
React components: PascalCase UserCard.tsx
|
|
23
|
-
Hooks: camelCase useUserData.ts
|
|
24
|
-
Utilities: camelCase formatDate.ts
|
|
25
|
-
Constants: camelCase apiEndpoints.ts
|
|
26
|
-
Types: camelCase userTypes.ts
|
|
27
|
-
CSS Modules: PascalCase UserCard.module.css
|
|
28
|
-
Java classes: PascalCase UserService.java
|
|
29
|
-
SQL migrations: snake_case V1__create_users_table.sql
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Variables and Functions
|
|
33
|
-
```
|
|
34
|
-
Variables: camelCase userName, isLoading, hasError
|
|
35
|
-
Functions: camelCase getUser(), createOrder(), formatDate()
|
|
36
|
-
Constants: SCREAMING MAX_RETRIES, API_BASE_URL
|
|
37
|
-
React components: PascalCase UserCard, LoginForm
|
|
38
|
-
Types/Interfaces: PascalCase UserResponse, ApiError
|
|
39
|
-
Enums: PascalCase UserRole, OrderStatus
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Name for what it does, not what it is
|
|
43
|
-
```
|
|
44
|
-
❌ getData(), handleThing(), doStuff(), temp, x
|
|
45
|
-
✅ getUserById(), handleLoginSubmit(), formatCurrency()
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Boolean naming
|
|
49
|
-
```
|
|
50
|
-
❌ user, loading, error
|
|
51
|
-
✅ isLoading, hasError, isAuthenticated, canEdit
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Functions
|
|
57
|
-
- One responsibility per function
|
|
58
|
-
- Max 3 parameters — if more, use an options object
|
|
59
|
-
- Return early to avoid deep nesting
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// ❌
|
|
63
|
-
function processUser(user, role, permissions, sendEmail, notify) { ... }
|
|
64
|
-
|
|
65
|
-
// ✅
|
|
66
|
-
function processUser(user: User, options: ProcessUserOptions) { ... }
|
|
67
|
-
|
|
68
|
-
// ❌ deep nesting
|
|
69
|
-
function getUser(id) {
|
|
70
|
-
if (id) {
|
|
71
|
-
if (isValid(id)) {
|
|
72
|
-
// logic
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ✅ early return
|
|
78
|
-
function getUser(id) {
|
|
79
|
-
if (!id) return null
|
|
80
|
-
if (!isValid(id)) return null
|
|
81
|
-
// logic
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Imports
|
|
88
|
-
|
|
89
|
-
### Frontend
|
|
90
|
-
- Always use absolute imports with `@/` prefix
|
|
91
|
-
- Never use relative `../../` imports
|
|
92
|
-
- Barrel exports (`index.ts`) for public API of a feature only
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// ❌
|
|
96
|
-
import { UserCard } from '../../../components/ui/UserCard'
|
|
97
|
-
|
|
98
|
-
// ✅
|
|
99
|
-
import { UserCard } from '@/components/ui/UserCard'
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Import Order (enforced by ESLint)
|
|
103
|
-
```
|
|
104
|
-
1. External libraries
|
|
105
|
-
2. Internal absolute (@/)
|
|
106
|
-
3. Types
|
|
107
|
-
4. Styles / assets
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Constants
|
|
113
|
-
- No magic numbers or strings in logic
|
|
114
|
-
- All constants in `src/constants/index.ts` or feature-level constants file
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// ❌
|
|
118
|
-
if (role === 'ADMIN') { ... }
|
|
119
|
-
setTimeout(fn, 900000)
|
|
120
|
-
|
|
121
|
-
// ✅
|
|
122
|
-
import { ROLES, TOKEN_EXPIRY } from '@/constants'
|
|
123
|
-
if (role === ROLES.ADMIN) { ... }
|
|
124
|
-
setTimeout(fn, TOKEN_EXPIRY.ACCESS)
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Async / Error Handling
|
|
130
|
-
|
|
131
|
-
### Frontend
|
|
132
|
-
- Always async/await — never `.then()` chains
|
|
133
|
-
- Always handle errors at the hook level, not component level
|
|
134
|
-
- Never swallow errors silently
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// ❌
|
|
138
|
-
fetchUser().then(data => setUser(data)).catch(e => console.log(e))
|
|
139
|
-
|
|
140
|
-
// ✅
|
|
141
|
-
try {
|
|
142
|
-
const user = await fetchUser()
|
|
143
|
-
setUser(user)
|
|
144
|
-
} catch (error) {
|
|
145
|
-
logger.error('fetchUser failed:', error)
|
|
146
|
-
throw error
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Backend
|
|
151
|
-
- Always throw `AppException` — never raw `RuntimeException`
|
|
152
|
-
- Never expose stack traces in API responses
|
|
153
|
-
- Always catch at the `GlobalExceptionHandler` level
|
|
154
|
-
|
|
155
|
-
```java
|
|
156
|
-
// ❌
|
|
157
|
-
throw new RuntimeException("User not found");
|
|
158
|
-
|
|
159
|
-
// ✅
|
|
160
|
-
throw new AppException("USER_NOT_FOUND", HttpStatus.NOT_FOUND);
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Logging
|
|
166
|
-
- NEVER use `console.log` directly
|
|
167
|
-
- Always use `lib/logger.ts` on frontend
|
|
168
|
-
- Always use SLF4J logger on backend
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// ❌
|
|
172
|
-
console.log('user:', user)
|
|
173
|
-
console.error('error:', error)
|
|
174
|
-
|
|
175
|
-
// ✅
|
|
176
|
-
import { logger } from '@/lib/logger'
|
|
177
|
-
logger.info('user fetched:', user)
|
|
178
|
-
logger.error('fetch failed:', error)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
```java
|
|
182
|
-
// ❌
|
|
183
|
-
System.out.println("user: " + user);
|
|
184
|
-
|
|
185
|
-
// ✅
|
|
186
|
-
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
|
187
|
-
log.info("user fetched: {}", userId);
|
|
188
|
-
log.error("fetch failed for userId: {}", userId, e);
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Comments
|
|
194
|
-
- Comment WHY, not WHAT
|
|
195
|
-
- Code should be readable enough to not need WHAT comments
|
|
196
|
-
- Remove all debug/TODO comments before committing
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
// ❌ explains what (obvious from code)
|
|
200
|
-
// increment counter
|
|
201
|
-
count++
|
|
202
|
-
|
|
203
|
-
// ✅ explains why (not obvious from code)
|
|
204
|
-
// Retry once — server returns 503 on cold start for ~200ms
|
|
205
|
-
await retry(fetchUser, { times: 1, delay: 300 })
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## No Debug Code in Commits
|
|
211
|
-
```
|
|
212
|
-
❌ console.log
|
|
213
|
-
❌ debugger
|
|
214
|
-
❌ TODO comments
|
|
215
|
-
❌ hardcoded test values
|
|
216
|
-
❌ commented-out code blocks
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
ESLint and Husky enforce this — build fails if present.
|
|
220
|
-
|
|
221
|
-
---
|
|
222
|
-
|
|
223
|
-
## One Thing Per File
|
|
224
|
-
```
|
|
225
|
-
❌ UserCardAndForm.tsx — two components in one file
|
|
226
|
-
❌ userUtils.ts — 30 unrelated utility functions
|
|
227
|
-
|
|
228
|
-
✅ UserCard.tsx
|
|
229
|
-
✅ UserForm.tsx
|
|
230
|
-
✅ formatDate.ts
|
|
231
|
-
✅ formatCurrency.ts
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Exception: small helper types or constants directly related to the file.
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## Exports
|
|
239
|
-
|
|
240
|
-
### Frontend
|
|
241
|
-
- Named exports everywhere except page/route components
|
|
242
|
-
- Page components use default export (Next.js / React Router requirement)
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
// ❌ default export for reusable components
|
|
246
|
-
export default function UserCard() { ... }
|
|
247
|
-
|
|
248
|
-
// ✅ named export
|
|
249
|
-
export function UserCard() { ... }
|
|
250
|
-
|
|
251
|
-
// ✅ default export for pages only
|
|
252
|
-
export default function UserPage() { ... }
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Backend
|
|
256
|
-
- Spring-managed beans: `@Service`, `@Repository`, `@RestController` — Spring handles export
|
|
257
|
-
- Utility classes: `public static` methods
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## Agent Rules
|
|
262
|
-
```
|
|
263
|
-
Before writing any code:
|
|
264
|
-
1. Check if the functionality already exists
|
|
265
|
-
2. Check which layer owns this responsibility
|
|
266
|
-
3. Choose the simplest correct implementation
|
|
267
|
-
4. Follow existing patterns in the codebase — don't introduce new ones
|
|
268
|
-
|
|
269
|
-
Before creating a new file:
|
|
270
|
-
1. Check if an existing file should be extended instead
|
|
271
|
-
2. Follow the selected stack's structure playbook and current feature ownership
|
|
272
|
-
|
|
273
|
-
Before adding a dependency:
|
|
274
|
-
1. Check if existing tools already solve it
|
|
275
|
-
2. Prefer what the playbook recommends over personal preference
|
|
276
|
-
|
|
277
|
-
Naming:
|
|
278
|
-
1. Name things for what they do
|
|
279
|
-
2. Booleans start with is/has/can
|
|
280
|
-
3. No abbreviations unless universally understood (id, url, dto)
|
|
281
|
-
```
|