@se-studio/skills 1.0.5 → 1.0.11
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/CHANGELOG.md +38 -0
- package/package.json +1 -1
- package/skills/contentful-cms-setup/SKILL.md +91 -0
- package/skills/site-workflows-apply-design-snapshot/SKILL.md +285 -0
- package/skills/site-workflows-contentful-vercel-setup/SKILL.md +300 -0
- package/skills/site-workflows-copy-doc-snapshot/SKILL.md +222 -0
- package/skills/site-workflows-figma-design-snapshot/SKILL.md +390 -0
- package/skills/site-workflows-new-project/SKILL.md +278 -0
- package/skills/site-workflows-project-cleanup/SKILL.md +362 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Figma Design Snapshot"
|
|
3
|
+
description: "Regenerates design.md from Figma by extracting all design tokens, typography, and component specs. Produces a deterministic, git-diffable output — run on any day to see what designers have changed."
|
|
4
|
+
metadata:
|
|
5
|
+
author: se-studio
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Figma Design Snapshot
|
|
10
|
+
|
|
11
|
+
Regenerates `design.md` from Figma. Every run produces output in the same format and order, so `git diff design.md` shows exactly what designers changed between snapshots.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/site-workflows-figma-design-snapshot
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No arguments needed — reads config from `.design-snapshot.json` in the project root.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Step 1 — Read config
|
|
24
|
+
|
|
25
|
+
Read `.design-snapshot.json` from the project root. It has this shape:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"figmaFileKey": "<your-figma-file-key>",
|
|
30
|
+
"figmaPageId": "<page-node-id>",
|
|
31
|
+
"outputPath": "design.md",
|
|
32
|
+
"componentFrameIds": {
|
|
33
|
+
"Components": "<frame-node-id>"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If the file does not exist, ask the user:
|
|
39
|
+
- Figma file URL (extract `fileKey` from it)
|
|
40
|
+
- Which frame on the page contains the component library (name and node ID)
|
|
41
|
+
- Desired output path (default: `design.md`)
|
|
42
|
+
|
|
43
|
+
Then write `.design-snapshot.json` before continuing.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 2 — Extract tokens via Figma Plugin API
|
|
48
|
+
|
|
49
|
+
Call `use_figma` with the `fileKey` from config and the following JavaScript. This extracts all design tokens deterministically. **Do not deviate from this script** — it produces the stable, sorted output that makes git diffs useful.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
function round1(n) { return Math.round(n * 10) / 10; }
|
|
53
|
+
|
|
54
|
+
function hexFromColor(c, opacity) {
|
|
55
|
+
const hex = '#' + [c.r, c.g, c.b]
|
|
56
|
+
.map(v => Math.round(v * 255).toString(16).padStart(2, '0'))
|
|
57
|
+
.join('');
|
|
58
|
+
if (opacity !== undefined && Math.round(opacity * 100) < 100) {
|
|
59
|
+
return hex + ' / ' + Math.round(opacity * 100) + '%';
|
|
60
|
+
}
|
|
61
|
+
return hex;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// — Colors (sorted alphabetically by name) —
|
|
65
|
+
const colors = figma.getLocalPaintStyles()
|
|
66
|
+
.map(s => {
|
|
67
|
+
const p = s.paints[0];
|
|
68
|
+
if (!p) return null;
|
|
69
|
+
return {
|
|
70
|
+
name: s.name,
|
|
71
|
+
type: p.type,
|
|
72
|
+
value: p.type === 'SOLID' ? hexFromColor(p.color, p.opacity) : p.type,
|
|
73
|
+
};
|
|
74
|
+
})
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
77
|
+
|
|
78
|
+
// — Text styles (sorted alphabetically by name) —
|
|
79
|
+
const typeStyles = figma.getLocalTextStyles()
|
|
80
|
+
.map(s => ({
|
|
81
|
+
name: s.name,
|
|
82
|
+
family: s.fontName.family,
|
|
83
|
+
style: s.fontName.style,
|
|
84
|
+
size: s.fontSize,
|
|
85
|
+
lineHeight: s.lineHeight.unit === 'PERCENT'
|
|
86
|
+
? Math.round(s.lineHeight.value) + '%'
|
|
87
|
+
: s.lineHeight.unit === 'PIXELS'
|
|
88
|
+
? round1(s.lineHeight.value) + 'px'
|
|
89
|
+
: 'AUTO',
|
|
90
|
+
letterSpacing: s.letterSpacing.unit === 'PERCENT'
|
|
91
|
+
? round1(s.letterSpacing.value) + '%'
|
|
92
|
+
: round1(s.letterSpacing.value) + 'px',
|
|
93
|
+
textCase: s.textCase,
|
|
94
|
+
}))
|
|
95
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
96
|
+
|
|
97
|
+
// — Variables (sorted by collection/name) —
|
|
98
|
+
const varCollections = figma.variables.getLocalVariableCollections();
|
|
99
|
+
const vars = figma.variables.getLocalVariables()
|
|
100
|
+
.map(v => {
|
|
101
|
+
const col = varCollections.find(vc => vc.variableIds.includes(v.id));
|
|
102
|
+
const values = {};
|
|
103
|
+
for (const [modeId, val] of Object.entries(v.valuesByMode)) {
|
|
104
|
+
const mode = col?.modes.find(m => m.modeId === modeId);
|
|
105
|
+
const key = mode?.name || modeId;
|
|
106
|
+
if (val && typeof val === 'object' && 'type' in val && val.type === 'VARIABLE_ALIAS') {
|
|
107
|
+
const ref = figma.variables.getVariableById(val.id);
|
|
108
|
+
values[key] = '→ ' + (ref?.name || val.id);
|
|
109
|
+
} else if (val && typeof val === 'object' && 'r' in val) {
|
|
110
|
+
values[key] = hexFromColor(val);
|
|
111
|
+
} else {
|
|
112
|
+
values[key] = String(val);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
collection: col?.name || '',
|
|
117
|
+
name: v.name,
|
|
118
|
+
type: v.resolvedType,
|
|
119
|
+
values,
|
|
120
|
+
};
|
|
121
|
+
})
|
|
122
|
+
.sort((a, b) => (a.collection + '/' + a.name).localeCompare(b.collection + '/' + b.name));
|
|
123
|
+
|
|
124
|
+
// — Top-level frames on this page (sorted by name) —
|
|
125
|
+
const frames = figma.currentPage.children
|
|
126
|
+
.filter(n => n.type === 'FRAME' || n.type === 'COMPONENT' || n.type === 'COMPONENT_SET')
|
|
127
|
+
.map(n => ({ id: n.id, name: n.name, width: Math.round(n.width), height: Math.round(n.height) }))
|
|
128
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
129
|
+
|
|
130
|
+
return JSON.stringify({ colors, typeStyles, vars, frames }, null, 2);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Store the parsed result as `tokens`.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Step 3 — Extract component frame properties
|
|
138
|
+
|
|
139
|
+
For each entry in `componentFrameIds` from config, call `use_figma` with this script, replacing `TARGET_ID` with the frame's node ID:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
function round1(n) { return Math.round(n * 10) / 10; }
|
|
143
|
+
|
|
144
|
+
function hexFromColor(c, a) {
|
|
145
|
+
const hex = '#' + [c.r, c.g, c.b]
|
|
146
|
+
.map(v => Math.round(v * 255).toString(16).padStart(2, '0'))
|
|
147
|
+
.join('');
|
|
148
|
+
return (a !== undefined && Math.round(a * 100) < 100) ? hex + ' / ' + Math.round(a * 100) + '%' : hex;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractLayout(node) {
|
|
152
|
+
const out = {
|
|
153
|
+
id: node.id,
|
|
154
|
+
name: node.name,
|
|
155
|
+
type: node.type,
|
|
156
|
+
width: round1(node.width),
|
|
157
|
+
height: round1(node.height),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Border radius
|
|
161
|
+
if (typeof node.cornerRadius === 'number' && node.cornerRadius > 0) {
|
|
162
|
+
out.cornerRadius = node.cornerRadius;
|
|
163
|
+
} else if ('topLeftRadius' in node) {
|
|
164
|
+
const radii = [node.topLeftRadius, node.topRightRadius, node.bottomRightRadius, node.bottomLeftRadius];
|
|
165
|
+
if (radii.some(r => r > 0)) out.cornerRadius = radii.join(' ');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fill colours
|
|
169
|
+
if ('fills' in node && Array.isArray(node.fills)) {
|
|
170
|
+
const fills = node.fills.filter(f => f.visible !== false && f.type === 'SOLID');
|
|
171
|
+
if (fills.length > 0) out.fill = hexFromColor(fills[0].color, fills[0].opacity);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Strokes / borders
|
|
175
|
+
if ('strokes' in node && node.strokes.length > 0) {
|
|
176
|
+
const strokes = node.strokes.filter(s => s.visible !== false && s.type === 'SOLID');
|
|
177
|
+
if (strokes.length > 0) {
|
|
178
|
+
out.stroke = hexFromColor(strokes[0].color);
|
|
179
|
+
// Individual stroke weights (three-sided buttons etc.)
|
|
180
|
+
if ('strokeTopWeight' in node) {
|
|
181
|
+
out.strokeWeights = {
|
|
182
|
+
top: node.strokeTopWeight,
|
|
183
|
+
right: node.strokeRightWeight,
|
|
184
|
+
bottom: node.strokeBottomWeight,
|
|
185
|
+
left: node.strokeLeftWeight,
|
|
186
|
+
};
|
|
187
|
+
} else if ('strokeWeight' in node) {
|
|
188
|
+
out.strokeWeight = node.strokeWeight;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Auto-layout (padding + gap)
|
|
194
|
+
if ('layoutMode' in node && node.layoutMode !== 'NONE') {
|
|
195
|
+
out.layout = {
|
|
196
|
+
direction: node.layoutMode,
|
|
197
|
+
paddingTop: node.paddingTop,
|
|
198
|
+
paddingRight: node.paddingRight,
|
|
199
|
+
paddingBottom: node.paddingBottom,
|
|
200
|
+
paddingLeft: node.paddingLeft,
|
|
201
|
+
gap: node.itemSpacing,
|
|
202
|
+
wrap: node.layoutWrap === 'WRAP',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Recurse into named children (skip hidden, unnamed, or pure graphic nodes)
|
|
207
|
+
if ('children' in node) {
|
|
208
|
+
const namedChildren = node.children.filter(c =>
|
|
209
|
+
c.visible !== false &&
|
|
210
|
+
c.name &&
|
|
211
|
+
!c.name.startsWith('_') &&
|
|
212
|
+
(c.type === 'FRAME' || c.type === 'COMPONENT' || c.type === 'INSTANCE' || c.type === 'GROUP')
|
|
213
|
+
);
|
|
214
|
+
if (namedChildren.length > 0) {
|
|
215
|
+
out.children = namedChildren.map(extractLayout);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const node = figma.getNodeById('TARGET_ID');
|
|
223
|
+
if (!node) return JSON.stringify({ error: 'Node not found: TARGET_ID' });
|
|
224
|
+
return JSON.stringify(extractLayout(node), null, 2);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Store results as `componentFrames[frameName]`.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Step 4 — Get today's date
|
|
232
|
+
|
|
233
|
+
Run `date -u +"%Y-%m-%d"` via Bash to get today's UTC date. Store as `snapshotDate`.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Step 5 — Write design.md
|
|
238
|
+
|
|
239
|
+
Write the output file to `outputPath` from config. Use **exactly this structure and section order** every run — consistent ordering is what makes git diffs useful.
|
|
240
|
+
|
|
241
|
+
### Rules for determinism
|
|
242
|
+
|
|
243
|
+
- **Colors**: output in alphabetical order by `name`. Within each group (Primary / Neutral), sort alphabetically.
|
|
244
|
+
- **Typography**: output sorted by `name` (alphabetical). Because names follow the `DSK/H1` / `MBL/H1` pattern, this naturally groups desktop then mobile.
|
|
245
|
+
- **Variables**: output sorted by `collection/name`.
|
|
246
|
+
- **Frame inventory**: sorted by name.
|
|
247
|
+
- **Numbers**: always round to 1 decimal place. Drop `.0` (e.g. `12` not `12.0`). Exception: font sizes are always whole numbers.
|
|
248
|
+
- **Never include Figma asset URLs** — they expire in 7 days and will always produce diff noise.
|
|
249
|
+
- **No screenshots** — text and tables only.
|
|
250
|
+
|
|
251
|
+
### Output template
|
|
252
|
+
|
|
253
|
+
```markdown
|
|
254
|
+
# [Page Name] — Design Reference
|
|
255
|
+
|
|
256
|
+
> Source: Figma file `[fileKey]` · Page `[figmaPageId]` · Snapshot: [snapshotDate]
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Colour Palette
|
|
261
|
+
|
|
262
|
+
### [Group name — derived from paint style name prefix, e.g. "Pedestal Mini/..." → "Pedestal Mini"]
|
|
263
|
+
|
|
264
|
+
If all styles share the same prefix (e.g. `Pedestal Mini/Black`) group them under that prefix.
|
|
265
|
+
If styles have no prefix group, list them all under a single "Colours" heading.
|
|
266
|
+
|
|
267
|
+
| Token | Hex | Swatch |
|
|
268
|
+
|-------|-----|--------|
|
|
269
|
+
| [name (without prefix)] | `[hex]` | <div style="width:48px;height:24px;background:[hex];border:1px solid #e0e0e0;border-radius:4px"></div> |
|
|
270
|
+
|
|
271
|
+
Repeat for each group, sorted alphabetically within each group.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Typography
|
|
276
|
+
|
|
277
|
+
**Font family:** [family name(s), comma-separated if multiple]
|
|
278
|
+
**Weights used:** [list weights as Regular (400), Medium (500), etc.]
|
|
279
|
+
|
|
280
|
+
### Desktop
|
|
281
|
+
|
|
282
|
+
| Style | Size | Weight | Line Height | Letter Spacing |
|
|
283
|
+
|-------|------|--------|-------------|----------------|
|
|
284
|
+
[one row per DSK/* style, sorted by name]
|
|
285
|
+
|
|
286
|
+
### Mobile
|
|
287
|
+
|
|
288
|
+
| Style | Size | Weight | Line Height | Letter Spacing |
|
|
289
|
+
|-------|------|--------|-------------|----------------|
|
|
290
|
+
[one row per MBL/* style, sorted by name]
|
|
291
|
+
|
|
292
|
+
[If styles use a different naming convention than DSK/MBL, use a single unsplit table instead]
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Variables
|
|
297
|
+
|
|
298
|
+
[Only include this section if vars.length > 0]
|
|
299
|
+
|
|
300
|
+
### [Collection name]
|
|
301
|
+
|
|
302
|
+
| Variable | Type | [Mode 1] | [Mode 2] |
|
|
303
|
+
|----------|------|----------|----------|
|
|
304
|
+
[one row per variable, sorted by name within collection]
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Breakpoints & Canvas
|
|
309
|
+
|
|
310
|
+
| Breakpoint | Canvas Width |
|
|
311
|
+
|------------|-------------|
|
|
312
|
+
[derived from frames — look for the widest desktop frame and widest mobile frame]
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Spacing & Layout
|
|
317
|
+
|
|
318
|
+
Extract these values from the component frame data. Produce a flat table — one row per spacing token.
|
|
319
|
+
Derive tokens by inspecting named component frames for padding/gap values.
|
|
320
|
+
|
|
321
|
+
| Token | Desktop | Mobile |
|
|
322
|
+
|-------|---------|--------|
|
|
323
|
+
| Page side padding | [px] | [px] |
|
|
324
|
+
| Nav vertical padding | [px] | [px] |
|
|
325
|
+
| Section vertical padding | [px] | [px] |
|
|
326
|
+
| [etc — only include rows you have data for] | | |
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Components
|
|
331
|
+
|
|
332
|
+
For each named top-level child of the component frame, write a subsection.
|
|
333
|
+
Use the frame name as the heading. Include:
|
|
334
|
+
- A spec table of key layout properties (padding, gap, border-radius, fill, stroke, dimensions)
|
|
335
|
+
- A brief plain-English description of the component's visual behaviour (1–2 sentences)
|
|
336
|
+
|
|
337
|
+
### [Component Name]
|
|
338
|
+
|
|
339
|
+
[1–2 sentence description]
|
|
340
|
+
|
|
341
|
+
| Property | Value |
|
|
342
|
+
|----------|-------|
|
|
343
|
+
| Width | [px] |
|
|
344
|
+
| Height | [px] |
|
|
345
|
+
| Border radius | [px] |
|
|
346
|
+
| Fill | [hex or "none"] |
|
|
347
|
+
| Stroke | [hex, weight, sides] |
|
|
348
|
+
| Padding | [top / right / bottom / left in px] |
|
|
349
|
+
| Gap | [px] |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Frame Inventory
|
|
354
|
+
|
|
355
|
+
All top-level frames on this Figma page, sorted alphabetically.
|
|
356
|
+
|
|
357
|
+
| Frame | Node ID | Width | Height |
|
|
358
|
+
|-------|---------|-------|--------|
|
|
359
|
+
[one row per frame from tokens.frames]
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Figma Source
|
|
364
|
+
|
|
365
|
+
| Field | Value |
|
|
366
|
+
|-------|-------|
|
|
367
|
+
| File key | `[fileKey]` |
|
|
368
|
+
| Page ID | `[figmaPageId]` |
|
|
369
|
+
| Component frame | `[name]` (`[nodeId]`) |
|
|
370
|
+
| Snapshot date | [snapshotDate] |
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Step 6 — Verify
|
|
376
|
+
|
|
377
|
+
After writing the file:
|
|
378
|
+
1. Confirm the file was written successfully.
|
|
379
|
+
2. Report which sections were populated and which were empty (e.g. "0 variables found").
|
|
380
|
+
3. Tell the user the snapshot date and how to commit it: `git add design.md && git commit -m "design: snapshot [date]"`.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Notes for future runs
|
|
385
|
+
|
|
386
|
+
- The output is intentionally **idempotent**: running the skill twice on the same day with no Figma changes should produce an identical file (no diff).
|
|
387
|
+
- Running it after a designer makes changes will produce a clean, readable diff showing exactly what tokens or component values changed.
|
|
388
|
+
- The `.design-snapshot.json` config file should also be committed so any team member can regenerate the snapshot.
|
|
389
|
+
- If the Figma file gains new paint styles, text styles, or component frames, they will appear in the diff as additions.
|
|
390
|
+
- If items are renamed in Figma, they will appear as a deletion + addition in the diff (because output is sorted by name).
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: site-workflows-new-project
|
|
3
|
+
description: Bootstrap a new SE Studio customer project from the SE Studio template. Runs full project cleanup (black/white colors, system font, minimal registrations), then sets up CLAUDE.md, AGENTS.md, README files, STYLING.md, ANIMATION.md, and updates project identity (site title, description, .cursorrules). Use this immediately after cloning the template for a new client.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: se-studio
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# SE New Project Bootstrap
|
|
11
|
+
|
|
12
|
+
Use this skill immediately after cloning the SE Studio customer project template for a new client.
|
|
13
|
+
|
|
14
|
+
**Prerequisites:**
|
|
15
|
+
- The project has been cloned/copied from the SE Studio customer project template
|
|
16
|
+
- `.env.local` is configured with the new Contentful space credentials (`CONTENTFUL_SPACE_ID`, `CONTENTFUL_ACCESS_TOKEN`, `CONTENTFUL_MANAGEMENT_TOKEN`, `CONTENTFUL_PREVIEW_ACCESS_TOKEN`)
|
|
17
|
+
- `pnpm install` has been run
|
|
18
|
+
- A `scripts/master-schema.json` file exists (exported from the source Contentful space, or the new empty space)
|
|
19
|
+
- You know: **customer/project name**, **site title**, **site description**
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Step 1 — Gather project identity
|
|
24
|
+
|
|
25
|
+
Before making any changes, confirm the following with the user if not already provided:
|
|
26
|
+
|
|
27
|
+
1. **Customer/project name** — used in README, CLAUDE.md header, and Navigation stub (e.g. "Acme Corp")
|
|
28
|
+
2. **Site title** — used in `src/lib/constants.ts` and `<title>` tag (e.g. "Acme Corp — Engineering")
|
|
29
|
+
3. **Site description** — used in meta description (1–2 sentences describing the site)
|
|
30
|
+
4. **Domain** (optional) — used in sitemap and OG URLs if known
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 2 — Run the full cleanup
|
|
35
|
+
|
|
36
|
+
Execute every step in the `site-workflows-project-cleanup` skill. This strips the template down to:
|
|
37
|
+
- Black/white color system
|
|
38
|
+
- System font
|
|
39
|
+
- Generic Component, Generic Collection, Related Articles Collection only
|
|
40
|
+
- Minimal Navigation and Footer stubs
|
|
41
|
+
- Cleaned Contentful schema imported to the new space
|
|
42
|
+
|
|
43
|
+
After cleanup, `pnpm validate` must pass before continuing.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 3 — Update project identity in `src/lib/constants.ts`
|
|
48
|
+
|
|
49
|
+
Read `src/lib/constants.ts` and update:
|
|
50
|
+
- `siteTitle` → the confirmed site title
|
|
51
|
+
- `siteDescription` → the confirmed site description
|
|
52
|
+
- Any domain or URL constants → the confirmed domain (or leave as placeholder if unknown)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Step 4 — Update the Navigation and Footer stubs
|
|
57
|
+
|
|
58
|
+
In `src/project/components/Navigation/index.tsx`, replace `"Site Name"` with the customer/project name.
|
|
59
|
+
|
|
60
|
+
In `src/project/components/Footer/index.tsx`, replace `"Site Name"` with the customer/project name.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Step 5 — Create `CLAUDE.md`
|
|
65
|
+
|
|
66
|
+
Write `CLAUDE.md` at the project root:
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
@.cursorrules
|
|
70
|
+
|
|
71
|
+
@AGENTS.md
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Step 6 — Create `AGENTS.md`
|
|
77
|
+
|
|
78
|
+
Write `AGENTS.md` at the project root. Fill in the user's email address (from memory if known, otherwise ask) and today's date:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
# Next.js: ALWAYS read docs before coding
|
|
82
|
+
|
|
83
|
+
Before any Next.js work, find and read the relevant doc in `node_modules/next/dist/docs/`. Your training data is outdated — the docs are the source of truth.
|
|
84
|
+
|
|
85
|
+
# userEmail
|
|
86
|
+
The user's email address is [USER_EMAIL].
|
|
87
|
+
|
|
88
|
+
# currentDate
|
|
89
|
+
Today's date is [CURRENT_DATE].
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Step 7 — Update `.cursorrules`
|
|
95
|
+
|
|
96
|
+
Read `.cursorrules` and update the **Project Overview** section at the top:
|
|
97
|
+
- Replace the previous project name with the new customer/project name
|
|
98
|
+
- Update the "Key technologies" list if anything changed
|
|
99
|
+
- Remove any project-specific notes about the previous customer
|
|
100
|
+
|
|
101
|
+
Do not change the Code Style, Component Architecture, or other generic sections — those apply to all SE Studio projects.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Step 8 — Create `src/project/components/README.md`
|
|
106
|
+
|
|
107
|
+
Write the standard component development guide. This file should explain the four-layer architecture pattern used in this project. Reference the `se-marketing-sites-create-component` skill, which contains a comprehensive template.
|
|
108
|
+
|
|
109
|
+
Key sections to include:
|
|
110
|
+
- Architecture overview (four-layer pattern)
|
|
111
|
+
- FIELD_KEYS / USED_FIELDS pattern
|
|
112
|
+
- `getSizingInformation(index)` usage
|
|
113
|
+
- `getPreviewFieldProps` usage
|
|
114
|
+
- Client component isolation pattern
|
|
115
|
+
- Checklist for new components
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Step 9 — Create `src/project/collections/README.md`
|
|
120
|
+
|
|
121
|
+
Write the standard collection development guide. Reference the `se-marketing-sites-create-collection` skill.
|
|
122
|
+
|
|
123
|
+
Key sections to include:
|
|
124
|
+
- Five-layer architecture (Card, Core, Wrapper, Renderer, Registration)
|
|
125
|
+
- Dual FIELD_KEYS pattern (collection-level + card-level)
|
|
126
|
+
- `getSizingInformation(1)` for card titles (ChildElement)
|
|
127
|
+
- Parent-child ID tracking for preview helpers
|
|
128
|
+
- Client component isolation (carousels, filters)
|
|
129
|
+
- Checklist for new collections
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Step 10 — Create `src/project/STYLING.md`
|
|
134
|
+
|
|
135
|
+
Create a styling reference document for this project. Template:
|
|
136
|
+
|
|
137
|
+
```markdown
|
|
138
|
+
# Styling System — [Project Name]
|
|
139
|
+
|
|
140
|
+
This project uses Tailwind CSS v4 with configuration driven by `tailwind.config.json`.
|
|
141
|
+
|
|
142
|
+
## Colors
|
|
143
|
+
|
|
144
|
+
Two named colors:
|
|
145
|
+
|
|
146
|
+
| Name | Hex | Tailwind class |
|
|
147
|
+
|-------|---------|-----------------|
|
|
148
|
+
| Light | #FFFFFF | `bg-light` / `text-light` |
|
|
149
|
+
| Dark | #000000 | `bg-dark` / `text-dark` |
|
|
150
|
+
|
|
151
|
+
Use `lookupColourClassNames()` from `@/generated/colors` when applying background + text color pairs.
|
|
152
|
+
|
|
153
|
+
## Typography
|
|
154
|
+
|
|
155
|
+
Typography utility classes are generated from `tailwind.config.json`. Common classes:
|
|
156
|
+
|
|
157
|
+
| Class | Usage |
|
|
158
|
+
|----------|----------------------------|
|
|
159
|
+
| `h1` | Hero headings (uppercase) |
|
|
160
|
+
| `h2` | Section headings |
|
|
161
|
+
| `h3` | Sub-headings |
|
|
162
|
+
| `h4` | Small headings |
|
|
163
|
+
| `p1` | Large body text |
|
|
164
|
+
| `p2` | Default body text |
|
|
165
|
+
| `p3` | Small/caption text |
|
|
166
|
+
| `nav` | Navigation links |
|
|
167
|
+
|
|
168
|
+
Use `getSizingInformation(index)` from `@/lib/SizingInformation` for dynamic semantic headings.
|
|
169
|
+
|
|
170
|
+
## Grid System
|
|
171
|
+
|
|
172
|
+
| Breakpoint | Min-width | Columns |
|
|
173
|
+
|-----------|-----------|---------|
|
|
174
|
+
| mobile | 0 | 6 |
|
|
175
|
+
| tablet | 768px | 8 |
|
|
176
|
+
| laptop | 1024px | 12 |
|
|
177
|
+
| desktop | 1440px | 12 |
|
|
178
|
+
|
|
179
|
+
Gap: 16px at all breakpoints. Margin: 16px at all breakpoints.
|
|
180
|
+
|
|
181
|
+
Use `content-cols-grid` for standard content layout.
|
|
182
|
+
|
|
183
|
+
## Section Spacing
|
|
184
|
+
|
|
185
|
+
Standard vertical padding: `py-16 laptop:py-24`
|
|
186
|
+
|
|
187
|
+
## RTF Classes
|
|
188
|
+
|
|
189
|
+
| Class | Usage |
|
|
190
|
+
|----------------|-----------------------------------|
|
|
191
|
+
| `.rtf-standard`| General rich text content |
|
|
192
|
+
| `.rtf-article` | Article/blog body text |
|
|
193
|
+
| `.rtf-legal` | Terms, conditions, legal text |
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Step 11 — Create `src/project/ANIMATION.md`
|
|
199
|
+
|
|
200
|
+
Create a minimal animation guide. Template:
|
|
201
|
+
|
|
202
|
+
```markdown
|
|
203
|
+
# Animation System — [Project Name]
|
|
204
|
+
|
|
205
|
+
Animations use a two-part trigger pattern:
|
|
206
|
+
|
|
207
|
+
1. Add `animate-when-seen` to pause the animation until the element is visible
|
|
208
|
+
2. Add the animation class (e.g. `animate-text-in`)
|
|
209
|
+
|
|
210
|
+
`Section` sets `data-seen` on the element when it enters the viewport.
|
|
211
|
+
|
|
212
|
+
## Adding a New Animation
|
|
213
|
+
|
|
214
|
+
1. Define `@keyframes` in `src/app/globals.css`
|
|
215
|
+
2. Register a `--animate-*` CSS variable in the `@theme` block
|
|
216
|
+
3. Create a `@utility animate-your-name` block that applies the animation
|
|
217
|
+
4. Apply `animate-when-seen animate-your-name` to the element
|
|
218
|
+
|
|
219
|
+
## Current Animations
|
|
220
|
+
|
|
221
|
+
None — this is a bare-bones project. Add animations as the design is built out.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Step 12 — Verify the Contentful schema import
|
|
227
|
+
|
|
228
|
+
Confirm the cleaned schema was successfully imported to the new space:
|
|
229
|
+
|
|
230
|
+
1. Open the Contentful web app for the new space
|
|
231
|
+
2. Navigate to Content model → Component → `componentType` field
|
|
232
|
+
3. Verify the allowed values are: `Generic` only
|
|
233
|
+
4. Navigate to Content model → Collection → `collectionType` field
|
|
234
|
+
5. Verify the allowed values are: `Generic`, `Related articles`
|
|
235
|
+
|
|
236
|
+
If the import did not run during cleanup (Step 2), run it now:
|
|
237
|
+
```bash
|
|
238
|
+
node scripts/clean-schema.js
|
|
239
|
+
contentful space import \
|
|
240
|
+
--space-id $(grep CONTENTFUL_SPACE_ID .env.local | cut -d= -f2) \
|
|
241
|
+
--environment-id master \
|
|
242
|
+
--content-file scripts/clean-schema.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Step 13 — Final validation
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pnpm validate
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
All TypeScript errors must be resolved. Then start the dev server to confirm the site runs:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pnpm dev
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Check:
|
|
260
|
+
- `/` — home page loads (may show "not found" if no CMS content, that's fine)
|
|
261
|
+
- `/cms/showcase` — shows only Generic Component, Generic Collection, Related Articles
|
|
262
|
+
- `/cms/guidelines.md` — component guidelines render
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## What to do next
|
|
267
|
+
|
|
268
|
+
After bootstrap, the project is ready for:
|
|
269
|
+
1. **CMS content setup** — use `contentful-cms-core` skill to create initial pages and navigation
|
|
270
|
+
2. **Design system** — update `tailwind.config.json` with brand colors and typography when the design is ready
|
|
271
|
+
3. **Components** — use `se-marketing-sites-create-component` and `se-marketing-sites-create-collection` skills to build project-specific components
|
|
272
|
+
4. **Navigation/Footer** — replace the stubs with real components once the design is known
|
|
273
|
+
|
|
274
|
+
Commit the bootstrapped state before starting component work:
|
|
275
|
+
```bash
|
|
276
|
+
git add -A
|
|
277
|
+
git commit -m "Bootstrap new project from SE Studio template"
|
|
278
|
+
```
|