fimo 0.7.0-experimental.1 → 0.7.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 (310) hide show
  1. package/assets/content-templates/hooks-template.eta +27 -73
  2. package/assets/content-templates/types-template.eta +1 -4
  3. package/assets/skills/fimo/SKILL.md +2 -2
  4. package/assets/skills/fimo/references/content.md +42 -46
  5. package/assets/skills/fimo/references/locales.md +0 -8
  6. package/assets/skills/fimo/references/setup-react-router.md +2 -2
  7. package/assets/skills/fimo/references/ui.md +1 -1
  8. package/assets/skills/fimo-cli/references/content.md +14 -17
  9. package/assets/skills/fimo-cli/references/locales.md +0 -6
  10. package/assets/skills/fimo-migration/SKILL.md +22 -2
  11. package/assets/skills/fimo-migration/references/content.md +8 -0
  12. package/assets/skills/fimo-migration/references/manifest.md +35 -0
  13. package/assets/skills/fimo-migration/references/qa.md +24 -2
  14. package/assets/skills/fimo-migration/references/workflow.md +12 -1
  15. package/assets/skills/fimo-migration/references/wrap.md +617 -0
  16. package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
  17. package/dist/build/vite/plugins/translations.d.ts +2 -8
  18. package/dist/build/vite/plugins/translations.d.ts.map +1 -1
  19. package/dist/build/vite/plugins/translations.js +3 -121
  20. package/dist/cli/bundle.json +1 -1
  21. package/dist/cli/index.js +20655 -12551
  22. package/dist/runtime/app/FimoProviders.d.ts +1 -11
  23. package/dist/runtime/app/FimoProviders.d.ts.map +1 -1
  24. package/dist/runtime/app/FimoProviders.js +2 -23
  25. package/dist/runtime/astro/index.d.ts +2 -0
  26. package/dist/runtime/astro/index.d.ts.map +1 -0
  27. package/dist/runtime/astro/index.js +3 -0
  28. package/dist/runtime/astro/server.d.ts +3 -0
  29. package/dist/runtime/astro/server.d.ts.map +1 -0
  30. package/dist/runtime/astro/server.js +6 -0
  31. package/dist/runtime/content/utils/retrieve-data-sources.d.ts +0 -1
  32. package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +1 -1
  33. package/dist/runtime/content/utils/retrieve-data-sources.js +4 -27
  34. package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +1 -1
  35. package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
  36. package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
  37. package/dist/runtime/next/FimoNextProviders.d.ts.map +1 -0
  38. package/dist/runtime/next/FimoNextProviders.js +20 -0
  39. package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
  40. package/dist/runtime/next/FimoNextScripts.d.ts.map +1 -0
  41. package/dist/runtime/next/FimoNextScripts.js +19 -0
  42. package/dist/runtime/next/index.d.ts +14 -0
  43. package/dist/runtime/next/index.d.ts.map +1 -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.d.ts.map +1 -0
  47. package/dist/runtime/next/server.js +4 -0
  48. package/dist/runtime/nuxt/composables.d.ts +9 -0
  49. package/dist/runtime/nuxt/composables.d.ts.map +1 -0
  50. package/dist/runtime/nuxt/composables.js +57 -0
  51. package/dist/runtime/nuxt/index.d.ts +3 -0
  52. package/dist/runtime/nuxt/index.d.ts.map +1 -0
  53. package/dist/runtime/nuxt/index.js +6 -0
  54. package/dist/runtime/nuxt/server.d.ts +3 -0
  55. package/dist/runtime/nuxt/server.d.ts.map +1 -0
  56. package/dist/runtime/nuxt/server.js +6 -0
  57. package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -1
  58. package/dist/runtime/paths/get-fimo-paths.js +5 -15
  59. package/dist/runtime/primitives/labels-hook.d.ts +33 -0
  60. package/dist/runtime/primitives/labels-hook.d.ts.map +1 -0
  61. package/dist/runtime/primitives/labels-hook.js +41 -0
  62. package/dist/runtime/primitives/labels-store.d.ts +12 -0
  63. package/dist/runtime/primitives/labels-store.d.ts.map +1 -0
  64. package/dist/runtime/primitives/labels-store.js +60 -0
  65. package/dist/runtime/primitives/labels-store.test.d.ts +2 -0
  66. package/dist/runtime/primitives/labels-store.test.d.ts.map +1 -0
  67. package/dist/runtime/primitives/labels-store.test.js +66 -0
  68. package/dist/runtime/primitives/lib/primitives.d.ts +2 -2
  69. package/dist/runtime/primitives/lib/primitives.d.ts.map +1 -1
  70. package/dist/runtime/primitives/lib/query.d.ts +3 -5
  71. package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
  72. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
  73. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +1 -1
  74. package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
  75. package/dist/runtime/primitives/public-surface.test.js +5 -9
  76. package/dist/runtime/primitives/translations.d.ts +1 -27
  77. package/dist/runtime/primitives/translations.d.ts.map +1 -1
  78. package/dist/runtime/primitives/translations.js +7 -85
  79. package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
  80. package/dist/runtime/react/FimoSpaProviders.d.ts.map +1 -0
  81. package/dist/runtime/react/FimoSpaProviders.js +63 -0
  82. package/dist/runtime/react/index.d.ts +33 -0
  83. package/dist/runtime/react/index.d.ts.map +1 -0
  84. package/dist/runtime/react/index.js +39 -0
  85. package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
  86. package/dist/runtime/remix/FimoRemixProviders.d.ts.map +1 -0
  87. package/dist/runtime/remix/FimoRemixProviders.js +12 -0
  88. package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
  89. package/dist/runtime/remix/FimoRemixScripts.d.ts.map +1 -0
  90. package/dist/runtime/remix/FimoRemixScripts.js +19 -0
  91. package/dist/runtime/remix/index.d.ts +12 -0
  92. package/dist/runtime/remix/index.d.ts.map +1 -0
  93. package/dist/runtime/remix/index.js +11 -0
  94. package/dist/runtime/remix/server.d.ts +2 -0
  95. package/dist/runtime/remix/server.d.ts.map +1 -0
  96. package/dist/runtime/remix/server.js +2 -0
  97. package/dist/runtime/seo/buildSeoForPath.d.ts +3 -1
  98. package/dist/runtime/seo/buildSeoForPath.d.ts.map +1 -1
  99. package/dist/runtime/seo/buildSeoForPath.js +1 -4
  100. package/dist/runtime/shared/display-routes.d.ts +14 -0
  101. package/dist/runtime/shared/display-routes.d.ts.map +1 -0
  102. package/dist/runtime/shared/display-routes.js +25 -0
  103. package/dist/runtime/shared/fimo-config.server.d.ts.map +1 -1
  104. package/dist/runtime/shared/fimo-config.server.js +1 -0
  105. package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
  106. package/dist/runtime/shared/fimo-config.types.d.ts.map +1 -1
  107. package/dist/runtime/shared/i18n-messages.d.ts +29 -0
  108. package/dist/runtime/shared/i18n-messages.d.ts.map +1 -0
  109. package/dist/runtime/shared/i18n-messages.js +79 -0
  110. package/dist/runtime/shared/i18n-messages.test.d.ts +2 -0
  111. package/dist/runtime/shared/i18n-messages.test.d.ts.map +1 -0
  112. package/dist/runtime/shared/i18n-messages.test.js +39 -0
  113. package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
  114. package/dist/runtime/shared/labels-fetch.pure.d.ts.map +1 -0
  115. package/dist/runtime/shared/labels-fetch.pure.js +71 -0
  116. package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
  117. package/dist/runtime/shared/labels-fetch.server.d.ts.map +1 -0
  118. package/dist/runtime/shared/labels-fetch.server.js +97 -0
  119. package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
  120. package/dist/runtime/shared/labels-t.pure.d.ts.map +1 -0
  121. package/dist/runtime/shared/labels-t.pure.js +10 -0
  122. package/dist/runtime/strapi/client.d.ts +26 -0
  123. package/dist/runtime/strapi/client.d.ts.map +1 -0
  124. package/dist/runtime/strapi/client.js +88 -0
  125. package/dist/runtime/strapi/client.test.d.ts +2 -0
  126. package/dist/runtime/strapi/client.test.d.ts.map +1 -0
  127. package/dist/runtime/strapi/client.test.js +64 -0
  128. package/dist/runtime/strapi/index.d.ts +5 -0
  129. package/dist/runtime/strapi/index.d.ts.map +1 -0
  130. package/dist/runtime/strapi/index.js +6 -0
  131. package/dist/runtime/strapi/reshape.d.ts +28 -0
  132. package/dist/runtime/strapi/reshape.d.ts.map +1 -0
  133. package/dist/runtime/strapi/reshape.js +79 -0
  134. package/dist/runtime/strapi/reshape.test.d.ts +2 -0
  135. package/dist/runtime/strapi/reshape.test.d.ts.map +1 -0
  136. package/dist/runtime/strapi/reshape.test.js +33 -0
  137. package/dist/runtime/svelte/index.d.ts +3 -0
  138. package/dist/runtime/svelte/index.d.ts.map +1 -0
  139. package/dist/runtime/svelte/index.js +7 -0
  140. package/dist/runtime/svelte/server.d.ts +3 -0
  141. package/dist/runtime/svelte/server.d.ts.map +1 -0
  142. package/dist/runtime/svelte/server.js +6 -0
  143. package/dist/runtime/vue/index.d.ts +3 -0
  144. package/dist/runtime/vue/index.d.ts.map +1 -0
  145. package/dist/runtime/vue/index.js +5 -0
  146. package/dist/runtime/vue/server.d.ts +3 -0
  147. package/dist/runtime/vue/server.d.ts.map +1 -0
  148. package/dist/runtime/vue/server.js +6 -0
  149. package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
  150. package/dist/runtime/wrapped/FimoFileLink.d.ts.map +1 -0
  151. package/dist/runtime/wrapped/FimoFileLink.js +19 -0
  152. package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
  153. package/dist/runtime/wrapped/FimoMediaImage.d.ts.map +1 -0
  154. package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
  155. package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
  156. package/dist/runtime/wrapped/FimoMediaVideo.d.ts.map +1 -0
  157. package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
  158. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +23 -0
  159. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts.map +1 -0
  160. package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
  161. package/dist/runtime/wrapped/FimoText.d.ts +22 -0
  162. package/dist/runtime/wrapped/FimoText.d.ts.map +1 -0
  163. package/dist/runtime/wrapped/FimoText.js +20 -0
  164. package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
  165. package/dist/runtime/wrapped/WrappedContentContext.d.ts.map +1 -0
  166. package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
  167. package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
  168. package/dist/runtime/wrapped/WrappedProviders.d.ts.map +1 -0
  169. package/dist/runtime/wrapped/WrappedProviders.js +101 -0
  170. package/dist/runtime/wrapped/dom.d.ts +4 -0
  171. package/dist/runtime/wrapped/dom.d.ts.map +1 -0
  172. package/dist/runtime/wrapped/dom.js +23 -0
  173. package/dist/runtime/wrapped/dom.test.d.ts +2 -0
  174. package/dist/runtime/wrapped/dom.test.d.ts.map +1 -0
  175. package/dist/runtime/wrapped/dom.test.js +16 -0
  176. package/dist/runtime/wrapped/server.d.ts +77 -0
  177. package/dist/runtime/wrapped/server.d.ts.map +1 -0
  178. package/dist/runtime/wrapped/server.js +237 -0
  179. package/dist/runtime/wrapped/server.test.d.ts +2 -0
  180. package/dist/runtime/wrapped/server.test.d.ts.map +1 -0
  181. package/dist/runtime/wrapped/server.test.js +91 -0
  182. package/dist/scripts/apply-wrap-templates.d.ts +61 -0
  183. package/dist/scripts/apply-wrap-templates.js +132 -0
  184. package/dist/scripts/apply-wrap-templates.test.d.ts +1 -0
  185. package/dist/scripts/apply-wrap-templates.test.js +199 -0
  186. package/dist/scripts/apply-wrap.d.ts +46 -0
  187. package/dist/scripts/apply-wrap.js +278 -0
  188. package/dist/scripts/apply-wrap.test.d.ts +1 -0
  189. package/dist/scripts/apply-wrap.test.js +226 -0
  190. package/dist/scripts/cms/discover.d.ts +2 -0
  191. package/dist/scripts/cms/discover.js +17 -0
  192. package/dist/scripts/cms/example-content.d.ts +32 -0
  193. package/dist/scripts/cms/example-content.js +108 -0
  194. package/dist/scripts/cms/example-content.test.d.ts +1 -0
  195. package/dist/scripts/cms/example-content.test.js +66 -0
  196. package/dist/scripts/cms/strapi/detect.d.ts +2 -0
  197. package/dist/scripts/cms/strapi/detect.js +186 -0
  198. package/dist/scripts/cms/strapi/detect.test.d.ts +1 -0
  199. package/dist/scripts/cms/strapi/detect.test.js +56 -0
  200. package/dist/scripts/cms/strapi/index.d.ts +3 -0
  201. package/dist/scripts/cms/strapi/index.js +25 -0
  202. package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
  203. package/dist/scripts/cms/strapi/list-source.js +90 -0
  204. package/dist/scripts/cms/strapi/list-source.test.d.ts +1 -0
  205. package/dist/scripts/cms/strapi/list-source.test.js +33 -0
  206. package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
  207. package/dist/scripts/cms/strapi/schema-map.js +149 -0
  208. package/dist/scripts/cms/strapi/schema-map.test.d.ts +1 -0
  209. package/dist/scripts/cms/strapi/schema-map.test.js +91 -0
  210. package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
  211. package/dist/scripts/cms/strapi/touchpoints.js +145 -0
  212. package/dist/scripts/cms/strapi/touchpoints.test.d.ts +1 -0
  213. package/dist/scripts/cms/strapi/touchpoints.test.js +62 -0
  214. package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
  215. package/dist/scripts/cms/strapi/wire-client.js +107 -0
  216. package/dist/scripts/cms/strapi/wire-client.test.d.ts +1 -0
  217. package/dist/scripts/cms/strapi/wire-client.test.js +51 -0
  218. package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
  219. package/dist/scripts/cms/strapi/wiring.js +36 -0
  220. package/dist/scripts/cms/strapi/wiring.test.d.ts +1 -0
  221. package/dist/scripts/cms/strapi/wiring.test.js +27 -0
  222. package/dist/scripts/cms/types.d.ts +117 -0
  223. package/dist/scripts/cms/types.js +11 -0
  224. package/dist/scripts/detect-framework.d.ts +39 -0
  225. package/dist/scripts/detect-framework.js +133 -0
  226. package/dist/scripts/detect-framework.test.d.ts +1 -0
  227. package/dist/scripts/detect-framework.test.js +122 -0
  228. package/dist/scripts/draft-rows.d.ts +19 -0
  229. package/dist/scripts/draft-rows.js +135 -0
  230. package/dist/scripts/draft-rows.test.d.ts +1 -0
  231. package/dist/scripts/draft-rows.test.js +113 -0
  232. package/dist/scripts/draft-schemas.d.ts +27 -0
  233. package/dist/scripts/draft-schemas.js +188 -0
  234. package/dist/scripts/draft-schemas.test.d.ts +1 -0
  235. package/dist/scripts/draft-schemas.test.js +152 -0
  236. package/dist/scripts/extract-translations.js +16 -11
  237. package/dist/scripts/extract-translations.test.d.ts +1 -0
  238. package/dist/scripts/extract-translations.test.js +66 -0
  239. package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
  240. package/dist/scripts/frameworks/astro/routes.js +55 -0
  241. package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
  242. package/dist/scripts/frameworks/astro/template-style.js +19 -0
  243. package/dist/scripts/frameworks/discover.d.ts +70 -0
  244. package/dist/scripts/frameworks/discover.js +178 -0
  245. package/dist/scripts/frameworks/next/rewire.d.ts +30 -0
  246. package/dist/scripts/frameworks/next/rewire.js +258 -0
  247. package/dist/scripts/frameworks/next/rewire.test.d.ts +1 -0
  248. package/dist/scripts/frameworks/next/rewire.test.js +134 -0
  249. package/dist/scripts/frameworks/next/routes.d.ts +17 -0
  250. package/dist/scripts/frameworks/next/routes.js +134 -0
  251. package/dist/scripts/frameworks/next/routes.test.d.ts +1 -0
  252. package/dist/scripts/frameworks/next/routes.test.js +64 -0
  253. package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
  254. package/dist/scripts/frameworks/next/wiring.js +186 -0
  255. package/dist/scripts/frameworks/next/wiring.test.d.ts +1 -0
  256. package/dist/scripts/frameworks/next/wiring.test.js +174 -0
  257. package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
  258. package/dist/scripts/frameworks/nuxt/routes.js +54 -0
  259. package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
  260. package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
  261. package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
  262. package/dist/scripts/frameworks/remix/routes.js +50 -0
  263. package/dist/scripts/frameworks/support.d.ts +75 -0
  264. package/dist/scripts/frameworks/support.js +165 -0
  265. package/dist/scripts/frameworks/support.test.d.ts +1 -0
  266. package/dist/scripts/frameworks/support.test.js +87 -0
  267. package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
  268. package/dist/scripts/frameworks/sveltekit/routes.js +50 -0
  269. package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
  270. package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
  271. package/dist/scripts/frameworks/vite/routes.d.ts +15 -0
  272. package/dist/scripts/frameworks/vite/routes.js +93 -0
  273. package/dist/scripts/frameworks/vite/routes.test.d.ts +1 -0
  274. package/dist/scripts/frameworks/vite/routes.test.js +73 -0
  275. package/dist/scripts/frameworks/vite/wiring.d.ts +10 -0
  276. package/dist/scripts/frameworks/vite/wiring.js +99 -0
  277. package/dist/scripts/frameworks/vite/wiring.test.d.ts +1 -0
  278. package/dist/scripts/frameworks/vite/wiring.test.js +88 -0
  279. package/dist/scripts/generated-types.test.js +1 -1
  280. package/dist/scripts/i18n-namespace.d.ts +25 -0
  281. package/dist/scripts/i18n-namespace.js +53 -0
  282. package/dist/scripts/lib/validation-report.d.ts +1 -0
  283. package/dist/scripts/lib/validation-report.js +3 -0
  284. package/dist/scripts/render-parity/capture.d.ts +56 -0
  285. package/dist/scripts/render-parity/capture.js +366 -0
  286. package/dist/scripts/render-parity/capture.test.d.ts +1 -0
  287. package/dist/scripts/render-parity/capture.test.js +306 -0
  288. package/dist/scripts/render-parity/compare.d.ts +59 -0
  289. package/dist/scripts/render-parity/compare.js +0 -0
  290. package/dist/scripts/render-parity/compare.test.d.ts +1 -0
  291. package/dist/scripts/render-parity/compare.test.js +98 -0
  292. package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
  293. package/dist/scripts/render-parity/normalize-html.js +223 -0
  294. package/dist/scripts/render-parity/normalize-html.test.d.ts +1 -0
  295. package/dist/scripts/render-parity/normalize-html.test.js +58 -0
  296. package/dist/scripts/render-parity/types.d.ts +41 -0
  297. package/dist/scripts/render-parity/types.js +1 -0
  298. package/dist/scripts/resolve-routes.js +1 -1
  299. package/dist/scripts/scan-wrappable.d.ts +196 -0
  300. package/dist/scripts/scan-wrappable.js +1227 -0
  301. package/dist/scripts/scan-wrappable.test.d.ts +1 -0
  302. package/dist/scripts/scan-wrappable.test.js +521 -0
  303. package/dist/scripts/sync-schemas.test.js +4 -24
  304. package/dist/scripts/validate-route-metadata.js +11 -0
  305. package/dist/scripts/validate-schemas.test.js +2 -2
  306. package/package.json +92 -5
  307. package/templates/react-router/package.json +7 -10
  308. package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
  309. package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
  310. package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
@@ -11,7 +11,7 @@
11
11
  // AUTO-GENERATED ⚠️ – do not edit by hand
12
12
  import { <% if (!model.isSingleton) { %>useInfiniteQuery, <% } %>useSuspenseQuery, useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
13
13
  <% if (!model.isSingleton) { -%>
14
- import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult, UseSuspenseQueryResult } from "@tanstack/react-query";
14
+ import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult } from "@tanstack/react-query";
15
15
  <% } -%>
16
16
  import { FimoString, FimoMedia, FimoBoolean, FimoRichText, useLocale<% if (!model.isSingleton) { %>, parseCollectionResponse<% } %> } from "fimo/ui";
17
17
  // @ts-ignore - virtual module provided by Fimo's Vite plugin
@@ -37,21 +37,15 @@ export type { <%= model.pascal %>, <%= model.pascal %>Fields, <%= model.pascal %
37
37
  */
38
38
  export function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
39
39
  const id = entry.id;
40
- const entryId = entry.entryId;
41
- const documentId = entry.documentId ?? id;
42
- const locale = entry.locale ?? defaultLocale();
43
- const sourcePrefix = `<%= model.pascal %>.id:${id}:${encodeURIComponent(locale)}`;
40
+ const sourcePrefix = `<%= model.pascal %>.${id}`;
44
41
  const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
45
42
 
46
43
  return {
47
44
  id,
48
- entryId,
49
- documentId,
45
+ documentId: entry.documentId,
50
46
  slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
51
- <% if (!model.isSingleton || model.localized) { -%>
52
- locale,
53
- <% } else { -%>
54
- ...(entry.locale ? { locale } : {}),
47
+ <% if (model.localized) { -%>
48
+ locale: entry.locale,
55
49
  <% } -%>
56
50
  createdAt: entry.createdAt,
57
51
  updatedAt: entry.updatedAt,
@@ -107,11 +101,11 @@ if (model.fields) {
107
101
  const wrapper = field.target === model.uid ? 'wrapWithSource' : `wrap${field.targetPascal}WithSource`;
108
102
  if (field.many) {
109
103
  -%>
110
- <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => isPopulatedEntry(value) ? <%= wrapper %>(value) : value) : entry.<%= key %>,
104
+ <%= key %>: Array.isArray(entry.<%= key %>) ? entry.<%= key %>.map((value: any) => value != null && typeof value === "object" && typeof value.id === "string" ? <%= wrapper %>(value) : value) : entry.<%= key %>,
111
105
  <%
112
106
  } else {
113
107
  -%>
114
- <%= key %>: isPopulatedEntry(entry.<%= key %>) ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
108
+ <%= key %>: entry.<%= key %> != null && typeof entry.<%= key %> === "object" && typeof entry.<%= key %>.id === "string" ? <%= wrapper %>(entry.<%= key %>) : entry.<%= key %>,
115
109
  <%
116
110
  }
117
111
  break;
@@ -127,23 +121,6 @@ if (model.fields) {
127
121
  } as <%= model.pascal %>;
128
122
  }
129
123
 
130
- interface PopulatedEntryRecord extends Record<string, unknown> {
131
- id: string;
132
- __fimo: { contentType: string };
133
- }
134
-
135
- function isPopulatedEntry(value: unknown): value is PopulatedEntryRecord {
136
- if (value == null || typeof value !== "object") {
137
- return false;
138
- }
139
- const entry = value as Record<string, unknown>;
140
- const fimo = entry.__fimo;
141
- return typeof entry.id === "string"
142
- && fimo != null
143
- && typeof fimo === "object"
144
- && typeof (fimo as Record<string, unknown>).contentType === "string";
145
- }
146
-
147
124
  <%
148
125
  const hasPolymorphicReferences = model.fields && Object.values(model.fields).some((field) => field.type === 'reference' && Array.isArray(field.targetRefs) && field.targetRefs.length > 0);
149
126
  if (hasPolymorphicReferences) {
@@ -155,9 +132,7 @@ if (hasPolymorphicReferences) {
155
132
  * (unpopulated) values pass through untouched.
156
133
  */
157
134
  function wrapPolymorphic(value: any, wrappers: Record<string, (entry: Record<string, any>) => any>): any {
158
- if (!isPopulatedEntry(value)) {
159
- return value;
160
- }
135
+ if (value == null || typeof value !== "object" || typeof value.id !== "string") return value;
161
136
  const contentType = value.__fimo && typeof value.__fimo.contentType === "string" ? value.__fimo.contentType : null;
162
137
  const wrapper = contentType ? wrappers[contentType] : undefined;
163
138
  if (!wrapper) return value;
@@ -184,11 +159,6 @@ function useQueryLocale(locale?: string): string {
184
159
  return locale ?? activeLocale ?? defaultLocale();
185
160
  }
186
161
 
187
- function withMutationLocale<T extends object>(payload: T, activeLocale: string): T & { locale: string } {
188
- const locale = (payload as { locale?: string }).locale;
189
- return { ...payload, locale: locale ?? activeLocale };
190
- }
191
-
192
162
  const LOCALIZED_CONTENT = <%= model.localized ? 'true' : 'false' %>;
193
163
  const LOCALIZED_REFERENCE_FIELDS = new Set<string>(<%= JSON.stringify(model.localizedReferenceFields) %>);
194
164
 
@@ -202,12 +172,8 @@ function readLocale(
202
172
  ownerUsesLocale: boolean,
203
173
  fallbackLocale = defaultLocale()
204
174
  ): string | undefined {
205
- <% if (!model.isSingleton) { -%>
206
- return options.locale ?? fallbackLocale;
207
- <% } else { -%>
208
175
  const populatesLocalizedTarget = populateFields(options.populate).some((field) => LOCALIZED_REFERENCE_FIELDS.has(field));
209
176
  return ownerUsesLocale || populatesLocalizedTarget ? options.locale ?? fallbackLocale : undefined;
210
- <% } -%>
211
177
  }
212
178
 
213
179
  function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
@@ -236,7 +202,6 @@ function withReadOptions<TPopulate extends <%= model.pascal %>Populate | undefin
236
202
  function splitWritePayload(payload: Partial<<%= model.pascal %>Input>): Record<string, unknown> {
237
203
  const {
238
204
  id,
239
- entryId,
240
205
  documentId,
241
206
  locale,
242
207
  createdAt,
@@ -288,7 +253,7 @@ export async function getById<TPopulate extends <%= model.pascal %>Populate | un
288
253
  id: string,
289
254
  options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}
290
255
  ): Promise<<%= model.pascal %>Result<undefined, TPopulate>> {
291
- const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/${id}`, options)}`, { headers: fimoHeaders() });
256
+ const res = await fetch(`${base}${withReadOptions(`/entries/<%= model.uid %>/${id}`, options, false)}`, { headers: fimoHeaders() });
292
257
  if (!res.ok) throw new Error(await res.text());
293
258
  const data = await res.json();
294
259
  return wrapWithSource(data.data) as <%= model.pascal %>Result<undefined, TPopulate>;
@@ -389,21 +354,18 @@ export async function create(payload: <%= model.pascal %>Input): Promise<<%= mod
389
354
  }
390
355
 
391
356
  export async function update(id: string, payload: Partial<<%= model.pascal %>Input>): Promise<<%= model.pascal %>> {
392
- const write = splitWritePayload(payload) as { data: Record<string, unknown>; locale?: string };
393
- const url = withReadOptions(`/entries/<%= model.uid %>/${id}`, { locale: write.locale });
394
- const res = await fetch(`${base}${url}`, {
357
+ const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, {
395
358
  method: "PUT",
396
359
  headers: fimoHeaders({ "Content-Type": "application/json" }),
397
- body: JSON.stringify({ data: write.data })
360
+ body: JSON.stringify(splitWritePayload(payload))
398
361
  });
399
362
  if (!res.ok) throw new Error(await res.text());
400
363
  const data = await res.json();
401
364
  return wrapWithSource(data.data);
402
365
  }
403
366
 
404
- export async function remove(id: string, options: { locale?: string } = {}): Promise<void> {
405
- const url = withReadOptions(`/entries/<%= model.uid %>/${id}`, options);
406
- const res = await fetch(`${base}${url}`, { method: "DELETE", headers: fimoHeaders() });
367
+ export async function remove(id: string): Promise<void> {
368
+ const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, { method: "DELETE", headers: fimoHeaders() });
407
369
  if (!res.ok) throw new Error(await res.text());
408
370
  }
409
371
  <% } else { -%>
@@ -437,7 +399,7 @@ export async function update(payload: Partial<<%= model.pascal %>Input>): Promis
437
399
  <% if (!model.isSingleton) { -%>
438
400
  const qk = {
439
401
  root : ["<%= model.pascal %>"] as const,
440
- byId : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "id", id, options.locale, options.populate] as const,
402
+ byId : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", id, options.locale, options.populate] as const,
441
403
  bySlug : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(slug: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", "slug", slug, options.locale, options.populate] as const,
442
404
  byField : <TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(field: string, value: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) => ["<%= model.pascal %>", field, value, options.locale, options.populate] as const,
443
405
  list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const,
@@ -447,8 +409,8 @@ const qk = {
447
409
  >(params: InfiniteListQuery<TFields, TPopulate>) => ["<%= model.pascal %>", "infinite", params] as const
448
410
  };
449
411
 
450
- export function useGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
451
- const locale = useQueryLocale(options.locale);
412
+ export function useGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
413
+ const locale = readLocale(options, false, useQueryLocale(options.locale));
452
414
  const localizedOptions = { ...options, locale };
453
415
  return useQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
454
416
  }
@@ -513,8 +475,8 @@ export function useInfiniteList<
513
475
  };
514
476
  }
515
477
 
516
- export function useSuspenseGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}): UseSuspenseQueryResult<<%= model.pascal %>Result<undefined, TPopulate>, Error> {
517
- const locale = useQueryLocale(options.locale);
478
+ export function useSuspenseGetById<TPopulate extends <%= model.pascal %>Populate | undefined = undefined>(id: string, options: { locale?: string; populate?: TPopulate; localeFallbacks?: boolean } = {}) {
479
+ const locale = readLocale(options, false, useQueryLocale(options.locale));
518
480
  const localizedOptions = { ...options, locale };
519
481
  return useSuspenseQuery<<%= model.pascal %>Result<undefined, TPopulate>, Error>({ queryKey: qk.byId(id, localizedOptions), queryFn: () => getById(id, localizedOptions) });
520
482
  }
@@ -546,37 +508,30 @@ export function useSuspenseGet<
546
508
 
547
509
  export function useCreate() {
548
510
  const qc = useQueryClient();
549
- const activeLocale = useQueryLocale();
550
511
  return useMutation<<%= model.pascal %>, Error, <%= model.pascal %>Input>({
551
- mutationFn : (payload) => create(withMutationLocale(payload, activeLocale)),
512
+ mutationFn : create,
552
513
  onSuccess : () => qc.invalidateQueries({ queryKey: qk.root })
553
514
  });
554
515
  }
555
516
 
556
517
  export function useUpdate() {
557
518
  const qc = useQueryClient();
558
- const activeLocale = useQueryLocale();
559
- return useMutation<<%= model.pascal %>, Error, { id: string; locale?: string } & Partial<<%= model.pascal %>Input>>({
560
- mutationFn : ({ id, ...patch }) => update(id, withMutationLocale(patch, activeLocale)),
519
+ return useMutation<<%= model.pascal %>, Error, { id: string } & Partial<<%= model.pascal %>Input>>({
520
+ mutationFn : ({ id, ...patch }) => update(id, patch),
561
521
  onSuccess : (_data, vars) => {
562
522
  qc.invalidateQueries({ queryKey: qk.root });
563
- qc.invalidateQueries({ queryKey: qk.byId(vars.id, { locale: vars.locale ?? activeLocale }) });
523
+ qc.invalidateQueries({ queryKey: qk.byId(vars.id) });
564
524
  }
565
525
  });
566
526
  }
567
527
 
568
528
  export function useRemove() {
569
529
  const qc = useQueryClient();
570
- const activeLocale = useQueryLocale();
571
- return useMutation<void, Error, string | { id: string; locale?: string }>({
572
- mutationFn : (value) => {
573
- const { id, locale } = typeof value === "string" ? { id: value, locale: activeLocale } : { ...value, locale: value.locale ?? activeLocale };
574
- return remove(id, { locale });
575
- },
576
- onSuccess : (_data, value) => {
577
- const { id, locale } = typeof value === "string" ? { id: value, locale: activeLocale } : { ...value, locale: value.locale ?? activeLocale };
530
+ return useMutation<void, Error, string>({
531
+ mutationFn : remove,
532
+ onSuccess : (_data, id) => {
578
533
  qc.invalidateQueries({ queryKey: qk.root });
579
- qc.removeQueries({ queryKey: qk.byId(id, { locale }), exact: false });
534
+ qc.removeQueries({ queryKey: ["<%= model.pascal %>", id], exact: false });
580
535
  }
581
536
  });
582
537
  }
@@ -600,9 +555,8 @@ export function useSuspenseGet<TPopulate extends <%= model.pascal %>Populate | u
600
555
 
601
556
  export function useUpdate() {
602
557
  const qc = useQueryClient();
603
- const activeLocale = useQueryLocale();
604
558
  return useMutation<<%= model.pascal %>, Error, Partial<<%= model.pascal %>Input>>({
605
- mutationFn: (payload) => update(withMutationLocale(payload, activeLocale)),
559
+ mutationFn: update,
606
560
  onSuccess: () => qc.invalidateQueries({ queryKey: qk.root })
607
561
  });
608
562
  }
@@ -33,13 +33,10 @@ export interface <%= model.pascal %>FimoMeta {
33
33
 
34
34
  export interface <%= model.pascal %> extends WithReferences<<%= model.pascal %>References> {
35
35
  id: string;
36
- entryId: string;
37
36
  documentId: string;
38
37
  slug: FimoString;
39
- <% if (!model.isSingleton || model.localized) { -%>
38
+ <% if (model.localized) { -%>
40
39
  locale: string;
41
- <% } else { -%>
42
- locale?: string;
43
40
  <% } -%>
44
41
  createdAt: string;
45
42
  updatedAt: string;
@@ -14,7 +14,7 @@ You are writing code in a Fimo project — a content + media + forms + analytics
14
14
 
15
15
  This skill is version-locked to this project's installed `fimo` package — the shapes here match what your code can actually use.
16
16
 
17
- For CLI commands and workflows (creating a project, pushing schemas/forms, deploying, managing labels, etc.), the **`fimo-cli`** skill is the source of truth. Run every CLI command as `npx fimo@latest <command>` (always the latest, no global install). If `fimo-cli` isn't loaded for your AI tool, register it with `npx fimo@latest skills install --mode=copy`.
17
+ For CLI commands and workflows (creating a project, pushing schemas/forms, deploying, managing labels, etc.), the **`fimo-cli`** skill is the source of truth. Run every CLI command as `npx fimo <command>` when the project declares a `fimo` dependency (it pins the version matching the project's backend); otherwise use `npx fimo@latest`. If `fimo-cli` isn't loaded for your AI tool, register it with `npx fimo skills install --mode=copy`.
18
18
 
19
19
  ## Critical: NEVER hardcode user-facing things
20
20
 
@@ -43,7 +43,7 @@ Workflow / CLI-side concerns (pushing schemas, generating images, managing label
43
43
  ## Decision Tree (specific tasks)
44
44
 
45
45
  - **Setting up Fimo in a new project** — pick the framework:
46
- - React Router framework mode (new projects use React Router 8) → `references/setup-react-router.md`
46
+ - React Router 7 framework mode → `references/setup-react-router.md`
47
47
  - Plain Vite + React (or upgrading a legacy v3 project) → `references/setup-plain-vite.md`
48
48
  - Writing schema JSON / picking field types / Tiptap richtext → `references/content.md`
49
49
  - Writing form JSON / generated form client / form code → `references/forms.md`
@@ -34,9 +34,9 @@ Every entry already carries this metadata automatically. Declaring any of them a
34
34
  schema field is rejected by `fimo schemas push` with a 400 (e.g. `fields.slug: "slug" is
35
35
  reserved entry metadata and cannot be used as a content field.`):
36
36
 
37
- `id`, `entryId`, `documentId`, `slug`, `locale`, `createdAt`, `updatedAt`, `__fimo`.
37
+ `id`, `documentId`, `slug`, `locale`, `createdAt`, `updatedAt`, `__fimo`.
38
38
 
39
- Collection entries expose the effective `locale`. It remains reserved for both localized and shared schemas.
39
+ Localized entries expose `locale`; non-localized entries omit it. It remains reserved in both schemas.
40
40
 
41
41
  In particular **do not add a `slug` field** — every entry gets a `slug` for free. You still
42
42
  pass `slug` inside `data` when _creating an entry_ (see "Entry body shape" below); it just
@@ -45,16 +45,16 @@ something else (e.g. `handle`, `permalink`).
45
45
 
46
46
  ## Field types
47
47
 
48
- | Type | `data` JSON shape | Use for |
49
- | ----------- | ----------------------------------------------------------------------------- | --------------------------------- |
50
- | `string` | `"..."` | Titles, short text |
51
- | `richtext` | Tiptap JSONContent (see below) | Blog body, descriptions, articles |
52
- | `number` | `42` | Counts, quantities |
53
- | `boolean` | `true` / `false` | Flags |
54
- | `date` | ISO date string, e.g. `"2026-06-03"` | Publish dates, timestamps |
55
- | `media` | `{ "source": "local"\|"external", "url": "https://...", "alt": "..." }` | Images, video |
56
- | `json` | any JSON | Structured metadata |
57
- | `reference` | `{ "id": "..." }`, `null`, or an array of reference objects when `many: true` | References to another schema |
48
+ | Type | `data` JSON shape | Use for |
49
+ | ----------- | ------------------------------------------------------------------------------------- | --------------------------------- |
50
+ | `string` | `"..."` | Titles, short text |
51
+ | `richtext` | Tiptap JSONContent (see below) | Blog body, descriptions, articles |
52
+ | `number` | `42` | Counts, quantities |
53
+ | `boolean` | `true` / `false` | Flags |
54
+ | `date` | ISO date string, e.g. `"2026-06-03"` | Publish dates, timestamps |
55
+ | `media` | `{ "source": "local"\|"external", "url": "https://...", "alt": "..." }` | Images, video |
56
+ | `json` | any JSON | Structured metadata |
57
+ | `reference` | `{ "documentId": "..." }`, `null`, or an array of reference objects when `many: true` | References to another schema |
58
58
 
59
59
  A `date` field currently generates as `any` in the TS client, so for typed access (and the `<Date>` component) prefer a `string` field holding an ISO date — which is what the `publishDate` examples below do.
60
60
 
@@ -79,7 +79,7 @@ Rules:
79
79
  - `target` is required and must be the PascalCase schema `uid` of the referenced content type.
80
80
  - Omit `many` for a to-one reference. Use `"many": true` for a to-many reference.
81
81
  - Self-references are allowed, e.g. a `Category` schema can have `parent: { "type": "reference", "target": "Category" }`.
82
- - Entry values use the target's stable `id`, never the variant `entryId`: `{ "id": "..." }`.
82
+ - Entry values store reference objects with the target `documentId`, not row `id` values: `{ "documentId": "..." }`.
83
83
  - Do not include `contentType` for normal fixed-target references; the schema's `target` already defines it.
84
84
  - Do not add `localized` or field-level `i18n` to a reference. The reference value belongs to the entry variant that stores it.
85
85
  - Use `null` for an empty to-one reference and `[]` for an empty to-many reference.
@@ -92,22 +92,22 @@ Entry payload example:
92
92
  "data": {
93
93
  "slug": "reference-example",
94
94
  "title": "Reference example",
95
- "author": { "id": "750e8400-e29b-41d4-a716-446655440000" },
96
- "contributors": [{ "id": "750e8400-e29b-41d4-a716-446655440000" }]
95
+ "author": { "documentId": "750e8400-e29b-41d4-a716-446655440000" },
96
+ "contributors": [{ "documentId": "750e8400-e29b-41d4-a716-446655440000" }]
97
97
  }
98
98
  }
99
99
  ```
100
100
 
101
101
  ## Schema transitions
102
102
 
103
- After editing schema JSON, run `fimo schemas push {Uid}` and then `fimo validate`. Push regenerates the schema client and applies the schema change to existing content automatically:
103
+ After editing schema JSON, run `fimo schemas push {Uid}` and then `fimo validate`. Push regenerates the schema client and applies the schema change to stored content automatically:
104
104
 
105
105
  - Adding fields and changing labels preserve entries.
106
106
  - Removing a field removes only that field's values.
107
107
  - A one-to-many reference change wraps each existing reference in an array.
108
108
  - A many-to-one change, reference target change, scalar type change, or scalar/reference change clears only the changed field.
109
109
  - Collection-to-singleton removes that type's entries and cleans references to those documents.
110
- - Singleton-to-collection preserves the existing entry.
110
+ - Singleton-to-collection preserves the existing row.
111
111
 
112
112
  There are no reset or data-loss flags. Do not invent one or promise that incompatible field values survive; the schema change itself selects the behavior above.
113
113
 
@@ -192,10 +192,9 @@ Import from the public schema module, e.g. `import BlogPost from '@/schemas/Blog
192
192
  ```ts
193
193
  interface BlogPost {
194
194
  id: string;
195
- entryId: string;
196
195
  documentId: string;
197
196
  slug: FimoString;
198
- locale: string;
197
+ locale: string; // localized schemas only; omitted when i18n.enabled is false
199
198
  createdAt: string;
200
199
  updatedAt: string;
201
200
  __fimo: {
@@ -214,14 +213,13 @@ Content fields live at the top level. Fimo-owned metadata that is not part of th
214
213
 
215
214
  ## Localized and shared content
216
215
 
217
- Localized content has one variant per locale. Generated hooks read the active Fimo locale automatically.
216
+ Localized content is stored per locale. Generated hooks read the active Fimo locale automatically.
218
217
 
219
218
  When a project has multiple configured locales, create and update each localized content variant explicitly. Use the default locale first, then create non-default variants with the same `documentId` and the target `locale` through the content tools or `fimo entries` commands.
220
219
 
221
220
  For shared content, set `i18n: { "enabled": false }` on the schema and create each entry once. Shared entries:
222
221
 
223
- - send and expose the effective active/default locale in generated collection queries and models;
224
- - omit `locale` from raw API responses because there is no localized variant;
222
+ - have no public `locale` property;
225
223
  - are returned in every locale context without duplication;
226
224
  - ignore locale on owner reads and writes;
227
225
  - still use the active/requested locale when a populated reference targets a localized schema.
@@ -230,7 +228,7 @@ Do not duplicate shared entries across locales or add field-level localization s
230
228
 
231
229
  A populated reference whose target has no variant in the requested locale resolves to null. Reads accept an opt-in `localeFallbacks: true` that resolves those to the best available variant instead (project default first), marked by its `locale` field — never implement fallbacks by hand; public reads stay strict unless the option is passed.
232
230
 
233
- **Polymorphic references:** a field may declare `targets: ["Post", "Video"]` instead of a single `target` (never both) when one slot genuinely holds different types — mixed feeds, hero blocks, curated collections. Prefer a fixed `target` whenever every value is the same type: fixed fields keep `where` filtering and simpler generated types. Polymorphic values use `{ contentType, id }`; populated values in generated hooks carry a top-level `$type` that narrows the union:
231
+ **Polymorphic references:** a field may declare `targets: ["Post", "Video"]` instead of a single `target` (never both) when one slot genuinely holds different types — mixed feeds, hero blocks, curated collections. Prefer a fixed `target` whenever every value is the same type: fixed fields keep `where` filtering and simpler generated types. Polymorphic values store `{ contentType, documentId }`; populated values in generated hooks carry a top-level `$type` that narrows the union:
234
232
 
235
233
  ```ts
236
234
  for (const item of page.featured) {
@@ -247,21 +245,19 @@ Filtering on polymorphic fields is not supported yet.
247
245
 
248
246
  **Generated API:**
249
247
 
250
- Collection responses use `id` as the stable identity shared by all locale variants. `locale` selects the variant, and generated hooks use the active Fimo locale when it is omitted. `entryId` identifies the exact variant returned by Fimo and is only for diagnostics or migrations. `documentId` remains a compatibility alias for `id`; reference inputs use `{ id: entry.id }`. Filters and sorts follow the same rule: `where.id` and `sort: "id"` use stable identity, while `entryId` addresses an exact variant.
251
-
252
248
  ```ts
253
249
  // low-level fetch helpers
254
- getById<TPopulate extends BlogPostPopulate | undefined>(id: string, options?: { locale?: string; populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate>>
250
+ getById<TPopulate extends BlogPostPopulate | undefined>(id: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate>>
255
251
  getBySlug<TPopulate extends BlogPostPopulate | undefined>(slug: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
256
252
  getByField<TPopulate extends BlogPostPopulate | undefined>(fieldName: string, value: string, options?: { populate?: TPopulate }): Promise<BlogPostResult<undefined, TPopulate> | null>
257
253
  list<TFields extends BlogPostFields | undefined, TPopulate extends BlogPostPopulate | undefined>(params?: BlogPostQuery<TFields, TPopulate>): Promise<CollectionPage<BlogPostResult<TFields, TPopulate>>>
258
254
  get<TFields extends BlogPostFields | undefined, TPopulate extends BlogPostPopulate | undefined>(params?: BlogPostQuery<TFields, TPopulate>): Promise<BlogPostResult<TFields, TPopulate>[]>
259
255
  create(payload: BlogPostInput): Promise<BlogPost>
260
256
  update(id: string, payload: Partial<BlogPostInput>): Promise<BlogPost>
261
- remove(id: string, options?: { locale?: string }): Promise<void>
257
+ remove(id: string): Promise<void>
262
258
 
263
259
  // react-query hooks
264
- useGetById(id, { locale?, populate? }) // → { data: BlogPostResult<undefined, TPopulate> | undefined, isLoading, ... }
260
+ useGetById(id, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | undefined, isLoading, ... }
265
261
  useGetBySlug(slug, { populate? }) // → { data: BlogPostResult<undefined, TPopulate> | null | undefined, ... } — ideal for /:slug detail routes
266
262
  useGetByField(fieldName, value, { populate? })
267
263
  useList<TFields, TPopulate>(params?) // → { data: CollectionPage<BlogPostResult<TFields, TPopulate>> | undefined, ... }
@@ -283,7 +279,7 @@ update(payload: Partial<SettingsInput>): Promise<Settings>
283
279
  useGet(options?) / useSuspenseGet(options?) / useUpdate()
284
280
  ```
285
281
 
286
- Singleton clients do not expose `create`, `remove`, `getById`, or list queries. Localized singletons have one variant per locale; shared singletons have one entry across locales.
282
+ Singleton clients do not expose `create`, `remove`, `getById`, or list queries. Localized singletons have one variant per locale; non-localized singletons have one locale-free row.
287
283
 
288
284
  Choose the collection interface from the UI behavior. Use `useList` when the UI controls a page, and read pagination state from `pageInfo`:
289
285
 
@@ -339,10 +335,10 @@ BlogPost.useGet({
339
335
  ```
340
336
 
341
337
  Operators: scalar equality, `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$startsWith`, `$null`.
342
- Reference field values are reference objects, but reference filters use the target `id` string, for example `where: { author: author.id }`; for to-many references that means "contains this target".
338
+ Reference field values are reference objects, but reference filters use the target `documentId` string, for example `where: { author: "750e..." }`; for to-many references that means "contains this target document".
343
339
  For to-many references, `$eq` and `$contains` mean membership, `$in` means any listed document is present, `$ne` and `$nin` mean none are present, and `$null: true` means empty or missing. Reference fields cannot be sorted.
344
340
 
345
- Use `fields` to keep list payloads small. The API always keeps `id`, `entryId`, `documentId`, and `locale`, and returns only the requested fields beyond that:
341
+ Use `fields` to keep list payloads small. The API always keeps `id`, and returns only the requested fields beyond that:
346
342
 
347
343
  ```tsx
348
344
  const { data: summaries = [] } = BlogPost.useGet({
@@ -350,7 +346,7 @@ const { data: summaries = [] } = BlogPost.useGet({
350
346
  sort: '-publishedAt',
351
347
  limit: 12,
352
348
  });
353
- // summaries: Array<Pick<BlogPost, 'id' | 'entryId' | 'documentId' | 'locale' | 'slug' | 'title' | 'excerpt' | 'coverImage'>>
349
+ // summaries: Array<Pick<BlogPost, 'id' | 'slug' | 'title' | 'excerpt' | 'coverImage'>>
354
350
  ```
355
351
 
356
352
  Use `populate` to embed reference targets in read results:
@@ -360,7 +356,7 @@ const { data: posts = [] } = BlogPost.useGet({
360
356
  fields: ['slug', 'title', 'author'] as const,
361
357
  populate: ['author'] as const,
362
358
  });
363
- // Without populate: post.author is { id: "..." }.
359
+ // Without populate: post.author is { documentId: "..." }.
364
360
  // With populate: post.author is the target Author entry object, or null.
365
361
  ```
366
362
 
@@ -388,16 +384,16 @@ Populate is depth-one. It only works for reference fields; trying to populate a
388
384
 
389
385
  The entry objects a hook returns are **not** plain JS values. The generated client wraps each _trackable_ field in a `fimo/ui` primitive that carries source metadata for inline editing. Which fields wrap, and which stay plain:
390
386
 
391
- | Schema field type | Field value you get | Notes |
392
- | ----------------- | ----------------------------------- | ------------------------------------------------------------------------------------- |
393
- | `string` | `FimoString` | `extends String` — **not** a primitive `string` |
394
- | `richtext` | `FimoRichText` | Tiptap JSONContent + source metadata |
395
- | `boolean` | `FimoBoolean` | class; `.valueOf()` → `boolean` |
396
- | `media` | `FimoMedia` / `FimoMedia[]` | shape + rendering in `references/assets.md` |
397
- | `number` | `number` | plain — not wrapped |
398
- | `json` | `Record<string, any>` | plain — not wrapped |
399
- | `date` | `any` (raw ISO date string) | not wrapped (`FimoDate` is not emitted — see below) |
400
- | `reference` | `FimoReference` / `FimoReference[]` | reference object(s) with target `id`; use `populate` when embedded targets are needed |
387
+ | Schema field type | Field value you get | Notes |
388
+ | ----------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------- |
389
+ | `string` | `FimoString` | `extends String` — **not** a primitive `string` |
390
+ | `richtext` | `FimoRichText` | Tiptap JSONContent + source metadata |
391
+ | `boolean` | `FimoBoolean` | class; `.valueOf()` → `boolean` |
392
+ | `media` | `FimoMedia` / `FimoMedia[]` | shape + rendering in `references/assets.md` |
393
+ | `number` | `number` | plain — not wrapped |
394
+ | `json` | `Record<string, any>` | plain — not wrapped |
395
+ | `date` | `any` (raw ISO date string) | not wrapped (`FimoDate` is not emitted — see below) |
396
+ | `reference` | `FimoReference` / `FimoReference[]` | reference object(s) with target `documentId`; use `populate` when embedded target rows are needed |
401
397
 
402
398
  Rules of thumb:
403
399
 
@@ -438,7 +434,7 @@ export function BlogList() {
438
434
  return (
439
435
  <ul>
440
436
  {page.items.map((post) => (
441
- <li key={`${post.id}:${post.locale}`}>
437
+ <li key={post.id}>
442
438
  <Text value={post.title} as="h2" />
443
439
  <Image value={post.coverImage} width={800} height={400} fit="cover" />
444
440
  <RichText value={post.body} className="prose" />
@@ -455,9 +451,9 @@ For the `<Text>` / `<RichText>` / `<Image>` / `<Date>` components and the rules
455
451
  ## Read path (published app, no auth)
456
452
 
457
453
  ```ts
458
- const res = await fetch(`${import.meta.env.VITE_API_URL}/entries/<typeName>?locale=en&fields=slug,title&limit=12`);
454
+ const res = await fetch(`${import.meta.env.VITE_API_URL}/entries/<typeName>?fields=slug,title&limit=12`);
459
455
  const { data, meta } = await res.json();
460
- // data: Entry[] — fields flattened at top level (id, entryId, documentId, locale, slug, title, ...)
456
+ // data: Entry[] — fields flattened at top level (id, slug, title, ...)
461
457
  // meta: { limit, offset, total, hasMore }
462
458
  ```
463
459
 
@@ -67,14 +67,6 @@ This helper exists so the Fimo UI route selector, preview navigation, labels, an
67
67
 
68
68
  Copyable examples live in `assets/react-router-locales/routes.ts` and `assets/react-router-locales/navigation.tsx`.
69
69
 
70
- ## Existing Custom Locale Systems
71
-
72
- Before changing a multilingual app, inspect its locale provider, route table, language switcher, redirects, and current public URLs. Preserve that behavior while migrating.
73
-
74
- For React Router projects, prefer converging on one locale contract owned by `fimo-config.json`, `fimoRoutes()`, and `FimoLink`. Move existing providers and language-switcher UI into the framework root instead of replacing them or adding a second locale context. Keep route metadata canonical and reuse page modules across locales.
75
-
76
- If adopting the standard helper would change established public URLs, do not silently rewrite them. Keep the custom router temporarily, pass its resolved locale to `<FimoProviders locale={locale}>`, and ask the user to choose the canonical URLs and redirects before normalizing the route table.
77
-
78
70
  ## Links
79
71
 
80
72
  Use `FimoLink` for internal links that should preserve or switch locale:
@@ -1,4 +1,4 @@
1
- # Setup Fimo on React Router 8
1
+ # Setup Fimo on React Router 7
2
2
 
3
3
  This recipe describes the canonical Fimo + React Router framework-mode setup.
4
4
  It mirrors what `fimo init` scaffolds today; you can also apply it to an
@@ -6,7 +6,7 @@ existing RR project.
6
6
 
7
7
  ## Prerequisites
8
8
 
9
- - Node 22.22+
9
+ - Node 20+
10
10
  - A Fimo project created via `fimo init` (gives you `projectId`,
11
11
  `organizationId`, and an `apiUrl`).
12
12
 
@@ -42,7 +42,7 @@ The template uses **Vite + React 19 + TypeScript + Tailwind v4 + shadcn/ui + rea
42
42
 
43
43
  ### Routing
44
44
 
45
- - This template uses React Router 8 framework mode (file-based route config in `src/routes.ts`). Use `<FimoLink>` from `fimo/react-router` for locale-aware internal links, especially in projects with more than one configured locale. Use React Router `<Link>` / `<NavLink>` only when you intentionally do not want Fimo's locale behavior. Never raw `<a href="/...">` for internal navigation because it causes full page reloads.
45
+ - This template uses React Router v7 framework mode (file-based route config in `src/routes.ts`). Use `<FimoLink>` from `fimo/react-router` for locale-aware internal links, especially in projects with more than one configured locale. Use React Router `<Link>` / `<NavLink>` only when you intentionally do not want Fimo's locale behavior. Never raw `<a href="/...">` for internal navigation because it causes full page reloads.
46
46
  - Reserve `<a>` for external links, with `target="_blank" rel="noreferrer"`.
47
47
 
48
48
  ### Loading states