@zigrivers/scaffold 3.6.0 → 3.8.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/README.md +127 -12
- package/content/knowledge/backend/backend-api-design.md +103 -0
- package/content/knowledge/backend/backend-architecture.md +100 -0
- package/content/knowledge/backend/backend-async-patterns.md +101 -0
- package/content/knowledge/backend/backend-auth-patterns.md +100 -0
- package/content/knowledge/backend/backend-conventions.md +105 -0
- package/content/knowledge/backend/backend-data-modeling.md +102 -0
- package/content/knowledge/backend/backend-deployment.md +100 -0
- package/content/knowledge/backend/backend-dev-environment.md +102 -0
- package/content/knowledge/backend/backend-observability.md +102 -0
- package/content/knowledge/backend/backend-project-structure.md +100 -0
- package/content/knowledge/backend/backend-requirements.md +103 -0
- package/content/knowledge/backend/backend-security.md +104 -0
- package/content/knowledge/backend/backend-testing.md +101 -0
- package/content/knowledge/backend/backend-worker-patterns.md +100 -0
- package/content/knowledge/cli/cli-architecture.md +101 -0
- package/content/knowledge/cli/cli-conventions.md +117 -0
- package/content/knowledge/cli/cli-dev-environment.md +121 -0
- package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
- package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
- package/content/knowledge/cli/cli-output-patterns.md +107 -0
- package/content/knowledge/cli/cli-project-structure.md +124 -0
- package/content/knowledge/cli/cli-requirements.md +101 -0
- package/content/knowledge/cli/cli-shell-integration.md +130 -0
- package/content/knowledge/cli/cli-testing.md +134 -0
- package/content/knowledge/library/library-api-design.md +306 -0
- package/content/knowledge/library/library-architecture.md +247 -0
- package/content/knowledge/library/library-bundling.md +244 -0
- package/content/knowledge/library/library-conventions.md +229 -0
- package/content/knowledge/library/library-dev-environment.md +220 -0
- package/content/knowledge/library/library-documentation.md +300 -0
- package/content/knowledge/library/library-project-structure.md +237 -0
- package/content/knowledge/library/library-requirements.md +173 -0
- package/content/knowledge/library/library-security.md +257 -0
- package/content/knowledge/library/library-testing.md +319 -0
- package/content/knowledge/library/library-type-definitions.md +284 -0
- package/content/knowledge/library/library-versioning.md +300 -0
- package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
- package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
- package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
- package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
- package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
- package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
- package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
- package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
- package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
- package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
- package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
- package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
- package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
- package/content/knowledge/web-app/web-app-architecture.md +116 -0
- package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
- package/content/knowledge/web-app/web-app-conventions.md +121 -0
- package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
- package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
- package/content/knowledge/web-app/web-app-deployment.md +134 -0
- package/content/knowledge/web-app/web-app-design-system.md +158 -0
- package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
- package/content/knowledge/web-app/web-app-observability.md +221 -0
- package/content/knowledge/web-app/web-app-project-structure.md +160 -0
- package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
- package/content/knowledge/web-app/web-app-requirements.md +112 -0
- package/content/knowledge/web-app/web-app-security.md +193 -0
- package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
- package/content/knowledge/web-app/web-app-testing.md +249 -0
- package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
- package/content/methodology/backend-overlay.yml +73 -0
- package/content/methodology/cli-overlay.yml +69 -0
- package/content/methodology/library-overlay.yml +67 -0
- package/content/methodology/mobile-app-overlay.yml +71 -0
- package/content/methodology/web-app-overlay.yml +79 -0
- package/dist/cli/commands/init.d.ts +21 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +261 -13
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +206 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +1392 -64
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +82 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +302 -1
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
- package/dist/core/assembly/overlay-loader.js +2 -1
- package/dist/core/assembly/overlay-loader.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +56 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/game-pipeline.test.js +1 -0
- package/dist/e2e/game-pipeline.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +16 -0
- package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
- package/dist/e2e/project-type-overlays.test.js +834 -0
- package/dist/e2e/project-type-overlays.test.js.map +1 -0
- package/dist/types/config.d.ts +19 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/wizard/questions.d.ts +27 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +142 -3
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +206 -8
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +21 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +27 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +1 -1
- package/dist/types/wizard.d.ts +0 -14
- package/dist/types/wizard.d.ts.map +0 -1
- package/dist/types/wizard.js +0 -2
- package/dist/types/wizard.js.map +0 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-data-patterns
|
|
3
|
+
description: Client-side caching, optimistic updates, real-time sync, pagination strategies, form state management, and file upload patterns
|
|
4
|
+
topics: [web-app, data-fetching, caching, react-query, swr, pagination, forms, file-upload]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Data management in web applications spans from simple fetch-and-display to complex real-time collaborative state. The wrong patterns here produce stale UIs, conflicting updates, degraded performance under load, and frustrated users. The right patterns make applications feel instantaneous even over slow networks — by understanding the difference between server state, client state, and ephemeral UI state, and applying the appropriate tool to each.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
### Server State vs Client State
|
|
12
|
+
|
|
13
|
+
The most important conceptual distinction in modern web data management:
|
|
14
|
+
|
|
15
|
+
- **Server state** — data owned by the server, shared across clients, and potentially stale as soon as it's fetched: user profiles, product listings, feed items. Use a data-fetching library (React Query, SWR) to manage this.
|
|
16
|
+
- **Client state** — data owned by the current user's session, not persisted to the server: currently selected tab, sidebar open/closed, in-progress form data. Use React local state or Zustand/Jotai/Redux.
|
|
17
|
+
|
|
18
|
+
Mixing these causes anti-patterns: putting server data in Redux, or making API calls from useEffect without caching. Libraries like React Query exist precisely because server state needs cache management, background refetching, deduplication, and stale-while-revalidate semantics that generic state managers don't provide.
|
|
19
|
+
|
|
20
|
+
### SWR vs React Query
|
|
21
|
+
|
|
22
|
+
Both implement stale-while-revalidate caching for server state:
|
|
23
|
+
|
|
24
|
+
| Concern | SWR | React Query (TanStack Query) |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Bundle size | ~6 KB | ~13 KB |
|
|
27
|
+
| Mutations | Manual invalidation | Built-in `useMutation` + auto-invalidation |
|
|
28
|
+
| Infinite scroll | `useSWRInfinite` | `useInfiniteQuery` |
|
|
29
|
+
| Optimistic updates | Manual | First-class via `onMutate` + rollback |
|
|
30
|
+
| DevTools | None built-in | Excellent DevTools panel |
|
|
31
|
+
| Best for | Read-heavy apps, minimal mutations | Apps with complex mutation flows |
|
|
32
|
+
|
|
33
|
+
**Rule:** Use React Query for most production applications. Use SWR for read-heavy dashboards where its simplicity is a net benefit.
|
|
34
|
+
|
|
35
|
+
### Optimistic Updates
|
|
36
|
+
|
|
37
|
+
Update the UI before the server confirms the mutation. If the server rejects, roll back.
|
|
38
|
+
|
|
39
|
+
Optimistic updates are appropriate when: the mutation has a very high success rate, the latency is noticeable, and the rollback experience is not confusing. They are not appropriate when: the server-side result is unpredictable (e.g., a bid on an auction where you may lose).
|
|
40
|
+
|
|
41
|
+
### Pagination Strategies
|
|
42
|
+
|
|
43
|
+
**Cursor-based pagination** (preferred):
|
|
44
|
+
- Each page returns a `nextCursor` opaque token; the next request passes `?cursor=<token>`
|
|
45
|
+
- Stable across concurrent inserts/deletes — no items skipped or duplicated
|
|
46
|
+
- Required for infinite scroll (offset pagination breaks on live data)
|
|
47
|
+
- Cannot jump to arbitrary page numbers
|
|
48
|
+
|
|
49
|
+
**Offset-based pagination**:
|
|
50
|
+
- `?page=3&limit=20` or `?offset=40&limit=20`
|
|
51
|
+
- Supports "jump to page N" UI
|
|
52
|
+
- Items shift when rows are inserted/deleted — users see duplicates or skipped items on live data
|
|
53
|
+
- Appropriate for admin tables with infrequent writes and explicit page navigation
|
|
54
|
+
|
|
55
|
+
### Real-Time Data Sync
|
|
56
|
+
|
|
57
|
+
Three patterns in increasing complexity:
|
|
58
|
+
1. **Polling** — simplest, least efficient: re-fetch every N seconds. Acceptable for dashboards that update every few minutes.
|
|
59
|
+
2. **Server-Sent Events (SSE)** — server pushes updates to client over a single long-lived HTTP connection. One-directional. Excellent for notifications, activity feeds, live counters. No WebSocket negotiation overhead.
|
|
60
|
+
3. **WebSocket** — bidirectional full-duplex. Required for chat, collaborative editing, live games. Higher complexity: connection management, reconnection, presence.
|
|
61
|
+
|
|
62
|
+
## Deep Guidance
|
|
63
|
+
|
|
64
|
+
### React Query Patterns
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// 1. Standard query with stale time
|
|
68
|
+
const { data: posts, isLoading, error } = useQuery({
|
|
69
|
+
queryKey: ['posts', { authorId, page }],
|
|
70
|
+
queryFn: () => fetchPosts({ authorId, page }),
|
|
71
|
+
staleTime: 5 * 60 * 1000, // Data considered fresh for 5 minutes
|
|
72
|
+
gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes after unmount
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 2. Optimistic mutation with rollback
|
|
76
|
+
const queryClient = useQueryClient();
|
|
77
|
+
|
|
78
|
+
const likeMutation = useMutation({
|
|
79
|
+
mutationFn: (postId: string) => api.likePost(postId),
|
|
80
|
+
|
|
81
|
+
onMutate: async (postId) => {
|
|
82
|
+
// Cancel any in-flight refetches that would overwrite optimistic update
|
|
83
|
+
await queryClient.cancelQueries({ queryKey: ['posts'] });
|
|
84
|
+
|
|
85
|
+
// Snapshot the previous value
|
|
86
|
+
const previousPosts = queryClient.getQueryData(['posts']);
|
|
87
|
+
|
|
88
|
+
// Optimistically update
|
|
89
|
+
queryClient.setQueryData(['posts'], (old: Post[]) =>
|
|
90
|
+
old.map(p => p.id === postId ? { ...p, likeCount: p.likeCount + 1, likedByMe: true } : p)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Return context for rollback
|
|
94
|
+
return { previousPosts };
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
onError: (error, postId, context) => {
|
|
98
|
+
// Roll back to snapshot on failure
|
|
99
|
+
queryClient.setQueryData(['posts'], context?.previousPosts);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
onSettled: () => {
|
|
103
|
+
// Always refetch to sync with server truth
|
|
104
|
+
queryClient.invalidateQueries({ queryKey: ['posts'] });
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 3. Infinite scroll
|
|
109
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
|
|
110
|
+
queryKey: ['feed'],
|
|
111
|
+
queryFn: ({ pageParam }) => fetchFeed({ cursor: pageParam }),
|
|
112
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
|
|
113
|
+
initialPageParam: undefined as string | undefined,
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### File Upload Pattern
|
|
118
|
+
|
|
119
|
+
Uploads require a different flow from standard JSON mutations: multipart form data, progress tracking, and (for large files) direct-to-storage upload via presigned URLs.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// PATTERN: Presigned URL upload (bypasses app server, goes direct to S3/GCS)
|
|
123
|
+
async function uploadFile(file: File, onProgress: (pct: number) => void) {
|
|
124
|
+
// Step 1: Get presigned URL from app server
|
|
125
|
+
const { uploadUrl, fileKey } = await api.getUploadUrl({
|
|
126
|
+
filename: file.name,
|
|
127
|
+
contentType: file.type,
|
|
128
|
+
size: file.size,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Step 2: Upload directly to storage (no app server in the critical path)
|
|
132
|
+
await new Promise<void>((resolve, reject) => {
|
|
133
|
+
const xhr = new XMLHttpRequest();
|
|
134
|
+
xhr.open('PUT', uploadUrl);
|
|
135
|
+
xhr.setRequestHeader('Content-Type', file.type);
|
|
136
|
+
|
|
137
|
+
xhr.upload.onprogress = (event) => {
|
|
138
|
+
if (event.lengthComputable) {
|
|
139
|
+
onProgress(Math.round((event.loaded / event.total) * 100));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
xhr.onload = () => xhr.status === 200 ? resolve() : reject(new Error(`Upload failed: ${xhr.status}`));
|
|
144
|
+
xhr.onerror = () => reject(new Error('Network error during upload'));
|
|
145
|
+
xhr.send(file);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Step 3: Notify app server that upload is complete
|
|
149
|
+
return api.confirmUpload({ fileKey });
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Presigned URL uploads: never proxy large files through your app server. A 100 MB upload through an app server occupies a Node.js worker for the entire transfer duration.
|
|
154
|
+
|
|
155
|
+
### Form State Management
|
|
156
|
+
|
|
157
|
+
For most forms: `react-hook-form` + Zod schema validation. This pattern avoids controlled component re-renders (critical for large forms), provides schema-driven validation, and integrates cleanly with TypeScript.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { useForm } from 'react-hook-form';
|
|
161
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
162
|
+
import { z } from 'zod';
|
|
163
|
+
|
|
164
|
+
const profileSchema = z.object({
|
|
165
|
+
displayName: z.string().min(2).max(50),
|
|
166
|
+
email: z.string().email(),
|
|
167
|
+
bio: z.string().max(500).optional(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
type ProfileForm = z.infer<typeof profileSchema>;
|
|
171
|
+
|
|
172
|
+
function ProfileEditor() {
|
|
173
|
+
const { register, handleSubmit, formState: { errors, isDirty, isSubmitting } } = useForm<ProfileForm>({
|
|
174
|
+
resolver: zodResolver(profileSchema),
|
|
175
|
+
defaultValues: { displayName: user.displayName, email: user.email },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const onSubmit = async (data: ProfileForm) => {
|
|
179
|
+
await updateProfile(data);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
184
|
+
<input {...register('displayName')} />
|
|
185
|
+
{errors.displayName && <span>{errors.displayName.message}</span>}
|
|
186
|
+
<button type="submit" disabled={!isDirty || isSubmitting}>Save</button>
|
|
187
|
+
</form>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Reserve heavier solutions (Formik, final-form) only for highly dynamic form generation requirements. For most product forms, react-hook-form is sufficient and faster.
|
|
193
|
+
|
|
194
|
+
### Cursor Pagination Implementation
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Server-side cursor pagination (PostgreSQL + Prisma)
|
|
198
|
+
async function getPaginatedPosts(cursor?: string, limit = 20) {
|
|
199
|
+
const posts = await prisma.post.findMany({
|
|
200
|
+
take: limit + 1, // Fetch one extra to determine if there's a next page
|
|
201
|
+
...(cursor && {
|
|
202
|
+
cursor: { id: cursor },
|
|
203
|
+
skip: 1, // Skip the cursor item itself
|
|
204
|
+
}),
|
|
205
|
+
orderBy: { createdAt: 'desc' },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const hasNextPage = posts.length > limit;
|
|
209
|
+
const items = hasNextPage ? posts.slice(0, -1) : posts;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
items,
|
|
213
|
+
nextCursor: hasNextPage ? items[items.length - 1].id : null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Always fetch `limit + 1` to check for next page existence without an extra COUNT query.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-deployment-workflow
|
|
3
|
+
description: Preview deploys per PR, staging environments, deployment branches, CI/CD pipeline stages, rollback strategies, and canary deployments
|
|
4
|
+
topics: [web-app, deployment, ci-cd, staging, preview, rollback, canary]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A mature deployment workflow transforms deployment from a risky, manual event into a routine, automated step. The goal is to make every merge to main automatically and safely deliverable to production, with fast rollback when something goes wrong. The cost of building this infrastructure up front is trivially small compared to the cost of a major incident caused by a manual deploy process.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
A mature deployment workflow makes every merge to main automatically deliverable with fast rollback. Every PR gets a preview deploy. CI/CD runs lint, typecheck, test, and build in fail-fast order. Test rollback procedures quarterly. For high-traffic apps, use canary deployments with explicit rollback criteria.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Preview Deploys Per PR
|
|
16
|
+
|
|
17
|
+
Every pull request should get its own preview deployment — a fully functional URL that reviewers and QA can use to verify behavior before merging. This is the single highest-ROI practice in modern web deployment:
|
|
18
|
+
|
|
19
|
+
- **Vercel, Netlify, Railway**: Automatically create preview deploys on PR open/push. Zero configuration for most frameworks.
|
|
20
|
+
- **Custom CI**: Build and deploy to a path-based preview URL (`preview/<pr-number>/`) or a subdomain. Tear down on PR close.
|
|
21
|
+
|
|
22
|
+
Preview deploys must use isolated environment variables (dev database, dev third-party API keys). Never point a preview deploy at a production database.
|
|
23
|
+
|
|
24
|
+
### Deployment Branches
|
|
25
|
+
|
|
26
|
+
Establish a clear branch-to-environment mapping and document it in the repo:
|
|
27
|
+
|
|
28
|
+
| Branch | Environment | Deploys |
|
|
29
|
+
|--------|-------------|---------|
|
|
30
|
+
| `main` | Production | Automatic on merge |
|
|
31
|
+
| `staging` | Staging | Automatic on merge |
|
|
32
|
+
| `feature/*`, `fix/*` | Preview | Automatic on PR push |
|
|
33
|
+
|
|
34
|
+
Avoid long-lived branches other than `main` and optionally `staging`. Feature branches merge to `main` via PR. `main` deploys to production. This is the simplest model that works.
|
|
35
|
+
|
|
36
|
+
If you have a separate `staging` branch: keep it in sync with `main` via regular merges. Staging branches that lag `main` by weeks create false confidence and painful integration surprises.
|
|
37
|
+
|
|
38
|
+
### CI/CD Pipeline Stages
|
|
39
|
+
|
|
40
|
+
Every push to a branch should run a pipeline in this order (fast-to-slow, fail-fast):
|
|
41
|
+
|
|
42
|
+
1. **Install dependencies** (cached; skip if lockfile unchanged)
|
|
43
|
+
2. **Lint** — ESLint, Prettier check (fail fast; ~30 seconds)
|
|
44
|
+
3. **Typecheck** — `tsc --noEmit` (fail fast; ~60 seconds)
|
|
45
|
+
4. **Unit tests** — Vitest/Jest with coverage threshold (1–3 minutes)
|
|
46
|
+
5. **Build** — Production build to verify no build errors (2–5 minutes)
|
|
47
|
+
6. **E2E tests** (optional, on main/staging only) — Playwright or Cypress against preview deploy (5–15 minutes)
|
|
48
|
+
7. **Deploy** — Only if all above pass
|
|
49
|
+
|
|
50
|
+
Do not run all tests on every PR if the test suite is slow. Use test impact analysis (run only tests related to changed files) or split E2E tests to a separate workflow that runs on merge to `main`.
|
|
51
|
+
|
|
52
|
+
### Rollback Strategies
|
|
53
|
+
|
|
54
|
+
Every deployment system must have a tested rollback procedure. "We can just revert the commit and redeploy" is not a rollback strategy — that takes 5+ minutes and requires a developer to execute it under pressure.
|
|
55
|
+
|
|
56
|
+
- **Vercel/Netlify**: One-click rollback to any previous deployment in the dashboard. Target: under 30 seconds to rollback.
|
|
57
|
+
- **Custom infrastructure**: Maintain the previous two deployment artifacts. Rollback = swap the active artifact. Use blue-green or immutable deployment patterns (see below).
|
|
58
|
+
- **Database migrations**: Rollback is the hard part. Write migrations that are forward-compatible (additive only). Keep destructive changes separate, deployed only after the new code is stable. Use a migration tool that tracks state (Prisma, Flyway, Liquibase).
|
|
59
|
+
|
|
60
|
+
Test the rollback procedure at least quarterly. A rollback you have never practiced will fail in an incident.
|
|
61
|
+
|
|
62
|
+
### Canary Deployments
|
|
63
|
+
|
|
64
|
+
For high-traffic production apps, deploy changes to a small percentage of traffic before full rollout:
|
|
65
|
+
|
|
66
|
+
- Route 5% of requests to the new version, 95% to the old
|
|
67
|
+
- Monitor error rates, latency, and business metrics for 15–30 minutes
|
|
68
|
+
- Gradually increase the percentage or roll back if metrics degrade
|
|
69
|
+
|
|
70
|
+
Canary deployments require infrastructure support: feature flag services (LaunchDarkly, Unleash), traffic splitting at the load balancer (AWS ALB weighted routing, Cloudflare Workers), or platform-level support (Vercel edge middleware). Do not implement canaries manually — use the platform's built-in mechanism.
|
|
71
|
+
|
|
72
|
+
### CI/CD Pipeline Template (GitHub Actions)
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
# .github/workflows/ci.yml
|
|
76
|
+
name: CI
|
|
77
|
+
|
|
78
|
+
on:
|
|
79
|
+
push:
|
|
80
|
+
branches: [main, staging]
|
|
81
|
+
pull_request:
|
|
82
|
+
|
|
83
|
+
jobs:
|
|
84
|
+
quality:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
- uses: actions/setup-node@v4
|
|
89
|
+
with:
|
|
90
|
+
node-version: 20
|
|
91
|
+
cache: npm
|
|
92
|
+
- run: npm ci
|
|
93
|
+
- run: npm run lint
|
|
94
|
+
- run: npm run typecheck
|
|
95
|
+
- run: npm run test -- --coverage
|
|
96
|
+
- run: npm run build
|
|
97
|
+
|
|
98
|
+
deploy-preview:
|
|
99
|
+
needs: quality
|
|
100
|
+
if: github.event_name == 'pull_request'
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v4
|
|
104
|
+
- uses: actions/setup-node@v4
|
|
105
|
+
with:
|
|
106
|
+
node-version: 20
|
|
107
|
+
cache: npm
|
|
108
|
+
- run: npm ci && npm run build
|
|
109
|
+
- name: Deploy preview
|
|
110
|
+
run: npx vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Deployment Environment Variables
|
|
114
|
+
|
|
115
|
+
Separate environment variables by tier and manage them securely:
|
|
116
|
+
|
|
117
|
+
- **Local**: `.env.local` (gitignored, developer-managed)
|
|
118
|
+
- **Preview**: Vercel/Netlify project environment variables, marked "Preview" tier; use dev API keys only
|
|
119
|
+
- **Staging**: Staging-specific secrets in GitHub Actions or your secrets manager
|
|
120
|
+
- **Production**: Production secrets never visible to developers; managed by infra/ops
|
|
121
|
+
|
|
122
|
+
Audit who has access to production secrets quarterly. Rotate API keys and tokens on team member offboarding without exception.
|
|
123
|
+
|
|
124
|
+
### Defining "Deployment Complete"
|
|
125
|
+
|
|
126
|
+
A deployment is not complete when the deploy command exits. It is complete when:
|
|
127
|
+
|
|
128
|
+
1. The new version is serving traffic (health check passes)
|
|
129
|
+
2. Error rate is not elevated above baseline
|
|
130
|
+
3. Response latency p95 is not elevated above baseline
|
|
131
|
+
4. At least one synthetic monitor has confirmed the critical user journey works
|
|
132
|
+
|
|
133
|
+
Automate this check in your deploy pipeline. Do not send "deploy succeeded" notifications until you have validated real traffic behavior.
|
|
134
|
+
|
|
135
|
+
### Zero-Downtime Deployments
|
|
136
|
+
|
|
137
|
+
For apps that cannot tolerate downtime:
|
|
138
|
+
|
|
139
|
+
- **Rolling deployments**: Bring up new instances, drain old instances. Requires stateless services (no in-memory session storage).
|
|
140
|
+
- **Blue-green deployments**: Run two identical environments (blue = current, green = new). Switch traffic at the load balancer level. Old environment stays up as instant rollback target.
|
|
141
|
+
- **Feature flags**: Deploy code to production without enabling it. Enable via flag without a deployment. Fastest and most controllable rollout mechanism.
|
|
142
|
+
|
|
143
|
+
For stateful workloads (databases, file storage): always plan the data migration before the code migration. Code deploys are reversible; bad data migrations often are not.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-deployment
|
|
3
|
+
description: Static CDN hosting, serverless platforms, container deployments, edge runtimes, long-running servers, and blue-green deploy patterns for web apps
|
|
4
|
+
topics: [web-app, deployment, vercel, netlify, aws, docker, cloudflare, serverless, containers]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Deployment platform selection determines your app's operational cost, performance ceiling, and scaling characteristics. Each platform has a different cost model, latency profile, and runtime constraint. Choose based on your app's rendering strategy, traffic patterns, and team's operational expertise — not on what is fashionable.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Web app deployment platforms range from static CDN hosting (cheapest, fastest TTFB) through serverless (pay-per-invocation, cold start concerns), containers (persistent connections, custom runtimes), edge functions (global low-latency transformations), to long-running servers (WebSockets, background jobs). Choose based on rendering strategy, traffic patterns, and operational expertise.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Static Hosting (CDN)
|
|
16
|
+
|
|
17
|
+
For SSG applications with no server-side rendering at request time:
|
|
18
|
+
|
|
19
|
+
- **Best platforms**: Cloudflare Pages, Netlify, Vercel (static tier), AWS S3 + CloudFront, GitHub Pages
|
|
20
|
+
- **Cost**: Near zero for low-to-medium traffic; CDN egress costs at very high traffic
|
|
21
|
+
- **Performance**: Best possible — globally distributed, no cold starts, ~50 ms TTFB from any major city
|
|
22
|
+
- **Limitations**: No request-time server logic, no secrets at request time, data freshness = deploy frequency
|
|
23
|
+
|
|
24
|
+
Configure aggressive caching headers. Static assets (hashed filenames) get `Cache-Control: immutable, max-age=31536000`. HTML pages get `Cache-Control: no-cache` (validated on every request, served from cache when fresh).
|
|
25
|
+
|
|
26
|
+
### Serverless (Vercel, Netlify, AWS Lambda)
|
|
27
|
+
|
|
28
|
+
For SSR apps or API routes that need server logic at request time without managing servers:
|
|
29
|
+
|
|
30
|
+
- **Best platforms**: Vercel (Next.js-native), Netlify Functions, AWS Lambda + API Gateway, Cloudflare Pages Functions
|
|
31
|
+
- **Cost model**: Pay per invocation and compute time. Typically very cheap at low traffic, can become expensive at sustained high traffic vs. a long-running server.
|
|
32
|
+
- **Cold starts**: The primary performance concern. Lambda cold starts: 100–500 ms (Node.js), 1–3 seconds (container-based). Vercel/Netlify Edge Functions: 0 ms (V8 isolates, not containers).
|
|
33
|
+
- **Limitations**: Execution time limits (Vercel: 10–300 seconds depending on plan; Lambda: 15 minutes max), no persistent memory between invocations, no long-lived connections
|
|
34
|
+
|
|
35
|
+
Minimize cold start impact: keep bundles small (Lambda-specific: prefer ESM + tree-shaking over CommonJS), use Provisioned Concurrency for latency-critical endpoints, or migrate latency-sensitive APIs to edge functions.
|
|
36
|
+
|
|
37
|
+
### Container (Docker, AWS ECS/Fargate, Google Cloud Run)
|
|
38
|
+
|
|
39
|
+
For apps that need more control than serverless, persistent connections, or custom runtime environments:
|
|
40
|
+
|
|
41
|
+
- **Best platforms**: AWS ECS/Fargate, Google Cloud Run, Azure Container Apps, Railway, Fly.io, self-managed Kubernetes
|
|
42
|
+
- **Cost model**: Per-container-hour (Fargate) or per-request with scale-to-zero (Cloud Run). More expensive than serverless at low traffic, cheaper at sustained high traffic.
|
|
43
|
+
- **Benefits over serverless**: No cold starts with min replicas > 0, persistent WebSocket connections, custom binaries/runtimes, larger memory limits
|
|
44
|
+
- **Operational overhead**: You manage container definitions, health checks, and scaling policies
|
|
45
|
+
|
|
46
|
+
Use multi-stage Docker builds to minimize image size. Target images under 200 MB:
|
|
47
|
+
|
|
48
|
+
```dockerfile
|
|
49
|
+
FROM node:20-alpine AS builder
|
|
50
|
+
WORKDIR /app
|
|
51
|
+
COPY package*.json ./
|
|
52
|
+
RUN npm ci
|
|
53
|
+
COPY . .
|
|
54
|
+
RUN npm run build
|
|
55
|
+
|
|
56
|
+
FROM node:20-alpine AS runner
|
|
57
|
+
WORKDIR /app
|
|
58
|
+
ENV NODE_ENV=production
|
|
59
|
+
COPY --from=builder /app/.next ./.next
|
|
60
|
+
COPY --from=builder /app/public ./public
|
|
61
|
+
COPY --from=builder /app/package.json ./
|
|
62
|
+
RUN npm ci --omit=dev
|
|
63
|
+
USER node
|
|
64
|
+
EXPOSE 3000
|
|
65
|
+
CMD ["npm", "start"]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Edge (Cloudflare Workers, Vercel Edge Functions)
|
|
69
|
+
|
|
70
|
+
For global, low-latency request processing with simple logic:
|
|
71
|
+
|
|
72
|
+
- **Runtime**: V8 isolates (not Node.js). Fast startup (~0 ms), runs at 300+ edge locations globally, ~1–5 ms TTFB worldwide.
|
|
73
|
+
- **Use cases**: Auth token validation, A/B testing, geo-routing, request rewriting, rate limiting, personalized cache headers
|
|
74
|
+
- **Limitations**: No Node.js built-ins (`fs`, `crypto` partially available, `child_process` unavailable), no SQLite, execution time limits (50 ms CPU time on Cloudflare free tier), no persistent file system
|
|
75
|
+
- **Data access**: Use edge-native databases: Cloudflare D1 (SQLite), Cloudflare KV, Upstash Redis, PlanetScale edge
|
|
76
|
+
|
|
77
|
+
Never put complex business logic in edge functions. Their value is speed and global distribution for request/response transformations, not application logic.
|
|
78
|
+
|
|
79
|
+
### Long-Running Server (Express, Fastify, Node.js HTTP)
|
|
80
|
+
|
|
81
|
+
For apps that need WebSockets, background jobs, or full control over the request lifecycle:
|
|
82
|
+
|
|
83
|
+
- **Best platforms**: AWS EC2 + ALB, Fly.io, Railway, DigitalOcean Droplets, Hetzner (cost-efficient)
|
|
84
|
+
- **When to choose**: Real-time features (WebSockets, SSE), background workers, long-running database transactions, legacy apps that cannot be adapted to serverless constraints
|
|
85
|
+
|
|
86
|
+
### Blue-Green Deployments
|
|
87
|
+
|
|
88
|
+
Blue-green deployments eliminate downtime and reduce rollback time to seconds:
|
|
89
|
+
|
|
90
|
+
1. **Blue** = current production (100% of traffic)
|
|
91
|
+
2. **Green** = new version (deployed but receiving 0% of traffic)
|
|
92
|
+
3. Run smoke tests against the green environment
|
|
93
|
+
4. Switch the load balancer to route 100% of traffic to green
|
|
94
|
+
5. Monitor for 5–15 minutes
|
|
95
|
+
6. If healthy: decommission blue. If unhealthy: switch back to blue (rollback complete in < 30 seconds)
|
|
96
|
+
|
|
97
|
+
Requirements: stateless app servers (session data in Redis/DB, not in-memory), database schema changes must be backward-compatible with both versions simultaneously.
|
|
98
|
+
|
|
99
|
+
### Platform Selection Decision Matrix
|
|
100
|
+
|
|
101
|
+
| Criteria | Static CDN | Serverless | Container | Edge | Long-Running |
|
|
102
|
+
|----------|-----------|------------|-----------|------|--------------|
|
|
103
|
+
| SSG only | Best | Overkill | Overkill | Good | Overkill |
|
|
104
|
+
| SSR with SEO | — | Best | Good | Good | Good |
|
|
105
|
+
| Real-time (WebSocket) | No | No | Best | No | Best |
|
|
106
|
+
| Low traffic / cost | Best | Best | Expensive | Best | Expensive |
|
|
107
|
+
| High sustained traffic | Best | Expensive | Best | Best | Best |
|
|
108
|
+
| Cold start sensitive | N/A | Problem | Solved | Solved | Solved |
|
|
109
|
+
| Ops complexity | Lowest | Low | Medium | Low | High |
|
|
110
|
+
|
|
111
|
+
### Health Checks and Readiness Probes
|
|
112
|
+
|
|
113
|
+
Every deployed service must expose health endpoints:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// app/api/health/route.ts
|
|
117
|
+
export async function GET() {
|
|
118
|
+
try {
|
|
119
|
+
// Check critical dependencies
|
|
120
|
+
await db.query("SELECT 1");
|
|
121
|
+
return Response.json({ status: "healthy", version: process.env.npm_package_version });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return Response.json({ status: "unhealthy", error: String(error) }, { status: 503 });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Configure your load balancer or container orchestrator to route traffic only to healthy instances. Health check failure should trigger automatic rollback in your deployment pipeline.
|
|
129
|
+
|
|
130
|
+
### Cost Optimization
|
|
131
|
+
|
|
132
|
+
- Use serverless for variable or low traffic; switch to containers once monthly serverless cost exceeds 2–3 baseline container instances
|
|
133
|
+
- CDN cache hit rate should be above 90% for SSG content — if it is not, investigate cache-busting headers
|
|
134
|
+
- Set spending alerts at 50% and 100% of monthly budget on cloud providers — auto-remediation (scale down) if the alert fires on unexpected traffic
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-app-design-system
|
|
3
|
+
description: Responsive token systems, dark/light mode, component library patterns, and CSS methodology selection for web applications
|
|
4
|
+
topics: [web-app, design-system, css, tokens, dark-mode, responsive]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A design system is the contract between design and engineering. Without one, components drift, spacing is inconsistent, and every engineer makes independent decisions about color, typography, and layout. A well-structured token system makes that contract explicit, machine-enforceable, and refactorable — changing a spacing scale or switching a brand color becomes a one-line edit rather than a codebase-wide search-and-replace.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
A design system encodes design decisions as tokens at three tiers: primitive (raw values), semantic (intent-based aliases), and component-scoped overrides. Support dark/light mode via CSS custom properties with both `prefers-color-scheme` and explicit `data-theme` toggle. Commit to a consistent breakpoint scale and CSS methodology. Use headless component libraries for accessible behavior with full visual control.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Token Architecture
|
|
16
|
+
|
|
17
|
+
Design tokens are the atoms of a design system. They encode decisions — not values — at three tiers:
|
|
18
|
+
|
|
19
|
+
1. **Primitive tokens** — Raw values with no semantic meaning: `--color-blue-500: #3b82f6`, `--spacing-4: 16px`, `--font-size-lg: 1.125rem`. Never use primitive tokens directly in components.
|
|
20
|
+
|
|
21
|
+
2. **Semantic tokens** — Intent-based aliases of primitives: `--color-interactive: var(--color-blue-500)`, `--space-component-padding: var(--spacing-4)`. Components consume semantic tokens.
|
|
22
|
+
|
|
23
|
+
3. **Component tokens** — Component-scoped overrides: `--button-background: var(--color-interactive)`. Allows per-component theming without touching semantic tokens.
|
|
24
|
+
|
|
25
|
+
Spacing, typography, and breakpoints belong in this hierarchy. A spacing scale (4/8/12/16/24/32/48/64px) enforced via tokens prevents the "just add a margin-top: 11px" habit that destroys visual rhythm.
|
|
26
|
+
|
|
27
|
+
### Dark/Light Mode
|
|
28
|
+
|
|
29
|
+
Use CSS custom properties with `prefers-color-scheme` and an explicit `data-theme` attribute override:
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
/* Primitive layer */
|
|
33
|
+
:root {
|
|
34
|
+
--color-neutral-0: #ffffff;
|
|
35
|
+
--color-neutral-900: #111827;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Semantic layer — light mode defaults */
|
|
39
|
+
:root {
|
|
40
|
+
--color-surface: var(--color-neutral-0);
|
|
41
|
+
--color-text-primary: var(--color-neutral-900);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Dark mode via media query */
|
|
45
|
+
@media (prefers-color-scheme: dark) {
|
|
46
|
+
:root {
|
|
47
|
+
--color-surface: var(--color-neutral-900);
|
|
48
|
+
--color-text-primary: var(--color-neutral-0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Manual override via JS toggle */
|
|
53
|
+
[data-theme="dark"] {
|
|
54
|
+
--color-surface: var(--color-neutral-900);
|
|
55
|
+
--color-text-primary: var(--color-neutral-0);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Always support both mechanisms: `prefers-color-scheme` for first-visit experience, `data-theme` for user preference stored in `localStorage`.
|
|
60
|
+
|
|
61
|
+
### CSS Methodology Selection
|
|
62
|
+
|
|
63
|
+
| Approach | Best For | Trade-offs |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Utility-first (Tailwind) | Rapid iteration, small teams, consistent constraints | Verbose JSX, limited custom design expression |
|
|
66
|
+
| CSS Modules | Scoped styles with full CSS power, no runtime cost | Manual naming, no global token enforcement |
|
|
67
|
+
| CSS-in-JS (Emotion, styled-components) | Dynamic theming, colocation, TypeScript safety | Runtime cost, hydration complexity in SSR |
|
|
68
|
+
| Zero-runtime (Vanilla Extract, Linaria) | SSR-safe, type-safe tokens, no runtime overhead | Build-time complexity, less dynamic |
|
|
69
|
+
|
|
70
|
+
For most production web apps, the recommendation is: **Tailwind + CSS custom properties** for utility-heavy UIs, or **CSS Modules + tokens** for design-system-first projects where designers own the token layer.
|
|
71
|
+
|
|
72
|
+
### Responsive Breakpoints
|
|
73
|
+
|
|
74
|
+
Commit to a consistent breakpoint scale and never deviate:
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
/* Mobile-first breakpoints */
|
|
78
|
+
--bp-sm: 640px; /* Large phones */
|
|
79
|
+
--bp-md: 768px; /* Tablets */
|
|
80
|
+
--bp-lg: 1024px; /* Small desktops */
|
|
81
|
+
--bp-xl: 1280px; /* Large desktops */
|
|
82
|
+
--bp-2xl: 1536px; /* Wide screens */
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use `min-width` queries exclusively (mobile-first). Avoid magic numbers in media queries — always reference the token scale.
|
|
86
|
+
|
|
87
|
+
### Component Library Patterns
|
|
88
|
+
|
|
89
|
+
A component library is the implementation layer of the design system. Key architectural decisions:
|
|
90
|
+
|
|
91
|
+
**Compound components over prop explosion:**
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// BAD: Props explode as requirements grow
|
|
95
|
+
<Select
|
|
96
|
+
label="Country"
|
|
97
|
+
options={countries}
|
|
98
|
+
placeholder="Select..."
|
|
99
|
+
isSearchable
|
|
100
|
+
isClearable
|
|
101
|
+
isMulti
|
|
102
|
+
maxSelectedItems={3}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
// GOOD: Compound pattern — composable, extensible
|
|
106
|
+
<Select value={value} onChange={setValue}>
|
|
107
|
+
<Select.Trigger>
|
|
108
|
+
<Select.Value placeholder="Select country..." />
|
|
109
|
+
</Select.Trigger>
|
|
110
|
+
<Select.Content>
|
|
111
|
+
<Select.Search />
|
|
112
|
+
{countries.map(c => (
|
|
113
|
+
<Select.Item key={c.code} value={c.code}>{c.name}</Select.Item>
|
|
114
|
+
))}
|
|
115
|
+
</Select.Content>
|
|
116
|
+
</Select>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Headless components for styling flexibility:**
|
|
120
|
+
Use Radix UI, Headless UI, or React Aria as the unstyled behavior layer. Wire your token system on top. This gives accessible keyboard navigation and ARIA semantics for free while preserving full visual control.
|
|
121
|
+
|
|
122
|
+
**Token enforcement via linting:**
|
|
123
|
+
Use `stylelint-no-invalid-hex` and custom Stylelint rules (or ESLint for CSS-in-JS) to reject hardcoded color values not referencing a token. Automate this in CI.
|
|
124
|
+
|
|
125
|
+
### Typography Scale
|
|
126
|
+
|
|
127
|
+
Build a modular type scale with explicit roles:
|
|
128
|
+
|
|
129
|
+
```css
|
|
130
|
+
:root {
|
|
131
|
+
/* Scale steps — use a modular scale ratio (1.25 or 1.333) */
|
|
132
|
+
--text-xs: 0.75rem; /* 12px — labels, captions */
|
|
133
|
+
--text-sm: 0.875rem; /* 14px — body secondary */
|
|
134
|
+
--text-base: 1rem; /* 16px — body primary */
|
|
135
|
+
--text-lg: 1.125rem; /* 18px — subheadings */
|
|
136
|
+
--text-xl: 1.25rem; /* 20px — section headings */
|
|
137
|
+
--text-2xl: 1.5rem; /* 24px — page headings */
|
|
138
|
+
--text-3xl: 1.875rem; /* 30px — hero headings */
|
|
139
|
+
|
|
140
|
+
/* Line heights tied to text size for proper vertical rhythm */
|
|
141
|
+
--leading-tight: 1.25;
|
|
142
|
+
--leading-normal: 1.5;
|
|
143
|
+
--leading-relaxed: 1.75;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Never use pixel values for font sizes in component code — only reference scale tokens. This ensures user font size preferences (browser zoom, accessibility settings) are respected.
|
|
148
|
+
|
|
149
|
+
### Design Token Pipeline
|
|
150
|
+
|
|
151
|
+
For teams with a Figma design system, automate the token pipeline:
|
|
152
|
+
|
|
153
|
+
1. Designers export tokens from Figma using the Tokens Studio plugin as a JSON file
|
|
154
|
+
2. CI runs a token transformer (Style Dictionary) that converts JSON to CSS custom properties, TypeScript constants, and platform-specific formats
|
|
155
|
+
3. The generated files are committed to the repository and reviewed in PRs
|
|
156
|
+
4. Token changes are flagged in design review before code review
|
|
157
|
+
|
|
158
|
+
This makes "design changed the brand blue" a designer-owned PR rather than an engineering ticket.
|