@peripheries/renderer 0.17.0 → 0.19.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 (46) hide show
  1. package/README.md +1 -0
  2. package/dist/catalog.js +2 -2
  3. package/dist/chunks/{projection-BTt_LUza.js → projection-CNtMyJ-p.js} +2 -2
  4. package/dist/chunks/projection-CNtMyJ-p.js.map +1 -0
  5. package/dist/chunks/{query-DRdMhtcX.js → query-BPuCJC1C.js} +109 -21
  6. package/dist/chunks/query-BPuCJC1C.js.map +1 -0
  7. package/dist/chunks/{structure-BPBwFPO0.js → structure-o9pYoDjD.js} +29 -3
  8. package/dist/chunks/{structure-BPBwFPO0.js.map → structure-o9pYoDjD.js.map} +1 -1
  9. package/dist/chunks/{validate-XuJViQas.js → validate-LRaUUXIm.js} +3 -3
  10. package/dist/chunks/{validate-XuJViQas.js.map → validate-LRaUUXIm.js.map} +1 -1
  11. package/dist/core.js +130 -17
  12. package/dist/core.js.map +1 -1
  13. package/dist/print.js +1391 -0
  14. package/dist/print.js.map +1 -0
  15. package/dist/react.js +21 -9
  16. package/dist/react.js.map +1 -1
  17. package/dist/runtime.js +5 -4
  18. package/dist/runtime.js.map +1 -1
  19. package/dist/testing.js +1 -1
  20. package/dist/types/core/cell-props.d.ts +3 -1
  21. package/dist/types/core/data-context.d.ts +4 -0
  22. package/dist/types/core/index.d.ts +2 -1
  23. package/dist/types/core/load-page.d.ts +15 -1
  24. package/dist/types/core/references.d.ts +8 -2
  25. package/dist/types/core/sources.d.ts +97 -0
  26. package/dist/types/core/structure.d.ts +16 -0
  27. package/dist/types/core/types.d.ts +34 -0
  28. package/dist/types/print/document-bindings.d.ts +76 -0
  29. package/dist/types/print/document-profile.d.ts +203 -0
  30. package/dist/types/print/index.d.ts +11 -0
  31. package/dist/types/print/map-staleness.d.ts +75 -0
  32. package/dist/types/print/page-map.d.ts +427 -0
  33. package/dist/types/print/paginate.d.ts +231 -0
  34. package/dist/types/print/print-layout.d.ts +212 -0
  35. package/dist/types/print/print-lint.d.ts +124 -0
  36. package/dist/types/print/print-policy.d.ts +210 -0
  37. package/dist/types/print/print-stylesheet.d.ts +168 -0
  38. package/dist/types/print/running-header.d.ts +105 -0
  39. package/dist/types/print/structure-print.d.ts +302 -0
  40. package/dist/types/react/cell-view.d.ts +3 -0
  41. package/dist/types/react/page-renderer.d.ts +6 -0
  42. package/dist/types/runtime/install.d.ts +9 -3
  43. package/dist/types/testing/components.d.ts +1 -1
  44. package/package.json +6 -2
  45. package/dist/chunks/projection-BTt_LUza.js.map +0 -1
  46. package/dist/chunks/query-DRdMhtcX.js.map +0 -1
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The host's read of a bundle's `documentProfiles`.
3
+ *
4
+ * ── Why the host reads this at all ──────────────────────────────────────────
5
+ *
6
+ * A `DocumentProfile` is authored in the component repo (an org's document
7
+ * bundle owns its profile module) and describes everything two document styles
8
+ * DISAGREE about — sheet, margins, measure, ink, rules, type scale, depth.
9
+ * Most of those are the bundle's business and the host never looks at them.
10
+ *
11
+ * Two are not. `depth.max` and `depth.numbering` are needed **before anything
12
+ * renders**, because the outline is derived from `layout.structure` rather than
13
+ * discovered from the rendered tree (a nav slot is a sibling region, so React
14
+ * context cannot reach it). A value the host needs pre-render cannot arrive
15
+ * through a component prop, so it is lifted off the bundle barrel — exactly the
16
+ * way `ThemeProvider` already is (`loadRemote.ts`).
17
+ *
18
+ * ── Why a resolved map and not `resolveProfile` ─────────────────────────────
19
+ *
20
+ * The bundle exports a plain frozen object with `extends` already applied, so
21
+ * reading configuration never means executing bundle logic. The cost of that
22
+ * trade is that a plain map has no behaviour to throw with — the bundle's
23
+ * `resolveProfile` threw on an unknown id precisely to stop a document
24
+ * rendering in the wrong system. **That behaviour moves here, because the shape
25
+ * we asked for moved it here**.
26
+ *
27
+ * ── The three states, and why the third is not "absent" ─────────────────────
28
+ *
29
+ * documentProfiles absent -> host defaults, debug note. Benign:
30
+ * bundles predating the contract.
31
+ * present, id found -> use it.
32
+ * present, id MISSING -> console.warn naming the id AND the
33
+ * ids that ARE present, then defaults.
34
+ *
35
+ * The third case is the one worth the code. Folding it into "absent" makes a
36
+ * page declaring `profile: 'instrument'` against a workbook-only bundle render
37
+ * with a workbook's geometry, orange accents and unbounded depth — which looks
38
+ * like a document rather than like an error. Naming what IS present is what
39
+ * sends the reader to the bundle instead of to the page config.
40
+ */
41
+ import type { LoadedBundle } from '../core/bundles/load.js';
42
+ /**
43
+ * What the resolver reads off a bundle: its `documentProfiles`, and a name for
44
+ * the warnings. The renderer's `LoadedBundle` satisfies it (named by `label`);
45
+ * `key` is the legacy host's name, kept so a caller with only a key still works.
46
+ */
47
+ export type ProfileBundle = Pick<LoadedBundle, 'documentProfiles'> & {
48
+ label?: string;
49
+ key?: string;
50
+ };
51
+ export type ProfileId = string;
52
+ export type DocumentInkRole = 'full' | 'muted' | 'subtle' | 'border' | 'accent' | 'accentText' | 'sunken';
53
+ export type DocumentRuleWeight = 'structural' | 'hairline' | 'head' | 'row';
54
+ export interface DocumentPageRegionSlots {
55
+ left?: string;
56
+ centre?: string;
57
+ right?: string;
58
+ }
59
+ /**
60
+ * Named for CSS `@page` margin boxes. British spelling in, CSS spelling out:
61
+ * `centre` maps to `@bottom-center` at the one place both spellings appear
62
+ * (stage 5's print stylesheet).
63
+ */
64
+ export interface DocumentPageRegions {
65
+ 'page-header'?: DocumentPageRegionSlots;
66
+ 'page-footer'?: DocumentPageRegionSlots;
67
+ }
68
+ /**
69
+ * The host's structural view of a profile.
70
+ *
71
+ * Deliberately NOT a copy of the bundle's interface. Every field the host does not
72
+ * read is optional here, because the host has no business rejecting a profile
73
+ * over a field it never looks at — and a stricter type would turn each new
74
+ * authoring field into a host release. The fields the host DOES read carry
75
+ * their own runtime guards below; the type is a convenience, not the check.
76
+ */
77
+ export interface DocumentProfile {
78
+ id: ProfileId;
79
+ extends?: ProfileId;
80
+ label?: string;
81
+ sheet?: {
82
+ width: string;
83
+ height: string;
84
+ };
85
+ margins?: {
86
+ top: string;
87
+ right: string;
88
+ bottom: string;
89
+ left: string;
90
+ };
91
+ measure?: string;
92
+ bodyMeasureCh?: number;
93
+ /**
94
+ * The marginalia column, on paper.
95
+ *
96
+ * **Not `layout.shell.regions.rail`**, which is the chat panel. Two different
97
+ * things one word apart, both live in this system's vocabulary, and a reader
98
+ * who conflates them concludes the rail is already handled.
99
+ *
100
+ * `placement: 'column'` establishes a real second column that the PAGE owns —
101
+ * the bundle's `MetaRail` header is explicit that *"it is not something MetaRail
102
+ * does to itself"*. `'strip'` and `'none'` establish nothing, which is why
103
+ * `workbook` and `report` are unaffected by the column geometry.
104
+ *
105
+ * `gutter` was shipped by the bundle and absent from this interface until
106
+ * the column geometry landed, so it was dropped on read — the declared-and-unread shape again,
107
+ * in the type rather than in the code.
108
+ */
109
+ rail?: {
110
+ placement: 'strip' | 'column' | 'none';
111
+ /** Where the column starts, measured from the text block's left edge. */
112
+ axis?: string;
113
+ width?: string;
114
+ /** Space between the measure and the column. */
115
+ gutter?: string;
116
+ };
117
+ ink?: Partial<Record<DocumentInkRole, string>>;
118
+ /** `Partial` is load-bearing: an undefined weight is a PROHIBITION, not a gap. */
119
+ rules?: Partial<Record<DocumentRuleWeight, string>>;
120
+ type?: Record<string, unknown>;
121
+ floorPx?: number;
122
+ depth?: {
123
+ max?: number;
124
+ indent?: string[];
125
+ numbering?: 'none' | 'ladder';
126
+ };
127
+ fills?: boolean;
128
+ regions?: DocumentPageRegions;
129
+ /**
130
+ * `known` absent and `known: []` are DIFFERENT and must stay so. Empty means
131
+ * "no sectionId is valid"; absent means "not enumerable in advance" — which is
132
+ * the instrument profile's deliberate state, because articles are generated
133
+ * from the instrument rather than drawn from a fixed skeleton. Any check that
134
+ * compares against `known` must guard the absent branch or it fails every
135
+ * instrument document at once.
136
+ */
137
+ sections?: {
138
+ required?: string[];
139
+ known?: string[];
140
+ };
141
+ /**
142
+ * Region bindings the document MUST NOT be produced without.
143
+ *
144
+ * `instrument` declares `['execution-state']`. The reasoning is
145
+ * domain, not mechanism, and it is the one place the host's general rule is
146
+ * the wrong rule:
147
+ *
148
+ * Every other missing binding produces a document that is INCOMPLETE.
149
+ * This one produces a document that is WRONG ABOUT ITS OWN LEGAL STATUS.
150
+ *
151
+ * A draft will that silently loses *"Not for execution"* is, on paper, a will
152
+ * that does not say it is a draft. So an omitted margin box — correct
153
+ * everywhere else, because a blank box is indistinguishable from a right one —
154
+ * fails toward the dangerous side here. `buildPageStylesheet` refuses to
155
+ * produce the stylesheet at all rather than produce one that misrepresents the
156
+ * document.
157
+ *
158
+ * A **paginator** binding must never appear here. The host cannot supply
159
+ * `page-x-of-y` — CSS computes it during pagination — so requiring one would
160
+ * be a permanent, unsatisfiable failure.
161
+ *
162
+ * Open `string[]`, not a closed union, for the same reason `ProfileId` is
163
+ * open: the vocabulary is authored in the component repo, and a binding a bundle
164
+ * adds must not be a host type error before it is a design question.
165
+ */
166
+ requiredBindings?: string[];
167
+ }
168
+ export type DocumentProfileMap = Readonly<Record<ProfileId, DocumentProfile>>;
169
+ /**
170
+ * Where the resolved profile came from. Callers that render a diagnostic want
171
+ * to distinguish "the bundle said so" from "nobody said anything".
172
+ */
173
+ export type DocumentProfileSource = 'bundle' | 'defaults';
174
+ export interface ResolvedDocumentProfile {
175
+ /** `null` when no profile was found. Callers fall back to their own defaults. */
176
+ profile: DocumentProfile | null;
177
+ source: DocumentProfileSource;
178
+ /**
179
+ * `profile.depth.max` when it is a usable bound, else `undefined`.
180
+ *
181
+ * `undefined` means UNBOUNDED, and that is a real answer rather than a
182
+ * missing one — the 191 flat `grid` pages have no depth to bound and the
183
+ * workbook profile's skeleton is flat. `deriveTableOfContents` and
184
+ * `validateStructure` both treat `maxDepth: undefined` as "do not check".
185
+ */
186
+ maxDepth: number | undefined;
187
+ /** `profile.depth.numbering`, defaulting to `'ladder'`. */
188
+ numbering: 'none' | 'ladder';
189
+ }
190
+ /** Test seam. Nothing in the app should need this. */
191
+ export declare function __clearDocumentProfileWarnings(): void;
192
+ /**
193
+ * Resolve `profileId` against an ordered bundle cascade.
194
+ *
195
+ * Order matches the cell cascade — earlier registries win — so a page-level
196
+ * `bundle_refs` entry overrides the org default for the profile the same way it
197
+ * does for a component. A page that declares no profile gets host defaults
198
+ * without a word: that is the 191-flat-page case and it is not a fault.
199
+ *
200
+ * @param registries Ordered, highest priority first. Loading? Pass `[]`.
201
+ * @param profileId From `layout.profile`. `undefined` when the page declares none.
202
+ */
203
+ export declare function resolveDocumentProfile(registries: readonly ProfileBundle[], profileId: ProfileId | undefined): ResolvedDocumentProfile;
@@ -0,0 +1,11 @@
1
+ export * from './document-profile.js';
2
+ export * from './document-bindings.js';
3
+ export * from './page-map.js';
4
+ export * from './paginate.js';
5
+ export * from './print-layout.js';
6
+ export * from './map-staleness.js';
7
+ export * from './print-lint.js';
8
+ export * from './print-policy.js';
9
+ export * from './print-stylesheet.js';
10
+ export * from './running-header.js';
11
+ export * from './structure-print.js';
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Is the frozen page map still describing the document that is on the page?
3
+ *
4
+ * ── Why this needs to be asked at all ───────────────────────────────────────
5
+ *
6
+ * `DocPrintContent` measures the document once, assigns its rows to pages, and
7
+ * freezes — because typesetting changes the document it measured, so measuring
8
+ * the already-typeset document gives a different and wrong answer.
9
+ *
10
+ * Freezing needs the document to have finished rendering, and nothing can be
11
+ * sure it has. The settle gate waits for the height to stop changing, and a
12
+ * document that is halfway through drawing its charts STALLS: the height climbs
13
+ * 4,472 → 10,283 → 11,083px in under a second on the proof document, with pauses.
14
+ * Reads taken during a pause are indistinguishable from a finished document by
15
+ * arithmetic. Observed twice: a **6-page map for a 15-page document**, and a
16
+ * **7-page map for a 10,613px document** — and a reload of the same URL giving
17
+ * 15 both times. Same code, two answers, nothing in the output saying which.
18
+ *
19
+ * A prediction cannot be made reliable by tightening it. So the map is checked
20
+ * against the document afterwards, and a stale map fails by thousands of pixels
21
+ * rather than by a rounding error.
22
+ *
23
+ * ── Why the arithmetic lives here and not in the component ─────────────────
24
+ *
25
+ * Because it is the part that was wrong, twice, and both times it FAILED SILENT
26
+ * — a check that cannot conclude looks exactly like a check that passes:
27
+ *
28
+ * 1. It ignored the scaling the typesetting applies, so every document with an
29
+ * over-tall row looked stale by that row's shrink.
30
+ * 2. It ignored the on-screen page separators — margin and padding, ~66px per
31
+ * page start, ~792px over 15 pages. That condemned a correct map, unfroze,
32
+ * measured the now-shorter document, froze, and condemned it again:
33
+ * an oscillation between 10,283 and 11,083px that sometimes ended with the
34
+ * document not typeset at all. A check that creates the instability it was
35
+ * added to detect.
36
+ *
37
+ * Both are arithmetic about heights, testable with numbers, and were shipped
38
+ * un-unit-tested because they lived inside an effect that needs a browser.
39
+ */
40
+ import type { PrintLayout } from './print-layout.js';
41
+ /**
42
+ * The container height to expect once a layout has been applied.
43
+ *
44
+ * Forced page breaks change nothing on screen, so only two things move:
45
+ *
46
+ * - **Scaling shortens** an over-tall row: its box goes from `heightPx` to
47
+ * `scaledHeightPx`. Subtract the difference.
48
+ * - **Screen separators lengthen** the document: they are margin, border and
49
+ * padding on each page's first row. Add `screenExtraPx`, which the layout
50
+ * reports rather than this function re-deriving from the CSS text.
51
+ *
52
+ * @param measuredContainerPx The container height the map was measured from —
53
+ * `reconciliation.containerPx`, not a fresh read.
54
+ */
55
+ export declare function expectedHeightAfterTypesetting(measuredContainerPx: number, layout: Pick<PrintLayout, 'scaled' | 'screenExtraPx'>): number;
56
+ export interface Staleness {
57
+ stale: boolean;
58
+ /** Live height minus expected. Positive means the document grew. */
59
+ deltaPx: number;
60
+ tolerancePx: number;
61
+ }
62
+ /**
63
+ * Half a page of slack.
64
+ *
65
+ * Chosen to sit in the wide gap between the two populations rather than tuned:
66
+ * the things that legitimately move a settled document — a late font, sub-pixel
67
+ * rounding, a border — are tens of pixels, and the observed stale maps were
68
+ * 4,200 and 5,600px adrift. Anything in that range separates them.
69
+ *
70
+ * A floor of 120px, because a document on a very short page would otherwise get
71
+ * a tolerance smaller than one row.
72
+ */
73
+ export declare function stalenessTolerancePx(textBlockPx: number): number;
74
+ /** Compare the document on the page against the map that was frozen for it. */
75
+ export declare function mapStaleness(liveHeightPx: number, expectedPx: number, textBlockPx: number): Staleness;