fimo 2.2.0-staging.1 → 2.2.0-staging.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.
- package/README.md +3 -2
- package/assets/agent-templates/_default/config.yaml +1 -0
- package/assets/agent-templates/broken-link-sweeper/config.yaml +1 -0
- package/assets/agent-templates/content-translator/config.yaml +1 -0
- package/assets/agent-templates/post-deploy-check/config.yaml +1 -0
- package/assets/agent-templates/seo-audit/config.yaml +1 -0
- package/assets/skills/fimo-cli/SKILL.md +4 -4
- package/assets/skills/fimo-cli/references/agents.md +2 -1
- package/assets/skills/fimo-cli/references/assets.md +4 -1
- package/assets/skills/fimo-cli/references/content.md +3 -3
- package/assets/skills/fimo-cli/references/deploy.md +2 -2
- package/assets/skills/fimo-cli/references/forms.md +3 -1
- package/assets/skills/fimo-cli/references/history.md +1 -0
- package/assets/skills/fimo-cli/references/init.md +30 -39
- package/assets/skills/fimo-cli/references/schedules.md +4 -2
- package/assets/skills/fimo-cli/references/webhooks.md +2 -0
- package/assets/skills/fimo-code/references/frameworks/next.md +4 -1
- package/assets/skills/fimo-code/references/frameworks/react-router.md +4 -3
- package/assets/skills/fimo-studio/references/preview-and-responsive.md +4 -1
- package/assets/skills/fimo-studio/references/visual-editing.md +9 -1
- package/dist/build/astro/config.js +2 -1
- package/dist/build/vite/plugins/preview-health.d.ts +4 -0
- package/dist/build/vite/plugins/preview-health.js +40 -0
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +112 -113
- package/dist/runtime/content/client/collection-core.js +3 -2
- package/dist/runtime/content/client/core-types.d.ts +4 -3
- package/dist/runtime/content/client/internal.d.ts +2 -0
- package/dist/runtime/content/client/internal.js +54 -25
- package/dist/runtime/content/client/singleton-core.js +2 -1
- package/dist/runtime/content/index.d.ts +1 -1
- package/dist/runtime/content/index.js +1 -1
- package/dist/runtime/primitives/lib/index.d.ts +1 -1
- package/dist/runtime/primitives/lib/index.js +1 -1
- package/dist/runtime/primitives/lib/primitives.d.ts +24 -5
- package/dist/runtime/primitives/lib/primitives.js +69 -16
- package/dist/runtime/primitives/lib/template.js +2 -2
- package/dist/runtime/templates.d.ts +8 -2
- package/dist/runtime/templates.js +34 -8
- package/package.json +13 -9
- package/templates/astro/package.json +2 -2
- package/templates/nextjs/AGENTS.md +1 -1
- package/templates/nextjs/package.json +4 -4
- package/templates/react-router/.fimo/config.json +1 -2
- package/templates/react-router/AGENTS.md +1 -1
- package/templates/react-router/package.json +4 -4
- package/templates/sveltekit/package.json +2 -2
- package/templates/vite/package.json +4 -4
- package/assets/starters/fimo-site/manifest.json +0 -64
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/GOAL.md +0 -8
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +0 -1
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/config.yaml +0 -9
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/GOAL.md +0 -14
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +0 -3
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/config.yaml +0 -9
- package/assets/starters/fimo-site/template/src/forms/contact.json +0 -14
- package/assets/starters/fimo-site/template/src/forms/contact.ts +0 -46
- package/assets/starters/fimo-site/template/src/pages/Contact.tsx +0 -193
- package/assets/starters/fimo-site/template/src/pages/Index.tsx +0 -27
- package/assets/starters/fimo-site/template/src/pages/SiteContent.tsx +0 -191
- package/assets/starters/fimo-site/template/src/schemas/HomePage.json +0 -28
- package/assets/starters/fimo-site/template/src/schemas/HomePage.ts +0 -29
- package/assets/starters/fimo-site/template/src/schemas/HomePage.types.ts +0 -64
|
@@ -28,7 +28,7 @@ const PACKAGE_ROOT = path.resolve(MODULE_DIR, '..', '..');
|
|
|
28
28
|
const RELEASE_FILE = path.join(PACKAGE_ROOT, 'release.json');
|
|
29
29
|
const PACKAGE_JSON_FILE = path.join(PACKAGE_ROOT, 'package.json');
|
|
30
30
|
export const TEMPLATE_FRAMEWORKS = ['react-router', 'vite', 'astro', 'nextjs', 'sveltekit'];
|
|
31
|
-
export const DEFAULT_TEMPLATE = '
|
|
31
|
+
export const DEFAULT_TEMPLATE = 'nextjs';
|
|
32
32
|
/**
|
|
33
33
|
* Absolute path to the requested framework's template directory.
|
|
34
34
|
*
|
|
@@ -69,8 +69,8 @@ export function readReleaseMeta() {
|
|
|
69
69
|
try {
|
|
70
70
|
const raw = fs.readFileSync(RELEASE_FILE, 'utf8');
|
|
71
71
|
const parsed = JSON.parse(raw);
|
|
72
|
-
const source = parsed.source === 'npm' || parsed.source === 'dev' ? parsed.source : null;
|
|
73
|
-
return { source };
|
|
72
|
+
const source = parsed.source === 'npm' || parsed.source === 'dev' || parsed.source === 'url' ? parsed.source : null;
|
|
73
|
+
return { source, url: typeof parsed.url === 'string' && parsed.url.length > 0 ? parsed.url : null };
|
|
74
74
|
}
|
|
75
75
|
catch {
|
|
76
76
|
return { source: null };
|
|
@@ -91,16 +91,34 @@ export function readCliSemver() {
|
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
93
|
* Pure version: given a release manifest + CLI semver, return the value
|
|
94
|
-
* to pin `fimo` to (or null
|
|
94
|
+
* to pin `fimo` to (or null when nothing is known). Exported for unit tests.
|
|
95
|
+
*
|
|
96
|
+
* Throws rather than returning an unreproducible pin: a project that records a
|
|
97
|
+
* package-manager cache path (or a `fimo` its companions cannot follow) is
|
|
98
|
+
* broken on every other machine, and the error names the missing publish step.
|
|
95
99
|
*/
|
|
96
100
|
export function computeFimoPin(meta, semver, options = {}) {
|
|
97
101
|
if (meta.source === 'npm') {
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
return semver;
|
|
103
|
+
}
|
|
104
|
+
// A checkout wins over a baked artifact URL: `publish-cli --id` keeps its
|
|
105
|
+
// release.json in the workspace so the next local run still iterates on
|
|
106
|
+
// source, not on the tarball it just uploaded.
|
|
107
|
+
const localPin = options.devPackageRoot ? `file:${options.devPackageRoot}` : null;
|
|
108
|
+
if (localPin && pinsWholeGroup(localPin)) {
|
|
109
|
+
return localPin;
|
|
110
|
+
}
|
|
111
|
+
if (meta.source === 'url') {
|
|
112
|
+
if (!meta.url) {
|
|
113
|
+
throw new Error('Refusing to create a project from a development CLI that was published without a public artifact URL. ' +
|
|
114
|
+
'Republish it remotely with `pnpm -F fimo publish-cli -- --id <task-name>` and install that artifact.');
|
|
100
115
|
}
|
|
116
|
+
return meta.url;
|
|
101
117
|
}
|
|
102
|
-
if (
|
|
103
|
-
|
|
118
|
+
if (localPin) {
|
|
119
|
+
throw new Error(`Refusing to create a project pinned to ${localPin}: that path is not a Fimo checkout, so it is neither ` +
|
|
120
|
+
'reproducible on another machine nor able to pin the matching companion packages. Install `fimo` from npm, ' +
|
|
121
|
+
'or publish this CLI with `pnpm -F fimo publish-cli -- --id <task-name>` and install that artifact.');
|
|
104
122
|
}
|
|
105
123
|
return null;
|
|
106
124
|
}
|
|
@@ -233,6 +251,14 @@ export function deriveFimoPackagePin(name, fimoPin) {
|
|
|
233
251
|
}
|
|
234
252
|
return fimoPin;
|
|
235
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Whether every Fimo package can be pinned from this `fimo` pin. A pin that
|
|
256
|
+
* only the CLI can follow leaves the companions on the template's `latest`,
|
|
257
|
+
* which silently mixes channels inside one project (FIMO-2070).
|
|
258
|
+
*/
|
|
259
|
+
function pinsWholeGroup(fimoPin) {
|
|
260
|
+
return FIMO_PACKAGES.every((name) => deriveFimoPackagePin(name, fimoPin) !== null);
|
|
261
|
+
}
|
|
236
262
|
/**
|
|
237
263
|
* Rewrite `fimo` and any companion packages present (in dependencies +
|
|
238
264
|
* devDependencies) to the current pin. Only touches entries that look like a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "2.2.0-staging.
|
|
3
|
+
"version": "2.2.0-staging.10",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro-component"
|
|
@@ -77,6 +77,10 @@
|
|
|
77
77
|
"types": "./dist/runtime/astro/internal.d.ts",
|
|
78
78
|
"import": "./dist/runtime/astro/internal.js"
|
|
79
79
|
},
|
|
80
|
+
"./vite/preview-health": {
|
|
81
|
+
"types": "./dist/build/vite/plugins/preview-health.d.ts",
|
|
82
|
+
"import": "./dist/build/vite/plugins/preview-health.js"
|
|
83
|
+
},
|
|
80
84
|
"./next": {
|
|
81
85
|
"types": "./dist/build/next/moved.d.ts",
|
|
82
86
|
"import": "./dist/build/next/moved.js"
|
|
@@ -184,12 +188,12 @@
|
|
|
184
188
|
"devDependencies": {
|
|
185
189
|
"@clack/core": "^1.2.0",
|
|
186
190
|
"@clack/prompts": "^1.2.0",
|
|
187
|
-
"@fimo/agents-bundle": "2.2.0-staging.
|
|
188
|
-
"@fimo/api-contracts": "2.2.0-staging.
|
|
189
|
-
"@fimo/cli-kit": "2.2.0-staging.
|
|
190
|
-
"@fimo/framework-detect": "2.2.0-staging.
|
|
191
|
-
"@fimo/routes": "2.2.0-staging.
|
|
192
|
-
"@fimo/tsconfig": "2.2.0-staging.
|
|
191
|
+
"@fimo/agents-bundle": "2.2.0-staging.10",
|
|
192
|
+
"@fimo/api-contracts": "2.2.0-staging.10",
|
|
193
|
+
"@fimo/cli-kit": "2.2.0-staging.10",
|
|
194
|
+
"@fimo/framework-detect": "2.2.0-staging.10",
|
|
195
|
+
"@fimo/routes": "2.2.0-staging.10",
|
|
196
|
+
"@fimo/tsconfig": "2.2.0-staging.10",
|
|
193
197
|
"@types/node": "22.18.6",
|
|
194
198
|
"agent-install": "^0.0.5",
|
|
195
199
|
"astro": "^7.1.6",
|
|
@@ -201,8 +205,8 @@
|
|
|
201
205
|
"tsx": "4.22.4",
|
|
202
206
|
"typescript": "7.0.2",
|
|
203
207
|
"vite": "8.2.2",
|
|
204
|
-
"vitest": "4.1.
|
|
205
|
-
"wrangler": "4.
|
|
208
|
+
"vitest": "4.1.11",
|
|
209
|
+
"wrangler": "4.126.0",
|
|
206
210
|
"yaml": "2.8.2"
|
|
207
211
|
},
|
|
208
212
|
"engines": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"check": "astro sync && astro check"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"fimo": "2.2.0-staging.
|
|
13
|
-
"fimo-astro": "2.2.0-staging.
|
|
12
|
+
"fimo": "2.2.0-staging.10",
|
|
13
|
+
"fimo-astro": "2.2.0-staging.10"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@astrojs/check": "^0.9.10",
|
|
@@ -37,7 +37,7 @@ The scaffolded homepage is disposable. Replace it on the first real generation.
|
|
|
37
37
|
- **Deploy is on-demand**, not a save step. Batch related edits into one deploy.
|
|
38
38
|
- Use the selected package manager's committed build-script policy. pnpm projects pin `pnpm@10.26.0` and approve `esbuild` and `fimo` through `pnpm-workspace.yaml#allowBuilds`; Bun projects trust the same packages through `package.json#trustedDependencies`. Run a plain install with the selected manager so local installation matches Preview and production publishing. Review new dependencies before extending the policy; all three paths stop on unreviewed scripts.
|
|
39
39
|
- Treat the user's language request as part of setup. The first explicitly requested locale is the primary locale; put it first and set it as `i18n.defaultLocale` in `.fimo/config.json`, with every requested locale in `i18n.locales`. If no language is requested, keep `en` and state that choice. That file is the only place to change: `src/locales.ts` reads it, so the served language and `<html lang>` follow. It does not create locale URLs; implement a URL strategy only when the product asks for one. Never introduce a default-locale environment variable.
|
|
40
|
-
- Next's `app` tree is the router and stays authoritative: the
|
|
40
|
+
- Next's `app` tree is the router and stays authoritative: the default shell is one ordinary root layout plus `/` as the homepage, and unknown paths get Next's native 404. When a product requires locale URLs, implement the framework-native strategy (`[locale]` segments, middleware, cookies, domains, or an i18n library), pass the resolved locale into Fimo server reads and `FimoProvider`, and load `fimo skills get fimo-code --ref frameworks/next/locales` first. Do not impose locale-prefixed URLs on a default-only project.
|
|
41
41
|
- `FimoProvider` owns the optional Studio preview runtime. Do not add a preview bridge component, read Fimo preview environment variables, or inject Fimo scripts in application code.
|
|
42
42
|
- Keep `.fimo/config.json#routes` as Studio metadata, not as the Next.js router. Describe only URLs the application actually serves; add a `:locale` segment to a route's metadata only after the application implements that locale routing. There, `params.locale.optional: true` means omit the configured default locale from the URL; use `optional: false` only when every locale, including the default, must be prefixed.
|
|
43
43
|
- The application owns routes, locale resolution, links, HTML language/direction, and SEO. Fimo connects to those decisions.
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@tanstack/react-query": "^5.80.6",
|
|
15
|
-
"fimo": "2.2.0-staging.
|
|
16
|
-
"fimo-next": "2.2.0-staging.
|
|
17
|
-
"fimo-react": "2.2.0-staging.
|
|
18
|
-
"next": "^16.3.
|
|
15
|
+
"fimo": "2.2.0-staging.10",
|
|
16
|
+
"fimo-next": "2.2.0-staging.10",
|
|
17
|
+
"fimo-react": "2.2.0-staging.10",
|
|
18
|
+
"next": "^16.3.3",
|
|
19
19
|
"react": "^19.2.7",
|
|
20
20
|
"react-dom": "^19.2.7"
|
|
21
21
|
},
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
"runtime": { "framework": "react-router" },
|
|
3
3
|
"seo": {
|
|
4
4
|
"title": "Welcome to your Fimo website",
|
|
5
|
-
"description": "A default homepage
|
|
6
|
-
"siteName": "Fimo Starter Site",
|
|
5
|
+
"description": "A default homepage for Fimo projects.",
|
|
7
6
|
"url": "https://fimo.ai/",
|
|
8
7
|
"image": "https://fimo.ai/socials/og_cover.png",
|
|
9
8
|
"favicon": "https://fimo.ai/favicon-orange.svg",
|
|
@@ -47,6 +47,6 @@ breaks that. Load `fimo-code` for the exact primitives, but the rule is:
|
|
|
47
47
|
- Do not add a second deploy tool (Vercel, Netlify, etc.).
|
|
48
48
|
- **Never edit `.fimo/project.json`** — it links this folder to the remote project.
|
|
49
49
|
- **Keep `.fimo/config.json#runtime` true to the code** — framework, `dev`/`build`/`install` commands, output directory. Update it when you rename a package script, move the build output, or change framework; the reference is `fimo skills get fimo-code --ref frameworks/hosting`.
|
|
50
|
-
- The scaffolded homepage is disposable
|
|
50
|
+
- The scaffolded homepage is disposable template content. Replace it on the first real generation.
|
|
51
51
|
- **Secrets live in the Fimo dashboard**, not in a committed `.env`.
|
|
52
52
|
- When scripting, pass flags to `fimo` commands (`fimo deploy -m "…"`, `fimo delete -y`) — bare interactive forms hang in non-TTY shells.
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"cmdk": "^1.1.1",
|
|
22
22
|
"date-fns": "^4.1.0",
|
|
23
23
|
"embla-carousel-react": "^8.6.0",
|
|
24
|
-
"fimo": "2.2.0-staging.
|
|
25
|
-
"fimo-react": "2.2.0-staging.
|
|
26
|
-
"fimo-react-router": "2.2.0-staging.
|
|
27
|
-
"fimo-vite": "2.2.0-staging.
|
|
24
|
+
"fimo": "2.2.0-staging.10",
|
|
25
|
+
"fimo-react": "2.2.0-staging.10",
|
|
26
|
+
"fimo-react-router": "2.2.0-staging.10",
|
|
27
|
+
"fimo-vite": "2.2.0-staging.10",
|
|
28
28
|
"input-otp": "^1.4.2",
|
|
29
29
|
"isbot": "^5",
|
|
30
30
|
"lucide-react": "^0.577.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"prepare": "svelte-kit sync"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"fimo": "2.2.0-staging.
|
|
15
|
-
"fimo-sveltekit": "2.2.0-staging.
|
|
14
|
+
"fimo": "2.2.0-staging.10",
|
|
15
|
+
"fimo-sveltekit": "2.2.0-staging.10"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@sveltejs/kit": "^2.70.2",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@tanstack/react-query": "^5.80.6",
|
|
14
|
-
"fimo": "2.2.0-staging.
|
|
15
|
-
"fimo-react": "2.2.0-staging.
|
|
16
|
-
"fimo-react-router": "2.2.0-staging.
|
|
17
|
-
"fimo-vite": "2.2.0-staging.
|
|
14
|
+
"fimo": "2.2.0-staging.10",
|
|
15
|
+
"fimo-react": "2.2.0-staging.10",
|
|
16
|
+
"fimo-react-router": "2.2.0-staging.10",
|
|
17
|
+
"fimo-vite": "2.2.0-staging.10",
|
|
18
18
|
"react": "^19.2.7",
|
|
19
19
|
"react-dom": "^19.2.7",
|
|
20
20
|
"react-router": "^8.3.0"
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "fimo-site",
|
|
3
|
-
"version": 1,
|
|
4
|
-
"description": "A content-led site with a homepage, contact form, labels, and manual review agents.",
|
|
5
|
-
"singletons": [
|
|
6
|
-
{
|
|
7
|
-
"type": "HomePage",
|
|
8
|
-
"data": {
|
|
9
|
-
"eyebrow": "Field notes",
|
|
10
|
-
"title": "Your website, ready to edit.",
|
|
11
|
-
"intro": {
|
|
12
|
-
"type": "doc",
|
|
13
|
-
"content": [
|
|
14
|
-
{
|
|
15
|
-
"type": "paragraph",
|
|
16
|
-
"content": [
|
|
17
|
-
{
|
|
18
|
-
"type": "text",
|
|
19
|
-
"text": "This starter is real website content. Edit the copy in Studio, change the code with your favorite agent, and keep both surfaces in sync."
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
},
|
|
25
|
-
"primaryCtaLabel": "Read the field notes",
|
|
26
|
-
"primaryCtaHref": "#stories",
|
|
27
|
-
"featured": [
|
|
28
|
-
{
|
|
29
|
-
"label": "Start here",
|
|
30
|
-
"title": "A calmer content workflow",
|
|
31
|
-
"summary": "Keep content, code, previews, and the team in one editable project.",
|
|
32
|
-
"href": "#stories"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"label": "For agents",
|
|
36
|
-
"title": "Build once, keep iterating",
|
|
37
|
-
"summary": "Give your coding agent a real CMS surface to work with after the first deploy.",
|
|
38
|
-
"href": "#stories"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"label": "For teams",
|
|
42
|
-
"title": "Make every edit reviewable",
|
|
43
|
-
"summary": "Let teammates shape the site without asking them to open a code editor.",
|
|
44
|
-
"href": "#stories"
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"contactHeading": "Make something useful",
|
|
48
|
-
"contactIntro": "Tell us what you are building and we will help you find the shortest path from first draft to a site your team can own."
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
],
|
|
52
|
-
"labels": [
|
|
53
|
-
{ "key": "nav.stories", "value": "Field notes", "locale": "en" },
|
|
54
|
-
{ "key": "nav.contact", "value": "Start a conversation", "locale": "en" },
|
|
55
|
-
{ "key": "hero.scroll", "value": "Explore the starter", "locale": "en" },
|
|
56
|
-
{ "key": "contact.name", "value": "Your name", "locale": "en" },
|
|
57
|
-
{ "key": "contact.email", "value": "Email address", "locale": "en" },
|
|
58
|
-
{ "key": "contact.message", "value": "What are you building?", "locale": "en" },
|
|
59
|
-
{ "key": "contact.submit", "value": "Send a note", "locale": "en" },
|
|
60
|
-
{ "key": "contact.sent", "value": "Thanks. Your note is on its way.", "locale": "en" },
|
|
61
|
-
{ "key": "contact.failed", "value": "That did not send. Please try again.", "locale": "en" }
|
|
62
|
-
],
|
|
63
|
-
"forms": ["contact"]
|
|
64
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Content guide
|
|
2
|
-
|
|
3
|
-
Inspect the site's content model and homepage copy. Suggest a small editorial
|
|
4
|
-
backlog that makes the site more useful to its intended audience.
|
|
5
|
-
|
|
6
|
-
Return three suggestions, each with the content type or field it touches and
|
|
7
|
-
why it matters. Use the existing content as evidence. Do not write files,
|
|
8
|
-
change entries, or create labels.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
- cms:read
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Keep starter agents manual-only. Adding a schedule or webhook and pushing to
|
|
2
|
-
# main registers an automation for the project.
|
|
3
|
-
version: 1
|
|
4
|
-
description: 'Suggest useful editorial improvements from the existing CMS content.'
|
|
5
|
-
data_sources:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
triggers:
|
|
9
|
-
- type: manual
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Site review
|
|
2
|
-
|
|
3
|
-
Review the starter project as a teammate would before the first publish.
|
|
4
|
-
|
|
5
|
-
Read the declared routes, the CMS content, the labels, and the public site.
|
|
6
|
-
Return a short report with:
|
|
7
|
-
|
|
8
|
-
- one thing that already feels clear;
|
|
9
|
-
- the three highest-value improvements;
|
|
10
|
-
- any missing content or broken route you can prove;
|
|
11
|
-
- one next action that a human or coding agent can review.
|
|
12
|
-
|
|
13
|
-
This is a read-only review. Do not edit files, change CMS content, create
|
|
14
|
-
branches, or publish anything.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Keep starter agents manual-only. Adding a schedule or webhook and pushing to
|
|
2
|
-
# main registers an automation for the project.
|
|
3
|
-
version: 1
|
|
4
|
-
description: 'Review the starter site and return a concise, read-only launch report.'
|
|
5
|
-
data_sources:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
triggers:
|
|
9
|
-
- type: manual
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "contact",
|
|
3
|
-
"label": "Start a conversation",
|
|
4
|
-
"fields": {
|
|
5
|
-
"name": { "type": "text", "label": "Your name", "required": true },
|
|
6
|
-
"email": { "type": "email", "label": "Email address", "required": true },
|
|
7
|
-
"message": {
|
|
8
|
-
"type": "textarea",
|
|
9
|
-
"label": "What are you building?",
|
|
10
|
-
"required": true,
|
|
11
|
-
"validation": { "minLength": 10, "maxLength": 2000 }
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// AUTO-GENERATED ⚠️ — do not edit by hand
|
|
2
|
-
// Source: src/forms/contact.json
|
|
3
|
-
// The CLI replaces the placeholder form id while seeding the starter. `fimo
|
|
4
|
-
// validate` regenerates this file from the JSON spec on later edits.
|
|
5
|
-
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
|
|
8
|
-
export const FORM_ID = '__FIMO_FORM_ID__';
|
|
9
|
-
|
|
10
|
-
export const ContactSchema = z.object({
|
|
11
|
-
name: z.string().min(1, 'Your name is required'),
|
|
12
|
-
email: z.string().min(1, 'Email address is required').email('Invalid email address'),
|
|
13
|
-
message: z.string().min(1, 'What are you building? is required').min(10).max(2000),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export type ContactData = z.infer<typeof ContactSchema>;
|
|
17
|
-
|
|
18
|
-
// @ts-ignore - meta.env is a vite feature
|
|
19
|
-
const base = import.meta.env.VITE_API_URL;
|
|
20
|
-
|
|
21
|
-
export async function submitContact(data: ContactData): Promise<{
|
|
22
|
-
success: boolean;
|
|
23
|
-
errors?: Record<string, string>;
|
|
24
|
-
}> {
|
|
25
|
-
let response: Response;
|
|
26
|
-
try {
|
|
27
|
-
response = await fetch(`${base}/forms/${FORM_ID}/submit`, {
|
|
28
|
-
method: 'POST',
|
|
29
|
-
headers: { 'Content-Type': 'application/json' },
|
|
30
|
-
body: JSON.stringify(data),
|
|
31
|
-
});
|
|
32
|
-
} catch {
|
|
33
|
-
return { success: false };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (response.ok) {
|
|
37
|
-
return { success: true };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (response.status === 422) {
|
|
41
|
-
const json = (await response.json()) as { errors?: Record<string, string> };
|
|
42
|
-
return { success: false, errors: json.errors };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return { success: false };
|
|
46
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { type FimoString, Text, useLabels } from 'fimo-react';
|
|
2
|
-
import { ArrowUpRight, Check } from 'lucide-react';
|
|
3
|
-
import { type FormEvent, type ReactNode, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { Button } from '@/components/ui/button';
|
|
6
|
-
import { Input } from '@/components/ui/input';
|
|
7
|
-
import { Label } from '@/components/ui/label';
|
|
8
|
-
import { Spinner } from '@/components/ui/spinner';
|
|
9
|
-
import { Textarea } from '@/components/ui/textarea';
|
|
10
|
-
import { ContactSchema, submitContact } from '@/forms/contact';
|
|
11
|
-
import { cn } from '@/lib/utils';
|
|
12
|
-
import type { HomePage } from '@/schemas/HomePage';
|
|
13
|
-
|
|
14
|
-
import { Bar } from './SiteContent';
|
|
15
|
-
|
|
16
|
-
export function Contact({ page, loading }: { page?: HomePage; loading: boolean }) {
|
|
17
|
-
return (
|
|
18
|
-
<section id="contact" className="dark bg-background text-foreground scroll-mt-16">
|
|
19
|
-
<div className="mx-auto grid w-full max-w-6xl gap-12 px-6 py-20 lg:grid-cols-12 lg:gap-16 lg:px-10 lg:py-28">
|
|
20
|
-
<div className="lg:col-span-5">
|
|
21
|
-
{page ? (
|
|
22
|
-
<>
|
|
23
|
-
<Text
|
|
24
|
-
value={page.contactHeading}
|
|
25
|
-
as="h2"
|
|
26
|
-
className="text-4xl font-semibold tracking-[-0.03em] text-balance lg:text-5xl"
|
|
27
|
-
/>
|
|
28
|
-
<Text value={page.contactIntro} as="p" className="text-muted-foreground mt-6 max-w-md leading-relaxed" />
|
|
29
|
-
</>
|
|
30
|
-
) : (
|
|
31
|
-
<div aria-hidden>
|
|
32
|
-
<div className="grid gap-3">
|
|
33
|
-
<Bar loading={loading} className="h-10 w-4/5" />
|
|
34
|
-
<Bar loading={loading} className="h-10 w-2/5" />
|
|
35
|
-
</div>
|
|
36
|
-
<div className="mt-8 grid gap-2">
|
|
37
|
-
<Bar loading={loading} className="h-4 w-full max-w-md" />
|
|
38
|
-
<Bar loading={loading} className="h-4 w-11/12 max-w-md" />
|
|
39
|
-
<Bar loading={loading} className="h-4 w-2/3 max-w-md" />
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<div className="lg:col-span-7">
|
|
46
|
-
<ContactForm />
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</section>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type SubmitState = 'idle' | 'submitting' | 'sent' | 'invalid' | 'failed';
|
|
54
|
-
|
|
55
|
-
function ContactForm() {
|
|
56
|
-
const { t } = useLabels();
|
|
57
|
-
const [state, setState] = useState<SubmitState>('idle');
|
|
58
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
59
|
-
|
|
60
|
-
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
61
|
-
event.preventDefault();
|
|
62
|
-
// `currentTarget` is cleared once the handler yields, so keep the node.
|
|
63
|
-
const form = event.currentTarget;
|
|
64
|
-
setErrors({});
|
|
65
|
-
|
|
66
|
-
const parsed = ContactSchema.safeParse(Object.fromEntries(new FormData(form)));
|
|
67
|
-
if (!parsed.success) {
|
|
68
|
-
const fieldErrors: Record<string, string> = {};
|
|
69
|
-
for (const issue of parsed.error.issues) {
|
|
70
|
-
fieldErrors[issue.path[0] as string] = issue.message;
|
|
71
|
-
}
|
|
72
|
-
setErrors(fieldErrors);
|
|
73
|
-
setState('invalid');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
setState('submitting');
|
|
78
|
-
const result = await submitContact(parsed.data);
|
|
79
|
-
if (result.success) {
|
|
80
|
-
form.reset();
|
|
81
|
-
setState('sent');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// A rejected submission comes back with per-field messages; a network or
|
|
86
|
-
// server failure has nothing to attach to a field, so it needs its own.
|
|
87
|
-
const fieldErrors = result.errors ?? {};
|
|
88
|
-
setErrors(fieldErrors);
|
|
89
|
-
setState(Object.keys(fieldErrors).length > 0 ? 'invalid' : 'failed');
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const busy = state === 'submitting';
|
|
93
|
-
const sent = state === 'sent';
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<form
|
|
97
|
-
onSubmit={handleSubmit}
|
|
98
|
-
noValidate
|
|
99
|
-
aria-busy={busy}
|
|
100
|
-
className="border-border bg-card rounded-xl border p-6 sm:p-8"
|
|
101
|
-
>
|
|
102
|
-
<div className="grid gap-6 sm:grid-cols-2">
|
|
103
|
-
<FormField id="contact-name" label={t('contact.name')} error={errors.name}>
|
|
104
|
-
<Input
|
|
105
|
-
id="contact-name"
|
|
106
|
-
name="name"
|
|
107
|
-
type="text"
|
|
108
|
-
autoComplete="name"
|
|
109
|
-
disabled={busy || sent}
|
|
110
|
-
aria-invalid={Boolean(errors.name)}
|
|
111
|
-
aria-describedby={errors.name ? 'contact-name-error' : undefined}
|
|
112
|
-
/>
|
|
113
|
-
</FormField>
|
|
114
|
-
|
|
115
|
-
<FormField id="contact-email" label={t('contact.email')} error={errors.email}>
|
|
116
|
-
<Input
|
|
117
|
-
id="contact-email"
|
|
118
|
-
name="email"
|
|
119
|
-
type="email"
|
|
120
|
-
autoComplete="email"
|
|
121
|
-
disabled={busy || sent}
|
|
122
|
-
aria-invalid={Boolean(errors.email)}
|
|
123
|
-
aria-describedby={errors.email ? 'contact-email-error' : undefined}
|
|
124
|
-
/>
|
|
125
|
-
</FormField>
|
|
126
|
-
|
|
127
|
-
<div className="sm:col-span-2">
|
|
128
|
-
<FormField id="contact-message" label={t('contact.message')} error={errors.message}>
|
|
129
|
-
<Textarea
|
|
130
|
-
id="contact-message"
|
|
131
|
-
name="message"
|
|
132
|
-
rows={5}
|
|
133
|
-
disabled={busy || sent}
|
|
134
|
-
aria-invalid={Boolean(errors.message)}
|
|
135
|
-
aria-describedby={errors.message ? 'contact-message-error' : undefined}
|
|
136
|
-
/>
|
|
137
|
-
</FormField>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<div className="mt-8 flex flex-wrap items-center gap-x-4 gap-y-3">
|
|
142
|
-
<Button
|
|
143
|
-
type="submit"
|
|
144
|
-
size="lg"
|
|
145
|
-
disabled={busy || sent}
|
|
146
|
-
className={cn('group', state === 'invalid' && 'ring-destructive/40 ring-[3px]')}
|
|
147
|
-
>
|
|
148
|
-
{busy ? <Spinner /> : null}
|
|
149
|
-
{sent ? <Check /> : null}
|
|
150
|
-
<Text value={t('contact.submit')} />
|
|
151
|
-
{!busy && !sent ? (
|
|
152
|
-
<ArrowUpRight className="transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
|
153
|
-
) : null}
|
|
154
|
-
</Button>
|
|
155
|
-
|
|
156
|
-
{/* Both regions stay mounted and empty: a live region inserted at the same
|
|
157
|
-
moment as its text is announced unreliably. */}
|
|
158
|
-
<p role="status" className="text-muted-foreground text-sm">
|
|
159
|
-
{sent ? <Text value={t('contact.sent')} /> : null}
|
|
160
|
-
</p>
|
|
161
|
-
<p role="alert" className="text-destructive text-sm">
|
|
162
|
-
{state === 'failed' ? <Text value={t('contact.failed')} /> : null}
|
|
163
|
-
</p>
|
|
164
|
-
</div>
|
|
165
|
-
</form>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function FormField({
|
|
170
|
-
id,
|
|
171
|
-
label,
|
|
172
|
-
error,
|
|
173
|
-
children,
|
|
174
|
-
}: {
|
|
175
|
-
id: string;
|
|
176
|
-
label: FimoString;
|
|
177
|
-
error?: string;
|
|
178
|
-
children: ReactNode;
|
|
179
|
-
}) {
|
|
180
|
-
return (
|
|
181
|
-
<div className="grid gap-2">
|
|
182
|
-
<Label htmlFor={id} className="text-muted-foreground text-[11px] font-medium tracking-[0.16em] uppercase">
|
|
183
|
-
<Text value={label} />
|
|
184
|
-
</Label>
|
|
185
|
-
{children}
|
|
186
|
-
{error ? (
|
|
187
|
-
<p id={`${id}-error`} role="alert" className="text-destructive text-xs">
|
|
188
|
-
{error}
|
|
189
|
-
</p>
|
|
190
|
-
) : null}
|
|
191
|
-
</div>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import HomePageContent from '@/schemas/HomePage';
|
|
2
|
-
|
|
3
|
-
import { Contact } from './Contact';
|
|
4
|
-
import { ContentFallback, Hero, SiteHeader, Stories } from './SiteContent';
|
|
5
|
-
|
|
6
|
-
const Index = () => {
|
|
7
|
-
const { data: page, isLoading } = HomePageContent.useGet();
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div className="bg-background text-foreground min-h-screen">
|
|
11
|
-
<SiteHeader />
|
|
12
|
-
<main>
|
|
13
|
-
{page ? (
|
|
14
|
-
<>
|
|
15
|
-
<Hero page={page} />
|
|
16
|
-
<Stories items={page.featured ?? []} />
|
|
17
|
-
</>
|
|
18
|
-
) : (
|
|
19
|
-
<ContentFallback loading={isLoading} />
|
|
20
|
-
)}
|
|
21
|
-
<Contact page={page} loading={isLoading} />
|
|
22
|
-
</main>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default Index;
|