fimo 2.6.0 → 2.7.0-staging.1

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 (34) hide show
  1. package/assets/skills/fimo-cli/SKILL.md +1 -1
  2. package/assets/skills/fimo-cli/references/analytics.md +14 -0
  3. package/assets/skills/fimo-cli/references/content.md +2 -2
  4. package/assets/skills/fimo-cli/references/hosting.md +11 -0
  5. package/assets/skills/fimo-cli/references/locales.md +2 -2
  6. package/assets/skills/fimo-cli/references/pages.md +14 -2
  7. package/assets/skills/fimo-code/references/frameworks/astro.md +2 -0
  8. package/assets/skills/fimo-code/references/frameworks/next.md +12 -9
  9. package/assets/skills/fimo-code/references/ui.md +6 -1
  10. package/assets/skills/fimo-migration/SKILL.md +9 -6
  11. package/assets/skills/fimo-migration/references/frameworks/nextjs.md +24 -27
  12. package/assets/skills/fimo-migration/references/media.md +98 -62
  13. package/assets/skills/fimo-migration/references/qa.md +111 -11
  14. package/assets/skills/fimo-migration/references/workers/content-execution.md +2 -1
  15. package/assets/skills/fimo-migration/references/workers/validator.md +22 -6
  16. package/assets/skills/fimo-migration/references/workers.md +5 -3
  17. package/assets/skills/fimo-migration/scripts/check-rendered-guardrails.mjs +500 -0
  18. package/assets/skills/fimo-studio/references/publish-and-deploy.md +1 -0
  19. package/dist/cli/bundle.json +1 -1
  20. package/dist/cli/index.js +26 -26
  21. package/dist/runtime/paths/get-fimo-paths.d.ts +3 -1
  22. package/dist/runtime/paths/get-fimo-paths.js +59 -5
  23. package/dist/runtime/primitives/lib/primitives.d.ts +20 -1
  24. package/dist/runtime/primitives/lib/primitives.js +24 -1
  25. package/dist/runtime/routes/index.d.ts +19 -3
  26. package/dist/runtime/routes/index.js +27 -2
  27. package/dist/scripts/validate-page-registry.js +14 -3
  28. package/package.json +10 -9
  29. package/release.json +2 -2
  30. package/templates/astro/package.json +2 -2
  31. package/templates/nextjs/package.json +3 -3
  32. package/templates/react-router/package.json +4 -4
  33. package/templates/sveltekit/package.json +2 -2
  34. package/templates/vite/package.json +4 -4
@@ -44,7 +44,7 @@ A Fimo project is identified by `.fimo/project.json`, written by `fimo init`. Th
44
44
 
45
45
  ## Critical: commands target the Branch matching the current git branch
46
46
 
47
- A project can have multiple **Branches** — each is a git branch _plus_ its own DB, sandbox, preview, and assets. Project-scoped commands (`deploy`, `status`, `entries`, `diff`, …) operate on the Branch matching the **current git branch**, and print a one-line banner saying which one. New projects start on `main`. Override for a single command with the canonical `--branch <name>` flag; the removed `--env <name>` spelling is rejected. Never edit the link file to switch. The full git-shaped workflow (`branch` / `checkout` / `switch` / `pull` / `diff` / `merge` / `status` / `publish`) lives in `references/branches.md`.
47
+ A project can have multiple **Branches** — each is a git branch _plus_ its own DB, sandbox, preview, and assets. Project-scoped commands (`deploy`, `status`, `entries`, `diff`, …) operate on the Branch matching the **current git branch**, and print a one-line banner saying which one. Analytics reads are the exception: they default to canonical `prod`, independent of the checkout; see `references/analytics.md`. Annotation writes keep the current or explicit Branch. New projects start on `main`. Override for a single command with the canonical `--branch <name>` flag; the removed `--env <name>` spelling is rejected. Never edit the link file to switch. The full git-shaped workflow (`branch` / `checkout` / `switch` / `pull` / `diff` / `merge` / `status` / `publish`) lives in `references/branches.md`.
48
48
 
49
49
  ## Quick Start
50
50
 
@@ -4,6 +4,19 @@ Pageview aggregates for the **published** site are stored in Fimo's analytics pi
4
4
 
5
5
  Analytics has a **retained preview for every org** — free included. The free default is a 7-day retention window, so short ranges (`today`, `yesterday`, `this_week`, `last_week`, `last_7_days`) work for everyone. **Extended date ranges and longer retention are paid** — `last_30_days`, `last_90_days`, `this_month`, `last_month`, `last_quarter`, `last_year`, and `custom` require the `canUseExtendedDateRanges` privilege, which only paid orgs have. Asking for an extended range as a free user returns a 403 with an upgrade hint.
6
6
 
7
+ ## Select published traffic
8
+
9
+ For traffic, growth, or site-performance questions, read **`prod`**, regardless of the current Git branch, `FIMO_ENV`, or the source branch published to Production. Analytics reads (`pageviews`, `top`, `annotations list`) default to this canonical published target. On older CLI versions, select it explicitly:
10
+
11
+ ```bash
12
+ fimo --branch prod analytics pageviews --days 7 --json
13
+ fimo --branch prod analytics top countries --json
14
+ ```
15
+
16
+ Structured read results include `branch` (`data.branch` in the CLI JSON envelope). Check it before interpreting metrics. An intentional `--branch <name>` overrides the default; zero traffic on `main` or a preview branch does **not** mean the published site has no traffic. Preview visits are not collected. If publication or access is unavailable, report that limitation; do not fall back to another branch or present the failure as zero traffic.
17
+
18
+ SDK and hosted MCP reads also default omitted `branch` to `prod` and return the selected `branch`. Annotation **writes** keep the current/explicit Branch and its existing permissions; the read default does not grant permission to write to `prod`.
19
+
7
20
  ## Bot traffic is excluded
8
21
 
9
22
  Every number below is **humans only**. Bot hits — AI crawlers and headless browsers — are tagged
@@ -37,6 +50,7 @@ The headline command returns everything in one payload — KPIs, the visitors se
37
50
 
38
51
  ```json
39
52
  {
53
+ "branch": "prod",
40
54
  "range": { "preset": "last_30_days", "effectiveFrom": "...", "effectiveTo": "...", "groupBy": "day", ... },
41
55
  "deviceFilter": null,
42
56
  "visitors": 1240, "pageviews": 3870, "bounceRate": 42.5, "viewsPerVisit": 3.12, "averageVisitDurationSeconds": 95.4,
@@ -71,7 +71,7 @@ fimo entries create <TypeName> --body '<json>'
71
71
  fimo entries create <TypeName> --locale es --document-id <documentId> --body '<json>'
72
72
  fimo entries list <TypeName> [--limit N] [--cursor CURSOR] [--all-pages] [--search Q] [--where JSON] [--sort F] [--fields FIELDS] [--populate FIELDS]
73
73
  fimo entries list <TypeName> --locale es
74
- fimo entries get <TypeName> <id> [--locale es]
74
+ fimo entries get <TypeName> <id> [--locale es] [--populate FIELDS]
75
75
  fimo entries update <TypeName> <id> [--locale es] --body '<json>'
76
76
  fimo entries delete <TypeName> <id> [--locale es]
77
77
  fimo entries query (--sql '<select>' | --file <path> | --stdin)
@@ -101,7 +101,7 @@ Use the canonical read query flags: `--search`, `--sort`, `--where`, `--limit`,
101
101
  Reference filters take the target `id` string (`{"author":"750e..."}`). On a to-many field, `$eq` and `$contains` mean membership, `$in` means any listed target is present, `$ne` and `$nin` mean none are present, and `$null: true` means empty or missing. Reference fields cannot be sorted; use `--sort field` / `--sort -field` on scalar fields.
102
102
  `--fields` is a comma-separated projection for list payloads. The API always keeps `id`, `entryId`, `documentId`, and `locale`; pass only the other fields your view needs, such as `slug,title,excerpt,coverImage`.
103
103
 
104
- `--populate` is a comma-separated list of reference fields to embed. Without it, reference fields return reference objects like `{ "id": "..." }`; with `--populate author,contributors`, those fields return the target entry objects for the requested locale. Only reference fields can be populated (scalar fields 400), and populate never selects which target fields come back. A populated target missing in the requested locale resolves to null unless you pass `localeFallbacks=true`, which falls back to the best available locale variant, marked by its `locale` field. Limits: 200 references per to-many write, 500 distinct populated documents per field per request (400 past either).
104
+ `--populate` is a comma-separated list of top-level fields to embed, on `entries list`, `entries get`, and `singles get`. It accepts reference fields, media fields, and blocks fields that contain media; naming a blocks field populates every media value inside it. Nested paths such as `sections.image` are not supported, and other field types return 400. Run `fimo schemas get <TypeName> --json` to inspect field names and types. Without it, reference fields return `{ "id": "..." }` and media fields return the stored `{ "source", "id", "url", "alt" }`; with `--populate author,cover`, `author` returns the target entry for the requested locale and `cover` adds the asset's current `name`, `mime`, `width`, and `height`. Populate never selects which target fields come back. A populated target missing in the requested locale resolves to null unless you pass `localeFallbacks=true`, which falls back to the best available locale variant, marked by its `locale` field. Limits: 200 references per to-many write, 500 distinct populated documents per field per request (400 past either).
105
105
 
106
106
  Polymorphic references declare `targets: ["Post", "Product"]` instead of `target` (never both) and their values carry both parts: `{ "contentType": "Post", "id": "..." }`. Populate returns mixed full entries, and generated hooks expose `$type` on each populated value to branch on. `--where` on polymorphic fields is not supported yet and fails with a clear error. Fixed and polymorphic references also work inside blocks with these same write shapes (`id`, `documentId`, or `slug` selectors), but nested fields cannot be populated or filtered.
107
107
 
@@ -33,6 +33,17 @@ Declare redirects, headers, rewrites, and URL shape in
33
33
 
34
34
  Every field is optional. Only what you declare is emitted.
35
35
 
36
+ ## Preview and security headers
37
+
38
+ A restrictive application-owned CSP or `X-Frame-Options` policy can block Studio
39
+ Preview. Do not assume automatic header rewriting; Next.js `withFimo` does not
40
+ provide it. Inspect the served policy and scope any manual adaptation to Preview,
41
+ preserving production protections and unrelated headers. `frame-ancestors`
42
+ permits the Studio parent, not the framed app's preview origin. Never derive
43
+ ancestor sources from a preview-origin variable. For `script-src`, `connect-src`,
44
+ `frame-src`, and `form-action`, use resolved/observed Fimo resources and endpoints
45
+ actually used by the iframe. Studio-parent WebSockets do not belong in app CSP.
46
+
36
47
  ## Matching
37
48
 
38
49
  `source` matches the pathname only, never the query string.
@@ -90,7 +90,7 @@ Write the locale into a route's path with the reserved `:locale` segment, never
90
90
  | `/:locale/blog/:slug` | `{ "type": "locale", "optional": false }` | `/en/blog/hello` and `/fr/blog/bonjour` |
91
91
  | `/:locale/pricing` | omitted | `/pricing` for `en`, `/fr/pricing` for `fr` |
92
92
 
93
- - `locale` is reserved. It is read from the URL, so it never takes `schema` or `queryField` and never becomes a CMS query. Every other param is content-backed and needs both.
93
+ - `locale` is reserved. It is read from the URL, so it never takes `schema` or `queryField` and never becomes a CMS query. Every other param is an `entry` param that needs both, unless it declares `"type": "external"`: an external param takes no other keys, and the application owns its values.
94
94
  - `optional: true` means omit the configured default locale from the URL. With `i18n.defaultLocale: "en"`, `/pricing` is English and `/fr/pricing` is French.
95
95
  - `optional: false` prefixes every locale, including the default (`/en/pricing` and `/fr/pricing`). Changing `i18n.defaultLocale` chooses the fallback locale; it does not by itself change this prefix policy.
96
96
  - A route with no `:locale` segment stays application-owned. Studio shows it once and neither adds nor interprets a locale segment.
@@ -102,7 +102,7 @@ Register the same shape in the router. React Router spells the omittable segment
102
102
  route(':locale?/blog/:slug', './pages/BlogPost.tsx');
103
103
  ```
104
104
 
105
- These canonical entries are also the optional metadata used by Studio Pages. See `references/pages.md` for content-backed `params` and declared `sources`.
105
+ These canonical entries are also the optional metadata used by Studio Pages. See `references/pages.md` for the three `params` types and declared `sources`.
106
106
 
107
107
  For every framework, the application owns its URL scheme, resolves the locale, and passes it into Fimo. Studio stores the locale the app reports for the current location — including when only the locale changed — and reads content in it. Route metadata is optional unless the project wants Fimo Studio route selection/navigation.
108
108
 
@@ -7,15 +7,27 @@ Pages can mirror the application's routes in Fimo Studio. It gives editors a rou
7
7
  A route has three separate parts:
8
8
 
9
9
  - `path` is the URL pattern, such as `/blog/:slug`.
10
- - `params` optionally binds path placeholders to Collection entries so Studio can select a concrete route in the page header.
10
+ - `params` optionally describes each path placeholder: an entry bound to a Collection so Studio can select a concrete route in the page header, the reserved `locale` segment, or a value the application owns.
11
11
  - `sources` optionally names the Labels, Single, and Collection results editors manage after selecting a route.
12
12
 
13
- Each param key matches a `:placeholder` in `path`. `schema` selects the Collection, `queryField` is the entry field stored in the URL, and `displayField` is the field Studio shows to editors. Use `id` or the built-in `slug` for `queryField`; a custom URL field must set both `"required": true` and `"unique": true` in its schema. Params resolve in path order, so a later param query may use an earlier selection:
13
+ Each param key matches a `:placeholder` in `path`. For an entry param, `schema` selects the Collection, `queryField` is the entry field stored in the URL, and `displayField` is the field Studio shows to editors. Use `id` or the built-in `slug` for `queryField`; a custom URL field must set both `"required": true` and `"unique": true` in its schema. Params resolve in path order, so a later param query may use an earlier selection:
14
14
 
15
15
  ```bash
16
16
  fimo pages set <id> --path '/shop/:category/:product' --params '{"category":{"schema":"Category","queryField":"slug","displayField":"name"},"product":{"schema":"Product","queryField":"slug","displayField":"name","query":{"where":{"category":"$params.category.id"},"sort":"name"}}}'
17
17
  ```
18
18
 
19
+ A descriptor's `type` is optional and selects one of three param kinds:
20
+
21
+ - `entry` (the default) binds the placeholder to a Collection with `schema`, `queryField`, `displayField`, and an optional `query`.
22
+ - `locale` is the reserved `:locale` segment with an optional `optional` boolean. See `references/locales.md`.
23
+ - `external` takes no other keys. The application owns the values: Fimo matches the segment from the URL and never looks it up in the CMS.
24
+
25
+ ```bash
26
+ fimo pages set <id> --path '/cartes/:slug' --params '{"slug":{"type":"external"}}'
27
+ ```
28
+
29
+ Declare a placeholder `external` when its values are not Fimo content — a catalogue synced from an upstream source and stored as a media asset, ids that exist only in the visitor's browser, computed archive or pagination segments, third-party ids. Never bind a placeholder to an unrelated Collection to make `fimo validate` pass. `fimo pages list` reports `dynamic:external` and `fimo pages list --expand` skips the route; `fimo/paths` and the generated sitemap emit nothing for it, so the application enumerates those URLs itself. A `$params.<name>.<field>` expression in a param query or a source may reference entry params only. A route may mix kinds: entry params keep their selectors in Studio, and an external param takes a typed value in the preview header.
30
+
19
31
  `sources` is a named object under `routes.<id>.sources`. It is not an array or a route definition. Keep its keys stable and descriptive:
20
32
 
21
33
  ```json
@@ -170,6 +170,8 @@ The new-project shell pairs unprefixed default-locale pages with a parallel `[lo
170
170
  }
171
171
  ```
172
172
 
173
+ A dynamic segment whose values are not Fimo content takes `{ "type": "external" }` instead of a Collection binding; Fimo matches it from the URL, and the page's own `getStaticPaths` (or on-demand rendering) supplies those values.
174
+
173
175
  ## Client islands
174
176
 
175
177
  Generated content reads remain server-only. Preserve existing islands and do not add React or a client SDK to work around this boundary.
@@ -46,11 +46,8 @@ own tenant URL.
46
46
 
47
47
  ## Hosted preview config
48
48
 
49
- Wrap the existing Next config once so `next dev` accepts Fimo's hosted preview
50
- origins without exposing the active preview provider to the application. If
51
- the application does not set Next's `devIndicators`, the wrapper hides it in
52
- hosted Preview while local `next dev` keeps Next's default. Any explicit
53
- `devIndicators` setting is preserved:
49
+ Keep the generated `FimoProvider` and `withFimo` integration to render Fimo
50
+ Preview in Fimo Studio and enable Edit Mode. Wrap the existing Next config once:
54
51
 
55
52
  ```ts
56
53
  import type { NextConfig } from 'next';
@@ -60,10 +57,16 @@ const nextConfig: NextConfig = {};
60
57
  export default withFimo(nextConfig);
61
58
  ```
62
59
 
63
- Preserve all existing config fields or config-function logic. Do not hardcode a
64
- preview domain in `allowedDevOrigins`; Fimo supplies it to the development
65
- sandbox. The wrapper changes nothing outside `next dev` and is not the
66
- application's Fimo connection.
60
+ Preserve existing config fields and config-function logic. `withFimo` currently
61
+ does not rewrite response headers or adapt application-owned CSP. A restrictive
62
+ policy may need a manual Preview adaptation; inspect actual framing and blocked
63
+ requests, preserve production protections, and verify the result in a browser.
64
+ `frame-ancestors` permits the Studio parent, not the app's preview origin or a
65
+ value derived from a preview-origin variable. `script-src`, `connect-src`,
66
+ `frame-src`, and `form-action` concern resources/endpoints used by the iframe;
67
+ use the resolved connection and observed requests. Never add a Studio-parent
68
+ WebSocket to application CSP. Scope exceptions to Preview and keep unrelated
69
+ security headers unchanged.
67
70
 
68
71
  ## Client dependency
69
72
 
@@ -53,7 +53,7 @@ Use `<StaticImage>` only when the image is deliberately not editor-managed.
53
53
  `FimoString`, `FimoBoolean`, and the other tracked values are objects, not primitives: a plain string or boolean cannot carry the source field, locale, and edit-target metadata Fimo editing needs. That gives one two-sided rule:
54
54
 
55
55
  - **MUST** pass the wrapper directly to Fimo rendering primitives (`Text`, `Boolean`, …). Unwrapping first removes the edit target.
56
- - **MUST** normalize the wrapper before any JavaScript comparison, condition, attribute, or third-party API: `String(value)` for strings, `value.value` (or `value.valueOf()`) for booleans.
56
+ - **MUST** normalize the wrapper before any JavaScript comparison, condition, attribute, or third-party API: `String(value)` for strings, `value.value` for booleans.
57
57
 
58
58
  ```ts
59
59
  fimoValue === 'build'; // false — an object never strict-equals a primitive
@@ -63,6 +63,11 @@ if (fimoFlag) { … } // always runs — an object is truthy even whe
63
63
  if (fimoFlag.value) { … } // correct
64
64
  ```
65
65
 
66
+ Nothing catches `if (fimoFlag)` for you: an object is unconditionally truthy in
67
+ JavaScript, and TypeScript reports no error. Read `.value` every time. A boolean
68
+ field without `required: true` generates `flag?: FimoBoolean`, so read
69
+ `flag?.value`.
70
+
66
71
  Keep editable visible text in `<Text>`; normalized primitives are for logic and APIs only, never a substitute for rendering through the primitive.
67
72
 
68
73
  Numeric fields stay plain JavaScript numbers, so they do not carry Studio source metadata. SvelteKit's `Number` component is an optional display/formatting helper; it is not source-editable. Other framework surfaces should render numeric fields normally until a shared tracked-number contract exists.
@@ -174,18 +174,18 @@ report the exact failed command and setup requirement.
174
174
  reason. Better modeling never redesigns the site: preserve the source's
175
175
  content shape and behavior, and record the source-to-Fimo mapping
176
176
  (`references/content.md`).
177
- - Media owns its alt text. Preserve the source alt on the media value and pass
178
- it to asset metadata when the upload command supports it; never add a
179
- standalone alt field to a content schema. Record the limitation when the
180
- command cannot carry it (`references/media.md`).
177
+ - Before writing media or wiring `<Image>`, load
178
+ `fimo skills get fimo-migration --ref media` for the stored value, explicit
179
+ `populate`, and responsive-image checks (`references/media.md`).
181
180
  - Every write is create-or-update, so any step can be rerun without duplicating
182
181
  a schema, entry, asset, single, or label.
183
182
  - Every decision cites the source file (and line when it matters) that proves
184
183
  it. What no worker could prove stays unresolved with an owner action.
185
184
  - Keep coordination evidence in the plain-text files
186
185
  `.fimo/migration/migration-plan.md` and
187
- `.fimo/migration/migration-report.md`. Use JSON only for actual Fimo
188
- schema/content payloads that a data-plane command needs to import. Do not
186
+ `.fimo/migration/migration-report.md`. Use JSON for actual Fimo
187
+ schema/content import payloads and the rendered guardrail inventory and
188
+ capture response metadata required by `references/qa.md`. Do not
189
189
  create a target description, worker plan, decision registry, or JSON receipt
190
190
  just to describe the filesystem or agent work.
191
191
  - Do not report completion until the QA gate passes or every failed check is
@@ -378,6 +378,9 @@ writes go through the normal commands against the linked target:
378
378
  - `references/workers/content-execution.md` - data import and code-wiring
379
379
  contracts.
380
380
  - `references/workers/validator.md` - independent fan-in validation.
381
+ - `scripts/check-rendered-guardrails.mjs` evaluates the cited guardrail
382
+ inventory against rendered HTML or a local server and fails closed on empty,
383
+ missing, redirected, or vacuous evidence.
381
384
  - `scripts/apply-decision.mjs` and `scripts/write-migration-report.mjs` replay
382
385
  an older JSON-ledger migration. A new migration writes the plain-text plan
383
386
  and report instead; do not invoke them unless the user asks for that replay.
@@ -99,8 +99,7 @@ Follow the target project's `fimo-code` Next.js reference and render its
99
99
  `FimoProvider` once at the application root. The provider uses an explicit
100
100
  host connection when one exists; otherwise it resolves the linked project and
101
101
  active git branch on the server and passes only the public connection to the
102
- browser. `withFimo` handles hosted preview origins, not the application
103
- connection.
102
+ browser.
104
103
 
105
104
  Do not create or edit `.env` / `.env.local` for a linked project's Fimo
106
105
  connection. Run the project's normal `next dev`, `next build`, and `next start`
@@ -108,31 +107,29 @@ commands, then exercise at least one server read and one client-side data read.
108
107
 
109
108
  ## Preview embedding and response headers
110
109
 
111
- Fimo Preview loads the app in an iframe and drives it over the postMessage
112
- preview bridge, so development/preview responses must let the preview origin
113
- frame the app.
114
-
115
- - `withFimo` consumes `FIMO_PREVIEW_ALLOWED_ORIGINS` (platform-supplied,
116
- comma-separated `**.<domain>` patterns) only to extend `allowedDevOrigins`
117
- during `next dev`. It does not add, remove, or rewrite response headers;
118
- wrapping the config never fixes a framing block by itself.
119
- - When the source `headers()` sends `X-Frame-Options` or a CSP
120
- `frame-ancestors` directive, keep `X-Content-Type-Options`,
121
- `Strict-Transport-Security`, and `Referrer-Policy` unchanged, and translate
122
- the framing block in the migrated config. For a hosted/remote preview, while
123
- `FIMO_PREVIEW_ALLOWED_ORIGINS` is set, omit `X-Frame-Options` and send
124
- `Content-Security-Policy: frame-ancestors 'self' https://*.<domain>` derived
125
- from each `**.<domain>` entry. For local `next dev`, when the platform
126
- variable is absent, omit the framing block only while
127
- `process.env.NODE_ENV !== 'production'`; keep the source framing policy in
128
- production. Never hardcode a preview provider domain.
129
- - Record every translated or dropped header in the runtime report's Hosting
130
- section.
131
-
132
- Without a browser, verify embeddability deterministically: with the variable
133
- set, request a route from the running dev server and assert the response
134
- headers carry no unconditional framing block, then mark visual proof
135
- unavailable in the QA report.
110
+ Keep the generated `FimoProvider` and `withFimo` integration. It supports
111
+ rendering Fimo Preview in Fimo Studio and enabling Edit Mode.
112
+
113
+ The application owns response and security headers; `withFimo` currently does
114
+ not rewrite them or adapt CSP automatically. A restrictive policy may still
115
+ need a manual Preview adaptation. Inspect framing behavior and response headers
116
+ first. Scope changes to Preview, preserve production protections and unrelated
117
+ security headers, and record them in the runtime report's Hosting section.
118
+ When the existing policy sends `X-Frame-Options` or an unconditional
119
+ `frame-ancestors` block, condition or relax that framing control only for the
120
+ verified Preview embedding context; retain it for non-Preview and production
121
+ responses.
122
+
123
+ - `frame-ancestors` controls which Fimo Studio parent may embed the app. The
124
+ incident's manual fix used the current Studio sources `https://fimo.ai` and
125
+ `https://*.fimo.ai`. Verify the Studio parent for the target environment;
126
+ these sources are not a universal resource allowlist. Never derive them from
127
+ a preview-origin variable. The sandbox or hosted preview origin identifies
128
+ the app being framed, not its ancestor.
129
+ - `script-src` controls scripts, `connect-src` API and WebSocket connections,
130
+ `frame-src` frames embedded by the app, and `form-action` form submission
131
+ destinations. Base Fimo sources on the resolved connection and observed
132
+ requests made by the preview iframe, only for resources/endpoints it uses.
136
133
 
137
134
  ## Locales
138
135
 
@@ -18,7 +18,7 @@ Prefer local files from exports. Use remote URLs when no local file exists.
18
18
 
19
19
  ## Required Metadata
20
20
 
21
- Record what is available:
21
+ Record what is available in the inventory and reports:
22
22
 
23
23
  - source URL or source path
24
24
  - filename
@@ -29,47 +29,50 @@ Record what is available:
29
29
  - sha256 or another stable hash when possible
30
30
  - source references and target references
31
31
 
32
+ These are migration records, not extra fields to copy into entry media values.
33
+
32
34
  ## Alt Text Belongs To The Media
33
35
 
34
- Alt text is media metadata, not a content field. An image that has alt text in
35
- the source never justifies a standalone alt field in a collection or content
36
- schema; the alt travels with the media value.
36
+ Preserve each source occurrence's alt on its media value, including an explicit
37
+ empty alt. Do not add a separate schema field or use the filename as alt.
38
+
39
+ Neither `fimo assets upload` nor `fimo assets bulk-upload` currently accepts
40
+ alt metadata: keep the source alt on the value and note that upload limitation
41
+ in the migration report. Population preserves the entry's alt; it does not
42
+ synchronize it with asset alt metadata. If source alt is absent and you write
43
+ a meaningful fallback, identify it as generated in the report.
37
44
 
38
- - Preserve the source alt on the media value alongside `source` and `url`, and
39
- pass it through the asset upload metadata when the CLI exposes it. When the
40
- upload command cannot carry it, keep it on the media value and record the
41
- limitation as a note on the media item.
42
- - Never use the filename as alt text.
43
- - When the source has no alt, a meaningful generated alt is an explicit
44
- fallback only: mark it with the media item's `altSource`
45
- (`references/manifest.md`) so an editor can see which alt text the source did
46
- not provide.
45
+ Use the relevant worker report and `.fimo/migration/migration-report.md` for
46
+ provenance and dispositions. New migrations do not create a canonical
47
+ `manifest.json`; see `fimo skills get fimo-migration --ref manifest` for legacy
48
+ manifest handling.
47
49
 
48
50
  ## Dedupe
49
51
 
50
- Deduplicate by hash when local bytes are available. If not, deduplicate by
51
- canonicalized URL plus filename. Keep all original references even when multiple
52
- sources point to one uploaded asset.
52
+ Deduplicate by hash when local bytes are available. Otherwise use canonicalized
53
+ URL plus filename. Keep every source occurrence and its alt even when several
54
+ occurrences reuse one uploaded asset.
53
55
 
54
56
  ## Upload And Replacement
55
57
 
56
58
  Referenced media must be uploaded into the Fimo media library. Use
57
- `fimo assets bulk-upload --file <manifest.json>` for a batch and keep one
58
- unique `sourceId` per media item; use `fimo assets upload` for a single file.
59
- Never encode paths or names in whitespace-delimited shell input. Copying a file under `public/` so the deployed site can
60
- serve it is **not** a completed import: it has no Fimo asset id/URL, editors
61
- cannot manage it, and the QA gate treats a referenced-but-not-uploaded asset as
62
- failing. Either upload it (and record the Fimo target), or mark it `unresolved`
63
- with an owner action. "Bundled locally, upload later" is not an acceptable
64
- terminal state for referenced media.
65
-
66
- After upload, record the Fimo target asset ID/URL in the worker and migration
67
- reports.
68
-
69
- When using downloaded files from a Fimo migration pack, copy the recorded
70
- `localPath` from the media map exactly. Do not reconstruct local filenames from
71
- source URLs, because the downloader normalizes characters such as underscores,
72
- spaces, and encoded characters before writing files.
59
+ `fimo assets bulk-upload --file <manifest.json>` for a batch or
60
+ `fimo assets upload "<file-path>"` for a single file. The bulk-upload input is a
61
+ JSON array with a unique `sourceId` and a `path` per item. Paths resolve from the
62
+ project working directory, not the manifest's directory. Never encode paths or
63
+ names in whitespace-delimited shell input. Load
64
+ `fimo skills get fimo-cli --ref assets` for upload options and result details.
65
+ This upload payload is separate from the migration's Markdown reports.
66
+
67
+ Copying a file under `public/` is not a completed import: editors cannot manage
68
+ it in Fimo and it has no Fimo asset ID/URL. Either upload it and record the Fimo
69
+ target in the worker and migration reports, or mark it `unresolved` with an
70
+ owner action. "Bundled locally, upload later" is not a terminal disposition.
71
+
72
+ If a supplied Fimo migration pack includes a media map, copy its recorded
73
+ `localPath` exactly. Do not reconstruct filenames from source URLs or undo
74
+ normalization of underscores, spaces, or encoded characters. Verify the recorded
75
+ path exists before upload.
73
76
 
74
77
  Replace source references in this order:
75
78
 
@@ -78,34 +81,60 @@ Replace source references in this order:
78
81
  3. page components
79
82
  4. form definitions or confirmation content
80
83
 
81
- Schema media should render through Fimo media primitives. Do not hardcode source
82
- URLs for editable media fields.
83
-
84
- The library asset and the media value stored in an entry are distinct concerns:
85
- the asset owns the uploaded bytes and their metadata, and the entry field holds
86
- the media value `{ source, url, alt }` that points at it. Keep both carrying the
87
- same alt text, and do not duplicate alt into a second schema field.
88
-
89
- Do not copy upload metadata into media values yourself. Writing `{ source, url,
90
- alt }` is enough: the write path resolves and persists the media-library ID, and
91
- a read that names the field in `populate` returns the asset's current URL, name,
92
- MIME type, and intrinsic dimensions from the library. A read that does not name
93
- it returns the stored reference. The ID lets you inspect the asset later with
94
- `fimo assets open <id> --download`.
95
-
96
- Treat intrinsic dimensions as source facts, not layout instructions. Choose the
97
- displayed size, crop, `sizes`, loading behavior, and fit from the migrated page's
98
- design and responsive behavior.
84
+ Write each imported media field as `{ source, url, alt }`. For an uploaded
85
+ asset, use `{ source: 'local', url, alt }` with the URL returned by Fimo. Do not
86
+ copy the asset's ID, name, MIME type, width, height, or other upload metadata
87
+ into the stored value. The write path resolves and persists the media-library
88
+ ID; the asset owns the uploaded bytes and their metadata. Inspect an asset
89
+ later with `fimo assets open <id> --download`.
90
+
91
+ ### Populate and render
92
+
93
+ Use Fimo media primitives for schema media rather than hardcoded URLs. Load
94
+ `fimo skills get fimo-code --ref ui` and the detected framework reference for
95
+ component imports and usage.
96
+
97
+ Explicitly populate the media field before wiring it to `<Image>`. Without
98
+ `populate`, a read returns the stored reference `{ source, id, url, alt }`,
99
+ including its fallback URL, but does not look up current asset metadata. With
100
+ the field named in `populate`, it returns current asset facts from the library:
101
+ URL, name, MIME type, and intrinsic width and height when available.
102
+
103
+ For media inside blocks, name the top-level blocks field, for example
104
+ `--populate sections` or
105
+ `populate: ['sections'] as const`, not a nested image path. This populates all
106
+ media inside that field.
107
+
108
+ CLI examples:
109
+
110
+ ```bash
111
+ fimo entries list BlogPost --limit 1 --populate cover --json
112
+ fimo entries get BlogPost <entry-id> --populate cover --json
113
+ ```
114
+
115
+ Generated-client example:
116
+
117
+ ```tsx
118
+ const article = await BlogPost.getBySlug('hello', { populate: ['cover'] as const });
119
+ return article ? <Image value={article.cover} sizes="100vw" /> : null;
120
+ ```
121
+
122
+ Before completing the migration, verify that the populated raster has its
123
+ intrinsic `width` and `height`. Render the page at desktop and mobile widths,
124
+ then check the optimized responsive `srcset`. With optimization enabled on an
125
+ eligible Fimo URL, `sizes` set, and no custom `srcSet`, its generated width
126
+ candidates stop at the intrinsic width. Choose displayed size, crop, `sizes`,
127
+ and fit from the source layout; intrinsic dimensions are source facts, not
128
+ layout choices. Fix a missing `populate` request instead of copying metadata
129
+ into the entry or hardcoding intrinsic dimensions. If a populated read still
130
+ lacks dimensions, report the gap rather than inventing them.
99
131
 
100
132
  ## Failure Handling
101
133
 
102
- If a media asset cannot be downloaded or uploaded:
103
-
104
- - keep the source reference
105
- - mark the media item `unresolved` or `unsupported`
106
- - list every page/entry that references it
107
- - include a suggested action such as re-exporting the asset folder or providing
108
- credentials
134
+ If an asset cannot be downloaded or uploaded, keep its source reference,
135
+ record `unresolved` or `unsupported` in the report, and list affected
136
+ pages/entries, the failure reason, and an owner action, such as re-exporting the
137
+ asset folder or providing credentials.
109
138
 
110
139
  ## Media obligations
111
140
 
@@ -115,8 +144,15 @@ terminal, the coordinator combines and deduplicates those findings into the
115
144
  complete media-obligation list. This consolidated list is the media worker's
116
145
  only input; do not make the content planner rescan the whole application.
117
146
 
118
- Record each obligation's source location, consuming route/entry, and intended
119
- target field or node. Give it a stable identity that includes its field or
120
- rich-text node path; a shared label or source URL is not unique. The media
121
- worker cannot report complete until every obligation has an uploaded or
122
- duplicate Fimo mapping, or an explicit unresolved/unsupported disposition.
147
+ Keep one migration mapping per source occurrence: source file or page, field
148
+ or rich-text node path, consuming route/entry, and target field or node plus
149
+ the uploaded asset ID/URL. Use the source location and field or node path for
150
+ stable identity. A shared URL or label is not a unique occurrence;
151
+ retain every mapping even when occurrences reuse one uploaded asset.
152
+
153
+ The media worker cannot report complete until every obligation has an uploaded
154
+ or duplicate Fimo mapping, or an explicit unresolved/unsupported disposition.
155
+ Reconcile the mappings against every discovery lane's findings, including
156
+ schema media fields and nested richtext/Tiptap image nodes. Run the broader
157
+ migration gate from `fimo skills get fimo-migration --ref qa` before claiming
158
+ the migration is complete.