@peripheries/renderer 0.23.0 → 0.24.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.
- package/README.md +62 -34
- package/dist/catalog.js +2 -6
- package/dist/chunks/{forms-CCAE7sCu.js → forms-DxA9WzCt.js} +0 -6
- package/dist/chunks/{load-CjPvISzH.js → load-b38vUiZM.js} +0 -4
- package/dist/chunks/{page-bundles-DLRIBP8g.js → page-bundles-CPj_vx-J.js} +1 -5
- package/dist/chunks/{persistence-DCMakM9H.js → persistence-hurkUUQ0.js} +0 -6
- package/dist/chunks/{projection-yvYKzMOS.js → projection-zhu32_KJ.js} +1 -15
- package/dist/chunks/{query-LnTbQghF.js → query-D6xQbTFK.js} +0 -6
- package/dist/chunks/{refs-BLUokra5.js → refs-Csgh6rXq.js} +0 -4
- package/dist/chunks/{structure-BrKSgzRJ.js → structure-CKBtQHJq.js} +1 -7
- package/dist/chunks/{validate-B_g7719l.js → validate-BcB_PA1k.js} +2 -10
- package/dist/core.js +26 -26
- package/dist/forms.js +1 -1
- package/dist/print.js +0 -24
- package/dist/react.js +9 -27
- package/dist/runtime.js +2 -12
- package/dist/styles.css +9 -15
- package/dist/testing.js +5 -21
- package/dist/theme.js +0 -10
- package/dist/types/catalog/check.d.ts +0 -13
- package/dist/types/core/actions.d.ts +0 -24
- package/dist/types/core/bundles/load.d.ts +0 -23
- package/dist/types/core/bundles/page-bundles.d.ts +0 -9
- package/dist/types/core/bundles/refs.d.ts +1 -28
- package/dist/types/core/bundles/resolve.d.ts +0 -10
- package/dist/types/core/cell-props.d.ts +0 -20
- package/dist/types/core/cells.d.ts +0 -21
- package/dist/types/core/data-context.d.ts +0 -22
- package/dist/types/core/diagnostics.d.ts +1 -3
- package/dist/types/core/graph.d.ts +0 -54
- package/dist/types/core/index.d.ts +1 -0
- package/dist/types/core/layers.d.ts +8 -0
- package/dist/types/core/layers.gen.d.ts +12 -0
- package/dist/types/core/layout.d.ts +0 -9
- package/dist/types/core/load-page.d.ts +0 -111
- package/dist/types/core/query.d.ts +0 -64
- package/dist/types/core/references.d.ts +0 -21
- package/dist/types/core/release.d.ts +0 -10
- package/dist/types/core/rules.d.ts +0 -1
- package/dist/types/core/selected.d.ts +0 -5
- package/dist/types/core/sources.d.ts +5 -64
- package/dist/types/core/store/create-store.d.ts +0 -6
- package/dist/types/core/store/persistence.d.ts +0 -1
- package/dist/types/core/store/projection.d.ts +0 -5
- package/dist/types/core/store/scope-keys.d.ts +0 -1
- package/dist/types/core/store/types.d.ts +0 -6
- package/dist/types/core/structure.d.ts +0 -33
- package/dist/types/core/types.d.ts +0 -13
- package/dist/types/core/validate.d.ts +0 -12
- package/dist/types/core/values.d.ts +0 -6
- package/dist/types/forms/conditions.d.ts +0 -37
- package/dist/types/forms/index.d.ts +1 -0
- package/dist/types/forms/model.d.ts +4 -48
- package/dist/types/forms/port.d.ts +0 -22
- package/dist/types/forms/section.d.ts +22 -0
- package/dist/types/print/document-bindings.d.ts +0 -61
- package/dist/types/print/document-profile.d.ts +0 -137
- package/dist/types/print/map-staleness.d.ts +0 -66
- package/dist/types/print/page-map.d.ts +1 -334
- package/dist/types/print/paginate.d.ts +0 -190
- package/dist/types/print/print-layout.d.ts +0 -161
- package/dist/types/print/print-lint.d.ts +0 -109
- package/dist/types/print/print-policy.d.ts +0 -184
- package/dist/types/print/print-stylesheet.d.ts +0 -135
- package/dist/types/print/running-header.d.ts +0 -86
- package/dist/types/print/structure-print.d.ts +1 -243
- package/dist/types/react/cell-states.d.ts +0 -2
- package/dist/types/react/cell-view.d.ts +0 -16
- package/dist/types/react/page-renderer.d.ts +0 -37
- package/dist/types/react/structure-view.d.ts +0 -12
- package/dist/types/react/use-cell-store.d.ts +0 -5
- package/dist/types/react/use-container-size.d.ts +0 -5
- package/dist/types/react/use-forms-runtime.d.ts +2 -55
- package/dist/types/react/use-paging.d.ts +0 -9
- package/dist/types/runtime/boot.d.ts +0 -11
- package/dist/types/runtime/install.d.ts +0 -15
- package/dist/types/runtime/shims.d.ts +0 -8
- package/dist/types/runtime/store-export.d.ts +0 -7
- package/dist/types/testing/base-props.d.ts +0 -1
- package/dist/types/testing/components.d.ts +0 -8
- package/dist/types/testing/form-workbook.d.ts +0 -3
- package/dist/types/testing/golden.d.ts +0 -16
- package/dist/types/testing/ids.d.ts +0 -2
- package/dist/types/testing/sources.d.ts +0 -7
- package/dist/types/theme/check.d.ts +0 -2
- package/dist/types/theme/color.d.ts +0 -12
- package/dist/types/theme/derive.d.ts +0 -12
- package/dist/types/theme/parse.d.ts +0 -7
- package/package.json +2 -2
- package/dist/catalog.js.map +0 -1
- package/dist/chunks/forms-CCAE7sCu.js.map +0 -1
- package/dist/chunks/load-CjPvISzH.js.map +0 -1
- package/dist/chunks/page-bundles-DLRIBP8g.js.map +0 -1
- package/dist/chunks/persistence-DCMakM9H.js.map +0 -1
- package/dist/chunks/projection-yvYKzMOS.js.map +0 -1
- package/dist/chunks/query-LnTbQghF.js.map +0 -1
- package/dist/chunks/refs-BLUokra5.js.map +0 -1
- package/dist/chunks/structure-BrKSgzRJ.js.map +0 -1
- package/dist/chunks/validate-B_g7719l.js.map +0 -1
- package/dist/core.js.map +0 -1
- package/dist/print.js.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/testing.js.map +0 -1
- package/dist/theme.js.map +0 -1
|
@@ -1,43 +1,27 @@
|
|
|
1
1
|
import { type Condition } from './conditions.js';
|
|
2
|
-
/**
|
|
3
|
-
* A form as the forms runtime reads it (forms-as-pages.md §4.2–§4.3): steps, an overlay on the
|
|
4
|
-
* page's flat cells, each holding items (cells) and the questions they ask. Where it comes from
|
|
5
|
-
* is an adapter's business: a page's `layout.steps` (`fromPageSteps`) or, until forms are pages
|
|
6
|
-
* (SCH M3b), the legacy definition (the host's adapter). Pure: no React, no network.
|
|
7
|
-
*/
|
|
8
2
|
export type FormQuestion = {
|
|
9
|
-
/** The answer's key: what conditions, progress and the port name. */
|
|
10
3
|
key: string;
|
|
11
4
|
label: string;
|
|
12
5
|
required: boolean;
|
|
13
|
-
/** Enter may move on from it (forms-as-pages.md §4.3): a one-line question. */
|
|
14
6
|
singleLine: boolean;
|
|
15
7
|
when?: Condition | null;
|
|
16
|
-
/** The question node an answer is saved against (answer_save's p_question_node), when the form is a page. */
|
|
17
8
|
node?: string;
|
|
18
|
-
/** How to ask it, from the question node's state: what a cell renders its control from. */
|
|
19
9
|
definition?: FormQuestionDefinition;
|
|
20
10
|
};
|
|
21
|
-
/**
|
|
22
|
-
* A question's definition as its node's state holds it (forms-as-pages.md §4.2): the control's
|
|
23
|
-
* type, its options, validation and help. The runtime passes it through to the cell untouched.
|
|
24
|
-
*/
|
|
25
11
|
export type FormQuestionDefinition = {
|
|
26
12
|
type: string;
|
|
27
|
-
options?: unknown[];
|
|
28
|
-
validation?: Record<string, unknown
|
|
29
|
-
description?: string;
|
|
30
|
-
helpText?: string;
|
|
13
|
+
options?: unknown[] | null;
|
|
14
|
+
validation?: Record<string, unknown> | null;
|
|
15
|
+
description?: string | null;
|
|
16
|
+
helpText?: string | null;
|
|
31
17
|
};
|
|
32
18
|
export type FormItemKind = 'question' | 'content' | 'document' | 'review' | 'upload' | 'other';
|
|
33
|
-
/** One cell of a step: its questions, if any. */
|
|
34
19
|
export type FormItem = {
|
|
35
20
|
id: string;
|
|
36
21
|
kind: FormItemKind;
|
|
37
22
|
questions: FormQuestion[];
|
|
38
23
|
when?: Condition | null;
|
|
39
24
|
};
|
|
40
|
-
/** `label`: an authored word for the step shown in place of its derived kind's (advisor review, status); the kind stays derived. */
|
|
41
25
|
export type FormStep = {
|
|
42
26
|
id: string;
|
|
43
27
|
title: string;
|
|
@@ -54,50 +38,32 @@ export type FormModel = {
|
|
|
54
38
|
title: string;
|
|
55
39
|
chapters: FormChapter[];
|
|
56
40
|
steps: FormStep[];
|
|
57
|
-
/** On submit: `warn` lists what's missing and submits; `block` refuses until it's answered (owner, Q-F7; default warn). */
|
|
58
41
|
submitRequired: 'warn' | 'block';
|
|
59
|
-
/** Back is offered (the template's `allow_back`, default true). */
|
|
60
42
|
allowBack: boolean;
|
|
61
|
-
/** The form takes a submission (false for a legacy form, which has none); default true. */
|
|
62
43
|
submittable?: boolean;
|
|
63
44
|
};
|
|
64
|
-
/** A step's kind, derived from its cells (§4.2): for labels and progress. */
|
|
65
45
|
export type StepKind = 'answer' | 'read' | 'review' | 'upload' | 'submit';
|
|
66
|
-
/** Where a submission stands (the `submission` layer's status, schema-g2.md §2.7). */
|
|
67
46
|
export type SubmissionStatus = 'in_progress' | 'submitted' | 'changes_requested' | 'completed';
|
|
68
47
|
export type DisplayMode = 'step' | 'question';
|
|
69
|
-
/** Steps, cells and questions whose conditions hold for these answers; a step left with nothing visible is skipped. */
|
|
70
48
|
export declare function visibleSteps(model: FormModel, values: Record<string, unknown>): FormStep[];
|
|
71
49
|
export declare const questionsOf: (step: FormStep) => FormQuestion[];
|
|
72
50
|
export declare function stepKind(step: FormStep, isLast: boolean): StepKind;
|
|
73
|
-
/** Where a person is: a step, and in question mode one of its questions. */
|
|
74
51
|
export type Position = {
|
|
75
52
|
step: string;
|
|
76
53
|
question?: string;
|
|
77
54
|
};
|
|
78
|
-
/** The places Next and Back move through: a step each, or in question mode each question (a step with none counts once). */
|
|
79
55
|
export declare function positionsOf(steps: FormStep[], mode: DisplayMode): Position[];
|
|
80
|
-
/** A position in these steps: the same, else its step's first, else the first. */
|
|
81
56
|
export declare function settle(steps: FormStep[], mode: DisplayMode, at: Position | null): Position | null;
|
|
82
|
-
/** One move on, or back; null at either end. A move across a step boundary is a step change (it pushes history). */
|
|
83
57
|
export declare function move(steps: FormStep[], mode: DisplayMode, at: Position, by: 1 | -1): Position | null;
|
|
84
|
-
/** The contents click (§4.3): that step, in question mode at its first unanswered question, else its first. */
|
|
85
58
|
export declare function goTo(steps: FormStep[], mode: DisplayMode, stepId: string, values: Record<string, unknown>): Position | null;
|
|
86
|
-
/** A question's place (§4.3 question mode, `?question=`): its step, settled for the mode. Null when no visible step has it. */
|
|
87
59
|
export declare function goToQuestion(steps: FormStep[], mode: DisplayMode, key: string): Position | null;
|
|
88
|
-
/** Enter moves on (§4.3): a step with exactly one visible one-line question, or a one-line question in question mode. */
|
|
89
60
|
export declare function enterAdvances(step: FormStep, mode: DisplayMode, question?: string): boolean;
|
|
90
|
-
/** Required questions not yet answered, in order: in one step, or (no step) in the whole form. */
|
|
91
61
|
export declare function missing(steps: FormStep[], values: Record<string, unknown>, stepId?: string): FormQuestion[];
|
|
92
62
|
export type StepProgress = {
|
|
93
63
|
answered: number;
|
|
94
64
|
total: number;
|
|
95
65
|
complete: boolean;
|
|
96
66
|
};
|
|
97
|
-
/**
|
|
98
|
-
* Progress (§4.3): overall, required answered of required visible; per step, answered of visible
|
|
99
|
-
* questions, complete when all its required ones are answered. A read step counts once visited.
|
|
100
|
-
*/
|
|
101
67
|
export declare function progress(steps: FormStep[], values: Record<string, unknown>, visited: ReadonlySet<string>): {
|
|
102
68
|
answered: number;
|
|
103
69
|
required: number;
|
|
@@ -108,27 +74,17 @@ export declare function progress(steps: FormStep[], values: Record<string, unkno
|
|
|
108
74
|
};
|
|
109
75
|
perStep: Record<string, StepProgress>;
|
|
110
76
|
};
|
|
111
|
-
/** Answers can't change: submitted or completed (Edit answers reopens a submitted form). */
|
|
112
77
|
export declare const isReadOnlyStatus: (status: SubmissionStatus | null | undefined) => status is "submitted" | "completed";
|
|
113
|
-
/** Step mode on desktop, one question at a time on a phone (§4.3). */
|
|
114
78
|
export declare const defaultMode: (phone: boolean) => DisplayMode;
|
|
115
|
-
/**
|
|
116
|
-
* Steps from a page's overlay (§4.2): `layout.steps: [{ id, title, chapter?, cell_ids, when? }]`
|
|
117
|
-
* and `layout.chapters`, each cell's questions from `questionsOfCell` (the page knows its
|
|
118
|
-
* question cells' bindings). Cells the overlay doesn't name aren't in the form.
|
|
119
|
-
*/
|
|
120
79
|
export declare function fromPageSteps(page: {
|
|
121
80
|
name?: string | null;
|
|
122
81
|
layout?: Record<string, unknown> | null;
|
|
123
82
|
}, itemOf: (cellId: string) => FormItem | null): FormModel;
|
|
124
|
-
/** A deep link (§4.3, the URL contract `?mode=&page=§ion=&question=`): `section` a step id, `page` a chapter id, `question` a question key. */
|
|
125
83
|
export type FormLink = {
|
|
126
84
|
mode?: 'question';
|
|
127
85
|
page?: string;
|
|
128
86
|
section?: string;
|
|
129
87
|
question?: string;
|
|
130
88
|
};
|
|
131
|
-
/** Where a link lands: the step it names (and question), else its chapter's first step, else the first. */
|
|
132
89
|
export declare function positionFromLink(steps: FormStep[], mode: DisplayMode, link: FormLink): Position | null;
|
|
133
|
-
/** The link to a position, as an assistant reply carries it. */
|
|
134
90
|
export declare function linkTo(steps: FormStep[], mode: DisplayMode, at: Position): FormLink;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How the forms runtime saves (joint-releases.md §2.4): an injected port, so the runtime never
|
|
3
|
-
* names a table or a function. The legacy adapter (the host's, over form_answer_save) implements
|
|
4
|
-
* it in G2; answer_save, form_submit, form_visit and form_reopen (SCH M3b; schema-g2.md §2.7) in
|
|
5
|
-
* G3, with no renderer release. A refusal is a code, never a throw, so it's said in words like an edit's.
|
|
6
|
-
*/
|
|
7
1
|
import type { FileAnswer } from '@peripheries/runtime';
|
|
8
|
-
/** The codes a port answers with (schema-g2.md §2.7), and NETWORK when the call didn't arrive. */
|
|
9
2
|
export type FormsRefusal = 'CONFLICT' | 'ANSWER_TYPE' | 'ANSWER_OPTION' | 'ANSWER_REQUIRED' | 'ANSWER_FILE_PATH' | 'ANSWER_VALIDATION' | 'FORM_INCOMPLETE' | 'FORM_NOT_OPEN' | 'FORM_COMPLETED' | 'FORM_STEP_UNKNOWN' | 'NO_CAPABILITY' | 'NETWORK' | (string & {});
|
|
10
3
|
export type FormsResult<T extends object = object> = ({
|
|
11
4
|
ok: true;
|
|
@@ -15,11 +8,9 @@ export type FormsResult<T extends object = object> = ({
|
|
|
15
8
|
message?: string;
|
|
16
9
|
detail?: unknown;
|
|
17
10
|
};
|
|
18
|
-
/** A file to upload for an answer: any Blob with a name (a File in the browser). */
|
|
19
11
|
export type FileToUpload = Blob & {
|
|
20
12
|
readonly name: string;
|
|
21
13
|
};
|
|
22
|
-
/** One answer to save: its key, the question node when the form is a page, and the state it replaces. */
|
|
23
14
|
export type AnswerToSave = {
|
|
24
15
|
key: string;
|
|
25
16
|
node?: string;
|
|
@@ -27,31 +18,19 @@ export type AnswerToSave = {
|
|
|
27
18
|
expectedStateId?: string | null;
|
|
28
19
|
};
|
|
29
20
|
export interface FormsPort {
|
|
30
|
-
/** Save one answer; the state it wrote is what the next save of that key expects (CONFLICT otherwise). */
|
|
31
21
|
save(answer: AnswerToSave): Promise<FormsResult<{
|
|
32
22
|
stateId?: string | null;
|
|
33
23
|
}>>;
|
|
34
|
-
/** Submit: `missing` is what the runtime found unanswered (recorded under `warn`; `block` refuses FORM_INCOMPLETE). */
|
|
35
24
|
submit(input: {
|
|
36
25
|
missing: string[];
|
|
37
26
|
}): Promise<FormsResult<{
|
|
38
27
|
status: 'submitted';
|
|
39
28
|
missing: string[];
|
|
40
29
|
}>>;
|
|
41
|
-
/**
|
|
42
|
-
* Edit answers (forms-as-pages.md §4.3): a submitted form back to `in_progress`, recorded in the
|
|
43
|
-
* submission. Refused once staff have completed it (FORM_COMPLETED).
|
|
44
|
-
*/
|
|
45
30
|
reopen(): Promise<FormsResult<{
|
|
46
31
|
status: 'in_progress';
|
|
47
32
|
}>>;
|
|
48
|
-
/** A step was seen (`submission.visited_steps`): read steps count once visited. A repeat is a no-op. */
|
|
49
33
|
visit(stepId: string): Promise<FormsResult>;
|
|
50
|
-
/**
|
|
51
|
-
* Upload a file for a file question, as the viewer: the adapter stores it where answer_save
|
|
52
|
-
* accepts it and returns the answer to save (`{ v: 1, file }`); saving it is `save`'s. Absent
|
|
53
|
-
* where the host takes no files.
|
|
54
|
-
*/
|
|
55
34
|
upload?(input: {
|
|
56
35
|
key: string;
|
|
57
36
|
node?: string;
|
|
@@ -62,7 +41,6 @@ export interface FormsPort {
|
|
|
62
41
|
file: FileAnswer;
|
|
63
42
|
};
|
|
64
43
|
}>>;
|
|
65
|
-
/** A short-lived URL to open a saved file answer, as the viewer (storage decides who may). */
|
|
66
44
|
openFile?(file: FileAnswer): Promise<FormsResult<{
|
|
67
45
|
url: string;
|
|
68
46
|
}>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Condition } from './conditions.js';
|
|
2
|
+
import type { FormQuestionDefinition } from './model.js';
|
|
3
|
+
export type FormContentBlock = {
|
|
4
|
+
id: string;
|
|
5
|
+
type: string;
|
|
6
|
+
when?: Condition | null;
|
|
7
|
+
[prop: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type FormSection = {
|
|
10
|
+
id: string;
|
|
11
|
+
sectionId: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
sectionType?: string;
|
|
15
|
+
questions: Array<{
|
|
16
|
+
key: string;
|
|
17
|
+
label: string;
|
|
18
|
+
required: boolean;
|
|
19
|
+
definition: FormQuestionDefinition | null;
|
|
20
|
+
}>;
|
|
21
|
+
content: FormContentBlock[];
|
|
22
|
+
};
|
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Values for the region bindings a profile names.
|
|
3
|
-
*
|
|
4
|
-
* ── The gap this closes ─────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* A profile says WHICH margin box holds WHAT: `instrument`'s footer is
|
|
7
|
-
* `{ left: 'execution-state', right: 'page-x-of-y' }`. Only the second is
|
|
8
|
-
* computable — CSS produces page numbers during pagination. The rest are facts
|
|
9
|
-
* about *this document*, which the bundle cannot know and the host must supply.
|
|
10
|
-
*
|
|
11
|
-
* Until now it supplied none, so every `instrument` page was unprintable:
|
|
12
|
-
* `execution-state` is in `requiredBindings`, and a will that does not say
|
|
13
|
-
* "Not for execution" is a will that does not say it is a draft.
|
|
14
|
-
*
|
|
15
|
-
* ── Why the page declares them, keyed by binding name ───────────────────────
|
|
16
|
-
*
|
|
17
|
-
* `project_page.metadata.document.bindings` — a flat map from binding name to
|
|
18
|
-
* string. Keyed by the binding name itself rather than by some host-side
|
|
19
|
-
* camelCase alias, because a translation layer is one more place for
|
|
20
|
-
* `document-id` and `documentId` to disagree, and because a binding a bundle adds
|
|
21
|
-
* then works with no host change at all.
|
|
22
|
-
*
|
|
23
|
-
* ── What this deliberately does NOT do ──────────────────────────────────────
|
|
24
|
-
*
|
|
25
|
-
* It does not DERIVE anything. `execution-state` could plausibly be computed
|
|
26
|
-
* from a document status — an org's naming system defines DRAFT / REVIEW /
|
|
27
|
-
* FINAL / EXECUTED, and is emphatic that *"`FINAL` and `EXECUTED` are not the
|
|
28
|
-
* same thing… never collapse these"*. Encoding that mapping here would be
|
|
29
|
-
* inventing legal policy in a stylesheet helper, and getting it wrong prints a
|
|
30
|
-
* document asserting the wrong status — the exact failure `requiredBindings`
|
|
31
|
-
* exists to prevent, arriving through the fix for it.
|
|
32
|
-
*
|
|
33
|
-
* So: declared values only. A page that has not declared them stays unprintable,
|
|
34
|
-
* visibly, with the reason on the button. That is the correct state for a
|
|
35
|
-
* document nobody has said the status of.
|
|
36
|
-
*/
|
|
37
1
|
import type { PageRegionValues } from './print-stylesheet.js';
|
|
38
|
-
/** The `metadata.document` shape. Open — a profile may name bindings we predate. */
|
|
39
2
|
export interface DocumentBindingsMetadata {
|
|
40
3
|
bindings?: Record<string, unknown>;
|
|
41
4
|
}
|
|
@@ -45,32 +8,8 @@ interface PageLike {
|
|
|
45
8
|
}
|
|
46
9
|
export interface ResolvedBindings {
|
|
47
10
|
values: PageRegionValues;
|
|
48
|
-
/** Declared keys whose value was not a usable string. Named, never coerced. */
|
|
49
11
|
malformed: string[];
|
|
50
12
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Read declared binding values off a page record.
|
|
53
|
-
*
|
|
54
|
-
* Non-string values are DROPPED and reported rather than stringified. `42` would
|
|
55
|
-
* render as "42" in a footer and look deliberate; `null` would render as "null".
|
|
56
|
-
* A footer is read as an assertion about the document, so a value that arrived
|
|
57
|
-
* by accident must not reach one.
|
|
58
|
-
*/
|
|
59
13
|
export declare function resolveRegionBindings(page: PageLike | null | undefined): ResolvedBindings;
|
|
60
|
-
/**
|
|
61
|
-
* The profile id the page names, plus a detector for the second copy of it.
|
|
62
|
-
*
|
|
63
|
-
* `layout.profile` is authoritative — a profile is geometry and belongs beside
|
|
64
|
-
* `structure`. But the proof page carries `metadata.profile` as well, with
|
|
65
|
-
* the same value, and **the host reads only one of them**. Today they agree; the
|
|
66
|
-
* database has 1 page with each key and 0 disagreements. That is two sources for
|
|
67
|
-
* one fact, in sync by luck, with no detector — the shape this pair of repos has
|
|
68
|
-
* now caught four times (`PaperConfig` vs the profile, two type scales, two
|
|
69
|
-
* paginators, and the carried-field lists).
|
|
70
|
-
*
|
|
71
|
-
* Rather than reading both and picking, or silently ignoring one: read `layout`,
|
|
72
|
-
* and say so loudly the moment the copies disagree. Enforcement is cheap here
|
|
73
|
-
* precisely because it is not yet a problem.
|
|
74
|
-
*/
|
|
75
14
|
export declare function resolveProfileId(page: PageLike | null | undefined): string | undefined;
|
|
76
15
|
export {};
|
|
@@ -1,49 +1,4 @@
|
|
|
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
1
|
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
2
|
export type ProfileBundle = Pick<LoadedBundle, 'documentProfiles'> & {
|
|
48
3
|
label?: string;
|
|
49
4
|
key?: string;
|
|
@@ -56,24 +11,10 @@ export interface DocumentPageRegionSlots {
|
|
|
56
11
|
centre?: string;
|
|
57
12
|
right?: string;
|
|
58
13
|
}
|
|
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
14
|
export interface DocumentPageRegions {
|
|
65
15
|
'page-header'?: DocumentPageRegionSlots;
|
|
66
16
|
'page-footer'?: DocumentPageRegionSlots;
|
|
67
17
|
}
|
|
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
18
|
export interface DocumentProfile {
|
|
78
19
|
id: ProfileId;
|
|
79
20
|
extends?: ProfileId;
|
|
@@ -90,32 +31,13 @@ export interface DocumentProfile {
|
|
|
90
31
|
};
|
|
91
32
|
measure?: string;
|
|
92
33
|
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
34
|
rail?: {
|
|
110
35
|
placement: 'strip' | 'column' | 'none';
|
|
111
|
-
/** Where the column starts, measured from the text block's left edge. */
|
|
112
36
|
axis?: string;
|
|
113
37
|
width?: string;
|
|
114
|
-
/** Space between the measure and the column. */
|
|
115
38
|
gutter?: string;
|
|
116
39
|
};
|
|
117
40
|
ink?: Partial<Record<DocumentInkRole, string>>;
|
|
118
|
-
/** `Partial` is load-bearing: an undefined weight is a PROHIBITION, not a gap. */
|
|
119
41
|
rules?: Partial<Record<DocumentRuleWeight, string>>;
|
|
120
42
|
type?: Record<string, unknown>;
|
|
121
43
|
floorPx?: number;
|
|
@@ -126,78 +48,19 @@ export interface DocumentProfile {
|
|
|
126
48
|
};
|
|
127
49
|
fills?: boolean;
|
|
128
50
|
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
51
|
sections?: {
|
|
138
52
|
required?: string[];
|
|
139
53
|
known?: string[];
|
|
140
54
|
};
|
|
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
55
|
requiredBindings?: string[];
|
|
167
56
|
}
|
|
168
57
|
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
58
|
export type DocumentProfileSource = 'bundle' | 'defaults';
|
|
174
59
|
export interface ResolvedDocumentProfile {
|
|
175
|
-
/** `null` when no profile was found. Callers fall back to their own defaults. */
|
|
176
60
|
profile: DocumentProfile | null;
|
|
177
61
|
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
62
|
maxDepth: number | undefined;
|
|
187
|
-
/** `profile.depth.numbering`, defaulting to `'ladder'`. */
|
|
188
63
|
numbering: 'none' | 'ladder';
|
|
189
64
|
}
|
|
190
|
-
/** Test seam. Nothing in the app should need this. */
|
|
191
65
|
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
66
|
export declare function resolveDocumentProfile(registries: readonly ProfileBundle[], profileId: ProfileId | undefined): ResolvedDocumentProfile;
|
|
@@ -1,75 +1,9 @@
|
|
|
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
1
|
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
2
|
export declare function expectedHeightAfterTypesetting(measuredContainerPx: number, layout: Pick<PrintLayout, 'scaled' | 'screenExtraPx'>): number;
|
|
56
3
|
export interface Staleness {
|
|
57
4
|
stale: boolean;
|
|
58
|
-
/** Live height minus expected. Positive means the document grew. */
|
|
59
5
|
deltaPx: number;
|
|
60
6
|
tolerancePx: number;
|
|
61
7
|
}
|
|
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
8
|
export declare function stalenessTolerancePx(textBlockPx: number): number;
|
|
74
|
-
/** Compare the document on the page against the map that was frozen for it. */
|
|
75
9
|
export declare function mapStaleness(liveHeightPx: number, expectedPx: number, textBlockPx: number): Staleness;
|