@open-assets/open-assets 0.1.11 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-assets/open-assets",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "Dev server and export tool for app screenshots, icons, and logos. Like Storybook for marketing assets.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,6 +38,11 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
+ "scripts": {
42
+ "assets": "node bin/open-assets.mjs render --force -o ./public/img",
43
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
44
+ "release": "./scripts/release.sh"
45
+ },
41
46
  "homepage": "https://openassets.dev",
42
47
  "dependencies": {
43
48
  "commander": "^13.0.0",
@@ -45,6 +50,6 @@
45
50
  "puppeteer": "^24.37.5"
46
51
  },
47
52
  "devDependencies": {
48
- "@open-assets/open-assets": "^0.1.10"
53
+ "jest": "^30.3.0"
49
54
  }
50
55
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -4,6 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Open Assets</title>
7
+ <link rel="icon" type="image/png" sizes="32x32" href="/img/icon/32/icon.png" />
8
+ <link rel="icon" type="image/png" sizes="16x16" href="/img/icon/16/icon.png" />
7
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
8
10
  <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
9
11
  <style>
@@ -12,6 +14,7 @@
12
14
 
13
15
  .toolbar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; height: 48px; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; background: rgba(10,10,10,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.06); }
14
16
  .toolbar-left { display: flex; align-items: center; gap: 12px; }
17
+ .toolbar-left img.toolbar-icon { width: 20px; height: 20px; border-radius: 4px; }
15
18
  .toolbar-left h1 { font-size: 14px; font-weight: 700; color: #fff; }
16
19
  .segmented { display: flex; background: rgba(255,255,255,0.06); border-radius: 6px; padding: 2px; gap: 1px; }
17
20
  .seg-btn { padding: 4px 14px; border-radius: 5px; border: none; background: transparent; color: #888; font-size: 12px; font-weight: 600; cursor: pointer; transition: background 0.15s, color 0.15s; font-family: inherit; text-decoration: none; }
@@ -82,6 +85,7 @@
82
85
  <body>
83
86
  <div class="toolbar">
84
87
  <div class="toolbar-left">
88
+ <img class="toolbar-icon" src="/img/icon/32/icon.png" alt="" />
85
89
  <h1 id="app-title">Loading...</h1>
86
90
  <div class="segmented" id="tab-bar"></div>
87
91
  </div>
@@ -174,7 +178,7 @@
174
178
  }
175
179
 
176
180
  function flatSizes(col) {
177
- return (col.exportSizes || []).flatMap(g => g.sizes || []);
181
+ return col.export || [];
178
182
  }
179
183
 
180
184
  // ---- Build collection tab bar ----
@@ -250,69 +254,68 @@
250
254
  const sh = col.sourceSize.height;
251
255
  const hasMultipleTemplates = col.templates.length > 1;
252
256
 
253
- // Outputs (e.g., Xcode)
254
- if (col.outputs) {
255
- for (let i = 0; i < col.outputs.length; i++) {
256
- const output = col.outputs[i];
257
- if (output.type === 'xcode') {
258
- const sectionLabel = document.createElement('div');
259
- sectionLabel.className = 'section-label';
260
- sectionLabel.textContent = 'Xcode';
261
- exportDropdown.appendChild(sectionLabel);
262
-
263
- const btn = document.createElement('button');
264
- btn.innerHTML = 'Export to Xcode <span class="dim">.appiconset</span>';
265
- btn.addEventListener('click', () => runOutput(col.id, i));
266
- exportDropdown.appendChild(btn);
267
-
268
- exportDropdown.appendChild(document.createElement('hr'));
269
- } else if (output.type === 'copy-source') {
270
- const sectionLabel = document.createElement('div');
271
- sectionLabel.className = 'section-label';
272
- sectionLabel.textContent = output.format.toUpperCase();
273
- exportDropdown.appendChild(sectionLabel);
274
-
275
- // SVG download for each template
276
- for (const template of col.templates) {
277
- if (template.src.endsWith('.' + output.format)) {
278
- const btn = document.createElement('button');
279
- const displayName = hasMultipleTemplates ? template.label : `Download ${output.format.toUpperCase()}`;
280
- btn.innerHTML = `${displayName} <span class="dim">${output.format}</span>`;
281
- btn.addEventListener('click', () => downloadSource(template));
282
- exportDropdown.appendChild(btn);
283
- }
284
- }
257
+ // Outputs (e.g., Xcode, copy-source) — export entries with a type field
258
+ const allExports = col.export || [];
259
+ const outputs = allExports.filter(e => e.type);
260
+ for (let i = 0; i < outputs.length; i++) {
261
+ const output = outputs[i];
262
+ if (output.type === 'xcode') {
263
+ const sectionLabel = document.createElement('div');
264
+ sectionLabel.className = 'section-label';
265
+ sectionLabel.textContent = 'Xcode';
266
+ exportDropdown.appendChild(sectionLabel);
285
267
 
286
- exportDropdown.appendChild(document.createElement('hr'));
287
- }
288
- }
289
- }
268
+ const btn = document.createElement('button');
269
+ btn.innerHTML = 'Export to Xcode <span class="dim">.appiconset</span>';
270
+ btn.addEventListener('click', () => runOutput(col.id, i));
271
+ exportDropdown.appendChild(btn);
290
272
 
291
- // Export sizes grouped by platform
292
- for (const group of col.exportSizes || []) {
293
- if (group.platform) {
273
+ exportDropdown.appendChild(document.createElement('hr'));
274
+ } else if (output.type === 'copy-source') {
294
275
  const sectionLabel = document.createElement('div');
295
276
  sectionLabel.className = 'section-label';
296
- sectionLabel.textContent = group.platform;
277
+ sectionLabel.textContent = output.format.toUpperCase();
297
278
  exportDropdown.appendChild(sectionLabel);
298
- }
299
279
 
300
- for (const size of group.sizes || []) {
301
- const btn = document.createElement('button');
302
- btn.innerHTML = `${size.label} <span class="dim">${size.width} &times; ${size.height}</span>`;
303
- btn.addEventListener('click', () => {
304
- if (hasMultipleTemplates) {
305
- exportZip(size.width, size.height, sw, sh, size.name);
306
- } else {
307
- exportSinglePNG(col.templates[0], size.width, size.height, sw, sh, size.name);
280
+ // SVG download for each template
281
+ for (const template of col.templates) {
282
+ if (template.src.endsWith('.' + output.format)) {
283
+ const btn = document.createElement('button');
284
+ const displayName = hasMultipleTemplates ? template.label : `Download ${output.format.toUpperCase()}`;
285
+ btn.innerHTML = `${displayName} <span class="dim">${output.format}</span>`;
286
+ btn.addEventListener('click', () => downloadSource(template));
287
+ exportDropdown.appendChild(btn);
308
288
  }
309
- });
310
- exportDropdown.appendChild(btn);
289
+ }
290
+
291
+ exportDropdown.appendChild(document.createElement('hr'));
311
292
  }
312
293
  }
313
294
 
295
+ // Export sizes (entries without a type field)
296
+ const sizes = allExports.filter(e => !e.type);
297
+ if (sizes.length > 0) {
298
+ const sectionLabel = document.createElement('div');
299
+ sectionLabel.className = 'section-label';
300
+ sectionLabel.textContent = 'Sizes';
301
+ exportDropdown.appendChild(sectionLabel);
302
+ }
303
+
304
+ for (const entry of sizes) {
305
+ const btn = document.createElement('button');
306
+ btn.innerHTML = `${entry.label} <span class="dim">${entry.size.width} &times; ${entry.size.height}</span>`;
307
+ btn.addEventListener('click', () => {
308
+ if (hasMultipleTemplates) {
309
+ exportZip(entry.size.width, entry.size.height, sw, sh, entry.name);
310
+ } else {
311
+ exportSinglePNG(col.templates[0], entry.size.width, entry.size.height, sw, sh, entry.name);
312
+ }
313
+ });
314
+ exportDropdown.appendChild(btn);
315
+ }
316
+
314
317
  // Custom size
315
- if (col.exportSizes && flatSizes(col).length > 0 || col.customExport) {
318
+ if (col.export && col.export.length > 0 || col.customExport) {
316
319
  exportDropdown.appendChild(document.createElement('hr'));
317
320
 
318
321
  const customLabel = document.createElement('div');
@@ -320,8 +323,8 @@
320
323
  customLabel.textContent = 'Custom Size';
321
324
  exportDropdown.appendChild(customLabel);
322
325
 
323
- const defaultW = col.customExport?.defaultWidth || flatSizes(col)[0]?.width || sw;
324
- const defaultH = col.customExport?.defaultHeight || flatSizes(col)[0]?.height || sh;
326
+ const defaultW = col.customExport?.defaultWidth || flatSizes(col)[0]?.size?.width || sw;
327
+ const defaultH = col.customExport?.defaultHeight || flatSizes(col)[0]?.size?.height || sh;
325
328
 
326
329
  // Check if square (icon-like) for simplified input
327
330
  const isSquare = sw === sh;
@@ -446,7 +449,7 @@
446
449
 
447
450
  // ---- Init ----
448
451
  async function init() {
449
- const res = await fetch('manifest.json');
452
+ const res = await fetch('assets.json');
450
453
  manifest = await res.json();
451
454
 
452
455
  // Set title
@@ -15,15 +15,13 @@ open-assets is a dev server and export tool for app marketing assets — screens
15
15
  open-assets dev
16
16
 
17
17
  # Or with Tailwind
18
- concurrently "npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --watch" "open-assets dev"
18
+ concurrently "npx @tailwindcss/cli -i assets/styles.css -o dist/styles.css --watch" "open-assets dev"
19
19
 
20
20
  # Headless render
21
21
  open-assets render --collection screenshots --size iphone-6.9
22
- open-assets render --collection icon --all
23
- open-assets render --all
24
- open-assets render --template 01-hero --all
25
- open-assets render --platform "App Store"
26
- open-assets render --force # ignore cache, re-render everything
22
+ open-assets render --collection icon --force
23
+ open-assets render --force
24
+ open-assets render --template 01-hero --force
27
25
  ```
28
26
 
29
27
  ## Concepts
@@ -33,20 +31,19 @@ open-assets render --force # ignore cache, re-render everything
33
31
  | **Collection** | A named group of related assets sharing the same source size and export sizes. One tab in the dev UI. |
34
32
  | **Template** | A single source file (HTML or SVG) that produces one image per export size. |
35
33
  | **Export Size** | A named output dimension that templates are rendered at (e.g., "iPhone 6.9" → 1320×2868). |
36
- | **Platform** | Optional grouping label for related export sizes (e.g., "App Store", "Play Store"). |
37
34
  | **Source Size** | The dimensions the HTML template is authored at. Puppeteer scales from source → export size. |
38
35
  | **Output** | An optional post-render action (e.g., write to Xcode `.appiconset`, copy SVG source). |
39
36
 
40
- ## manifest.json
37
+ ## assets.json
41
38
 
42
- The `manifest.json` at the project root defines all asset collections. All collections follow the same unified schema — no `type` field needed.
39
+ The `assets.json` at the project root defines all asset collections. All collections follow the same unified schema — no `type` field needed.
43
40
 
44
41
  ```json
45
42
  {
46
43
  "version": 1,
47
44
  "name": "App Display Name",
48
45
  "publicDir": "public",
49
- "command": "npx open-assets render --all",
46
+ "command": "npx open-assets render --force",
50
47
  "collections": [
51
48
  {
52
49
  "id": "unique-id",
@@ -54,17 +51,11 @@ The `manifest.json` at the project root defines all asset collections. All colle
54
51
  "sourceSize": { "width": 440, "height": 956 },
55
52
  "borderRadius": 4,
56
53
  "templates": [
57
- { "src": "src/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" }
54
+ { "src": "assets/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" }
58
55
  ],
59
- "exportSizes": [
60
- {
61
- "platform": "App Store",
62
- "sizes": [
63
- { "name": "iphone-6.9", "label": "iPhone 6.9\"", "width": 1320, "height": 2868 }
64
- ]
65
- }
56
+ "export": [
57
+ { "name": "iphone-6.9", "label": "iPhone 6.9\"", "size": { "width": 1320, "height": 2868 } }
66
58
  ],
67
- "outputs": [],
68
59
  "customExport": { "defaultWidth": 1320, "defaultHeight": 2868 }
69
60
  }
70
61
  ]
@@ -80,12 +71,13 @@ The `manifest.json` at the project root defines all asset collections. All colle
80
71
  | `sourceSize` | object | `{ width, height }` — dimensions templates are authored at |
81
72
  | `borderRadius` | number | Border radius for preview frames (px) |
82
73
  | `templates` | array | Source files: `{ src, name, label }` |
83
- | `exportSizes` | array | Size groups: `{ platform?, sizes: [{ name, label, width, height }] }` |
84
- | `outputs` | array | Post-render actions (optional) |
74
+ | `export` | array | Export sizes and output actions. Entries with a `type` field are post-render actions |
85
75
  | `customExport` | object | Custom size defaults for UI (optional) |
86
76
 
87
77
  ### Output Types
88
78
 
79
+ Export entries with a `type` field are post-render actions (run with `--force`):
80
+
89
81
  | Type | Config | Description |
90
82
  |------|--------|-------------|
91
83
  | `xcode` | `{ "type": "xcode", "path": "..." }` | Render icon and write to Xcode asset catalog |
@@ -101,17 +93,12 @@ The `manifest.json` at the project root defines all asset collections. All colle
101
93
  "sourceSize": { "width": 440, "height": 956 },
102
94
  "borderRadius": 4,
103
95
  "templates": [
104
- { "src": "src/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" },
105
- { "src": "src/screenshots/02-features.html", "name": "02-features", "label": "Features" }
96
+ { "src": "assets/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" },
97
+ { "src": "assets/screenshots/02-features.html", "name": "02-features", "label": "Features" }
106
98
  ],
107
- "exportSizes": [
108
- {
109
- "platform": "App Store",
110
- "sizes": [
111
- { "name": "iphone-6.9", "label": "iPhone 6.9\"", "width": 1320, "height": 2868 },
112
- { "name": "iphone-6.7", "label": "iPhone 6.7\"", "width": 1290, "height": 2796 }
113
- ]
114
- }
99
+ "export": [
100
+ { "name": "iphone-6.9", "label": "iPhone 6.9\"", "size": { "width": 1320, "height": 2868 } },
101
+ { "name": "iphone-6.7", "label": "iPhone 6.7\"", "size": { "width": 1290, "height": 2796 } }
115
102
  ]
116
103
  }
117
104
  ```
@@ -124,18 +111,13 @@ The `manifest.json` at the project root defines all asset collections. All colle
124
111
  "sourceSize": { "width": 1024, "height": 1024 },
125
112
  "borderRadius": 224,
126
113
  "templates": [
127
- { "src": "src/icon.html", "name": "icon", "label": "App Icon" }
114
+ { "src": "assets/icon.html", "name": "icon", "label": "App Icon" }
128
115
  ],
129
- "exportSizes": [
130
- {
131
- "platform": "Apple",
132
- "sizes": [
133
- { "name": "1024", "label": "1024px", "width": 1024, "height": 1024 },
134
- { "name": "180", "label": "180px", "width": 180, "height": 180 }
135
- ]
136
- }
137
- ],
138
- "outputs": [{ "type": "xcode", "path": "../MyApp/Assets.xcassets/AppIcon.appiconset" }]
116
+ "export": [
117
+ { "name": "1024", "label": "1024px", "size": { "width": 1024, "height": 1024 } },
118
+ { "name": "180", "label": "180px", "size": { "width": 180, "height": 180 } },
119
+ { "type": "xcode", "path": "../MyApp/Assets.xcassets/AppIcon.appiconset" }
120
+ ]
139
121
  }
140
122
  ```
141
123
 
@@ -147,11 +129,11 @@ The `manifest.json` at the project root defines all asset collections. All colle
147
129
  "sourceSize": { "width": 1024, "height": 1024 },
148
130
  "borderRadius": 224,
149
131
  "templates": [
150
- { "src": "src/icons/concept-a.html", "name": "concept-a", "label": "Concept A" },
151
- { "src": "src/icons/concept-b.html", "name": "concept-b", "label": "Concept B" }
132
+ { "src": "assets/icons/concept-a.html", "name": "concept-a", "label": "Concept A" },
133
+ { "src": "assets/icons/concept-b.html", "name": "concept-b", "label": "Concept B" }
152
134
  ],
153
- "exportSizes": [
154
- { "sizes": [{ "name": "1024", "label": "1024px", "width": 1024, "height": 1024 }] }
135
+ "export": [
136
+ { "name": "1024", "label": "1024px", "size": { "width": 1024, "height": 1024 } }
155
137
  ]
156
138
  }
157
139
  ```
@@ -163,16 +145,14 @@ The `manifest.json` at the project root defines all asset collections. All colle
163
145
  "label": "Logo",
164
146
  "sourceSize": { "width": 940, "height": 940 },
165
147
  "templates": [
166
- { "src": "src/logo.svg", "name": "logo", "label": "Logo" },
167
- { "src": "src/logo-dark.svg", "name": "logo-dark", "label": "Dark" }
148
+ { "src": "assets/logo.svg", "name": "logo", "label": "Logo" },
149
+ { "src": "assets/logo-dark.svg", "name": "logo-dark", "label": "Dark" }
168
150
  ],
169
- "exportSizes": [
170
- { "sizes": [
171
- { "name": "1024", "label": "1024px", "width": 1024, "height": 1024 },
172
- { "name": "512", "label": "512px", "width": 512, "height": 512 }
173
- ] }
174
- ],
175
- "outputs": [{ "type": "copy-source", "format": "svg" }]
151
+ "export": [
152
+ { "name": "1024", "label": "1024px", "size": { "width": 1024, "height": 1024 } },
153
+ { "name": "512", "label": "512px", "size": { "width": 512, "height": 512 } },
154
+ { "type": "copy-source", "format": "svg" }
155
+ ]
176
156
  }
177
157
  ```
178
158
 
@@ -183,35 +163,96 @@ The `manifest.json` at the project root defines all asset collections. All colle
183
163
  "label": "Social Cards",
184
164
  "sourceSize": { "width": 1200, "height": 630 },
185
165
  "templates": [
186
- { "src": "src/og/default.html", "name": "default", "label": "Default OG" }
166
+ { "src": "assets/og/default.html", "name": "default", "label": "Default OG" }
187
167
  ],
188
- "exportSizes": [
189
- { "sizes": [{ "name": "og", "label": "OG Image", "width": 1200, "height": 630 }] }
168
+ "export": [
169
+ { "name": "og", "label": "OG Image", "size": { "width": 1200, "height": 630 } }
190
170
  ]
191
171
  }
192
172
  ```
193
173
 
194
174
  ## CLI Reference
195
175
 
176
+ ### All commands
177
+
178
+ | Command | Description |
179
+ |---------|-------------|
180
+ | `open-assets dev [dir]` | Start dev server with live preview and export UI |
181
+ | `open-assets render [dir]` | Render assets headlessly via CLI |
182
+ | `open-assets list [dir]` | List all collections and templates in the config |
183
+ | `open-assets validate [dir]` | Validate assets.json and check referenced files exist. Use `--fix` to interactively remove missing templates |
184
+ | `open-assets init [dir]` | Scaffold a new assets.json and example assets |
185
+ | `open-assets add collection [dir]` | Add a new collection (from presets or custom) |
186
+ | `open-assets add template [dir]` | Add a new template to an existing collection |
187
+ | `open-assets add size [dir]` | Add an export size to a collection |
188
+ | `open-assets skills [dir]` | Install Claude Code skills into a project |
189
+
196
190
  ### render command flags
197
191
 
198
192
  | Flag | Description |
199
193
  |------|-------------|
200
194
  | `--collection <id>` | Render only the collection with this ID |
201
195
  | `--template <name>` | Render only the template with this name |
202
- | `--size <name>` | Use a named export size from manifest |
203
- | `--platform <name>` | Render only sizes for this platform |
204
- | `--all` | Export at every size defined in the manifest |
205
- | `--force` | Re-render all assets even if unchanged |
196
+ | `--size <name>` | Use a named export size from config |
197
+ | `-f, --force` | Export at every size and re-render all (ignores cache) |
206
198
  | `-o, --output <dir>` | Output directory (default: `./exports`) |
207
199
  | `--json` | Output results as JSON |
208
200
  | `-q, --quiet` | Suppress progress logs |
209
201
 
210
202
  Flags compose naturally:
211
203
  - `--collection screenshots --template 01-hero --size iphone-6.9` → single template, single size
212
- - `--collection screenshots --template 01-hero --all` → one template, all sizes
204
+ - `--collection screenshots --template 01-hero --force` → one template, all sizes
213
205
  - `--collection screenshots --size iphone-6.9` → all templates, one size
214
- - `--collection screenshots --platform "App Store"` → all templates, all App Store sizes
206
+
207
+ ### add commands
208
+
209
+ Interactive commands for managing the config without editing JSON by hand. All support `--config <path>` for custom config filenames.
210
+
211
+ #### `open-assets add collection`
212
+
213
+ Add a new asset collection. Choose from presets or configure custom dimensions and format.
214
+
215
+ **Presets:**
216
+ | Preset | Source Size | Format |
217
+ |--------|-----------|--------|
218
+ | App Store Screenshots | 440x956 | HTML |
219
+ | App Icon | 1024x1024 | HTML |
220
+ | Logo | 800x800 | SVG |
221
+ | Wordmark | 1200x400 | SVG |
222
+ | Feature Graphic (Google Play) | 1024x500 | HTML |
223
+ | OG Image / Social | 1200x630 | HTML |
224
+ | Custom | user-defined | HTML or SVG |
225
+
226
+ Creates a starter template file and adds the collection to `assets.json` with appropriate export sizes.
227
+
228
+ ```bash
229
+ open-assets add collection # interactive preset picker
230
+ open-assets add collection ./my-assets # in a specific directory
231
+ ```
232
+
233
+ #### `open-assets add template`
234
+
235
+ Add a new template (HTML or SVG) to an existing collection. Prompts for which collection, template name/label, and format. Creates the source file in the same directory as the collection's existing templates.
236
+
237
+ ```bash
238
+ open-assets add template # interactive
239
+ ```
240
+
241
+ #### `open-assets add size`
242
+
243
+ Add an export size to a collection. Choose from common device/platform presets or enter custom dimensions.
244
+
245
+ **Size presets include:**
246
+ - iPhone 6.9", 6.7", 6.5", 6.1"
247
+ - iPad 13", 12.9"
248
+ - Google Play Phone, 7" Tablet
249
+ - Mac App Store Retina
250
+ - OG Image, Twitter Card, Product Hunt Gallery
251
+ - Custom dimensions
252
+
253
+ ```bash
254
+ open-assets add size # interactive
255
+ ```
215
256
 
216
257
  ### Output structure
217
258
 
@@ -237,9 +278,11 @@ exports/
237
278
 
238
279
  ## Creating New Screenshot HTML Files
239
280
 
240
- Each screenshot is a standalone HTML file at fixed dimensions. Key rules:
281
+ Each screenshot is a standalone HTML file at fixed dimensions. You can create them manually or use `open-assets add template` to scaffold one.
241
282
 
242
- 1. Set `width` and `height` on the body to match `sourceSize` in the manifest
283
+ Key rules:
284
+
285
+ 1. Set `width` and `height` on the body to match `sourceSize` in the config
243
286
  2. Set `overflow: hidden` on the body
244
287
  3. Reference compiled Tailwind CSS via `<link rel="stylesheet" href="../dist/styles.css" />`
245
288
  4. All paths are relative to the project root
@@ -263,11 +306,13 @@ Example template:
263
306
  </html>
264
307
  ```
265
308
 
266
- After creating the file, add it to the `templates` array in the relevant collection in `manifest.json`.
309
+ After creating the file manually, add it to the `templates` array in the relevant collection in `assets.json`. Or use `open-assets add template` to do both steps at once.
267
310
 
268
311
  ## Adding a New Collection
269
312
 
270
- Add a new object to the `collections` array in `manifest.json`. Each collection needs a unique `id`, a `sourceSize`, `templates`, and `exportSizes`.
313
+ Use `open-assets add collection` to interactively add a collection from presets or with custom dimensions and format (HTML/SVG). This creates the starter template file and updates `assets.json` automatically.
314
+
315
+ Alternatively, add a new object to the `collections` array in `assets.json` manually. Each collection needs a unique `id`, a `sourceSize`, `templates`, and `export`.
271
316
 
272
317
  ## Common Export Sizes
273
318
 
@@ -284,9 +329,9 @@ Add a new object to the `collections` array in `manifest.json`. Each collection
284
329
  | Web | OG Image | 1200 | 630 |
285
330
  | Mac App Store | Retina | 2880 | 1800 |
286
331
 
287
- ## Incremental Builds (manifest.lock)
332
+ ## Incremental Builds (assets.lock)
288
333
 
289
- The `render` command maintains a `manifest.lock` file with SHA256 checksums of source files. On subsequent renders, unchanged assets are skipped automatically. Use `--force` to re-render everything.
334
+ The `render` command maintains a `assets.lock` file with SHA256 checksums of source files. On subsequent renders, unchanged assets are skipped automatically. Use `--force` to re-render everything.
290
335
 
291
336
  Note: only source HTML/SVG files are checksummed. Changes to referenced assets (images in publicDir, compiled CSS) will not trigger re-renders — use `--force` when those change.
292
337
 
@@ -294,15 +339,15 @@ Note: only source HTML/SVG files are checksummed. Changes to referenced assets (
294
339
 
295
340
  ```
296
341
  project/
297
- manifest.json
298
- manifest.lock # Auto-generated, add to .gitignore
342
+ assets.json
343
+ assets.lock # Auto-generated cache (commit this)
299
344
  public/ # Shared assets (images, logos, icons, photos)
300
345
  logo-round.png
301
346
  social/
302
347
  youtube.svg
303
348
  screenshots/ # Real app screenshots for use in templates
304
349
  01.png
305
- src/
350
+ assets/
306
351
  styles.css # Tailwind input
307
352
  icon.html # App icon template
308
353
  icons/ # Icon concept explorations
@@ -490,8 +535,8 @@ colored background on the key word. Add the app rating below the logo. Make the
490
535
  background food photo bleed off the bottom-right edge for visual impact.
491
536
  ```
492
537
 
493
- **Adding a new platform:**
538
+ **Adding a new export size:**
494
539
  ```
495
- Add Play Store screenshot sizes to the manifest. Use 1080x2400 as the export size
496
- under a "Play Store" platform group. The screenshots can share the same templates.
540
+ Add Play Store screenshot sizes to the config. Use 1080x2400 as the export size.
541
+ The screenshots can share the same templates.
497
542
  ```