@voltro/cli 0.52.0 → 0.54.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 (244) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/THIRD-PARTY-NOTICES.md +8311 -3318
  3. package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
  4. package/dist/agentsMd-DCY1RSs8.js +2 -0
  5. package/dist/apiBuild-CeUN55uk.js +2 -0
  6. package/dist/{apiBuild-CSFI8QGq.js → apiBuild-DTWp0S_q.js} +11 -5
  7. package/dist/bin.js +1 -1
  8. package/dist/build-D4ygSbnV.js +843 -0
  9. package/dist/{checkCommand-COmqc2cB.js → checkCommand-Dg1G7Gwd.js} +6 -6
  10. package/dist/{checkCommand-2SbqzukH.js → checkCommand-L7DTlpIF.js} +1 -1
  11. package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
  12. package/dist/{codegen-VF479Cnb.js → codegen-DSLM8Su9.js} +1 -1
  13. package/dist/codegen-DjgxEOnD.js +2 -0
  14. package/dist/codegenCommand-CG_Vx4lc.js +41 -0
  15. package/dist/{codemodRunner-r7J9lIa7.js → codemodRunner-Cd4xkC6u.js} +109 -11
  16. package/dist/{commands-Cc_nV8WI.js → commands-6Kzi92Np.js} +96 -73
  17. package/dist/{dashboardCommand-C-gKvwqh.js → dashboardCommand-Cq1PWvI1.js} +5 -5
  18. package/dist/{dataCommand-BgpBHnlB.js → dataCommand-DYzW8vkv.js} +3 -3
  19. package/dist/{dbCommand-sHedr-NJ.js → dbCommand-B4NWZtGL.js} +278 -237
  20. package/dist/dbCommand-CSFWs9ev.js +2 -0
  21. package/dist/{dev-CRHoCEiy.js → dev-CmuvUKRq.js} +2903 -2306
  22. package/dist/{dev--A3nsxA3.js → dev-cKUiZZsB.js} +1 -1
  23. package/dist/{doctorCommand-DtfJ3FA6.js → doctorCommand-DCiFVMtZ.js} +101 -69
  24. package/dist/doctorCommand-J3qu4E0Y.js +2 -0
  25. package/dist/{dormancyCommand-Drn7o0No.js → dormancyCommand-w1TrmgYP.js} +1 -1
  26. package/dist/{embeddingsCommand-Z-jO1fWN.js → embeddingsCommand-CMgPyRTr.js} +1 -1
  27. package/dist/{envCommand-D4gCrrTZ.js → envCommand-Cyynmcfa.js} +8 -8
  28. package/dist/{evolveCommand-CMROeKeA.js → evolveCommand-BwvQ8dVH.js} +2 -2
  29. package/dist/fileConventions-l-RIXbx8.js +36 -0
  30. package/dist/{fileTaxonomy-DvDUV9wq.js → fileTaxonomy-CbyMQYx_.js} +42 -42
  31. package/dist/fontPipeline-LxIHa1vo.js +2 -0
  32. package/dist/fontPipeline-Tsh8kZfA.js +152 -0
  33. package/dist/{frameworkTableAssembly-w-XnLa3q.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
  34. package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
  35. package/dist/imagePipeline-B_GVJgm6.js +2 -0
  36. package/dist/imagePipeline-CBZmjT4i.js +127 -0
  37. package/dist/index.js +2 -2
  38. package/dist/{infoCommand-DXM868o_.js → infoCommand-DlYlUPqs.js} +1 -1
  39. package/dist/{inspect-CtL_xTbu.js → inspect-Bd8-9wsi.js} +1 -1
  40. package/dist/inspect-CuoDInfZ.js +2 -0
  41. package/dist/{inspectGateHint-BjnFubmH.js → inspectGateHint-4LxkNtrz.js} +1 -1
  42. package/dist/manifestBuild-C4-J1-m_.js +2 -0
  43. package/dist/{manifestBuild-CuU1VrSm.js → manifestBuild-Cqgsx2bM.js} +1 -1
  44. package/dist/{metaCommands-C6RFmF1r.js → metaCommands-x7RCi2AF.js} +9 -3
  45. package/dist/{migrate-D0F-eTlK.js → migrate-BK_Bbx-_.js} +2 -2
  46. package/dist/mobileCommand-D9O6iq3D.js +428 -0
  47. package/dist/mobileCommand-DAum7tsG.js +2 -0
  48. package/dist/{pageConvention-CzUiSbtU.js → pageConvention-CMpfDN6r.js} +1 -1
  49. package/dist/{privacyCommand-DGdopOI6.js → privacyCommand-BCa2OoZG.js} +1 -1
  50. package/dist/{probeCommand-C9gazU0H.js → probeCommand-_C0YU207.js} +83 -24
  51. package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
  52. package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
  53. package/dist/renderModeScan-43yQ2opo.js +147 -0
  54. package/dist/{renderProfile-Ck32Fzxr.js → renderProfile-DvrhVJHa.js} +2 -2
  55. package/dist/{runtimeTrace-BPQyCmC5.js → runtimeTrace-CGWx1Q6l.js} +1 -1
  56. package/dist/{sdkgen-Se88ifTd.js → sdkgen-CDGHQUFj.js} +1 -1
  57. package/dist/serveCommand-BiPe8BJm.js +2 -0
  58. package/dist/{serveCommand-DkP3OT0W.js → serveCommand-Bje09q1v.js} +889 -825
  59. package/dist/serveEntry.js +1 -1
  60. package/dist/start-B0bnJgxI.js +3 -0
  61. package/dist/{start-jw89Xbqy.js → start-Clz-1BHB.js} +633 -455
  62. package/dist/startEntry.js +1 -1
  63. package/dist/{staticCommand-BwNEDlSU.js → staticCommand-ey0kYmOT.js} +1 -1
  64. package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
  65. package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
  66. package/dist/{test-f3amja6a.js → test-D_kW4KMj.js} +1 -1
  67. package/dist/updateCommand-CIoVDKnj.js +2 -0
  68. package/dist/{updateCommand-BMk2e4ky.js → updateCommand-CRJlAOaM.js} +139 -115
  69. package/dist/{webDev-BgWL9gKV.js → webDev-DSI9SOhs.js} +1598 -1028
  70. package/dist/webDev-DlvZO30c.js +2 -0
  71. package/dist/{webhooksCommand-CoIO3jbj.js → webhooksCommand-BvzXNHji.js} +1 -1
  72. package/dist/workspaceDeps-RKEkX92S.js +45 -0
  73. package/package.json +60 -19
  74. package/templates/AGENTS.core.md +2 -0
  75. package/templates/AGENTS.md +8 -4
  76. package/templates/agent-docs/_index.md +6 -4
  77. package/templates/agent-docs/_manifest.json +21 -5
  78. package/templates/agent-docs/ai.md +6 -6
  79. package/templates/agent-docs/authentication.md +73 -1
  80. package/templates/agent-docs/cli.md +6 -3
  81. package/templates/agent-docs/configuration.md +17 -0
  82. package/templates/agent-docs/data.md +522 -29
  83. package/templates/agent-docs/database/advancedqueries.md +8 -8
  84. package/templates/agent-docs/database/columntypes.md +2 -2
  85. package/templates/agent-docs/database/migrations.md +1 -1
  86. package/templates/agent-docs/database/querying.md +1 -1
  87. package/templates/agent-docs/database/schema.md +1 -1
  88. package/templates/agent-docs/database/seedsdialects.md +65 -3
  89. package/templates/agent-docs/database/transactions.md +3 -3
  90. package/templates/agent-docs/deployment.md +8 -0
  91. package/templates/agent-docs/internationalization.md +4 -2
  92. package/templates/agent-docs/introduction.md +32 -1
  93. package/templates/agent-docs/local-first-mobile.md +226 -30
  94. package/templates/agent-docs/observability.md +5 -1
  95. package/templates/agent-docs/plugins/atlassian.md +2 -2
  96. package/templates/agent-docs/plugins/audit.md +2 -2
  97. package/templates/agent-docs/plugins/auth.md +1 -1
  98. package/templates/agent-docs/plugins/billing.md +1 -1
  99. package/templates/agent-docs/plugins/cdc-out.md +8 -3
  100. package/templates/agent-docs/plugins/comments.md +164 -0
  101. package/templates/agent-docs/plugins/notifications.md +47 -4
  102. package/templates/agent-docs/plugins/presence.md +45 -3
  103. package/templates/agent-docs/plugins/prometheus.md +3 -1
  104. package/templates/agent-docs/plugins/queue.md +172 -0
  105. package/templates/agent-docs/plugins.md +17 -13
  106. package/templates/agent-docs/reference.md +35 -4
  107. package/templates/agent-docs/routing.md +585 -7
  108. package/templates/agent-docs/scheduling.md +1 -1
  109. package/templates/agent-docs/schema-driven-ui.md +226 -4
  110. package/templates/agent-docs/security.md +3 -3
  111. package/templates/agent-docs/templates/appshells.md +36 -4
  112. package/templates/agent-docs/whats-new.md +134 -66
  113. package/templates/apps/api-ai/package.json +6 -6
  114. package/templates/apps/api-auth/package.json +8 -8
  115. package/templates/apps/api-backend/package.json +7 -7
  116. package/templates/apps/api-backend-deactivation/package.json +7 -7
  117. package/templates/apps/api-backend-mail/package.json +8 -8
  118. package/templates/apps/api-backend-mariadb/package.json +9 -9
  119. package/templates/apps/api-backend-sqlite/package.json +8 -8
  120. package/templates/apps/api-backend-storage/package.json +8 -8
  121. package/templates/apps/api-cms/package.json +9 -9
  122. package/templates/apps/api-collab/README.md +3 -3
  123. package/templates/apps/api-collab/app.config.ts +1 -1
  124. package/templates/apps/api-collab/database/schema.ts +12 -8
  125. package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
  126. package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
  127. package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
  128. package/templates/apps/api-collab/package.json +8 -8
  129. package/templates/apps/api-collab/template.json +1 -1
  130. package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
  131. package/templates/apps/api-data-advanced/package.json +8 -8
  132. package/templates/apps/api-durable/package.json +8 -8
  133. package/templates/apps/api-feature-flags/package.json +9 -9
  134. package/templates/apps/api-governance/package.json +8 -8
  135. package/templates/apps/api-kv/package.json +8 -8
  136. package/templates/apps/api-moderation/package.json +8 -8
  137. package/templates/apps/api-observability/package.json +8 -8
  138. package/templates/apps/api-ratelimit/package.json +8 -8
  139. package/templates/apps/api-rbac/package.json +8 -8
  140. package/templates/apps/api-rest/package.json +7 -7
  141. package/templates/apps/api-row-history/package.json +8 -8
  142. package/templates/apps/api-saas/package.json +11 -10
  143. package/templates/apps/api-saas-starter/package.json +10 -10
  144. package/templates/apps/api-search/package.json +8 -8
  145. package/templates/apps/api-status/package.json +8 -8
  146. package/templates/apps/api-webhooks/package.json +9 -9
  147. package/templates/apps/changelog/app.config.ts +26 -2
  148. package/templates/apps/changelog/content/releases/{0.1.0.mdx → v0-1-0.mdx} +0 -1
  149. package/templates/apps/changelog/content/releases/{0.2.0.mdx → v0-2-0.mdx} +0 -1
  150. package/templates/apps/changelog/package.json +9 -8
  151. package/templates/apps/changelog/src/collections/releases.collection.ts +48 -0
  152. package/templates/apps/changelog/src/globals.d.ts +1 -1
  153. package/templates/apps/changelog/src/locales/de.ts +1 -1
  154. package/templates/apps/changelog/src/locales/en.ts +1 -1
  155. package/templates/apps/changelog/src/pages/[locale]/[slug]/page.tsx +7 -4
  156. package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +13 -41
  157. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +26 -51
  158. package/templates/apps/changelog/src/pages/[slug]/page.tsx +21 -19
  159. package/templates/apps/changelog/src/pages/page.test.tsx +14 -37
  160. package/templates/apps/changelog/src/pages/page.tsx +18 -12
  161. package/templates/apps/edge-functions/package.json +2 -2
  162. package/templates/apps/frontend-admin/package.json +8 -8
  163. package/templates/apps/frontend-app/package.json +9 -9
  164. package/templates/apps/frontend-auth/package.json +8 -8
  165. package/templates/apps/frontend-blank/package.json +7 -7
  166. package/templates/apps/frontend-cms/package.json +9 -9
  167. package/templates/apps/frontend-collab/README.md +43 -24
  168. package/templates/apps/frontend-collab/app.config.ts +3 -3
  169. package/templates/apps/frontend-collab/package.json +14 -10
  170. package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
  171. package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
  172. package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
  173. package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
  174. package/templates/apps/frontend-collab/template.json +2 -2
  175. package/templates/apps/frontend-contact/package.json +7 -7
  176. package/templates/apps/frontend-dashboard/package.json +7 -7
  177. package/templates/apps/frontend-docs/content/docs/de/guides/first-page.md +4 -0
  178. package/templates/apps/frontend-docs/content/docs/de/intro/getting-started.md +4 -0
  179. package/templates/apps/frontend-docs/content/docs/en/guides/first-page.md +4 -0
  180. package/templates/apps/frontend-docs/content/docs/en/intro/getting-started.md +4 -0
  181. package/templates/apps/frontend-docs/package.json +9 -6
  182. package/templates/apps/frontend-docs/src/collections/docs.collection.ts +21 -0
  183. package/templates/apps/frontend-docs/src/locales/de.ts +0 -5
  184. package/templates/apps/frontend-docs/src/locales/en.ts +0 -5
  185. package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug]/page.tsx +17 -9
  186. package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +10 -5
  187. package/templates/apps/frontend-docs/src/pages/[locale]/page.tsx +8 -0
  188. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.test.tsx +53 -15
  189. package/templates/apps/frontend-docs/src/pages/docs/[...slug]/page.tsx +27 -33
  190. package/templates/apps/frontend-docs/src/pages/page.test.tsx +17 -3
  191. package/templates/apps/frontend-docs/src/pages/page.tsx +16 -12
  192. package/templates/apps/frontend-i18n/package.json +6 -6
  193. package/templates/apps/frontend-landing/README.md +48 -0
  194. package/templates/apps/frontend-landing/app.config.ts +28 -0
  195. package/templates/apps/frontend-landing/package.json +7 -6
  196. package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
  197. package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
  198. package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
  199. package/templates/apps/frontend-landing/src/globals.css +15 -0
  200. package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
  201. package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
  202. package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
  203. package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
  204. package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
  205. package/templates/apps/frontend-landing/template.json +2 -2
  206. package/templates/apps/frontend-portal/package.json +8 -8
  207. package/templates/apps/frontend-saas/package.json +8 -8
  208. package/templates/apps/frontend-spa/package.json +7 -7
  209. package/templates/apps/frontend-ssr/package.json +7 -7
  210. package/templates/apps/frontend-ssr-api/package.json +8 -8
  211. package/templates/apps/frontend-static-blog/content/posts/cms-to-ssg.md +12 -0
  212. package/templates/apps/frontend-static-blog/content/posts/hello-static.md +12 -0
  213. package/templates/apps/frontend-static-blog/content/posts/islands-not-hydration.md +14 -0
  214. package/templates/apps/frontend-static-blog/package.json +9 -6
  215. package/templates/apps/frontend-static-blog/src/collections/posts.collection.ts +27 -0
  216. package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug]/page.tsx +7 -4
  217. package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +7 -1
  218. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.test.tsx +19 -9
  219. package/templates/apps/frontend-static-blog/src/pages/blog/[slug]/page.tsx +19 -21
  220. package/templates/apps/frontend-static-blog/src/pages/page.test.tsx +18 -5
  221. package/templates/apps/frontend-static-blog/src/pages/page.tsx +21 -14
  222. package/templates/apps/frontend-status/package.json +8 -8
  223. package/templates/apps/mobile-app/package.json +12 -11
  224. package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
  225. package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
  226. package/dist/agentsMd-SDDSkyl4.js +0 -2
  227. package/dist/apiBuild-BYBpL7Pz.js +0 -2
  228. package/dist/build-CPgcMQug.js +0 -793
  229. package/dist/codegen-CctkDO-1.js +0 -2
  230. package/dist/codegenCommand-DCdG2JN-.js +0 -137
  231. package/dist/dbCommand-DNb6yeOG.js +0 -2
  232. package/dist/doctorCommand-CqoWA2p5.js +0 -2
  233. package/dist/fileConventions-DOqD3lPS.js +0 -34
  234. package/dist/frameworkTableAssembly-C6ETawPR.js +0 -2
  235. package/dist/inspect-CuGDYES0.js +0 -2
  236. package/dist/manifestBuild-CPjhvM62.js +0 -2
  237. package/dist/renderModeScan-CcH2X1_D.js +0 -120
  238. package/dist/serveCommand-DLc-BznW.js +0 -2
  239. package/dist/start-DfL3fOiN.js +0 -3
  240. package/dist/updateCommand-5gFVfK5q.js +0 -2
  241. package/dist/webDev-CZbTsDcH.js +0 -2
  242. package/templates/apps/changelog/scripts/generate-rss.mjs +0 -38
  243. package/templates/apps/changelog/src/lib/releases.ts +0 -21
  244. package/templates/apps/frontend-static-blog/src/content/posts.ts +0 -64
@@ -379,7 +379,7 @@ Emits `CONSTRAINT <name> UNIQUE (col1, col2, ...)` inline in CREATE
379
379
  TABLE on every dialect. Standard SQL.
380
380
 
381
381
  This is what backs `ctx.store.upsert(..., { conflictColumns: ['a', 'b'] })`
382
- — see [Bulk operations](/docs/database/bulk-operations#upsert).
382
+ — see [Bulk operations](/docs/database/bulk-operations#upsert-insert-or-update-on-conflict).
383
383
 
384
384
  ## GiST indexes (PostGIS spatial)
385
385
 
@@ -627,7 +627,7 @@ await ctx.store.update('notes', id, {
627
627
 
628
628
  Two layers of validation apply:
629
629
 
630
- 1. **JSON validity** — that the stored bytes are well-formed JSON — is enforced automatically by the database on every dialect (see [Storage + validation per dialect](#storage--validation-per-dialect)). You don't declare anything.
630
+ 1. **JSON validity** — that the stored bytes are well-formed JSON — is enforced automatically by the database on every dialect (see [Storage + validation per dialect](#storage-validation-per-dialect)). You don't declare anything.
631
631
  2. **JSON *shape*** — that the value matches your expected structure — is up to you: enforce it at the table level with `table().validate(Schema)`:
632
632
 
633
633
  ```ts
@@ -689,7 +689,7 @@ mssql, sqlite 3.8+).
689
689
  depend on?"
690
690
 
691
691
  When you don't have a recursive structure, plain
692
- [`withCte()`](/docs/database/query-builder#ctes) is enough.
692
+ [`withCte()`](/docs/database/query-builder#ctes-common-table-expressions) is enough.
693
693
 
694
694
  ## Shape
695
695
 
@@ -820,7 +820,7 @@ eager-load with `.with({...})` to get the per-field pre-filter.
820
820
 
821
821
  ## See also
822
822
 
823
- - [Plain CTEs](/docs/database/query-builder#ctes) — `withCte()` for
823
+ - [Plain CTEs](/docs/database/query-builder#ctes-common-table-expressions) — `withCte()` for
824
824
  non-recursive named sub-queries
825
825
  - [Self-joins](/docs/database/self-joins) — for single-level
826
826
  parent/child queries
@@ -830,7 +830,7 @@ eager-load with `.with({...})` to get the per-field pre-filter.
830
830
  `unionAll`, the mechanism a recursive CTE is built on
831
831
  - [Joins](/docs/database/joins) — relation-based traversal when the
832
832
  graph depth is fixed (e.g. parent + immediate children)
833
- - [Aggregations](/docs/database/query-builder#aggregations) —
833
+ - [Aggregations](/docs/database/query-builder#aggregates) —
834
834
  COUNT/SUM/AVG over a recursive CTE's result set
835
835
 
836
836
 
@@ -940,7 +940,7 @@ keep the branches' result sets bounded.
940
940
  the column-level "in A but not in B" case
941
941
  - [Aggregations](/docs/database/aggregations) — `count()` etc. on
942
942
  a set-op result is a common pattern
943
- - [CTEs](/docs/database/query-builder#ctes) — name a complex set-op
943
+ - [CTEs](/docs/database/query-builder#ctes-common-table-expressions) — name a complex set-op
944
944
  result so you can reference it in a larger query
945
945
 
946
946
 
@@ -1044,7 +1044,7 @@ Non-correlated only. The inner query can NOT reference outer-row
1044
1044
  columns like `WHERE inner.userId = users.id`. For correlated
1045
1045
  sub-queries (a common shape: "user who has at least one post created
1046
1046
  in the last hour") use a [Self-join](/docs/database/self-joins) or
1047
- an [Eager-load](/docs/database/joins#eager-loading) — both can
1047
+ an [Eager-load](/docs/database/joins#eager-loading-via-with-spec) — both can
1048
1048
  express the same query without the correlation reference.
1049
1049
 
1050
1050
  ## Reactivity
@@ -1069,7 +1069,7 @@ every dialect we ship. No per-dialect dispatch.
1069
1069
  with `count()` etc. for "count of X where Y belongs to Z"
1070
1070
  - [Self-joins](/docs/database/self-joins) — when the relationship
1071
1071
  can be expressed as a join instead
1072
- - [CTEs](/docs/database/query-builder#ctes) — for naming a
1072
+ - [CTEs](/docs/database/query-builder#ctes-common-table-expressions) — for naming a
1073
1073
  sub-query you reuse multiple times in the same outer query
1074
1074
 
1075
1075
 
@@ -350,7 +350,7 @@ on it explicitly via `.expressionIndex(name, [...], { ... })`.
350
350
 
351
351
  - [Columns](/docs/database/columns) — `.computed(row => ...)` and
352
352
  `.default(() => ...)` for the app-side variants
353
- - [Indexes](/docs/database/indexes#expression) — `.expressionIndex()`
353
+ - [Indexes](/docs/database/indexes#expression-indexes) — `.expressionIndex()`
354
354
  for indexing a generated column
355
355
  - [Full-text search](/docs/database/full-text-search) — the FTS
356
356
  pattern uses STORED tsvector generated columns
@@ -989,7 +989,7 @@ behavioural gaps are too large to paper over.
989
989
  ## See also
990
990
 
991
991
  - [Columns](/docs/database/columns) — the regular schema-DSL types
992
- - [Expression indexes](/docs/database/indexes#expression) — the
992
+ - [Expression indexes](/docs/database/indexes#expression-indexes) — the
993
993
  framework's index API (`kind: 'gist' | 'gin'` on postgres)
994
994
  - [PostGIS docs](https://postgis.net/docs/) — the official manual,
995
995
  authoritative for every spatial function the framework re-exports
@@ -700,7 +700,7 @@ A handler that a `voltro dev` session or a test actually ran is reported with wh
700
700
 
701
701
  ### What the codemod does per kind
702
702
 
703
- - **`rename-column`** gets a real `transform`: it renames the field in the `*.entity.ts` AND chains **`.renamedFrom('old')`** (so the differ plans a catalog RENAME, not the lossy drop+create described [above](#renamedfromoldname)), then annotates the handler sites the blast radius found.
703
+ - **`rename-column`** gets a real `transform`: it renames the field in the `*.entity.ts` AND chains **`.renamedFrom('old')`** (so the differ plans a catalog RENAME, not the lossy drop+create described [above](#renamedfrom-oldname)), then annotates the handler sites the blast radius found.
704
704
  - **`retype-column` / `split-column` / `drop-column` / `rename-table`** are reshaping changes with no single mechanical rewrite, so they get a **`manual`** codemod: a generated, numbered checklist of the edits + the annotation to add, printed for you to apply.
705
705
 
706
706
  `voltro evolve` produces the plan; it does not apply the schema change. **`voltro check` is the gate on the result**, and `voltro db apply` lands it — after `--write`, review the annotated handlers, then run those two.
@@ -309,7 +309,7 @@ The query builder has dedicated pages for the deeper topics:
309
309
  - **[Self-joins](/docs/database/self-joins)** — `.as(alias)` +
310
310
  `.innerJoin(table, alias, on)` + `.selectJoined({...})` for parent/
311
311
  child trees and CTE references.
312
- - **[CTEs](/docs/database/query-builder#ctes)** — `.withCte(name, sub)`
312
+ - **[CTEs](/docs/database/query-builder#ctes-common-table-expressions)** — `.withCte(name, sub)`
313
313
  for named sub-queries reusable inside the outer SELECT.
314
314
  - **[Recursive CTEs](/docs/database/recursive-cte)** — `.recursiveCte`
315
315
  for tree walks (org hierarchy, comment threads, file folders).
@@ -445,7 +445,7 @@ framework runs on the post-commit change channel.
445
445
  **Reach for `reference()` first.** A real foreign key across a plugin boundary
446
446
  works and survives the plugin renaming its table, because `reference()` takes
447
447
  the table as a VALUE — see
448
- [plugins/overview](/docs/plugins/overview#pointing-your-table-at-a-plugins-row).
448
+ [plugins/overview](/docs/plugins/overview).
449
449
  `pluginRef` is for the case where you have deliberately chosen NOT to have a
450
450
  key: it enforces nothing at the database level.
451
451
 
@@ -391,7 +391,7 @@ What the framework hides for you vs what's worth knowing. Per-dialect pages dril
391
391
  | `RETURNING *` on DELETE | yes | no | yes (10.0+) | OUTPUT DELETED.* | yes | yes |
392
392
  | Parameterized `LIMIT ?` | yes | no — integer-literal inlined | yes | no — integer-literal inlined | yes | yes |
393
393
  | `LIMIT N OFFSET N` syntax | yes | yes | yes | no — `OFFSET … ROWS FETCH NEXT … ROWS ONLY` | yes | yes |
394
- | DEFAULT on TEXT columns | yes | **NO** — auto-uses VARCHAR(255) | yes | yes (NVARCHAR(MAX)) | yes | yes |
394
+ | DEFAULT on TEXT columns | yes | **NO** — framework emits VARCHAR(255) | yes — framework still emits VARCHAR(255) (engine parity) | yes — framework emits NVARCHAR(450) (indexable) | yes | yes |
395
395
  | Native JSON column type | JSONB | JSON | JSON | NVARCHAR(MAX) | TEXT | TEXT |
396
396
  | JSON columns returned as objects | yes | yes | yes | **no — strings** — framework auto-parses | **no — strings** — framework auto-parses | **no — strings** — framework auto-parses |
397
397
  | Booleans | proper booleans | 0/1 (TINYINT) | 0/1 | BIT (proper bool) | 0/1 (INTEGER) | 0/1 (INTEGER) |
@@ -796,11 +796,48 @@ The framework's DDL emitter detects the case and switches to `VARCHAR(255)`:
796
796
  ```typescript
797
797
  text().default('json') // → VARCHAR(255) DEFAULT 'json'
798
798
  text().oneOf(['a', 'b', 'c']).default('a') // → VARCHAR(255) DEFAULT 'a' CHECK (col IN ('a','b','c'))
799
- text().nullable() // → TEXT (unchanged — no default to trip up)
799
+ text().nullable() // → LONGTEXT (unchanged — no default to trip up)
800
800
  ```
801
801
 
802
802
  VARCHAR(255) is the framework's heuristic — enough for typical enum-like values, short status strings, format identifiers. If you need longer defaulted text, declare the column as `text().nullable()` + handle the missing-default case in application code, OR drop down to `unsafe()`.
803
803
 
804
+ ### Adding a default to an existing column
805
+
806
+ The rule holds for a migration too, not only for `CREATE TABLE`. Adding
807
+ `.default(…)` to a `text()` column that already exists **reshapes** the column
808
+ rather than setting a default on it:
809
+
810
+ ```sql
811
+ ALTER TABLE tickets MODIFY COLUMN `status` VARCHAR(255) NOT NULL DEFAULT 'active'
812
+ ```
813
+
814
+ That is deliberate, and it is what makes the change appliable at all: a plain
815
+ `ALTER TABLE … ALTER COLUMN status SET DEFAULT 'active'` is answered by MySQL
816
+ with `BLOB, TEXT, GEOMETRY or JSON column 'status' can't have a default value`,
817
+ so the migration would stop half-applied. Reshaping means the column has the same
818
+ type whether the default was declared before or after the table existed.
819
+
820
+ Two consequences worth knowing before you run it:
821
+
822
+ - **It is a narrowing.** If a row already holds more than 255 characters, the
823
+ ALTER fails (`Data too long for column 'status'`) and the migration stops
824
+ before it. Check first, and pick the width yourself with
825
+ `text().maxLength(n).default(…)` if 255 is too small:
826
+
827
+ ```sql
828
+ SELECT COUNT(*) FROM tickets WHERE CHAR_LENGTH(status) > 255
829
+ ```
830
+
831
+ - **Removing a default does not reshape back.** `DROP DEFAULT` is legal on any
832
+ mysql type, and widening a `VARCHAR(255)` back to `LONGTEXT` would fail for an
833
+ indexed column — so the column keeps its bounded type. Declare
834
+ `text().maxLength(255)` if you want that to be visible in the schema.
835
+
836
+ SQL Server does the same thing for its own reason (an `NVARCHAR(MAX)` column
837
+ cannot be indexed, so a defaulted text column is `NVARCHAR(450)`). On postgres a
838
+ `TEXT` column takes a `DEFAULT` directly, and SQLite rebuilds the table to the
839
+ declared shape — neither reshapes anything.
840
+
804
841
  ## JSON columns
805
842
 
806
843
  `json()` columns emit `JSON` (mysql's native binary JSON type since 5.7+). The driver auto-parses on read; same shape as postgres. No coercion overhead.
@@ -1055,7 +1092,7 @@ MariaDB's GTID format differs from MySQL's: `0-1-100` (domain-server-sequence) v
1055
1092
  - **`mariadb` schema package is wire-compatible with `mysql`**. If you migrate from MySQL → MariaDB, the framework re-emits DDL cleanly via `applySchema(..., 'mariadb')`. Production data round-trips through `mysqldump` without translation.
1056
1093
  - **`sql_mode=NO_BACKSLASH_ESCAPES`** is sometimes set on MariaDB deploys. The framework's identifier escaping handles it, but user-written `unsafe()` strings that hand-escape backslashes may produce wrong output. Leave that mode off if you can.
1057
1094
  - **Sequence-based ID columns**. MariaDB has true CREATE SEQUENCE; the framework doesn't use it (TypeID / ULID / Snowflake are client-side). If you reach for sequences for legacy reasons, they're outside the framework's auto-injection path.
1058
- - **Hand-rolled `AUTO_INCREMENT` primary keys** work the same as on mysql: an `insert` / `insertMany` with no client-side `id` recovers the DB-generated id via `LAST_INSERT_ID()` (connection-pinned; `insertMany` recovers the whole consecutive range). See the [mysql page](/docs/database/dialects/mysql#auto_increment-ids--last_insert_id-recovery) for the worked example — the recovery path is identical on both engines.
1095
+ - **Hand-rolled `AUTO_INCREMENT` primary keys** work the same as on mysql: an `insert` / `insertMany` with no client-side `id` recovers the DB-generated id via `LAST_INSERT_ID()` (connection-pinned; `insertMany` recovers the whole consecutive range). See the [mysql page](/docs/database/dialects/mysql) for the worked example — the recovery path is identical on both engines.
1059
1096
 
1060
1097
  ## Where it lives
1061
1098
 
@@ -1187,6 +1224,31 @@ When the caller didn't supply an `orderBy` but did set `skip` (uncommon but lega
1187
1224
 
1188
1225
  The compiler inlines integer literals for TOP/OFFSET/FETCH NEXT values rather than parameter binding. Same rationale as MySQL — tedious has bind-as-INT issues with large or unexpected-typed numeric params.
1189
1226
 
1227
+ ## Text columns with a DEFAULT — NVARCHAR(450)
1228
+
1229
+ A plain `text()` column is `NVARCHAR(MAX)`, which SQL Server cannot index. A text
1230
+ column that carries a literal default or a closed value set is therefore emitted
1231
+ bounded, at `NVARCHAR(450)` — under the 900-byte single-column key limit, so it
1232
+ stays indexable:
1233
+
1234
+ ```typescript
1235
+ text().default('open') // → NVARCHAR(450) + a DEFAULT constraint
1236
+ text().oneOf(['open', 'closed']) // → NVARCHAR(450) + a CHECK constraint
1237
+ text().nullable() // → NVARCHAR(MAX)
1238
+ ```
1239
+
1240
+ This holds for migrations as well as for `CREATE TABLE`: adding `.default(…)` to
1241
+ an existing text column retypes it to `NVARCHAR(450)` and then adds the default
1242
+ constraint, so the column has the same type whether the default was declared
1243
+ before or after the table existed. It is a narrowing — if a row already holds
1244
+ more than 450 characters, the `ALTER COLUMN` fails ("String or binary data would
1245
+ be truncated") and the migration stops there. Check first, and use
1246
+ `text().maxLength(n).default(…)` to choose a different width:
1247
+
1248
+ ```sql
1249
+ SELECT COUNT(*) FROM tickets WHERE LEN(status) > 450
1250
+ ```
1251
+
1190
1252
  ## JSON columns — NVARCHAR(MAX) + auto-parse
1191
1253
 
1192
1254
  `json()` columns emit `NVARCHAR(MAX)` in DDL — mssql has no native JSON type pre-2025. Validation goes through `ISJSON(col) = 1` CHECK constraints; serialization is application-side.
@@ -261,7 +261,7 @@ await ctx.store.upsert('orgSlugs', {
261
261
  ```
262
262
 
263
263
  Requires a composite UNIQUE constraint on the table — declare it via
264
- `.unique([cols])` in the schema (see [Indexes](/docs/database/indexes#composite-unique)).
264
+ `.unique([cols])` in the schema (see [Indexes](/docs/database/indexes#composite-unique-constraints)).
265
265
 
266
266
  ## `insertIgnore` — keep existing on conflict
267
267
 
@@ -314,7 +314,7 @@ const count = await ctx.store.updateMany('posts', { hidden: true }, {
314
314
  // count: number of rows actually updated
315
315
  ```
316
316
 
317
- The `where` predicate is a regular [Predicate](/docs/database/query-builder#predicates)
317
+ The `where` predicate is a regular [Predicate](/docs/database/query-builder)
318
318
  AST — same shape `.where()` uses. Sub-queries via `inSubquery` /
319
319
  `exists` are supported.
320
320
 
@@ -395,5 +395,5 @@ matters.
395
395
  `aggregate()` for read-side bulk reads
396
396
  - [Sub-queries](/docs/database/sub-queries) — `inSubquery` /
397
397
  `exists` in `updateMany` `where:` clauses
398
- - [Composite UNIQUE](/docs/database/indexes#composite-unique) — for
398
+ - [Composite UNIQUE](/docs/database/indexes#composite-unique-constraints) — for
399
399
  the constraint that backs `upsert`'s `conflictColumns: ['a', 'b']`
@@ -1119,6 +1119,14 @@ VOLTRO_LOG_FORMAT=json
1119
1119
  VOLTRO_LOG_LEVEL=info
1120
1120
  ```
1121
1121
 
1122
+ In `json` mode (the default off a TTY, so a pod gets it without configuration)
1123
+ EVERY line the framework emits is a parseable record — the boot banner, the app
1124
+ surface, `voltro db apply`'s plan summary, refusal detail, and every subsystem
1125
+ logger (schedule, broadcast, workflow, flow-control). A hand-formatted table or
1126
+ a `[tag]`-prefixed adapter line between JSON records is a bug, not a style: log
1127
+ collectors show it as unparsed noise. On a TTY the same surfaces render as the
1128
+ human-readable banners and tables.
1129
+
1122
1130
  **Error reporting** — add `sentryPlugin()` from `@voltro/plugin-sentry`. It stays inert until `SENTRY_DSN` is set, and reported errors correlate to the request `traceId`:
1123
1131
 
1124
1132
  ```ts
@@ -78,6 +78,8 @@ Server-side, the active locale is determined by, in priority order:
78
78
  2. **`Accept-Language` header** — the browser/OS preference, q-weighted and sorted per RFC 4647.
79
79
  3. **`defaultLocale`** — last-resort fallback.
80
80
 
81
+ **One resolver decides, and everything the server renders for that request reads its answer** — the page render and its `<I18nProvider>`, the `<html lang>` attribute, the ISR cache key (so a language switch cannot re-serve the previous locale's cached HTML), and the validation errors an [`<AutoForm>` renders on the no-JavaScript form-POST path](/docs/ui/forms-and-tables#forms-without-javascript). That last one is worth naming because a server has no `<html lang>` to read yet at the time it validates; deriving the locale a second way there would answer `en` for every request.
82
+
81
83
  The resolved locale is **guaranteed** to be one of the codes in `locales`. Any unsupported value (a cookie pointing at a code you no longer ship, a browser asking for `xx-YY`) falls through to the next signal. RFC 4647 lookup strips subtags one segment at a time — `de-CH-1996` → `de-CH` → `de` — so a `de` catalog serves a `de-CH` browser.
82
84
 
83
85
  The client **adopts what the server resolved**, reading it from the `<html lang>` attribute the server render sets, then falling back to the cookie and the default. `Accept-Language` is never read in the browser: `navigator.languages` can diverge from what the server saw.
@@ -96,7 +98,7 @@ The client **adopts what the server resolved**, reading it from the `<html lang>
96
98
  | `data-voltro-tz` | the IANA zone every date/time formatter renders in — set `timeZone` in `app.config.ts` |
97
99
  | `data-voltro-now` | the server's render instant, so `useRelativeTime` produces the same string in the hydration pass |
98
100
 
99
- Locale was already agreed; the zone and the clock were each read from the ambient runtime, which meant a server-rendered timestamp was a hydration mismatch waiting for a wide enough offset or a slow enough connection. See [Plurals & formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr--the-setting-that-is-not-a-preference) — that is the page to read before you migrate hand-rolled `toLocaleString()` calls onto the hooks.
101
+ Locale was already agreed; the zone and the clock were each read from the ambient runtime, which meant a server-rendered timestamp was a hydration mismatch waiting for a wide enough offset or a slow enough connection. See [Plurals & formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr-the-setting-that-is-not-a-preference) — that is the page to read before you migrate hand-rolled `toLocaleString()` calls onto the hooks.
100
102
 
101
103
  See [Catalogs](/docs/i18n/catalogs) for the type-safe catalog convention and the component hooks, [Plurals & formatting](/docs/i18n/formatting) for CLDR plural selection and the `Intl`-backed date / number / relative-time hooks, and [URL strategies](/docs/i18n/url-strategies) for cookie-only vs URL-prefix routing.
102
104
 
@@ -951,7 +953,7 @@ default, so call sites don't handle a missing-service error.
951
953
  > request, publishes it on the document, and every `@voltro/i18n` formatter on
952
954
  > both sides of the hydration boundary uses it. Read it with `useTimeZone()`
953
955
  > from `@voltro/i18n`. See
954
- > [Formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr--the-setting-that-is-not-a-preference).
956
+ > [Formatting → Timezones under SSR](/docs/i18n/formatting#timezones-under-ssr-the-setting-that-is-not-a-preference).
955
957
  >
956
958
  > The **server-side compute zone** — what `startOfDay` or a workflow's "same
957
959
  > time tomorrow" resolves against inside a handler — is still the SEAM only.
@@ -505,7 +505,7 @@ export default defineWebSocket({
505
505
  })
506
506
  ```
507
507
 
508
- `auth: 'subject'` authenticates through the same chain as rpc/SSR **before** the upgrade (401 while it is still http) and binds the connection to the credential's expiry (close code `4001`); every gateway path is origin-checked at upgrade. Two gateways on one path refuse the boot; a plain GET on a gateway path answers `426`. App realtime stays [subscriptions](/docs/data/subscriptions) — full detail under [Raw WebSocket gateways](/docs/data/subscriptions#raw-websocket-gateways--definewebsocket).
508
+ `auth: 'subject'` authenticates through the same chain as rpc/SSR **before** the upgrade (401 while it is still http) and binds the connection to the credential's expiry (close code `4001`); every gateway path is origin-checked at upgrade. Two gateways on one path refuse the boot; a plain GET on a gateway path answers `426`. App realtime stays [subscriptions](/docs/data/subscriptions) — full detail under [Raw WebSocket gateways](/docs/data/subscriptions#raw-websocket-gateways-definewebsocket).
509
509
 
510
510
  ## The web side (`apps/*/web/`)
511
511
 
@@ -518,6 +518,10 @@ export default defineWebSocket({
518
518
  | `src/pages/loading.tsx` | Pending UI shown while loaders resolve. |
519
519
  | `src/pages/(group)/` | Route group — does not contribute a URL segment, but layout/error files inside still apply. |
520
520
  | `src/*.island.tsx` | A client-side hydration island (split chunk). Used in `interactive: 'islands'` pages. |
521
+ | `src/**/*.collection.ts` | A content collection declaration (`defineCollection`) — schema-typed markdown/JSON under `content/<name>/**`. See [Content collections](/docs/data/content-collections). |
522
+ | `src/**/*.consumer.ts` | A queue consumer (`defineQueueConsumer`, @voltro/plugin-queue) — Schema-decoded, at-least-once, serial per partition. See [Queue](/docs/plugins/queue). |
523
+ | `grpc.manifest.json` | The gRPC field-number manifest (checked in — append-only wire identity; deletes go `reserved`). See [gRPC surface](/docs/data/grpc). |
524
+ | `content/<name>/**` | A collection's content files (markdown with frontmatter, or `.json` for data collections). Read by `getCollection`/`getEntry`. |
521
525
 
522
526
  Each page can opt into a render strategy via two exports:
523
527
 
@@ -540,6 +544,16 @@ export const searchParams = Schema.Struct({
540
544
 
541
545
  - `searchParams` (an `effect/Schema` struct — every field optional or with a default) types the page's query string: `useSearchParams(searchParams)` returns the decoded shape, and links built with `withQuery` type-check against it. Details: [Pages → Query strings](/docs/routing/pages#query-strings).
542
546
 
547
+ Two more page exports change what the framework produces for a route:
548
+
549
+ ```tsx
550
+ export const ogImage = ({ params, loaderData, locale }) => ({ type: 'div', props: { /* satori JSX */ } })
551
+ export const intercept = { from: '/photos' }
552
+ ```
553
+
554
+ - `ogImage` declares the page's `og:image` as a satori JSX template. `static` pages render the PNG at BUILD time into `dist/assets/og/`; `ssr` pages render it on demand over a signed route. The `og:image` / `twitter:image` / `twitter:card` tags are injected automatically unless your own `meta` already sets them. A declared font is REQUIRED (there is no bundled default), and an `ssr` route exporting it needs `VOLTRO_OG_SECRET` — `voltro start` refuses the boot otherwise. Details: [Loaders and meta → OG images](/docs/routing/loaders-and-meta#og-images-from-a-template-ogimage).
555
+ - `intercept` makes the page an **intercepting route**: `from` names one or more ROUTE PATTERNS (`'/photos'`, `['/photos', '/albums/[id]']`), and a soft navigation arriving from one of them renders this page as an overlay above the still-mounted origin. Every hard load — and a soft navigation from anywhere else — renders it standalone. Details: [Intercepting routes](/docs/routing/intercepting-routes).
556
+
543
557
  ## Discovery in practice
544
558
 
545
559
  ```text
@@ -599,11 +613,28 @@ If yes, the promise belongs in the name — you cannot see a contract before you
599
613
  | `*.tracking.ts` | analytics happens nowhere else | `tracking/outside-tracking-file` |
600
614
  | `*.client.ts` | it and its imports are browser-safe | boot-time import walk, `client/not-browser-safe` |
601
615
  | `*.store.ts` | exactly one `defineStore`, no server state | `store/one-per-file`, `store/mirrors-server-state` |
616
+ | `*.collection.ts` | declares content collections (`defineCollection`); frontmatter schema violations fail the build naming the file | the build's collection decode + reference validation |
617
+ | `*.consumer.ts` | declares queue consumers (`defineQueueConsumer`, @voltro/plugin-queue); loading registers, the plugin's activation starts them | the queue runner (decode→DLQ, retry→DLQ, commit-per-message) |
618
+ | `*.ws.ts` | default-exports one raw WebSocket gateway (`defineWebSocket`), mounting its own upgrade path beside the rpc socket | boot discovery on BOTH paths (`voltro dev` and `voltro serve`); two gateways on one path refuse the boot |
602
619
 
603
620
  A `*.component.tsx` promises exactly ONE component. It does not promise to export nothing else: types, and plain module-local values a `const COLUMNS = […]` beside the table that renders them, are fine and always were. What the rule counts is components — a declaration that renders — so an object, an array, a string or a `new` beside your component is not a second one, and neither is `export default Card` next to `export const Card`.
604
621
 
605
622
  The BOUNDARY rules (`internal/foreign-import`, `fixture/production-import`, `ui/unlinked`) are assertions about your import graph, so it is worth knowing which edges they follow: relative specifiers, your tsconfig `paths` aliases (read from the nearest `tsconfig.json`, so a per-app `@/*` works when you run `voltro doctor` at the repo root), `export … from` re-exports, and dynamic `import()`. A package import is a leaf — the walk stops at the edge of your app.
606
623
 
624
+ ## Contracts that are not suffixes
625
+
626
+ The admission test above is about the PROMISE, not about the spelling — and three of the framework's conventions carry one without being a suffix on a filename. They are listed here because a reader looking for "what does the framework read out of my tree" would otherwise stop at the table:
627
+
628
+ | Convention | Promise | Read by |
629
+ |---|---|---|
630
+ | `searchParams` page export | the page's query string decodes through this `effect/Schema` struct — every field optional or with a default | `useSearchParams(searchParams)`, `withQuery` link typing, and the render-mode scan (a page declaring BOTH `renderMode: 'isr'` and `searchParams` is refused) |
631
+ | `ogImage` page export | this route's `og:image` is a satori JSX template, not a file you ship | the build (`static` → a hashed PNG in `dist/assets/og/`) and `voltro start` (`ssr` → a signed on-demand route, which needs `VOLTRO_OG_SECRET`) |
632
+ | `intercept` page export | `from` names the routes a soft navigation may arrive from for this page to render as an overlay above them | the client router; a hard load renders the page standalone regardless |
633
+ | `grpc.manifest.json` (app root) | field numbers are checked in and append-only — a deleted field goes `reserved`, never re-used | `voltro grpc proto` and the gRPC surface wiring, which derive wire identity from it rather than from declaration order |
634
+ | `content/<name>/**` | the files a `*.collection.ts` declares — markdown with frontmatter, or `.json` for a data collection | `getCollection` / `getEntry`, the build's collection artifacts, and the dev server's watcher |
635
+
636
+ The page exports are per-ROUTE and the last two are per-APP, which is the only reason they cannot be spellings: there is nothing to rename.
637
+
607
638
  ## `*.component.ui.tsx` — reads, never writes
608
639
 
609
640
  ```tsx