@issuegraph/editor 0.1.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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +238 -0
  3. package/dist/audit/findings.d.ts +225 -0
  4. package/dist/audit/findings.d.ts.map +1 -0
  5. package/dist/audit/findings.js +348 -0
  6. package/dist/audit/findings.js.map +1 -0
  7. package/dist/audit/styles.d.ts +37 -0
  8. package/dist/audit/styles.d.ts.map +1 -0
  9. package/dist/audit/styles.js +72 -0
  10. package/dist/audit/styles.js.map +1 -0
  11. package/dist/audit/surface.d.ts +155 -0
  12. package/dist/audit/surface.d.ts.map +1 -0
  13. package/dist/audit/surface.js +202 -0
  14. package/dist/audit/surface.js.map +1 -0
  15. package/dist/create/draft.d.ts +119 -0
  16. package/dist/create/draft.d.ts.map +1 -0
  17. package/dist/create/draft.js +114 -0
  18. package/dist/create/draft.js.map +1 -0
  19. package/dist/create/keys.d.ts +225 -0
  20. package/dist/create/keys.d.ts.map +1 -0
  21. package/dist/create/keys.js +228 -0
  22. package/dist/create/keys.js.map +1 -0
  23. package/dist/create/placement.d.ts +69 -0
  24. package/dist/create/placement.d.ts.map +1 -0
  25. package/dist/create/placement.js +76 -0
  26. package/dist/create/placement.js.map +1 -0
  27. package/dist/firstpass/batch.d.ts +190 -0
  28. package/dist/firstpass/batch.d.ts.map +1 -0
  29. package/dist/firstpass/batch.js +143 -0
  30. package/dist/firstpass/batch.js.map +1 -0
  31. package/dist/firstpass/candidates.d.ts +128 -0
  32. package/dist/firstpass/candidates.d.ts.map +1 -0
  33. package/dist/firstpass/candidates.js +39 -0
  34. package/dist/firstpass/candidates.js.map +1 -0
  35. package/dist/firstpass/keys.d.ts +90 -0
  36. package/dist/firstpass/keys.d.ts.map +1 -0
  37. package/dist/firstpass/keys.js +112 -0
  38. package/dist/firstpass/keys.js.map +1 -0
  39. package/dist/firstpass/queue.d.ts +183 -0
  40. package/dist/firstpass/queue.d.ts.map +1 -0
  41. package/dist/firstpass/queue.js +159 -0
  42. package/dist/firstpass/queue.js.map +1 -0
  43. package/dist/firstpass/render.d.ts +100 -0
  44. package/dist/firstpass/render.d.ts.map +1 -0
  45. package/dist/firstpass/render.js +216 -0
  46. package/dist/firstpass/render.js.map +1 -0
  47. package/dist/firstpass/styles.d.ts +36 -0
  48. package/dist/firstpass/styles.d.ts.map +1 -0
  49. package/dist/firstpass/styles.js +148 -0
  50. package/dist/firstpass/styles.js.map +1 -0
  51. package/dist/firstpass/view.d.ts +82 -0
  52. package/dist/firstpass/view.d.ts.map +1 -0
  53. package/dist/firstpass/view.js +60 -0
  54. package/dist/firstpass/view.js.map +1 -0
  55. package/dist/firstpass/words.d.ts +105 -0
  56. package/dist/firstpass/words.d.ts.map +1 -0
  57. package/dist/firstpass/words.js +28 -0
  58. package/dist/firstpass/words.js.map +1 -0
  59. package/dist/index.d.ts +150 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +147 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/overlay/grammar.d.ts +259 -0
  64. package/dist/overlay/grammar.d.ts.map +1 -0
  65. package/dist/overlay/grammar.js +223 -0
  66. package/dist/overlay/grammar.js.map +1 -0
  67. package/dist/overlay/render.d.ts +125 -0
  68. package/dist/overlay/render.d.ts.map +1 -0
  69. package/dist/overlay/render.js +439 -0
  70. package/dist/overlay/render.js.map +1 -0
  71. package/dist/overlay/styles.d.ts +41 -0
  72. package/dist/overlay/styles.d.ts.map +1 -0
  73. package/dist/overlay/styles.js +124 -0
  74. package/dist/overlay/styles.js.map +1 -0
  75. package/dist/picker/render.d.ts +80 -0
  76. package/dist/picker/render.d.ts.map +1 -0
  77. package/dist/picker/render.js +112 -0
  78. package/dist/picker/render.js.map +1 -0
  79. package/dist/picker/styles.d.ts +26 -0
  80. package/dist/picker/styles.d.ts.map +1 -0
  81. package/dist/picker/styles.js +116 -0
  82. package/dist/picker/styles.js.map +1 -0
  83. package/dist/picker/view.d.ts +135 -0
  84. package/dist/picker/view.d.ts.map +1 -0
  85. package/dist/picker/view.js +117 -0
  86. package/dist/picker/view.js.map +1 -0
  87. package/dist/picker/words.d.ts +53 -0
  88. package/dist/picker/words.d.ts.map +1 -0
  89. package/dist/picker/words.js +28 -0
  90. package/dist/picker/words.js.map +1 -0
  91. package/dist/reevaluate/render.d.ts +77 -0
  92. package/dist/reevaluate/render.d.ts.map +1 -0
  93. package/dist/reevaluate/render.js +211 -0
  94. package/dist/reevaluate/render.js.map +1 -0
  95. package/dist/reevaluate/styles.d.ts +24 -0
  96. package/dist/reevaluate/styles.d.ts.map +1 -0
  97. package/dist/reevaluate/styles.js +182 -0
  98. package/dist/reevaluate/styles.js.map +1 -0
  99. package/dist/reevaluate/view.d.ts +102 -0
  100. package/dist/reevaluate/view.d.ts.map +1 -0
  101. package/dist/reevaluate/view.js +111 -0
  102. package/dist/reevaluate/view.js.map +1 -0
  103. package/dist/reevaluate/words.d.ts +54 -0
  104. package/dist/reevaluate/words.d.ts.map +1 -0
  105. package/dist/reevaluate/words.js +29 -0
  106. package/dist/reevaluate/words.js.map +1 -0
  107. package/dist/scale/commands.d.ts +60 -0
  108. package/dist/scale/commands.d.ts.map +1 -0
  109. package/dist/scale/commands.js +49 -0
  110. package/dist/scale/commands.js.map +1 -0
  111. package/dist/scale/ladder.d.ts +141 -0
  112. package/dist/scale/ladder.d.ts.map +1 -0
  113. package/dist/scale/ladder.js +243 -0
  114. package/dist/scale/ladder.js.map +1 -0
  115. package/dist/scale/render.d.ts +85 -0
  116. package/dist/scale/render.d.ts.map +1 -0
  117. package/dist/scale/render.js +194 -0
  118. package/dist/scale/render.js.map +1 -0
  119. package/dist/scale/styles.d.ts +21 -0
  120. package/dist/scale/styles.d.ts.map +1 -0
  121. package/dist/scale/styles.js +105 -0
  122. package/dist/scale/styles.js.map +1 -0
  123. package/dist/workspace/inspector.d.ts +117 -0
  124. package/dist/workspace/inspector.d.ts.map +1 -0
  125. package/dist/workspace/inspector.js +140 -0
  126. package/dist/workspace/inspector.js.map +1 -0
  127. package/dist/workspace/rail.d.ts +122 -0
  128. package/dist/workspace/rail.d.ts.map +1 -0
  129. package/dist/workspace/rail.js +216 -0
  130. package/dist/workspace/rail.js.map +1 -0
  131. package/dist/workspace/render.d.ts +139 -0
  132. package/dist/workspace/render.d.ts.map +1 -0
  133. package/dist/workspace/render.js +366 -0
  134. package/dist/workspace/render.js.map +1 -0
  135. package/dist/workspace/selection.d.ts +92 -0
  136. package/dist/workspace/selection.d.ts.map +1 -0
  137. package/dist/workspace/selection.js +83 -0
  138. package/dist/workspace/selection.js.map +1 -0
  139. package/dist/workspace/styles.d.ts +39 -0
  140. package/dist/workspace/styles.d.ts.map +1 -0
  141. package/dist/workspace/styles.js +313 -0
  142. package/dist/workspace/styles.js.map +1 -0
  143. package/package.json +58 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The order rail: always present, always complete, and virtualised.
3
+ *
4
+ * ## The asymmetry with the canvas is the design, not an inconsistency
5
+ *
6
+ * §17f gives the two centre zones opposite obligations. The canvas answers
7
+ * "what surrounds this issue" and REFUSES above its budget — that is #10343's
8
+ * scale ladder, and it is correct. The rail answers "what gets worked next" for
9
+ * the whole backlog and must NEVER refuse. Assembling them must not average the
10
+ * two: a rail that paginates has stopped answering its question, and a canvas
11
+ * that tries to keep up with the rail has stopped honouring its budget.
12
+ *
13
+ * So virtualisation here is a requirement rather than an optimisation, and it
14
+ * is the resolution of what reads at first like a contradiction — "complete"
15
+ * and "windowed" at once.
16
+ *
17
+ * **The MODEL is complete; the WINDOW is bounded.** Every slot the host handed
18
+ * over is held, and {@link RailWindow.addressOf} answers for every rank in the
19
+ * order whether or not it is drawn. What is bounded is only how many rows are
20
+ * rendered at a time. Nothing is dropped, nothing is refused, and a host that
21
+ * scrolls asks for a different window over the same complete model.
22
+ *
23
+ * That is what "every rank is addressable" means, and it is the property the
24
+ * acceptance suite drives at 312 rows.
25
+ *
26
+ * ## `addressOf` is a closure over a private index
27
+ *
28
+ * The same shape — and the same reasoning — as `AuditOverlay.rowFor`. Handing
29
+ * out the index itself would let a JavaScript consumer mutate it, after which
30
+ * the lookups and `total` would disagree about what the order contains, which
31
+ * is the inconsistency this value exists to prevent. A closure cannot be
32
+ * reached at all, and still answers in constant time.
33
+ *
34
+ * ## What gets windowed
35
+ *
36
+ * Slots are sliced, and the issues and edges are cut down to exactly what the
37
+ * drawn rows need — a drawn slot's members, an exclusion's key, and the
38
+ * endpoints of any edge a drawn row owes a badge for.
39
+ *
40
+ * KEEPING THE WHOLE ISSUE LIST WAS THE OBVIOUS THING AND IT WAS WRONG. See the
41
+ * note at the filter itself for what it cost: the linear projection renders a
42
+ * count of the keys that appear in no slot and on no edge, so every edgeless
43
+ * issue outside the window was reported to the reader as isolated — the rail
44
+ * describing the reader's scroll position as though it were the document.
45
+ *
46
+ * `together-with` keeps its own rule, and it is layer 1's rather than a choice
47
+ * made here: that edge draws as an ENCLOSURE around one slot's members, so the
48
+ * viewer drops — and reports — any whose members no longer share a drawn slot.
49
+ * Those edges are left out of the windowed document and counted on
50
+ * {@link RailWindow.undrawn} instead, so the fact is reported as a property of
51
+ * the WINDOW rather than leaking out as a document defect.
52
+ */
53
+ import { edgeIdentity } from '@issuegraph/core';
54
+ import { normalizeDocument } from '@issuegraph/viewer';
55
+ /**
56
+ * How many rows a window holds when the host does not say.
57
+ *
58
+ * A round number rather than a measured one: this package has no mount and
59
+ * therefore no viewport to measure, so any default here is a guess. It is
60
+ * published so a host can see what it is overriding, and generous enough that
61
+ * a first paint fills an ordinary screen.
62
+ */
63
+ export const RAIL_WINDOW = 50;
64
+ /**
65
+ * A finite integer, or the fallback.
66
+ *
67
+ * `NaN` is the one input that survives a clamp: `Math.max(0, Math.min(NaN, n))`
68
+ * is `NaN`, so it would flow into `slice` — which yields an empty window — and
69
+ * into `before`/`after`, which would then not sum to `total`. That is the rail
70
+ * silently reporting an order it cannot account for, on the zone whose whole
71
+ * contract is that it accounts for everything. `Infinity` clamps correctly and
72
+ * is admitted; only a non-number is replaced.
73
+ */
74
+ function finite(value, fallback) {
75
+ const asked = value ?? fallback;
76
+ return Number.isNaN(asked) ? fallback : Math.trunc(asked);
77
+ }
78
+ /** Clamp a window onto an order of `total` slots. */
79
+ function clampWindow(total, options) {
80
+ // Fractions and negatives are clamped rather than refused: this reads a
81
+ // scroll position, and a host computing one from a measured height will hand
82
+ // over a float sooner or later. Refusing would take the rail down over a
83
+ // rounding error, which is the one thing it may never do.
84
+ const count = Math.max(0, Math.min(finite(options.count, RAIL_WINDOW), total));
85
+ const start = Math.max(0, Math.min(finite(options.start, 0), total - count));
86
+ return { start, count };
87
+ }
88
+ /**
89
+ * Window an order without narrowing it.
90
+ *
91
+ * Pure, and total over any document: there is no input that makes this throw or
92
+ * refuse, because the rail is the zone that is not allowed to.
93
+ */
94
+ export function railWindow(raw, options = {}) {
95
+ // IT NORMALIZES ITS OWN INPUT, like `inspectorView` and `renderWorkspace`.
96
+ // Every exported function in this module that takes a `ViewerDocument` now
97
+ // does, and that is the point: the reasoning was applied to one sibling and
98
+ // not the other, which left two public helpers with different contracts for
99
+ // the same argument.
100
+ //
101
+ // Slicing RAW slots is scroll-dependent on a malformed document. Layer 1
102
+ // keeps the FIRST placement of a key and drops the later one, so a duplicate
103
+ // straddling the window boundary became the valid placement whenever its
104
+ // earlier copy fell outside the slice — the visible order changing with the
105
+ // reader's position. `renderWorkspace` normalizing first fixed that for the
106
+ // workspace path and for nothing else; a direct caller of this still saw it.
107
+ //
108
+ // Idempotent, so the workspace normalizing first costs a pass and changes
109
+ // nothing. `total` therefore counts the slots the rail can actually show,
110
+ // which is the number a caller is asking about.
111
+ const input = normalizeDocument(raw).document;
112
+ const slots = input.order.slots;
113
+ const total = slots.length;
114
+ const { start, count } = clampWindow(total, options);
115
+ const rows = slots.slice(start, start + count);
116
+ // BUILT OVER EVERY SLOT, not the window. These are the indexes behind the two
117
+ // lookups, and narrowing them to the window is exactly the completeness bug
118
+ // this module exists to avoid.
119
+ const byRank = new Map();
120
+ const offsetByKey = new Map();
121
+ for (const [offset, slot] of slots.entries()) {
122
+ if (slot.rank !== null)
123
+ byRank.set(slot.rank, slot);
124
+ // FIRST WINS, matching `normalizeDocument`'s own rule for a key placed
125
+ // twice: it keeps the earlier slot and drops the later placement. A last-
126
+ // wins index here would send a host scrolling to a row the viewer never
127
+ // drew.
128
+ for (const member of slot.members)
129
+ if (!offsetByKey.has(member))
130
+ offsetByKey.set(member, offset);
131
+ }
132
+ // Which leads are drawn, so the `together-with` rule below can ask whether a
133
+ // unit survived the window.
134
+ const drawnLead = new Map();
135
+ for (const slot of rows)
136
+ for (const member of slot.members)
137
+ drawnLead.set(member, slot.lead);
138
+ // EVERY KEY THAT RENDERS A ROW, WHICH IS NOT THE SAME AS EVERY SLOT MEMBER.
139
+ // Exclusions are carried whole, and layer 1 draws each one as a footer row
140
+ // that calls `edgeBadges` for its own key — so an excluded row owes badges
141
+ // exactly like a slot does. Built from the slots alone, an excluded issue
142
+ // related only to an out-of-window slot lost its badge as the reader
143
+ // scrolled, on a row that never left the screen.
144
+ const drawnMember = new Set([
145
+ ...rows.flatMap((slot) => slot.members),
146
+ ...input.order.excluded.map((exclusion) => exclusion.key),
147
+ ]);
148
+ // AN EDGE IS KEPT WHEN A DRAWN ROW OWES A BADGE FOR IT — one endpoint on
149
+ // screen is enough, because that row draws the badge and the other end is
150
+ // named as text. `together-with` keeps its own rule: layer 1 draws it as an
151
+ // ENCLOSURE around one slot's members, so an edge whose members no longer
152
+ // share a drawn slot has nothing to draw it, and the viewer would report that
153
+ // as a document defect rather than as a fact about the window.
154
+ const drawable = (edge) => edge.field === 'together-with'
155
+ ? drawnLead.get(edge.from) !== undefined && drawnLead.get(edge.from) === drawnLead.get(edge.to)
156
+ : drawnMember.has(edge.from) || drawnMember.has(edge.to);
157
+ const edges = input.edges.filter(drawable);
158
+ // ISSUES ARE WINDOWED TOO, AND AN EARLIER VERSION OF THIS GOT IT WRONG in a
159
+ // way worth recording, because the reasoning that produced it was nearly
160
+ // right. It kept the WHOLE issue list, justified on the grounds that
161
+ // `normalizeDocument` reports a slot member that is not an issue and never
162
+ // the reverse — which is true, and is about DIAGNOSTICS. It is not about what
163
+ // gets DRAWN: the linear projection also renders a footer counting the keys
164
+ // that appear "in no slot and on no edge", so every edgeless issue outside
165
+ // the window was reported to the reader as an isolated issue. Windowing made
166
+ // the rail state a falsehood about the document, quietly, with no diagnostic
167
+ // anywhere — a justification that covered the surface it named and not the
168
+ // one that mattered.
169
+ //
170
+ // So the set is exactly what the drawn rows need, and nothing beyond it:
171
+ //
172
+ // - a member of a drawn slot, or
173
+ // - an exclusion's key, since an exclusion is a position too and the viewer
174
+ // drops one whose key it cannot find, or
175
+ // - an endpoint of a kept edge, so a badge pointing out of the window still
176
+ // resolves rather than being dropped with a diagnostic.
177
+ //
178
+ // Every issue that survives is therefore placed, excluded, or on an edge —
179
+ // so windowing adds nothing to `isolated` and the footer goes back to
180
+ // describing the document rather than the scroll position.
181
+ const keep = new Set([
182
+ ...drawnMember,
183
+ ...input.order.excluded.map((exclusion) => exclusion.key),
184
+ ...edges.flatMap((edge) => [edge.from, edge.to]),
185
+ ]);
186
+ // EXCLUSIONS ARE CARRIED WHOLE, and that is a stated bound rather than an
187
+ // oversight. An excluded issue is one the order deliberately never works, so
188
+ // it holds no rank — and the window slices the SEQUENCE, which exclusions are
189
+ // not part of. Windowing them would need a second window with its own
190
+ // coordinate, for a population that is duplicates and is small by
191
+ // construction. A host that needs them bounded filters before calling.
192
+ const document = {
193
+ issues: input.issues.filter((issue) => keep.has(issue.key)),
194
+ edges,
195
+ order: { slots: rows, excluded: input.order.excluded },
196
+ };
197
+ // BY IDENTITY, so a document carrying the same connector twice reports one
198
+ // drop rather than two — and counted over the edges that did NOT survive,
199
+ // which is one pass rather than two set builds and a subtraction.
200
+ const undrawn = new Set(input.edges
201
+ .filter((edge) => edge.field === 'together-with' && !drawable(edge))
202
+ .map((edge) => edgeIdentity(edge.field, edge.from, edge.to))).size;
203
+ return {
204
+ total,
205
+ start,
206
+ count: rows.length,
207
+ before: start,
208
+ after: total - start - rows.length,
209
+ rows,
210
+ document,
211
+ undrawn,
212
+ addressOf: (rank) => byRank.get(rank),
213
+ offsetOf: (key) => offsetByKey.get(key),
214
+ };
215
+ }
216
+ //# sourceMappingURL=rail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/workspace/rail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The three-zone workspace: the package's top-level surface.
3
+ *
4
+ * Rail on the left, canvas in the centre, inspector on the right, with the
5
+ * ambient audit count in the header. Every zone is COMPOSED from the leaf that
6
+ * already owns it — this file assembles, it does not re-derive.
7
+ *
8
+ * | zone | comes from |
9
+ * |-----------|-------------------------------------------------------|
10
+ * | header | `auditOverlay` + `renderAuditHeader` |
11
+ * | rail | `renderViewer(…, { projection: 'linear' })`, windowed |
12
+ * | canvas | `renderScaleLadder` |
13
+ * | inspector | `inspectorView`, rendered here |
14
+ *
15
+ * ## The audit left-bar is applied to the rail's SPEC, never to its markup
16
+ *
17
+ * The rail's rows are layer 1's, and this package's standing rule is that
18
+ * nothing splices a rendered string — that is the hand-rolled escaping surface
19
+ * the spec grammar exists to remove, and `reevaluate/render.ts` records the
20
+ * same decision when it nests `scene.root` rather than concatenating chips into
21
+ * the rail.
22
+ *
23
+ * `scene.root` is DATA, and `KEY_ATTRIBUTE` is published, so {@link markRail}
24
+ * walks the spec tree and adds `data-ig-audit` to the keyed rows. That is a
25
+ * pure transform over a public value rather than a reach past a surface, and it
26
+ * means no attribute in this file is ever escaped by anything but `renderMarkup`.
27
+ *
28
+ * ## A row's severity is the heaviest across its MEMBERS
29
+ *
30
+ * A `together-with` unit is one row and several refs, and a finding can name a
31
+ * member that does not lead. Read off the lead alone, an affected unit renders
32
+ * clean — which is the audit failing silently on exactly the rows where an
33
+ * encoding error is hardest to see.
34
+ *
35
+ * ## The zone frames, and the one place a tag is written here
36
+ *
37
+ * Two of the four zones hand back a STRING rather than a spec — the ladder's
38
+ * chrome and the audit header — so the frames are assembled by concatenation,
39
+ * as `renderScaleLadder` already assembles its own canvas and chrome. What
40
+ * makes that safe is not care: {@link ZONES} is a closed union of literals, so
41
+ * no caller value can reach an attribute here at all, and `zones.test.ts` pins
42
+ * it. Everything with a dynamic value in it goes through `renderMarkup`.
43
+ *
44
+ * ## Dark only
45
+ *
46
+ * The pass-2 brief carries "light + dark" over from pass 1; light was cut after
47
+ * that pass, and dark-only is the decision rather than an omission. There is no
48
+ * forked token set here and no second palette: the theme is the viewer's, and
49
+ * `styles.ts` writes structure against its custom properties.
50
+ */
51
+ import { type Theme, type ViewerDocument } from '@issuegraph/viewer';
52
+ import type { AuditInput } from '../audit/findings.ts';
53
+ import { type AuditOverlay } from '../audit/surface.ts';
54
+ import { type ScaleState } from '../scale/commands.ts';
55
+ import { type InspectorView } from './inspector.ts';
56
+ import { type RailWindow, type RailWindowOptions } from './rail.ts';
57
+ import { type WorkspaceSelection } from './selection.ts';
58
+ /** The four fixed positions. A closed union, which is what keeps `zone` safe. */
59
+ export declare const ZONES: readonly ["header", "rail", "canvas", "inspector"];
60
+ export type Zone = (typeof ZONES)[number];
61
+ export interface WorkspaceWords {
62
+ /** Shown in the inspector when nothing is selected. */
63
+ readonly nothingSelected: string;
64
+ /**
65
+ * The control that returns to nothing selected.
66
+ *
67
+ * NAMED FOR WHAT IT DOES, after a round of review found the previous name
68
+ * inviting a host to write the wrong sentence: called `clearFilter`, it read
69
+ * as "widen the list back", and the fixture here duly labelled it "show every
70
+ * relationship" — a button that emptied the panel it promised to fill. The
71
+ * command it publishes is `clear`, and that is the whole of its behaviour.
72
+ */
73
+ readonly clearSelection: string;
74
+ /** Names the relationships list. */
75
+ readonly relationships: string;
76
+ }
77
+ export interface WorkspaceOptions {
78
+ /**
79
+ * The words. Required, for the reason `ChangeWords` gives: this package does
80
+ * not invent an English sentence, and a default would be one.
81
+ */
82
+ readonly words: WorkspaceWords;
83
+ readonly selection?: WorkspaceSelection | undefined;
84
+ /** The ladder's reader position — search, focus, opened isolates. */
85
+ readonly scale?: ScaleState | undefined;
86
+ /** Which slice of the order the rail draws. See {@link railWindow}. */
87
+ readonly rail?: RailWindowOptions | undefined;
88
+ /**
89
+ * The audit's input, when the host has one.
90
+ *
91
+ * ABSENT MEANS "NOT RUN", NOT "CLEAN", and the two render differently: with
92
+ * no input the header is left out entirely rather than drawn at zero, because
93
+ * a zero the reader can trust and a zero nobody computed are different facts.
94
+ */
95
+ readonly audit?: AuditInput | undefined;
96
+ /**
97
+ * Whether the audit filter is narrowing the rail to affected rows.
98
+ *
99
+ * THE HEADER PUBLISHES THE TOGGLE, SO SOMETHING HAS TO HOLD ITS STATE.
100
+ * `renderAuditHeader` draws a `button` with `aria-pressed`, and without this
101
+ * option every render answered `false` and left the rail unnarrowed — a
102
+ * control that could not complete the action it advertised, which is the
103
+ * finding the scale ladder already records paying for once. The ladder's note
104
+ * is also the resolution: layer 2 CAN narrow, and the assembling surface is
105
+ * the layer that holds the state to narrow with.
106
+ *
107
+ * Ignored with no audit, because there is nothing to filter by.
108
+ */
109
+ readonly auditFiltered?: boolean | undefined;
110
+ readonly theme?: Theme | undefined;
111
+ /** The selector the theme's custom properties are written onto. */
112
+ readonly themeSelector?: string | undefined;
113
+ }
114
+ export interface WorkspaceView {
115
+ readonly selection: WorkspaceSelection;
116
+ readonly rail: RailWindow;
117
+ readonly inspector: InspectorView;
118
+ /** `null` when no audit input was supplied — see {@link WorkspaceOptions.audit}. */
119
+ readonly audit: AuditOverlay | null;
120
+ /**
121
+ * Whether the rail was narrowed to affected rows.
122
+ *
123
+ * DERIVED, not echoed: it is `auditFiltered` AND an audit to filter by, so a
124
+ * caller reading this is reading what actually happened rather than what was
125
+ * asked for.
126
+ */
127
+ readonly auditFiltered: boolean;
128
+ }
129
+ export interface WorkspaceResult {
130
+ readonly view: WorkspaceView;
131
+ /** The whole surface: the four zones inside one root. */
132
+ readonly markup: string;
133
+ /** Every stylesheet this surface needs, in install order. */
134
+ readonly styles: string;
135
+ readonly diagnostics: readonly string[];
136
+ }
137
+ /** Render one document at one reader position, as the whole workspace. */
138
+ export declare function renderWorkspace(input: ViewerDocument, options: WorkspaceOptions): WorkspaceResult;
139
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/workspace/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,EAIL,KAAK,KAAK,EACV,KAAK,cAAc,EASpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EAEL,KAAK,YAAY,EAKlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,UAAU,EAAuB,MAAM,sBAAsB,CAAC;AAI5E,OAAO,EAA8B,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAC/F,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAc,MAAM,WAAW,CAAC;AAChF,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,gBAAgB,CAAC;AAGxB,iFAAiF;AACjF,eAAO,MAAM,KAAK,oDAAoE,CAAC;AAEvF,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACpD,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACxC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC9C;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AA4MD,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,gBAAgB,GACxB,eAAe,CA6JjB"}