@rse/specbook 1.0.3 → 1.0.4

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 (40) hide show
  1. package/README.md +106 -19
  2. package/dst/specbook-api.d.ts +29 -9
  3. package/dst/specbook-api.js +108 -33
  4. package/dst/specbook-cli.js +84 -25
  5. package/dst/specbook-cmd-describe.d.ts +7 -1
  6. package/dst/specbook-cmd-describe.js +56 -11
  7. package/dst/specbook-cmd-export.d.ts +2 -1
  8. package/dst/specbook-cmd-export.js +46 -3
  9. package/dst/specbook-cmd-lint.d.ts +2 -1
  10. package/dst/specbook-cmd-lint.js +12 -8
  11. package/dst/specbook-cmd-preview.d.ts +12 -0
  12. package/dst/specbook-cmd-preview.js +57 -0
  13. package/dst/specbook-config.d.ts +1 -1
  14. package/dst/specbook-config.js +187 -37
  15. package/dst/specbook-diagnostic.d.ts +2 -0
  16. package/dst/specbook-diagnostic.js +4 -2
  17. package/dst/specbook-diagram.d.ts +1 -0
  18. package/dst/specbook-diagram.js +0 -0
  19. package/dst/specbook-export-ast.js +4 -2
  20. package/dst/specbook-export-common.js +5 -3
  21. package/dst/specbook-export-html-search.js +118 -30
  22. package/dst/specbook-export-html.css +1140 -233
  23. package/dst/specbook-export-html.d.ts +1 -1
  24. package/dst/specbook-export-html.js +764 -120
  25. package/dst/specbook-export-md.js +4 -2
  26. package/dst/specbook-format-schema.d.ts +33 -1
  27. package/dst/specbook-format-schema.js +34 -2
  28. package/dst/specbook-format.md +193 -25
  29. package/dst/specbook-format.yaml +2291 -299
  30. package/dst/specbook-link.d.ts +2 -1
  31. package/dst/specbook-link.js +43 -3
  32. package/dst/specbook-mcp.js +19 -10
  33. package/dst/specbook-parse-common.d.ts +4 -2
  34. package/dst/specbook-parse-common.js +6 -3
  35. package/dst/specbook-parse-semantic.js +290 -30
  36. package/dst/specbook-parse-syntax.js +3 -1
  37. package/dst/specbook-parse-value.d.ts +1 -0
  38. package/dst/specbook-parse-value.js +17 -3
  39. package/dst/specbook-parse.js +1 -1
  40. package/package.json +14 -6
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { specDiagrams } from "./specbook-diagram.js";
7
7
  import { becauseRegex } from "./specbook-parse-common.js";
8
+ import { isTitleObject } from "./specbook-export-common.js";
8
9
  /* format a timestamp in the frontmatter format */
9
10
  const formatTimestamp = (date) => {
10
11
  const pad = (n) => String(n).padStart(2, "0");
@@ -91,11 +92,12 @@ const renderObjectMd = (object, level, diagrams) => {
91
92
  export const renderMarkdown = (specification, config) => {
92
93
  /* derive the Gradia specs of the diagram-configured objects
93
94
  (an invalid diagram situation omits the diagram, as it is
94
- already reported as a lint diagnostic) */
95
+ already reported as a lint diagnostic, and the diagram of
96
+ the title object is reserved for the HTML/PDF export) */
95
97
  const diagrams = new Map();
96
98
  if (config !== undefined)
97
99
  for (const [object, result] of specDiagrams(specification, config))
98
- if (result.spec !== undefined)
100
+ if (result.spec !== undefined && !isTitleObject(object))
99
101
  diagrams.set(object, result.spec);
100
102
  /* the single frontmatter block (only recognized at the start of a
101
103
  file on re-parse) carries the earliest creation and the latest
@@ -7,22 +7,49 @@ export type SchemaObject = {
7
7
  id?: string;
8
8
  file?: string;
9
9
  desc?: string;
10
+ refs?: string;
10
11
  optional?: boolean;
12
+ referenced?: string[];
13
+ automaton?: SchemaAutomaton;
11
14
  diagram?: SchemaDiagram;
12
15
  format?: SchemaFormat;
13
16
  props?: SchemaProperty[];
14
17
  childs?: SchemaObject[];
15
18
  };
19
+ export type SchemaAutomaton = {
20
+ nodes: string;
21
+ edges: string;
22
+ source: string;
23
+ target: string;
24
+ initial: string;
25
+ final: string;
26
+ };
27
+ export type SchemaDiagramCenter = {
28
+ source?: string;
29
+ property?: string;
30
+ label?: string;
31
+ kind?: string;
32
+ };
33
+ export type SchemaDiagramCenterEdges = {
34
+ property: string;
35
+ inbound?: string;
36
+ outbound?: string;
37
+ both?: string;
38
+ labeled?: string;
39
+ };
16
40
  export type SchemaDiagram = {
17
41
  type?: "graph" | "hub" | "grid";
18
42
  nodes?: string;
19
43
  edges?: string;
20
- center?: string;
44
+ center?: string | SchemaDiagramCenter;
45
+ centerEdges?: SchemaDiagramCenterEdges;
21
46
  links?: "props" | "all";
47
+ labeled?: boolean;
22
48
  edgeSource?: string;
23
49
  edgeTarget?: string;
24
50
  edgeArity?: string;
25
51
  hierarchy?: boolean;
52
+ deep?: boolean;
26
53
  onlyConnected?: boolean;
27
54
  collapse?: boolean;
28
55
  qualified?: boolean;
@@ -39,5 +66,10 @@ export type SchemaProperty = {
39
66
  desc?: string;
40
67
  value?: string;
41
68
  optional?: boolean;
69
+ unique?: boolean | string;
70
+ present?: boolean | string;
71
+ local?: boolean;
72
+ symmetric?: boolean;
73
+ acyclic?: boolean;
42
74
  };
43
75
  export declare const Schema: v.GenericSchema<Schema>;
@@ -20,18 +20,47 @@ const SchemaProperty = v.strictObject({
20
20
  name: v.string(),
21
21
  desc: v.optional(v.string()),
22
22
  value: v.optional(v.string()),
23
- optional: v.optional(v.boolean())
23
+ optional: v.optional(v.boolean()),
24
+ unique: v.optional(v.union([v.boolean(), v.string()])),
25
+ present: v.optional(v.union([v.boolean(), v.string()])),
26
+ local: v.optional(v.boolean()),
27
+ symmetric: v.optional(v.boolean()),
28
+ acyclic: v.optional(v.boolean())
29
+ });
30
+ const SchemaAutomaton = v.strictObject({
31
+ nodes: v.string(),
32
+ edges: v.string(),
33
+ source: v.string(),
34
+ target: v.string(),
35
+ initial: v.string(),
36
+ final: v.string()
37
+ });
38
+ const SchemaDiagramCenter = v.strictObject({
39
+ source: v.optional(v.string()),
40
+ property: v.optional(v.string()),
41
+ label: v.optional(v.string()),
42
+ kind: v.optional(v.string())
43
+ });
44
+ const SchemaDiagramCenterEdges = v.strictObject({
45
+ property: v.string(),
46
+ inbound: v.optional(v.string()),
47
+ outbound: v.optional(v.string()),
48
+ both: v.optional(v.string()),
49
+ labeled: v.optional(v.string())
24
50
  });
25
51
  const SchemaDiagram = v.strictObject({
26
52
  type: v.optional(v.picklist(["graph", "hub", "grid"])),
27
53
  nodes: v.optional(v.string()),
28
54
  edges: v.optional(v.string()),
29
- center: v.optional(v.string()),
55
+ center: v.optional(v.union([v.string(), SchemaDiagramCenter])),
56
+ centerEdges: v.optional(SchemaDiagramCenterEdges),
30
57
  links: v.optional(v.picklist(["props", "all"])),
58
+ labeled: v.optional(v.boolean()),
31
59
  edgeSource: v.optional(v.string()),
32
60
  edgeTarget: v.optional(v.string()),
33
61
  edgeArity: v.optional(v.string()),
34
62
  hierarchy: v.optional(v.boolean()),
63
+ deep: v.optional(v.boolean()),
35
64
  onlyConnected: v.optional(v.boolean()),
36
65
  collapse: v.optional(v.boolean()),
37
66
  qualified: v.optional(v.boolean()),
@@ -49,7 +78,10 @@ const SchemaObject = v.strictObject({
49
78
  id: v.optional(v.string()),
50
79
  file: v.optional(v.string()),
51
80
  desc: v.optional(v.string()),
81
+ refs: v.optional(v.string()),
52
82
  optional: v.optional(v.boolean()),
83
+ referenced: v.optional(v.pipe(v.array(v.string()), v.minLength(1))),
84
+ automaton: v.optional(SchemaAutomaton),
53
85
  diagram: v.optional(SchemaDiagram),
54
86
  format: v.optional(SchemaFormat),
55
87
  props: v.optional(v.array(SchemaProperty)),
@@ -59,7 +59,9 @@ level, the object kinds of the deeper levels, and their properties
59
59
  with optional value constraints: regular expressions `/xxx/` (implicitly
60
60
  anchored, so they have to match the value as a whole),
61
61
  object references `[[xxx]]`, enumerations `enum(xxx,yyy)`, tag sets
62
- `tags(xxx,yyy)`, and lists `list(xxx[, ...])` of such alternatives.
62
+ `tags(xxx,yyy)`, and lists `list(xxx[, ...])` of such alternatives,
63
+ whose comma-separated items are split at top-level commas only, i.e.
64
+ outside double quotes, `[[xxx]]` references, and parentheses.
63
65
 
64
66
  ```ts
65
67
  type Schema = SchemaObject[]
@@ -69,22 +71,49 @@ type SchemaObject = {
69
71
  id?: string
70
72
  file?: string
71
73
  desc?: string
74
+ refs?: string
72
75
  optional?: boolean
76
+ referenced?: string[]
77
+ automaton?: SchemaAutomaton
73
78
  diagram?: SchemaDiagram
74
79
  format?: SchemaFormat
75
80
  props?: SchemaProperty[]
76
81
  childs?: SchemaObject[]
77
82
  }
83
+ type SchemaAutomaton = {
84
+ nodes: string
85
+ edges: string
86
+ source: string
87
+ target: string
88
+ initial: string
89
+ final: string
90
+ }
91
+ type SchemaDiagramCenter = {
92
+ source?: string
93
+ property?: string
94
+ label?: string
95
+ kind?: string
96
+ }
97
+ type SchemaDiagramCenterEdges = {
98
+ property: string
99
+ inbound?: string
100
+ outbound?: string
101
+ both?: string
102
+ labeled?: string
103
+ }
78
104
  type SchemaDiagram = {
79
105
  type?: "graph" | "hub" | "grid"
80
106
  nodes?: string
81
107
  edges?: string
82
- center?: string
108
+ center?: string | SchemaDiagramCenter
109
+ centerEdges?: SchemaDiagramCenterEdges
83
110
  links?: "props" | "all"
111
+ labeled?: boolean
84
112
  edgeSource?: string
85
113
  edgeTarget?: string
86
114
  edgeArity?: string
87
115
  hierarchy?: boolean
116
+ deep?: boolean
88
117
  onlyConnected?: boolean
89
118
  collapse?: boolean
90
119
  qualified?: boolean
@@ -101,6 +130,11 @@ type SchemaProperty = {
101
130
  desc?: string
102
131
  value?: string
103
132
  optional?: boolean
133
+ unique?: boolean | string
134
+ present?: boolean | string
135
+ local?: boolean
136
+ symmetric?: boolean
137
+ acyclic?: boolean
104
138
  }
105
139
  type SchemaGradiaConfig = Partial<{
106
140
  "font-family": string
@@ -160,7 +194,7 @@ type SchemaGradiaConfig = Partial<{
160
194
  BECAUSE artifacts and their content follow one recursive structure
161
195
 
162
196
  - `SchemaObject.kind: string`:
163
- kind name of the object (e.g. `SPEC`, `REQUIREMENT`), unique among
197
+ kind name of the object (e.g. `REQS`, `REQUIREMENT`), unique among
164
198
  its siblings, on level 1 together with its `id`,
165
199
  BECAUSE it discriminates objects and anchors `[[xxx]]` references
166
200
 
@@ -181,17 +215,35 @@ type SchemaGradiaConfig = Partial<{
181
215
  prose description of the object kind,
182
216
  BECAUSE it guides the authors and instructs the AI
183
217
 
218
+ - `SchemaObject.refs?: string`:
219
+ Markdown list of the standards, books, articles, or websites
220
+ (`- <id>: <title> (<year>),` followed by an indented `<url>` line)
221
+ primarily describing the methodology the object kind picks up (left
222
+ out when the configuration is emitted compressed by level 2 or higher),
223
+ BECAUSE the authors and the AI can consult the original method
224
+
184
225
  - `SchemaObject.optional?: boolean`:
185
226
  whether the kind may be absent below its parent or, for a level 1
186
227
  artifact, from its file (default: `false`),
187
228
  BECAUSE not every kind is mandatory below every parent object
188
229
 
230
+ - `SchemaObject.referenced?: string[]`:
231
+ wildcard references (e.g. `[[*]]` for any object, `[[UC.USE-CASE:*]]`
232
+ for the use cases) matching the objects -- themselves or through their
233
+ descendants -- from which every object of this kind has to be referenced
234
+ at least once, where a lapse is reported as a warning only,
235
+ BECAUSE a term nobody uses or a requirement no use case exercises is dead weight
236
+
237
+ - `SchemaObject.automaton?: SchemaAutomaton`:
238
+ finite state machine the child objects of every object of this kind form,
239
+ BECAUSE a lifecycle is only trustworthy when its dead-ends and livelocks are ruled out
240
+
189
241
  - `SchemaObject.diagram?: SchemaDiagram`:
190
242
  diagram generated for every object of this kind,
191
243
  BECAUSE relations buried in references are graspable only when drawn
192
244
 
193
245
  - `SchemaObject.format?: SchemaFormat`:
194
- HTML/PDF rendering format of the child objects of this kind,
246
+ HTML/PDF rendering format of the objects of this kind,
195
247
  BECAUSE rich objects need sections, uniform ones read as tables
196
248
 
197
249
  - `SchemaObject.props?: SchemaProperty[]`:
@@ -202,6 +254,37 @@ type SchemaGradiaConfig = Partial<{
202
254
  object kinds allowed one level below this object (RECURSION),
203
255
  BECAUSE the nesting defines and bounds the document structure
204
256
 
257
+ - `SchemaAutomaton`:
258
+ finite state machine formed by the child objects of an object: the
259
+ nodes have to be reachable from an initial node, a node without
260
+ outgoing edge has to be final (else it is a dead-end), and a final
261
+ node has to be reachable from every node (else it is a livelock),
262
+ BECAUSE the structural sanity of a state machine is checkable, not just describable
263
+
264
+ - `SchemaAutomaton.nodes: string`:
265
+ child object kind whose objects are the nodes (e.g. `STATE`),
266
+ BECAUSE the states are objects of their own, carrying descriptions and flags
267
+
268
+ - `SchemaAutomaton.edges: string`:
269
+ child object kind whose objects are the edges (e.g. `TRANSITION`),
270
+ BECAUSE the transitions are objects of their own, carrying guards and effects
271
+
272
+ - `SchemaAutomaton.source: string`:
273
+ reference-valued property of an edge object referencing its source node,
274
+ BECAUSE an edge is directed
275
+
276
+ - `SchemaAutomaton.target: string`:
277
+ reference-valued property of an edge object referencing its target node,
278
+ BECAUSE an edge is directed
279
+
280
+ - `SchemaAutomaton.initial: string`:
281
+ property of a node object flagging (with the value `true`) an initial node,
282
+ BECAUSE reachability needs a start (the check is skipped without any initial node)
283
+
284
+ - `SchemaAutomaton.final: string`:
285
+ property of a node object flagging (with the value `true`) a final node,
286
+ BECAUSE resting is legal in a final node only (the livelock check is skipped without any final node)
287
+
205
288
  - `SchemaDiagram`:
206
289
  diagram derived for every object of an object kind,
207
290
  BECAUSE the relations of a specification are graspable only when drawn
@@ -218,14 +301,33 @@ type SchemaGradiaConfig = Partial<{
218
301
  comma-separated `[[xxx]]` patterns of the objects acting as edges, not nodes,
219
302
  BECAUSE some relations are objects, yet still connect two nodes
220
303
 
221
- - `SchemaDiagram.center?: string`:
222
- object a `hub` diagram is projected onto (default: `self`),
223
- BECAUSE a hub needs one focus, not always the owning object
304
+ - `SchemaDiagram.center?: string | SchemaDiagramCenter`:
305
+ object a `hub` diagram is projected onto (default: `self`), or a
306
+ synthetic center node, labeled from a referenced `source` object (or
307
+ one of its properties, via `property`) or a literal `label`, typed
308
+ (under `qualified`) by `kind`, and linked to the `source` object,
309
+ BECAUSE the focus of a hub is not always a specification object --
310
+ the solution itself, e.g., sits in the middle of a context diagram
311
+ without being modeled anywhere
312
+
313
+ - `SchemaDiagram.centerEdges?: SchemaDiagramCenterEdges`:
314
+ synthesized center edges of a `hub`: the `property` of the node
315
+ objects whose `inbound` value maps onto a node-to-center edge, whose
316
+ `outbound` value onto a center-to-node edge, and whose `both` value
317
+ onto both edges (the second placement rendered as a "ghost" node),
318
+ each optionally named by the value of the `labeled` property,
319
+ BECAUSE the nodes carry no `[[xxx]]` reference to a synthetic center,
320
+ so their relation to it lives in a direction property instead
224
321
 
225
322
  - `SchemaDiagram.links?: "props" | "all"`:
226
323
  edge source: property values (`props`) or also texts (`all`),
227
324
  BECAUSE prose references are incidental and need an opt-in
228
325
 
326
+ - `SchemaDiagram.labeled?: boolean`:
327
+ whether the edges derived from property values carry the lower-cased
328
+ property key as their label (default: `false`),
329
+ BECAUSE edges of several reference properties need telling apart
330
+
229
331
  - `SchemaDiagram.edgeSource?: string`:
230
332
  edge property naming the source node (default: the parent object),
231
333
  BECAUSE an edge object is not always nested below its own source
@@ -243,6 +345,11 @@ type SchemaGradiaConfig = Partial<{
243
345
  whether containment edges derive from the nesting (default: `false`),
244
346
  BECAUSE the nesting carries no reference and would stay invisible
245
347
 
348
+ - `SchemaDiagram.deep?: boolean`:
349
+ whether the references of the descendants of a node count as its own,
350
+ lifted to their nearest node and counted as the arity (default: `false`),
351
+ BECAUSE the relations between coarse objects live in their fine parts
352
+
246
353
  - `SchemaDiagram.onlyConnected?: boolean`:
247
354
  whether a `graph` drops its edge-less nodes (default: `false`),
248
355
  BECAUSE isolated nodes dilute a diagram about relationships
@@ -264,7 +371,7 @@ type SchemaGradiaConfig = Partial<{
264
371
  BECAUSE layout tuning is cosmetics, not a schema concern
265
372
 
266
373
  - `SchemaFormat`:
267
- HTML/PDF rendering of the child objects of an object kind,
374
+ HTML/PDF rendering of the objects of an object kind among their siblings,
268
375
  BECAUSE one uniform rendering fits neither rich nor numerous objects
269
376
 
270
377
  - `SchemaFormat.type?: "auto" | "complex" | "concise"`:
@@ -272,7 +379,7 @@ type SchemaGradiaConfig = Partial<{
272
379
  BECAUSE sections bury small objects, tables truncate rich ones
273
380
 
274
381
  - `SchemaFormat.maxTableColumns?: number`:
275
- maximum column count of the tables below (default: `4`),
382
+ maximum column count of the table of the kind (default: `4`),
276
383
  BECAUSE the fixed print/PDF page width bounds the table width
277
384
 
278
385
  - `SchemaFormat.withUnusedProps?: boolean`:
@@ -299,6 +406,33 @@ type SchemaGradiaConfig = Partial<{
299
406
  whether the property may be missing (default: `false`),
300
407
  BECAUSE not every property applies to every object
301
408
 
409
+ - `SchemaProperty.unique?: boolean | string`:
410
+ whether every value (`true`) or only the values matching a regexp or
411
+ enum expression occur at most once among the sibling objects of the kind,
412
+ BECAUSE some markers, like a `Main` flow, designate a single sibling
413
+
414
+ - `SchemaProperty.present?: boolean | string`:
415
+ whether some value (`true`) or a value matching a regexp or enum
416
+ expression has to occur on at least one of the (existing) sibling
417
+ objects of the kind, so that `unique` plus `present` demand exactly one,
418
+ BECAUSE some markers, like an initial state, are not just single but indispensable
419
+
420
+ - `SchemaProperty.local?: boolean`:
421
+ whether the reference-valued property has to reference objects below
422
+ the parent object of the referencing object only (default: `false`),
423
+ BECAUSE a transition between the states of two different lifecycles is nonsense
424
+
425
+ - `SchemaProperty.symmetric?: boolean`:
426
+ whether every object the reference-valued property references has to
427
+ reference the referencing object back through the same property
428
+ (default: `false`),
429
+ BECAUSE an undirected relation authored on one side only is half a lie
430
+
431
+ - `SchemaProperty.acyclic?: boolean`:
432
+ whether following the reference-valued property from object to object
433
+ must never return to an object already passed (default: `false`),
434
+ BECAUSE a hierarchy with a loop has no root and an inclusion with a loop never ends
435
+
302
436
  - `SchemaGradiaConfig`:
303
437
  the Gradia rendering options, mirrored from `@rse/gradia`,
304
438
  BECAUSE the diagram cosmetics remain Gradia's concern, not SpecBook's
@@ -335,7 +469,15 @@ type SchemaGradiaConfig = Partial<{
335
469
 
336
470
  The edges are derived from the `[[xxx]]` references of the node objects
337
471
  -- from their property values only, or, with `links: all`, from their
338
- descriptions as well.
472
+ descriptions as well. With `deep: true` the references of all
473
+ descendants of a node object count as its own, every referenced object
474
+ is lifted to its nearest ancestor-or-self within the node set, and the
475
+ number of references behind an edge becomes its arity, so a diagram of
476
+ coarse objects (like the artifacts of a whole specification) shows the
477
+ relations living in their fine-grained parts. With `labeled: true` an
478
+ edge derived from a property value carries the lower-cased property key
479
+ as its label, telling the edges of different properties apart, while
480
+ the edges derived from descriptions stay unlabeled.
339
481
 
340
482
  An object acting as an edge (`edges`) connects its parent object to the
341
483
  object its target property references. Where an edge object is a sibling
@@ -360,6 +502,18 @@ comma-separated `[[xxx]]` reference patterns (`nodes` defaults to the
360
502
  object itself plus all objects below it), and `center` names the object
361
503
  a `hub` is projected onto (default: `self`).
362
504
 
505
+ Instead of naming an existing object, `center` can also declare a
506
+ *synthetic* center node, which represents something no specification
507
+ object models -- like the solution itself in the middle of a context
508
+ diagram. Such a center is labeled from a referenced `source` object (or
509
+ one of its properties, via `property`) or a literal `label`, and links
510
+ to the `source` object. As the nodes carry no `[[xxx]]` reference to a
511
+ synthetic center, `centerEdges` synthesizes the edges from a direction
512
+ `property` of the node objects: its `inbound` value maps onto a
513
+ node-to-center edge, its `outbound` value onto a center-to-node edge,
514
+ and its `both` value onto both edges, each optionally named by the value
515
+ of the `labeled` property.
516
+
363
517
  `onlyConnected` drops the edge-less nodes of a `graph`, `collapse`
364
518
  (default: `true`) omits a degenerated diagram consisting of a single
365
519
  node only, `qualified` labels every node with its object kind,
@@ -371,19 +525,26 @@ rendering options (e.g. `grid-columns-max: 5`) through to the diagram.
371
525
  ### Format
372
526
 
373
527
  An object kind can furthermore carry a `format` field, which controls
374
- how the HTML/PDF export renders the child objects of every object of
375
- that kind. Its `type` selects the rendering: `complex` (nested
376
- sections), `concise` (compact per-kind tables), or the default `auto`,
377
- which collapses only the deepest level into tables.
378
-
379
- Below a `concise` object, unconfigured children implicitly stay
528
+ how the HTML/PDF export renders the objects of that kind among their
529
+ siblings, so that the sibling kinds below one parent can render
530
+ differently (e.g. one kind as a table, another as sections). Its `type`
531
+ selects the rendering: `complex` (nested sections), `concise` (one
532
+ compact table of all sibling objects of the kind), or the default
533
+ `auto`, which collapses only the deepest level into tables.
534
+
535
+ Inside a `concise` table, unconfigured child kinds implicitly stay
380
536
  `concise` and render as sub-tables inside the description cells, while
381
537
  an explicitly configured `type` is always honored, even a `complex`
382
538
  rendering pressed into a cell. `maxTableColumns` (default: `4`) bounds
383
- the columns of the compact tables below the object (wider groups chunk
384
- their properties into embedded per-object tables), and `withUnusedProps`
539
+ the columns of the compact table of the kind (a wider group chunks
540
+ its properties into embedded per-object tables), and `withUnusedProps`
385
541
  (default: `false`) unconditionally renders the defined but unused
386
- properties of that kind, as empty property table lines or table columns.
542
+ properties of that kind, as property table lines or table columns. In
543
+ the HTML/PDF export, a property absent from an object shows an "empty
544
+ set" marker in place of its value, which tells it apart from a property
545
+ given with an empty value.
546
+ Property lines and table columns follow the property order of the
547
+ schema, with properties unknown to the schema appended in document order.
387
548
  Objects collapsed into tables carry no headings and hence leave the
388
549
  table of contents and the PDF outline.
389
550
 
@@ -531,9 +692,12 @@ SpecBook SCHEMA Model
531
692
  ### Artifacts
532
693
 
533
694
  A schema consists of a YAML file, which is exactly a serialization of
534
- the object model defined by the **SpecBook SCHEMA Meta Model**. If no
535
- particular schema is given, the standard schema configuration bundled
536
- with **SpecBook** applies.
695
+ the object model defined by the **SpecBook SCHEMA Meta Model**, or of
696
+ several such files, merged in order into one schema (the later files
697
+ into the earlier ones, where the objects merge deeply and the elements
698
+ of the lists are matched by their identity). If no particular schema is
699
+ given, the standard schema configuration bundled with **SpecBook**
700
+ applies.
537
701
 
538
702
  SpecBook SPEC Model
539
703
  -------------------
@@ -671,7 +835,7 @@ order:
671
835
  (e.g. `## ENTITY: Attendee Browser {{attendee}}`).
672
836
 
673
837
  - `(<token/>)`: a parenthesized token with three possible roles: on
674
- level 1 it becomes the artifact id (e.g. `# SPEC: Data Model (DM)`);
838
+ level 1 it becomes the artifact id (e.g. `# DATA: Data Model (DM)`);
675
839
  on deeper levels it acts as the implicit anchor id.
676
840
 
677
841
  - `(*)`: the *primary* marker, flagging the object as primary (e.g.
@@ -701,7 +865,7 @@ whole.
701
865
 
702
866
  The description of a Complex Format object is all block content below
703
867
  its heading (up to the next heading) except the property list:
704
- paragraphs, blockquotes, ordered lists (e.g. scenario steps), and
868
+ paragraphs, blockquotes, ordered lists (e.g. scenario steps or test case procedures), and
705
869
  fenced code blocks. A fenced code block
706
870
  of language `gradia` is skipped, as it is the derived diagram which
707
871
  **SpecBook** itself emits into exported Markdown and which must not
@@ -741,7 +905,11 @@ below the artifact with id `FV`).
741
905
  A reference has to resolve to exactly one object: a single-segment
742
906
  reference tries the variants (1) id/anchor, (2) name, and (3)
743
907
  `<kind/>:<name-or-id/>` in order, where the first variant yielding any
744
- matches decides; several matches are an ambiguity and zero matches an
908
+ matches decides; several matches are narrowed down to the ones nearest
909
+ to the referencing object, i.e. sharing the longest ancestor chain with
910
+ it (so `[[STATE:Draft]]` inside a lifecycle picks the `Draft` state of
911
+ this very lifecycle, even if other lifecycles carry a `Draft` state,
912
+ too); still several matches are an ambiguity and zero matches an
745
913
  unresolvable reference, both reported by the linter. Wildcard references
746
914
  resolve into match *sets* and are used in the schema configuration to
747
915
  constrain reference-valued properties (e.g. `[[PERSONA:*]]` or