fimo 0.14.0 → 0.15.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 (168) hide show
  1. package/README.md +1 -0
  2. package/assets/astro/components/Boolean.astro +14 -0
  3. package/assets/astro/components/Date.astro +23 -0
  4. package/assets/astro/components/Image.astro +79 -0
  5. package/assets/astro/components/Json.astro +14 -0
  6. package/assets/astro/components/RichText.astro +17 -0
  7. package/assets/astro/components/StaticImage.astro +66 -0
  8. package/assets/astro/components/Text.astro +23 -0
  9. package/assets/astro/components/Video.astro +47 -0
  10. package/assets/astro/components/index.d.ts +8 -0
  11. package/assets/astro/components/index.js +8 -0
  12. package/assets/content-templates/shared/server.eta +56 -0
  13. package/assets/content-templates/sveltekit/content.eta +86 -0
  14. package/assets/skills/fimo/SKILL.md +2 -0
  15. package/assets/skills/fimo-cli/SKILL.md +0 -1
  16. package/assets/skills/fimo-cli/references/branches.md +14 -13
  17. package/assets/skills/fimo-cli/references/deploy.md +4 -3
  18. package/assets/skills/fimo-cli/references/describe.md +1 -1
  19. package/assets/skills/fimo-cli/references/init.md +11 -11
  20. package/assets/skills/fimo-cli/references/labels.md +3 -3
  21. package/assets/skills/fimo-cli/references/locales.md +1 -1
  22. package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +1 -1
  23. package/assets/skills/fimo-code/SKILL.md +11 -3
  24. package/assets/skills/fimo-code/references/assets.md +2 -2
  25. package/assets/skills/fimo-code/references/content/clients.md +5 -1
  26. package/assets/skills/fimo-code/references/content.md +3 -1
  27. package/assets/skills/fimo-code/references/frameworks/astro.md +127 -0
  28. package/assets/skills/fimo-code/references/frameworks/compatibility.md +19 -0
  29. package/assets/skills/fimo-code/references/frameworks/hosting.md +15 -3
  30. package/assets/skills/fimo-code/references/frameworks/react-router.md +37 -209
  31. package/assets/skills/fimo-code/references/frameworks/sveltekit.md +110 -0
  32. package/assets/skills/fimo-code/references/frameworks/vite-react.md +35 -166
  33. package/assets/skills/fimo-code/references/labels.md +16 -6
  34. package/assets/skills/fimo-code/references/locales.md +3 -1
  35. package/assets/skills/fimo-code/references/ui.md +19 -15
  36. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +0 -1
  37. package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +0 -1
  38. package/assets/sveltekit/components/Boolean.svelte +19 -0
  39. package/assets/sveltekit/components/Date.svelte +33 -0
  40. package/assets/sveltekit/components/FimoProvider.svelte +35 -0
  41. package/assets/sveltekit/components/Image.svelte +80 -0
  42. package/assets/sveltekit/components/Json.svelte +18 -0
  43. package/assets/sveltekit/components/Label.svelte +16 -0
  44. package/assets/sveltekit/components/Number.svelte +21 -0
  45. package/assets/sveltekit/components/RichText.svelte +19 -0
  46. package/assets/sveltekit/components/Text.svelte +30 -0
  47. package/assets/sveltekit/components/Video.svelte +37 -0
  48. package/assets/sveltekit/components/index.d.ts +10 -0
  49. package/assets/sveltekit/components/index.js +10 -0
  50. package/dist/build/astro/config.d.ts +4 -0
  51. package/dist/build/astro/config.js +45 -0
  52. package/dist/build/next/config.js +16 -0
  53. package/dist/build/sveltekit/config.d.ts +6 -0
  54. package/dist/build/sveltekit/config.js +8 -0
  55. package/dist/build/sveltekit/data-id.d.ts +3 -0
  56. package/dist/build/sveltekit/data-id.js +171 -0
  57. package/dist/build/sveltekit/vite.d.ts +13 -0
  58. package/dist/build/sveltekit/vite.js +15 -0
  59. package/dist/build/vite/index.js +2 -0
  60. package/dist/build/vite/plugins/data-id.d.ts +9 -0
  61. package/dist/build/vite/plugins/data-id.js +6 -1
  62. package/dist/build/vite/plugins/runtime-env.d.ts +21 -0
  63. package/dist/build/vite/plugins/runtime-env.js +68 -0
  64. package/dist/cli/bundle.json +1 -1
  65. package/dist/cli/index.js +100 -99
  66. package/dist/runtime/app/FimoProviders.d.ts +1 -1
  67. package/dist/runtime/app/FimoProviders.js +11 -5
  68. package/dist/runtime/astro/internal.d.ts +6 -0
  69. package/dist/runtime/astro/internal.js +16 -0
  70. package/dist/runtime/astro/preview.d.ts +14 -0
  71. package/dist/runtime/astro/preview.js +103 -0
  72. package/dist/runtime/astro/render-rich-text.d.ts +2 -0
  73. package/dist/runtime/astro/render-rich-text.js +1 -0
  74. package/dist/runtime/astro/server-runtime.d.ts +3 -0
  75. package/dist/runtime/astro/server-runtime.js +20 -0
  76. package/dist/runtime/astro/server.d.ts +15 -0
  77. package/dist/runtime/astro/server.js +14 -0
  78. package/dist/runtime/content/client/collection-core.d.ts +2 -17
  79. package/dist/runtime/content/client/collection-core.js +15 -8
  80. package/dist/runtime/content/client/core-types.d.ts +96 -0
  81. package/dist/runtime/content/client/core-types.js +1 -0
  82. package/dist/runtime/content/client/internal.d.ts +4 -4
  83. package/dist/runtime/content/client/internal.js +4 -3
  84. package/dist/runtime/content/client/runtime.d.ts +1 -1
  85. package/dist/runtime/content/client/server-content-client.d.ts +3 -3
  86. package/dist/runtime/content/client/server-content-client.js +2 -29
  87. package/dist/runtime/content/client/singleton-core.d.ts +2 -9
  88. package/dist/runtime/content/client/singleton-core.js +9 -4
  89. package/dist/runtime/content/client/types.d.ts +5 -115
  90. package/dist/runtime/content/index.d.ts +3 -3
  91. package/dist/runtime/labels/fetch.js +5 -3
  92. package/dist/runtime/next/client.d.ts +2 -1
  93. package/dist/runtime/next/facade.d.ts +1 -1
  94. package/dist/runtime/next/provider.js +1 -1
  95. package/dist/runtime/next/server-runtime.js +1 -1
  96. package/dist/runtime/next/server.d.ts +5 -4
  97. package/dist/runtime/paths/get-fimo-paths.js +8 -3
  98. package/dist/runtime/preview/bridge.d.ts +8 -0
  99. package/dist/runtime/preview/bridge.js +20 -0
  100. package/dist/runtime/preview/location.d.ts +23 -0
  101. package/dist/runtime/preview/location.js +93 -0
  102. package/dist/runtime/preview/routes.d.ts +10 -0
  103. package/dist/runtime/preview/routes.js +29 -0
  104. package/dist/runtime/primitives/index.d.ts +4 -1
  105. package/dist/runtime/primitives/index.js +1 -0
  106. package/dist/runtime/primitives/lib/image-optimization.d.ts +27 -2
  107. package/dist/runtime/primitives/lib/image-optimization.js +40 -0
  108. package/dist/runtime/primitives/lib/index.d.ts +1 -1
  109. package/dist/runtime/primitives/lib/primitives.d.ts +0 -54
  110. package/dist/runtime/primitives/lib/primitives.js +6 -0
  111. package/dist/runtime/primitives/lib/render-rich-text.d.ts +3 -0
  112. package/dist/runtime/primitives/lib/render-rich-text.js +112 -0
  113. package/dist/runtime/react/primitives/contracts.d.ts +40 -2
  114. package/dist/runtime/react/primitives/renderers/ImageRenderer.js +15 -34
  115. package/dist/runtime/react/primitives/renderers/RichTextRenderer.d.ts +2 -1
  116. package/dist/runtime/react/primitives/renderers/StaticImage.js +13 -30
  117. package/dist/runtime/routing/AppRoutesProvider.js +20 -8
  118. package/dist/runtime/shared/env-identity.d.ts +43 -0
  119. package/dist/runtime/shared/env-identity.js +88 -0
  120. package/dist/runtime/shared/local-runtime-env.d.ts +65 -0
  121. package/dist/runtime/shared/local-runtime-env.js +169 -0
  122. package/dist/runtime/{react-router → shared}/preview-location.d.ts +7 -13
  123. package/dist/runtime/{react-router → shared}/preview-location.js +26 -27
  124. package/dist/runtime/sveltekit/content.d.ts +25 -0
  125. package/dist/runtime/sveltekit/content.js +33 -0
  126. package/dist/runtime/sveltekit/context-contract.d.ts +5 -0
  127. package/dist/runtime/sveltekit/context-contract.js +2 -0
  128. package/dist/runtime/sveltekit/context.d.ts +8 -0
  129. package/dist/runtime/sveltekit/context.js +25 -0
  130. package/dist/runtime/sveltekit/index.d.ts +7 -0
  131. package/dist/runtime/sveltekit/index.js +4 -0
  132. package/dist/runtime/sveltekit/internal.d.ts +6 -0
  133. package/dist/runtime/sveltekit/internal.js +12 -0
  134. package/dist/runtime/sveltekit/labels.d.ts +16 -0
  135. package/dist/runtime/sveltekit/labels.js +13 -0
  136. package/dist/runtime/sveltekit/preview.d.ts +19 -0
  137. package/dist/runtime/sveltekit/preview.js +77 -0
  138. package/dist/runtime/sveltekit/runtime.d.ts +8 -0
  139. package/dist/runtime/sveltekit/runtime.js +17 -0
  140. package/dist/runtime/templates.d.ts +1 -1
  141. package/dist/runtime/templates.js +1 -1
  142. package/dist/runtime/vite/Label.d.ts +13 -0
  143. package/dist/runtime/vite/Label.js +16 -0
  144. package/dist/scripts/extract-translations.js +103 -43
  145. package/dist/scripts/sync-forms.js +1 -1
  146. package/dist/scripts/sync-schemas.js +83 -16
  147. package/package.json +56 -2
  148. package/release.json +2 -2
  149. package/templates/nextjs/_gitignore +1 -0
  150. package/templates/react-router/package.json +1 -1
  151. package/templates/sveltekit/.fimo/config.json +15 -0
  152. package/templates/sveltekit/AGENTS.md +16 -0
  153. package/templates/sveltekit/CLAUDE.md +5 -0
  154. package/templates/sveltekit/_gitignore +16 -0
  155. package/templates/sveltekit/package.json +28 -0
  156. package/templates/sveltekit/pnpm-workspace.yaml +4 -0
  157. package/templates/sveltekit/src/app.html +11 -0
  158. package/templates/sveltekit/src/routes/+layout.server.ts +8 -0
  159. package/templates/sveltekit/src/routes/+layout.svelte +12 -0
  160. package/templates/sveltekit/src/routes/+page.svelte +48 -0
  161. package/templates/sveltekit/svelte.config.js +5 -0
  162. package/templates/sveltekit/tsconfig.json +14 -0
  163. package/templates/sveltekit/vite.config.ts +5 -0
  164. /package/assets/content-templates/{form-template.eta → forms/react.eta} +0 -0
  165. /package/assets/content-templates/{next-client-template.eta → next/client.eta} +0 -0
  166. /package/assets/content-templates/{next-server-template.eta → next/server.eta} +0 -0
  167. /package/assets/content-templates/{hooks-template.eta → react/client.eta} +0 -0
  168. /package/assets/content-templates/{types-template.eta → shared/types.eta} +0 -0
@@ -1,4 +1,3 @@
1
- import type React from 'react';
2
1
  /**
3
2
  * Fimo Primitives - Source tracking for inline editing
4
3
  *
@@ -98,59 +97,6 @@ export interface FimoRichTextContent {
98
97
  }>;
99
98
  text?: string;
100
99
  }
101
- /**
102
- * Props passed to custom node renderers
103
- */
104
- export interface FimoRichTextNodeProps {
105
- children?: React.ReactNode;
106
- node: FimoRichTextContent;
107
- }
108
- /**
109
- * Props passed to custom mark renderers
110
- */
111
- export interface FimoRichTextMarkProps {
112
- children: React.ReactNode;
113
- mark: {
114
- type: string;
115
- attrs?: Record<string, unknown>;
116
- };
117
- }
118
- /**
119
- * Custom renderer map for RichText component.
120
- *
121
- * Override how specific node types or mark types are rendered.
122
- * Unspecified types fall back to default HTML elements.
123
- *
124
- * @example
125
- * const components: FimoRichTextComponents = {
126
- * heading: ({ node, children }) => <MyHeading level={node.attrs?.level}>{children}</MyHeading>,
127
- * image: ({ node }) => <OptimizedImage src={node.attrs?.src} alt={node.attrs?.alt} />,
128
- * link: ({ mark, children }) => <CustomLink href={mark.attrs?.href}>{children}</CustomLink>,
129
- * }
130
- */
131
- export interface FimoRichTextComponents {
132
- paragraph?: React.ComponentType<FimoRichTextNodeProps>;
133
- heading?: React.ComponentType<FimoRichTextNodeProps>;
134
- bulletList?: React.ComponentType<FimoRichTextNodeProps>;
135
- orderedList?: React.ComponentType<FimoRichTextNodeProps>;
136
- listItem?: React.ComponentType<FimoRichTextNodeProps>;
137
- blockquote?: React.ComponentType<FimoRichTextNodeProps>;
138
- codeBlock?: React.ComponentType<FimoRichTextNodeProps>;
139
- horizontalRule?: React.ComponentType<FimoRichTextNodeProps>;
140
- hardBreak?: React.ComponentType<FimoRichTextNodeProps>;
141
- image?: React.ComponentType<FimoRichTextNodeProps>;
142
- table?: React.ComponentType<FimoRichTextNodeProps>;
143
- tableRow?: React.ComponentType<FimoRichTextNodeProps>;
144
- tableCell?: React.ComponentType<FimoRichTextNodeProps>;
145
- tableHeader?: React.ComponentType<FimoRichTextNodeProps>;
146
- iframe?: React.ComponentType<FimoRichTextNodeProps>;
147
- bold?: React.ComponentType<FimoRichTextMarkProps>;
148
- italic?: React.ComponentType<FimoRichTextMarkProps>;
149
- underline?: React.ComponentType<FimoRichTextMarkProps>;
150
- strike?: React.ComponentType<FimoRichTextMarkProps>;
151
- code?: React.ComponentType<FimoRichTextMarkProps>;
152
- link?: React.ComponentType<FimoRichTextMarkProps>;
153
- }
154
100
  /**
155
101
  * FimoRichText - Rich text content with source tracking
156
102
  *
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Fimo Primitives - Source tracking for inline editing
3
+ *
4
+ * All Fimo types carry hidden metadata that enables the preview editor
5
+ * to know where content comes from (schema field, translation key, etc.)
6
+ */
1
7
  var _a, _b, _c, _d, _e, _f;
2
8
  // Symbol for accessing fimo metadata (hidden from JSON.stringify)
3
9
  export const FIMO_SOURCE = Symbol.for('fimo.source');
@@ -0,0 +1,3 @@
1
+ import type { FimoRichTextContent } from './primitives.js';
2
+ export declare function renderRichTextHtml(document: FimoRichTextContent | null | undefined): string;
3
+ //# sourceMappingURL=render-rich-text.d.ts.map
@@ -0,0 +1,112 @@
1
+ const IFRAME_STYLE = 'position:relative;width:100%;padding-bottom:56.25%;border-radius:0.375rem;overflow:hidden';
2
+ const IFRAME_CONTENT_STYLE = 'position:absolute;top:0;left:0;width:100%;height:100%;border:0';
3
+ function escapeHtml(value) {
4
+ return String(value ?? '')
5
+ .replace(/&/g, '&amp;')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, '&quot;')
9
+ .replace(/'/g, '&#39;');
10
+ }
11
+ function safeUrl(value, protocols) {
12
+ if (typeof value !== 'string' || value.trim() === '') {
13
+ return undefined;
14
+ }
15
+ const url = value.trim();
16
+ try {
17
+ const parsed = new URL(url, 'https://fimo.invalid');
18
+ return protocols.includes(parsed.protocol) ? url : undefined;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ function attribute(name, value) {
25
+ return value == null || value === '' ? '' : ` ${name}="${escapeHtml(value)}"`;
26
+ }
27
+ function renderMarks(node) {
28
+ let content = escapeHtml(node.text);
29
+ for (const mark of node.marks ?? []) {
30
+ switch (mark.type) {
31
+ case 'bold':
32
+ case 'strong':
33
+ content = `<strong>${content}</strong>`;
34
+ break;
35
+ case 'italic':
36
+ case 'em':
37
+ content = `<em>${content}</em>`;
38
+ break;
39
+ case 'underline':
40
+ content = `<u>${content}</u>`;
41
+ break;
42
+ case 'strike':
43
+ content = `<s>${content}</s>`;
44
+ break;
45
+ case 'code':
46
+ content = `<code>${content}</code>`;
47
+ break;
48
+ case 'link': {
49
+ const href = safeUrl(mark.attrs?.href, ['http:', 'https:', 'mailto:', 'tel:']);
50
+ if (href) {
51
+ content = `<a href="${escapeHtml(href)}"${attribute('target', mark.attrs?.target)}${attribute('rel', mark.attrs?.rel)}>${content}</a>`;
52
+ }
53
+ break;
54
+ }
55
+ }
56
+ }
57
+ return content;
58
+ }
59
+ function renderNode(node) {
60
+ if (node.type === 'text') {
61
+ return renderMarks(node);
62
+ }
63
+ const children = (node.content ?? []).map(renderNode).join('');
64
+ switch (node.type) {
65
+ case 'doc':
66
+ return children;
67
+ case 'paragraph':
68
+ return `<p>${children}</p>`;
69
+ case 'heading': {
70
+ const requestedLevel = Number(node.attrs?.level);
71
+ const level = Number.isInteger(requestedLevel) ? Math.min(6, Math.max(1, requestedLevel)) : 1;
72
+ return `<h${level}>${children}</h${level}>`;
73
+ }
74
+ case 'bulletList':
75
+ return `<ul>${children}</ul>`;
76
+ case 'orderedList':
77
+ return `<ol>${children}</ol>`;
78
+ case 'listItem':
79
+ return `<li>${children}</li>`;
80
+ case 'blockquote':
81
+ return `<blockquote>${children}</blockquote>`;
82
+ case 'codeBlock':
83
+ return `<pre><code>${children}</code></pre>`;
84
+ case 'horizontalRule':
85
+ return '<hr>';
86
+ case 'hardBreak':
87
+ return '<br>';
88
+ case 'image': {
89
+ const src = safeUrl(node.attrs?.src, ['http:', 'https:']);
90
+ return src ? `<img src="${escapeHtml(src)}" alt="${escapeHtml(node.attrs?.alt)}">` : '';
91
+ }
92
+ case 'table':
93
+ return `<table class="border-border w-full border-collapse border"><tbody>${children}</tbody></table>`;
94
+ case 'tableRow':
95
+ return `<tr class="border-border border-b last:border-b-0">${children}</tr>`;
96
+ case 'tableCell':
97
+ return `<td class="border-border border px-3 py-2 align-top">${children}</td>`;
98
+ case 'tableHeader':
99
+ return `<th class="border-border bg-muted border px-3 py-2 text-left align-top font-semibold">${children}</th>`;
100
+ case 'iframe': {
101
+ const src = safeUrl(node.attrs?.src, ['http:', 'https:']);
102
+ return src
103
+ ? `<div style="${IFRAME_STYLE}"><iframe src="${escapeHtml(src)}" style="${IFRAME_CONTENT_STYLE}" allowfullscreen></iframe></div>`
104
+ : '';
105
+ }
106
+ default:
107
+ return children;
108
+ }
109
+ }
110
+ export function renderRichTextHtml(document) {
111
+ return document ? renderNode(document) : '';
112
+ }
@@ -1,6 +1,40 @@
1
- import type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
1
+ import type { ComponentPropsWithoutRef, ComponentType, ElementType, ReactNode } from 'react';
2
2
  import type { ImageFit } from '../../primitives/lib/image-optimization.js';
3
- import type { FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoRichTextComponents, FimoString } from '../../primitives/lib/index.js';
3
+ import type { FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoRichTextContent, FimoString } from '../../primitives/lib/index.js';
4
+ export interface FimoRichTextNodeProps {
5
+ children?: ReactNode;
6
+ node: FimoRichTextContent;
7
+ }
8
+ export interface FimoRichTextMarkProps {
9
+ children: ReactNode;
10
+ mark: {
11
+ type: string;
12
+ attrs?: Record<string, unknown>;
13
+ };
14
+ }
15
+ export interface FimoRichTextComponents {
16
+ paragraph?: ComponentType<FimoRichTextNodeProps>;
17
+ heading?: ComponentType<FimoRichTextNodeProps>;
18
+ bulletList?: ComponentType<FimoRichTextNodeProps>;
19
+ orderedList?: ComponentType<FimoRichTextNodeProps>;
20
+ listItem?: ComponentType<FimoRichTextNodeProps>;
21
+ blockquote?: ComponentType<FimoRichTextNodeProps>;
22
+ codeBlock?: ComponentType<FimoRichTextNodeProps>;
23
+ horizontalRule?: ComponentType<FimoRichTextNodeProps>;
24
+ hardBreak?: ComponentType<FimoRichTextNodeProps>;
25
+ image?: ComponentType<FimoRichTextNodeProps>;
26
+ table?: ComponentType<FimoRichTextNodeProps>;
27
+ tableRow?: ComponentType<FimoRichTextNodeProps>;
28
+ tableCell?: ComponentType<FimoRichTextNodeProps>;
29
+ tableHeader?: ComponentType<FimoRichTextNodeProps>;
30
+ iframe?: ComponentType<FimoRichTextNodeProps>;
31
+ bold?: ComponentType<FimoRichTextMarkProps>;
32
+ italic?: ComponentType<FimoRichTextMarkProps>;
33
+ underline?: ComponentType<FimoRichTextMarkProps>;
34
+ strike?: ComponentType<FimoRichTextMarkProps>;
35
+ code?: ComponentType<FimoRichTextMarkProps>;
36
+ link?: ComponentType<FimoRichTextMarkProps>;
37
+ }
4
38
  export type BooleanProps<T extends ElementType = 'span'> = {
5
39
  value: FimoBoolean;
6
40
  as?: T;
@@ -46,6 +80,10 @@ export type TextProps<T extends ElementType = 'span'> = {
46
80
  as?: T;
47
81
  children?: (value: string) => ReactNode;
48
82
  } & Omit<ComponentPropsWithoutRef<T>, 'value' | 'as' | 'children'>;
83
+ export type LabelProps<T extends ElementType = 'span'> = Omit<TextProps<T>, 'value'> & {
84
+ /** Literal label key, e.g. `hero.title`. Dynamic keys cannot be validated. */
85
+ value: string;
86
+ };
49
87
  export interface BaseVideoProps extends Omit<ComponentPropsWithoutRef<'video'>, 'src' | 'children'> {
50
88
  overrideSrc?: string;
51
89
  }
@@ -1,50 +1,31 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { buildOptimizedUrl, clampQuality, computeImageOptimizationConfig, isOptimizableFimoUrl, resolveFit, toObjectFit, toPositiveInt, } from '../../../primitives/lib/image-optimization.js';
2
+ import { resolveImagePresentation } from '../../../primitives/lib/image-optimization.js';
3
3
  import { FIMO_SOURCE } from '../../../primitives/lib/primitives.js';
4
4
  export function ImageRenderer({ value: media, children, quality, unoptimized = false, fit, width, height, alt: altOverride, overrideSrc, srcSet: srcSetOverride, sizes, style, hostSuffixes, elementRef, ...imgProps }) {
5
5
  const mediaSource = media[FIMO_SOURCE];
6
6
  if (children) {
7
7
  return (_jsx("span", { ref: elementRef, "data-fimo-source": mediaSource, children: children(media) }));
8
8
  }
9
- const normalizedQuality = clampQuality(quality);
10
- const normalizedFit = resolveFit(fit);
11
- const requestedWidth = toPositiveInt(width);
12
- const requestedHeight = toPositiveInt(height);
13
- const displayWidth = requestedWidth ?? media.width;
14
- const displayHeight = requestedHeight ?? media.height;
15
- const originalSrc = media.url;
16
- const resolvedAlt = altOverride ?? media.alt;
17
- const hasSizesAttr = typeof sizes === 'string' && sizes.trim().length > 0;
18
- const hasSizingHint = hasSizesAttr || requestedWidth !== undefined || requestedHeight !== undefined;
19
- const canOptimizeSource = !unoptimized && isOptimizableFimoUrl(originalSrc, hostSuffixes);
20
- const shouldOptimize = canOptimizeSource && hasSizingHint;
21
- const { generatedSrcSet, transformWidth, transformDpr } = computeImageOptimizationConfig({
22
- shouldOptimize,
23
- hasSizesAttr,
24
- srcSetOverride,
25
- originalSrc,
9
+ const image = resolveImagePresentation({
10
+ src: media.url,
11
+ alt: altOverride ?? media.alt,
26
12
  mediaWidth: media.width,
27
- renderWidth: displayWidth,
28
- quality: normalizedQuality,
29
- fit: normalizedFit,
13
+ mediaHeight: media.height,
14
+ quality,
15
+ fit,
16
+ unoptimized,
17
+ overrideSrc,
18
+ width,
19
+ height,
20
+ sizes,
21
+ srcSet: srcSetOverride,
30
22
  hostSuffixes,
31
23
  });
32
- const finalSrcSet = srcSetOverride ?? generatedSrcSet;
33
- const optimizedSrc = shouldOptimize
34
- ? buildOptimizedUrl(originalSrc, {
35
- width: transformWidth,
36
- quality: normalizedQuality,
37
- fit: normalizedFit,
38
- dpr: transformDpr,
39
- hostSuffixes,
40
- })
41
- : originalSrc;
42
- const finalSrc = overrideSrc ?? optimizedSrc;
43
24
  const finalStyle = style?.objectFit
44
25
  ? style
45
26
  : {
46
27
  ...style,
47
- objectFit: toObjectFit(normalizedFit),
28
+ objectFit: image.objectFit,
48
29
  };
49
- return (_jsx("img", { ref: elementRef, src: finalSrc, alt: resolvedAlt, width: displayWidth, height: displayHeight, style: finalStyle, sizes: sizes, srcSet: finalSrcSet, ...imgProps, "data-fimo-source": mediaSource }));
30
+ return (_jsx("img", { ref: elementRef, src: image.src, alt: image.alt, width: image.width, height: image.height, style: finalStyle, sizes: image.sizes, srcSet: image.srcSet, ...imgProps, "data-fimo-source": mediaSource }));
50
31
  }
@@ -1,5 +1,6 @@
1
1
  import { type ComponentPropsWithoutRef, type Ref } from 'react';
2
- import type { FimoRichTextComponents, FimoRichTextContent } from '../../../primitives/lib/index.js';
2
+ import type { FimoRichTextContent } from '../../../primitives/lib/index.js';
3
+ import type { FimoRichTextComponents } from '../contracts.js';
3
4
  interface RichTextRendererProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
4
5
  document: FimoRichTextContent | null | undefined;
5
6
  source: string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { clampQuality, resolveFit, toObjectFit, toPositiveInt, isOptimizableFimoUrl, buildOptimizedUrl, computeImageOptimizationConfig, } from '../../../primitives/lib/image-optimization.js';
2
+ import { resolveImagePresentation } from '../../../primitives/lib/image-optimization.js';
3
3
  /**
4
4
  * Optimized image component for non-schema images.
5
5
  *
@@ -36,40 +36,23 @@ export function StaticImage(props) {
36
36
  if (!src) {
37
37
  return null;
38
38
  }
39
- const normalizedQuality = clampQuality(quality);
40
- const normalizedFit = resolveFit(fit);
41
- const requestedWidth = toPositiveInt(width);
42
- const requestedHeight = toPositiveInt(height);
43
- const hasSizesAttr = typeof sizes === 'string' && sizes.trim().length > 0;
44
- const hasSizingHint = hasSizesAttr || requestedWidth !== undefined || requestedHeight !== undefined;
45
- const canOptimizeSource = !unoptimized && isOptimizableFimoUrl(src, hostSuffixes);
46
- const shouldOptimize = canOptimizeSource && hasSizingHint;
47
- const { generatedSrcSet, transformWidth, transformDpr } = computeImageOptimizationConfig({
48
- shouldOptimize,
49
- hasSizesAttr,
50
- srcSetOverride,
51
- originalSrc: src,
52
- mediaWidth: undefined,
53
- renderWidth: requestedWidth,
54
- quality: normalizedQuality,
55
- fit: normalizedFit,
39
+ const image = resolveImagePresentation({
40
+ src,
41
+ alt,
42
+ quality,
43
+ fit,
44
+ unoptimized,
45
+ width,
46
+ height,
47
+ sizes,
48
+ srcSet: srcSetOverride,
56
49
  hostSuffixes,
57
50
  });
58
- const finalSrcSet = srcSetOverride ?? generatedSrcSet;
59
- const optimizedSrc = shouldOptimize
60
- ? buildOptimizedUrl(src, {
61
- width: transformWidth,
62
- quality: normalizedQuality,
63
- fit: normalizedFit,
64
- dpr: transformDpr,
65
- hostSuffixes,
66
- })
67
- : src;
68
51
  const finalStyle = style?.objectFit
69
52
  ? style
70
53
  : {
71
54
  ...style,
72
- objectFit: toObjectFit(normalizedFit),
55
+ objectFit: image.objectFit,
73
56
  };
74
- return (_jsx("img", { src: optimizedSrc, alt: alt, width: requestedWidth, height: requestedHeight, style: finalStyle, sizes: sizes, srcSet: finalSrcSet, ...imgProps }));
57
+ return (_jsx("img", { src: image.src, alt: image.alt, width: image.width, height: image.height, style: finalStyle, sizes: image.sizes, srcSet: image.srcSet, ...imgProps }));
75
58
  }
@@ -7,11 +7,12 @@ import { RefreshContentProvider } from '../content/RefreshContentProvider.js';
7
7
  import { AppErrorBoundary, onError } from '../core/AppErrorBoundary.js';
8
8
  import { ViteLifecycles } from '../core/ViteLifecycles.js';
9
9
  import { ScreenshotProvider } from '../iframe/ScreenshotProvider.js';
10
- import { getPreviewLocationChange, getPreviewParentOrigin } from '../react-router/preview-location.js';
10
+ import { getSafePreviewNavigationPath, isPreviewRuntimeEnabled } from '../preview/bridge.js';
11
+ import { getPreviewLocationChange, getPreviewParentOrigin } from '../preview/location.js';
11
12
  import { OptionalTracking } from '../t/OptionalTracking.js';
12
13
  import { AppResetScrollOnNavigate } from './AppResetScrollOnNavigate.js';
13
14
  const titleCase = (seg) => seg
14
- .replace(/[_\-]+/g, ' ')
15
+ .replace(/[_-]+/g, ' ')
15
16
  .replace(/([a-z])([A-Z])/g, '$1 $2')
16
17
  .split(/\s+/)
17
18
  .filter(Boolean)
@@ -54,23 +55,31 @@ export function AppRoutesProvider({ appRoutes, children }) {
54
55
  const navigate = useNavigate();
55
56
  const location = useLocation();
56
57
  const displayRoutes = useMemo(() => getDisplayRoutes(appRoutes ?? []), [appRoutes]);
58
+ // @ts-ignore - import.meta.env is provided by Vite.
59
+ const previewEnabled = isPreviewRuntimeEnabled(import.meta.env.VITE_PREVIEW_SCRIPT_URL);
57
60
  const sendRoutes = useCallback(() => {
58
- if (!appRoutes?.length) {
61
+ if (!previewEnabled || !appRoutes?.length) {
59
62
  return;
60
63
  }
61
64
  window.parent.postMessage({ type: 'nav/routes', payload: { routes: displayRoutes } }, getPreviewParentOrigin() ?? '*');
62
- }, [appRoutes, displayRoutes]);
65
+ }, [appRoutes, displayRoutes, previewEnabled]);
63
66
  const sendLocationChange = useCallback(() => {
67
+ if (!previewEnabled) {
68
+ return;
69
+ }
64
70
  const locationChange = getPreviewLocationChange(location);
65
71
  const parentOrigin = getPreviewParentOrigin();
66
72
  window.parent.postMessage({
67
73
  type: 'nav/locationChange',
68
74
  payload: parentOrigin ? locationChange : { path: locationChange.path },
69
75
  }, parentOrigin ?? '*');
70
- }, [location.hash, location.pathname, location.search]);
76
+ }, [location.hash, location.pathname, location.search, previewEnabled]);
71
77
  useEffect(sendRoutes, [sendRoutes]);
72
78
  useEffect(sendLocationChange, [sendLocationChange]);
73
79
  useEffect(() => {
80
+ if (!previewEnabled) {
81
+ return;
82
+ }
74
83
  const handleMessage = (event) => {
75
84
  const parentOrigin = getPreviewParentOrigin();
76
85
  if (event.source !== window.parent || !parentOrigin || event.origin !== parentOrigin) {
@@ -80,8 +89,11 @@ export function AppRoutesProvider({ appRoutes, children }) {
80
89
  const payload = event.data?.payload;
81
90
  switch (type) {
82
91
  case 'nav/navigate':
83
- if (payload?.to) {
84
- navigate(payload.to);
92
+ {
93
+ const target = getSafePreviewNavigationPath(payload?.to, window.location.origin);
94
+ if (target) {
95
+ navigate(target);
96
+ }
85
97
  }
86
98
  return;
87
99
  case 'nav/read-routes':
@@ -94,6 +106,6 @@ export function AppRoutesProvider({ appRoutes, children }) {
94
106
  return () => {
95
107
  window.removeEventListener('message', handleMessage);
96
108
  };
97
- }, [navigate, sendRoutes, sendLocationChange]);
109
+ }, [navigate, previewEnabled, sendRoutes, sendLocationChange]);
98
110
  return (_jsx(ErrorBoundary, { FallbackComponent: AppErrorBoundary, onError: onError, children: _jsx(DataSourcesProvider, { children: _jsx(ScreenshotProvider, { children: _jsx(ViteLifecycles, { children: _jsxs(RefreshContentProvider, { children: [_jsx(OptionalTracking, { routes: displayRoutes }), _jsx(AppResetScrollOnNavigate, {}), appRoutes?.length ? routing : children] }) }) }) }) }));
99
111
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * How a local checkout names and addresses a Fimo env — runtime-safe copy.
3
+ *
4
+ * TRAP: this looks like a duplicate of `@fimo/api-contracts` and it is, on
5
+ * purpose. `dist/runtime/` and `dist/build/` ship UNBUNDLED inside the `fimo`
6
+ * npm package and can only import published dependencies; `@fimo/api-contracts`
7
+ * is a private workspace package, so a runtime import of it breaks every user
8
+ * project (`ERR_MODULE_NOT_FOUND` from `vite.config.ts`). The CLI binary is
9
+ * bundled and may import contracts freely — the runtime may not.
10
+ *
11
+ * The two copies must agree byte-for-byte or a checkout computes a different
12
+ * tenant host than the API hands out. `env-identity.test.ts` pins them
13
+ * together against a corpus; change one, and that test tells you to change the
14
+ * other.
15
+ */
16
+ export declare const DEFAULT_ENV = "main";
17
+ export declare const PROD_ENV_PREFIX = "release/";
18
+ /** Mirror of `isValidEnv` in `@fimo/api-contracts`. */
19
+ export declare function isValidEnv(env: string): boolean;
20
+ /** Mirror of `envResourceKey` in `@fimo/api-contracts`. */
21
+ export declare function envResourceKey(env: string): string;
22
+ /** Mirror of `buildTenantApiUrl` in `@fimo/api-contracts`. */
23
+ export declare function buildTenantApiUrl(projectId: string, env: string, tenantApiBaseUrl: string): string;
24
+ /**
25
+ * Content-API aliases a local checkout exposes — one per framework family
26
+ * that has a Fimo startup hook (Vite/React Router, Next, Astro, SvelteKit).
27
+ * Add a key when its framework's hook lands, not before: an alias no runtime
28
+ * reads is one more line to reason about when a site talks to the wrong env.
29
+ */
30
+ export declare const LOCAL_CONTENT_API_ENV_KEYS: readonly ['FIMO_CONTENT_API_URL', 'VITE_API_URL', 'NEXT_PUBLIC_FIMO_API_URL', 'PUBLIC_FIMO_API_URL'];
31
+ /** Env selectors a runtime turns into the `X-Fimo-Env` request header. */
32
+ export declare const LOCAL_ENV_SELECTOR_KEYS: readonly ['FIMO_ENV', 'NEXT_PUBLIC_FIMO_ENV'];
33
+ /**
34
+ * Both halves move together. The tenant URL selects the env for content reads
35
+ * (each env has its own host); the selectors select it for every service that
36
+ * reads `X-Fimo-Env`. Setting one without the other points a checkout at two
37
+ * different envs.
38
+ */
39
+ export declare function buildLocalRuntimeEnv(options: {
40
+ readonly tenantApiUrl: string;
41
+ readonly env: string;
42
+ }): Record<string, string>;
43
+ //# sourceMappingURL=env-identity.d.ts.map
@@ -0,0 +1,88 @@
1
+ /**
2
+ * How a local checkout names and addresses a Fimo env — runtime-safe copy.
3
+ *
4
+ * TRAP: this looks like a duplicate of `@fimo/api-contracts` and it is, on
5
+ * purpose. `dist/runtime/` and `dist/build/` ship UNBUNDLED inside the `fimo`
6
+ * npm package and can only import published dependencies; `@fimo/api-contracts`
7
+ * is a private workspace package, so a runtime import of it breaks every user
8
+ * project (`ERR_MODULE_NOT_FOUND` from `vite.config.ts`). The CLI binary is
9
+ * bundled and may import contracts freely — the runtime may not.
10
+ *
11
+ * The two copies must agree byte-for-byte or a checkout computes a different
12
+ * tenant host than the API hands out. `env-identity.test.ts` pins them
13
+ * together against a corpus; change one, and that test tells you to change the
14
+ * other.
15
+ */
16
+ export const DEFAULT_ENV = 'main';
17
+ export const PROD_ENV_PREFIX = 'release/';
18
+ const ENV_ID_REGEX = /^[a-zA-Z0-9._/-]+$/;
19
+ const MAX_ENV_LENGTH = 255;
20
+ /** Mirror of `isValidEnv` in `@fimo/api-contracts`. */
21
+ export function isValidEnv(env) {
22
+ if (typeof env !== 'string') {
23
+ return false;
24
+ }
25
+ if (env.length === 0 || env.length > MAX_ENV_LENGTH) {
26
+ return false;
27
+ }
28
+ if (env.startsWith('-') || env.startsWith('.')) {
29
+ return false;
30
+ }
31
+ if (env.includes('//') || env.includes('--')) {
32
+ return false;
33
+ }
34
+ if (env.split('/').some((segment) => segment === '..')) {
35
+ return false;
36
+ }
37
+ return ENV_ID_REGEX.test(env);
38
+ }
39
+ function fnv1a8(s) {
40
+ let h = 0x811c9dc5;
41
+ for (let i = 0; i < s.length; i++) {
42
+ h ^= s.charCodeAt(i);
43
+ h = Math.imul(h, 0x01000193);
44
+ }
45
+ return (h >>> 0).toString(16).padStart(8, '0');
46
+ }
47
+ /** Mirror of `envResourceKey` in `@fimo/api-contracts`. */
48
+ export function envResourceKey(env) {
49
+ const base = env
50
+ .toLowerCase()
51
+ .replace(/[^a-z0-9]+/g, '-')
52
+ .replace(/^-+|-+$/g, '')
53
+ .slice(0, 15);
54
+ const hash = fnv1a8(env);
55
+ return base ? `${base}-${hash}` : hash;
56
+ }
57
+ /** Mirror of `buildTenantApiUrl` in `@fimo/api-contracts`. */
58
+ export function buildTenantApiUrl(projectId, env, tenantApiBaseUrl) {
59
+ const url = new URL(tenantApiBaseUrl);
60
+ url.hostname = `${projectId}-${envResourceKey(env)}.${url.hostname}`;
61
+ return url.toString().replace(/\/$/, '');
62
+ }
63
+ /**
64
+ * Content-API aliases a local checkout exposes — one per framework family
65
+ * that has a Fimo startup hook (Vite/React Router, Next, Astro, SvelteKit).
66
+ * Add a key when its framework's hook lands, not before: an alias no runtime
67
+ * reads is one more line to reason about when a site talks to the wrong env.
68
+ */
69
+ export const LOCAL_CONTENT_API_ENV_KEYS = [
70
+ 'FIMO_CONTENT_API_URL',
71
+ 'VITE_API_URL',
72
+ 'NEXT_PUBLIC_FIMO_API_URL',
73
+ 'PUBLIC_FIMO_API_URL',
74
+ ];
75
+ /** Env selectors a runtime turns into the `X-Fimo-Env` request header. */
76
+ export const LOCAL_ENV_SELECTOR_KEYS = ['FIMO_ENV', 'NEXT_PUBLIC_FIMO_ENV'];
77
+ /**
78
+ * Both halves move together. The tenant URL selects the env for content reads
79
+ * (each env has its own host); the selectors select it for every service that
80
+ * reads `X-Fimo-Env`. Setting one without the other points a checkout at two
81
+ * different envs.
82
+ */
83
+ export function buildLocalRuntimeEnv(options) {
84
+ return {
85
+ ...Object.fromEntries(LOCAL_CONTENT_API_ENV_KEYS.map((key) => [key, options.tenantApiUrl])),
86
+ ...Object.fromEntries(LOCAL_ENV_SELECTOR_KEYS.map((key) => [key, options.env])),
87
+ };
88
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Which Fimo env a local checkout is on, and how to reach it — derived, not
3
+ * stored.
4
+ *
5
+ * The env is the git branch. The project id and tenant API base URL sit in
6
+ * `.fimo/project.json`, written once at link and never on a switch. The tenant
7
+ * URL for the current env is a pure function of the three, so nothing has to
8
+ * be rewritten when the branch changes: `git checkout` alone is a complete env
9
+ * switch, offline.
10
+ *
11
+ * This module only DERIVES. Precedence against explicit values is the caller's
12
+ * job and is the same everywhere: a variable the user (or the sandbox) already
13
+ * set is left alone; derivation fills the rest, one variable at a time.
14
+ *
15
+ * Shared by the CLI, `fimo/vite`, and `fimo/next/config` so the three can
16
+ * never disagree about which env the app is calling.
17
+ */
18
+ export type LocalRuntimeEnvSource = 'branch' | 'fallback';
19
+ export interface LocalRuntimeEnv {
20
+ readonly env: string;
21
+ readonly tenantApiUrl: string;
22
+ /** Where `env` came from — surfaced so a wrong guess is diagnosable. */
23
+ readonly source: LocalRuntimeEnvSource;
24
+ /** Every alias and selector a framework should see, ready to inject. */
25
+ readonly values: Readonly<Record<string, string>>;
26
+ readonly link: LocalProjectLink;
27
+ }
28
+ export interface LocalProjectLink {
29
+ readonly projectId: string;
30
+ readonly tenantApiBaseUrl: string;
31
+ }
32
+ /**
33
+ * Derive the local runtime env, or `null` when this directory is not a linked
34
+ * Fimo project (or the link predates `tenantApiBaseUrl` — nothing safe to
35
+ * derive, so nothing is).
36
+ */
37
+ export declare function resolveLocalRuntimeEnv(options: {
38
+ readonly cwd: string;
39
+ }): LocalRuntimeEnv | null;
40
+ /**
41
+ * The one case where "explicit wins" pins the app to the WRONG env: an
42
+ * explicit tenant URL that Fimo itself minted for this project (old-CLI
43
+ * `fimo clone` seeded the clone-time env's URL into `.env`) and that disagrees
44
+ * with the branch. A URL the user typed — `http://localhost:4000` — is never
45
+ * reported; that is the override the design exists to protect.
46
+ */
47
+ export declare function findStaleFimoOverride(explicit: Readonly<Record<string, string | undefined>>, derived: LocalRuntimeEnv): {
48
+ readonly key: string;
49
+ readonly explicit: string;
50
+ readonly derived: string;
51
+ } | null;
52
+ /** One message for every surface (dev-server console, `fimo checkout`) so the fix reads the same everywhere. */
53
+ export declare function staleOverrideMessage(stale: {
54
+ readonly explicit: string;
55
+ readonly derived: string;
56
+ }): string;
57
+ /** Whether `value` is a tenant URL Fimo minted for `link`'s project — for any env. */
58
+ export declare function isFimoMintedTenantUrl(value: string, link: LocalProjectLink): boolean;
59
+ /**
60
+ * `.env` then `.env.local` (later wins), the way every supported framework
61
+ * layers them. For CLI commands, which — unlike Vite and Next — do not load
62
+ * these files themselves but must see the same explicit values the app will.
63
+ */
64
+ export declare function readLocalEnvFiles(cwd: string): Record<string, string>;
65
+ //# sourceMappingURL=local-runtime-env.d.ts.map