fimo 2.1.0 → 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.
Files changed (77) hide show
  1. package/README.md +3 -2
  2. package/assets/agent-templates/_default/config.yaml +1 -0
  3. package/assets/agent-templates/broken-link-sweeper/config.yaml +1 -0
  4. package/assets/agent-templates/content-translator/config.yaml +1 -0
  5. package/assets/agent-templates/content-translator/scripts/translate-entries.ts +1 -1
  6. package/assets/agent-templates/post-deploy-check/config.yaml +1 -0
  7. package/assets/agent-templates/post-deploy-check/scripts/check-logs.ts +1 -1
  8. package/assets/agent-templates/seo-audit/config.yaml +1 -0
  9. package/assets/agent-templates/seo-audit/scripts/list-routes.ts +3 -3
  10. package/assets/skills/fimo-cli/SKILL.md +5 -5
  11. package/assets/skills/fimo-cli/references/agents.md +2 -1
  12. package/assets/skills/fimo-cli/references/assets.md +4 -1
  13. package/assets/skills/fimo-cli/references/branches.md +1 -1
  14. package/assets/skills/fimo-cli/references/content.md +3 -3
  15. package/assets/skills/fimo-cli/references/deploy.md +2 -2
  16. package/assets/skills/fimo-cli/references/forms.md +3 -1
  17. package/assets/skills/fimo-cli/references/history.md +1 -0
  18. package/assets/skills/fimo-cli/references/init.md +30 -39
  19. package/assets/skills/fimo-cli/references/preview.md +5 -3
  20. package/assets/skills/fimo-cli/references/schedules.md +4 -2
  21. package/assets/skills/fimo-cli/references/webhooks.md +2 -0
  22. package/assets/skills/fimo-code/references/frameworks/next.md +4 -1
  23. package/assets/skills/fimo-code/references/frameworks/react-router.md +4 -3
  24. package/assets/skills/fimo-migration/SKILL.md +3 -3
  25. package/assets/skills/fimo-studio/references/preview-and-responsive.md +4 -1
  26. package/assets/skills/fimo-studio/references/visual-editing.md +9 -1
  27. package/dist/build/astro/config.js +2 -1
  28. package/dist/build/vite/plugins/preview-health.d.ts +4 -0
  29. package/dist/build/vite/plugins/preview-health.js +40 -0
  30. package/dist/cli/bundle.json +1 -1
  31. package/dist/cli/index.js +136 -137
  32. package/dist/runtime/astro/server-runtime.js +7 -3
  33. package/dist/runtime/content/client/collection-core.js +3 -2
  34. package/dist/runtime/content/client/core-types.d.ts +4 -3
  35. package/dist/runtime/content/client/internal.d.ts +2 -0
  36. package/dist/runtime/content/client/internal.js +54 -25
  37. package/dist/runtime/content/client/singleton-core.js +2 -1
  38. package/dist/runtime/content/index.d.ts +1 -1
  39. package/dist/runtime/content/index.js +1 -1
  40. package/dist/runtime/paths/get-fimo-paths.js +7 -3
  41. package/dist/runtime/primitives/lib/index.d.ts +1 -1
  42. package/dist/runtime/primitives/lib/index.js +1 -1
  43. package/dist/runtime/primitives/lib/primitives.d.ts +24 -5
  44. package/dist/runtime/primitives/lib/primitives.js +69 -16
  45. package/dist/runtime/primitives/lib/template.js +2 -2
  46. package/dist/runtime/shared/branch-identity.d.ts +56 -0
  47. package/dist/runtime/shared/{env-identity.js → branch-identity.js} +40 -27
  48. package/dist/runtime/shared/local-runtime-env.d.ts +2 -2
  49. package/dist/runtime/shared/local-runtime-env.js +12 -10
  50. package/dist/runtime/templates.d.ts +8 -2
  51. package/dist/runtime/templates.js +34 -8
  52. package/package.json +13 -9
  53. package/release.json +2 -2
  54. package/templates/astro/package.json +2 -2
  55. package/templates/nextjs/AGENTS.md +1 -1
  56. package/templates/nextjs/package.json +4 -4
  57. package/templates/react-router/.fimo/config.json +1 -2
  58. package/templates/react-router/AGENTS.md +1 -1
  59. package/templates/react-router/package.json +4 -4
  60. package/templates/sveltekit/package.json +2 -2
  61. package/templates/vite/package.json +4 -4
  62. package/assets/starters/fimo-site/manifest.json +0 -64
  63. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/GOAL.md +0 -8
  64. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +0 -1
  65. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/config.yaml +0 -9
  66. package/assets/starters/fimo-site/template/.fimo/agents/site-review/GOAL.md +0 -14
  67. package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +0 -3
  68. package/assets/starters/fimo-site/template/.fimo/agents/site-review/config.yaml +0 -9
  69. package/assets/starters/fimo-site/template/src/forms/contact.json +0 -14
  70. package/assets/starters/fimo-site/template/src/forms/contact.ts +0 -46
  71. package/assets/starters/fimo-site/template/src/pages/Contact.tsx +0 -193
  72. package/assets/starters/fimo-site/template/src/pages/Index.tsx +0 -27
  73. package/assets/starters/fimo-site/template/src/pages/SiteContent.tsx +0 -191
  74. package/assets/starters/fimo-site/template/src/schemas/HomePage.json +0 -28
  75. package/assets/starters/fimo-site/template/src/schemas/HomePage.ts +0 -29
  76. package/assets/starters/fimo-site/template/src/schemas/HomePage.types.ts +0 -64
  77. package/dist/runtime/shared/env-identity.d.ts +0 -43
@@ -8,9 +8,13 @@ export const astroServerContentRuntime = {
8
8
  },
9
9
  headers(extra) {
10
10
  const headers = { ...extra };
11
- const env = import.meta.env.FIMO_ENV?.trim();
12
- if (env) {
13
- headers['X-Fimo-Env'] = env;
11
+ // `FIMO_ENV` is the variable name a customer's deployed build reads and
12
+ // keeps its spelling; only the header this build emits is canonical
13
+ // (branch-cutover ledger, F5). Builds already in production keep sending
14
+ // `X-Fimo-Env`, which the API still accepts.
15
+ const branch = import.meta.env.FIMO_ENV?.trim();
16
+ if (branch) {
17
+ headers['X-Fimo-Branch'] = branch;
14
18
  }
15
19
  return headers;
16
20
  },
@@ -11,11 +11,12 @@ export function createCollectionContentCore(config, runtime) {
11
11
  const meta = { uid: info.uid, pascal: info.pascal, localized: info.localized };
12
12
  const self = { wrapWithSource, meta };
13
13
  const resolve = (uid) => uid === info.uid ? self : resolveClientHandle(references[uid]);
14
- function wrapWithSource(entry) {
14
+ function wrapWithSource(entry, context) {
15
15
  return wrapEntry(info, entry, {
16
16
  alwaysIncludeLocale: true,
17
17
  defaultLocale: requestDefaultLocale ?? 'en',
18
18
  resolve,
19
+ context,
19
20
  });
20
21
  }
21
22
  function readPath(url, options) {
@@ -88,7 +89,7 @@ export function createCollectionContentCore(config, runtime) {
88
89
  throw new Error(await response.text());
89
90
  }
90
91
  const body = parseCollectionResponse(await response.json());
91
- const items = body.data.map(wrapWithSource);
92
+ const items = body.data.map((entry) => wrapWithSource(entry));
92
93
  const pageInfo = {
93
94
  limit: body.meta.limit,
94
95
  offset: body.meta.offset,
@@ -1,3 +1,4 @@
1
+ import type { FimoSourceContext } from '../../primitives/lib/primitives.js';
1
2
  import type { Fields, Populate, PopulatedResult, ProjectedWithPopulate, Query } from '../../primitives/lib/query.js';
2
3
  export type ContentResult<T extends {
3
4
  id: string;
@@ -43,7 +44,7 @@ export interface ContentModuleMeta {
43
44
  readonly localized: boolean;
44
45
  }
45
46
  export interface ContentClientHandle {
46
- wrapWithSource: (entry: Record<string, any>) => unknown;
47
+ wrapWithSource: (entry: Record<string, any>, context?: FimoSourceContext) => unknown;
47
48
  meta: ContentModuleMeta;
48
49
  }
49
50
  export type ContentModuleRef = ContentClientHandle | {
@@ -73,7 +74,7 @@ export interface CollectionContentCore<T extends {
73
74
  id: string;
74
75
  }, TInput extends object> {
75
76
  readonly meta: ContentModuleMeta;
76
- wrapWithSource(entry: Record<string, any>): T;
77
+ wrapWithSource(entry: Record<string, any>, context?: FimoSourceContext): T;
77
78
  getById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate>>;
78
79
  getBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate> | null>;
79
80
  getByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate> | null>;
@@ -89,7 +90,7 @@ export interface SingletonContentCore<T extends {
89
90
  id: string;
90
91
  }, TInput extends object> {
91
92
  readonly meta: ContentModuleMeta;
92
- wrapWithSource(entry: Record<string, any>): T;
93
+ wrapWithSource(entry: Record<string, any>, context?: FimoSourceContext): T;
93
94
  get<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): Promise<ContentResult<T, undefined, TPopulate>>;
94
95
  update(payload: Partial<TInput>): Promise<T>;
95
96
  }
@@ -1,3 +1,4 @@
1
+ import { type FimoSourceContext } from '../../primitives/lib/primitives.js';
1
2
  import type { ContentClientHandle, ContentModuleRef, ContentSchema, ContentSchemaField } from './core-types.js';
2
3
  export interface InterpretedSchema {
3
4
  uid: string;
@@ -37,6 +38,7 @@ export declare function wrapEntry(info: InterpretedSchema, entry: Record<string,
37
38
  alwaysIncludeLocale: boolean;
38
39
  defaultLocale: string;
39
40
  resolve: ReferenceResolver;
41
+ context?: FimoSourceContext;
40
42
  }): Record<string, unknown>;
41
43
  export {};
42
44
  //# sourceMappingURL=internal.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { pascalCase } from 'change-case';
2
- import { FIMO_SOURCE, FimoBoolean, FimoMedia, FimoRichText, FimoString } from '../../primitives/lib/primitives.js';
2
+ import { FIMO_SOURCE, FIMO_SOURCE_CONTEXT, FimoBoolean, FimoMedia, FimoRichText, FimoString, } from '../../primitives/lib/primitives.js';
3
3
  export function interpretSchema(schema) {
4
4
  if (!schema || typeof schema.uid !== 'string' || schema.uid.length === 0) {
5
5
  throw new Error('Content schema is missing a "uid".');
@@ -94,6 +94,19 @@ function isPopulatedEntry(value) {
94
94
  typeof fimo === 'object' &&
95
95
  typeof fimo.contentType === 'string');
96
96
  }
97
+ function appendSourceContext(context, item) {
98
+ return [...(context ?? []), item];
99
+ }
100
+ function tagWithSourceContext(value, context) {
101
+ if (context && context.length > 0) {
102
+ Object.defineProperty(value, FIMO_SOURCE_CONTEXT, {
103
+ value: Object.freeze([...context]),
104
+ enumerable: false,
105
+ configurable: true,
106
+ });
107
+ }
108
+ return value;
109
+ }
97
110
  function asClientHandle(candidate) {
98
111
  if (candidate == null || typeof candidate !== 'object') {
99
112
  return undefined;
@@ -133,7 +146,7 @@ function unresolvableReference(uid) {
133
146
  * union narrows in a switch. Raw (unpopulated) values and types outside the
134
147
  * field's declared targets pass through untouched.
135
148
  */
136
- function wrapPolymorphic(value, targetUids, resolve) {
149
+ function wrapPolymorphic(value, targetUids, source, resolve, context) {
137
150
  if (!isPopulatedEntry(value)) {
138
151
  return value;
139
152
  }
@@ -145,11 +158,11 @@ function wrapPolymorphic(value, targetUids, resolve) {
145
158
  if (!target) {
146
159
  throw unresolvableReference(contentType);
147
160
  }
148
- const wrapped = target.wrapWithSource(value);
161
+ const wrapped = target.wrapWithSource(value, appendSourceContext(context, { kind: 'relation', source }));
149
162
  wrapped.$type = contentType;
150
- return wrapped;
163
+ return tagWithSource(wrapped, source, context);
151
164
  }
152
- function wrapReference(value, uid, resolve) {
165
+ function wrapReference(value, uid, source, resolve, context) {
153
166
  if (!isPopulatedEntry(value)) {
154
167
  return value;
155
168
  }
@@ -157,7 +170,7 @@ function wrapReference(value, uid, resolve) {
157
170
  if (!target) {
158
171
  throw unresolvableReference(uid);
159
172
  }
160
- return target.wrapWithSource(value);
173
+ return tagWithSource(target.wrapWithSource(value, appendSourceContext(context, { kind: 'relation', source })), source, context);
161
174
  }
162
175
  /**
163
176
  * Blocks values have no wrapper class of their own — the array structure stays
@@ -174,7 +187,7 @@ function blockItemFields(field, item) {
174
187
  }
175
188
  return schema.fields;
176
189
  }
177
- function wrapBlockItem(field, item, source, resolve) {
190
+ function wrapBlockItem(field, item, listSource, itemSource, resolve, context) {
178
191
  if (item == null || typeof item !== 'object' || Array.isArray(item)) {
179
192
  return item;
180
193
  }
@@ -183,61 +196,77 @@ function wrapBlockItem(field, item, source, resolve) {
183
196
  if (!fields) {
184
197
  return item;
185
198
  }
199
+ const itemContext = typeof record._key === 'string' && record._key.length > 0
200
+ ? appendSourceContext(context, {
201
+ kind: 'component',
202
+ source: listSource,
203
+ _key: record._key,
204
+ ...(typeof record._type === 'string' && record._type.length > 0 ? { _type: record._type } : {}),
205
+ })
206
+ : context;
186
207
  const wrapped = { ...record };
187
208
  for (const [key, childField] of Object.entries(fields)) {
188
- wrapped[key] = wrapField(childField, record[key], `${source}.${key}`, resolve);
209
+ wrapped[key] = wrapField(childField, record[key], `${itemSource}.${key}`, resolve, itemContext);
189
210
  }
190
211
  return wrapped;
191
212
  }
192
213
  /**
193
214
  * A JSON field's declared shape is a plain object/array — tagging it in place
194
215
  * (rather than boxing it, like the scalar wrappers below) keeps that shape
195
- * intact so consumers that read it as ordinary JSON see no difference.
216
+ * intact so consumers that read it as ordinary JSON see no difference. A
217
+ * populated reference is tagged the same way, with the source of the field
218
+ * that HOLDS it (`Post.id:p1:en.author`), so the toolbar can select and
219
+ * repoint the reference itself; the target's own fields keep the sources the
220
+ * target client gave them.
196
221
  */
197
- function tagWithSource(value, source) {
222
+ function tagWithSource(value, source, context) {
198
223
  Object.defineProperty(value, FIMO_SOURCE, { value: source, enumerable: false, configurable: true });
199
- return value;
224
+ return tagWithSourceContext(value, context);
200
225
  }
201
- function wrapField(field, value, source, resolve) {
226
+ function wrapField(field, value, source, resolve, context) {
202
227
  switch (field.type) {
203
228
  case 'string':
204
229
  case 'text':
205
230
  case 'date':
206
231
  case 'datetime':
207
- return value != null ? new FimoString(String(value), source) : value;
232
+ return value != null ? new FimoString(String(value), source, context) : value;
208
233
  case 'richtext':
209
- return value != null ? new FimoRichText(value, source) : value;
234
+ return value != null ? new FimoRichText(value, source, context) : value;
210
235
  case 'boolean':
211
- return value != null ? new FimoBoolean(value, source) : value;
236
+ return value != null ? new FimoBoolean(value, source, context) : value;
212
237
  case 'json':
213
238
  return value != null && typeof value === 'object'
214
- ? tagWithSource(Array.isArray(value) ? [...value] : { ...value }, source)
239
+ ? tagWithSource(Array.isArray(value) ? [...value] : { ...value }, source, context)
215
240
  : value;
216
241
  case 'media':
217
242
  if (field.multiple) {
218
- return (value?.map((m, i) => new FimoMedia(m, `${source}[${i}]`)) ?? value);
243
+ return (value?.map((m, i) => new FimoMedia(m, `${source}[${i}]`, context)) ?? value);
219
244
  }
220
- return value != null ? new FimoMedia(value, source) : value;
245
+ return value != null ? new FimoMedia(value, source, context) : value;
221
246
  case 'reference': {
222
247
  const polymorphic = polymorphicTargetUids(field);
223
248
  if (polymorphic) {
224
249
  if (field.many) {
225
- return Array.isArray(value) ? value.map((item) => wrapPolymorphic(item, polymorphic, resolve)) : value;
250
+ return Array.isArray(value)
251
+ ? value.map((item, i) => wrapPolymorphic(item, polymorphic, `${source}[${i}]`, resolve, context))
252
+ : value;
226
253
  }
227
- return wrapPolymorphic(value, polymorphic, resolve);
254
+ return wrapPolymorphic(value, polymorphic, source, resolve, context);
228
255
  }
229
256
  const uid = field.target;
230
257
  if (!uid) {
231
258
  return value;
232
259
  }
233
260
  if (field.many) {
234
- return Array.isArray(value) ? value.map((item) => wrapReference(item, uid, resolve)) : value;
261
+ return Array.isArray(value)
262
+ ? value.map((item, i) => wrapReference(item, uid, `${source}[${i}]`, resolve, context))
263
+ : value;
235
264
  }
236
- return wrapReference(value, uid, resolve);
265
+ return wrapReference(value, uid, source, resolve, context);
237
266
  }
238
267
  case 'blocks':
239
268
  return Array.isArray(value)
240
- ? value.map((item, i) => wrapBlockItem(field, item, `${source}[${i}]`, resolve))
269
+ ? value.map((item, i) => wrapBlockItem(field, item, source, `${source}[${i}]`, resolve, context))
241
270
  : value;
242
271
  // 'number' deliberately falls through here — this is a decision, not a
243
272
  // gap. React renders a boxed String directly (`<span>{new FimoString(...)}
@@ -267,7 +296,7 @@ export function wrapEntry(info, entry, options) {
267
296
  id,
268
297
  entryId,
269
298
  documentId,
270
- slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
299
+ slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`, options.context) : entry.slug,
271
300
  ...(options.alwaysIncludeLocale || entry.locale ? { locale } : {}),
272
301
  createdAt: entry.createdAt,
273
302
  updatedAt: entry.updatedAt,
@@ -276,7 +305,7 @@ export function wrapEntry(info, entry, options) {
276
305
  },
277
306
  };
278
307
  for (const [key, field] of info.fields) {
279
- wrapped[key] = wrapField(field, entry[key], `${sourcePrefix}.${key}`, options.resolve);
308
+ wrapped[key] = wrapField(field, entry[key], `${sourcePrefix}.${key}`, options.resolve, options.context);
280
309
  }
281
310
  return wrapped;
282
311
  }
@@ -28,11 +28,12 @@ export function createSingletonContentCore(config, runtime) {
28
28
  localizedReferenceFields = fields;
29
29
  return fields;
30
30
  }
31
- function wrapWithSource(entry) {
31
+ function wrapWithSource(entry, context) {
32
32
  return wrapEntry(info, entry, {
33
33
  alwaysIncludeLocale: info.localized,
34
34
  defaultLocale: requestDefaultLocale ?? 'en',
35
35
  resolve,
36
+ context,
36
37
  });
37
38
  }
38
39
  function localize(options, fallbackLocale = requestDefaultLocale) {
@@ -16,6 +16,6 @@ export type { SingletonContentCore } from './client/singleton-core.js';
16
16
  export { serializeContent, type SerializedContent, type SerializedMedia } from './serialization.js';
17
17
  export { parseCollectionResponse, type CollectionResponse } from './parseCollectionResponse.js';
18
18
  export type { CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentSchema, ContentSchemaField, } from './client/core-types.js';
19
- export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, type FimoPart, type FimoPolymorphicReference, type FimoReference, type FimoRichTextContent, type FimoSource, } from '../primitives/lib/primitives.js';
19
+ export { FIMO_PARTS, FIMO_SOURCE, FIMO_SOURCE_CONTEXT, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, getFimoSourceContext, hasFimoSource, isFimoDate, isFimoString, type FimoPart, type FimoPolymorphicReference, type FimoReference, type FimoRichTextContent, type FimoSourceContext, type FimoSourceContextItem, type FimoSource, } from '../primitives/lib/primitives.js';
20
20
  export type { BoolOps, DateOps, Field, Fields, FilterFor, NullOnlyOps, NumberOps, Populate, Populated, PopulatedResult, Projected, ProjectedWithPopulate, Query, ReferenceField, ReferenceMap, ScalarOps, Sort, SortField, StringOps, Where, WithReferences, } from '../primitives/lib/query.js';
21
21
  //# sourceMappingURL=index.d.ts.map
@@ -13,4 +13,4 @@ export { readCollectionLocale, splitWritePayload, withMutationLocale } from './c
13
13
  export { requireContentApiUrl } from './client/runtime.js';
14
14
  export { serializeContent } from './serialization.js';
15
15
  export { parseCollectionResponse } from './parseCollectionResponse.js';
16
- export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../primitives/lib/primitives.js';
16
+ export { FIMO_PARTS, FIMO_SOURCE, FIMO_SOURCE_CONTEXT, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, getFimoSourceContext, hasFimoSource, isFimoDate, isFimoString, } from '../primitives/lib/primitives.js';
@@ -46,9 +46,13 @@ async function fetchEntries(baseUrl, schema, rootDir, locale) {
46
46
  url.searchParams.set('limit', '1');
47
47
  }
48
48
  const headers = {};
49
- const env = readEnvValue(rootDir, 'FIMO_ENV') ?? resolveLocalRuntimeEnv({ cwd: rootDir })?.env;
50
- if (env) {
51
- headers['X-Fimo-Env'] = env;
49
+ // `FIMO_ENV` is a customer-facing variable name and keeps its spelling; the
50
+ // header this build emits is canonical (branch-cutover ledger, F5). A
51
+ // deployed site always talks to the current API, and builds produced by an
52
+ // older `fimo` keep sending `X-Fimo-Env`, which the API still accepts.
53
+ const branch = readEnvValue(rootDir, 'FIMO_ENV') ?? resolveLocalRuntimeEnv({ cwd: rootDir })?.env;
54
+ if (branch) {
55
+ headers['X-Fimo-Branch'] = branch;
52
56
  }
53
57
  for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt += 1) {
54
58
  const res = await fetch(url, { headers });
@@ -1,4 +1,4 @@
1
- export { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, type FimoPolymorphicReference, type FimoReference, type FimoSource, type FimoPart, type FimoRichTextContent, FIMO_SOURCE, FIMO_PARTS, isFimoString, isFimoDate, hasFimoSource, getFimoSource, getFimoParts, } from './primitives.js';
1
+ export { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, type FimoPolymorphicReference, type FimoReference, type FimoSource, type FimoPart, type FimoRichTextContent, type FimoSourceContext, type FimoSourceContextItem, FIMO_SOURCE, FIMO_SOURCE_CONTEXT, FIMO_PARTS, isFimoString, isFimoDate, hasFimoSource, getFimoSource, getFimoSourceContext, getFimoParts, } from './primitives.js';
2
2
  export { fimo } from './template.js';
3
3
  export { renderRichTextHtml, renderRichTextHtml as renderRichText } from './render-rich-text.js';
4
4
  export { parseImageHostSuffixes, resolveImagePresentation, type ImageFit, type ImageObjectFit, type ImagePresentation, type ImagePresentationOptions, } from './image-optimization.js';
@@ -1,5 +1,5 @@
1
1
  // Fimo - Source tracking for inline editing
2
- export { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, FIMO_SOURCE, FIMO_PARTS, isFimoString, isFimoDate, hasFimoSource, getFimoSource, getFimoParts, } from './primitives.js';
2
+ export { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, FIMO_SOURCE, FIMO_SOURCE_CONTEXT, FIMO_PARTS, isFimoString, isFimoDate, hasFimoSource, getFimoSource, getFimoSourceContext, getFimoParts, } from './primitives.js';
3
3
  export { fimo } from './template.js';
4
4
  export { renderRichTextHtml, renderRichTextHtml as renderRichText } from './render-rich-text.js';
5
5
  export { parseImageHostSuffixes, resolveImagePresentation, } from './image-optimization.js';
@@ -16,13 +16,25 @@ export interface FimoPolymorphicReference {
16
16
  id: string;
17
17
  }
18
18
  export declare const FIMO_SOURCE: unique symbol;
19
+ export declare const FIMO_SOURCE_CONTEXT: unique symbol;
19
20
  export declare const FIMO_PARTS: unique symbol;
21
+ export type FimoSourceContextItem = {
22
+ kind: 'relation';
23
+ source: string;
24
+ } | {
25
+ kind: 'component';
26
+ source: string;
27
+ _key: string;
28
+ _type?: string;
29
+ };
30
+ export type FimoSourceContext = readonly FimoSourceContextItem[];
20
31
  /**
21
32
  * A piece of a composed FimoString — maps text to its source
22
33
  */
23
34
  export type FimoPart = {
24
35
  text: string;
25
36
  source?: string;
37
+ context?: FimoSourceContext;
26
38
  };
27
39
  /**
28
40
  * FimoString - A string with source tracking
@@ -32,8 +44,9 @@ export type FimoPart = {
32
44
  */
33
45
  export declare class FimoString extends String {
34
46
  readonly [FIMO_SOURCE]: string | null;
47
+ readonly [FIMO_SOURCE_CONTEXT]?: FimoSourceContext;
35
48
  readonly [FIMO_PARTS]?: FimoPart[];
36
- constructor(value: string, source: string | null);
49
+ constructor(value: string, source: string | null, context?: FimoSourceContext);
37
50
  /**
38
51
  * Compose multiple tracked values into one FimoString.
39
52
  * Parts carry individual sources; the main FIMO_SOURCE is null.
@@ -50,7 +63,8 @@ export declare class FimoString extends String {
50
63
  */
51
64
  export declare class FimoDate extends Date {
52
65
  readonly [FIMO_SOURCE]: string;
53
- constructor(value: Date | string | number, source: string);
66
+ readonly [FIMO_SOURCE_CONTEXT]?: FimoSourceContext;
67
+ constructor(value: Date | string | number, source: string, context?: FimoSourceContext);
54
68
  get __fimo(): FimoSource;
55
69
  }
56
70
  /**
@@ -64,6 +78,7 @@ export declare class FimoMedia {
64
78
  readonly width?: number;
65
79
  readonly height?: number;
66
80
  readonly [FIMO_SOURCE]: string;
81
+ readonly [FIMO_SOURCE_CONTEXT]?: FimoSourceContext;
67
82
  constructor(data: {
68
83
  url: string;
69
84
  alt: string;
@@ -71,7 +86,7 @@ export declare class FimoMedia {
71
86
  mime: string;
72
87
  width?: number;
73
88
  height?: number;
74
- }, source: string);
89
+ }, source: string, context?: FimoSourceContext);
75
90
  get __fimo(): FimoSource;
76
91
  }
77
92
  /**
@@ -80,7 +95,8 @@ export declare class FimoMedia {
80
95
  export declare class FimoBoolean {
81
96
  readonly value: boolean;
82
97
  readonly [FIMO_SOURCE]: string;
83
- constructor(value: boolean, source: string);
98
+ readonly [FIMO_SOURCE_CONTEXT]?: FimoSourceContext;
99
+ constructor(value: boolean, source: string, context?: FimoSourceContext);
84
100
  get __fimo(): FimoSource;
85
101
  valueOf(): boolean;
86
102
  }
@@ -105,7 +121,8 @@ export interface FimoRichTextContent {
105
121
  export declare class FimoRichText {
106
122
  readonly content: FimoRichTextContent;
107
123
  readonly [FIMO_SOURCE]: string;
108
- constructor(content: FimoRichTextContent, source: string);
124
+ readonly [FIMO_SOURCE_CONTEXT]?: FimoSourceContext;
125
+ constructor(content: FimoRichTextContent, source: string, context?: FimoSourceContext);
109
126
  get __fimo(): FimoSource;
110
127
  }
111
128
  /**
@@ -126,6 +143,8 @@ export declare function hasFimoSource(value: unknown): value is {
126
143
  * Get the source from a fimo value, or undefined if not tracked
127
144
  */
128
145
  export declare function getFimoSource(value: unknown): string | undefined;
146
+ /** Get the hidden relation/component ancestry from a wrapped value. */
147
+ export declare function getFimoSourceContext(value: unknown): FimoSourceContext | undefined;
129
148
  /**
130
149
  * Get the decomposed parts from a composed FimoString, or undefined
131
150
  */
@@ -4,10 +4,21 @@
4
4
  * All Fimo types carry hidden metadata that enables the preview editor
5
5
  * to know where content comes from (schema field, translation key, etc.)
6
6
  */
7
- var _a, _b, _c, _d, _e, _f;
7
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
8
8
  // Symbol for accessing fimo metadata (hidden from JSON.stringify)
9
9
  export const FIMO_SOURCE = Symbol.for('fimo.source');
10
+ export const FIMO_SOURCE_CONTEXT = Symbol.for('fimo.sourceContext');
10
11
  export const FIMO_PARTS = Symbol.for('fimo.parts');
12
+ function defineSourceContext(value, context) {
13
+ if (context && context.length > 0) {
14
+ Object.defineProperty(value, FIMO_SOURCE_CONTEXT, {
15
+ value: Object.freeze([...context]),
16
+ enumerable: false,
17
+ configurable: true,
18
+ });
19
+ }
20
+ return value;
21
+ }
11
22
  /**
12
23
  * FimoString - A string with source tracking
13
24
  *
@@ -15,7 +26,7 @@ export const FIMO_PARTS = Symbol.for('fimo.parts');
15
26
  * but carries hidden metadata for inline editing.
16
27
  */
17
28
  export class FimoString extends String {
18
- constructor(value, source) {
29
+ constructor(value, source, context) {
19
30
  super(value);
20
31
  Object.defineProperty(this, _a, {
21
32
  enumerable: true,
@@ -29,7 +40,14 @@ export class FimoString extends String {
29
40
  writable: true,
30
41
  value: void 0
31
42
  });
43
+ Object.defineProperty(this, _c, {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: void 0
48
+ });
32
49
  this[FIMO_SOURCE] = source;
50
+ defineSourceContext(this, context);
33
51
  }
34
52
  /**
35
53
  * Compose multiple tracked values into one FimoString.
@@ -41,7 +59,7 @@ export class FimoString extends String {
41
59
  const trackedParts = parts.filter((p) => p.source);
42
60
  // Single tracked part with no static text — just return a normal FimoString
43
61
  if (trackedParts.length === 1 && parts.length === 1) {
44
- return new FimoString(text, trackedParts[0].source);
62
+ return new FimoString(text, trackedParts[0].source, trackedParts[0].context);
45
63
  }
46
64
  const instance = new FimoString(text, null);
47
65
  instance[FIMO_PARTS] = parts;
@@ -54,31 +72,38 @@ export class FimoString extends String {
54
72
  return { source: this[FIMO_SOURCE] ?? '' };
55
73
  }
56
74
  }
57
- _a = FIMO_SOURCE, _b = FIMO_PARTS;
75
+ _a = FIMO_SOURCE, _b = FIMO_SOURCE_CONTEXT, _c = FIMO_PARTS;
58
76
  /**
59
77
  * FimoDate - A date with source tracking
60
78
  */
61
79
  export class FimoDate extends Date {
62
- constructor(value, source) {
80
+ constructor(value, source, context) {
63
81
  super(value);
64
- Object.defineProperty(this, _c, {
82
+ Object.defineProperty(this, _d, {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: void 0
87
+ });
88
+ Object.defineProperty(this, _e, {
65
89
  enumerable: true,
66
90
  configurable: true,
67
91
  writable: true,
68
92
  value: void 0
69
93
  });
70
94
  this[FIMO_SOURCE] = source;
95
+ defineSourceContext(this, context);
71
96
  }
72
97
  get __fimo() {
73
98
  return { source: this[FIMO_SOURCE] };
74
99
  }
75
100
  }
76
- _c = FIMO_SOURCE;
101
+ _d = FIMO_SOURCE, _e = FIMO_SOURCE_CONTEXT;
77
102
  /**
78
103
  * FimoMedia - Media object with source tracking
79
104
  */
80
105
  export class FimoMedia {
81
- constructor(data, source) {
106
+ constructor(data, source, context) {
82
107
  Object.defineProperty(this, "url", {
83
108
  enumerable: true,
84
109
  configurable: true,
@@ -115,7 +140,13 @@ export class FimoMedia {
115
140
  writable: true,
116
141
  value: void 0
117
142
  });
118
- Object.defineProperty(this, _d, {
143
+ Object.defineProperty(this, _f, {
144
+ enumerable: true,
145
+ configurable: true,
146
+ writable: true,
147
+ value: void 0
148
+ });
149
+ Object.defineProperty(this, _g, {
119
150
  enumerable: true,
120
151
  configurable: true,
121
152
  writable: true,
@@ -128,24 +159,31 @@ export class FimoMedia {
128
159
  this.width = data.width;
129
160
  this.height = data.height;
130
161
  this[FIMO_SOURCE] = source;
162
+ defineSourceContext(this, context);
131
163
  }
132
164
  get __fimo() {
133
165
  return { source: this[FIMO_SOURCE] };
134
166
  }
135
167
  }
136
- _d = FIMO_SOURCE;
168
+ _f = FIMO_SOURCE, _g = FIMO_SOURCE_CONTEXT;
137
169
  /**
138
170
  * FimoBoolean - Boolean with source tracking (for display purposes)
139
171
  */
140
172
  export class FimoBoolean {
141
- constructor(value, source) {
173
+ constructor(value, source, context) {
142
174
  Object.defineProperty(this, "value", {
143
175
  enumerable: true,
144
176
  configurable: true,
145
177
  writable: true,
146
178
  value: void 0
147
179
  });
148
- Object.defineProperty(this, _e, {
180
+ Object.defineProperty(this, _h, {
181
+ enumerable: true,
182
+ configurable: true,
183
+ writable: true,
184
+ value: void 0
185
+ });
186
+ Object.defineProperty(this, _j, {
149
187
  enumerable: true,
150
188
  configurable: true,
151
189
  writable: true,
@@ -153,6 +191,7 @@ export class FimoBoolean {
153
191
  });
154
192
  this.value = value;
155
193
  this[FIMO_SOURCE] = source;
194
+ defineSourceContext(this, context);
156
195
  }
157
196
  get __fimo() {
158
197
  return { source: this[FIMO_SOURCE] };
@@ -161,21 +200,27 @@ export class FimoBoolean {
161
200
  return this.value;
162
201
  }
163
202
  }
164
- _e = FIMO_SOURCE;
203
+ _h = FIMO_SOURCE, _j = FIMO_SOURCE_CONTEXT;
165
204
  /**
166
205
  * FimoRichText - Rich text content with source tracking
167
206
  *
168
207
  * Wraps Tiptap JSONContent for rich text fields with inline editing support.
169
208
  */
170
209
  export class FimoRichText {
171
- constructor(content, source) {
210
+ constructor(content, source, context) {
172
211
  Object.defineProperty(this, "content", {
173
212
  enumerable: true,
174
213
  configurable: true,
175
214
  writable: true,
176
215
  value: void 0
177
216
  });
178
- Object.defineProperty(this, _f, {
217
+ Object.defineProperty(this, _k, {
218
+ enumerable: true,
219
+ configurable: true,
220
+ writable: true,
221
+ value: void 0
222
+ });
223
+ Object.defineProperty(this, _l, {
179
224
  enumerable: true,
180
225
  configurable: true,
181
226
  writable: true,
@@ -183,12 +228,13 @@ export class FimoRichText {
183
228
  });
184
229
  this.content = content;
185
230
  this[FIMO_SOURCE] = source;
231
+ defineSourceContext(this, context);
186
232
  }
187
233
  get __fimo() {
188
234
  return { source: this[FIMO_SOURCE] };
189
235
  }
190
236
  }
191
- _f = FIMO_SOURCE;
237
+ _k = FIMO_SOURCE, _l = FIMO_SOURCE_CONTEXT;
192
238
  /**
193
239
  * Type guard to check if a value is a FimoString
194
240
  */
@@ -219,6 +265,13 @@ export function getFimoSource(value) {
219
265
  }
220
266
  return undefined;
221
267
  }
268
+ /** Get the hidden relation/component ancestry from a wrapped value. */
269
+ export function getFimoSourceContext(value) {
270
+ if (value != null && typeof value === 'object' && FIMO_SOURCE_CONTEXT in value) {
271
+ return value[FIMO_SOURCE_CONTEXT];
272
+ }
273
+ return undefined;
274
+ }
222
275
  /**
223
276
  * Get the decomposed parts from a composed FimoString, or undefined
224
277
  */
@@ -1,4 +1,4 @@
1
- import { FIMO_SOURCE, FimoString, hasFimoSource } from './primitives.js';
1
+ import { FIMO_SOURCE, getFimoSourceContext, FimoString, hasFimoSource } from './primitives.js';
2
2
  /**
3
3
  * Template literal helper for composing multiple Fimo values
4
4
  *
@@ -19,7 +19,7 @@ export function fimo(strings, ...values) {
19
19
  const value = values[i];
20
20
  const text = String(value);
21
21
  const source = hasFimoSource(value) ? value[FIMO_SOURCE] : undefined;
22
- parts.push({ text, source: source ?? undefined });
22
+ parts.push({ text, source: source ?? undefined, context: getFimoSourceContext(value) });
23
23
  }
24
24
  }
25
25
  return FimoString.compose(parts);