fimo 2.4.0-staging.1 → 2.4.0-staging.3
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.
- package/assets/skills/fimo-cli/SKILL.md +2 -2
- package/assets/skills/fimo-cli/references/branches.md +4 -4
- package/assets/skills/fimo-cli/references/deploy.md +2 -5
- package/assets/skills/fimo-cli/references/domains.md +5 -5
- package/assets/skills/fimo-cli/references/hosting.md +5 -4
- package/assets/skills/fimo-cli/references/preview.md +1 -1
- package/assets/skills/fimo-cli/references/projects.md +5 -5
- package/assets/skills/fimo-code/SKILL.md +1 -0
- package/assets/skills/fimo-code/references/assets.md +4 -2
- package/assets/skills/fimo-code/references/best-practices/next.md +0 -2
- package/assets/skills/fimo-code/references/content/schema.md +4 -1
- package/assets/skills/fimo-code/references/design.md +2 -2
- package/assets/skills/fimo-code/references/frameworks/astro.md +37 -1
- package/assets/skills/fimo-code/references/frameworks/hosting.md +23 -2
- package/assets/skills/fimo-code/references/frameworks/next/app-router.md +7 -21
- package/assets/skills/fimo-code/references/frameworks/next/seo.md +168 -0
- package/assets/skills/fimo-code/references/frameworks/next.md +1 -1
- package/assets/skills/fimo-code/references/frameworks/seo-head.md +78 -0
- package/assets/skills/fimo-code/references/frameworks/sveltekit.md +57 -4
- package/assets/skills/fimo-migration/SKILL.md +33 -33
- package/assets/skills/fimo-migration/references/content.md +1 -1
- package/assets/skills/fimo-migration/references/frameworks/nextjs.md +7 -0
- package/assets/skills/fimo-migration/references/import-contract.md +4 -6
- package/assets/skills/fimo-migration/references/manifest.md +4 -3
- package/assets/skills/fimo-migration/references/qa.md +6 -0
- package/assets/skills/fimo-migration/references/questions-and-improvements.md +1 -1
- package/assets/skills/fimo-migration/references/workflow.md +7 -5
- package/assets/skills/fimo-studio/references/billing-and-credits.md +7 -4
- package/assets/skills/fimo-studio/references/seo-settings.md +2 -0
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +494 -978
- package/dist/runtime/seo/head.d.ts +31 -0
- package/dist/runtime/seo/head.js +47 -0
- package/dist/runtime/seo/index.d.ts +2 -0
- package/dist/runtime/seo/index.js +1 -0
- package/dist/runtime/seo/resolver.d.ts +16 -2
- package/dist/runtime/seo/resolver.js +10 -0
- package/dist/runtime/seo/server.d.ts +13 -0
- package/dist/runtime/seo/server.js +23 -0
- package/dist/runtime/shared/fimo-config.server.d.ts +2 -0
- package/dist/runtime/shared/fimo-config.server.js +18 -14
- package/package.json +12 -8
- package/templates/astro/.fimo/config.json +1 -0
- package/templates/astro/AGENTS.md +1 -1
- package/templates/astro/astro.config.mjs +1 -0
- package/templates/astro/package.json +2 -2
- package/templates/astro/src/layouts/Base.astro +20 -4
- package/templates/astro/src/pages/[locale]/index.astro +1 -1
- package/templates/astro/src/pages/[locale]/projects/[slug].astro +4 -1
- package/templates/astro/src/pages/index.astro +1 -1
- package/templates/astro/src/pages/projects/[slug].astro +4 -1
- package/templates/nextjs/AGENTS.md +1 -1
- package/templates/nextjs/package.json +3 -3
- package/templates/nextjs/src/app/layout.tsx +5 -3
- package/templates/nextjs/src/app/page.tsx +6 -0
- package/templates/react-router/package.json +4 -4
- package/templates/sveltekit/AGENTS.md +1 -1
- package/templates/sveltekit/package.json +2 -2
- package/templates/sveltekit/src/routes/+layout.server.ts +4 -0
- package/templates/sveltekit/src/routes/+layout.svelte +12 -0
- package/templates/sveltekit/src/routes/[[locale=locale]]/+page.svelte +0 -5
- package/templates/vite/package.json +4 -4
- package/assets/skills/fimo-migration/scripts/build-canonical-manifest.mjs +0 -595
|
@@ -100,7 +100,7 @@ Load the matching reference when the user's request fires its trigger. Recipes t
|
|
|
100
100
|
- "List history / create a restore point / recover content and code / restore a previous version / snapshot usage or retention" → `references/history.md`
|
|
101
101
|
- "Create a Branch / feature Branch / switch Branches / merge Branches / diff two Branches / Branch status / what changed on this Branch / sync from parent / cut a release / tag a version / go to prod" → `references/branches.md`
|
|
102
102
|
- "List my projects / clone an existing project locally / check out a project / delete this project / unpublish / rename the project / manage projects across the org" → `references/projects.md`
|
|
103
|
-
- "Migrate an existing Webflow / Framer / Squarespace / Wix / Carrd site from only a published URL" → load the `fimo-migration` skill
|
|
103
|
+
- "Migrate an existing Webflow / Framer / Squarespace / Wix / Carrd site from only a published URL" → load the `fimo-migration` skill, set the target up with `fimo migrate init <dir>`, and read the published site yourself with a browser — the CLI does not crawl it
|
|
104
104
|
- "Migrate from an exported project, source repository, Webflow code export plus CMS CSVs, Framer CMS CSVs, Squarespace XML/CSV, Wix CSVs, Carrd/static export, or downloaded site files" → load the `fimo-migration` skill and use the source repo/export as the migration source of truth
|
|
105
105
|
- "How do AI agents read my site / markdown for agents" → `references/deploy.md`
|
|
106
106
|
- "Modify the URL of the website / change my project's address / connect a custom domain (e.g. blog.example.com) / rename the Fimo-provided `*.fimo.site` subdomain / buy a new domain through Fimo / set up a domain" → `references/domains.md`
|
|
@@ -145,7 +145,7 @@ These fire at specific moments in the project lifecycle. **Surface them proactiv
|
|
|
145
145
|
- **Using `fimo deploy --publish` casually** — `--publish` makes the site live to the public. Plain `fimo deploy` (preview) is the default. Use `--publish` only when the user says "ship it" / "go live" / "publish".
|
|
146
146
|
- **Bare `fimo init` in a non-interactive shell** — will hang. Always pass `[dir]`, `--no-install` (or `--install`), `--no-skills` (or `--skills`), and `--org`/`--agents` as needed explicitly when scripted.
|
|
147
147
|
- **Hardcoding content / strings / images in code** — that's what schemas, `t()`, and the media library are for. See the `fimo-code` skill's anti-patterns.
|
|
148
|
-
- **Adding another deploy tool
|
|
148
|
+
- **Adding another deploy tool**. Fimo handles deploys; don't bolt another platform on.
|
|
149
149
|
- **Committing secrets** — hidden values use `fimo secrets`; public browser-safe values use `fimo vars`. Local env files are ignored and must not become deploy inputs.
|
|
150
150
|
- **Hardcoding tokens in flags** — use the credentials written by `fimo login` (one file per backend at `~/.config/fimo/credentials/<host>-<hash>.json`).
|
|
151
151
|
- **Treating the daily allowance as extra credits** — the allowance section appears only for legacy Premium organizations, and `fimo credits` already includes its spendable credits in "Available now"; it is accounting context, not an amount to add.
|
|
@@ -36,7 +36,7 @@ fimo --branch <name> <command> # peek at another Branch for one command (does n
|
|
|
36
36
|
| `fimo diff [from] [to]` | `git diff` | Two-Branch diff across all surfaces. Defaults `from` = current Branch, `to` = `main`. `--surface <name>` limits to one; `--json`; `--no-color`. |
|
|
37
37
|
| `fimo status` | `git status` | Multi-surface status for the current Branch vs its parent. See below. |
|
|
38
38
|
| `fimo merge <from>` | `git merge` | Merge another Branch into the current one (multi-surface, atomic, interactive conflict resolver). See below. |
|
|
39
|
-
| `fimo publish` | — | Publish the
|
|
39
|
+
| `fimo publish` | — | Publish the revision already on the Fimo remote (no push) with a stable Fimo publication ID. See "Publishing" below. |
|
|
40
40
|
|
|
41
41
|
All of these accept the global `--branch` override. `diff`, `status`, `merge`, and `publish` additionally accept `-C, --cwd <dir>` to point at a project directory.
|
|
42
42
|
|
|
@@ -98,10 +98,10 @@ Syncs the current Branch's data plane from its parent (or `--from <ancestor>`).
|
|
|
98
98
|
|
|
99
99
|
Two different "go live" mechanisms — don't confuse them:
|
|
100
100
|
|
|
101
|
-
- **`fimo publish [--label <text>] [--notes <md>]`** — publishes the
|
|
102
|
-
- **`fimo deploy --publish`** —
|
|
101
|
+
- **`fimo publish [--label <text>] [--notes <md>]`** — publishes the revision already on the Fimo remote without pushing. Fimo assigns the immutable publication ID; labels and notes are optional annotations. `--skip-validate` is an operator override.
|
|
102
|
+
- **`fimo deploy --publish`** — pushes the current Production source Branch and flips the project live. See `references/deploy.md`.
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Choose `fimo deploy --publish` when local changes need to go live; choose `fimo publish` when the remote revision is the intended release. Both only run from the configured Production source Branch. Existing projects use `main`.
|
|
105
105
|
|
|
106
106
|
## Anti-patterns
|
|
107
107
|
|
|
@@ -123,12 +123,9 @@ There's nothing special about this — it's the same `fimo deploy` you'd run lat
|
|
|
123
123
|
|
|
124
124
|
## Publishing: `fimo deploy --publish` vs `fimo publish`
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
Use `fimo deploy --publish` to push and publish local changes. Use `fimo publish` to publish the revision already on the Fimo remote without pushing. Both target the configured Production source Branch.
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
- **`fimo publish [--label <text>] [--notes <md>]`** — publish the synced Production source revision without pushing. Fimo assigns the immutable publication ID; a label and notes are optional human annotations. See `references/branches.md`.
|
|
130
|
-
|
|
131
|
-
When unsure, use `fimo deploy --publish` so the local revision is pushed before it goes live. Use `fimo publish` when the Production source Branch is already synced.
|
|
128
|
+
Check the reported source commit and warnings before reporting completion. If local work is excluded or the built commit cannot be confirmed, follow the CLI's hint.
|
|
132
129
|
|
|
133
130
|
## Markdown for AI agents
|
|
134
131
|
|
|
@@ -51,8 +51,8 @@ Global flags: `--project <id>` / `-C, --cwd <dir>` for project resolution, `--or
|
|
|
51
51
|
When a user says "I want to change my website's URL," ask which case fits:
|
|
52
52
|
|
|
53
53
|
- **"It's fine as-is, just nicer"** → `rename-default` or `suggest-default`. Free, instant, no DNS.
|
|
54
|
-
- **"I own example.com already"** → `add example.com`.
|
|
55
|
-
- **"I want to buy a domain"** → `search` → `pricing` → `buy`.
|
|
54
|
+
- **"I own example.com already"** → `add example.com`. Pay as you go or higher. They configure CNAME at their registrar.
|
|
55
|
+
- **"I want to buy a domain"** → `search` → `pricing` → `buy`. The registered-domains feature flag and an eligible plan must be on (the flag is not enabled in every environment).
|
|
56
56
|
|
|
57
57
|
If they aren't sure, run `fimo domains suggest-default` first — it's free, takes one step, and a good fallback if they don't want to commit to a domain purchase.
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ If they aren't sure, run `fimo domains suggest-default` first — it's free, tak
|
|
|
60
60
|
|
|
61
61
|
After the automatic first-preview deploy on a newly created project, offer a cleaner free `*.fimo.site` URL before the first public publish when there is no custom domain and the current default slug still looks generated or was never explicitly chosen. Choosing it now lets the first public launch use the clean URL. Skip the offer if the user already chose the default slug, attached a custom domain, or declined.
|
|
62
62
|
|
|
63
|
-
The default URL is free and needs no DNS. A custom domain
|
|
63
|
+
The default URL is free and needs no DNS. A custom domain uses the Pay as you go (or higher) workflow above. A default slug can be reserved before publication, but it returns "Project Not Found" until the site is published. Renaming changes the address that will become public, so never silently claim a suggestion:
|
|
64
64
|
|
|
65
65
|
1. Run `fimo domains suggest-default --base "<brand>" --json` to get candidates.
|
|
66
66
|
2. Show the candidates and ask the user to choose one explicitly.
|
|
@@ -72,7 +72,7 @@ Choosing the slug does not publish the site. The first successful `fimo deploy -
|
|
|
72
72
|
|
|
73
73
|
The CLI never charges directly. Two distinct money paths:
|
|
74
74
|
|
|
75
|
-
1. **
|
|
75
|
+
1. **Pay as you go or higher** — gates custom-domain operations. A valid card activates Pay as you go through the Fimo web app (Hyperline-hosted checkout). When a Free user runs a gated command they get a 403 with the billing URL; surface it as-is to the user.
|
|
76
76
|
2. **Domain registration** — `fimo domains buy` creates a Hyperline invoice with `payment_method_strategy: 'external'` and returns a `checkoutUrl`. The user opens that URL in a browser to pay. **No money moves until they pay the invoice.** That's why `--yes` on `buy` is safe: it queues an invoice; the human still has to consent at checkout.
|
|
77
77
|
|
|
78
78
|
Domain prices come from name.com via the registrar reseller. They vary by TLD and can change. Pricing is in USD (cents in the API, formatted as dollars in CLI output).
|
|
@@ -121,7 +121,7 @@ Propagation: typically 15–30 minutes, up to 48 hours rarely.
|
|
|
121
121
|
## Common errors
|
|
122
122
|
|
|
123
123
|
- **"Project Not Found" on a new URL or custom domain.** The route exists but the site was never published — run `fimo deploy --publish`.
|
|
124
|
-
- **"Custom domains require
|
|
124
|
+
- **"Custom domains require Pay as you go or higher."** — add a card at `/app/billing`, then retry. Free users can still run `list`, `rename-default`, `suggest-default`, `search`, and `pricing`.
|
|
125
125
|
- **"Domain purchase is not enabled on this account."** — the registered-domains feature flag is off on this API instance. Contact support.
|
|
126
126
|
- **"This URL is not available."** — slug or hostname collides with another project or is reserved.
|
|
127
127
|
- **"That's a Fimo-owned domain."** — user tried `add <something>.fimo.site`. Use `rename-default` instead.
|
|
@@ -57,9 +57,9 @@ them are present.
|
|
|
57
57
|
{
|
|
58
58
|
"redirects": [
|
|
59
59
|
{
|
|
60
|
-
"source": "/
|
|
61
|
-
"has": [{ "type": "header", "key": "x-
|
|
62
|
-
"destination": "/
|
|
60
|
+
"source": "/offers",
|
|
61
|
+
"has": [{ "type": "header", "key": "x-promo", "value": "summer" }],
|
|
62
|
+
"destination": "/summer-sale"
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
65
|
"rewrites": [
|
|
@@ -74,12 +74,13 @@ them are present.
|
|
|
74
74
|
|
|
75
75
|
| `type` | Matches on |
|
|
76
76
|
| -------- | ---------------------------------------------------- |
|
|
77
|
-
| `header` | a request header
|
|
77
|
+
| `header` | a request header |
|
|
78
78
|
| `cookie` | a cookie |
|
|
79
79
|
| `query` | a query parameter |
|
|
80
80
|
| `host` | the hostname the request arrived on — takes no `key` |
|
|
81
81
|
|
|
82
82
|
Omit `value` to match on presence alone, as the `consent` cookie above does.
|
|
83
|
+
The redirect example requires the request to include `x-promo: summer`.
|
|
83
84
|
|
|
84
85
|
**Prefer doing this in the application when it can.** Middleware, a loader, or a
|
|
85
86
|
server component can be run and tested locally; a condition here only takes
|
|
@@ -43,10 +43,10 @@ fimo rename "<Site Name>" # local edit: set seo.siteName in the project c
|
|
|
43
43
|
|
|
44
44
|
## Publish commands
|
|
45
45
|
|
|
46
|
-
| Command | Effect
|
|
47
|
-
| ----------------------- |
|
|
48
|
-
| `fimo deploy --publish` | Push the current Production source + flip the project live in one shot.
|
|
49
|
-
| `fimo projects publish` | Flip the project live (no git push).
|
|
50
|
-
| `fimo publish` | Flip the
|
|
46
|
+
| Command | Effect |
|
|
47
|
+
| ----------------------- | ----------------------------------------------------------------------- |
|
|
48
|
+
| `fimo deploy --publish` | Push the current Production source + flip the project live in one shot. |
|
|
49
|
+
| `fimo projects publish` | Flip the project live (no git push). |
|
|
50
|
+
| `fimo publish` | Flip the revision already on the Fimo remote live (no git push). |
|
|
51
51
|
|
|
52
52
|
See `references/deploy.md` and `references/branches.md` for the full picture.
|
|
@@ -72,6 +72,7 @@ Each reference defines one package contract. Fetch it when the task reaches that
|
|
|
72
72
|
- Locale ownership and framework handoff → `references/locales.md`
|
|
73
73
|
- Upgrading a React Router app that imports `fimo/react-router/routes` → `references/migrations/react-router-route-ownership.md`
|
|
74
74
|
- Astro connector and output/adapter contract → `references/frameworks/astro.md`
|
|
75
|
+
- Astro/SvelteKit SEO or a new content route → the framework reference, then `references/frameworks/seo-head.md` for value ownership and edit-and-render acceptance
|
|
75
76
|
- Framework detection, production routing, and hosting adapters → `references/frameworks/hosting.md`
|
|
76
77
|
- Major visual work → `references/design.md`
|
|
77
78
|
|
|
@@ -6,7 +6,9 @@ This file covers media fields and the Fimo image components.
|
|
|
6
6
|
|
|
7
7
|
## Media field values
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
For single-image and gallery schemas, see `references/content/schema.md`.
|
|
10
|
+
|
|
11
|
+
Each media object stores a reference to an asset, not a copy of the asset metadata. Use the value returned by upload or generation:
|
|
10
12
|
|
|
11
13
|
```json
|
|
12
14
|
{
|
|
@@ -31,7 +33,7 @@ const withAsset = await Post.getBySlug('hello', { populate: ['cover'] });
|
|
|
31
33
|
|
|
32
34
|
`populate` takes top-level field names. A blocks field populates every media value inside it. One lookup covers all selected media in the response. Media inside a populated reference target stays a reference. Use `fimo assets open <id> --download` to inspect the source file.
|
|
33
35
|
|
|
34
|
-
Generated reads wrap media in `FimoMedia`. Its `id`, `alt`, `name`, `mime`, `width`, and `height` can be absent. A populated value has the current facts when the asset resolves; missing or external assets stay unchanged. `source` is internal. Pass
|
|
36
|
+
Generated reads wrap each media object in `FimoMedia`. Its `id`, `alt`, `name`, `mime`, `width`, and `height` can be absent. A populated value has the current facts when the asset resolves; missing or external assets stay unchanged. `source` is internal. Pass one `FimoMedia` value, not the array, to `<Image>` or `<Video>`.
|
|
35
37
|
|
|
36
38
|
## Rendering in application code
|
|
37
39
|
|
|
@@ -38,5 +38,3 @@ Use `instant = true` to find blocking code, then verify with `next build` and `n
|
|
|
38
38
|
- Avoid duplicate content reads between `generateMetadata` and the page.
|
|
39
39
|
|
|
40
40
|
Verify valid and missing slugs, canonical and initial HTML, shipped JavaScript, duplicate reads, image sizing, instant shell rendering, streaming, freshness, and bounded prefetching.
|
|
41
|
-
|
|
42
|
-
Sources: [Instant Navigations](https://nextjs.org/blog/next-16-3-instant-navigations), [`partialPrefetching`](https://github.com/vercel/next.js/blob/canary/docs/01-app/03-api-reference/05-config/01-next-config-js/partialPrefetching.mdx), and [`instant`](https://github.com/vercel/next.js/blob/canary/docs/01-app/03-api-reference/03-file-conventions/02-route-segment-config/instant.mdx).
|
|
@@ -14,6 +14,7 @@ Write one schema per `src/schemas/<Uid>.json`. The PascalCase filename must matc
|
|
|
14
14
|
"publishedAt": { "type": "date" },
|
|
15
15
|
"featured": { "type": "boolean" },
|
|
16
16
|
"cover": { "type": "media" },
|
|
17
|
+
"gallery": { "type": "media", "multiple": true },
|
|
17
18
|
"metadata": { "type": "json" },
|
|
18
19
|
"author": { "type": "reference", "target": "Author" },
|
|
19
20
|
"contributors": { "type": "reference", "target": "Author", "many": true }
|
|
@@ -80,11 +81,13 @@ Never declare `id`, `entryId`, `documentId`, `slug`, `locale`, `createdAt`, `upd
|
|
|
80
81
|
| `boolean` | boolean | flags |
|
|
81
82
|
| `date` | ISO date string | calendar days; loose about the form |
|
|
82
83
|
| `datetime` | ISO instant with offset | exact moments (`2026-08-13T09:30:00Z`) |
|
|
83
|
-
| `media` | media object
|
|
84
|
+
| `media` | media object by default | images, video, files |
|
|
84
85
|
| `json` | JSON | structured metadata |
|
|
85
86
|
| `reference` | `{ "id": "..." }` or array | links to another schema |
|
|
86
87
|
| `blocks` | ordered object array | nested/repeatable sections |
|
|
87
88
|
|
|
89
|
+
Set `multiple: true` on a media field for an array, even with one image. See `references/assets.md` for the object shape.
|
|
90
|
+
|
|
88
91
|
A `datetime` value must carry `Z` or `±HH:MM`; a bare day or a naive local time is rejected. Render it with `<DateTime>`, which keeps the whole instant in the `<time datetime>` attribute, and a `date` with `<Date>`. `unique` is not supported on `datetime`.
|
|
89
92
|
|
|
90
93
|
An enum string uses `options`; its optional `default` must be one of them:
|
|
@@ -44,10 +44,10 @@ Reference sites to anchor the aesthetic direction. Pull any well-known site, bra
|
|
|
44
44
|
| Category | Example Anchors | Quality Bar |
|
|
45
45
|
| ------------------------ | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
46
46
|
| **Creative / Agency** | Awwwards, FWA, CSS Design Awards, Pentagram, Sagmeister & Walsh, Active Theory, Fantasy Interactive, Resn | Would it win Awwwards SOTD? Would Pentagram put this in their portfolio? |
|
|
47
|
-
| **SaaS / Product** | Linear,
|
|
47
|
+
| **SaaS / Product** | Linear, Stripe, Notion, Figma, Raycast, Arc Browser, Supabase, Mercury, Clerk, Loom, Pitch | Is it Linear-polished? Would a developer tweet "their site is so clean"? |
|
|
48
48
|
| **Editorial / Blog** | Medium, Substack, The Verge, NYT, The Atlantic, Monocle, It's Nice That, Eye on Design, Wired | Would The Verge's design team respect this? Is it magazine-worthy? |
|
|
49
49
|
| **E-commerce** | Apple Store, Shopify Dawn, Gumroad, Everlane, Glossier, SSENSE, Mr Porter, Mejuri, Allbirds | Does it feel Apple Store premium? Would you trust buying from this? |
|
|
50
|
-
| **Documentation** | Stripe Docs, Tailwind Docs,
|
|
50
|
+
| **Documentation** | Stripe Docs, Tailwind Docs, Supabase Docs, Prisma Docs, Algolia Docs, Clerk Docs | Is it Stripe Docs clear? Can a developer find what they need in 3 seconds? |
|
|
51
51
|
| **Personal / Portfolio** | Read.cv, Paco Coursey, Josh Comeau, Rauno Freiberg, Brian Lovin, Lee Robinson, Linus Rogge | Would Read.cv feature this? Is it memorable and distinctly personal? |
|
|
52
52
|
| **Corporate** | Apple, Stripe, Airbnb, Slack, Square, Intercom, Dropbox, Zendesk | Does it feel Airbnb-trustworthy? Premium but not flashy? |
|
|
53
53
|
| **Dashboard** | Linear, Figma, Raycast, Retool, Rows, Height, Airtable, Coda | Is it Linear-efficient? Dense but never cluttered? |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Astro integration
|
|
2
2
|
|
|
3
|
-
Use this reference for an existing Astro application. The application keeps its route files, output mode, adapter, locale system, links, `<html lang/dir>`, and SEO. Fimo connects content, labels, native rendering, preview, and
|
|
3
|
+
Use this reference for an existing Astro application. The application keeps its route files, output mode, adapter, locale system, links, `<html lang/dir>`, and per-page SEO. Fimo connects content, labels, native rendering, preview, hosting, and the site-wide SEO defaults from Studio's Settings → SEO.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -90,6 +90,42 @@ browser values before submitting. Do not import the server form module into a
|
|
|
90
90
|
client island; add a project endpoint if the form is intentionally browser-
|
|
91
91
|
submitted.
|
|
92
92
|
|
|
93
|
+
## SEO defaults
|
|
94
|
+
|
|
95
|
+
Resolve the current route, apply page values, then convert to native attributes.
|
|
96
|
+
New projects have this in `src/layouts/Base.astro`. Existing apps should extend
|
|
97
|
+
their current head component, preserving unrelated metadata.
|
|
98
|
+
|
|
99
|
+
```astro
|
|
100
|
+
---
|
|
101
|
+
import { resolveSeoForPath, toSeoHead } from 'fimo/seo';
|
|
102
|
+
import config from '../../.fimo/config.json';
|
|
103
|
+
|
|
104
|
+
const { seo = resolveSeoForPath(Astro.url.pathname, config) } = Astro.props;
|
|
105
|
+
const canonicalUrl = seo?.url ? new URL(Astro.url.pathname, seo.url).href : undefined;
|
|
106
|
+
const head = toSeoHead(seo, { canonicalUrl });
|
|
107
|
+
---
|
|
108
|
+
<head>
|
|
109
|
+
<title>{head.title ?? 'My site'}</title>
|
|
110
|
+
{head.meta.map((attributes) => <meta {...attributes} />)}
|
|
111
|
+
{head.links.map((attributes) => <link {...attributes} />)}
|
|
112
|
+
</head>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For a content page, resolve after loading the entry and pass the final values
|
|
116
|
+
to the layout: `const seo = resolveSeoForPath(Astro.url.pathname, config, { article })`,
|
|
117
|
+
then `<Base lang={locale} seo={seo}>`. Route templates can read
|
|
118
|
+
`{{article.title}}`. Apply app-owned overrides before conversion so the title,
|
|
119
|
+
Open Graph and Twitter tags agree.
|
|
120
|
+
|
|
121
|
+
The example canonical policy fits a root-hosted site. Preserve the app's own
|
|
122
|
+
production domain, base path and trailing-slash policy. Omitting the converter's
|
|
123
|
+
`canonicalUrl` omits canonical and `og:url`; `seo.url` alone is only a site base.
|
|
124
|
+
Imported config changes need sync and rebuild/redeploy. Content freshness
|
|
125
|
+
follows the route's rendering and caching policy.
|
|
126
|
+
|
|
127
|
+
Full field and composition contract: read `references/frameworks/seo-head.md` when changing SEO.
|
|
128
|
+
|
|
93
129
|
## Static and on-demand rendering
|
|
94
130
|
|
|
95
131
|
Keep the application's per-route rendering choice:
|
|
@@ -64,6 +64,28 @@ local environment alias by hand.
|
|
|
64
64
|
projects. Do not add it to new code, env files, setup instructions, or migration
|
|
65
65
|
plans.
|
|
66
66
|
|
|
67
|
+
## `hosting`: how the deployed site is served
|
|
68
|
+
|
|
69
|
+
Declare hosting rules only in `.fimo/config.json#hosting`, not provider
|
|
70
|
+
deployment files. Run `fimo validate` to catch unsupported keys and malformed rules.
|
|
71
|
+
|
|
72
|
+
| Key | Shape |
|
|
73
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
74
|
+
| `rewrites` | `{ source, destination }`, URL unchanged |
|
|
75
|
+
| `redirects` | `{ source, destination, permanent?, statusCode? }` |
|
|
76
|
+
| `headers` | `{ source, headers: [{ key, value }] }`, response headers |
|
|
77
|
+
| `cleanUrls` | Serve `/about` for `/about.html` and redirect the extension away |
|
|
78
|
+
| `trailingSlash` | `true` requires a trailing slash, `false` forbids one |
|
|
79
|
+
|
|
80
|
+
Rewrites, redirects, and header rules accept `has` / `missing` conditions:
|
|
81
|
+
`header`, `cookie`, and `query` require `key`; `host` requires only `value`.
|
|
82
|
+
|
|
83
|
+
Redirects default to temporary. Use `permanent: true` only for lasting moves;
|
|
84
|
+
browsers cache it. `statusCode`, one of 301, 302, 307, 308, overrides `permanent`.
|
|
85
|
+
|
|
86
|
+
Verify rules on a hosted Preview or published site, not a local dev server.
|
|
87
|
+
Legacy static hosting ignores them; contact support if deployed rules have no effect.
|
|
88
|
+
|
|
67
89
|
## React Router framework mode
|
|
68
90
|
|
|
69
91
|
For the supported client-rendered setup, keep `ssr: false` and add Fimo's
|
|
@@ -111,5 +133,4 @@ Astro has a dedicated connector — load `references/frameworks/astro.md` before
|
|
|
111
133
|
editing an Astro config.
|
|
112
134
|
|
|
113
135
|
`.fimo/config.json#routes` is the Studio page catalog, not deployment routing.
|
|
114
|
-
Never generate hosting rewrites from it.
|
|
115
|
-
provider-specific deployment file, preserve it; that file remains authoritative.
|
|
136
|
+
Never generate hosting rewrites from it.
|
|
@@ -109,24 +109,10 @@ Do not add `'use client'` to a page solely because Fimo is present. Keep promise
|
|
|
109
109
|
|
|
110
110
|
## Metadata
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
If the root layout lacks this wiring, add it (updating the fimo packages if `fimo-next` does not export the helpers); without it, Settings → SEO never reaches the rendered head.
|
|
122
|
-
|
|
123
|
-
Per-page `metadata`/`generateMetadata` exports override the defaults field by field. Resolve labels/content on the server and convert tracked strings only where Next expects primitives:
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
export async function generateMetadata() {
|
|
127
|
-
const labels = await getLabels();
|
|
128
|
-
return { title: String(labels.t('seo.home.title')) };
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Do not expect `FimoProvider` to inject SEO, routes, language, or direction.
|
|
112
|
+
When creating a site, adding a route, or integrating SEO, load
|
|
113
|
+
`references/frameworks/next/seo.md` (`--ref frameworks/next/seo`). Keep root
|
|
114
|
+
global defaults separate from explicit Home and route SEO. Reuse the app's
|
|
115
|
+
established route integration for new pages; a dynamic route module covers all
|
|
116
|
+
its instances. Next replaces nested metadata objects, not their individual
|
|
117
|
+
fields. Preserve app-owned policies and image routes. `FimoProvider` does not
|
|
118
|
+
inject SEO.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Next.js SEO
|
|
2
|
+
|
|
3
|
+
Use the installed package APIs. The app owns routing, metadata policy and
|
|
4
|
+
rendering mode. Fimo supplies editable values, not a router or a merge policy.
|
|
5
|
+
|
|
6
|
+
## Read, resolve, convert
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { getFimoSeo } from 'fimo/seo/server';
|
|
10
|
+
import { toNextMetadata, toNextViewport } from 'fimo-next/seo';
|
|
11
|
+
|
|
12
|
+
// Root layout: global defaults, never implicitly Home.
|
|
13
|
+
export const generateMetadata = () => toNextMetadata(getFimoSeo());
|
|
14
|
+
export const generateViewport = () => toNextViewport(getFimoSeo());
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For a new App Router site, keep this root wiring. Home is an explicit route:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// app/page.tsx
|
|
21
|
+
export const generateMetadata = () => toNextMetadata(getFimoSeo({ pathname: '/' }));
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For each route module with configured route SEO, resolve its real pathname.
|
|
25
|
+
One dynamic module handles every slug. Reuse the app's content loader and URL
|
|
26
|
+
builder; do not try to infer a full pathname from Next's params in the root.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// app/blog/[slug]/page.tsx, using the app's existing loader and URL builder
|
|
30
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
31
|
+
const { slug } = await params;
|
|
32
|
+
const article = await getArticle(slug);
|
|
33
|
+
return toNextMetadata(
|
|
34
|
+
getFimoSeo({
|
|
35
|
+
pathname: articlePath(article),
|
|
36
|
+
routeData: { article },
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Loaders should retain their normal not-found and caching behavior. Add
|
|
43
|
+
`generateViewport` at a route only when route-specific `themeColor` needs to
|
|
44
|
+
apply; share the existing cached loader if both exports need its data. Other
|
|
45
|
+
routes inherit the root color. When adding a page, follow the established
|
|
46
|
+
pattern and keep its Fimo catalog entry aligned with a URL the app serves.
|
|
47
|
+
|
|
48
|
+
`getFimoSeo({ pathname?, routeData?, rootDir? })` is synchronous and Node-only.
|
|
49
|
+
No pathname reads global SEO. A pathname applies matching route overrides.
|
|
50
|
+
It reads current or legacy project config using Fimo's config discovery;
|
|
51
|
+
missing config/SEO returns `undefined`, malformed JSON and other I/O errors
|
|
52
|
+
throw. Pass `rootDir` for a monorepo app located away from the process cwd.
|
|
53
|
+
It does not cache, fetch content, infer locales or invalidate Next's caches.
|
|
54
|
+
Config edits reach static output on rebuild and cached output on the app's
|
|
55
|
+
normal revalidation path. Fetched route content can remain cached across
|
|
56
|
+
builds; use the app's data-cache invalidation/revalidation policy and verify
|
|
57
|
+
the rendered value after a content edit. Rebuild success is not freshness proof.
|
|
58
|
+
|
|
59
|
+
For bundled data or Edge, supply config yourself to the pure resolver instead
|
|
60
|
+
of importing the Node reader:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { resolveSeoForPath } from 'fimo/seo';
|
|
64
|
+
import { toNextMetadata } from 'fimo-next/seo';
|
|
65
|
+
|
|
66
|
+
const seo = resolveSeoForPath(pathname, publicSeoConfig, { article });
|
|
67
|
+
const metadata = toNextMetadata(seo);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Bundle only public SEO/routes, not credentials or project connection data.
|
|
71
|
+
Every ancestor layout of an Edge page must also avoid the Node reader. Use
|
|
72
|
+
bundled public SEO for shared root defaults, not just for the Edge leaf.
|
|
73
|
+
The converters have no I/O or Next request dependency. Their output is for
|
|
74
|
+
App Router; Pages Router keeps its existing `next/head` or SEO library and
|
|
75
|
+
maps resolved Fimo values there. Do not convert routers for SEO.
|
|
76
|
+
|
|
77
|
+
## Available fields
|
|
78
|
+
|
|
79
|
+
Global values live at `seo` and overrides at `routes.<id>.seo` in the project's
|
|
80
|
+
existing Fimo config (normally `.fimo/config.json`). Preserve other config keys.
|
|
81
|
+
`fimo pages set` manages the route catalog but has no `--seo` flag; edit the
|
|
82
|
+
config to author route SEO. For example:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"seo": { "title": "Journal", "description": "Notes from the field" },
|
|
87
|
+
"routes": {
|
|
88
|
+
"article": {
|
|
89
|
+
"path": "/blog/:slug",
|
|
90
|
+
"seo": { "title": "{{article.title}}", "description": "{{article.summary}}" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`FimoSEO` from `fimo/seo` has these optional string fields:
|
|
97
|
+
|
|
98
|
+
| Fimo field | Next output |
|
|
99
|
+
| ------------- | ------------------------------------------------------ |
|
|
100
|
+
| `title` | `title` |
|
|
101
|
+
| `description` | `description` |
|
|
102
|
+
| `siteName` | `openGraph.siteName` |
|
|
103
|
+
| `url` | `metadataBase`, relative canonical and `openGraph.url` |
|
|
104
|
+
| `image` | `openGraph.images` |
|
|
105
|
+
| `favicon` | `icons.icon` and `icons.apple` |
|
|
106
|
+
| `twitterSite` | `twitter.site` |
|
|
107
|
+
| `robots` | `robots` meta, not robots.txt |
|
|
108
|
+
| `themeColor` | `Viewport.themeColor`, not Metadata |
|
|
109
|
+
| `locale` | `openGraph.locale`, not URL or translation selection |
|
|
110
|
+
|
|
111
|
+
Route fields use the same shape. `{{article.title}}` reads caller-supplied
|
|
112
|
+
`routeData.article.title`; `siteName` and route `label` are built-in context.
|
|
113
|
+
A missing, empty or nonscalar token falls back for the entire field to its
|
|
114
|
+
global value, then absence. Tracked Fimo strings are supported. This does not
|
|
115
|
+
invent content when an article is missing.
|
|
116
|
+
|
|
117
|
+
Converters return `{}` for undefined SEO. They leave social title/description
|
|
118
|
+
and Twitter images unset so Next derives them from final page metadata/OG.
|
|
119
|
+
They default OG type to `website` and Twitter card to `summary_large_image`.
|
|
120
|
+
`url` is a site base; relative canonical/OG URLs follow the active route.
|
|
121
|
+
App-specific canonical policies, hreflang, JSON-LD, sitemaps, robots.txt,
|
|
122
|
+
verification tags and generated social images remain app-owned.
|
|
123
|
+
|
|
124
|
+
## Existing app: adopt only chosen fields
|
|
125
|
+
|
|
126
|
+
Inspect existing root/section/page exports and file-based icons/social images
|
|
127
|
+
first. Do not spread every resolved global value into a leaf: that can replace
|
|
128
|
+
a section's `noindex` policy or image. Keep the app's established metadata
|
|
129
|
+
builder and choose the fields Fimo should supply. For example, an app choosing
|
|
130
|
+
Fimo's editable description while retaining its title and policy:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
export async function generateMetadata(context: Props) {
|
|
134
|
+
const application = await getApplicationMetadata(context);
|
|
135
|
+
const seo = getFimoSeo({ pathname: await getApplicationPathname(context) });
|
|
136
|
+
return {
|
|
137
|
+
...application,
|
|
138
|
+
...(seo?.description ? { description: seo.description } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The loader/path helpers above belong to the app, not Fimo. For nested fields,
|
|
144
|
+
explicitly retain the siblings the app intends to keep. Next replaces
|
|
145
|
+
`openGraph`, `twitter`, `icons` and `alternates` objects across segments; there
|
|
146
|
+
is no generic deep-merge or null-based suppression guarantee. Never spread
|
|
147
|
+
parent `ResolvedMetadata` wholesale. File-based images can mask config values;
|
|
148
|
+
preserve them unless the user chooses to replace that behavior.
|
|
149
|
+
|
|
150
|
+
Use one title-suffix owner. If a Fimo template already produces a complete
|
|
151
|
+
title, return `title: { absolute: seo.title }` to bypass an ancestor
|
|
152
|
+
`title.template`, or remove the duplicated suffix from the Fimo template.
|
|
153
|
+
Next's layout `title.template` does not apply to a page in that same segment;
|
|
154
|
+
give Home its complete app-owned title when it needs the suffix too.
|
|
155
|
+
Preserve deliberate Twitter-specific images; otherwise allow Next's OG fallback.
|
|
156
|
+
|
|
157
|
+
The existing `buildFimoMetadata(rootDir?)` and `buildFimoViewport(rootDir?)` from `fimo-next`
|
|
158
|
+
remain global-only helpers. Their string argument is a directory, not a route.
|
|
159
|
+
|
|
160
|
+
## Prove the rendered result
|
|
161
|
+
|
|
162
|
+
Build and request representative Home, static and dynamic routes. Check the
|
|
163
|
+
actual title, description, canonical, OG/Twitter images, icons, robots and
|
|
164
|
+
theme color against configured values. Include missing template data and an
|
|
165
|
+
existing section policy. Confirm social titles follow the final page title,
|
|
166
|
+
suffixes occur once, and no Home values leak to other routes. A helper import
|
|
167
|
+
or a successful build alone does not prove SEO. Check supported runtime and
|
|
168
|
+
revalidation behavior when those are part of the task.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference when `fimo describe --json` reports `framework.slug: "nextjs"`, or the project uses an App Router (`app/` or `src/app/`) or Pages Router (`pages/` or `src/pages/`) tree.
|
|
4
4
|
|
|
5
|
-
Fimo supports schema-first content in both routers across static generation, ISR, request-time rendering, and Client Components. The application owns routes, locale resolution, links, `<html lang/dir>`, and
|
|
5
|
+
Fimo supports schema-first content in both routers across static generation, ISR, request-time rendering, and Client Components. The application owns routes, locale resolution, links, `<html lang/dir>`, and SEO policy. Fimo supplies content, labels, editing context and optional resolved SEO values. For site creation, new routes or SEO changes, load `references/frameworks/next/seo.md`.
|
|
6
6
|
|
|
7
7
|
## Choose the router reference
|
|
8
8
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Astro and SvelteKit SEO
|
|
2
|
+
|
|
3
|
+
Resolve route data, apply the app's policy, then convert. Both imports are pure
|
|
4
|
+
and bundle-safe. Neither reads files, fetches content, discovers a route, nor
|
|
5
|
+
sets framework caching. Use the installed package's exports.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { resolveSeoForPath, toSeoHead, type FimoSEO } from 'fimo/seo';
|
|
9
|
+
import config from '../../.fimo/config.json'; // adjust to the actual file
|
|
10
|
+
|
|
11
|
+
const resolved = resolveSeoForPath(pathname, config, { article });
|
|
12
|
+
const seo: FimoSEO = { ...resolved, title: String(article.title) };
|
|
13
|
+
const head = toSeoHead(seo, { canonicalUrl });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`resolveSeoForPath` combines global defaults and the matching catalog route's
|
|
17
|
+
`seo`. Supply the actual pathname, including the app's locale/base path, and
|
|
18
|
+
the already-loaded data named by templates such as `{{article.title}}`.
|
|
19
|
+
Unresolved tokens fall back for the entire field. The route catalog is not a
|
|
20
|
+
router. For domain/custom locale routing, the app selects locale-specific
|
|
21
|
+
values explicitly. No global defaults are required to use route SEO.
|
|
22
|
+
|
|
23
|
+
All `FimoSEO` fields are optional strings:
|
|
24
|
+
|
|
25
|
+
| Fields | Output |
|
|
26
|
+
| ---------------------- | --------------------------------------------------------------------------------- |
|
|
27
|
+
| `title`, `description` | Native title/description and matching Open Graph/Twitter values |
|
|
28
|
+
| `image` | Open Graph and Twitter share image |
|
|
29
|
+
| `favicon` | Icon and apple-touch-icon links |
|
|
30
|
+
| `siteName`, `locale` | Open Graph site name and locale |
|
|
31
|
+
| `twitterSite` | Twitter site account |
|
|
32
|
+
| `robots`, `themeColor` | Native meta tags |
|
|
33
|
+
| `url` | Site base only. The app computes `canonicalUrl`; it emits canonical and `og:url`. |
|
|
34
|
+
|
|
35
|
+
`toSeoHead` returns `{ title?, meta, links }`. No configured values produces
|
|
36
|
+
empty arrays. Absent/empty fields emit no corresponding tags. The default
|
|
37
|
+
social type/card are `website`/`summary_large_image`; use the existing head
|
|
38
|
+
component for article tags, alternate languages, JSON-LD, and other app-owned
|
|
39
|
+
metadata. Filter a converter attribute before replacing that same tag.
|
|
40
|
+
|
|
41
|
+
Preserve editor-owned settings through resolution; do not copy their initial
|
|
42
|
+
values into app constants that overwrite config. Derive content-owned title
|
|
43
|
+
and description from the same loaded entry or singleton as the visible page,
|
|
44
|
+
including Home/About. Pass that data to resolution or compose its current
|
|
45
|
+
values before conversion. A correct first render does not prove editability.
|
|
46
|
+
|
|
47
|
+
App values win only where intentionally required. Keep overrides together and
|
|
48
|
+
record their reason in the existing code or handoff, not a new ownership
|
|
49
|
+
registry. Set `image: undefined` to remove a
|
|
50
|
+
global image, or `robots: 'noindex'` for an editorial page before conversion. An
|
|
51
|
+
explicit `undefined` in a spread removes a field; use conditional spreads to
|
|
52
|
+
retain defaults for missing optional overrides. Resolve once, then compose;
|
|
53
|
+
do not resolve again after applying policy.
|
|
54
|
+
|
|
55
|
+
Pass a final absolute canonical URL from the app's production URL policy.
|
|
56
|
+
`new URL(pathname, seo.url).href` fits a root-hosted starter; preserve existing
|
|
57
|
+
base paths, domains, trailing slashes and query policy in other apps. Never use
|
|
58
|
+
the preview/request origin as a production canonical. Omit `canonicalUrl` to
|
|
59
|
+
emit neither canonical nor `og:url`.
|
|
60
|
+
|
|
61
|
+
For existing apps, inventory the current head owner and extend it in place.
|
|
62
|
+
Do not add duplicate tags or overwrite unrelated metadata. Framework-escape
|
|
63
|
+
attributes; never render a raw HTML string. Each new route must use the app's
|
|
64
|
+
SEO path, with route data where needed. Verify title, description, canonical,
|
|
65
|
+
robots and social values in built HTML, not only the browser tab title.
|
|
66
|
+
|
|
67
|
+
Imported JSON is a build snapshot. Studio config changes need sync and
|
|
68
|
+
rebuild/redeploy. Content freshness follows the app's SSR/prerender/cache
|
|
69
|
+
policy; changing a helper does not change that policy.
|
|
70
|
+
|
|
71
|
+
For SEO integration acceptance, use an authorized isolated project to change
|
|
72
|
+
one inherited setting and one content-owned value after the initial render.
|
|
73
|
+
Follow the app's sync/build/cache policy, then compare built HTML and the
|
|
74
|
+
hydrated head; verify intentional overrides remain unchanged. Restore the
|
|
75
|
+
test values. Include client navigation and Back where supported. Never mutate
|
|
76
|
+
customer production data for this check. If edits or rendering are unavailable,
|
|
77
|
+
report freshness unproven. `fimo validate` checks declared contracts, not rendered
|
|
78
|
+
head propagation; a green result or helper import is not this acceptance proof.
|