@rse/specbook 1.2.3 → 1.2.5

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.
@@ -40,26 +40,47 @@ const propValue = (parenProps, object, key) => propsOf(parenProps, object).find(
40
40
  "#type" directive and the configured "#config" directives so every
41
41
  embedded spec is self-contained: one node statement per object (the
42
42
  unique anchor path as the node id, the object name as the displayed
43
- label) and one edge statement per derived edge */
44
- const renderSpec = (diagram, type, center, centerUrl, nodes, edges, index, anchors, parenProps) => {
43
+ label, the container node as the parent) and one edge statement per
44
+ derived edge */
45
+ const renderSpec = (diagram, type, center, centerUrl, nodes, edges, nesting, index, anchors, positions, parenProps) => {
45
46
  const lines = [`#type ${type}`];
46
47
  const config = diagram.config ?? {};
47
48
  for (const [key, value] of Object.entries(config))
48
49
  if (value !== undefined)
49
50
  lines.push(`#config ${key} ${configValue(value)}`);
50
51
  lines.push("");
52
+ /* the explicit top-down order of an "ordered" diagram: the members
53
+ of a container share one row (which Gradia wraps), while the
54
+ top-level nodes stack in document order, a synthetic container
55
+ taking the position of its first member */
56
+ const orderOf = (node) => {
57
+ if (nesting.parentOf.has(node))
58
+ return 0;
59
+ let order = positions.get(node) ?? Number.MAX_SAFE_INTEGER;
60
+ for (const [member, container] of nesting.parentOf)
61
+ if (container === node)
62
+ order = Math.min(order, positions.get(member) ?? order);
63
+ return order;
64
+ };
51
65
  for (const node of nodes) {
52
66
  const anchor = anchors.get(node);
53
67
  const attrs = new Array();
54
- /* a synthetic center node (the only node without an anchor
55
- path) links to its source object, or nowhere at all */
56
- const url = anchor !== undefined ? `#${anchor}` : centerUrl;
68
+ /* a synthetic center or container node (the only nodes without
69
+ an anchor path) links to its source object, or nowhere at all */
70
+ const url = anchor !== undefined ? `#${anchor}` : (node === center ? centerUrl : undefined);
57
71
  if (url !== undefined)
58
72
  attrs.push(`url: ${atom(url)}`);
59
73
  if (diagram.qualified === true && node.kind !== "")
60
74
  attrs.push(`type: ${atom(node.kind)}`);
61
75
  if (type === "hub" ? node === center : node.primary === true)
62
76
  attrs.push("primary: true");
77
+ const container = nesting.parentOf.get(node);
78
+ if (container !== undefined)
79
+ attrs.push(`parent: ${atom(anchors.get(container) ?? container.id)}`);
80
+ if (diagram.nest?.layout !== undefined && nesting.containers.has(node))
81
+ attrs.push(`container: ${diagram.nest.layout}`);
82
+ if (diagram.ordered === true)
83
+ attrs.push(`order: ${orderOf(node)}`);
63
84
  /* attach the values of the configured properties as Gradia
64
85
  key/value attributes (a node lacking a property is fine, as
65
86
  the node set can mix objects of different kinds), with every
@@ -89,13 +110,13 @@ const renderSpec = (diagram, type, center, centerUrl, nodes, edges, index, ancho
89
110
  /* derive the reference edges of a diagram from the "[[...]]" references
90
111
  of the node objects (per the "links" selection, named with the
91
112
  property key when "labeled", and for a "deep" diagram also of their
92
- descendants, with every target lifted to its nearest node and the
93
- reference count as the arity) */
113
+ descendants, with the reference count as the arity), with every
114
+ target lifted to its nearest node */
94
115
  const deriveReferenceEdges = (diagram, nodes, nodeSet, index, anchors, parents, parenProps) => {
95
116
  const edges = new Array();
96
117
  /* lift an object to its nearest ancestor-or-self within the
97
- node set, as a "deep" diagram attributes the references of
98
- the descendants to their nearest node object */
118
+ node set, as a reference onto a descendant of a node object
119
+ (like the scenario of a use case) is a reference onto the node */
99
120
  const lift = (object) => {
100
121
  while (object !== undefined && !nodeSet.has(object))
101
122
  object = parents.get(object);
@@ -109,7 +130,7 @@ const deriveReferenceEdges = (diagram, nodes, nodeSet, index, anchors, parents,
109
130
  const walk = (object) => {
110
131
  objects.push(object);
111
132
  if (diagram.deep === true)
112
- for (const child of object.childs)
133
+ for (const child of object.children)
113
134
  if (!nodeSet.has(child))
114
135
  walk(child);
115
136
  };
@@ -127,9 +148,7 @@ const deriveReferenceEdges = (diagram, nodes, nodeSet, index, anchors, parents,
127
148
  }
128
149
  for (const { text, name } of texts)
129
150
  for (const m of text.matchAll(referenceRegex)) {
130
- let target = resolveUnique(index, m[1].trim(), object).target;
131
- if (diagram.deep === true)
132
- target = lift(target);
151
+ const target = lift(resolveUnique(index, m[1].trim(), object).target);
133
152
  if (target !== undefined && target !== node && nodeSet.has(target)) {
134
153
  const key = `${anchors.get(target) ?? target.id}\u0000${name ?? ""}`;
135
154
  const entry = counts.get(key) ?? { target, name, count: 0 };
@@ -310,7 +329,7 @@ const deriveCenter = (object, diagram, nodes, nodeSet, index, anchors, parenProp
310
329
  /* inject the synthetic center node into the node set, with a
311
330
  collision-free id ("@" never occurs in an anchor path) */
312
331
  const center = { kind: centerCfg.kind ?? "",
313
- id: `${anchors.get(object) ?? object.id}-@center`, name: label, properties: [], childs: [] };
332
+ id: `${anchors.get(object) ?? object.id}-@center`, name: label, properties: [], children: [] };
314
333
  nodes.unshift(center);
315
334
  nodeSet.add(center);
316
335
  return { center, centerUrl };
@@ -347,10 +366,104 @@ const deriveCenterEdges = (cfg, nodes, center, index, parenProps, errors) => {
347
366
  }
348
367
  return edges;
349
368
  };
369
+ /* derive the nesting of the nodes into container nodes: per node the
370
+ first configured property carrying exactly one resolvable reference
371
+ names its container (added to the node set when absent, and nested
372
+ on its own in turn), else its parent object when part of the node
373
+ set, else a synthetic container per object kind; a node nested into
374
+ itself or into a cycle stays un-nested and is reported */
375
+ const deriveNesting = (nest, nodes, nodeSet, index, parents, parenProps, errors) => {
376
+ const parentOf = new Map();
377
+ const byKind = new Map();
378
+ /* the iteration also visits the containers appended underway */
379
+ for (const node of nodes) {
380
+ let container;
381
+ for (const key of nest.properties ?? []) {
382
+ const value = propValue(parenProps, node, key);
383
+ const refs = value !== undefined ? Array.from(value.matchAll(referenceRegex)) : [];
384
+ if (refs.length !== 1)
385
+ continue;
386
+ container = resolveUnique(index, refs[0][1].trim(), node).target;
387
+ if (container !== undefined)
388
+ break;
389
+ }
390
+ if (container === undefined && nest.parent === true) {
391
+ const parent = parents.get(node);
392
+ if (parent !== undefined && nodeSet.has(parent))
393
+ container = parent;
394
+ }
395
+ if (container === undefined && nest.kind === true && node.kind !== "") {
396
+ container = byKind.get(node.kind);
397
+ if (container === undefined) {
398
+ container = { kind: "", id: `@kind-${node.kind}`, name: node.kind, properties: [], children: [] };
399
+ byKind.set(node.kind, container);
400
+ }
401
+ }
402
+ if (container === undefined)
403
+ continue;
404
+ /* a container chain leading back to the node would close a
405
+ cycle, which Gradia rejects exactly like a self-nesting */
406
+ let ancestor = container;
407
+ while (ancestor !== undefined && ancestor !== node)
408
+ ancestor = parentOf.get(ancestor);
409
+ if (ancestor === node) {
410
+ errors.push({ reason: `node "${plainText(node.name)}" is nested into itself` });
411
+ continue;
412
+ }
413
+ parentOf.set(node, container);
414
+ if (!nodeSet.has(container)) {
415
+ nodes.push(container);
416
+ nodeSet.add(container);
417
+ }
418
+ }
419
+ return { parentOf, containers: new Set(parentOf.values()) };
420
+ };
421
+ /* lift the edges crossing a container boundary onto the containers:
422
+ an end is replaced by its outermost ancestor container not enclosing
423
+ the other end as well (the target end only, or both ends), and the
424
+ edges coinciding afterwards merge into one, with their arities summed
425
+ where numeric, so a bundle of references into a container becomes a
426
+ single edge onto it */
427
+ const liftEdges = (edges, parentOf, mode, anchors) => {
428
+ const ancestors = (object) => {
429
+ const chain = new Array();
430
+ for (let p = parentOf.get(object); p !== undefined; p = parentOf.get(p))
431
+ chain.push(p);
432
+ return chain;
433
+ };
434
+ const lift = (end, other) => {
435
+ const enclosing = new Set([other, ...ancestors(other)]);
436
+ let lifted = end;
437
+ for (const ancestor of ancestors(end)) {
438
+ if (enclosing.has(ancestor))
439
+ break;
440
+ lifted = ancestor;
441
+ }
442
+ return lifted;
443
+ };
444
+ const merged = new Map();
445
+ for (const edge of edges) {
446
+ const target = lift(edge.target, edge.source);
447
+ const source = mode === "both" ? lift(edge.source, edge.target) : edge.source;
448
+ if (source === target)
449
+ continue;
450
+ const key = `${anchors.get(source) ?? source.id}${anchors.get(target) ?? target.id}` +
451
+ `${edge.name ?? ""}`;
452
+ const known = merged.get(key);
453
+ if (known === undefined)
454
+ merged.set(key, { source, target, name: edge.name, arity: edge.arity });
455
+ else if (known.arity !== undefined && edge.arity !== undefined
456
+ && /^\d+$/.test(known.arity) && /^\d+$/.test(edge.arity))
457
+ known.arity = String(Number(known.arity) + Number(edge.arity));
458
+ else if (known.arity !== edge.arity)
459
+ known.arity = undefined;
460
+ }
461
+ return Array.from(merged.values());
462
+ };
350
463
  /* derive the Gradia spec of a single object from its "diagram:"
351
464
  schema configuration (the returned spec is absent whenever the
352
465
  configured diagram situation is invalid) */
353
- const deriveDiagram = (object, diagram, index, anchors, parents, parenProps) => {
466
+ const deriveDiagram = (object, diagram, index, anchors, positions, parents, parenProps) => {
354
467
  const type = diagram.type ?? "graph";
355
468
  const errors = new Array();
356
469
  /* determine the node objects (default: the current object plus
@@ -363,7 +476,7 @@ const deriveDiagram = (object, diagram, index, anchors, parents, parenProps) =>
363
476
  nodes = new Array();
364
477
  const walk = (o) => {
365
478
  nodes.push(o);
366
- o.childs.forEach(walk);
479
+ o.children.forEach(walk);
367
480
  };
368
481
  walk(object);
369
482
  }
@@ -418,15 +531,32 @@ const deriveDiagram = (object, diagram, index, anchors, parents, parenProps) =>
418
531
  (edges.some((edge) => edge.source === center) ? 1 : 0);
419
532
  }
420
533
  }
534
+ /* nest the nodes into container nodes (except in a "hub" diagram,
535
+ where every container would need a primary node of its own),
536
+ dropping the edges between a container and its own members,
537
+ which Gradia rejects, as the nesting already shows them */
538
+ let nesting = { parentOf: new Map(), containers: new Set() };
539
+ if (diagram.nest !== undefined) {
540
+ if (type === "hub")
541
+ errors.push({ reason: "\"hub\" diagram cannot carry a \"nest\" configuration" });
542
+ else {
543
+ nesting = deriveNesting(diagram.nest, nodes, nodeSet, index, parents, parenProps, errors);
544
+ if (diagram.nest.crossing !== undefined && diagram.nest.crossing !== "nodes")
545
+ edges = liftEdges(edges, nesting.parentOf, diagram.nest.crossing, anchors);
546
+ edges = edges.filter((edge) => nesting.parentOf.get(edge.source) !== edge.target
547
+ && nesting.parentOf.get(edge.target) !== edge.source);
548
+ }
549
+ }
421
550
  /* the "onlyConnected" filtering keeps only the nodes with at
422
- least one incident edge (sensible for "graph" diagrams only) */
551
+ least one incident edge (sensible for "graph" diagrams only),
552
+ and the container nodes, whose members connect them */
423
553
  if (diagram.onlyConnected === true && type === "graph") {
424
554
  const connected = new Set();
425
555
  for (const edge of edges) {
426
556
  connected.add(edge.source);
427
557
  connected.add(edge.target);
428
558
  }
429
- nodes = nodes.filter((node) => connected.has(node));
559
+ nodes = nodes.filter((node) => connected.has(node) || nesting.containers.has(node));
430
560
  }
431
561
  if (errors.length > 0)
432
562
  return { errors };
@@ -438,8 +568,7 @@ const deriveDiagram = (object, diagram, index, anchors, parents, parenProps) =>
438
568
  if (nodes.length === 0
439
569
  || (diagram.collapse !== false && nodes.length === 1 && edges.length === 0))
440
570
  return { errors };
441
- return { spec: renderSpec(diagram, type, center, centerUrl, nodes, edges, index, anchors, parenProps),
442
- config: diagram.config, columns, errors };
571
+ return { spec: renderSpec(diagram, type, center, centerUrl, nodes, edges, nesting, index, anchors, positions, parenProps), config: diagram.config, columns, errors };
443
572
  };
444
573
  /* the memoized diagram derivations, keyed by specification, as the
445
574
  lint validation and every renderer of an export derive the very
@@ -454,8 +583,11 @@ export const specDiagrams = (specification, config) => {
454
583
  const index = buildLinkIndex(specification);
455
584
  const anchors = anchorPaths(index);
456
585
  const parents = new Map();
457
- for (const node of index)
586
+ const positions = new Map();
587
+ index.forEach((node, position) => {
458
588
  parents.set(node.object, node.parent?.object);
589
+ positions.set(node.object, position);
590
+ });
459
591
  const schemas = collectSchemas(specification, config);
460
592
  /* re-derive the synthetic properties of the consumed parenthesized
461
593
  name tokens, as the derivation sees the AST alone, while the
@@ -464,7 +596,7 @@ export const specDiagrams = (specification, config) => {
464
596
  const results = new Map();
465
597
  for (const [object, schema] of schemas)
466
598
  if (schema.diagram !== undefined)
467
- results.set(object, deriveDiagram(object, schema.diagram, index, anchors, parents, parenProps));
599
+ results.set(object, deriveDiagram(object, schema.diagram, index, anchors, positions, parents, parenProps));
468
600
  derivations.set(specification, { config, results });
469
601
  return results;
470
602
  };
@@ -23,7 +23,7 @@ export const renderAst = (specification, format, config) => {
23
23
  const result = diagrams.get(object);
24
24
  if (result?.spec !== undefined && !isTitleObject(object))
25
25
  node.diagram = result.spec;
26
- object.childs.forEach((child, i) => walk(child, node.childs[i]));
26
+ object.children.forEach((child, i) => walk(child, node.children[i]));
27
27
  };
28
28
  specification.artifacts.forEach((artifact, i) => artifact.objects.forEach((object, j) => walk(object, plain.artifacts[i].objects[j])));
29
29
  }
@@ -26,7 +26,7 @@ export const titleObject = (specification) => {
26
26
  for (const object of objects) {
27
27
  if (isTitleObject(object))
28
28
  return object;
29
- const found = search(object.childs);
29
+ const found = search(object.children);
30
30
  if (found !== undefined)
31
31
  return found;
32
32
  }
@@ -96,6 +96,9 @@ html.theme-light {
96
96
  --gradia-color-group-name: var(--theme-color-accent-18);
97
97
  --gradia-color-group-box: var(--theme-color-accent-32);
98
98
  --gradia-color-group-border: var(--theme-color-accent-26);
99
+ --gradia-color-container-name: var(--theme-color-base-12);
100
+ --gradia-color-container-box: var(--theme-color-base-31);
101
+ --gradia-color-container-border: var(--theme-color-base-24);
99
102
  --gradia-color-edge-line: var(--theme-color-base-22);
100
103
  --gradia-color-edge-name: var(--theme-color-base-5);
101
104
  --gradia-color-edge-arity: var(--theme-color-base-5);
@@ -138,6 +141,9 @@ html.theme-light {
138
141
  --gradia-color-group-name: var(--theme-color-accent-15);
139
142
  --gradia-color-group-box: var(--theme-color-accent-1);
140
143
  --gradia-color-group-border: var(--theme-color-accent-7);
144
+ --gradia-color-container-name: var(--theme-color-base-24);
145
+ --gradia-color-container-box: var(--theme-color-base-4);
146
+ --gradia-color-container-border: var(--theme-color-base-16);
141
147
  --gradia-color-edge-line: var(--theme-color-base-20);
142
148
  --gradia-color-edge-name: var(--theme-color-base-29);
143
149
  --gradia-color-edge-arity: var(--theme-color-base-29);
@@ -395,6 +401,173 @@ div.info-popup.spec a.link-compact::before {
395
401
  display: none;
396
402
  }
397
403
  }
404
+ /* the folding tab (screen-only): the tab riding right of the brand
405
+ bar between the search tab and the table of contents tab (as wide
406
+ as those, fixed independent of the content scrolling), showing a
407
+ fold icon (drawn like the search icon), which slides the two
408
+ controls out to the right (and back in), where the tab follows the
409
+ bar when the table of contents panel shifts it aside; the controls
410
+ fold and unfold all diagrams and all over-long cell texts of the
411
+ content at once and carry, like the fold icon itself, the mark
412
+ colors in a rounded box while anything of their kind is folded */
413
+ div.fold-switch {
414
+ position: fixed;
415
+ top: 10.66rem;
416
+ left: calc(0.6rem - 1px);
417
+ z-index: 30;
418
+ display: flex;
419
+ align-items: center;
420
+ border-radius: 0 0.3rem 0.3rem 0;
421
+ color: var(--theme-color-specbook-background);
422
+ background-color: var(--theme-color-specbook-accent);
423
+ transition: left 0.3s ease;
424
+ }
425
+ body:has(nav.toc-panel.open) div.fold-switch {
426
+ left: calc(31rem - 1px);
427
+ box-shadow: 0.2rem 0 0.6rem rgba(0,0,0,0.3);
428
+ }
429
+ div.fold-switch div.fold-toggle {
430
+ padding: 0.65rem 0.1rem;
431
+ line-height: 0;
432
+ border-radius: 0 0.3rem 0.3rem 0;
433
+ cursor: pointer;
434
+ }
435
+ div.fold-switch div.fold-toggle:hover {
436
+ background-color: var(--theme-color-specbook-accent-soft);
437
+ }
438
+ div.fold-switch svg.fold-icon {
439
+ width: 1.1rem;
440
+ height: 1.1rem;
441
+ padding: 0.25rem;
442
+ border-radius: 0.3rem;
443
+ margin-right: 0.15rem;
444
+ }
445
+ div.fold-switch.folded svg.fold-icon {
446
+ color: var(--theme-color-specbook-mark-text);
447
+ background-color: var(--theme-color-specbook-mark-back);
448
+ }
449
+ div.fold-switch div.fold-controls {
450
+ display: flex;
451
+ align-items: center;
452
+ gap: 0.3rem;
453
+ overflow: hidden;
454
+ max-width: 0;
455
+ padding: 0;
456
+ transition: max-width 0.3s ease, padding 0.3s ease;
457
+ }
458
+ div.fold-switch.open div.fold-controls {
459
+ max-width: 6rem;
460
+ padding: 0 0.5rem 0 0.2rem;
461
+ }
462
+ div.fold-switch div.fold-controls > div {
463
+ padding: 0.25rem;
464
+ line-height: 0;
465
+ border-radius: 0.3rem;
466
+ cursor: pointer;
467
+ -webkit-user-select: none;
468
+ user-select: none;
469
+ }
470
+ div.fold-switch div.fold-controls > div:hover {
471
+ background-color: var(--theme-color-specbook-accent-soft);
472
+ }
473
+ div.fold-switch div.fold-controls > div.folded {
474
+ color: var(--theme-color-specbook-mark-text);
475
+ background-color: var(--theme-color-specbook-mark-back);
476
+ }
477
+ div.fold-switch div.fold-controls svg {
478
+ width: 1.1rem;
479
+ height: 1.1rem;
480
+ }
481
+ @media print {
482
+ div.fold-switch {
483
+ display: none;
484
+ }
485
+ }
486
+ /* the folding of the diagrams and the over-long table cells
487
+ (screen-only): a diagram sits in a fold container carrying a chevron
488
+ mark in the muted symbol color of the anchors at its top left corner
489
+ -- in the page margin beside the content, and in the flow left of
490
+ the diagram inside a table cell, where no margin is left -- while an
491
+ over-long cell carries its own chevron in a rounded grey box behind
492
+ the last still shown word; a folded diagram leaves the icon of its
493
+ tab control behind in the muted color, and a running search unfolds
494
+ everything, so no match hides inside */
495
+ div.fold {
496
+ position: relative;
497
+ }
498
+ span.fold-mark,
499
+ span.fold-more {
500
+ color: var(--theme-color-specbook-symbol);
501
+ line-height: 0;
502
+ cursor: pointer;
503
+ -webkit-user-select: none;
504
+ user-select: none;
505
+ }
506
+ span.fold-mark:hover,
507
+ span.fold-more:hover {
508
+ color: var(--theme-color-specbook-accent);
509
+ }
510
+ span.fold-mark {
511
+ position: absolute;
512
+ top: 0.35rem;
513
+ left: -1.15rem;
514
+ }
515
+ span.fold-more {
516
+ display: inline-block;
517
+ margin-left: 0.2rem;
518
+ padding: 0.1rem 0.25rem;
519
+ vertical-align: -0.05em;
520
+ border: 1px solid var(--theme-color-specbook-border);
521
+ border-radius: 0.25rem;
522
+ }
523
+ span.fold-more:hover {
524
+ border-color: var(--theme-color-specbook-accent);
525
+ }
526
+ span.fold-mark svg,
527
+ span.fold-more svg {
528
+ width: 0.8rem;
529
+ height: 0.8rem;
530
+ }
531
+ div.fold.folded > span.fold-mark svg,
532
+ td.folded span.fold-more svg {
533
+ transform: rotate(-90deg);
534
+ }
535
+ span.fold-label {
536
+ display: none;
537
+ line-height: 0;
538
+ margin-bottom: 1rem;
539
+ color: var(--theme-color-specbook-muted);
540
+ }
541
+ span.fold-label svg {
542
+ width: 1.1rem;
543
+ height: 1.1rem;
544
+ }
545
+ @media screen {
546
+ td div.fold > span.fold-mark {
547
+ position: static;
548
+ display: inline-block;
549
+ vertical-align: middle;
550
+ margin-right: 0.3rem;
551
+ }
552
+ body:not(.searching) div.fold.folded > span.fold-label {
553
+ display: block;
554
+ }
555
+ body:not(.searching) td div.fold.folded > span.fold-label {
556
+ display: inline-block;
557
+ vertical-align: middle;
558
+ margin-bottom: 0;
559
+ }
560
+ body:not(.searching) div.fold.folded > div.diagram,
561
+ body:not(.searching) td.folded .fold-rest {
562
+ display: none;
563
+ }
564
+ }
565
+ @media print {
566
+ span.fold-mark,
567
+ span.fold-more {
568
+ display: none;
569
+ }
570
+ }
398
571
  /* the connection status tab of the live preview (screen-only): the
399
572
  bottommost tab riding right of the brand bar below the table of
400
573
  contents tab (sized like the search tab, fixed independent of the
@@ -406,7 +579,7 @@ div.info-popup.spec a.link-compact::before {
406
579
  for 2s (toggling every 250ms) after a content reload */
407
580
  div.realtime-status {
408
581
  position: fixed;
409
- top: 13.89rem;
582
+ top: 17.12rem;
410
583
  left: calc(0.6rem - 2px);
411
584
  z-index: 30;
412
585
  padding: 0.65rem 0.1rem;
@@ -506,7 +679,7 @@ div.scroll-progress svg.scroll-icon {
506
679
  left of the brand bar and to shift it aside when slid out, with its
507
680
  tab (a menu icon drawn like the search icon, as none of the bundled
508
681
  fonts carries the U+2630 glyph, and sized like the search tab)
509
- riding right of the bar below the search tab */
682
+ riding right of the bar below the folding tab */
510
683
  nav.toc-panel {
511
684
  position: fixed;
512
685
  top: 0;
@@ -536,7 +709,7 @@ nav.toc-panel.open div.toc-tab {
536
709
  }
537
710
  nav.toc-panel div.toc-tab {
538
711
  position: absolute;
539
- top: 10.66rem;
712
+ top: 13.89rem;
540
713
  left: calc(100% - 1px);
541
714
  padding: 0.9rem 0.35rem;
542
715
  line-height: 0;
@@ -1042,8 +1215,8 @@ div.titlepage table.meta td {
1042
1215
  text-align: left;
1043
1216
  }
1044
1217
  /* the fuzzy search tab (screen-only): the middle tab riding right of
1045
- the brand bar between the info tab and the table of
1046
- contents tab (as wide as those, fixed independent of the content
1218
+ the brand bar between the info tab and the folding
1219
+ tab (as wide as those, fixed independent of the content
1047
1220
  scrolling), showing the search icon, which slides the input field
1048
1221
  and its clear symbol out to the right (and back in), where the tab
1049
1222
  follows the bar when the table of contents panel shifts it aside
@@ -4,7 +4,7 @@ import type { Verbose } from "./specbook-verbose.js";
4
4
  export type OutlineEntry = {
5
5
  title: string;
6
6
  anchor: string;
7
- childs: OutlineEntry[];
7
+ children: OutlineEntry[];
8
8
  };
9
9
  export declare const htmlOutline: (specification: Spec, config?: Schema) => OutlineEntry[];
10
10
  export declare const titlePageObject: (specification: Spec) => SpecObject | undefined;