@bytesbybrandon/living-docs 0.3.0 → 0.3.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.
package/README.md CHANGED
@@ -50,7 +50,7 @@ Install the package globally or run it without installing.
50
50
 
51
51
  1. **Install from npm:**
52
52
  ```bash
53
- npm install -g @bytesbybrandon/living-docs
53
+ npm install -g living-docs-axi
54
54
  ```
55
55
  2. **Or clone the repository:**
56
56
  ```bash
@@ -85,7 +85,7 @@ node bin/living-docs-axi.js render docs/note.model.json --theme paper-dark -o do
85
85
  node bin/living-docs-axi.js preview docs
86
86
  ```
87
87
 
88
- With the published package, use `npx -y -p @bytesbybrandon/living-docs living-docs-axi` for each command.
88
+ With the published package, use `npx -y living-docs-axi` for each command.
89
89
 
90
90
  Detailed references:
91
91
 
@@ -0,0 +1,61 @@
1
+ # CLI reference
2
+
3
+ The `living-docs-axi` command follows the AXI principles: compact TOON-style output, a content-first home view, and `help[]` next-step lines.
4
+
5
+ ```text
6
+ living-docs-axi home: themes, built docs, next steps
7
+ living-docs-axi themes theme list with when to use each
8
+ living-docs-axi schema [--block figure] block fields and types
9
+ living-docs-axi ingest <path|url|-> outline, or --section <n|id> for one section
10
+ living-docs-axi ingest <path> --draft write a model, with a drafted timeline, from the source
11
+ living-docs-axi render <model.json> validate and write HTML, or --format mp4|webm|gif for video
12
+ living-docs-axi validate <model.json> validation only
13
+ living-docs-axi preview [dir] serve built docs with an index
14
+ ```
15
+
16
+ ## Flags
17
+
18
+ | Flag | Purpose |
19
+ | --- | --- |
20
+ | `--theme <name>` | `paper-light`, `paper-dark`, `hud`, or a curated look: `field-notes`, `editorial`, `blueprint`. With `--draft`, omitted means the source picks |
21
+ | `--seed <hex>` | with `--draft`: accent seed, overriding the one derived from the title |
22
+ | `-o`, `--out <file>` | output path |
23
+ | `--section <n\|id>` | ingest one section |
24
+ | `--max-chars <n>` | cap section text, default 12000 |
25
+ | `--full` | disable truncation |
26
+ | `--stdout` | print HTML instead of writing a file |
27
+ | `--url <url>` | ingest a URL |
28
+ | `--text <text>` | ingest inline text |
29
+ | `--port <n>` | preview port, default 4600 |
30
+ | `--no-open` | do not open a browser |
31
+ | `--static` | draft without timeline cues |
32
+ | `--format <f>` | `html` (default), `mp4`, `webm`, or `gif` |
33
+ | `--size <WxH>` | video stage, default `1080x1350` |
34
+ | `--fps <n>` | video frames per second, default 30 |
35
+ | `--scale <n>` | device pixel ratio for the stage, default 1 |
36
+
37
+ ## Exit codes
38
+
39
+ | Code | Meaning |
40
+ | --- | --- |
41
+ | 0 | success |
42
+ | 1 | invalid model or runtime failure |
43
+ | 2 | usage error, including unknown flags |
44
+
45
+ Errors are structured on standard output.
46
+ Unknown flags fail loud instead of being ignored.
47
+
48
+ ## Examples
49
+
50
+ ```bash
51
+ npx -y living-docs-axi themes
52
+ npx -y living-docs-axi ingest samples/source/harness-notes.md --draft -o docs/draft.model.json
53
+ npx -y living-docs-axi render docs/draft.model.json --theme paper-dark -o docs/draft.html
54
+ npx -y living-docs-axi preview docs --no-open
55
+ ```
56
+
57
+ ## See also
58
+
59
+ - [Pipeline](pipeline.md)
60
+ - [Document model](document-model.md)
61
+ - [Timeline and video](timeline.md)
@@ -0,0 +1,117 @@
1
+ # Document model
2
+
3
+ A model is `{ schemaVersion: 1, theme?, meta, blocks }`.
4
+ Validation is strict: unknown fields, unknown block types, and unresolved references fail with an exact path.
5
+
6
+ ## Meta
7
+
8
+ `meta` accepts `title`, `subtitle`, `kicker`, `runningHead`, `author`, `affiliation`, `date`, `stamps`, `abstract`, `indexTerms`, `footer`, `accent`, and `style`.
9
+ `style` carries the [style tokens](style-tokens.md).
10
+
11
+ ## Blocks
12
+
13
+ | Block | Purpose |
14
+ | --- | --- |
15
+ | `prose` | body text with inline markdown and `[[1]]` footnote refs |
16
+ | `heading` | numbered section (`level: 2`) or subsection (`level: 3`) |
17
+ | `figure` | generated diagram or chart, auto-numbered |
18
+ | `table` | rule-based table with small-caps head |
19
+ | `algorithm` | numbered pseudocode with an optional highlighted line |
20
+ | `callout` | `takeaway`, `note`, or `warn` aside |
21
+ | `stats` | row of headline numbers |
22
+ | `list`, `quote`, `code` | supporting text blocks |
23
+ | `image` | supplied file, inlined as a data URI |
24
+ | `footnotes` | footnote list printed at the foot |
25
+ | `ticker` | one-line console status strip |
26
+ | `graphScene` | console hero: tabs, prompt, graph, active-node card |
27
+ | `panels` | console grid with 13 panel kinds |
28
+
29
+ ## Figures
30
+
31
+ A figure takes either a `diagram` spec or a `chart` spec.
32
+ Diagrams place nodes and edges on a 1000-unit canvas, with optional groups and notes.
33
+ Charts accept `line`, `scatter`, `bar`, and `steps` series, with ticks, bounds, legend, and annotations.
34
+ Both render as inline SVG, so no image assets are required.
35
+
36
+ `ingest --draft` never writes figures.
37
+ It drafts text, lists, stats, and a timeline from the source, so every figure is authored into the model by hand or by an agent.
38
+ Run `living-docs-axi schema --block figure` for every nested field.
39
+
40
+ ### Diagram
41
+
42
+ Nodes sit at fixed `x` and `y` positions on a canvas that is 1000 units wide by default.
43
+ There is no auto layout, so choose positions that leave room for labels.
44
+ Node ids must be unique, and every edge and group member must name an existing node id.
45
+ Node `kind` is `box`, `accent`, `plain`, `dashed`, or `stack`.
46
+ Edge `route` is `straight` or `elbow`.
47
+
48
+ ```json
49
+ {
50
+ "type": "figure",
51
+ "id": "segments",
52
+ "caption": "Appends go to the active segment. Sealed segments stay read-only.",
53
+ "spec": "diagram",
54
+ "span": "page",
55
+ "motion": "draw",
56
+ "diagram": {
57
+ "width": 1000,
58
+ "height": 360,
59
+ "nodes": [
60
+ { "id": "writer", "label": "writer", "sub": "append", "x": 40, "y": 150, "w": 160, "h": 64 },
61
+ { "id": "dir", "label": "segment directory", "x": 280, "y": 150, "w": 200, "h": 64, "kind": "accent" },
62
+ { "id": "hot", "label": "active segment", "x": 580, "y": 60, "w": 180, "h": 64 },
63
+ { "id": "cold", "label": "sealed segments", "x": 580, "y": 240, "w": 180, "h": 64, "kind": "dashed" }
64
+ ],
65
+ "edges": [
66
+ { "from": "writer", "to": "dir", "label": "publish" },
67
+ { "from": "dir", "to": "hot", "route": "elbow" },
68
+ { "from": "dir", "to": "cold", "route": "elbow" }
69
+ ],
70
+ "groups": [{ "label": "segment store", "nodes": ["hot", "cold"] }],
71
+ "notes": ["Sealing copies no payload."]
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Chart
77
+
78
+ Chart `kind` is `line`, `scatter`, `bar`, or `steps`.
79
+ A series carries `points` of `{ x, y }`, or `values` and `labels` for bars.
80
+ Series `tone` is `accent`, `ink`, `muted`, `kept`, or `rejected`.
81
+
82
+ ```json
83
+ {
84
+ "type": "figure",
85
+ "caption": "Lookup probes grow with segment count.",
86
+ "spec": "chart",
87
+ "chart": {
88
+ "kind": "line",
89
+ "xLabel": "segments",
90
+ "yLabel": "probes",
91
+ "series": [
92
+ { "name": "nacre", "tone": "accent", "points": [{ "x": 1, "y": 1 }, { "x": 32, "y": 32 }, { "x": 128, "y": 128 }] }
93
+ ]
94
+ }
95
+ }
96
+ ```
97
+
98
+ Use an `image` block instead when the graphic already exists as a raster or SVG file.
99
+
100
+ ### Motion
101
+
102
+ `motion` is optional and accepts `none`, `draw`, `reveal`, or `steps`.
103
+ Motion is scoped CSS on the generated SVG: edges draw, marks fade in, and bars grow.
104
+ Print and `prefers-reduced-motion` freeze the final frame, and content stays visible while the animation runs.
105
+
106
+ ## Console blocks
107
+
108
+ `graphScene` accepts a title, meta chips, tabs, a prompt line, a graph payload, an action strip, and a status line.
109
+ The graph layout is seeded, so the same model produces the same scene.
110
+ `panels` holds numbered panels with one of 13 kinds: `repo`, `bars`, `list`, `radar`, `tree`, `log`, `heat`, `big`, `spark`, `code`, `hist`, `metrics`, and `diff`.
111
+
112
+ Other themes render console blocks as annotated cards, so a model can switch themes without losing content.
113
+
114
+ ## See also
115
+
116
+ - [Pipeline](pipeline.md)
117
+ - [Style tokens](style-tokens.md)
@@ -0,0 +1,31 @@
1
+ # Guarantees
2
+
3
+ ## One file per document
4
+
5
+ CSS, SVG, and the console runtime are inlined.
6
+ No CDN, fonts, or remote images are loaded.
7
+
8
+ ## Deterministic output
9
+
10
+ Identical model and theme produce identical bytes, including generated SVG ids, graph layout, and derived palette values.
11
+
12
+ ## Curated styling
13
+
14
+ `meta.style` only accepts known palettes, type pairings, densities, and figure treatments.
15
+ Accent colors are contrast corrected.
16
+ No arbitrary CSS is injected.
17
+
18
+ ## Escaped content
19
+
20
+ Document text is HTML escaped.
21
+ Only `http(s)`, `mailto:`, `#`, and relative URLs survive as links.
22
+
23
+ ## Print ready
24
+
25
+ Paper themes ship `@page` rules and keep figures, tables, algorithms, and callouts unbroken across pages.
26
+ Figure motion freezes to its final frame in print and under reduced-motion preferences.
27
+
28
+ ## Known limits
29
+
30
+ PDF ingestion depends on the optional `unpdf` package and reports a typed error when it is unavailable.
31
+ There is no visual regression suite and no per-theme versioning.
@@ -0,0 +1,47 @@
1
+ # MCP server
2
+
3
+ The package ships a stdio MCP server over the same core as the CLI.
4
+
5
+ ## Published package
6
+
7
+ ```json
8
+ {
9
+ "mcpServers": {
10
+ "living-docs": {
11
+ "command": "npx",
12
+ "args": ["-y", "-p", "living-docs-axi", "living-docs-mcp"]
13
+ }
14
+ }
15
+ }
16
+ ```
17
+
18
+ ## Local checkout
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "living-docs": {
24
+ "command": "node",
25
+ "args": ["<absolute-checkout>/bin/living-docs-mcp.js"]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Tools
32
+
33
+ | Tool | Purpose |
34
+ | --- | --- |
35
+ | `living_docs_themes` | list the themes (three structural bases plus curated looks) and what each is best for |
36
+ | `living_docs_schema` | block fields, or one block in detail |
37
+ | `living_docs_ingest` | outline a source, optionally draft a model with timeline cues (`static` skips them) |
38
+ | `living_docs_render` | validate and write HTML, or `format` mp4, webm, or gif, from a model, a model path, or a source |
39
+ | `living_docs_preview` | serve built docs and return the URL |
40
+
41
+ `living_docs_render` accepts a model object, a model path, or a source to draft from.
42
+ Tools return compact text, and errors are marked with `isError`.
43
+
44
+ ## See also
45
+
46
+ - [CLI reference](cli.md)
47
+ - [Document model](document-model.md)
@@ -0,0 +1,36 @@
1
+ # Pipeline
2
+
3
+ The engine turns one source into one self-contained HTML document.
4
+
5
+ ```text
6
+ source -> ingest -> outline + normalized blocks
7
+ |
8
+ +-> --draft -> valid document model
9
+ |
10
+ +-> author a richer model against the schema
11
+ |
12
+ render --theme -> single HTML file
13
+ ```
14
+
15
+ ## Ingest
16
+
17
+ `ingest` accepts a file path (Markdown, plain text, HTML, or PDF), a URL, inline text, or standard input.
18
+ It returns an outline with section ids, titles, and word counts.
19
+ Large sources stay bounded: read the outline, then pull one section at a time with `--section <n|id>`.
20
+
21
+ ## Draft models
22
+
23
+ `ingest --draft` is the zero-judgment path.
24
+ It writes a document model that validates and renders immediately.
25
+ An author then adds figures, charts, callouts, stats, and console panels against the schema.
26
+
27
+ ## Render
28
+
29
+ `render` validates the model, resolves style tokens, and writes one file with inline CSS, inline SVG, and the console runtime.
30
+ The file loads no fonts, images, or scripts from the network.
31
+
32
+ ## See also
33
+
34
+ - [CLI reference](cli.md)
35
+ - [Document model](document-model.md)
36
+ - [Style tokens](style-tokens.md)
@@ -0,0 +1,21 @@
1
+ # Reactive workflow
2
+
3
+ An optional reactive skill wraps the same pipeline: intake, outline, theme and style plan, model authoring, validation, render, human review, and finalize.
4
+ It calls the CLI and does not reimplement rendering.
5
+
6
+ ## Boundary
7
+
8
+ The skill is maintained separately from this package.
9
+ `src/model/schema.js` and the AXI commands are the stable engine surface.
10
+
11
+ ## Plan and review
12
+
13
+ The workflow holds two human gates.
14
+ The first approves the theme, style tokens, sections, and figures.
15
+ The second reviews the rendered page.
16
+ Model revisions are bounded at four attempts before the run ends in an error state.
17
+
18
+ ## See also
19
+
20
+ - [CLI reference](cli.md)
21
+ - [Style tokens](style-tokens.md)
@@ -0,0 +1,60 @@
1
+ # Style tokens
2
+
3
+ `meta.style` adapts a theme to its source without accepting arbitrary CSS.
4
+ Every choice is curated and validated, so the page stays coherent, printable, and deterministic.
5
+
6
+ | Token | Values | Effect |
7
+ | --- | --- | --- |
8
+ | `palette` | `default`, `ember`, `ocean`, `moss`, `graphite`, `ink`, `plum` | curated accent family |
9
+ | `seed` | hex color, for example `#2f6f8f` | derives the accent, tinted surfaces, and figure fills; lightness is corrected until the accent clears WCAG contrast against the page |
10
+ | `type` | papers: `classic`, `editorial`, `technical`; console: `mono`, `hybrid`, `terminal` | curated font stacks, no web fonts |
11
+ | `density` | `compact`, `normal`, `roomy` | body size, leading, figure margins, panel height |
12
+ | `figure` | `outline`, `solid`, `minimal` | diagram fill and stroke; `minimal` also drops chart gridlines |
13
+ | `layout` | `columns`, `single` | paper themes only: two columns by default, or one reading column for code and wide tables |
14
+
15
+ `meta.accent` remains available as a direct accent override.
16
+ Invalid tokens fail validation at `meta.style`.
17
+
18
+ ## Looks and designed drafts
19
+
20
+ Three themes set the structure: `paper-light`, `paper-dark`, and `hud`.
21
+ Curated looks sit on a structure with their own palette and type: `field-notes` and `editorial` on `paper-light`, `blueprint` on `paper-dark`.
22
+ A look is only a palette and a short stylesheet, so blocks, figures, timelines, and print behave exactly like the base theme.
23
+
24
+ `ingest --draft` designs the doc from its source.
25
+ Code-heavy sources get `blueprint`, papers get `paper-light`, long prose gets `editorial`, short notes get `field-notes`, and the rest get `paper-dark`.
26
+ Paper themes also get an accent seed derived from the title, and density follows length.
27
+ The draft writes these as ordinary `meta.style` tokens, so edit them freely.
28
+ Pass `--theme` or `--seed` to override the choice.
29
+
30
+ ## Motion
31
+
32
+ Motion has two layers.
33
+
34
+ `figure.motion` animates one generated figure with `draw`, `reveal`, or `steps`.
35
+ It is scoped CSS on the SVG, freezes for print, and disables under `prefers-reduced-motion`.
36
+ The content stays visible while the animation runs.
37
+
38
+ The console theme also animates its graph, pulse, and clock.
39
+ Those animations are theme owned, not tokens.
40
+
41
+ ## Example
42
+
43
+ ```json
44
+ {
45
+ "meta": {
46
+ "title": "Example",
47
+ "style": {
48
+ "seed": "#2f6f8f",
49
+ "type": "editorial",
50
+ "density": "roomy",
51
+ "figure": "minimal"
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## See also
58
+
59
+ - [Document model](document-model.md)
60
+ - [Guarantees](guarantees.md)
@@ -0,0 +1,75 @@
1
+ # Timeline and video
2
+
3
+ A timeline makes a doc move in step with its own text.
4
+ Cues highlight a source sentence, light up the figure part it describes, count numbers up, type text out, and scroll the video frame to a block.
5
+ The same timeline plays on the live page and records to MP4, WebM, or GIF.
6
+
7
+ ## Marks and ids
8
+
9
+ Wrap a sentence in any prose string, the abstract included, with `[[mark:id|text]]`.
10
+ Give any block an `id` so cues can point at it.
11
+ Ids use lowercase letters, digits, and hyphens, and must be unique.
12
+
13
+ ```json
14
+ { "type": "figure", "id": "loop", "caption": "...", "spec": "diagram", "diagram": { "nodes": [{ "id": "editor", "label": "4B editor", "x": 552, "y": 28 }] } }
15
+ ```
16
+
17
+ ## Cues
18
+
19
+ `timeline.cues` is a list of `{ at, dur, do, target }`.
20
+ `at` and `dur` are seconds; `dur` defaults to 1.2.
21
+
22
+ | `do` | What it does |
23
+ | --- | --- |
24
+ | `highlight` | Sweeps a highlighter across a mark or block, then leaves a faint underline. |
25
+ | `focus` | Outlines a block or part and dims its siblings. The glow stays inside the block. |
26
+ | `reveal` | Fades a block or part in at the cue time, without moving it. |
27
+ | `count` | Counts the numbers inside the target up from zero. |
28
+ | `type` | Types the target's text out. The full text keeps its space, so nothing reflows. |
29
+ | `camera` | Video only: scrolls the frame to a block or part, or back to `page`. The page stays flat at 100%; `zoom` is an opt-in that scales it. |
30
+ | `activate` | HUD only: glides the scene crosshair to a label. |
31
+
32
+ Targets are `mark:<id>`, `<blockId>`, `<blockId>/<part>`, or `page` for the camera.
33
+ A part is a diagram node id, a panel index such as `08`, or a scene label.
34
+ Validation rejects a cue whose target does not exist.
35
+
36
+ `timeline.duration` defaults to the last cue end plus 1.5 seconds.
37
+ `timeline.loop` restarts the live page when it ends; reveals, counts, and typing stay settled after the first pass.
38
+ On the live page, a block whose cue comes up while it is still below the fold waits, with its cues in order, until the reader scrolls to it.
39
+
40
+ ## Drafted cues
41
+
42
+ `ingest --draft` writes a timeline from the source: up to four abstract sentences and five section leads are marked and highlighted in reading order, the frame scrolls to each lead, and stats count up.
43
+ Pass `--static` for a draft without cues.
44
+ Edit or replace the drafted cues like any other part of the model.
45
+
46
+ ## Video
47
+
48
+ ```bash
49
+ living-docs-axi render docs/notes.model.json --format mp4 -o docs/notes.mp4
50
+ living-docs-axi render docs/notes.model.json --format gif --size 1080x1350 --fps 30
51
+ ```
52
+
53
+ The renderer lays the doc out on a fixed stage, seeks the timeline frame by frame in headless Chromium, and pipes the frames to ffmpeg.
54
+ Every frame is a pure function of time, so two renders on one machine produce the same frames.
55
+ MP4 is the default for sharing; GIF is 720 wide at 15 fps for READMEs.
56
+ Video needs the optional `playwright` package with Chromium, and `ffmpeg` on `PATH` or in `LIVING_DOCS_FFMPEG`.
57
+ In a project, run `npm i -D playwright`; with npx, add it to the same call:
58
+
59
+ ```bash
60
+ npx -y -p living-docs-axi -p playwright living-docs-axi render notes.model.json --format mp4
61
+ npx playwright install chromium
62
+ ```
63
+
64
+ ## Driving a live doc from another page
65
+
66
+ A same-origin page that embeds a timed doc in an iframe can take over its clock through `window.__ld`.
67
+ `pause()` hands the clock to the host, and `seek(t)` then renders the frame at `t` seconds.
68
+ `play()` resumes from the last shown time, `replay()` restarts from zero, and `time()` reads the current time.
69
+ The home page of this site pins one doc and scrubs it as the reader scrolls.
70
+ The controls exist only on a live page; under reduced motion, in print, and in video capture the doc shows its final state or seeks per frame, so a host should check that `pause` is a function first.
71
+
72
+ ## Accessibility and print
73
+
74
+ Under `prefers-reduced-motion`, print, or with scripts off, the page shows its final state: everything revealed, counted, and typed, with narrated sentences underlined.
75
+ Docs without a timeline ship no runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytesbybrandon/living-docs",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Turn a source document into a designed, self-contained living doc: academic paper, dark reading notes, or telemetry console. Ships an AXI CLI and an MCP server.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "bin",
41
41
  "src",
42
42
  "samples",
43
+ "docs/guide",
43
44
  "README.md",
44
45
  "LICENSE"
45
46
  ],
@@ -33,7 +33,7 @@ async function loadChromium() {
33
33
  } catch {
34
34
  throw new ExportError('video export needs the optional playwright package', {
35
35
  code: 'missing-playwright',
36
- hint: 'install playwright beside living-docs (npm i -D playwright, or npx -p @bytesbybrandon/living-docs -p playwright living-docs-axi ...), then npx playwright install chromium'
36
+ hint: 'install playwright beside living-docs (npm i -D playwright, or npx -y -p living-docs-axi -p playwright living-docs-axi ...), then npx playwright install chromium'
37
37
  });
38
38
  }
39
39
  }
@@ -382,19 +382,31 @@ export const THEMES = {
382
382
  }
383
383
  };
384
384
 
385
+ function describeField(name, spec) {
386
+ return {
387
+ name,
388
+ kind: spec.kind,
389
+ required: !spec.optional,
390
+ desc: spec.desc,
391
+ ...(spec.values ? { values: spec.values } : {}),
392
+ ...(spec.of ? { of: spec.of.kind } : {})
393
+ };
394
+ }
395
+
396
+ // Nested object fields are listed under dotted paths and array items under `[]`,
397
+ // so `diagram.nodes[].x` is discoverable without reading the source.
398
+ function flattenFields(fields, prefix = '') {
399
+ return Object.entries(fields).flatMap(([name, spec]) => {
400
+ const path = `${prefix}${name}`;
401
+ const row = describeField(path, spec);
402
+ const inner = spec.kind === 'object' ? spec : spec.kind === 'array' && spec.of?.kind === 'object' ? spec.of : null;
403
+ if (!inner) return [row];
404
+ return [row, ...flattenFields(inner.fields, `${path}${spec.kind === 'array' ? '[].' : '.'}`)];
405
+ });
406
+ }
407
+
385
408
  export function blockFieldSummary(type) {
386
409
  const block = BLOCKS[type];
387
410
  if (!block) return null;
388
- return {
389
- type,
390
- summary: block.summary,
391
- fields: Object.entries(block.fields).map(([name, spec]) => ({
392
- name,
393
- kind: spec.kind,
394
- required: !spec.optional,
395
- desc: spec.desc,
396
- ...(spec.values ? { values: spec.values } : {}),
397
- ...(spec.of ? { of: spec.of.kind } : {})
398
- }))
399
- };
411
+ return { type, summary: block.summary, fields: flattenFields(block.fields) };
400
412
  }