@pipelex/mthds-form 0.11.0 → 0.12.0

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.
@@ -3,6 +3,8 @@ export { a as FieldRenderer, b as FieldRendererProps } from '../field-renderer-C
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
  import { BooleanRunField, DateRunField, EnumRunField, FileRunField, ListRunField, NumberRunField, ObjectRunField, ProseRunField, TextRunField, UnknownRunField, RunField, ConceptCategory } from '../core/descriptor.js';
6
+ import { SaveFiles, SaveFile, SaveResult } from '../core/save-plan.js';
7
+ import { StuffFileKind } from '../core/stuff-files.js';
6
8
  import { HtmlContentView } from '../core/native-content.js';
7
9
  import '../core/contracts.js';
8
10
  import 'mthds/protocol';
@@ -217,115 +219,14 @@ interface ResultFieldProps {
217
219
  */
218
220
  hideLabel?: boolean;
219
221
  }
220
- /** The single dispatch point, mirroring `FieldRenderer` on the input side. */
221
- declare function ResultField({ field, value, depth, hideLabel }: ResultFieldProps): react.JSX.Element | null;
222
-
223
- /**
224
- * A pipe's result, with the two views a result actually needs.
225
- *
226
- * ## Two, and why not three
227
- *
228
- * **Rendered** is the answer for a person: the descriptor-driven view, which
229
- * knows a field is an enum, that a date arrived in the serializer's typed
230
- * envelope, that fifteen records are a table. **JSON** is the answer for whoever
231
- * is debugging the pipe: what exactly came back, verbatim, copyable. Those are
232
- * different jobs and neither substitutes for the other.
233
- *
234
- * They are deliberately NOT peers, and the toggle should not read as a menu of
235
- * equal options. One is the result; the other is the receipt. Rendered opens
236
- * first, always.
237
- *
238
- * A third view is the one to resist. An engine-rendered HTML or plain-text
239
- * presentation — the shape the runtime's own viewer offers — is a second human
240
- * rendering of the same payload, produced by another codebase, carrying no
241
- * descriptor (so it cannot know a kind, a plurality or a nesting) and unable to
242
- * match a host's design system. It is a renderer that cannot be improved without
243
- * shipping the engine. If a plain-text form is wanted, it is a COPY FORMAT and
244
- * belongs behind a copy control, not beside the view that reads the standard.
245
- */
246
- type StuffViewerView = 'rendered' | 'json';
247
- interface StuffViewerProps {
248
- field: RunField;
249
- value: unknown;
250
- /**
251
- * The STUFF's name, for the header — `report_pages`, not `output`.
252
- *
253
- * A result descriptor's root node is named by the engine that built it, and
254
- * `build_output_form` calls it `output` for every pipe there has ever been.
255
- * That is correct in the artifact: the descriptor describes a pipe's output
256
- * slot, which has no name of its own. It is wrong on screen, where the reader
257
- * is looking at ONE data item and every other surface — the graph node they
258
- * clicked, the input panel beside it, the method's own code — calls that item
259
- * by the name the author gave it.
260
- *
261
- * So the name comes from the caller, because only the caller knows it: the
262
- * graph knows which node was opened, a run page knows which variable it is
263
- * showing. Absent, the descriptor's own name stands, which keeps a host that
264
- * has nothing better to say honest rather than blank.
265
- *
266
- * It also supplies `downloadBaseName`'s default, so the file a reader saves
267
- * is named after the thing they were reading rather than `output.json`.
268
- */
269
- name?: string;
270
- /** Which view opens first. Rendered, unless a host has a reason. */
271
- defaultView?: StuffViewerView;
272
- /**
273
- * Names the saved files: `<baseName>.json`, and any file inside the stuff
274
- * that carries no name of its own. Defaults to the field's name, which is
275
- * what the header shows.
276
- */
277
- downloadBaseName?: string;
278
- /**
279
- * Hide the download control. For a host that saves results its own way, or a
280
- * surface where saving makes no sense — a preview inside an editor, say.
281
- */
282
- hideDownload?: boolean;
283
- className?: string;
284
- }
285
222
  /**
286
- * The payload as JSON — the receipt, with the whole of it one click from the
287
- * clipboard.
223
+ * The single dispatch point, mirroring `FieldRenderer` on the input side.
288
224
  *
289
- * Exported because it is also the honest FLOOR: a host that holds a value but
290
- * not the artifacts describing it (an older engine, a spec restored without its
291
- * validate report) should show the value and say why it is not laid out, rather
292
- * than show nothing. That is a different thing from what `StuffViewer`'s JSON
293
- * tab was — one labelled fallback, not one of three guesses offered as a
294
- * choice.
225
+ * The outermost one also marks the root of the result, when no panel above it
226
+ * has: a file's own download button names the file by its place in the result,
227
+ * and the place starts here. Every nested one finds the root already there.
295
228
  */
296
- declare function JsonView({ value }: {
297
- value: unknown;
298
- }): react.JSX.Element;
299
- declare function StuffViewer({ field, value, name, defaultView, downloadBaseName, hideDownload, className, }: StuffViewerProps): react.JSX.Element;
300
-
301
- /** How a host turns a stored reference into something fetchable. */
302
- type ResolveForDownload = (url: string) => string | undefined;
303
- /**
304
- * Everything a stuff contains, saved.
305
- *
306
- * The rule the caller asked for, and the one that matches what a result IS: the
307
- * data goes down as JSON, and any image or document inside it goes down as
308
- * ITSELF. A JSON file holding a URL to a picture is not the picture, and a
309
- * reader who clicks download on a report with three attachments wants the three
310
- * attachments — the reference is what the payload had, not what they asked for.
311
- *
312
- * The JSON is written whenever the stuff is anything more than its files: a
313
- * result that IS one image has nothing left over once the image is saved, so
314
- * writing `{"url": …}` beside it would be a second file saying less than the
315
- * first. A result with fields around its files keeps both.
316
- *
317
- * A file that cannot be fetched is opened in a tab instead of failing. The
318
- * fetch is same-origin for a host that proxies its storage, but a remote URL
319
- * with no CORS header cannot be read into a blob at all, and handing the reader
320
- * the file in a tab is the difference between an awkward download and none.
321
- */
322
- declare function downloadStuff({ field, value, baseName, resolveUrl, }: {
323
- field: RunField;
324
- value: unknown;
325
- /** Names the JSON file and prefixes any file that has no name of its own. */
326
- baseName: string;
327
- resolveUrl?: ResolveForDownload;
328
- }): Promise<void>;
229
+ declare function ResultField(props: ResultFieldProps): react.JSX.Element;
329
230
 
330
231
  /**
331
232
  * The host's seam for turning a stored reference into something a browser can
@@ -401,10 +302,57 @@ type ResolveShareUrl = (url: string) => Promise<string | undefined>;
401
302
  * Only `http:` and `https:` are ever painted or linked either way.
402
303
  */
403
304
  type ProseImages = 'link' | 'load';
305
+ /**
306
+ * Which download controls a result view draws, each set on its own.
307
+ *
308
+ * Apps want different things here: one shows only the download of the whole
309
+ * result, another only a download on each image, a third both, a fourth
310
+ * neither because it saves results its own way. So the two are independent,
311
+ * and the per-file one can be narrowed to the kinds of file that should carry
312
+ * it.
313
+ *
314
+ * Set once on `ResultEnvProvider`, because a file's own button is drawn deep
315
+ * inside the result (a gallery tile, a table row's detail, a nested record) and
316
+ * has to follow the setting whether or not a `StuffViewer` wraps it.
317
+ * `StuffViewer`'s own `downloads` prop overrides it, key by key, for that one
318
+ * panel.
319
+ *
320
+ * Hiding a control never disables saving: a host with its own save UI still
321
+ * plans with `planStuffSave` and delivers through its own function.
322
+ */
323
+ interface DownloadDisplay {
324
+ /** The header control that saves the whole result. Drawn unless `false`. */
325
+ result?: boolean;
326
+ /**
327
+ * Each file's own download button: on every file when `true` (the default),
328
+ * on none when `false`, or only on the kinds listed — `'image'`,
329
+ * `'document'`, and `'markup'` for an HTML page.
330
+ */
331
+ files?: boolean | readonly StuffFileKind[];
332
+ }
404
333
  interface ResultEnv {
405
334
  resolveUrl?: ResolveUrl;
406
335
  resolveShareUrl?: ResolveShareUrl;
407
336
  proseImages?: ProseImages;
337
+ /**
338
+ * How saved files reach the reader. Every download goes through it: the
339
+ * whole-result control and each file's own button alike, each handing it the
340
+ * files to save as a plan (a name, a media type, and the URL the gate
341
+ * admitted or the inline text).
342
+ *
343
+ * Unset, files are saved by the browser tab (`saveInBrowser`), which fetches
344
+ * each URL and saves the bytes through an object URL and a clicked link. A
345
+ * host whose view runs in a sandboxed frame is refused all of that, and
346
+ * supplies a function that asks the host to deliver the files instead —
347
+ * passing the URLs on as links, since a host's download bridge fetches links
348
+ * itself.
349
+ *
350
+ * It reports the files it could not deliver rather than throwing, so the
351
+ * control can tell the reader which ones did not arrive.
352
+ */
353
+ saveFiles?: SaveFiles;
354
+ /** Which download controls are drawn. Both, on every kind of file, when unset. */
355
+ downloads?: DownloadDisplay;
408
356
  /**
409
357
  * How many data columns a table of records shows. Five when unset.
410
358
  *
@@ -422,7 +370,7 @@ interface ResultEnv {
422
370
  */
423
371
  tableColumns?: number;
424
372
  }
425
- declare function ResultEnvProvider({ resolveUrl, resolveShareUrl, proseImages, tableColumns, children, }: ResultEnv & {
373
+ declare function ResultEnvProvider({ resolveUrl, resolveShareUrl, proseImages, tableColumns, saveFiles, downloads, children, }: ResultEnv & {
426
374
  children: ReactNode;
427
375
  }): react.JSX.Element;
428
376
  /**
@@ -443,6 +391,109 @@ declare function useResolveShareUrl(): ResolveShareUrl | undefined;
443
391
  /** The prose-image policy, defaulted to the safe answer for a host that stated none. */
444
392
  declare function useProseImages(): ProseImages;
445
393
 
394
+ /**
395
+ * A pipe's result, with the two views a result actually needs.
396
+ *
397
+ * ## Two, and why not three
398
+ *
399
+ * **Rendered** is the answer for a person: the descriptor-driven view, which
400
+ * knows a field is an enum, that a date arrived in the serializer's typed
401
+ * envelope, that fifteen records are a table. **JSON** is the answer for whoever
402
+ * is debugging the pipe: what exactly came back, verbatim, copyable. Those are
403
+ * different jobs and neither substitutes for the other.
404
+ *
405
+ * They are deliberately NOT peers, and the toggle should not read as a menu of
406
+ * equal options. One is the result; the other is the receipt. Rendered opens
407
+ * first, always.
408
+ *
409
+ * A third view is the one to resist. An engine-rendered HTML or plain-text
410
+ * presentation — the shape the runtime's own viewer offers — is a second human
411
+ * rendering of the same payload, produced by another codebase, carrying no
412
+ * descriptor (so it cannot know a kind, a plurality or a nesting) and unable to
413
+ * match a host's design system. It is a renderer that cannot be improved without
414
+ * shipping the engine. If a plain-text form is wanted, it is a COPY FORMAT and
415
+ * belongs behind a copy control, not beside the view that reads the standard.
416
+ */
417
+ type StuffViewerView = 'rendered' | 'json';
418
+ interface StuffViewerProps {
419
+ field: RunField;
420
+ value: unknown;
421
+ /**
422
+ * The STUFF's name, for the header — `report_pages`, not `output`.
423
+ *
424
+ * A result descriptor's root node is named by the engine that built it, and
425
+ * `build_output_form` calls it `output` for every pipe there has ever been.
426
+ * That is correct in the artifact: the descriptor describes a pipe's output
427
+ * slot, which has no name of its own. It is wrong on screen, where the reader
428
+ * is looking at ONE data item and every other surface — the graph node they
429
+ * clicked, the input panel beside it, the method's own code — calls that item
430
+ * by the name the author gave it.
431
+ *
432
+ * So the name comes from the caller, because only the caller knows it: the
433
+ * graph knows which node was opened, a run page knows which variable it is
434
+ * showing. Absent, the descriptor's own name stands, which keeps a host that
435
+ * has nothing better to say honest rather than blank.
436
+ *
437
+ * It also supplies `downloadBaseName`'s default, so the file a reader saves
438
+ * is named after the thing they were reading rather than `output.json`.
439
+ */
440
+ name?: string;
441
+ /** Which view opens first. Rendered, unless a host has a reason. */
442
+ defaultView?: StuffViewerView;
443
+ /**
444
+ * Names the saved files: `<baseName>.json`, and any file inside the stuff
445
+ * that carries no name of its own, whichever control saved it. Defaults to
446
+ * the field's name, which is what the header shows.
447
+ */
448
+ downloadBaseName?: string;
449
+ /**
450
+ * Which download controls this panel draws, laid key by key over what
451
+ * `ResultEnvProvider` says: `{ result: false }` drops the header's Download
452
+ * and keeps each file's own button, `{ files: false }` the reverse. For a
453
+ * surface where saving makes no sense (a preview inside an editor, say), set
454
+ * both to `false`.
455
+ */
456
+ downloads?: DownloadDisplay;
457
+ className?: string;
458
+ }
459
+ /**
460
+ * The payload as JSON — the receipt, with the whole of it one click from the
461
+ * clipboard.
462
+ *
463
+ * Exported because it is also the honest FLOOR: a host that holds a value but
464
+ * not the artifacts describing it (an older engine, a spec restored without its
465
+ * validate report) should show the value and say why it is not laid out, rather
466
+ * than show nothing. That is a different thing from what `StuffViewer`'s JSON
467
+ * tab was — one labelled fallback, not one of three guesses offered as a
468
+ * choice.
469
+ */
470
+ declare function JsonView({ value }: {
471
+ value: unknown;
472
+ }): react.JSX.Element;
473
+ declare function StuffViewer({ downloads, ...props }: StuffViewerProps): react.JSX.Element;
474
+
475
+ /**
476
+ * The default delivery: the planned files, saved by this browser tab.
477
+ *
478
+ * Inline content is written straight into a blob. A URL is fetched and its
479
+ * bytes saved under the planned name. The fetch is same-origin for a host that
480
+ * proxies its storage, and works cross-origin only where the server sends a
481
+ * CORS header, so a file that cannot be read is REPORTED rather than recovered.
482
+ *
483
+ * There used to be a recovery: open the URL in a new tab when the fetch failed.
484
+ * It could not run. The fetch is awaited, so by the time it failed the click
485
+ * that started the download was spent, and a popup blocker suppresses a window
486
+ * opened outside a click, silently, inside a `catch` that also swallowed the
487
+ * reason. Reporting the file instead is what lets the control that asked tell
488
+ * the reader, and the reader still has the file's own link in the rendered
489
+ * view.
490
+ *
491
+ * This is also what a host gets from `ResultEnvProvider` when it supplies no
492
+ * `saveFiles` of its own, and it is exported for a host that wants to fall back
493
+ * to it, from a bridge that is not always there.
494
+ */
495
+ declare function saveInBrowser(files: readonly SaveFile[]): Promise<SaveResult>;
496
+
446
497
  interface HtmlPreviewProps {
447
498
  content: HtmlContentView;
448
499
  /**
@@ -622,6 +673,20 @@ interface FieldStrings {
622
673
  download: string;
623
674
  /** Announced while the files are being fetched. */
624
675
  downloading: string;
676
+ /**
677
+ * What the whole-result download says when some files did not arrive: the
678
+ * ones no URL could be found for, and the ones the delivery reported. Takes
679
+ * the names the files would have been saved under.
680
+ */
681
+ downloadIncomplete: (names: readonly string[]) => string;
682
+ /**
683
+ * The button beside each file that saves that one file, given the name it
684
+ * saves under: a gallery of buttons all called "Download" would leave a
685
+ * screen-reader user unable to tell which file each one saves.
686
+ */
687
+ downloadFile: (name: string) => string;
688
+ /** What that button says when the file did not arrive. */
689
+ downloadFileFailed: string;
625
690
  /** The result panel's two views, and the control that copies the payload. */
626
691
  viewRendered: string;
627
692
  viewJson: string;
@@ -708,4 +773,4 @@ declare function useFieldPresentation(): FieldPresentation;
708
773
  */
709
774
  declare function humanizeFieldName(name: string): string;
710
775
 
711
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, HtmlPreview, type HtmlPreviewProps, ImageField, JsonView, ListField, Markdown, type MarkdownProps, NumberField, ObjectField, OptionalToggle, ProseField, type ProseImages, type ResolveForDownload, type ResolveShareUrl, type ResolveUrl, ResultEnvProvider, ResultField, type ResultFieldProps, StuffViewer, type StuffViewerProps, type StuffViewerView, TextField, UnknownField, downloadStuff, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
776
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, type DownloadDisplay, EnumField, FieldDomIdProvider, FieldEnv, type FieldPresentation, FieldPresentationProvider, FieldShell, type FieldStrings, FieldStringsProvider, type FileValue, HtmlPreview, type HtmlPreviewProps, ImageField, JsonView, ListField, Markdown, type MarkdownProps, NumberField, ObjectField, OptionalToggle, ProseField, type ProseImages, type ResolveShareUrl, type ResolveUrl, ResultEnvProvider, ResultField, type ResultFieldProps, StuffViewer, type StuffViewerProps, type StuffViewerView, TextField, UnknownField, fieldControlClass, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl };
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, StuffViewer, TextField, UnknownField, downloadStuff, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl } from '../chunk-E2IVBAW3.js';
3
- import '../chunk-4JQAJT7C.js';
4
- import '../chunk-TID7ZN63.js';
2
+ export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, HtmlPreview, ImageField, JsonView, ListField, Markdown, NumberField, ObjectField, OptionalToggle, ProseField, ResultEnvProvider, ResultField, StuffViewer, TextField, UnknownField, fieldControlClass, humanizeFieldName, saveInBrowser, useFieldDomId, useFieldPresentation, useFieldStrings, useProseImages, useResolveShareUrl, useResolveUrl, useResolvedUrl } from '../chunk-4KX6YTLW.js';
3
+ import '../chunk-V7T4DM63.js';
4
+ import '../chunk-VS5JMFUV.js';
5
+ import '../chunk-CM4KVG53.js';
5
6
  import '../chunk-FABY3DEY.js';
6
7
  import '../chunk-XCO6GTEC.js';
7
8
  import '../chunk-PM55AHRO.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/mthds-form",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Headless form kernel and control set for MTHDS method inputs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/stuff-files.ts"],"names":[],"mappings":"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","file":"chunk-4JQAJT7C.js","sourcesContent":["import { ownProp } from './own-property';\nimport {\n isNativeHtmlNode,\n readDocumentContent,\n readHtmlContent,\n readImageContent,\n} from './native-content';\nimport type { RunField } from './descriptor';\n\n/**\n * One file a stuff carries: what to fetch, and what to call it once saved.\n */\nexport interface StuffFile {\n /**\n * The durable reference or URL the value states, for a file held BY\n * REFERENCE. Absent for inline content, which needs no fetch.\n */\n url?: string;\n /**\n * The content itself, for a file the stuff carries INLINE — markup, today.\n * Exactly one of `url` and `text` is set.\n */\n text?: string;\n /** The extension to save inline content under, without the dot. */\n extension?: string;\n /**\n * The URL that works outside this session, when the value carries one. A host\n * whose display URL is session-scoped (an owner-checked proxy route) needs\n * this for anything that leaves the browser.\n */\n publicUrl?: string;\n /** The file's own name when it states one — otherwise the caller derives it. */\n filename?: string;\n mimeType?: string;\n /** Where it sat, dotted: `report.attachments.2.scan`. Names a saved file. */\n path: string;\n kind: 'image' | 'document' | 'markup';\n}\n\n/**\n * Every file inside a stuff, found by walking the DESCRIPTOR — never the value.\n *\n * The distinction is the whole point. A payload walk would have to decide what\n * a file is by looking at it: an object with a `url`, a string that ends in\n * `.pdf`. That is the guessing this package exists to remove, and it is wrong in\n * both directions — a method whose output legitimately has a `url` field of its\n * own is not a document, and a stored image whose reference carries no extension\n * is. The descriptor already states `kind: \"image\"` or `kind: \"document\"`, so\n * the walk reads the declaration and consults the value only for the members\n * that declaration promises.\n *\n * Ordering is the authored one, depth-first, which is the order the rendered\n * view puts them in — so a download of three images numbers them the way the\n * reader saw them.\n */\nexport function collectStuffFiles(field: RunField, value: unknown): StuffFile[] {\n const found: StuffFile[] = [];\n walk(field, value, [], found);\n return found;\n}\n\nfunction walk(field: RunField, raw: unknown, path: string[], out: StuffFile[]): void {\n const value = unwrapContent(field, raw);\n const here = path.length > 0 ? path : [field.name];\n\n // Markup, before the kind switch, exactly as the renderer takes it: a\n // `native.Html` node's KIND is `object` (its content model has two members),\n // so the switch below would walk into it looking for files and find none.\n //\n // It is a file even though nothing stores it as one. The stuff IS a page —\n // the reader is looking at a rendered report — and handing them a JSON\n // envelope with the page inside it as a string is the same mistake as handing\n // them a URL instead of a picture. `isNativeHtmlNode` asks the descriptor\n // whether the concept refines `native.Html`, which is the standard's own way\n // to ask; it never looks at the value to decide.\n if (isNativeHtmlNode(field)) {\n const content = readHtmlContent(value);\n if (content?.innerHtml) {\n out.push({\n text: content.innerHtml,\n extension: 'html',\n path: here.join('.'),\n kind: 'markup',\n });\n }\n return;\n }\n\n switch (field.kind) {\n case 'image': {\n const content = readImageContent(value);\n if (content?.url) {\n out.push({\n url: content.url,\n publicUrl: content.publicUrl,\n filename: content.filename,\n mimeType: content.mimeType,\n path: here.join('.'),\n kind: 'image',\n });\n }\n return;\n }\n case 'document': {\n const content = readDocumentContent(value);\n if (content?.url) {\n out.push({\n url: content.url,\n publicUrl: content.publicUrl,\n filename: content.filename,\n mimeType: content.mimeType,\n path: here.join('.'),\n kind: 'document',\n });\n }\n return;\n }\n case 'object': {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return;\n const record = value as Record<string, unknown>;\n for (const child of field.fields) {\n walk(child, ownProp(record, child.name), [...here, child.name], out);\n }\n return;\n }\n case 'list': {\n if (!Array.isArray(value)) return;\n // The element descriptor's own `name` is unused by the standard, so the\n // index is what identifies an entry — the same thing the rendered list\n // shows beside each row.\n value.forEach((entry, index) => walk(field.item, entry, [...here, String(index)], out));\n return;\n }\n default:\n // Every other kind is a scalar. It cannot contain a file, and a `native`\n // node with no declared members (an `unknown` kind — a composite, say)\n // states nothing a walk could follow, so there is nothing to look at.\n return;\n }\n}\n\n/**\n * The one unwrap path, matching the renderer's: a content model is opened by\n * the property the schema named, and by nothing else.\n */\nfunction unwrapContent(field: RunField, value: unknown): unknown {\n if (!field.contentKey) return value;\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return value;\n const unwrapped = ownProp(value as Record<string, unknown>, field.contentKey);\n return unwrapped === undefined ? value : unwrapped;\n}\n"]}