create-cogenta 0.2.0 → 0.4.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 (179) hide show
  1. package/dist/blueprint-defaults.d.ts.map +1 -1
  2. package/dist/blueprint-defaults.js +6 -0
  3. package/dist/blueprint-defaults.js.map +1 -1
  4. package/dist/blueprints/assets/photos/association/community-cleanup.jpg +0 -0
  5. package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
  6. package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
  7. package/dist/blueprints/assets/photos/association/harvest-food-drive.jpg +0 -0
  8. package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
  9. package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
  10. package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
  11. package/dist/blueprints/assets/photos/blog/blog-runs-itself.jpg +0 -0
  12. package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
  13. package/dist/blueprints/assets/photos/blog/editing.jpg +0 -0
  14. package/dist/blueprints/assets/photos/blog/hero.jpg +0 -0
  15. package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
  16. package/dist/blueprints/assets/photos/blog/plain-text-editor.jpg +0 -0
  17. package/dist/blueprints/assets/photos/blog/reading-nonfiction.jpg +0 -0
  18. package/dist/blueprints/assets/photos/blog/small-tool.jpg +0 -0
  19. package/dist/blueprints/assets/photos/blog/writing-schedule.jpg +0 -0
  20. package/dist/blueprints/assets/photos/magazine/business-2.jpg +0 -0
  21. package/dist/blueprints/assets/photos/magazine/business-3.jpg +0 -0
  22. package/dist/blueprints/assets/photos/magazine/business.jpg +0 -0
  23. package/dist/blueprints/assets/photos/magazine/culture-2.jpg +0 -0
  24. package/dist/blueprints/assets/photos/magazine/culture-3.jpg +0 -0
  25. package/dist/blueprints/assets/photos/magazine/culture.jpg +0 -0
  26. package/dist/blueprints/assets/photos/magazine/news-2.jpg +0 -0
  27. package/dist/blueprints/assets/photos/magazine/news-3.jpg +0 -0
  28. package/dist/blueprints/assets/photos/magazine/news.jpg +0 -0
  29. package/dist/blueprints/assets/photos/magazine/opinion-2.jpg +0 -0
  30. package/dist/blueprints/assets/photos/magazine/opinion-3.jpg +0 -0
  31. package/dist/blueprints/assets/photos/magazine/opinion.jpg +0 -0
  32. package/dist/blueprints/assets/photos/portfolio/adatum-publishing.jpg +0 -0
  33. package/dist/blueprints/assets/photos/portfolio/contoso-mobile-app.jpg +0 -0
  34. package/dist/blueprints/assets/photos/portfolio/fabrikam-annual-report.jpg +0 -0
  35. package/dist/blueprints/assets/photos/portfolio/hero.jpg +0 -0
  36. package/dist/blueprints/assets/photos/portfolio/litware-signage.jpg +0 -0
  37. package/dist/blueprints/assets/photos/portfolio/northwind-rebrand.jpg +0 -0
  38. package/dist/blueprints/assets/photos/portfolio/proseware-launch.jpg +0 -0
  39. package/dist/blueprints/assets/photos/portfolio/tailspin-streaming.jpg +0 -0
  40. package/dist/blueprints/assets/photos/portfolio/wingtip-wayfinding.jpg +0 -0
  41. package/dist/blueprints/assets/photos/restaurant/charred-octopus.jpg +0 -0
  42. package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
  43. package/dist/blueprints/assets/photos/restaurant/creme-brulee.jpg +0 -0
  44. package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
  45. package/dist/blueprints/assets/photos/restaurant/house-red.jpg +0 -0
  46. package/dist/blueprints/assets/photos/restaurant/house-white.jpg +0 -0
  47. package/dist/blueprints/assets/photos/restaurant/pan-seared-trout.jpg +0 -0
  48. package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
  49. package/dist/blueprints/assets/photos/restaurant/roasted-beet-salad.jpg +0 -0
  50. package/dist/blueprints/assets/photos/restaurant/slow-roast-duck-leg.jpg +0 -0
  51. package/dist/blueprints/assets/photos/restaurant/soup-of-the-day.jpg +0 -0
  52. package/dist/blueprints/assets/photos/restaurant/sparkling-water.jpg +0 -0
  53. package/dist/blueprints/assets/photos/restaurant/wild-mushroom-risotto.jpg +0 -0
  54. package/dist/blueprints/assets/photos/saas/avatar-1.jpg +0 -0
  55. package/dist/blueprints/assets/photos/saas/hero.jpg +0 -0
  56. package/dist/blueprints/assets/photos/store/camp-blanket.jpg +0 -0
  57. package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
  58. package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
  59. package/dist/blueprints/assets/photos/store/ceramic-pour-over-set.jpg +0 -0
  60. package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
  61. package/dist/blueprints/assets/photos/store/everyday-tee.jpg +0 -0
  62. package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
  63. package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
  64. package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
  65. package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
  66. package/dist/blueprints/assets/photos/store/trail-tote.jpg +0 -0
  67. package/dist/blueprints/assets/photos/store/wool-beanie.jpg +0 -0
  68. package/dist/blueprints/assets/photos/store/wool-overshirt.jpg +0 -0
  69. package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
  70. package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
  71. package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
  72. package/dist/blueprints/assets/photos/vitrine/dashboard.jpg +0 -0
  73. package/dist/blueprints/assets/photos/vitrine/hero.jpg +0 -0
  74. package/dist/blueprints/association.d.ts +93 -15
  75. package/dist/blueprints/association.d.ts.map +1 -1
  76. package/dist/blueprints/association.js +637 -130
  77. package/dist/blueprints/association.js.map +1 -1
  78. package/dist/blueprints/blog.d.ts +81 -94
  79. package/dist/blueprints/blog.d.ts.map +1 -1
  80. package/dist/blueprints/blog.js +513 -177
  81. package/dist/blueprints/blog.js.map +1 -1
  82. package/dist/blueprints/content-pack.d.ts +83 -2
  83. package/dist/blueprints/content-pack.d.ts.map +1 -1
  84. package/dist/blueprints/content-pack.js +54 -1
  85. package/dist/blueprints/content-pack.js.map +1 -1
  86. package/dist/blueprints/content-packs.d.ts +4 -3
  87. package/dist/blueprints/content-packs.d.ts.map +1 -1
  88. package/dist/blueprints/content-packs.js +6 -3
  89. package/dist/blueprints/content-packs.js.map +1 -1
  90. package/dist/blueprints/demo-media.d.ts +39 -0
  91. package/dist/blueprints/demo-media.d.ts.map +1 -0
  92. package/dist/blueprints/demo-media.js +30 -0
  93. package/dist/blueprints/demo-media.js.map +1 -0
  94. package/dist/blueprints/documentation.d.ts +78 -15
  95. package/dist/blueprints/documentation.d.ts.map +1 -1
  96. package/dist/blueprints/documentation.js +585 -104
  97. package/dist/blueprints/documentation.js.map +1 -1
  98. package/dist/blueprints/magazine.d.ts +81 -15
  99. package/dist/blueprints/magazine.d.ts.map +1 -1
  100. package/dist/blueprints/magazine.js +431 -85
  101. package/dist/blueprints/magazine.js.map +1 -1
  102. package/dist/blueprints/menus.d.ts +25 -0
  103. package/dist/blueprints/menus.d.ts.map +1 -0
  104. package/dist/blueprints/menus.js +33 -0
  105. package/dist/blueprints/menus.js.map +1 -0
  106. package/dist/blueprints/photo-assets.d.ts +3 -0
  107. package/dist/blueprints/photo-assets.d.ts.map +1 -0
  108. package/dist/blueprints/photo-assets.js +33 -0
  109. package/dist/blueprints/photo-assets.js.map +1 -0
  110. package/dist/blueprints/portfolio.d.ts +81 -13
  111. package/dist/blueprints/portfolio.d.ts.map +1 -1
  112. package/dist/blueprints/portfolio.js +605 -102
  113. package/dist/blueprints/portfolio.js.map +1 -1
  114. package/dist/blueprints/registry.d.ts +3 -1
  115. package/dist/blueprints/registry.d.ts.map +1 -1
  116. package/dist/blueprints/registry.js +4 -1
  117. package/dist/blueprints/registry.js.map +1 -1
  118. package/dist/blueprints/restaurant.d.ts +65 -20
  119. package/dist/blueprints/restaurant.d.ts.map +1 -1
  120. package/dist/blueprints/restaurant.js +350 -117
  121. package/dist/blueprints/restaurant.js.map +1 -1
  122. package/dist/blueprints/saas.d.ts +81 -14
  123. package/dist/blueprints/saas.d.ts.map +1 -1
  124. package/dist/blueprints/saas.js +469 -132
  125. package/dist/blueprints/saas.js.map +1 -1
  126. package/dist/blueprints/site-settings-seed.d.ts +17 -0
  127. package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
  128. package/dist/blueprints/site-settings-seed.js +45 -0
  129. package/dist/blueprints/site-settings-seed.js.map +1 -0
  130. package/dist/blueprints/starting-skins.d.ts +19 -0
  131. package/dist/blueprints/starting-skins.d.ts.map +1 -0
  132. package/dist/blueprints/starting-skins.js +301 -0
  133. package/dist/blueprints/starting-skins.js.map +1 -0
  134. package/dist/blueprints/store.d.ts +173 -0
  135. package/dist/blueprints/store.d.ts.map +1 -0
  136. package/dist/blueprints/store.js +643 -0
  137. package/dist/blueprints/store.js.map +1 -0
  138. package/dist/blueprints/vitrine.d.ts +94 -16
  139. package/dist/blueprints/vitrine.d.ts.map +1 -1
  140. package/dist/blueprints/vitrine.js +421 -142
  141. package/dist/blueprints/vitrine.js.map +1 -1
  142. package/dist/demo-art/compositions.d.ts +48 -0
  143. package/dist/demo-art/compositions.d.ts.map +1 -0
  144. package/dist/demo-art/compositions.js +690 -0
  145. package/dist/demo-art/compositions.js.map +1 -0
  146. package/dist/demo-art/index.d.ts +8 -0
  147. package/dist/demo-art/index.d.ts.map +1 -0
  148. package/dist/demo-art/index.js +5 -0
  149. package/dist/demo-art/index.js.map +1 -0
  150. package/dist/demo-art/oklch.d.ts +39 -0
  151. package/dist/demo-art/oklch.d.ts.map +1 -0
  152. package/dist/demo-art/oklch.js +79 -0
  153. package/dist/demo-art/oklch.js.map +1 -0
  154. package/dist/demo-art/png.d.ts +24 -0
  155. package/dist/demo-art/png.d.ts.map +1 -0
  156. package/dist/demo-art/png.js +110 -0
  157. package/dist/demo-art/png.js.map +1 -0
  158. package/dist/demo-art/render.d.ts +228 -0
  159. package/dist/demo-art/render.d.ts.map +1 -0
  160. package/dist/demo-art/render.js +478 -0
  161. package/dist/demo-art/render.js.map +1 -0
  162. package/dist/playground-reset.d.ts.map +1 -1
  163. package/dist/playground-reset.js +22 -4
  164. package/dist/playground-reset.js.map +1 -1
  165. package/dist/prompts.d.ts +12 -0
  166. package/dist/prompts.d.ts.map +1 -1
  167. package/dist/prompts.js +32 -0
  168. package/dist/prompts.js.map +1 -1
  169. package/dist/recap.d.ts.map +1 -1
  170. package/dist/recap.js +6 -0
  171. package/dist/recap.js.map +1 -1
  172. package/dist/scaffold.d.ts +22 -2
  173. package/dist/scaffold.d.ts.map +1 -1
  174. package/dist/scaffold.js +185 -23
  175. package/dist/scaffold.js.map +1 -1
  176. package/dist/wizard.d.ts.map +1 -1
  177. package/dist/wizard.js +4 -1
  178. package/dist/wizard.js.map +1 -1
  179. package/package.json +14 -11
@@ -1,23 +1,27 @@
1
- import { createContentStore, defineCollection, f, validateCollectionSet, } from '@cogenta/schema';
2
- import { toBlockZoneEntry, } from './content-pack.js';
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { createContentStore, createTaxonomyStore, defineCollection, defineTaxonomy, f, validateCollectionSet, validateTaxonomySet, } from '@cogenta/schema';
3
+ import { avatarArt, coverArt, heroArt, logoArt } from '../demo-art/compositions.js';
4
+ import { SEO_FIELDS, toBlockZoneEntry, } from './content-pack.js';
5
+ import { STARTING_SKINS } from './starting-skins.js';
3
6
  /**
4
- * The `blog` blueprint's content model (L9 task 3).
7
+ * The `blog` blueprint's content model (L9 task 3, richened for L25 D4).
5
8
  *
6
9
  * `post` reuses `SystemFields.status`/`createdAt` for publish state and
7
10
  * publish date rather than declaring its own — contract A already carries
8
11
  * both on every entry. Authorship reuses `SystemFields.createdBy`, which
9
12
  * points at the real user/actor system, rather than a separate author
10
13
  * collection this blueprint would have to invent and keep in sync.
14
+ *
15
+ * `category`/`tag` are `defineTaxonomy()` declarations, not collections
16
+ * (audit fiche 04, T02/T10 — `schema@2.0`, ADR-0022, already figé). `category`
17
+ * stays hierarchical (its default); `tag` is declared flat.
11
18
  */
12
- export const category = defineCollection({
19
+ export const category = defineTaxonomy({
13
20
  name: 'category',
14
- labels: { singular: 'Category', plural: 'Categories' },
15
- routing: { pattern: '/blog/category/:slug' },
16
- fields: {
17
- name: f.text({ required: true, max: 80 }),
18
- slug: f.slug({ from: 'name', unique: true }),
21
+ labels: {
22
+ singular: { en: 'Category', fr: 'Catégorie' },
23
+ plural: { en: 'Categories', fr: 'Catégories' },
19
24
  },
20
- indexes: [['slug']],
21
25
  permissions: {
22
26
  read: ['public'],
23
27
  create: ['editor', 'admin'],
@@ -25,14 +29,13 @@ export const category = defineCollection({
25
29
  delete: ['admin'],
26
30
  },
27
31
  });
28
- export const tag = defineCollection({
32
+ export const tag = defineTaxonomy({
29
33
  name: 'tag',
30
- labels: { singular: 'Tag', plural: 'Tags' },
31
- fields: {
32
- name: f.text({ required: true, max: 40 }),
33
- slug: f.slug({ from: 'name', unique: true }),
34
+ labels: {
35
+ singular: { en: 'Tag', fr: 'Étiquette' },
36
+ plural: { en: 'Tags', fr: 'Étiquettes' },
34
37
  },
35
- indexes: [['slug']],
38
+ hierarchical: false,
36
39
  permissions: {
37
40
  read: ['public'],
38
41
  create: ['editor', 'admin'],
@@ -48,13 +51,25 @@ export const post = defineCollection({
48
51
  fields: {
49
52
  title: f.text({ required: true, max: 200 }),
50
53
  slug: f.slug({ from: 'title', unique: true }),
51
- excerpt: f.text({ max: 300, multiline: true }),
52
54
  body: f.richText({ required: true }),
55
+ excerpt: f.text({ max: 300, multiline: true }),
56
+ // L25 D4: a cover image, read by `entryImage` (`@cogenta/theme-kit`)
57
+ // for the "Latest"/"From the archive" cards and the post's own
58
+ // `renderEntryHeader` cover.
53
59
  coverImage: f.media({ accept: ['image'] }),
54
- category: f.relation({ to: 'category', onDelete: 'setNull' }),
55
- tags: f.relation({ to: 'tag', many: true, onDelete: 'cascade' }),
60
+ category: f.taxonomy({ of: 'category', many: false }),
61
+ tags: f.taxonomy({ of: 'tag', many: true }),
62
+ // Declared (contract A's own `docs/04-contrats.md` example does the
63
+ // same on its `article` collection) so `createContentStore`'s own
64
+ // publish-time default (`store.ts`: only set when the collection
65
+ // declares this field) actually fires — without it every post's
66
+ // `renderEntryHeader` meta line silently drops the "date lisible" the
67
+ // brief asks for, since `theme-render.ts` only forwards `publishedAt`
68
+ // when it is non-null.
69
+ publishedAt: f.datetime(),
70
+ ...SEO_FIELDS,
56
71
  },
57
- indexes: [['slug']],
72
+ indexes: [['publishedAt', 'desc'], ['slug']],
58
73
  permissions: {
59
74
  read: ['public'],
60
75
  create: ['editor', 'admin'],
@@ -63,16 +78,6 @@ export const post = defineCollection({
63
78
  publish: ['admin'],
64
79
  },
65
80
  });
66
- /**
67
- * The blueprint's "page types" (L9 task 4): standalone pages composed of
68
- * vocabulary blocks rather than a bespoke template each — the same
69
- * title-plus-block-zone shape `theme-canonical`'s own test fixtures already
70
- * use for `{ collection: 'page', id: … }` internal-link targets, formalised
71
- * here as a real `CollectionDefinition` for the first time. `blocks` uses
72
- * `f.blocks()` (contract A), the field kind a `BlockZone` — an ordered list of
73
- * vocabulary blocks — is stored under; `renderPage` (`@cogenta/theme-canonical`)
74
- * takes exactly a title plus that list.
75
- */
76
81
  export const page = defineCollection({
77
82
  name: 'page',
78
83
  labels: { singular: 'Page', plural: 'Pages' },
@@ -81,6 +86,7 @@ export const page = defineCollection({
81
86
  title: f.text({ required: true, max: 200 }),
82
87
  slug: f.slug({ from: 'title', unique: true }),
83
88
  blocks: f.blocks({ required: true }),
89
+ ...SEO_FIELDS,
84
90
  },
85
91
  indexes: [['slug']],
86
92
  permissions: {
@@ -90,174 +96,299 @@ export const page = defineCollection({
90
96
  delete: ['admin'],
91
97
  },
92
98
  });
93
- /**
94
- * Also the export written into the scaffolded site's `cogenta.schema.mjs`
95
- * (`scaffold.ts`), so `loadCollections` (`@cogenta/cli`) reads back exactly
96
- * these four collections.
97
- */
98
- export const BLOG_COLLECTIONS = [post, category, tag, page];
99
- // Cross-collection checks (duplicate names, dangling relation targets) run
100
- // at import time, same as `defineCollection` itself: a mistake here costs a
101
- // restart, not a database.
99
+ export const BLOG_COLLECTIONS = [post, page];
100
+ export const BLOG_TAXONOMIES = [category, tag];
102
101
  validateCollectionSet(BLOG_COLLECTIONS);
102
+ validateTaxonomySet(BLOG_TAXONOMIES, BLOG_COLLECTIONS);
103
103
  let paragraphKey = 0;
104
104
  /** One `normal`-style rich-text paragraph, unmarked. */
105
105
  function paragraph(text) {
106
106
  paragraphKey += 1;
107
107
  return {
108
- _key: `demo-${paragraphKey}`,
108
+ _key: `demo-p-${paragraphKey}`,
109
+ _type: 'block',
110
+ style: 'normal',
111
+ children: [{ _key: `demo-p-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
112
+ markDefs: [],
113
+ };
114
+ }
115
+ function heading2(text) {
116
+ paragraphKey += 1;
117
+ return {
118
+ _key: `demo-h-${paragraphKey}`,
119
+ _type: 'block',
120
+ style: 'h2',
121
+ children: [{ _key: `demo-h-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
122
+ markDefs: [],
123
+ };
124
+ }
125
+ function bulletItem(text) {
126
+ paragraphKey += 1;
127
+ return {
128
+ _key: `demo-l-${paragraphKey}`,
109
129
  _type: 'block',
110
130
  style: 'normal',
111
- children: [{ _key: `demo-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
131
+ listItem: 'bullet',
132
+ level: 1,
133
+ children: [{ _key: `demo-l-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
134
+ markDefs: [],
135
+ };
136
+ }
137
+ function quoteLine(text) {
138
+ paragraphKey += 1;
139
+ return {
140
+ _key: `demo-q-${paragraphKey}`,
141
+ _type: 'block',
142
+ style: 'blockquote',
143
+ children: [{ _key: `demo-q-${paragraphKey}-span`, _type: 'span', text, marks: [] }],
112
144
  markDefs: [],
113
145
  };
114
146
  }
115
147
  export const BLOG_DEMO_CATEGORIES = [
116
- { name: 'Announcements', slug: 'announcements' },
117
- { name: 'Guides', slug: 'guides' },
148
+ { name: 'Reading', slug: 'reading' },
149
+ { name: 'Building', slug: 'building' },
150
+ { name: 'Writing', slug: 'writing' },
151
+ { name: 'Craft', slug: 'craft' },
118
152
  ];
153
+ // L26 D1: `name` is the display label shown as-is on a tag archive's <h1>
154
+ // (`renderTermArchive`) — lowercase read as unfinished placeholder text
155
+ // next to `category`'s title-cased labels ("Writing", not "writing").
156
+ // `slug` is untouched: it is the URL and the join-table key.
119
157
  export const BLOG_DEMO_TAGS = [
120
- { name: 'cms', slug: 'cms' },
121
- { name: 'open source', slug: 'open-source' },
122
- { name: 'agents', slug: 'agents' },
158
+ { name: 'Process', slug: 'process' },
159
+ { name: 'Tools', slug: 'tools' },
160
+ { name: 'Habits', slug: 'habits' },
161
+ { name: 'Editors', slug: 'editors' },
162
+ { name: 'Notebooks', slug: 'notebooks' },
163
+ { name: 'Focus', slug: 'focus' },
164
+ { name: 'Drafts', slug: 'drafts' },
165
+ { name: 'Revision', slug: 'revision' },
123
166
  ];
124
167
  export const BLOG_DEMO_POSTS = [
125
168
  {
126
- title: 'Welcome to Cogenta',
127
- slug: 'welcome-to-cogenta',
128
- excerpt: 'Cogenta is an open-source, agentic CMS: a site that monitors, patches and optimises itself, and reports what it did.',
169
+ title: 'Why I still write in a plain-text editor',
170
+ slug: 'plain-text-editor',
171
+ excerpt: 'Ten years of trying every tool that promised to make writing easier.',
172
+ categorySlug: 'writing',
173
+ tagSlugs: ['tools', 'editors'],
174
+ body: [
175
+ paragraph('I have paid for at least nine different writing apps since 2016. Each one promised the same ' +
176
+ 'thing: a calmer place to think, fewer distractions, a nicer font. Every single one eventually ' +
177
+ 'added a sidebar, a sync indicator, and a subscription reminder, and I went back to a plain-text ' +
178
+ 'editor within a month.'),
179
+ heading2('What a plain file actually gives you'),
180
+ bulletItem('It opens instantly, every time, on every machine I own.'),
181
+ bulletItem('It will still open in twenty years — the format is the text itself.'),
182
+ bulletItem('Nothing about the tool is trying to keep my attention.'),
183
+ paragraph('None of that is a complaint about design. It is a complaint about incentives: a writing tool that ' +
184
+ 'has to grow a business ends up optimising for engagement, and engagement is the opposite of what ' +
185
+ 'a first draft needs.'),
186
+ quoteLine('The best writing tool is the one that gets out of the way and stays out of the way.'),
187
+ paragraph('So the editor I actually finish drafts in has no plugins, no themes, and no update notifications. ' +
188
+ 'It has a cursor and a blinking line, and that turns out to be enough.'),
189
+ ],
190
+ },
191
+ {
192
+ title: 'The desk setup that finally stuck',
193
+ slug: 'desk-setup-that-stuck',
194
+ excerpt: 'Every previous version of this desk lasted about six weeks. This one is two years old.',
195
+ categorySlug: 'craft',
196
+ tagSlugs: ['tools', 'habits'],
197
+ body: [
198
+ paragraph('I used to rebuild my desk setup every time I read a good "how I work" post. New monitor arm, new ' +
199
+ 'keyboard, new lamp, new chair — a little dopamine hit, a week of feeling productive, and then back ' +
200
+ 'to the same problems.'),
201
+ paragraph('What actually fixed it was removing things rather than adding them: one monitor instead of three, a ' +
202
+ 'closed door instead of noise-cancelling headphones, and a chair I stopped noticing entirely. The ' +
203
+ 'setup that stuck is the one I stopped thinking about.'),
204
+ ],
205
+ },
206
+ {
207
+ title: 'What ten years of reading nonfiction taught me',
208
+ slug: 'ten-years-reading-nonfiction',
209
+ excerpt: 'Mostly that I was reading for the wrong reason for most of it.',
210
+ categorySlug: 'reading',
211
+ tagSlugs: ['habits', 'notebooks'],
212
+ body: [
213
+ paragraph('For the first several years I read to be able to say I had read the book. I finished things I ' +
214
+ 'disliked out of a sense of obligation, and I remembered almost none of them a month later.'),
215
+ heading2('The one change that mattered'),
216
+ paragraph('I started keeping a single notebook — not a book journal, just a running list of the one idea from ' +
217
+ 'each book that changed how I thought about something. The books I remember now are the ones that ' +
218
+ 'earned a line in that notebook, and nothing else.'),
219
+ ],
220
+ },
221
+ {
222
+ title: 'A small tool I built to stop losing drafts',
223
+ slug: 'tool-to-stop-losing-drafts',
224
+ excerpt: 'It is ugly, it has no users but me, and it has saved three articles this year alone.',
225
+ categorySlug: 'building',
226
+ tagSlugs: ['tools', 'drafts'],
129
227
  body: [
130
- paragraph('Cogenta is a content management system built around a simple idea: a site should be able to run ' +
131
- 'itself. Under the hood it is a normal, dependable CMS content types, drafts, versioning, media, ' +
132
- 'multi-locale entries with an agent runtime as a first-class part of the core rather than a ' +
133
- 'bolted-on plugin.'),
134
- paragraph('This post, and this blog, were scaffolded by create-cogenta as part of the "blog" blueprint: a ready ' +
135
- 'content model, a skin, and a few real demo posts to look at instead of an empty admin screen.'),
228
+ paragraph('Every text editor I have used eventually loses a draft a crash, a sync conflict, a laptop that ' +
229
+ 'died mid-sentence. After the third time it happened to something I actually cared about, I wrote a ' +
230
+ 'forty-line script that copies whatever is in my drafts folder to a second disk every five minutes.'),
231
+ paragraph('It has no interface, no settings, and no name beyond `backup.sh`. It is the single most useful piece ' +
232
+ 'of software I have ever written, and it took less time to build than writing this post did.'),
136
233
  ],
137
- categorySlug: 'announcements',
138
- tagSlugs: ['cms', 'open-source'],
139
234
  },
140
235
  {
141
- title: 'What a blueprint actually gives you',
142
- slug: 'what-a-blueprint-gives-you',
143
- excerpt: 'A blueprint is not a theme. It is a content model, a skin, recommended agents and demo content, all at once — and every part of it can still be changed afterwards.',
236
+ title: 'The one habit that fixed my writing schedule',
237
+ slug: 'habit-that-fixed-my-schedule',
238
+ excerpt: 'Not a morning routine. Not a word-count goal. Something much smaller.',
239
+ categorySlug: 'writing',
240
+ tagSlugs: ['habits', 'focus'],
144
241
  body: [
145
- paragraph('Picking the "blog" blueprint during setup creates three collections post, category and tag wires ' +
146
- "up routing for a post list, a post page and a category archive, and applies the canonical theme's " +
147
- 'default skin.'),
148
- paragraph('None of that is a cage. The schema is a normal cogenta.schema.mjs file, the skin is a normal ' +
149
- 'tokens.json, and both are meant to be edited the moment the defaults stop fitting.'),
242
+ paragraph('I tried the 5am wake-up. I tried a 1,000-word daily minimum. Both worked for about a week and then ' +
243
+ 'collapsed the first time life got in the way, which made me feel worse than not trying at all.'),
244
+ paragraph('What actually stuck was absurdly small: open the draft file before doing anything else on the ' +
245
+ 'computer, even if I only read the last paragraph and close it again. Some days that is all that ' +
246
+ 'happens. Most days, once the file is open, I keep going.'),
150
247
  ],
151
- categorySlug: 'guides',
152
- tagSlugs: ['cms'],
153
248
  },
154
249
  {
155
- title: 'Agents recommended for a blog',
156
- slug: 'agents-recommended-for-a-blog',
157
- excerpt: 'The blog blueprint names two agents worth turning on it does not turn them on for you.',
250
+ title: 'Notebooks I have actually finished',
251
+ slug: 'notebooks-actually-finished',
252
+ excerpt: 'A short, honest list, after a drawer full of notebooks with four used pages each.',
253
+ categorySlug: 'reading',
254
+ tagSlugs: ['notebooks', 'habits'],
158
255
  body: [
159
- paragraph('No site in Cogenta runs a live agent scheduler by default (R2: the CMS works with zero AI configured). ' +
160
- 'What the "blog" blueprint does instead is record a recommendation: seoAgent and contentAgent, both ' +
161
- 'shipped in @cogenta/agents-builtin, are a reasonable pair to enable for a content-heavy site once an ' +
162
- 'LLM provider is configured.'),
163
- paragraph('Turning them on is a deliberate, separate step not something a scaffold should decide on your behalf.'),
256
+ paragraph('I own a drawer of notebooks that got four pages of enthusiastic notes and then nothing. The ones I ' +
257
+ 'actually filled share one property: they were too plain and too cheap to feel precious.'),
258
+ bulletItem('A notebook I am afraid to ruin is a notebook I will not open.'),
259
+ bulletItem('Grid paper beats lined paper for anything that is not a diary.'),
260
+ bulletItem('A pen that writes badly is worse for the habit than no pen at all.'),
261
+ ],
262
+ },
263
+ {
264
+ title: 'Building a blog that runs itself',
265
+ slug: 'blog-that-runs-itself',
266
+ excerpt: 'What I actually automated, and — more importantly — what I deliberately did not.',
267
+ categorySlug: 'building',
268
+ tagSlugs: ['tools', 'process'],
269
+ body: [
270
+ paragraph('This blog is a static content model with a scheduler that checks for broken links, a spellchecker ' +
271
+ 'that flags anything odd before it publishes, and a script that reminds me when a post has not been ' +
272
+ 'revisited in two years.'),
273
+ paragraph('What it does not do is write anything, choose what to publish, or decide when a draft is ready. The ' +
274
+ 'automation handles the boring parts so the writing stays mine.'),
275
+ ],
276
+ },
277
+ {
278
+ title: 'Editing is where the writing happens',
279
+ slug: 'editing-is-where-writing-happens',
280
+ excerpt: 'A first draft is raw material. The real writing starts on the second pass.',
281
+ categorySlug: 'writing',
282
+ tagSlugs: ['revision', 'drafts'],
283
+ body: [
284
+ paragraph('I used to think of editing as cleanup — fixing typos, tightening a sentence here and there. Every ' +
285
+ 'post that actually worked was rebuilt at least once, sometimes from a completely different opening.'),
286
+ heading2('What I look for on a second pass'),
287
+ bulletItem('The paragraph that is doing the least work — usually the second one.'),
288
+ bulletItem('A claim I made without an example to back it up.'),
289
+ bulletItem('The sentence I am proudest of, which is often the one that should go.'),
290
+ quoteLine('A draft is a question. An edit is the answer you are actually willing to publish.'),
164
291
  ],
165
- categorySlug: 'guides',
166
- tagSlugs: ['agents', 'open-source'],
167
292
  },
168
293
  ];
169
294
  const BLOCK_VERSION = '1.0.0';
170
295
  /**
171
- * `home` and `about` — the two demo pages a freshly scaffolded blog needs so
172
- * its front page is not the empty admin screen. `home` pairs a `hero` with a
173
- * `collectionList` scoped to `post`, both part of the frozen block vocabulary
174
- * (contract B) and both already rendered generically by
175
- * `@cogenta/theme-canonical`'s `renderPage`/`renderBlock` — no bespoke
176
- * "home page" or "about page" template is needed for either.
296
+ * The blueprint's own starting skin (`starting-skins.js`)asserted present
297
+ * with a real check, not a `!`, the same guard `store.ts`'s `storePalette`
298
+ * uses: `STARTING_SKINS` is keyed by blueprint id and TypeScript cannot see
299
+ * that this particular key is always populated.
177
300
  */
178
- export const BLOG_DEMO_PAGES = [
301
+ function blogPalette() {
302
+ const skin = STARTING_SKINS.blog;
303
+ if (skin === undefined) {
304
+ throw new CogentaError({
305
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
306
+ message: 'STARTING_SKINS.blog is missing.',
307
+ hint: 'The "blog" entry must stay declared in starting-skins.ts for this blueprint to render its demo art.',
308
+ });
309
+ }
310
+ return skin.color;
311
+ }
312
+ /**
313
+ * Procedural visuals this blueprint seeds (L25 D4): a magazine-cover hero
314
+ * backdrop, one cover per demo post (keyed by the post's own slug), a reader
315
+ * avatar for the "reader's words" quote block, and five neutral wordmark
316
+ * stand-ins for the "As featured in" strip.
317
+ */
318
+ /** Bundled photography (`assets/photos/blog/`), keyed by the same slug `BLOG_DEMO_POSTS` uses. */
319
+ const BLOG_POST_PHOTOS = {
320
+ 'plain-text-editor': 'blog/plain-text-editor.jpg',
321
+ 'desk-setup-that-stuck': 'blog/desk-setup.jpg',
322
+ 'ten-years-reading-nonfiction': 'blog/reading-nonfiction.jpg',
323
+ 'tool-to-stop-losing-drafts': 'blog/small-tool.jpg',
324
+ 'habit-that-fixed-my-schedule': 'blog/writing-schedule.jpg',
325
+ 'notebooks-actually-finished': 'blog/notebooks.jpg',
326
+ 'blog-that-runs-itself': 'blog/blog-runs-itself.jpg',
327
+ 'editing-is-where-writing-happens': 'blog/editing.jpg',
328
+ };
329
+ export const BLOG_MEDIA_SPECS = [
179
330
  {
180
- title: 'Home',
181
- slug: 'home',
182
- blocks: [
183
- {
184
- _key: 'demo-home-hero',
185
- _type: 'hero',
186
- _version: BLOCK_VERSION,
187
- eyebrow: 'Cogenta',
188
- title: 'A blog that runs itself',
189
- subtitle: 'Scaffolded by create-cogenta with real demo posts, categories and tags — every one of them editable the moment you sign in.',
190
- actions: [
191
- {
192
- label: 'Read the latest post',
193
- target: { href: '/blog/welcome-to-cogenta' },
194
- emphasis: 'primary',
195
- },
196
- ],
197
- },
198
- {
199
- _key: 'demo-home-recent-posts',
200
- _type: 'collectionList',
201
- _version: BLOCK_VERSION,
202
- title: 'Latest posts',
203
- collection: 'post',
204
- // `publishedAt` is nullable (a draft has none) and was never in the
205
- // real, frozen `SortField` union (`id`/`createdAt`/`updatedAt` only —
206
- // cursor pagination needs a column that is never null). This block
207
- // asked for it anyway and nothing exercised the query until
208
- // `cogenta serve`'s theme-render fallback did, surfacing a real
209
- // QUERY_INVALID on every request for the seeded home page.
210
- sort: { field: 'createdAt', direction: 'desc' },
211
- limit: 10,
212
- layout: 'list',
213
- },
214
- {
215
- _key: 'demo-home-what',
216
- _type: 'featureGrid',
217
- _version: BLOCK_VERSION,
218
- title: 'What you are looking at',
219
- items: [
220
- {
221
- _key: 'demo-what-1',
222
- icon: 'blocks',
223
- title: 'Blocks, not HTML',
224
- text: 'Every section of this page is a block storing plain data. The theme decides what it looks like, so a new skin restyles all of it at once.',
225
- },
226
- {
227
- _key: 'demo-what-2',
228
- icon: 'content',
229
- title: 'Real content, from the first run',
230
- text: 'The posts, categories and tags below were seeded by the installer. Rename them, delete them — nothing here is special.',
231
- },
232
- {
233
- _key: 'demo-what-3',
234
- icon: 'zero-js',
235
- title: 'No client JavaScript',
236
- text: 'The accordion, the carousel and the dark mode are all CSS. There is no bundle to wait for on a slow connection.',
237
- },
238
- ],
239
- },
240
- ],
331
+ name: 'hero',
332
+ spec: heroArt(blogPalette(), 'radial', 7),
333
+ alt: 'A writer’s desk',
334
+ photo: 'blog/hero.jpg',
241
335
  },
242
336
  {
243
- title: 'About',
244
- slug: 'about',
245
- blocks: [
246
- {
247
- _key: 'demo-about-prose',
248
- _type: 'prose',
249
- _version: BLOCK_VERSION,
250
- body: [
251
- paragraph('This is a demo blog, scaffolded by create-cogenta from the "blog" blueprint. Its posts, ' +
252
- 'categories, tags and this very page were seeded by the installer so there is real content to ' +
253
- 'look at from the first run, not an empty admin screen.'),
254
- paragraph('Everything here the schema, the content, the skin — is a normal part of the site and is meant ' +
255
- 'to be edited, renamed or deleted the moment the defaults stop fitting.'),
256
- ],
257
- },
258
- ],
337
+ name: 'quote-avatar',
338
+ spec: avatarArt(blogPalette(), 3),
339
+ alt: 'Abstract avatar mark for a reader quote',
340
+ },
341
+ // L26 D2/D3: the About page had nothing but two paragraphs of text and no
342
+ // visual at all — a different seed from `quote-avatar` (7, not 3) so the
343
+ // two abstract marks read as distinct rather than a copy-pasted image.
344
+ // No bundled photo fits an author portrait (the nine real photos in
345
+ // `assets/photos/blog/` are the hero backdrop and eight post covers, all
346
+ // already spoken for), and there is no image-generation key to make a new
347
+ // one the procedural generator is the honest fallback here, exactly as
348
+ // it already is for `quote-avatar` and the five press logos below.
349
+ {
350
+ name: 'about-avatar',
351
+ spec: avatarArt(blogPalette(), 7),
352
+ alt: 'Abstract avatar mark for the About page',
259
353
  },
354
+ ...[1, 2, 3, 4, 5].map((n) => ({
355
+ name: `logo-${n}`,
356
+ spec: logoArt(n),
357
+ alt: `Neutral wordmark placeholder ${n}`,
358
+ })),
359
+ ...BLOG_DEMO_POSTS.map((demo, index) => {
360
+ const photo = BLOG_POST_PHOTOS[demo.slug];
361
+ return {
362
+ name: `post-${demo.slug}`,
363
+ spec: coverArt(blogPalette(), index + 1),
364
+ alt: `Cover art for "${demo.title}"`,
365
+ ...(photo === undefined ? {} : { photo }),
366
+ };
367
+ }),
260
368
  ];
369
+ /** Header/footer navigation and the header call-to-action button (L25 D4). */
370
+ export const BLOG_MENUS = {
371
+ header: [
372
+ { label: 'Home' },
373
+ { label: 'Writing', url: '/blog' },
374
+ { label: 'About', url: '/about' },
375
+ ],
376
+ footer: [
377
+ { label: 'About', url: '/about' },
378
+ { label: 'Archive', url: '/archive' },
379
+ { label: 'RSS', url: '/feed.xml' },
380
+ ],
381
+ headerAction: { label: 'Subscribe', url: '/#newsletter' },
382
+ };
383
+ export const BLOG_SITE_SETTINGS = {
384
+ 'general.tagline': 'Notes on reading, building and writing things down.',
385
+ 'general.socialLinks': [
386
+ { label: 'Mastodon', url: 'https://mastodon.social/@example' },
387
+ { label: 'X', url: 'https://x.com/example' },
388
+ { label: 'GitHub', url: 'https://github.com/example' },
389
+ ],
390
+ 'general.footerNote': 'A personal blog, scaffolded by create-cogenta.',
391
+ };
261
392
  export const BLOG_RECOMMENDED_AGENTS = [
262
393
  {
263
394
  name: 'seoAgent',
@@ -270,35 +401,234 @@ export const BLOG_RECOMMENDED_AGENTS = [
270
401
  reason: 'Flags terminology drift and topic gaps across the post archive.',
271
402
  },
272
403
  ];
404
+ /**
405
+ * `home` (the featured-post hero, a live "Latest" grid, the topics rail, a
406
+ * reader quote, an editorial "From the archive" list, a newsletter panel, a
407
+ * press strip and an FAQ — eight blocks, the brief's own order) and `about`.
408
+ * `media` (`SeedContext.media`, L25 task A0b) supplies the hero backdrop and
409
+ * the quote avatar; a function of it rather than a static const for the same
410
+ * reason `store.ts`'s `buildStoreDemoPages` is.
411
+ */
412
+ export function buildBlogDemoPages(media) {
413
+ const featured = BLOG_DEMO_POSTS[0];
414
+ if (featured === undefined) {
415
+ throw new CogentaError({
416
+ code: 'BLUEPRINT_REGISTRY_CORRUPT',
417
+ message: 'BLOG_DEMO_POSTS is empty.',
418
+ hint: 'The blog blueprint needs at least one demo post to feature on its home page.',
419
+ });
420
+ }
421
+ const logoItems = [1, 2, 3, 4, 5]
422
+ .filter((n) => media[`logo-${n}`] !== undefined)
423
+ .map((n) => ({ _key: `demo-logo-${n}`, media: media[`logo-${n}`] }));
424
+ const aboutAvatar = media['about-avatar'];
425
+ return [
426
+ {
427
+ title: 'Home',
428
+ slug: 'home',
429
+ blocks: [
430
+ {
431
+ _key: 'demo-home-hero',
432
+ _type: 'hero',
433
+ _version: BLOCK_VERSION,
434
+ eyebrow: 'Featured',
435
+ title: featured.title,
436
+ subtitle: featured.excerpt,
437
+ ...(media.hero === undefined ? {} : { media: media.hero }),
438
+ actions: [{ label: 'Read the story', target: { href: `/blog/${featured.slug}` } }],
439
+ },
440
+ {
441
+ _key: 'demo-home-latest',
442
+ _type: 'collectionList',
443
+ _version: BLOCK_VERSION,
444
+ title: 'Latest',
445
+ collection: 'post',
446
+ sort: { field: 'createdAt', direction: 'desc' },
447
+ limit: 6,
448
+ layout: 'grid',
449
+ },
450
+ {
451
+ _key: 'demo-home-topics',
452
+ _type: 'featureGrid',
453
+ _version: BLOCK_VERSION,
454
+ title: 'Topics',
455
+ items: [
456
+ {
457
+ _key: 'demo-topic-1',
458
+ icon: 'book',
459
+ title: 'Reading',
460
+ text: 'What I read, and why it stuck.',
461
+ },
462
+ {
463
+ _key: 'demo-topic-2',
464
+ icon: 'code',
465
+ title: 'Building',
466
+ text: 'Small tools, made to be used once.',
467
+ },
468
+ {
469
+ _key: 'demo-topic-3',
470
+ icon: 'pen',
471
+ title: 'Writing',
472
+ text: 'How a draft becomes a post.',
473
+ },
474
+ {
475
+ _key: 'demo-topic-4',
476
+ icon: 'coffee',
477
+ title: 'Craft',
478
+ text: 'The unglamorous parts of making things.',
479
+ },
480
+ ],
481
+ },
482
+ {
483
+ _key: 'demo-home-quote',
484
+ _type: 'quote',
485
+ _version: BLOCK_VERSION,
486
+ text: 'I started reading this blog for the writing advice and stayed for everything else.',
487
+ author: 'A. Reader',
488
+ role: 'Subscriber since issue one',
489
+ ...(media['quote-avatar'] === undefined ? {} : { avatar: media['quote-avatar'] }),
490
+ },
491
+ {
492
+ _key: 'demo-home-archive',
493
+ _type: 'collectionList',
494
+ _version: BLOCK_VERSION,
495
+ title: 'From the archive',
496
+ collection: 'post',
497
+ sort: { field: 'createdAt', direction: 'asc' },
498
+ limit: 5,
499
+ layout: 'list',
500
+ },
501
+ {
502
+ _key: 'demo-home-newsletter',
503
+ _type: 'cta',
504
+ _version: BLOCK_VERSION,
505
+ title: 'Get the weekly letter',
506
+ text: 'One email, every Thursday, no more than five minutes to read.',
507
+ actions: [
508
+ { label: 'Subscribe', target: { href: '/#newsletter' }, emphasis: 'primary' },
509
+ { label: 'See a past issue', target: { href: '/archive' } },
510
+ ],
511
+ },
512
+ // Omitted entirely, rather than sent with an empty `logos` array,
513
+ // when no logo mark was actually ingested (e.g. `seedDemoContent`
514
+ // called with `media: {}`) — contract B requires at least one item.
515
+ ...(logoItems.length === 0
516
+ ? []
517
+ : [
518
+ {
519
+ _key: 'demo-home-featured-in',
520
+ _type: 'logoStrip',
521
+ _version: BLOCK_VERSION,
522
+ caption: 'As featured in',
523
+ logos: logoItems,
524
+ },
525
+ ]),
526
+ {
527
+ _key: 'demo-home-faq',
528
+ _type: 'faq',
529
+ _version: BLOCK_VERSION,
530
+ title: 'About this blog',
531
+ items: [
532
+ {
533
+ _key: 'demo-faq-1',
534
+ question: 'How often do you publish?',
535
+ answer: [
536
+ paragraph('Every other Thursday, with the occasional extra when something is urgent.'),
537
+ ],
538
+ },
539
+ {
540
+ _key: 'demo-faq-2',
541
+ question: 'Do you accept guest posts?',
542
+ answer: [
543
+ paragraph('Not currently — everything here is written and edited by one person.'),
544
+ ],
545
+ },
546
+ {
547
+ _key: 'demo-faq-3',
548
+ question: 'Is there an RSS feed?',
549
+ answer: [paragraph('Yes — the link is in the footer of every page.')],
550
+ },
551
+ {
552
+ _key: 'demo-faq-4',
553
+ question: 'Can I republish a post?',
554
+ answer: [paragraph('With attribution and a link back, always — just ask first.')],
555
+ },
556
+ ],
557
+ },
558
+ ],
559
+ },
560
+ {
561
+ title: 'About',
562
+ slug: 'about',
563
+ blocks: [
564
+ // L26 D2/D3: a small figure above the fold — the page read as bare
565
+ // with nothing but two short paragraphs and no visual at all.
566
+ // `align: 'start'` keeps it a modest author-photo size (half the
567
+ // reading measure, not a full-bleed hero) rather than stretching an
568
+ // abstract 600×600 mark across the whole page width.
569
+ ...(aboutAvatar === undefined
570
+ ? []
571
+ : [
572
+ {
573
+ _key: 'demo-about-avatar',
574
+ _type: 'mediaFigure',
575
+ _version: BLOCK_VERSION,
576
+ media: aboutAvatar,
577
+ ratio: '1:1',
578
+ align: 'start',
579
+ },
580
+ ]),
581
+ {
582
+ _key: 'demo-about-prose',
583
+ _type: 'prose',
584
+ _version: BLOCK_VERSION,
585
+ body: [
586
+ paragraph('Ink & Paper started as a running list of one idea per book, kept so I would stop forgetting ' +
587
+ 'what I had just read. It grew into a place for the smaller things too: the desk setups that ' +
588
+ 'did not last, the tools that quietly disappeared, the habit that finally stuck after a dozen ' +
589
+ 'that did not.'),
590
+ paragraph('Nothing here is written to sound like advice. Most of it is closer to a lab notebook — what I ' +
591
+ 'tried, what broke, and the one detail that turned out to matter more than the rest. If a post ' +
592
+ 'reads as useful to someone else, that is a bonus I do not plan around.'),
593
+ paragraph('This is a demo blog, scaffolded by create-cogenta from the "blog" blueprint. Its posts, ' +
594
+ 'categories, tags and this very page were seeded by the installer so there is real content to ' +
595
+ 'look at from the first run, not an empty admin screen.'),
596
+ paragraph('Everything here — the schema, the content, the skin — is a normal part of the site and is ' +
597
+ 'meant to be edited, renamed or deleted the moment the defaults stop fitting.'),
598
+ ],
599
+ },
600
+ ],
601
+ },
602
+ ];
603
+ }
273
604
  /**
274
605
  * Inserts the `blog` blueprint's demo content through the real `ContentStore`
275
- * — never mocked (house rule) — so a scaffolded blog blueprint has genuine
276
- * rows to look at, not a claim that it does.
606
+ * — never mocked (house rule).
277
607
  */
278
- async function seedBlogDemoContent(db, defaultLocale, adminId) {
279
- const categoryStore = createContentStore({ db, collection: category, defaultLocale });
280
- const tagStore = createContentStore({ db, collection: tag, defaultLocale });
608
+ async function seedBlogDemoContent(ctx) {
609
+ const { db, defaultLocale, adminId, media } = ctx;
610
+ const categoryStore = createTaxonomyStore({ db, taxonomy: category });
611
+ const tagStore = createTaxonomyStore({ db, taxonomy: tag });
281
612
  const postStore = createContentStore({ db, collection: post, defaultLocale });
282
613
  const pageStore = createContentStore({ db, collection: page, defaultLocale });
283
614
  const categoryIdBySlug = new Map();
284
615
  for (const demo of BLOG_DEMO_CATEGORIES) {
285
- const entry = await categoryStore.create({
286
- status: 'published',
287
- createdBy: adminId,
288
- values: { name: demo.name, slug: demo.slug },
616
+ const term = await categoryStore.create({
617
+ slug: demo.slug,
618
+ labels: { [defaultLocale]: demo.name },
289
619
  });
290
- categoryIdBySlug.set(demo.slug, entry.id);
620
+ categoryIdBySlug.set(demo.slug, term.id);
291
621
  }
292
622
  const tagIdBySlug = new Map();
293
623
  for (const demo of BLOG_DEMO_TAGS) {
294
- const entry = await tagStore.create({
295
- status: 'published',
296
- createdBy: adminId,
297
- values: { name: demo.name, slug: demo.slug },
624
+ const term = await tagStore.create({
625
+ slug: demo.slug,
626
+ labels: { [defaultLocale]: demo.name },
298
627
  });
299
- tagIdBySlug.set(demo.slug, entry.id);
628
+ tagIdBySlug.set(demo.slug, term.id);
300
629
  }
301
630
  for (const demo of BLOG_DEMO_POSTS) {
631
+ const cover = media[`post-${demo.slug}`];
302
632
  await postStore.create({
303
633
  status: 'published',
304
634
  createdBy: adminId,
@@ -309,10 +639,11 @@ async function seedBlogDemoContent(db, defaultLocale, adminId) {
309
639
  body: demo.body,
310
640
  category: categoryIdBySlug.get(demo.categorySlug) ?? null,
311
641
  tags: demo.tagSlugs.map((slug) => tagIdBySlug.get(slug)).filter((id) => id !== undefined),
642
+ ...(cover === undefined ? {} : { coverImage: cover }),
312
643
  },
313
644
  });
314
645
  }
315
- for (const demo of BLOG_DEMO_PAGES) {
646
+ for (const demo of buildBlogDemoPages(media)) {
316
647
  await pageStore.create({
317
648
  status: 'published',
318
649
  createdBy: adminId,
@@ -323,7 +654,12 @@ async function seedBlogDemoContent(db, defaultLocale, adminId) {
323
654
  }
324
655
  export const blogContentPack = {
325
656
  collections: BLOG_COLLECTIONS,
657
+ taxonomies: BLOG_TAXONOMIES,
326
658
  recommendedAgents: BLOG_RECOMMENDED_AGENTS,
327
659
  seedDemoContent: seedBlogDemoContent,
660
+ defaultTheme: '@cogenta/theme-blog',
661
+ menus: BLOG_MENUS,
662
+ siteSettings: BLOG_SITE_SETTINGS,
663
+ mediaSpecs: BLOG_MEDIA_SPECS,
328
664
  };
329
665
  //# sourceMappingURL=blog.js.map