fastmode-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +561 -0
  2. package/bin/run.js +50 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +802 -0
  6. package/dist/lib/api-client.d.ts +81 -0
  7. package/dist/lib/api-client.d.ts.map +1 -0
  8. package/dist/lib/api-client.js +237 -0
  9. package/dist/lib/auth-state.d.ts +13 -0
  10. package/dist/lib/auth-state.d.ts.map +1 -0
  11. package/dist/lib/auth-state.js +24 -0
  12. package/dist/lib/context-fetcher.d.ts +67 -0
  13. package/dist/lib/context-fetcher.d.ts.map +1 -0
  14. package/dist/lib/context-fetcher.js +190 -0
  15. package/dist/lib/credentials.d.ts +52 -0
  16. package/dist/lib/credentials.d.ts.map +1 -0
  17. package/dist/lib/credentials.js +196 -0
  18. package/dist/lib/device-flow.d.ts +14 -0
  19. package/dist/lib/device-flow.d.ts.map +1 -0
  20. package/dist/lib/device-flow.js +244 -0
  21. package/dist/tools/cms-items.d.ts +56 -0
  22. package/dist/tools/cms-items.d.ts.map +1 -0
  23. package/dist/tools/cms-items.js +376 -0
  24. package/dist/tools/create-site.d.ts +9 -0
  25. package/dist/tools/create-site.d.ts.map +1 -0
  26. package/dist/tools/create-site.js +202 -0
  27. package/dist/tools/deploy-package.d.ts +9 -0
  28. package/dist/tools/deploy-package.d.ts.map +1 -0
  29. package/dist/tools/deploy-package.js +434 -0
  30. package/dist/tools/generate-samples.d.ts +19 -0
  31. package/dist/tools/generate-samples.d.ts.map +1 -0
  32. package/dist/tools/generate-samples.js +272 -0
  33. package/dist/tools/get-conversion-guide.d.ts +7 -0
  34. package/dist/tools/get-conversion-guide.d.ts.map +1 -0
  35. package/dist/tools/get-conversion-guide.js +1323 -0
  36. package/dist/tools/get-example.d.ts +7 -0
  37. package/dist/tools/get-example.d.ts.map +1 -0
  38. package/dist/tools/get-example.js +1568 -0
  39. package/dist/tools/get-field-types.d.ts +30 -0
  40. package/dist/tools/get-field-types.d.ts.map +1 -0
  41. package/dist/tools/get-field-types.js +154 -0
  42. package/dist/tools/get-schema.d.ts +5 -0
  43. package/dist/tools/get-schema.d.ts.map +1 -0
  44. package/dist/tools/get-schema.js +320 -0
  45. package/dist/tools/get-started.d.ts +21 -0
  46. package/dist/tools/get-started.d.ts.map +1 -0
  47. package/dist/tools/get-started.js +624 -0
  48. package/dist/tools/get-tenant-schema.d.ts +18 -0
  49. package/dist/tools/get-tenant-schema.d.ts.map +1 -0
  50. package/dist/tools/get-tenant-schema.js +158 -0
  51. package/dist/tools/list-projects.d.ts +5 -0
  52. package/dist/tools/list-projects.d.ts.map +1 -0
  53. package/dist/tools/list-projects.js +101 -0
  54. package/dist/tools/sync-schema.d.ts +41 -0
  55. package/dist/tools/sync-schema.d.ts.map +1 -0
  56. package/dist/tools/sync-schema.js +483 -0
  57. package/dist/tools/validate-manifest.d.ts +5 -0
  58. package/dist/tools/validate-manifest.d.ts.map +1 -0
  59. package/dist/tools/validate-manifest.js +311 -0
  60. package/dist/tools/validate-package.d.ts +5 -0
  61. package/dist/tools/validate-package.d.ts.map +1 -0
  62. package/dist/tools/validate-package.js +337 -0
  63. package/dist/tools/validate-template.d.ts +12 -0
  64. package/dist/tools/validate-template.d.ts.map +1 -0
  65. package/dist/tools/validate-template.js +790 -0
  66. package/package.json +54 -0
  67. package/scripts/postinstall.js +129 -0
@@ -0,0 +1,1568 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExample = getExample;
4
+ const EXAMPLES = {
5
+ manifest_basic: `# Basic manifest.json
6
+
7
+ A minimal manifest with static pages only:
8
+
9
+ \`\`\`json
10
+ {
11
+ "pages": [
12
+ {
13
+ "path": "/",
14
+ "file": "pages/index.html",
15
+ "title": "Home",
16
+ "editable": true
17
+ },
18
+ {
19
+ "path": "/about",
20
+ "file": "pages/about.html",
21
+ "title": "About Us",
22
+ "editable": true
23
+ },
24
+ {
25
+ "path": "/contact",
26
+ "file": "pages/contact.html",
27
+ "title": "Contact",
28
+ "editable": true
29
+ }
30
+ ],
31
+ "defaultHeadHtml": "<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap' rel='stylesheet'>"
32
+ }
33
+ \`\`\`
34
+
35
+ **Key points:**
36
+ - Each page has a unique \`path\` (the URL)
37
+ - \`file\` points to the HTML file in pages/ folder
38
+ - \`editable: true\` allows inline editing in the CMS
39
+ - \`defaultHeadHtml\` is injected into all pages`,
40
+ manifest_custom_paths: `# manifest.json with CMS Collections
41
+
42
+ Configure templates for your CMS collections:
43
+
44
+ \`\`\`json
45
+ {
46
+ "pages": [
47
+ {
48
+ "path": "/",
49
+ "file": "pages/index.html",
50
+ "title": "Home",
51
+ "editable": true
52
+ },
53
+ {
54
+ "path": "/about",
55
+ "file": "pages/about.html",
56
+ "title": "About",
57
+ "editable": true
58
+ }
59
+ ],
60
+ "cmsTemplates": {
61
+ "postsIndex": "templates/posts_index.html",
62
+ "postsIndexPath": "/blog",
63
+ "postsDetail": "templates/posts_detail.html",
64
+ "postsDetailPath": "/blog",
65
+ "teamIndex": "templates/team.html",
66
+ "teamIndexPath": "/team",
67
+ "servicesIndex": "templates/services.html",
68
+ "servicesIndexPath": "/services",
69
+ "servicesDetail": "templates/service-detail.html",
70
+ "servicesDetailPath": "/services"
71
+ }
72
+ }
73
+ \`\`\`
74
+
75
+ **Path Configuration Pattern:**
76
+ - \`{collectionSlug}Index\` - Template file for listing page
77
+ - \`{collectionSlug}IndexPath\` - URL for listing page
78
+ - \`{collectionSlug}Detail\` - Template file for detail page
79
+ - \`{collectionSlug}DetailPath\` - URL base for detail pages`,
80
+ manifest_minimal_with_ui: `# Minimal manifest.json (Configure Templates via Settings UI)
81
+
82
+ When you want to upload pages first and configure CMS templates later via the Settings UI:
83
+
84
+ \`\`\`json
85
+ {
86
+ "pages": [
87
+ {
88
+ "path": "/",
89
+ "file": "pages/index.html",
90
+ "title": "Home",
91
+ "editable": true
92
+ },
93
+ {
94
+ "path": "/about",
95
+ "file": "pages/about.html",
96
+ "title": "About Us",
97
+ "editable": true
98
+ },
99
+ {
100
+ "path": "/blog",
101
+ "file": "pages/blog.html",
102
+ "title": "Blog",
103
+ "editable": true
104
+ },
105
+ {
106
+ "path": "/blog-post",
107
+ "file": "pages/blog-post.html",
108
+ "title": "Blog Post Template",
109
+ "editable": true
110
+ }
111
+ ]
112
+ }
113
+ \`\`\`
114
+
115
+ **After uploading this package:**
116
+ 1. Go to Dashboard → Settings → CMS Templates
117
+ 2. Configure template mappings for your collections
118
+ 3. Set URL paths
119
+
120
+ **Benefits of this approach:**
121
+ - Faster initial upload - no need to configure cmsTemplates in JSON
122
+ - Visual UI makes it easier to understand the configuration
123
+ - Can change template mappings without re-uploading the package`,
124
+ blog_index_template: `# Blog/Posts Index Template
125
+
126
+ Lists all posts with featured section (using a "posts" collection):
127
+
128
+ \`\`\`html
129
+ <main class="blog-page">
130
+ <header class="page-header">
131
+ <h1 data-edit-key="blog-page-title">Our Blog</h1>
132
+ <p data-edit-key="blog-page-intro">Latest articles and insights</p>
133
+ </header>
134
+
135
+ <!-- Featured Post (first featured article) -->
136
+ {{#each posts featured=true limit=1}}
137
+ <article class="featured-post">
138
+ {{#if image}}
139
+ <img src="{{image}}" alt="{{name}}" class="featured-image">
140
+ {{/if}}
141
+ <div class="featured-content">
142
+ <h2><a href="{{url}}">{{name}}</a></h2>
143
+ <p class="excerpt">{{summary}}</p>
144
+ <div class="meta">
145
+ {{#if author}}
146
+ <span class="author">By {{author.name}}</span>
147
+ {{/if}}
148
+ <time>{{publishedAt}}</time>
149
+ </div>
150
+ </div>
151
+ </article>
152
+ {{/each}}
153
+
154
+ <!-- All Posts Grid -->
155
+ <div class="posts-grid">
156
+ {{#each posts limit=12 sort="publishedAt" order="desc"}}
157
+ <article class="post-card">
158
+ {{#if thumbnail}}
159
+ <img src="{{thumbnail}}" alt="{{name}}">
160
+ {{else}}
161
+ {{#if image}}
162
+ <img src="{{image}}" alt="{{name}}">
163
+ {{/if}}
164
+ {{/if}}
165
+ <div class="card-content">
166
+ <h3><a href="{{url}}">{{name}}</a></h3>
167
+ <p>{{summary}}</p>
168
+ {{#if author}}
169
+ <span class="byline">{{author.name}}</span>
170
+ {{/if}}
171
+ </div>
172
+ </article>
173
+ {{/each}}
174
+ </div>
175
+
176
+ {{#unless posts}}
177
+ <p>No posts yet. Check back soon!</p>
178
+ {{/unless}}
179
+ </main>
180
+ \`\`\`
181
+
182
+ **Key patterns:**
183
+ - \`{{#each posts featured=true limit=1}}\` for featured post
184
+ - \`{{#each posts limit=12 sort="publishedAt" order="desc"}}\` for post grid
185
+ - Always wrap images in \`{{#if image}}\`
186
+ - Use \`{{url}}\` for links to post detail page`,
187
+ blog_post_template: `# Blog Post Detail Template
188
+
189
+ Single post page with author information (using a "posts" collection with author relation):
190
+
191
+ \`\`\`html
192
+ <article class="blog-post">
193
+ {{#if image}}
194
+ <img src="{{image}}" alt="{{name}}" class="hero-image">
195
+ {{/if}}
196
+
197
+ <header class="post-header">
198
+ <h1>{{name}}</h1>
199
+
200
+ <div class="post-meta">
201
+ {{#if author}}
202
+ <div class="author-info">
203
+ {{#if author.photo}}
204
+ <img src="{{author.photo}}" alt="{{author.name}}" class="author-avatar">
205
+ {{/if}}
206
+ <a href="{{author.url}}" class="author-name">{{author.name}}</a>
207
+ </div>
208
+ {{/if}}
209
+ <time datetime="{{publishedAt}}">{{publishedAt}}</time>
210
+ </div>
211
+ </header>
212
+
213
+ {{#if summary}}
214
+ <p class="lead">{{summary}}</p>
215
+ {{/if}}
216
+
217
+ <div class="post-content">
218
+ {{{body}}}
219
+ </div>
220
+
221
+ {{#if author}}
222
+ <aside class="author-box">
223
+ {{#if author.photo}}
224
+ <img src="{{author.photo}}" alt="{{author.name}}">
225
+ {{/if}}
226
+ <div class="author-details">
227
+ <h3>About <a href="{{author.url}}">{{author.name}}</a></h3>
228
+ {{{author.bio}}}
229
+ </div>
230
+ </aside>
231
+ {{/if}}
232
+ </article>
233
+ \`\`\`
234
+
235
+ **IMPORTANT:**
236
+ - \`{{{body}}}\` uses TRIPLE braces (contains HTML)
237
+ - \`{{{author.bio}}}\` uses TRIPLE braces (contains HTML)
238
+ - Everything else uses double braces`,
239
+ team_template: `# Team Members Template
240
+
241
+ Using a "team" collection with name, role, photo, bio, order fields:
242
+
243
+ \`\`\`html
244
+ <section class="team-section">
245
+ <header class="section-header">
246
+ <h1 data-edit-key="team-page-title">Our Team</h1>
247
+ <p data-edit-key="team-page-intro">Meet the people behind our success</p>
248
+ </header>
249
+
250
+ <div class="team-grid">
251
+ {{#each team sort="order" order="asc"}}
252
+ <div class="team-member">
253
+ {{#if photo}}
254
+ <img src="{{photo}}" alt="{{name}}" class="member-photo">
255
+ {{/if}}
256
+ <h3>{{name}}</h3>
257
+ {{#if role}}
258
+ <p class="role">{{role}}</p>
259
+ {{/if}}
260
+ {{#if bio}}
261
+ <div class="bio">{{{bio}}}</div>
262
+ {{/if}}
263
+ {{#if email}}
264
+ <a href="mailto:{{email}}" class="email">{{email}}</a>
265
+ {{/if}}
266
+ </div>
267
+ {{/each}}
268
+ </div>
269
+ </section>
270
+ \`\`\`
271
+
272
+ **Key points:**
273
+ - \`sort="order" order="asc"\` maintains manual ordering
274
+ - \`{{{bio}}}\` uses triple braces for HTML content`,
275
+ downloads_template: `# Downloads/Resources Template
276
+
277
+ Using a "downloads" collection with name, description, fileUrl, category fields:
278
+
279
+ \`\`\`html
280
+ <section class="downloads-section">
281
+ <header class="section-header">
282
+ <h1 data-edit-key="downloads-title">Resources</h1>
283
+ <p data-edit-key="downloads-intro">Download our guides and materials</p>
284
+ </header>
285
+
286
+ <div class="downloads-list">
287
+ {{#each downloads sort="order" order="asc"}}
288
+ <div class="download-item">
289
+ <div class="download-info">
290
+ <h3>{{name}}</h3>
291
+ {{#if description}}
292
+ <p>{{description}}</p>
293
+ {{/if}}
294
+ {{#if category}}
295
+ <span class="category">{{category}}</span>
296
+ {{/if}}
297
+ </div>
298
+ <a href="{{fileUrl}}" class="download-btn" download>
299
+ Download
300
+ </a>
301
+ </div>
302
+ {{/each}}
303
+ </div>
304
+ </section>
305
+ \`\`\``,
306
+ authors_template: `# Authors Index Template
307
+
308
+ Using an "authors" collection:
309
+
310
+ \`\`\`html
311
+ <section class="authors-section">
312
+ <header class="section-header">
313
+ <h1 data-edit-key="authors-title">Our Authors</h1>
314
+ </header>
315
+
316
+ <div class="authors-grid">
317
+ {{#each authors}}
318
+ <a href="{{url}}" class="author-card">
319
+ {{#if photo}}
320
+ <img src="{{photo}}" alt="{{name}}" class="author-photo">
321
+ {{/if}}
322
+ <h3>{{name}}</h3>
323
+ {{#if bio}}
324
+ <p class="bio-preview">{{{bio}}}</p>
325
+ {{/if}}
326
+ </a>
327
+ {{/each}}
328
+ </div>
329
+ </section>
330
+ \`\`\``,
331
+ author_detail_template: `# Author Detail Template
332
+
333
+ Using an "authors" collection with detail pages:
334
+
335
+ \`\`\`html
336
+ <article class="author-detail">
337
+ <header class="author-header">
338
+ {{#if photo}}
339
+ <img src="{{photo}}" alt="{{name}}" class="author-photo-large">
340
+ {{/if}}
341
+ <h1>{{name}}</h1>
342
+
343
+ <div class="social-links">
344
+ {{#if email}}
345
+ <a href="mailto:{{email}}">Email</a>
346
+ {{/if}}
347
+ {{#if twitter}}
348
+ <a href="{{twitter}}">Twitter</a>
349
+ {{/if}}
350
+ {{#if linkedin}}
351
+ <a href="{{linkedin}}">LinkedIn</a>
352
+ {{/if}}
353
+ </div>
354
+ </header>
355
+
356
+ {{#if bio}}
357
+ <div class="author-bio">{{{bio}}}</div>
358
+ {{/if}}
359
+
360
+ <!-- Show posts by this author (if you have a posts collection with author relation) -->
361
+ <section class="author-articles">
362
+ <h2>Articles by {{name}}</h2>
363
+ <div class="articles-grid">
364
+ {{#each posts}}
365
+ {{#if (eq author.name ../name)}}
366
+ <a href="{{url}}" class="article-card">
367
+ {{#if thumbnail}}
368
+ <img src="{{thumbnail}}" alt="{{name}}">
369
+ {{/if}}
370
+ <h3>{{name}}</h3>
371
+ <p>{{summary}}</p>
372
+ </a>
373
+ {{/if}}
374
+ {{/each}}
375
+ </div>
376
+ </section>
377
+ </article>
378
+ \`\`\`
379
+
380
+ **Pattern:** \`{{#if (eq author.name ../name)}}\` filters posts by this author`,
381
+ custom_collection_template: `# Custom Collection Template
382
+
383
+ For any user-defined collection. All collections have built-in \`name\`, \`slug\`, \`url\`, and date fields.
384
+
385
+ ## Index Template
386
+
387
+ \`\`\`html
388
+ <!-- Index Template: templates/products_index.html -->
389
+ <section class="products-section">
390
+ <h1 data-edit-key="products-title">Our Products</h1>
391
+
392
+ <div class="products-grid">
393
+ {{#each products sort="publishedAt" order="desc"}}
394
+ <article class="product-card">
395
+ {{#if image}}
396
+ <img src="{{image}}" alt="{{name}}">
397
+ {{/if}}
398
+ <h3><a href="{{url}}">{{name}}</a></h3>
399
+ {{#if price}}
400
+ <span class="price">\${{price}}</span>
401
+ {{/if}}
402
+ {{#if description}}
403
+ <p>{{description}}</p>
404
+ {{/if}}
405
+ <time class="date">{{publishedAt}}</time>
406
+ </article>
407
+ {{/each}}
408
+ </div>
409
+
410
+ {{#unless products}}
411
+ <p>No products yet.</p>
412
+ {{/unless}}
413
+ </section>
414
+ \`\`\`
415
+
416
+ ## Detail Template
417
+
418
+ \`\`\`html
419
+ <!-- Detail Template: templates/products_detail.html -->
420
+ <article class="product-detail">
421
+ {{#if image}}
422
+ <img src="{{image}}" alt="{{name}}" class="hero-image">
423
+ {{/if}}
424
+
425
+ <h1>{{name}}</h1>
426
+ <time class="date">{{publishedAt}}</time>
427
+
428
+ {{#if price}}
429
+ <span class="price">\${{price}}</span>
430
+ {{/if}}
431
+
432
+ {{#if description}}
433
+ <div class="description">{{{description}}}</div>
434
+ {{/if}}
435
+ </article>
436
+ \`\`\`
437
+
438
+ ## Built-in Tokens (Available on ALL items)
439
+
440
+ - \`{{name}}\` - Item name/title
441
+ - \`{{slug}}\` - URL slug
442
+ - \`{{url}}\` - Full URL to detail page
443
+ - \`{{publishedAt}}\` - Publish date
444
+ - \`{{createdAt}}\` - Creation date
445
+ - \`{{updatedAt}}\` - Last modified date
446
+
447
+ ## In manifest.json
448
+
449
+ \`\`\`json
450
+ {
451
+ "cmsTemplates": {
452
+ "productsIndex": "templates/products_index.html",
453
+ "productsIndexPath": "/shop",
454
+ "productsDetail": "templates/products_detail.html",
455
+ "productsDetailPath": "/products"
456
+ }
457
+ }
458
+ \`\`\``,
459
+ form_handling: `# Form Handling
460
+
461
+ Forms are automatically captured by the CMS using the \`data-form\` attribute:
462
+
463
+ \`\`\`html
464
+ <form data-form="contact" class="contact-form">
465
+ <div class="form-group">
466
+ <label for="name">Name</label>
467
+ <input type="text" name="name" id="name" required>
468
+ </div>
469
+
470
+ <div class="form-group">
471
+ <label for="email">Email</label>
472
+ <input type="email" name="email" id="email" required>
473
+ </div>
474
+
475
+ <div class="form-group">
476
+ <label for="message">Message</label>
477
+ <textarea name="message" id="message" required></textarea>
478
+ </div>
479
+
480
+ <button type="submit">Send Message</button>
481
+ </form>
482
+ \`\`\`
483
+
484
+ ## Form Handler Script
485
+
486
+ Add this script to handle form submissions:
487
+
488
+ \`\`\`javascript
489
+ // Handle all forms with data-form attribute
490
+ document.querySelectorAll('form[data-form]').forEach(form => {
491
+ form.addEventListener('submit', async (e) => {
492
+ e.preventDefault();
493
+
494
+ const submitBtn = form.querySelector('button[type="submit"]');
495
+ const originalText = submitBtn?.textContent || 'Submit';
496
+
497
+ if (submitBtn) {
498
+ submitBtn.disabled = true;
499
+ submitBtn.textContent = 'Sending...';
500
+ }
501
+
502
+ try {
503
+ const formName = form.dataset.form || 'general';
504
+ const formData = new FormData(form);
505
+ const data = Object.fromEntries(formData);
506
+
507
+ // Endpoint is /_forms/{formName}
508
+ const response = await fetch('/_forms/' + formName, {
509
+ method: 'POST',
510
+ headers: { 'Content-Type': 'application/json' },
511
+ body: JSON.stringify(data)
512
+ });
513
+
514
+ if (response.ok) {
515
+ // Option 1: Redirect to thank you page
516
+ // window.location.href = '/thank-you';
517
+
518
+ // Option 2: Show success message
519
+ form.reset();
520
+ alert(form.dataset.successMessage || 'Thank you! Your message has been sent.');
521
+ } else {
522
+ throw new Error('Form submission failed');
523
+ }
524
+ } catch (error) {
525
+ console.error('Form error:', error);
526
+ alert('There was an error. Please try again.');
527
+ } finally {
528
+ if (submitBtn) {
529
+ submitBtn.disabled = false;
530
+ submitBtn.textContent = originalText;
531
+ }
532
+ }
533
+ });
534
+ });
535
+ \`\`\`
536
+
537
+ **Key points:**
538
+ - Add \`data-form="formname"\` to identify the form (e.g., \`data-form="contact"\`)
539
+ - Endpoint is \`/_forms/{formName}\` (e.g., \`/_forms/contact\`)
540
+ - All inputs must have \`name\` attributes to be captured
541
+ - Add a submit button for the form to work
542
+
543
+ **Note:** The legacy \`data-form-name\` attribute is deprecated. Use \`data-form\` instead.`,
544
+ asset_paths: `# Asset Path Rules
545
+
546
+ **ALL asset paths must use /public/ prefix:**
547
+
548
+ \`\`\`html
549
+ <!-- CSS -->
550
+ <link rel="stylesheet" href="/public/css/style.css">
551
+ <link rel="stylesheet" href="/public/css/components/header.css">
552
+
553
+ <!-- JavaScript -->
554
+ <script src="/public/js/main.js"></script>
555
+ <script src="/public/js/vendor/swiper.min.js"></script>
556
+
557
+ <!-- Images -->
558
+ <img src="/public/images/logo.png" alt="Logo">
559
+ <img src="/public/images/team/john.jpg" alt="John">
560
+
561
+ <!-- Favicon -->
562
+ <link rel="icon" href="/public/images/favicon.ico">
563
+ \`\`\`
564
+
565
+ **In CSS files:**
566
+ \`\`\`css
567
+ /* Correct */
568
+ background-image: url('/public/images/hero-bg.jpg');
569
+
570
+ /* Wrong - will break */
571
+ background-image: url('../images/hero-bg.jpg');
572
+ background-image: url('images/hero-bg.jpg');
573
+ \`\`\`
574
+
575
+ **Common conversions:**
576
+ - \`href="css/style.css"\` → \`href="/public/css/style.css"\`
577
+ - \`src="../images/logo.png"\` → \`src="/public/images/logo.png"\`
578
+ - \`url('../fonts/custom.woff')\` → \`url('/public/fonts/custom.woff')\``,
579
+ image_handling: `# Image Handling in Templates
580
+
581
+ ## Two Types of Images
582
+
583
+ ### 1. Static/UI Images (Keep as /public/ paths)
584
+ Logos, icons, decorative backgrounds, UI elements - these are bundled with the site:
585
+ \`\`\`html
586
+ <!-- KEEP these as static paths -->
587
+ <img src="/public/images/logo.png" alt="Company Logo">
588
+ <img src="/public/images/icons/arrow.svg" alt="">
589
+ <link rel="icon" href="/public/images/favicon.ico">
590
+ <div style="background-image: url('/public/images/hero-pattern.svg')">
591
+ \`\`\`
592
+
593
+ ### 2. Content Images (Use CMS Tokens)
594
+ Post images, team photos, product images - these are managed through the CMS:
595
+ \`\`\`html
596
+ <!-- USE CMS tokens for content -->
597
+ <img src="{{image}}" alt="{{name}}">
598
+ <img src="{{thumbnail}}" alt="{{name}}">
599
+ <img src="{{photo}}" alt="{{name}}">
600
+ \`\`\`
601
+
602
+ ---
603
+
604
+ ## Always Wrap Content Images in Conditionals
605
+
606
+ \`\`\`html
607
+ {{#if image}}
608
+ <img src="{{image}}" alt="{{name}}" class="hero-image">
609
+ {{/if}}
610
+ \`\`\`
611
+
612
+ Or provide a CSS fallback:
613
+ \`\`\`html
614
+ {{#if image}}
615
+ <img src="{{image}}" alt="{{name}}">
616
+ {{else}}
617
+ <div class="placeholder-image"></div>
618
+ {{/if}}
619
+ \`\`\`
620
+
621
+ ---
622
+
623
+ ## Common Mistakes
624
+
625
+ 1. **Replacing logos with CMS tokens** - Keep static UI images as \`/public/\` paths
626
+ 2. **Hardcoding example content images** - Use \`{{#each}}\` loops with CMS tokens
627
+ 3. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\``,
628
+ relation_fields: `# Relation Fields - Linking Collections
629
+
630
+ Relation fields let you link items from one collection to another.
631
+
632
+ ---
633
+
634
+ ## Using Relation Fields in Templates
635
+
636
+ When a relation field is set, access the related item's data using dot notation:
637
+
638
+ \`\`\`html
639
+ {{#each projects}}
640
+ <article class="project-card">
641
+ <h2><a href="{{url}}">{{name}}</a></h2>
642
+
643
+ {{#if category}}
644
+ <!-- Access any field from the related category -->
645
+ <span class="category-badge">{{category.name}}</span>
646
+ <a href="{{category.url}}" class="category-link">
647
+ View all {{category.name}} projects
648
+ </a>
649
+ {{/if}}
650
+ </article>
651
+ {{/each}}
652
+ \`\`\`
653
+
654
+ ---
655
+
656
+ ## Available Relation Tokens
657
+
658
+ | Token | Description |
659
+ |-------|-------------|
660
+ | \`{{relationField.name}}\` | Related item's name |
661
+ | \`{{relationField.slug}}\` | Related item's URL slug |
662
+ | \`{{relationField.url}}\` | Full URL to related item's detail page |
663
+ | \`{{relationField.anyField}}\` | Any field from the related collection |
664
+
665
+ ---
666
+
667
+ ## Filter by Relation on Detail Pages
668
+
669
+ On a category detail page, show only items with that category:
670
+
671
+ \`\`\`html
672
+ <h1>{{name}}</h1>
673
+ <p>Projects in this category:</p>
674
+
675
+ <div class="projects-grid">
676
+ {{#each projects}}
677
+ {{#if (eq category.slug ../slug)}}
678
+ <article class="project-card">
679
+ <h3>{{name}}</h3>
680
+ <a href="{{url}}">View Project</a>
681
+ </article>
682
+ {{/if}}
683
+ {{/each}}
684
+ </div>
685
+ \`\`\`
686
+
687
+ ---
688
+
689
+ ## Best Practices
690
+
691
+ 1. **Always wrap in {{#if}}** - The relation may not be set for all items
692
+ 2. **Use for categorization** - Tags, categories, content types
693
+ 3. **Link related content** - Featured author, related service, parent category`,
694
+ data_edit_keys: `# Inline Editing with data-edit-key
695
+
696
+ Make text editable in the CMS visual editor:
697
+
698
+ \`\`\`html
699
+ <!-- Unique, descriptive keys -->
700
+ <h1 data-edit-key="home-hero-title">Welcome to Our Company</h1>
701
+ <p data-edit-key="home-hero-subtitle">We provide excellent services</p>
702
+
703
+ <!-- Hierarchical naming for sections -->
704
+ <section class="about">
705
+ <h2 data-edit-key="about-section-title">About Us</h2>
706
+ <p data-edit-key="about-section-paragraph-1">First paragraph...</p>
707
+ <p data-edit-key="about-section-paragraph-2">Second paragraph...</p>
708
+ </section>
709
+
710
+ <!-- For different pages, prefix with page name -->
711
+ <h1 data-edit-key="contact-page-title">Contact Us</h1>
712
+ <p data-edit-key="contact-intro">Get in touch with our team</p>
713
+ \`\`\`
714
+
715
+ **Naming conventions:**
716
+ - \`{page}-{section}-{element}\`
717
+ - Examples: \`home-hero-title\`, \`about-team-heading\`, \`contact-form-intro\`
718
+ - Must be unique across the entire site
719
+ - Use lowercase with hyphens`,
720
+ each_loop: `# {{#each}} Loop Syntax
721
+
722
+ **Basic loop:**
723
+ \`\`\`html
724
+ {{#each posts}}
725
+ <article>
726
+ <h2>{{name}}</h2>
727
+ <p>{{summary}}</p>
728
+ </article>
729
+ {{/each}}
730
+ \`\`\`
731
+
732
+ **With limit:**
733
+ \`\`\`html
734
+ {{#each posts limit=6}}
735
+ ...
736
+ {{/each}}
737
+ \`\`\`
738
+
739
+ **Featured only (if collection has boolean "featured" field):**
740
+ \`\`\`html
741
+ {{#each posts featured=true}}
742
+ ...
743
+ {{/each}}
744
+ \`\`\`
745
+
746
+ **Combined:**
747
+ \`\`\`html
748
+ {{#each posts featured=true limit=3}}
749
+ ...
750
+ {{/each}}
751
+ \`\`\`
752
+
753
+ **With sorting:**
754
+ \`\`\`html
755
+ {{#each team sort="order" order="asc"}}
756
+ ...
757
+ {{/each}}
758
+ \`\`\`
759
+
760
+ **Loop variables:**
761
+ \`\`\`html
762
+ {{#each posts limit=5}}
763
+ <article class="{{#if @first}}featured{{/if}} {{#if @last}}last{{/if}}">
764
+ <span class="position">Item {{@index}}</span>
765
+ <h2>{{name}}</h2>
766
+ </article>
767
+ {{/each}}
768
+ \`\`\`
769
+
770
+ - \`{{@first}}\` - true for first item
771
+ - \`{{@last}}\` - true for last item
772
+ - \`{{@index}}\` - zero-based index (0, 1, 2...)`,
773
+ conditional_if: `# Conditional Rendering
774
+
775
+ **{{#if}} - Render if truthy:**
776
+ \`\`\`html
777
+ {{#if image}}
778
+ <img src="{{image}}" alt="{{name}}">
779
+ {{/if}}
780
+ \`\`\`
781
+
782
+ **{{#if}} with {{else}}:**
783
+ \`\`\`html
784
+ {{#if thumbnail}}
785
+ <img src="{{thumbnail}}" alt="{{name}}">
786
+ {{else}}
787
+ <div class="placeholder">No image</div>
788
+ {{/if}}
789
+ \`\`\`
790
+
791
+ **{{#unless}} - Render if falsy:**
792
+ \`\`\`html
793
+ {{#unless featured}}
794
+ <span class="regular-post">Regular</span>
795
+ {{/unless}}
796
+ \`\`\`
797
+
798
+ **Nested conditionals:**
799
+ \`\`\`html
800
+ {{#if author}}
801
+ <div class="author-info">
802
+ {{#if author.photo}}
803
+ <img src="{{author.photo}}" alt="{{author.name}}">
804
+ {{/if}}
805
+ <span>{{author.name}}</span>
806
+ </div>
807
+ {{/if}}
808
+ \`\`\`
809
+
810
+ **Multiple conditions (check both):**
811
+ \`\`\`html
812
+ {{#if image}}
813
+ {{#if featured}}
814
+ <img src="{{image}}" class="featured-image">
815
+ {{/if}}
816
+ {{/if}}
817
+ \`\`\``,
818
+ nested_fields: `# Nested Field Access (Relation Fields)
819
+
820
+ Access related item fields using dot notation:
821
+
822
+ \`\`\`html
823
+ {{#each posts}}
824
+ <article>
825
+ <h2>{{name}}</h2>
826
+
827
+ {{#if author}}
828
+ <div class="author">
829
+ {{#if author.photo}}
830
+ <img src="{{author.photo}}" alt="{{author.name}}">
831
+ {{/if}}
832
+ <span>By {{author.name}}</span>
833
+
834
+ {{#if author.bio}}
835
+ <p class="bio">{{{author.bio}}}</p>
836
+ {{/if}}
837
+ </div>
838
+ {{/if}}
839
+ </article>
840
+ {{/each}}
841
+ \`\`\`
842
+
843
+ **Available nested tokens (for a relation field named "author"):**
844
+ - \`{{author.name}}\`
845
+ - \`{{author.slug}}\`
846
+ - \`{{author.url}}\`
847
+ - \`{{{author.bio}}}\` (triple braces for richText!)
848
+ - Any other field from the related collection
849
+
850
+ **Always wrap in {{#if author}}** to handle items without the relation set`,
851
+ featured_posts: `# Featured Posts Section
852
+
853
+ **Homepage featured posts (3 most recent):**
854
+ \`\`\`html
855
+ <section class="featured-posts">
856
+ <h2 data-edit-key="home-blog-title">Latest News</h2>
857
+
858
+ {{#each posts featured=true limit=3}}
859
+ <article class="featured-card {{#if @first}}large{{/if}}">
860
+ {{#if image}}
861
+ <img src="{{image}}" alt="{{name}}">
862
+ {{/if}}
863
+ <div class="card-content">
864
+ <h3><a href="{{url}}">{{name}}</a></h3>
865
+ <p>{{summary}}</p>
866
+ {{#if author}}
867
+ <span class="byline">By {{author.name}}</span>
868
+ {{/if}}
869
+ <time>{{publishedAt}}</time>
870
+ </div>
871
+ </article>
872
+ {{/each}}
873
+
874
+ <a href="/blog" class="view-all">View All Posts</a>
875
+ </section>
876
+ \`\`\`
877
+
878
+ **Note:** Requires a boolean "featured" field on your collection`,
879
+ parent_context: `# Parent Context References (\`../\`)
880
+
881
+ Inside loops, access the **parent scope** (the page's current item) using \`../\`:
882
+
883
+ **Use Case: Author Detail Page - Show Only This Author's Posts**
884
+ \`\`\`html
885
+ <article class="author-detail">
886
+ <h1>{{name}}</h1>
887
+ <p class="bio">{{{bio}}}</p>
888
+
889
+ <section class="author-articles">
890
+ <h2>Posts by {{name}}</h2>
891
+
892
+ {{#each posts}}
893
+ {{#if (eq author.name ../name)}}
894
+ <article class="post-card">
895
+ <h3><a href="{{url}}">{{name}}</a></h3>
896
+ <p>{{summary}}</p>
897
+ <time>{{publishedAt}}</time>
898
+ </article>
899
+ {{/if}}
900
+ {{/each}}
901
+ </section>
902
+ </article>
903
+ \`\`\`
904
+
905
+ **How It Works:**
906
+ - Inside \`{{#each posts}}\`, the context is each post
907
+ - \`author.name\` = the post's author
908
+ - \`../name\` = the parent context (the author being displayed on the page)
909
+ - Only posts where author.name matches ../name are shown
910
+
911
+ **Use Case: Category Page - Highlight Current Category**
912
+ \`\`\`html
913
+ <nav class="category-nav">
914
+ {{#each categories}}
915
+ <a href="{{url}}" class="{{#if (eq slug ../slug)}}active{{/if}}">
916
+ {{name}}
917
+ </a>
918
+ {{/each}}
919
+ </nav>
920
+ \`\`\`
921
+
922
+ **Available Parent Fields:**
923
+ - \`../name\` - Parent item's name
924
+ - \`../slug\` - Parent item's slug
925
+ - \`../fieldName\` - Any field from the parent item`,
926
+ equality_comparison: `# Equality Comparisons
927
+
928
+ Compare two values using \`(eq field1 field2)\` helper:
929
+
930
+ ## Show When Equal ({{#if (eq ...)}})
931
+ \`\`\`html
932
+ {{#if (eq author.slug ../slug)}}
933
+ <span class="current-author-badge">Your Post</span>
934
+ {{/if}}
935
+ \`\`\`
936
+
937
+ ## Show When NOT Equal ({{#unless (eq ...)}}) - IMPORTANT!
938
+ The most common pattern for "Related Posts" or "Other Items" sections:
939
+
940
+ \`\`\`html
941
+ <!-- On a post page, show other posts EXCEPT the current one -->
942
+ <h3>Related Posts</h3>
943
+ {{#each posts limit=3}}
944
+ {{#unless (eq slug ../slug)}}
945
+ <article>
946
+ <a href="{{url}}">{{name}}</a>
947
+ <p>{{summary}}</p>
948
+ </article>
949
+ {{/unless}}
950
+ {{/each}}
951
+ \`\`\`
952
+
953
+ **How it works:**
954
+ - \`../slug\` accesses the current page's slug (parent context)
955
+ - \`slug\` is the loop item's slug
956
+ - \`{{#unless (eq slug ../slug)}}\` shows content when they're NOT equal
957
+ - This excludes the current post from the "related" list
958
+
959
+ ## Compare Field to Literal String ({{#eq}})
960
+ \`\`\`html
961
+ {{#eq status "published"}}
962
+ <span class="badge badge-success">Published</span>
963
+ {{/eq}}
964
+
965
+ {{#eq category "news"}}
966
+ <span class="news-icon">News</span>
967
+ {{/eq}}
968
+ \`\`\`
969
+
970
+ ## Summary Table
971
+
972
+ | Syntax | Shows content when... |
973
+ |--------|----------------------|
974
+ | \`{{#if (eq a b)}}\` | a equals b |
975
+ | \`{{#unless (eq a b)}}\` | a does NOT equal b |
976
+ | \`{{#eq field "value"}}\` | field equals "value" |`,
977
+ comparison_helpers: `# Comparison Helpers
978
+
979
+ Use comparison helpers for numeric and string comparisons in conditionals.
980
+
981
+ ## Available Helpers
982
+
983
+ | Helper | Meaning | Example |
984
+ |--------|---------|---------|
985
+ | \`lt\` | Less than | \`{{#if (lt @index 4)}}\` |
986
+ | \`gt\` | Greater than | \`{{#if (gt @index 0)}}\` |
987
+ | \`lte\` | Less than or equal | \`{{#if (lte @index 4)}}\` |
988
+ | \`gte\` | Greater than or equal | \`{{#if (gte @index 2)}}\` |
989
+ | \`ne\` | Not equal | \`{{#if (ne status "draft")}}\` |
990
+ | \`eq\` | Equal | \`{{#if (eq category.slug ../slug)}}\` |
991
+
992
+ ---
993
+
994
+ ## Loop Index Comparisons
995
+
996
+ ### Show First N Items Differently
997
+ \`\`\`html
998
+ {{#each posts}}
999
+ {{#if (lt @index 3)}}
1000
+ <!-- First 3 items get special styling -->
1001
+ <article class="featured-card">{{name}}</article>
1002
+ {{else}}
1003
+ <!-- Remaining items -->
1004
+ <article class="standard-card">{{name}}</article>
1005
+ {{/if}}
1006
+ {{/each}}
1007
+ \`\`\`
1008
+
1009
+ ### Skip First Item
1010
+ \`\`\`html
1011
+ {{#each posts}}
1012
+ {{#if (gt @index 0)}}
1013
+ <article>{{name}}</article>
1014
+ {{/if}}
1015
+ {{/each}}
1016
+ \`\`\`
1017
+
1018
+ ### Show Items 2-5 Only
1019
+ \`\`\`html
1020
+ {{#each posts}}
1021
+ {{#if (gte @index 1)}}
1022
+ {{#if (lte @index 4)}}
1023
+ <article>Item {{@index}}: {{name}}</article>
1024
+ {{/if}}
1025
+ {{/if}}
1026
+ {{/each}}
1027
+ \`\`\`
1028
+
1029
+ ---
1030
+
1031
+ ## Field Value Comparisons
1032
+
1033
+ ### Filter by Status
1034
+ \`\`\`html
1035
+ {{#each posts}}
1036
+ {{#if (ne status "draft")}}
1037
+ <article>{{name}}</article>
1038
+ {{/if}}
1039
+ {{/each}}
1040
+ \`\`\`
1041
+
1042
+ ### Numeric Field Comparison
1043
+ \`\`\`html
1044
+ {{#each products}}
1045
+ {{#if (gte price 100)}}
1046
+ <span class="premium-badge">Premium</span>
1047
+ {{/if}}
1048
+ {{#if (lt stock 5)}}
1049
+ <span class="low-stock">Low Stock!</span>
1050
+ {{/if}}
1051
+ {{/each}}
1052
+ \`\`\`
1053
+
1054
+ ---
1055
+
1056
+ ## With {{#unless}}
1057
+
1058
+ The opposite of \`{{#if}}\`:
1059
+
1060
+ \`\`\`html
1061
+ {{#each posts}}
1062
+ {{#unless (lt @index 3)}}
1063
+ <!-- Show for items 4 and beyond -->
1064
+ <article class="archive-card">{{name}}</article>
1065
+ {{/unless}}
1066
+ {{/each}}
1067
+ \`\`\`
1068
+
1069
+ ---
1070
+
1071
+ ## Common Patterns
1072
+
1073
+ ### Hero + Grid Layout
1074
+ \`\`\`html
1075
+ {{#each posts}}
1076
+ {{#if (lt @index 1)}}
1077
+ <!-- First item is hero -->
1078
+ <div class="hero-post">
1079
+ <h1>{{name}}</h1>
1080
+ {{{body}}}
1081
+ </div>
1082
+ {{else}}
1083
+ {{#if (lt @index 4)}}
1084
+ <!-- Items 2-4 in featured grid -->
1085
+ <div class="featured-grid-item">{{name}}</div>
1086
+ {{else}}
1087
+ <!-- Rest in list -->
1088
+ <div class="list-item">{{name}}</div>
1089
+ {{/if}}
1090
+ {{/if}}
1091
+ {{/each}}
1092
+ \`\`\`
1093
+
1094
+ ### Pagination Preview
1095
+ \`\`\`html
1096
+ <!-- Show first 6 items, with "view more" after -->
1097
+ {{#each posts}}
1098
+ {{#if (lt @index 6)}}
1099
+ <article>{{name}}</article>
1100
+ {{/if}}
1101
+ {{/each}}
1102
+ {{#if (gt @length 6)}}
1103
+ <a href="/blog">View all posts</a>
1104
+ {{/if}}
1105
+ \`\`\`
1106
+
1107
+ ---
1108
+
1109
+ ## Summary
1110
+
1111
+ | Syntax | Shows content when... |
1112
+ |--------|----------------------|
1113
+ | \`{{#if (lt @index 4)}}\` | index < 4 (first 4 items) |
1114
+ | \`{{#if (gt @index 0)}}\` | index > 0 (skip first) |
1115
+ | \`{{#if (lte @index 4)}}\` | index <= 4 (first 5 items) |
1116
+ | \`{{#if (gte @index 2)}}\` | index >= 2 (skip first 2) |
1117
+ | \`{{#if (ne field "value")}}\` | field != "value" |
1118
+ | \`{{#unless (lt @index 3)}}\` | index >= 3 (opposite) |`,
1119
+ youtube_embed: `# YouTube Video Embeds
1120
+
1121
+ **IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153.
1122
+
1123
+ ## Correct YouTube Embed Format
1124
+
1125
+ \`\`\`html
1126
+ <iframe
1127
+ src="{{videoUrl}}"
1128
+ title="{{name}}"
1129
+ frameborder="0"
1130
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
1131
+ referrerpolicy="strict-origin-when-cross-origin"
1132
+ allowfullscreen
1133
+ ></iframe>
1134
+ \`\`\`
1135
+
1136
+ ## Required Attributes
1137
+
1138
+ | Attribute | Required | Why |
1139
+ |-----------|----------|-----|
1140
+ | \`referrerpolicy="strict-origin-when-cross-origin"\` | **YES** | YouTube blocks embeds without this |
1141
+ | \`allowfullscreen\` | Recommended | Enables fullscreen button |
1142
+ | \`allow="..."\` | Recommended | Enables player features |
1143
+ | \`frameborder="0"\` | Recommended | Removes border |
1144
+ | \`title="..."\` | Recommended | Accessibility for screen readers |
1145
+
1146
+ ## Video URL Format
1147
+
1148
+ Store YouTube URLs in embed format:
1149
+ \`\`\`
1150
+ https://www.youtube.com/embed/VIDEO_ID
1151
+ \`\`\`
1152
+
1153
+ **NOT** watch format:
1154
+ \`\`\`
1155
+ https://www.youtube.com/watch?v=VIDEO_ID
1156
+ \`\`\`
1157
+
1158
+ ## Full Example with Conditional
1159
+
1160
+ \`\`\`html
1161
+ <div class="video-container">
1162
+ {{#if videoUrl}}
1163
+ <iframe
1164
+ src="{{videoUrl}}"
1165
+ title="{{name}}"
1166
+ frameborder="0"
1167
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
1168
+ referrerpolicy="strict-origin-when-cross-origin"
1169
+ allowfullscreen
1170
+ ></iframe>
1171
+ {{else}}
1172
+ <div class="video-placeholder">
1173
+ <p>Video coming soon</p>
1174
+ </div>
1175
+ {{/if}}
1176
+ </div>
1177
+ \`\`\``,
1178
+ nested_collection_loop: `# Nested Collection Loops
1179
+
1180
+ For hierarchical content like documentation categories with pages, product categories with products, or authors with their posts.
1181
+
1182
+ **Works on ALL page types:** static pages, index pages, AND detail pages.
1183
+
1184
+ ## The Pattern
1185
+
1186
+ \`\`\`html
1187
+ {{#each outer_collection}}
1188
+ <div class="category-section">
1189
+ <h3>{{name}}</h3>
1190
+ <ul>
1191
+ {{#each inner_collection where="relation_field.slug:{{slug}}"}}
1192
+ <li><a href="{{url}}">{{name}}</a></li>
1193
+ {{/each}}
1194
+ </ul>
1195
+ </div>
1196
+ {{/each}}
1197
+ \`\`\`
1198
+
1199
+ **How it works:**
1200
+ - The outer loop iterates through parent items (e.g., categories)
1201
+ - \`{{slug}}\` in the \`where\` clause references the current outer item's slug
1202
+ - The inner loop filters to only items matching that parent
1203
+
1204
+ ---
1205
+
1206
+ ## Using @root. Prefix
1207
+
1208
+ Use \`@root.\` to explicitly access collections from the root context:
1209
+
1210
+ \`\`\`html
1211
+ {{#each doc_categories}}
1212
+ <h3>{{name}}</h3>
1213
+ {{#each @root.doc_pages where="category.slug:{{slug}}"}}
1214
+ <a href="{{url}}">{{name}}</a>
1215
+ {{/each}}
1216
+ {{/each}}
1217
+ \`\`\`
1218
+
1219
+ Both syntaxes work identically:
1220
+ - \`{{#each doc_pages}}\` - Standard syntax
1221
+ - \`{{#each @root.doc_pages}}\` - Explicit root reference
1222
+
1223
+ ---
1224
+
1225
+ ## Documentation Sidebar Example
1226
+
1227
+ **Collections needed:**
1228
+ - \`doc_categories\` (name, slug, order)
1229
+ - \`doc_pages\` (name, slug, content, category → relation to doc_categories)
1230
+
1231
+ \`\`\`html
1232
+ <nav class="docs-sidebar">
1233
+ {{#each doc_categories sort="order" order="asc"}}
1234
+ <div class="sidebar-section">
1235
+ <h4 class="section-title">{{name}}</h4>
1236
+ <ul class="section-links">
1237
+ {{#each @root.doc_pages where="category.slug:{{slug}}" sort="order" order="asc"}}
1238
+ <li>
1239
+ <a href="{{url}}" class="doc-link">{{name}}</a>
1240
+ </li>
1241
+ {{/each}}
1242
+ </ul>
1243
+ </div>
1244
+ {{/each}}
1245
+ </nav>
1246
+ \`\`\`
1247
+
1248
+ This sidebar pattern can be included on:
1249
+ - **Static pages** (about, contact, etc.)
1250
+ - **Index pages** (docs listing page)
1251
+ - **Detail pages** (individual doc pages)
1252
+
1253
+ ---
1254
+
1255
+ ## Blog Categories with Posts Example
1256
+
1257
+ \`\`\`html
1258
+ <section class="categorized-posts">
1259
+ {{#each categories}}
1260
+ <div class="category-group">
1261
+ <h2><a href="{{url}}">{{name}}</a></h2>
1262
+ <div class="posts-grid">
1263
+ {{#each posts where="category.id:{{id}}" limit=3 sort="publishedAt" order="desc"}}
1264
+ <article class="post-card">
1265
+ {{#if image}}
1266
+ <img src="{{image}}" alt="{{name}}">
1267
+ {{/if}}
1268
+ <h3><a href="{{url}}">{{name}}</a></h3>
1269
+ <p>{{summary}}</p>
1270
+ </article>
1271
+ {{/each}}
1272
+ </div>
1273
+ <a href="{{url}}" class="view-all">View all {{name}}</a>
1274
+ </div>
1275
+ {{/each}}
1276
+ </section>
1277
+ \`\`\`
1278
+
1279
+ ---
1280
+
1281
+ ## Important Notes
1282
+
1283
+ 1. **Works on ALL page types** - Static pages, index pages, AND detail pages
1284
+ 2. **The \`where\` clause uses parent context** - \`{{slug}}\` or \`{{id}}\` comes from the outer loop's current item
1285
+ 3. **@root. is optional** - Use it when you want to be explicit about root context
1286
+ 4. **Supports all modifiers** - \`limit\`, \`sort\`, \`order\` work on inner loops
1287
+ 5. **Relation field format** - Use \`relation_field.slug:{{slug}}\` or \`relation_field.id:{{id}}\`
1288
+
1289
+ ---
1290
+
1291
+ ## Manifest Configuration
1292
+
1293
+ \`\`\`json
1294
+ {
1295
+ "cmsTemplates": {
1296
+ "doc_categoriesIndex": "templates/docs_index.html",
1297
+ "doc_categoriesIndexPath": "/docs",
1298
+ "doc_pagesDetail": "templates/doc_page.html",
1299
+ "doc_pagesDetailPath": "/docs"
1300
+ }
1301
+ }
1302
+ \`\`\`
1303
+
1304
+ This gives you URLs like:
1305
+ - \`/docs\` - Documentation index showing all categories with their pages
1306
+ - \`/docs/quick-start-guide\` - Individual doc page`,
1307
+ loop_variables: `# Loop Variables
1308
+
1309
+ Special variables available inside \`{{#each}}\` loops.
1310
+
1311
+ ## Available Variables
1312
+
1313
+ | Variable | Description |
1314
+ |----------|-------------|
1315
+ | \`{{@index}}\` | Zero-based index (0, 1, 2...) |
1316
+ | \`{{@first}}\` | True for first item only |
1317
+ | \`{{@last}}\` | True for last item only |
1318
+ | \`{{@length}}\` | Total number of items |
1319
+
1320
+ ---
1321
+
1322
+ ## Conditional Usage
1323
+
1324
+ Use loop variables in conditionals for styling:
1325
+
1326
+ \`\`\`html
1327
+ {{#each team_members}}
1328
+ {{#if @first}}
1329
+ <div class="featured-member">{{name}} - Team Lead</div>
1330
+ {{else}}
1331
+ <div class="member">{{name}}</div>
1332
+ {{/if}}
1333
+ {{/each}}
1334
+ \`\`\`
1335
+
1336
+ ---
1337
+
1338
+ ## Common Patterns
1339
+
1340
+ ### Add separator between items (not before first):
1341
+ \`\`\`html
1342
+ {{#each tags}}
1343
+ {{#unless @first}} | {{/unless}}
1344
+ <span>{{name}}</span>
1345
+ {{/each}}
1346
+ \`\`\`
1347
+ Output: \`Tag1 | Tag2 | Tag3\`
1348
+
1349
+ ### Comma-separated list (no comma after last):
1350
+ \`\`\`html
1351
+ {{#each authors}}
1352
+ <span>{{name}}</span>{{#unless @last}}, {{/unless}}
1353
+ {{/each}}
1354
+ \`\`\`
1355
+ Output: \`Alice, Bob, Charlie\`
1356
+
1357
+ ### Style first and last items:
1358
+ \`\`\`html
1359
+ {{#each posts}}
1360
+ <article class="{{#if @first}}first{{/if}} {{#if @last}}last{{/if}}">
1361
+ {{name}}
1362
+ </article>
1363
+ {{/each}}
1364
+ \`\`\`
1365
+
1366
+ ### Show position number:
1367
+ \`\`\`html
1368
+ {{#each leaderboard}}
1369
+ <div class="rank-{{@index}}">
1370
+ #{{@index}}: {{name}} - {{score}} points
1371
+ </div>
1372
+ {{/each}}
1373
+ \`\`\`
1374
+
1375
+ ---
1376
+
1377
+ ## Important Notes
1378
+
1379
+ - Loop variables **only work inside \`{{#each}}\`** blocks
1380
+ - Using them outside a loop will not work and will log a warning
1381
+ - \`{{#unless @first}}\` is the opposite of \`{{#if @first}}\`
1382
+ - \`{{#unless @last}}\` is the opposite of \`{{#if @last}}\``,
1383
+ common_mistakes: `# Common AI Mistakes - Quick Reference
1384
+
1385
+ This example shows the WRONG and CORRECT patterns side-by-side for quick reference.
1386
+
1387
+ ---
1388
+
1389
+ ## Manifest Format
1390
+
1391
+ **WRONG:**
1392
+ \`\`\`json
1393
+ {
1394
+ "collections": {
1395
+ "posts": {
1396
+ "indexPath": "/blog",
1397
+ "indexFile": "collections/posts/index.html"
1398
+ }
1399
+ }
1400
+ }
1401
+ \`\`\`
1402
+
1403
+ **CORRECT:**
1404
+ \`\`\`json
1405
+ {
1406
+ "cmsTemplates": {
1407
+ "postsIndex": "templates/posts_index.html",
1408
+ "postsDetail": "templates/posts_detail.html",
1409
+ "postsIndexPath": "/blog",
1410
+ "postsDetailPath": "/blog"
1411
+ }
1412
+ }
1413
+ \`\`\`
1414
+
1415
+ ---
1416
+
1417
+ ## Folder Structure
1418
+
1419
+ **WRONG:**
1420
+ \`\`\`
1421
+ assets/css/style.css ← Won't load
1422
+ collections/posts/ ← Wrong folder
1423
+ \`\`\`
1424
+
1425
+ **CORRECT:**
1426
+ \`\`\`
1427
+ public/css/style.css ← Loads at /css/style.css
1428
+ templates/posts_index.html
1429
+ templates/posts_detail.html
1430
+ \`\`\`
1431
+
1432
+ ---
1433
+
1434
+ ## Template Naming
1435
+
1436
+ **WRONG:**
1437
+ \`\`\`
1438
+ post-detail.html
1439
+ postDetail.html
1440
+ collections/posts/detail.html
1441
+ \`\`\`
1442
+
1443
+ **CORRECT:**
1444
+ \`\`\`
1445
+ posts_detail.html
1446
+ posts_index.html
1447
+ authors_detail.html
1448
+ \`\`\`
1449
+
1450
+ Pattern: \`{collection_slug}_{type}.html\`
1451
+
1452
+ ---
1453
+
1454
+ ## Inline Editing
1455
+
1456
+ **WRONG (no editing possible):**
1457
+ \`\`\`html
1458
+ <h1>{{headline}}</h1>
1459
+ <p>{{description}}</p>
1460
+ \`\`\`
1461
+
1462
+ **CORRECT (enables visual editing):**
1463
+ \`\`\`html
1464
+ <h1 data-edit-key="headline">{{headline}}</h1>
1465
+ <p data-edit-key="description">{{description}}</p>
1466
+ \`\`\`
1467
+
1468
+ ---
1469
+
1470
+ ## Links in Templates
1471
+
1472
+ **WRONG (hardcoded path):**
1473
+ \`\`\`html
1474
+ <a href="/posts/{{slug}}">Read more</a>
1475
+ \`\`\`
1476
+
1477
+ **CORRECT (uses manifest path):**
1478
+ \`\`\`html
1479
+ <a href="{{url}}">Read more</a>
1480
+ \`\`\`
1481
+
1482
+ The \`{{url}}\` token automatically uses the path from manifest.json.
1483
+
1484
+ ---
1485
+
1486
+ ## Asset References in HTML
1487
+
1488
+ **WRONG:**
1489
+ \`\`\`html
1490
+ <link href="/assets/css/style.css">
1491
+ <script src="assets/js/main.js">
1492
+ \`\`\`
1493
+
1494
+ **CORRECT:**
1495
+ \`\`\`html
1496
+ <link href="/css/style.css">
1497
+ <script src="/js/main.js">
1498
+ \`\`\`
1499
+
1500
+ Assets in \`public/\` are served without the \`public/\` prefix in the URL.
1501
+
1502
+ ---
1503
+
1504
+ ## Field Slugs
1505
+
1506
+ **WRONG (camelCase):**
1507
+ \`\`\`
1508
+ heroImage
1509
+ authorBio
1510
+ publishedDate
1511
+ \`\`\`
1512
+
1513
+ **CORRECT (snake_case):**
1514
+ \`\`\`
1515
+ hero_image
1516
+ author_bio
1517
+ published_date
1518
+ \`\`\`
1519
+
1520
+ ---
1521
+
1522
+ ## Rich Text Fields
1523
+
1524
+ **WRONG (double braces):**
1525
+ \`\`\`html
1526
+ <div>{{body}}</div> ← HTML will be escaped!
1527
+ \`\`\`
1528
+
1529
+ **CORRECT (triple braces):**
1530
+ \`\`\`html
1531
+ <div>{{{body}}}</div> ← HTML renders correctly
1532
+ \`\`\`
1533
+
1534
+ Use triple braces for rich text fields to render HTML formatting.
1535
+
1536
+ ---
1537
+
1538
+ ## Complete Example Package Structure
1539
+
1540
+ \`\`\`
1541
+ my-site/
1542
+ ├── manifest.json
1543
+ ├── pages/
1544
+ │ ├── index.html
1545
+ │ ├── about.html
1546
+ │ └── contact.html
1547
+ ├── templates/
1548
+ │ ├── posts_index.html
1549
+ │ ├── posts_detail.html
1550
+ │ ├── authors_index.html
1551
+ │ └── authors_detail.html
1552
+ └── public/
1553
+ ├── css/
1554
+ │ └── style.css
1555
+ ├── js/
1556
+ │ └── main.js
1557
+ └── images/
1558
+ └── logo.svg
1559
+ \`\`\`
1560
+
1561
+ This structure will work every time!`,
1562
+ };
1563
+ /**
1564
+ * Returns example code for a specific pattern
1565
+ */
1566
+ async function getExample(exampleType) {
1567
+ return EXAMPLES[exampleType] || `Example not found: ${exampleType}`;
1568
+ }