fimo 0.8.0 → 0.9.0-experimental.10

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 (218) hide show
  1. package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +66 -0
  2. package/assets/agents-starter/claude/settings.json +19 -0
  3. package/assets/content-templates/hooks-template.eta +4 -2
  4. package/assets/content-templates/types-template.eta +67 -1
  5. package/assets/skills/fimo/SKILL.md +1 -1
  6. package/assets/skills/fimo/references/content.md +86 -15
  7. package/assets/skills/fimo/references/translations.md +75 -0
  8. package/assets/skills/fimo-cli/SKILL.md +6 -2
  9. package/assets/skills/fimo-cli/references/content.md +61 -2
  10. package/assets/skills/fimo-cli/references/integrations/firecrawl.md +20 -4
  11. package/assets/skills/fimo-cli/references/integrations.md +5 -1
  12. package/assets/skills/fimo-cli/references/webhooks.md +162 -0
  13. package/assets/skills/fimo-migration/SKILL.md +24 -2
  14. package/assets/skills/fimo-migration/references/content.md +24 -0
  15. package/assets/skills/fimo-migration/references/manifest.md +46 -0
  16. package/assets/skills/fimo-migration/references/pages.md +234 -0
  17. package/assets/skills/fimo-migration/references/qa.md +24 -2
  18. package/assets/skills/fimo-migration/references/workflow.md +12 -1
  19. package/assets/skills/fimo-migration/references/wrap.md +662 -0
  20. package/assets/skills/fimo-studio/SKILL.md +5 -3
  21. package/assets/skills/fimo-studio/references/cms.md +9 -1
  22. package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
  23. package/assets/skills/fimo-studio/references/webhooks.md +42 -0
  24. package/dist/build/vite/plugins/translations.d.ts +2 -8
  25. package/dist/build/vite/plugins/translations.js +31 -131
  26. package/dist/cli/bundle.json +1 -1
  27. package/dist/cli/index.js +1337 -189
  28. package/dist/runtime/app/FimoProviders.d.ts +1 -11
  29. package/dist/runtime/app/FimoProviders.js +31 -30
  30. package/dist/runtime/astro/index.d.ts +2 -0
  31. package/dist/runtime/astro/index.js +3 -0
  32. package/dist/runtime/astro/server.d.ts +3 -0
  33. package/dist/runtime/astro/server.js +6 -0
  34. package/dist/runtime/content/client/internal.js +1 -0
  35. package/dist/runtime/content/client/types.d.ts +16 -0
  36. package/dist/runtime/core/BuiltWithBadge.d.ts +2 -0
  37. package/dist/runtime/core/BuiltWithBadge.js +68 -0
  38. package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
  39. package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
  40. package/dist/runtime/next/FimoNextProviders.js +20 -0
  41. package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
  42. package/dist/runtime/next/FimoNextScripts.js +19 -0
  43. package/dist/runtime/next/index.d.ts +14 -0
  44. package/dist/runtime/next/index.js +14 -0
  45. package/dist/runtime/next/server.d.ts +2 -0
  46. package/dist/runtime/next/server.js +4 -0
  47. package/dist/runtime/nuxt/composables.d.ts +9 -0
  48. package/dist/runtime/nuxt/composables.js +57 -0
  49. package/dist/runtime/nuxt/index.d.ts +3 -0
  50. package/dist/runtime/nuxt/index.js +6 -0
  51. package/dist/runtime/nuxt/server.d.ts +3 -0
  52. package/dist/runtime/nuxt/server.js +6 -0
  53. package/dist/runtime/primitives/components/RichText.d.ts +5 -1
  54. package/dist/runtime/primitives/components/RichText.js +2 -2
  55. package/dist/runtime/primitives/labels-hook.d.ts +33 -0
  56. package/dist/runtime/primitives/labels-hook.js +41 -0
  57. package/dist/runtime/primitives/labels-store.d.ts +12 -0
  58. package/dist/runtime/primitives/labels-store.js +60 -0
  59. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
  60. package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
  61. package/dist/runtime/primitives/translations.d.ts +1 -27
  62. package/dist/runtime/primitives/translations.js +7 -85
  63. package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
  64. package/dist/runtime/react/FimoSpaProviders.js +122 -0
  65. package/dist/runtime/react/index.d.ts +35 -0
  66. package/dist/runtime/react/index.js +40 -0
  67. package/dist/runtime/react/spa-content-cache.d.ts +38 -0
  68. package/dist/runtime/react/spa-content-cache.js +64 -0
  69. package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
  70. package/dist/runtime/remix/FimoRemixProviders.js +12 -0
  71. package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
  72. package/dist/runtime/remix/FimoRemixScripts.js +19 -0
  73. package/dist/runtime/remix/index.d.ts +12 -0
  74. package/dist/runtime/remix/index.js +11 -0
  75. package/dist/runtime/remix/server.d.ts +2 -0
  76. package/dist/runtime/remix/server.js +2 -0
  77. package/dist/runtime/seo/neutral.d.ts +22 -0
  78. package/dist/runtime/seo/neutral.js +17 -0
  79. package/dist/runtime/shared/display-routes.d.ts +14 -0
  80. package/dist/runtime/shared/display-routes.js +25 -0
  81. package/dist/runtime/shared/fimo-config.server.d.ts +0 -1
  82. package/dist/runtime/shared/fimo-config.server.js +1 -13
  83. package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
  84. package/dist/runtime/shared/i18n-messages.d.ts +29 -0
  85. package/dist/runtime/shared/i18n-messages.js +79 -0
  86. package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
  87. package/dist/runtime/shared/labels-fetch.pure.js +71 -0
  88. package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
  89. package/dist/runtime/shared/labels-fetch.server.js +97 -0
  90. package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
  91. package/dist/runtime/shared/labels-t.pure.js +10 -0
  92. package/dist/runtime/strapi/client.d.ts +30 -0
  93. package/dist/runtime/strapi/client.js +132 -0
  94. package/dist/runtime/strapi/index.d.ts +5 -0
  95. package/dist/runtime/strapi/index.js +6 -0
  96. package/dist/runtime/strapi/reshape.d.ts +50 -0
  97. package/dist/runtime/strapi/reshape.js +121 -0
  98. package/dist/runtime/svelte/index.d.ts +3 -0
  99. package/dist/runtime/svelte/index.js +7 -0
  100. package/dist/runtime/svelte/server.d.ts +3 -0
  101. package/dist/runtime/svelte/server.js +6 -0
  102. package/dist/runtime/vue/index.d.ts +3 -0
  103. package/dist/runtime/vue/index.js +5 -0
  104. package/dist/runtime/vue/server.d.ts +3 -0
  105. package/dist/runtime/vue/server.js +6 -0
  106. package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
  107. package/dist/runtime/wrapped/FimoFileLink.js +19 -0
  108. package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
  109. package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
  110. package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
  111. package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
  112. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +26 -0
  113. package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
  114. package/dist/runtime/wrapped/FimoText.d.ts +22 -0
  115. package/dist/runtime/wrapped/FimoText.js +20 -0
  116. package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
  117. package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
  118. package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
  119. package/dist/runtime/wrapped/WrappedProviders.js +101 -0
  120. package/dist/runtime/wrapped/content-fetch.pure.d.ts +20 -0
  121. package/dist/runtime/wrapped/content-fetch.pure.js +106 -0
  122. package/dist/runtime/wrapped/dom.d.ts +4 -0
  123. package/dist/runtime/wrapped/dom.js +23 -0
  124. package/dist/runtime/wrapped/server.d.ts +80 -0
  125. package/dist/runtime/wrapped/server.js +207 -0
  126. package/dist/scripts/apply-wrap-templates.d.ts +61 -0
  127. package/dist/scripts/apply-wrap-templates.js +137 -0
  128. package/dist/scripts/apply-wrap.d.ts +73 -0
  129. package/dist/scripts/apply-wrap.js +340 -0
  130. package/dist/scripts/cms/discover.d.ts +2 -0
  131. package/dist/scripts/cms/discover.js +17 -0
  132. package/dist/scripts/cms/example-content.d.ts +33 -0
  133. package/dist/scripts/cms/example-content.js +165 -0
  134. package/dist/scripts/cms/strapi/detect.d.ts +2 -0
  135. package/dist/scripts/cms/strapi/detect.js +186 -0
  136. package/dist/scripts/cms/strapi/index.d.ts +3 -0
  137. package/dist/scripts/cms/strapi/index.js +25 -0
  138. package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
  139. package/dist/scripts/cms/strapi/list-source.js +90 -0
  140. package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
  141. package/dist/scripts/cms/strapi/schema-map.js +289 -0
  142. package/dist/scripts/cms/strapi/shapes.d.ts +25 -0
  143. package/dist/scripts/cms/strapi/shapes.js +48 -0
  144. package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
  145. package/dist/scripts/cms/strapi/touchpoints.js +145 -0
  146. package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
  147. package/dist/scripts/cms/strapi/wire-client.js +107 -0
  148. package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
  149. package/dist/scripts/cms/strapi/wiring.js +46 -0
  150. package/dist/scripts/cms/types.d.ts +145 -0
  151. package/dist/scripts/cms/types.js +11 -0
  152. package/dist/scripts/detect-framework.d.ts +39 -0
  153. package/dist/scripts/detect-framework.js +139 -0
  154. package/dist/scripts/draft-rows.d.ts +19 -0
  155. package/dist/scripts/draft-rows.js +178 -0
  156. package/dist/scripts/draft-schemas.d.ts +27 -0
  157. package/dist/scripts/draft-schemas.js +247 -0
  158. package/dist/scripts/extract-translations.js +16 -11
  159. package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
  160. package/dist/scripts/frameworks/astro/routes.js +44 -0
  161. package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
  162. package/dist/scripts/frameworks/astro/template-style.js +19 -0
  163. package/dist/scripts/frameworks/discover.d.ts +83 -0
  164. package/dist/scripts/frameworks/discover.js +288 -0
  165. package/dist/scripts/frameworks/discovered-route.d.ts +9 -0
  166. package/dist/scripts/frameworks/discovered-route.js +17 -0
  167. package/dist/scripts/frameworks/next/rewire.d.ts +43 -0
  168. package/dist/scripts/frameworks/next/rewire.js +282 -0
  169. package/dist/scripts/frameworks/next/routes.d.ts +30 -0
  170. package/dist/scripts/frameworks/next/routes.js +176 -0
  171. package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
  172. package/dist/scripts/frameworks/next/wiring.js +186 -0
  173. package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
  174. package/dist/scripts/frameworks/nuxt/routes.js +38 -0
  175. package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
  176. package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
  177. package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
  178. package/dist/scripts/frameworks/remix/routes.js +36 -0
  179. package/dist/scripts/frameworks/support.d.ts +75 -0
  180. package/dist/scripts/frameworks/support.js +165 -0
  181. package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
  182. package/dist/scripts/frameworks/sveltekit/routes.js +35 -0
  183. package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
  184. package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
  185. package/dist/scripts/frameworks/vite/routes.d.ts +20 -0
  186. package/dist/scripts/frameworks/vite/routes.js +143 -0
  187. package/dist/scripts/frameworks/vite/wiring.d.ts +22 -0
  188. package/dist/scripts/frameworks/vite/wiring.js +144 -0
  189. package/dist/scripts/i18n-namespace.d.ts +27 -0
  190. package/dist/scripts/i18n-namespace.js +155 -0
  191. package/dist/scripts/lib/humanize.d.ts +14 -0
  192. package/dist/scripts/lib/humanize.js +54 -0
  193. package/dist/scripts/lib/validation-report.d.ts +1 -3
  194. package/dist/scripts/lib/validation-report.js +3 -9
  195. package/dist/scripts/render-parity/capture.d.ts +64 -0
  196. package/dist/scripts/render-parity/capture.js +436 -0
  197. package/dist/scripts/render-parity/compare.d.ts +59 -0
  198. package/dist/scripts/render-parity/compare.js +0 -0
  199. package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
  200. package/dist/scripts/render-parity/normalize-html.js +223 -0
  201. package/dist/scripts/render-parity/types.d.ts +41 -0
  202. package/dist/scripts/render-parity/types.js +1 -0
  203. package/dist/scripts/routes/bindings.d.ts +93 -0
  204. package/dist/scripts/routes/bindings.js +117 -0
  205. package/dist/scripts/routes/label-prefixes.d.ts +49 -0
  206. package/dist/scripts/routes/label-prefixes.js +136 -0
  207. package/dist/scripts/routes/module-resolve.d.ts +20 -0
  208. package/dist/scripts/routes/module-resolve.js +78 -0
  209. package/dist/scripts/routes/page-structure.d.ts +72 -0
  210. package/dist/scripts/routes/page-structure.js +234 -0
  211. package/dist/scripts/scan-wrappable.d.ts +200 -0
  212. package/dist/scripts/scan-wrappable.js +1355 -0
  213. package/dist/scripts/validate-route-metadata.js +11 -0
  214. package/dist/scripts/validate-schemas.js +42 -1
  215. package/package.json +88 -4
  216. package/release.json +2 -2
  217. package/templates/react-router/package.json +1 -1
  218. package/templates/react-router/translations/en.json +1 -0
@@ -0,0 +1,662 @@
1
+ # Wrap Mode
2
+
3
+ Wrap mode is the default migration for a **source-backed, working JS codebase**.
4
+ The supported-framework list lives in the CLI, not in this document: `fimo init`
5
+ detects the framework and says whether it can adopt the site (and offers the
6
+ rebuild path when it cannot). The contract below is framework-neutral;
7
+ framework-specific notes live in the "Framework notes" section at the end.
8
+ The user's stack stays. Nothing is rebuilt. The migration is a mechanical
9
+ wrapping pass that makes existing content editable from Fimo, plus a mandatory
10
+ content-seeding pass so Fimo becomes the source of truth.
11
+
12
+ **Never rewrite routing, styling, structure, or component logic.** The final
13
+ diff must contain only: the one-time wiring, wrapper components, label calls,
14
+ config files, and lockfile/dependency changes. If a change does not make
15
+ content editable, it does not belong in a wrap migration.
16
+
17
+ ## When wrap does NOT apply
18
+
19
+ - URL-only migrations (no code) → rebuild path (`fimo migrate <url>`).
20
+ - Builder exports / static HTML dumps → rebuild path.
21
+ - Codebases that do not build/run as-is → fix-first is the owner's call;
22
+ wrap assumes a working site.
23
+ - Non-JS backends (PHP/WordPress/Drupal) → not supported yet.
24
+
25
+ ## Phases
26
+
27
+ ### W0. Agent-driven run (default)
28
+
29
+ When YOU (the coding agent) are asked to migrate an adopted site, you own the
30
+ whole loop — the human's only remaining jobs are `fimo init` (done before you)
31
+ and the final `fimo deploy` (never yours). The deterministic machine does the
32
+ heavy lifting; you run it, relay the user's decisions, handle the judgment
33
+ items, and loop until the gates say done:
34
+
35
+ `fimo init` writes a starter `.fimo/migration/wrap/agent-prompt.md` and
36
+ `fimo migrate` regenerates it with the full work order — that file is the
37
+ source of truth at every step; the user's entry prompt just points to it.
38
+
39
+ 1. Preflight: `npx fimo whoami`. If it fails, STOP and ask the user to run
40
+ `fimo login` — do not investigate credentials or probe backends.
41
+ 2. Run `npx fimo migrate`. Non-interactive shells are the normal agent case:
42
+ it scans, wires, captures the render baseline, applies the safe transforms
43
+ (codemod), seeds staged labels, imports env keys, and detects collections
44
+ and external data sources. Recover mechanical failures yourself (missing
45
+ install, pnpm `minimumReleaseAgeExclude`, broken dev script) and re-run.
46
+ 3. Read the output facts and `.fimo/migration/manifest.json` →
47
+ `wrap.dataSources`: entries marked `decidedBy: "default"` are USER
48
+ decisions that could not be asked in your shell. Ask the user in your chat
49
+ NOW — before any wrapping work (one question per host: keep live, or
50
+ ingest into Fimo?), update the manifest (`decision`, `decidedBy: "user"`),
51
+ and re-run `npx fimo migrate` if anything changed (idempotent).
52
+ 4. Execute `.fimo/migration/wrap/agent-prompt.md`: the judgment items
53
+ (attributes, next/image, formatted text), the detected collection
54
+ candidates, and any user-approved ingestion. Collection candidates are
55
+ CLASSIFIED before anything is created: navigation links, style-bearing
56
+ lists, page furniture, and decorative imagery are NOT collections — ask
57
+ the user one batched question (candidate list + your recommendation),
58
+ keep-in-code candidates get their strings wrapped with `t()` (the array
59
+ stays), and only user-confirmed content lists become Fimo collections.
60
+ Candidates the scan annotated with REFERENCES (field values pointing at
61
+ another candidate's slugs/names) migrate with `reference` fields —
62
+ `{ "type": "reference", "target": "<Uid>" }`, `"many": true` for arrays,
63
+ `targets: [...]` for genuinely mixed slots — never as plain strings, and
64
+ their entries are created in dependency order (targets first, then
65
+ referencing entries carrying the recorded documentIds).
66
+ NAMING STANDARD (this is the judgment the whole flow exists for — hold
67
+ the bar): collection Uids are SINGULAR PascalCase DOMAIN terms — what the
68
+ site's owner would call one item — never the source variable or file name
69
+ when a better domain word exists (a `posts.json` of episode notes is
70
+ `Episode`, not `Post`; `items` rendered as staff cards is `TeamMember`).
71
+ Field names stay camelCase and faithful to the source unless the source
72
+ name is meaningless. Machine drafts under
73
+ `.fimo/migration/schema-drafts/` carry PROVISIONAL names precisely so you
74
+ rename them. Adopt them in ONE command:
75
+ `npx fimo schemas adopt-drafts --rename "Prov=Final,…" --drop "Junk,…"`
76
+ (copies kept drafts to src/schemas, rewrites reference targets through
77
+ the renames, renames the staged rows files in step, and pushes in
78
+ dependency order). Each draft carries `source`, `rowCount`, and a
79
+ `sample` row — classify and name from the drafts ALONE, without opening
80
+ the source data files. SEEDING is then ONE command for everything:
81
+ `npx fimo migrate seed`
82
+ — it seeds every `.fimo/migration/rows/*.rows.json` in
83
+ reference-dependency order (targets first, automatically, chunked at
84
+ 1000 rows/request); the staged rows are already transformed (media
85
+ envelopes, richtext docs, dates) and the SERVER resolves reference
86
+ source values to documentIds, reporting per-field resolved/dangling
87
+ counts. Do not write mapping scripts, do not seed staged types
88
+ one-by-one, do not re-transform from the source files. PAGE REWIRING is
89
+ also mechanical where the shape is safe (Next app router): `fimo migrate`
90
+ switches pages rendering a staged collection to
91
+ `wrapCollection('<Uid>', sourceRows)` — a Fimo overlay onto the retained
92
+ source rows (per-field type-matched fallback, source order), in-code
93
+ arrays extracted to `src/data/*.json` first. `adopt-drafts` renames the
94
+ call-site uids in step. Only pages listed with a bail reason in the
95
+ migrate warnings need hand wiring — reuse the same `wrapCollection`
96
+ pattern there. Your FINAL REPORT must end with the naming table: every
97
+ collection Uid (with the source it came from) and every reference field
98
+ with its target type(s) and cardinality (single / many / polymorphic).
99
+ DANGLING references (source values pointing at targets that do not
100
+ exist) are handled at TWO layers: in the DATABASE store them null/empty
101
+ and document them in the manifest (`wrap.unresolvedReferences`) — never
102
+ invent placeholder targets; at RENDER time fall back per-field to the
103
+ raw source value (the retained fallback array's slug/string) whenever
104
+ the Fimo reference is null, so the page shows exactly what the source
105
+ site showed and render parity holds. Honest data AND honest rendering
106
+ are not in tension.
107
+ This deliberately differs from `content.md`'s rebuild-path rule ("prefer
108
+ modeling as a collection"): in wrap mode the user's code stays primary —
109
+ the rebuild rule does NOT apply here.
110
+ On simple sites the machine may leave NOTHING to do: the regenerated
111
+ prompt then says so explicitly ("already done by the machine") — your job
112
+ collapses to running the check and reporting. Never invent work the
113
+ prompt does not list.
114
+ When you start the dev server yourself, pin it to a free port (PORT env
115
+ or --port flag) — never the Fimo API's own port (apiUrl in
116
+ .fimo.settings.json; localhost:3000 in local dev). Next and Nuxt default
117
+ to 3000 and silently shadow the API for the whole machine.
118
+ 5. Loop `npx fimo migrate wrap --check` after every batch of fixes until it
119
+ exits 0. Make the loop cheap: start the dev server once in the background
120
+ at the beginning of your work and pass it to every check with
121
+ `--server http://localhost:<port>` — the warm server recompiles
122
+ incrementally, so each check costs seconds instead of a cold boot. Never
123
+ recapture the baseline, never add a convenience exemption, never bypass a
124
+ gate. If the same gate fails twice with the same error, stop and report
125
+ honestly.
126
+ 6. Report the summary and hand back: deploying is the user's call
127
+ (`npx fimo deploy` — never run it yourself).
128
+
129
+ ### W1. Adopt
130
+
131
+ Connect the existing site to a Fimo project. From the site directory:
132
+
133
+ ```bash
134
+ fimo init # interactive: detects the framework, confirms the adopt
135
+ fimo init . -y # non-interactive (agents/CI): explicit dir = consent
136
+ ```
137
+
138
+ Adoption creates the project, writes `fimo-config.json` (`framework` +
139
+ scanned `routes` for the Studio page switcher), `.fimo.settings.json`, the
140
+ framework env flavor (`NEXT_PUBLIC_FIMO_API_URL`,
141
+ `NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL` for Next), wires the `fimo` git remote
142
+ into the existing repo, and pushes the site as-is. No template is copied.
143
+
144
+ If the user's config sets CSP headers with `frame-ancestors`, the Fimo Studio
145
+ origins must be permitted or the preview iframe will be blocked. Framework
146
+ quirks (preview origin allowlists, dev-server flags) are listed under
147
+ "Framework notes" at the end of this document.
148
+
149
+ Sites whose pages fetch external APIs server-side (GitHub, CMSes, analytics)
150
+ hang in the sandbox when their API keys are absent — the page never answers
151
+ and the preview proxy returns 502/504 while the dev server logs
152
+ `Request timed out … Retrying`. `fimo migrate` imports the keys declared in
153
+ the project's `.env`/`.env.local` into the Fimo project automatically
154
+ (sensitive-looking keys as secrets, the rest as variables; existing values
155
+ are never overwritten; Fimo-owned keys are skipped). If that step was
156
+ unavailable (not signed in), push them manually with
157
+ `fimo secrets set <KEY> <value>` / `fimo vars set`, then redeploy.
158
+
159
+ ### W2. Scan + automatic wiring
160
+
161
+ ```bash
162
+ fimo migrate # inside an adopted JS codebase this IS the wrap flow
163
+ fimo migrate --apply # + apply the provably-safe transforms automatically
164
+ ```
165
+
166
+ `--apply` performs the deterministic wraps itself in seconds — plain JSX
167
+ text → `FimoText` (whitespace preserved), `<img src="literal">` →
168
+ `FimoMediaImage`, file links → `FimoFileLink` — with deterministic key
169
+ naming and the generated labels staged AND seeded. The render-parity
170
+ baseline is always captured before any transform. Everything needing
171
+ judgment (attributes, formatted text, `next/image`, collections) is refused
172
+ with a reason and stays in the plan for the agent.
173
+
174
+ (`fimo migrate wrap` is the explicit form; `fimo migrate <url>` remains the
175
+ crawl-and-rebuild path for URL-only migrations.)
176
+
177
+ Writes `.fimo/migration/plan.json` (every unwrapped item with
178
+ `kind`/`file`/`line`/`preview`) and `.fimo/migration/wrap/agent-prompt.md`.
179
+ The scan finds hardcoded JSX text, `img`/`Image`/`video` sources, file links
180
+ (pdf/zip/docx/…), and user-visible attribute strings (`placeholder`, `title`,
181
+ `aria-label`). Content already inside Fimo wrappers or `t()` calls counts as
182
+ covered.
183
+
184
+ The command also performs the deterministic wiring itself: it adds the `fimo`
185
+ dependency (platform-pinned) and, for Next.js, inserts `allowedDevOrigins`
186
+ into the next config when missing. Run the project's package manager install
187
+ afterwards.
188
+
189
+ Known friction: pnpm ≥ 11 ships a `minimumReleaseAge` supply-chain policy
190
+ that refuses packages younger than the cutoff — experimental `fimo` builds
191
+ are minutes old by nature. If install fails with
192
+ `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`, propose adding to the project's
193
+ `pnpm-workspace.yaml`:
194
+
195
+ ```yaml
196
+ minimumReleaseAgeExclude:
197
+ - fimo
198
+ ```
199
+
200
+ ### W3. Wire the layout (one-time)
201
+
202
+ - Root layout (Next.js app router):
203
+
204
+ ```tsx
205
+ import { FimoNextProviders, FimoNextScripts } from 'fimo/next';
206
+ import { loadFimoConfig, loadFimoLabels } from 'fimo/next/server';
207
+
208
+ const config = loadFimoConfig();
209
+ const labels = await loadFimoLabels();
210
+ // <head><FimoNextScripts /></head>
211
+ // <body><FimoNextProviders config={config} labels={labels}>{children}</FimoNextProviders></body>
212
+ ```
213
+
214
+ - React (framework-agnostic) uses `fimo/react`; React Router projects keep
215
+ `fimo/react-router`.
216
+
217
+ RSC rule: the wrapper primitives are client components. Server components may
218
+ render them directly as JSX children; do not call hooks like `useLabels()`
219
+ outside client components (`'use client'`).
220
+
221
+ ### W4. Wrap
222
+
223
+ Work through `plan.json` item by item:
224
+
225
+ | Item kind | Wrap with |
226
+ | ----------------------------------- | -------------------------------------------------------------------------------------- |
227
+ | text (plain) | `<FimoText id="…">original text</FimoText>` |
228
+ | text (formatted: strong/em/u/links) | `<FimoRichTextBlock id="…">…original markup…</FimoRichTextBlock>` — marks must survive |
229
+ | attribute | `const { t } = useLabels()` then `placeholder={String(t('key', 'original value'))}` |
230
+ | image | `<FimoMediaImage id="…" src="/original.png" …/>` |
231
+ | video | `<FimoMediaVideo id="…" src="/original.mp4" …/>` |
232
+ | file-link | `<FimoFileLink id="…" href="/original.pdf">…</FimoFileLink>` |
233
+
234
+ Rules:
235
+
236
+ - The original value always stays inline as the fallback (child or prop). Wrap
237
+ fallbacks are deliberate: they keep the site rendering without Fimo
238
+ connectivity. This differs from Fimo-template projects, where inline
239
+ fallbacks are forbidden by lint — do not "fix" wrap fallbacks to match.
240
+ - Key naming: `<route>.<section>.<n>` — `home.hero.title`,
241
+ `pricing.plans.starter.description`. Stable, human-readable, no indexes
242
+ unless the content is genuinely a list.
243
+ - Repeated hardcoded arrays of 3+ structurally identical items (cards, tiles)
244
+ are still collections: model a schema + entries as in
245
+ `references/content.md`, render from `fetchEntries` (`fimo/next/server`).
246
+ Wrapping each array literal string individually is acceptable only as a
247
+ first pass and must be recorded as an unresolved modeling item.
248
+
249
+ ### W5. Seed (MANDATORY)
250
+
251
+ Fallbacks are resilience, not storage. Every wrapped item is seeded into Fimo:
252
+
253
+ - Labels: every `FimoText`/`t()` key with its original string via
254
+ `fimo labels set-many --file <json>`.
255
+ - Media/files: `fimo assets upload <path>` per item, then a `WrappedMedia`
256
+ entry per wrapper id (`key` = wrapper id, `image` = the asset) via
257
+ `fimo schemas push` + `fimo entries create` (or, for anything beyond ~20
258
+ rows, `fimo entries create-many <Uid> --file <rows.json>` — a JSON array of
259
+ bare data objects, chunked through the batch endpoint with per-row
260
+ results; never loop single creates over a large collection).
261
+ - Collections with machine-staged rows: `fimo migrate seed` — one command
262
+ seeds every `.fimo/migration/rows/*.rows.json` in reference-dependency
263
+ order at 1000 rows/request. Each file's `references` descriptor has the
264
+ server resolve reference source values (slugs) to documentIds;
265
+ self-references within one file resolve only against rows already
266
+ inserted (earlier chunks), so a fully intra-file cycle surfaces as
267
+ dangling counts — record those in the manifest like any dangling
268
+ reference. (`fimo entries create-many <Uid> --file <rows.json>` remains
269
+ the per-type primitive for rows you build yourself.)
270
+ - Rich text: `WrappedRichText` entries (`key`, `body` = TipTap JSON converted
271
+ from the original markup; bold/italic/underline/link marks preserved).
272
+
273
+ A wrap migration whose labels/entries were never created in Fimo is
274
+ `draft-only`, exactly like a rebuild migration with un-imported seed JSON.
275
+
276
+ ### W6. Verify — four machine-enforced gates
277
+
278
+ `fimo migrate wrap --check` is the definition of done. It fails until all
279
+ four gates pass:
280
+
281
+ 1. **Coverage** — zero unwrapped items. Deliberate exceptions are recorded in
282
+ the ledger and the gate honors them: add to
283
+ `.fimo/migration/manifest.json` → `wrap.exemptions[]` an entry
284
+ `{ file, kind, preview, reason, status }` where `(file, kind, preview)`
285
+ matches the plan item exactly (a content change invalidates the exemption
286
+ so the item resurfaces).
287
+ 2. **Render parity** — the check boots the project's dev server and compares
288
+ every route's rendered text, inline marks, media srcs, and link hrefs
289
+ against the pre-wrap baseline that `fimo migrate` captured
290
+ (`.fimo/migration/parity-baseline.json`). Drift is reported per route and
291
+ block. Dynamic routes are captured by substituting the project's default
292
+ locale for `[locale]`-style params (so an all-`[locale]` i18n site is
293
+ verified, not skipped); routes with content params (`[slug]`, `[id]`) that
294
+ have no safe default still skip — call those out as UNVERIFIED in the
295
+ handoff. In a monorepo, the baseline boot builds the app's workspace deps
296
+ first (root `build` / `turbo run build`) so `next dev` does not die on
297
+ `Cannot find module '@repo/…'`. Run the check right after wrapping (strict,
298
+ pre-seed) and again after seeding. If the baseline is missing, capture one
299
+ from the pre-wrap git state: `git stash && npx fimo migrate snapshot && git stash pop`.
300
+ Text the site renders dynamically BY DESIGN (live clock, visitor counter)
301
+ drifts on every capture: record `wrap.parityIgnores[]`
302
+ `{ pattern, reason }` in the manifest — a full-match regex both sides must
303
+ match to compare equal. A route rendering a keep-live external source
304
+ (`wrap.dataSources` decision `live`) drifts wholesale: record
305
+ `{ route, reason }` instead (counted as unverified, never silently
306
+ clean). Never use either form to hide drift caused by your edit.
307
+ 3. **Seeding** — every wrapped key exists in the Fimo database (labels and
308
+ WrappedMedia/WrappedRichText entries), verified via the API. Fallbacks are
309
+ not storage. Labels-decision data files (below) are verified too via
310
+ their recorded `labelPrefix`.
311
+ 4. **Wiring** — the root providers are mounted (framework marker file, e.g.
312
+ `FimoNextProviders` in the app-router layout, `FimoSpaProviders` in the
313
+ SPA entry, `FimoRemixProviders` in `app/root`, `fimoLabels` in the
314
+ SvelteKit root `+layout.server`). Without them every wrapped value renders
315
+ its fallback forever and no other gate notices — coverage counts wraps,
316
+ parity sees identical fallbacks, seeding is database-side. `fimo migrate`
317
+ applies this wiring itself for safe shapes (Next.js app router, Vite React
318
+ SPA); the gate enforces the rest. Enforced only once items are wrapped;
319
+ Next pages-router-only projects get a warning, not a failure.
320
+
321
+ **When a CMS was scaffolded, the four gates do NOT prove the reads resolve from
322
+ Fimo.** Parity drift from real→example content is expected and gets ignored, so
323
+ a wiring that silently falls through to the live CMS passes every gate while the
324
+ page renders the CMS's own content (or 404s when the CMS is down). This is the
325
+ CMS analog of the mandatory browser check — prove Fimo _ownership_ by hand:
326
+ load the homepage plus one page per scaffolded type and confirm the rendered
327
+ value is a scaffolded EXAMPLE marker (`Title example 1`, `Meta Title example 1`,
328
+ a `<type>-example-1` slug), NOT real CMS content and NOT a 404. Then confirm in
329
+ Studio Edit mode that a scaffolded block is selectable, not just rendered (a
330
+ block item with no stable id renders but cannot be edited — editability is the
331
+ whole point). **A single type (`Global`, `Header`, `Footer`) that reads empty
332
+ while collections render is the singular-vs-plural keying bug**: the generated
333
+ `fimo-strapi.ts` must key single types by their singular path (`'global'`, not
334
+ `'globals'`), and in dev the adapter logs `/api/global looks like a scaffolded
335
+ type but is not in the type map` on that fallthrough. Fix it in the CLI
336
+ (`scripts/cms/strapi/schema-map.ts`) and regenerate with
337
+ `fimo migrate --scaffold-cms` — never hand-edit the generated module.
338
+
339
+ **Populate real routes** (scaffolded CMS only): example entries carry generated
340
+ slugs, so only unfiltered reads (the homepage) resolve — every real route the
341
+ site links to misses its slug filter and 404s (the adapter answers a filtered
342
+ miss on a Fimo-owned type with an empty envelope; it never leaks the read to
343
+ the old CMS). Harvest the site's real slugs (live production `sitemap.xml`,
344
+ else nav/footer links in the code, else ask the user) and create one entry of
345
+ the routed type per linked route — matching slug + path field, distinct title,
346
+ varied block subset. Then click through the nav: every linked page renders,
347
+ none 404. This is navigability, not the content migration (the real pull is a
348
+ later, separate step).
349
+
350
+ **Config-driven sites** (all copy in a data file, templates render only
351
+ expressions): the scanner lists copy-bearing `.json`/`.yml` files and
352
+ markdown groups in the plan (`dataFiles`) and the coverage gate fails until
353
+ each has a decision in the manifest: `wrap.dataFiles[]`
354
+ `{ file, decision: "labels" | "entries" | "ignore", reason, labelPrefix? }`.
355
+ LOCAL CONTENT ALWAYS MIGRATES INTO FIMO — do not ask the user whether to
356
+ migrate local files (only external live sources are a user question).
357
+ `labels` = flat copy documents: seed each prose string as
358
+ `<labelPrefix><json.path>` (arrays by index) and deep-merge label values over
359
+ the parsed file at its server-side loader, keeping the file as fallback;
360
+ `entries` = repeated documents/structures, INCLUDING markdown blog posts:
361
+ the machine stages these as schema drafts + rows (a same-shaped group of ≥2
362
+ md/mdx docs → frontmatter parsed into typed fields, `body` kept verbatim as a
363
+ string so the site's own renderer still works, `slug` from the filename), so
364
+ you adopt them with `adopt-drafts` + `migrate seed` like any collection — no
365
+ hand-authoring, no re-parsing. Record the `entries` decision and rewire the
366
+ reading route to `fetchEntries()` with the files as fallback; `ignore` =
367
+ strictly non-content data (fixtures, geo payloads) — never a deferral.
368
+ GeoJSON/TopoJSON are auto-skipped.
369
+
370
+ Never bypass a failing gate (`--no-parity` / `--skip-seeding` are for the
371
+ owner to decide, not the agent). Additional manual checks:
372
+
373
+ - The site builds and runs **with its own toolchain** (`next build`,
374
+ `npm run dev`, …) — never assume Vite.
375
+ - Diff audit: the migration diff contains only wiring, wrappers, label calls,
376
+ config, and dependency changes.
377
+ - Then `fimo deploy` (allowed in wrap mode: it pushes the preview env and
378
+ boots the sandbox preview — it does not publish). Verify in Fimo Studio that
379
+ the preview renders and Edit mode can select and edit wrapped content, and
380
+ that an edited label round-trips (`fimo labels set <key> "new"` → reload →
381
+ rendered). `fimo publish` / `fimo deploy --publish` remain owner-only.
382
+
383
+ ### W7. Unwire the previous CMS — the cutover (when scaffolded)
384
+
385
+ Injecting Fimo is only **half** a migration. If a CMS was scaffolded (W2/W5),
386
+ the site now READS example content from Fimo but is still **wired to the old
387
+ CMS**: the generated Fimo adapter falls back to it, the SDK/client is still
388
+ imported, and the CMS env is still present. The migration is not done until a
389
+ plain `pnpm dev` runs **purely on Fimo, with zero requests to the old CMS**.
390
+ That cutover is the "wow" — deliver it.
391
+
392
+ This phase is the agent's: every codebase talks to its CMS differently (bare
393
+ `fetch`, an SDK client, axios, GraphQL, RSC loaders, image hosts), so the
394
+ replacement needs judgment. The machine hands you the worklist; you do the work.
395
+
396
+ 1. **Read the worklist.** `npx fimo migrate cutover` lists every remaining
397
+ coupling — SDK/client/type deps, source lines that reach the CMS (env /
398
+ content-type IDs / SDK import), and leftover env keys. The generated agent
399
+ prompt embeds the same list under **Unwire &lt;CMS&gt;**.
400
+ 2. **Replace the data-access layer.** Prefer rewriting the CMS **client module**
401
+ (the one file that instantiates the SDK / holds the fetch helpers) into a
402
+ Fimo-backed adapter exposing the **same interface**, so every caller is fixed
403
+ by a single change. Read from Fimo with `fetchEntries` (`fimo/<framework>/server`).
404
+ 3. **Sever the fallthrough.** The generated Fimo adapter falls back to the CMS
405
+ for unmapped reads — a scaffold crutch. Once the client is Fimo-backed, drop
406
+ the fallthrough so an unmapped read fails loudly, never silently hits the CMS.
407
+ 4. **Remove the coupling.** Delete the CMS SDK/client/type dependencies and the
408
+ base-URL / token env, and fix any standalone routing the CMS handled (e.g. a
409
+ `/` → `/[locale]` redirect its middleware provided).
410
+ 5. **Verify — the cutover gate.** `npx fimo migrate cutover --check` must report
411
+ a **clean** cutover (0 couplings). Then `pnpm dev` and confirm the network
412
+ tab shows **zero** requests to the old CMS. Record the cutover in the manifest.
413
+
414
+ Detection keys on actual CMS references, not filenames — a CMS client module you
415
+ rewrote to call Fimo reads as clean. Keep-live sites (the user chose NOT to
416
+ scaffold) skip this phase: the CMS stays the source of truth.
417
+
418
+ ## Manifest
419
+
420
+ Record wrap mode in the canonical ledger (`references/manifest.md`): set
421
+ `mode: "wrap"` and fill the `wrap` block (framework, coverage counts, seeded
422
+ counts, wiring status). Routes/media/forms sections apply as usual — wrap mode
423
+ changes how content becomes editable, not the accounting obligations.
424
+
425
+ ## Framework notes
426
+
427
+ One short block per wrappable framework. Add a block when the CLI gains a
428
+ framework — nothing above this section should name a specific framework.
429
+
430
+ ### Next.js
431
+
432
+ - `fimo migrate` inserts `allowedDevOrigins: ['*.preview.fimo.ai']` into the
433
+ next config (named `nextConfig` bindings, `export default {` literals, and
434
+ `module.exports = {`); without it the sandbox preview serves a black iframe
435
+ with failing HMR websockets.
436
+ - `fimo migrate` also wires the app-router ROOT LAYOUT itself for safe shapes
437
+ (one default-exported function returning `<html>…<body>…{children}`):
438
+ imports, async, loader calls, `<FimoNextScripts />` in head, `{children}`
439
+ wrapped in `<FimoNextProviders>`. When it bails (unusual layout), the agent
440
+ prompt's section 0 carries the recipe and the bail reason; pages-router
441
+ projects (`pages/_app`) are always manual and only WARN in the wiring gate.
442
+ - Env flavor: `NEXT_PUBLIC_FIMO_API_URL`, `NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL`.
443
+ - Server components cannot call `useLabels()`: use `loadFimoT()` from
444
+ `fimo/next/server` for attribute strings.
445
+ - `next dev` rejects an unknown `--host` flag (fimopm only passes it to
446
+ Vite-family dev servers).
447
+
448
+ ### Remix (v2)
449
+
450
+ - New Remix apps no longer exist (`create-remix` redirects to
451
+ create-react-router): this adapter targets the EXISTING remix-vite v2
452
+ fleet. Classic-compiler (`remix dev`) apps predate vite and are not
453
+ supported by the parity capture.
454
+ - The JSX codemod runs here (React guaranteed): `--apply` wraps plain text,
455
+ imgs and file links with `fimo/remix` imports automatically.
456
+ - Root wiring: a `loader` in `app/root.tsx` returns
457
+ `{ fimoConfig: loadFimoConfig(), fimoLabels: await loadFimoLabels() }`
458
+ (from `fimo/remix/server`); the `Layout` reads it with
459
+ `useRouteLoaderData<typeof loader>("root")` — GUARD the null case (error
460
+ boundaries render Layout without loader data) — and mounts
461
+ `<FimoRemixScripts />` in head plus
462
+ `<FimoRemixProviders config={…} labels={…}>{children}</FimoRemixProviders>`.
463
+
464
+ - React: the full wrapper set applies (`fimo/remix` re-exports it) plus
465
+ `FimoRemixProviders`/`FimoRemixScripts` for `app/root.tsx`.
466
+ - Env flavor: `FIMO_API_URL`, `FIMO_PREVIEW_SCRIPT_URL` (server-read).
467
+ - Routes: flat files under `app/routes` (dots are slashes, `$param` dynamic).
468
+ - Modern Remix dev runs on Vite (`--host` handled); classic `remix dev` honors HOST.
469
+
470
+ ### Astro
471
+
472
+ - Server-loader + DOM-attrs based (no React needed): in frontmatter
473
+ `const t = await loadFimoT()` (from `fimo/astro/server`), render
474
+ `{t('key', 'fallback')}` with `{...fimoLabelAttrs('key')}` on the element.
475
+ - **Islands are NOT React by default** (Solid, Svelte, Preact all ship as
476
+ `.tsx`/`.svelte`): never import `fimo/next` wrappers there — the JSX
477
+ runtime differs and the build breaks. The `--apply` codemod skips JSX in
478
+ Astro projects for this reason. Recipe for a mounted island: the parent
479
+ `.astro` loads `t` server-side and passes label values as props; the
480
+ island renders the prop with a literal
481
+ `data-fimo-source="__labels.<locale>.<key>.value"` attribute (no import
482
+ needed — it is just a DOM attribute).
483
+ - Mixed text nodes (`Hi, I'm <b>Name</b>, a developer…`) wrap per text node:
484
+ each segment gets its own `<span {...fimoLabelAttrs(...)}>{t(...)}</span>`
485
+ (or the attrs go on the existing `<b>`/`<a>`), punctuation stays in the
486
+ segment it belongs to.
487
+ - Playground/demo components not referenced by any route: exempt them
488
+ (`wrap.exemptions`, reason "dead template code") instead of fabricating
489
+ editability for UI that never renders.
490
+ - Config-driven Astro themes (all copy in `src/data/*.json`) surface as
491
+ `dataFiles` in the plan — follow the data-file triage in W6.
492
+ - Env flavor: `PUBLIC_FIMO_API_URL`, `PUBLIC_FIMO_PREVIEW_SCRIPT_URL`.
493
+ - Routes: file-based under `src/pages` (`.astro`/`.md`/`.mdx`).
494
+ - `astro dev` needs `--host` (fimopm passes it); verify the preview origin
495
+ allowlist on the first real dogfood.
496
+ - Scanner coverage: `.astro` templates are text-scanned — frontmatter,
497
+ `<script>`/`<style>` bodies, `<title>` head metadata, and nested
498
+ `{expressions}` are excluded; what remains is real copy.
499
+ - `--apply` auto-wraps PLAIN TEXT NODES in `.astro` templates (injects the
500
+ `loadFimoT` head into the frontmatter, wraps `{t('key', 'fallback')}`,
501
+ stages + seeds the labels). Files that already bind their own `t` (i18n
502
+ libraries) are left whole. Attributes, media, mixed markup, and
503
+ collections stay agent work via the recipe above.
504
+ - pnpm >= 11 hard-fails installs on unapproved build scripts; adopt writes
505
+ `allowBuilds: { fimo: true }` into `pnpm-workspace.yaml` (the project's
506
+ own blocked deps — esbuild, sharp — are the owner's existing approvals).
507
+
508
+ ### Nuxt (3+)
509
+
510
+ - Composable-based (SFCs are UNIVERSAL — they hydrate client-side, so never
511
+ import `fimo/nuxt/server` in a component; the nuxtship dogfood proved it
512
+ breaks `nuxt build` three different ways). In `<script setup>`:
513
+
514
+ ```vue
515
+ <script setup>
516
+ import { useFimoT, fimoLabelAttrs } from 'fimo/nuxt';
517
+ const t = await useFimoT();
518
+ </script>
519
+ <template>
520
+ <h1 v-bind="fimoLabelAttrs('hero.title')">{{ t('hero.title', 'Fallback') }}</h1>
521
+ </template>
522
+ ```
523
+
524
+ `useFimoT` fetches on the server only and hands the dictionary to the
525
+ client through the Nuxt payload (hydration-safe, one fetch per page).
526
+ `fimo/nuxt/server` remains for nitro server routes only.
527
+
528
+ - In-component arrays (pricing tiers, feature lists): keep the array, wrap
529
+ its copy strings with `t('key', 'fallback')` inside the array literal —
530
+ `t` is available above them in the same `<script setup>`.
531
+ - Env flavor: `NUXT_PUBLIC_FIMO_API_URL`, `NUXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL`.
532
+ - Routes: file-based under `pages/`. `nuxt dev` needs `--host`.
533
+ - Scanner coverage: `.vue` templates are text-scanned (template region only,
534
+ inline-svg `<title>` excluded); `<script setup>` arrays are
535
+ collection-scanned (nested copy like `button.text` counts).
536
+ - `--apply` auto-wraps PLAIN TEXT NODES in `.vue` templates (injects the
537
+ `useFimoT` head into `<script setup>`, wraps `{{ t('key', 'fallback') }}`,
538
+ stages + seeds the labels). Options-API files and files that bind their
539
+ own `t` (vue-i18n) are left whole for the agent; attributes, media, and
540
+ collections stay agent work.
541
+ - Parity capture: nuxi answers 503 while Vite builds — the boot probe waits
542
+ for a sub-500 response, so slow first compiles are fine.
543
+
544
+ ### SvelteKit
545
+
546
+ - Load labels ONCE in `src/routes/+layout.server.js` (SvelteKit's own
547
+ server-only convention — no client-bundle risk):
548
+
549
+ ```js
550
+ import { loadFimoLabels } from 'fimo/svelte/server';
551
+ export const load = async () => {
552
+ const bundle = await loadFimoLabels();
553
+ return { fimoLabels: bundle.dictionaries[bundle.defaultLocale] ?? {} };
554
+ };
555
+ ```
556
+
557
+ Components build `t` locally from client-safe `fimo/svelte`:
558
+ pages/layouts via the data prop (`let { data } = $props()` in Svelte 5),
559
+ deep components via the page store (`$page.data.fimoLabels` /
560
+ `page.data` from `$app/state`):
561
+
562
+ ```svelte
563
+ <script>
564
+ import { makeFimoT, fimoLabelAttrs } from 'fimo/svelte';
565
+ let { data } = $props();
566
+ const t = makeFimoT(data.fimoLabels);
567
+ </script>
568
+ <h1 {...fimoLabelAttrs('hero.title')}>{t('hero.title', 'Fallback')}</h1>
569
+ ```
570
+
571
+ Never import `fimo/svelte/server` in a `.svelte` file (components
572
+ hydrate; the server barrel reads fs).
573
+ Build `t` with the mode-agnostic form
574
+ `const t = makeFimoT($page.data.fimoLabels);` — a plain store read,
575
+ valid in BOTH legacy and runes components (labels are fixed per render,
576
+ no reactivity needed). Do NOT use `$derived(...)` (a single rune flips
577
+ the whole component to runes mode and any existing `$:` in it 500s) and
578
+ do NOT use `$: t = ...` (reactive statements run after init, so arrays
579
+ built at init see `t` undefined). In files with a
580
+ `<script lang="ts" module>` block, the import goes in the INSTANCE
581
+ script, never the module script (`$page` is illegal there).
582
+
583
+ - mdsvex blogs: `.md` post copy is markdown-ingestion territory (Tier 1b),
584
+ not wrap items; `+page.md` routes still discover and parity-capture.
585
+ - Env flavor: `PUBLIC_FIMO_API_URL` (SvelteKit's public prefix).
586
+ - Routes: `src/routes/**/+page.svelte` (+ `+page.md`); `(groups)` are
587
+ layout-only.
588
+ - Vite dev server (`--host` handled). `.svelte` templates are text-scanned.
589
+ - `--apply` auto-wraps PLAIN TEXT NODES in `.svelte` files (injects the
590
+ mode-agnostic head above into the instance script, wraps
591
+ `{t('key', 'fallback')}`, stages + seeds the labels) and writes
592
+ `src/routes/+layout.server.js` when the project has none. An EXISTING
593
+ root layout load is not touched — merging `fimoLabels` into it is your
594
+ work. Files binding their own `t` are left whole; attributes, media, and
595
+ collections stay agent work.
596
+ - Repo pitfalls seen in the wild: a checked-in FOREIGN lockfile (pnpm-lock
597
+ in an npm project) makes the capture spawn the wrong package manager —
598
+ align on one lockfile; a `master` default branch maps to a nonexistent
599
+ Fimo env — rename to `main` (or create the env) before data commands.
600
+
601
+ ### Vite + React (SPA)
602
+
603
+ - Detection: a `vite` dependency plus `react` (no meta-framework). The react
604
+ dependency pins the JSX runtime, so the full JSX codemod runs here —
605
+ `--apply` wraps plain text, imgs, and file links with `fimo/react` imports.
606
+ Bare Vite without react (Solid, Preact, lit) stays agent work.
607
+ - One-time wiring: `fimo migrate` wraps the root render itself for the
608
+ mainstream entry shape (`createRoot(…).render(<JSX/>)` in `src/main.*` /
609
+ `src/index.*`). When it bails, mount the SPA providers yourself once around
610
+ the app root — there is no server pass; labels are fetched in the browser
611
+ and everything renders inline fallbacks until they resolve:
612
+
613
+ ```tsx
614
+ import { FimoSpaProviders } from 'fimo/react';
615
+ import config from '../fimo-config.json';
616
+
617
+ <FimoSpaProviders
618
+ config={config}
619
+ apiUrl={import.meta.env.VITE_API_URL}
620
+ previewScriptUrl={import.meta.env.DEV ? import.meta.env.VITE_FIMO_PREVIEW_SCRIPT_URL : undefined}
621
+ >
622
+ <App />
623
+ </FimoSpaProviders>;
624
+ ```
625
+
626
+ - Instant content: the provider paints synchronously from
627
+ `window.__FIMO_CONTENT__` (baked) or the last visit's localStorage cache,
628
+ then revalidates in the background (Studio preview saves and window focus
629
+ trigger refetches — no reload needed). Only the FIRST EVER visit to a
630
+ non-baked site flashes inline fallbacks for one network round trip. If the
631
+ project prerenders its `index.html` (any SSG/prerender step), close that
632
+ gap by baking the payload at build time:
633
+
634
+ ```js
635
+ // e.g. scripts/bake-fimo-content.mjs — run AFTER the build/prerender step
636
+ import { fetchFimoSpaContent, serializeFimoSpaContent } from 'fimo/react';
637
+
638
+ const payload = await fetchFimoSpaContent(process.env.VITE_API_URL, config);
639
+ html = html.replace('</head>', `${serializeFimoSpaContent(payload)}</head>`);
640
+ ```
641
+
642
+ - Routes: no file convention — the scan reads LITERAL ABSOLUTE paths from
643
+ react-router (`<Route path="/x">` and `createBrowserRouter([{ path: '/x' }])`)
644
+ into the fimo-config route registry. Relative child paths, `index` routes,
645
+ and `*` wildcards are not resolved: add those to `fimo-config.json` routes
646
+ yourself (scanned routes only ever ADD, never overwrite).
647
+ - Parity caveat: a CSR dev server answers every route with the same HTML
648
+ shell, so the parity gate compares shells and passes vacuously. Coverage
649
+ and seeding carry the real signal; verify rendering in the browser.
650
+ - Env flavor: `VITE_API_URL`, `VITE_FIMO_PREVIEW_SCRIPT_URL` (Vite inlines
651
+ only `VITE_`-prefixed vars into client code).
652
+
653
+ ### Vue (Vite SPA)
654
+
655
+ - v1: DOM-attrs + client-side label hydration; routes come from fimo-config
656
+ (no file convention to discover).
657
+
658
+ ### Vite / React Router (native template)
659
+
660
+ - The template pipeline (virtual modules, `t()` extraction) predates wrap mode
661
+ and stays the canonical path for Fimo-created projects; wrap mode is for
662
+ adopted codebases.