@north-light/crouter 0.3.196 → 0.3.198
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/dist/api/client.d.ts +2 -2
- package/dist/api/dto/inbox.d.ts +46 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +3 -8
- package/dist/clients/attach/render/chat-view.js +3 -24
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/viewer.js +613 -602
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
- package/dist/clients/inbox/controller.js +4 -4
- package/dist/clients/inbox/page-adapter.d.ts +2 -1
- package/dist/clients/inbox/tui/render.js +46 -25
- package/dist/clients/inbox/tui/slots.d.ts +1 -3
- package/dist/clients/inbox/tui/slots.js +8 -37
- package/dist/clients/inbox/tui/types.d.ts +4 -3
- package/dist/commands/__tests__/human.test.js +160 -23
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/human/doc.js +21 -74
- package/dist/commands/human/html.d.ts +2 -0
- package/dist/commands/human/html.js +81 -0
- package/dist/commands/human/prompts.d.ts +5 -4
- package/dist/commands/human/prompts.js +90 -168
- package/dist/commands/human/shared.d.ts +5 -6
- package/dist/commands/human/shared.js +13 -61
- package/dist/commands/human.js +7 -2
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/human-deliver.test.js +28 -37
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
- package/dist/core/canvas/__tests__/attention.test.js +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +26 -0
- package/dist/core/canvas/canvas.js +55 -0
- package/dist/core/command.js +6 -11
- package/dist/core/config.js +3 -2
- package/dist/core/help.d.ts +3 -0
- package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
- package/dist/core/human/__tests__/html-markdown.test.js +52 -0
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +16 -0
- package/dist/core/human/__tests__/page-render.test.js +40 -23
- package/dist/core/human/__tests__/page-tickets.test.js +48 -27
- package/dist/core/human/__tests__/page.test.js +48 -65
- package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +51 -0
- package/dist/core/human/answer.js +125 -0
- package/dist/core/human/component-docs.d.ts +14 -0
- package/dist/core/human/component-docs.js +161 -0
- package/dist/core/human/html-markdown.d.ts +3 -0
- package/dist/core/human/html-markdown.js +288 -0
- package/dist/core/human/page-catalog.d.ts +19 -3
- package/dist/core/human/page-catalog.js +73 -16
- package/dist/core/human/page-render.d.ts +3 -2
- package/dist/core/human/page-render.js +19 -45
- package/dist/core/human/page-schema.d.ts +108 -8
- package/dist/core/human/page-schema.js +88 -31
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +23 -7
- package/dist/core/human/page.d.ts +14 -7
- package/dist/core/human/page.js +178 -90
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/scan.d.ts +3 -2
- package/dist/core/human/scan.js +7 -8
- package/dist/core/human/summary.d.ts +4 -7
- package/dist/core/human/summary.js +4 -60
- package/dist/core/human/tickets.d.ts +10 -9
- package/dist/core/human/tickets.js +5 -3
- package/dist/core/human/types.d.ts +4 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/review/realize.js +9 -1
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker.js +8 -6
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/termrender/display.d.ts +7 -3
- package/dist/core/termrender/display.js +45 -9
- package/dist/core/termrender/termrender.d.ts +3 -12
- package/dist/core/termrender/termrender.js +6 -37
- package/dist/core/user-settings.d.ts +17 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/daemon/api/handlers/human.js +1 -1
- package/dist/daemon/api/handlers/inbox.js +22 -7
- package/dist/daemon/cron-run.js +16 -2
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +2 -1
- package/dist/daemon/human/finish.js +45 -28
- package/dist/daemon/human/sweep.js +4 -2
- package/dist/daemon/reconcilers/broker-supervision.js +11 -8
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pages/bundle.css +1 -1
- package/dist/pages/bundle.js +1008 -577
- package/dist/pages/comments.d.ts +29 -0
- package/dist/pages/comments.js +51 -0
- package/dist/pages/controls.d.ts +98 -0
- package/dist/pages/controls.js +261 -0
- package/dist/pages/elements/cards.d.ts +15 -4
- package/dist/pages/elements/cards.js +299 -186
- package/dist/pages/elements/chart.d.ts +22 -0
- package/dist/pages/elements/chart.js +107 -91
- package/dist/pages/elements/options.d.ts +9 -0
- package/dist/pages/elements/options.js +268 -155
- package/dist/pages/elements/pages.d.ts +6 -0
- package/dist/pages/elements/pages.js +60 -38
- package/dist/pages/elements/slot.d.ts +5 -0
- package/dist/pages/elements/slot.js +17 -10
- package/dist/pages/elements/table.d.ts +17 -0
- package/dist/pages/elements/table.js +336 -228
- package/dist/pages/elements/text.d.ts +9 -3
- package/dist/pages/elements/text.js +350 -143
- package/dist/pages/host.d.ts +0 -24
- package/dist/pages/host.js +0 -17
- package/dist/pages/readonly.d.ts +24 -0
- package/dist/pages/readonly.js +31 -0
- package/dist/pages/responses.d.ts +37 -0
- package/dist/pages/responses.js +56 -0
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +12 -8
- package/dist/types.d.ts +19 -5
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/scripts/install-runtime.mjs +19 -24
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
package/dist/core/human/page.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { checkMarkdown } from '../termrender/termrender.js';
|
|
4
3
|
import { BUILTIN_PAGE_KINDS, isRegisteredPageKind } from './page-catalog.js';
|
|
5
|
-
import { isResponseBearingSlot, validatePageManifest } from './page-schema.js';
|
|
4
|
+
import { cardsSchema, chartDataRowsSchema, isResponseBearingSlot, issueText, tableRowsSchema, validateAuthoredPageManifest, validatePageManifest } from './page-schema.js';
|
|
6
5
|
/** A page failed authoring or validation; the caller can report it as bad page input. */
|
|
7
6
|
export class PageAuthoringError extends Error {
|
|
8
7
|
constructor(message) {
|
|
@@ -15,9 +14,18 @@ function asPageAuthoringError(error) {
|
|
|
15
14
|
return error;
|
|
16
15
|
return new PageAuthoringError(error instanceof Error ? error.message : String(error));
|
|
17
16
|
}
|
|
18
|
-
function validateAuthoredManifest(parsed,
|
|
17
|
+
function validateAuthoredManifest(parsed, productComponents) {
|
|
19
18
|
try {
|
|
20
|
-
|
|
19
|
+
const manifest = validateAuthoredPageManifest(parsed, productComponents);
|
|
20
|
+
const responseBearing = manifest.slots.filter(isResponseBearingSlot);
|
|
21
|
+
if (responseBearing.length >= 2) {
|
|
22
|
+
for (const slot of responseBearing) {
|
|
23
|
+
const label = slot.config.label;
|
|
24
|
+
if (typeof label !== 'string' || label === '')
|
|
25
|
+
throw new PageAuthoringError(`response-bearing slot "${slot.id}" requires a nonempty config.label when the page has multiple response-bearing slots`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return manifest;
|
|
21
29
|
}
|
|
22
30
|
catch (error) {
|
|
23
31
|
throw asPageAuthoringError(error);
|
|
@@ -35,53 +43,6 @@ function parseJsonObject(raw, thing) {
|
|
|
35
43
|
throw new PageAuthoringError(`${thing} JSON must be one object`);
|
|
36
44
|
return value;
|
|
37
45
|
}
|
|
38
|
-
function parseFrontMatter(document) {
|
|
39
|
-
if (!(document.startsWith('---\n') || document.startsWith('---\r\n')))
|
|
40
|
-
throw new PageAuthoringError('md page front matter must begin at the first bytes with a --- delimiter line');
|
|
41
|
-
const firstEnd = document.indexOf('\n') + 1;
|
|
42
|
-
let cursor = firstEnd;
|
|
43
|
-
let closingStart = -1;
|
|
44
|
-
while (cursor <= document.length) {
|
|
45
|
-
const end = document.indexOf('\n', cursor);
|
|
46
|
-
const lineEnd = end === -1 ? document.length : end;
|
|
47
|
-
const line = document.slice(cursor, lineEnd).replace(/\r$/, '');
|
|
48
|
-
if (line === '---') {
|
|
49
|
-
closingStart = cursor;
|
|
50
|
-
cursor = end === -1 ? document.length : end + 1;
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
if (end === -1)
|
|
54
|
-
break;
|
|
55
|
-
cursor = end + 1;
|
|
56
|
-
}
|
|
57
|
-
if (closingStart === -1)
|
|
58
|
-
throw new PageAuthoringError('md page front matter is missing its closing --- delimiter line');
|
|
59
|
-
const raw = document.slice(firstEnd, closingStart);
|
|
60
|
-
const values = new Map();
|
|
61
|
-
const rawLines = raw.split(/\r?\n/);
|
|
62
|
-
if (rawLines.at(-1) === '')
|
|
63
|
-
rawLines.pop();
|
|
64
|
-
for (const [index, line] of rawLines.entries()) {
|
|
65
|
-
const colon = line.indexOf(':');
|
|
66
|
-
if (colon <= 0)
|
|
67
|
-
throw new PageAuthoringError(`front matter line ${index + 2} must be "key: value"`);
|
|
68
|
-
const key = line.slice(0, colon).trim();
|
|
69
|
-
const value = line.slice(colon + 1).trim();
|
|
70
|
-
if (!['title', 'subtitle', 'placement'].includes(key))
|
|
71
|
-
throw new PageAuthoringError(`unknown page front matter key "${key}"`);
|
|
72
|
-
if (values.has(key))
|
|
73
|
-
throw new PageAuthoringError(`duplicate page front matter key "${key}"`);
|
|
74
|
-
values.set(key, value);
|
|
75
|
-
}
|
|
76
|
-
const title = values.get('title');
|
|
77
|
-
if (title === undefined || title === '')
|
|
78
|
-
throw new PageAuthoringError('page front matter title is required and must be nonempty');
|
|
79
|
-
const placement = values.get('placement') ?? 'panel';
|
|
80
|
-
if (placement !== 'inline' && placement !== 'panel')
|
|
81
|
-
throw new PageAuthoringError(`page front matter placement must be exactly inline or panel; received "${placement}"`);
|
|
82
|
-
const subtitle = values.get('subtitle');
|
|
83
|
-
return { title, ...(subtitle === undefined ? {} : { subtitle }), placement, bodyStart: cursor };
|
|
84
|
-
}
|
|
85
46
|
function sourceLines(source, start) {
|
|
86
47
|
const lines = [];
|
|
87
48
|
let cursor = start;
|
|
@@ -104,18 +65,35 @@ function addProse(projection, raw) {
|
|
|
104
65
|
if (markdown !== '')
|
|
105
66
|
projection.push({ kind: 'prose', markdown });
|
|
106
67
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Project a pre-cutover Markdown ticket for terminal replay only. This is not
|
|
70
|
+
* an authoring contract: no new page path accepts Markdown, and its old front
|
|
71
|
+
* matter is merely skipped to recover the stored source body.
|
|
72
|
+
*/
|
|
73
|
+
function projectLegacyMarkdownSource(document, productComponents) {
|
|
74
|
+
if (!(document.startsWith('---\n') || document.startsWith('---\r\n'))) {
|
|
75
|
+
throw new PageAuthoringError('stored md page is missing its opening front matter delimiter');
|
|
76
|
+
}
|
|
77
|
+
const firstEnd = document.indexOf('\n') + 1;
|
|
78
|
+
let bodyStart = -1;
|
|
79
|
+
let cursor = firstEnd;
|
|
80
|
+
while (cursor <= document.length) {
|
|
81
|
+
const newline = document.indexOf('\n', cursor);
|
|
82
|
+
const lineEnd = newline === -1 ? document.length : newline;
|
|
83
|
+
if (document.slice(cursor, lineEnd).replace(/\r$/, '') === '---') {
|
|
84
|
+
bodyStart = newline === -1 ? document.length : newline + 1;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (newline === -1)
|
|
88
|
+
break;
|
|
89
|
+
cursor = newline + 1;
|
|
90
|
+
}
|
|
91
|
+
if (bodyStart === -1)
|
|
92
|
+
throw new PageAuthoringError('stored md page is missing its closing front matter delimiter');
|
|
93
|
+
const lines = sourceLines(document, bodyStart);
|
|
116
94
|
const slots = [];
|
|
117
95
|
const projection = [];
|
|
118
|
-
let proseStart =
|
|
96
|
+
let proseStart = bodyStart;
|
|
119
97
|
let ordinaryFence = null;
|
|
120
98
|
for (let index = 0; index < lines.length; index++) {
|
|
121
99
|
const line = lines[index];
|
|
@@ -135,8 +113,8 @@ function scanMarkdown(document, productKinds) {
|
|
|
135
113
|
continue;
|
|
136
114
|
}
|
|
137
115
|
const kind = info.slice('slot:'.length);
|
|
138
|
-
if (kind === '' || info !== `slot:${kind}` || !isRegisteredPageKind(kind,
|
|
139
|
-
throw new PageAuthoringError(`unregistered or invalid page slot kind "${kind}" in md fence
|
|
116
|
+
if (kind === '' || info !== `slot:${kind}` || !isRegisteredPageKind(kind, productComponents)) {
|
|
117
|
+
throw new PageAuthoringError(`unregistered or invalid page slot kind "${kind}" in stored md fence`);
|
|
140
118
|
}
|
|
141
119
|
let closeIndex = -1;
|
|
142
120
|
for (let candidate = index + 1; candidate < lines.length; candidate++) {
|
|
@@ -146,19 +124,19 @@ function scanMarkdown(document, productKinds) {
|
|
|
146
124
|
}
|
|
147
125
|
}
|
|
148
126
|
if (closeIndex === -1)
|
|
149
|
-
throw new PageAuthoringError(`slot:${kind} fence
|
|
127
|
+
throw new PageAuthoringError(`slot:${kind} fence has no matching ${run.length}-backtick closing fence`);
|
|
150
128
|
addProse(projection, document.slice(proseStart, line.start));
|
|
151
|
-
const rawObject = parseJsonObject(document.slice(line.end, lines[closeIndex].start), `slot:${kind} fence
|
|
129
|
+
const rawObject = parseJsonObject(document.slice(line.end, lines[closeIndex].start), `stored slot:${kind} fence`);
|
|
152
130
|
const { id, ...config } = rawObject;
|
|
153
131
|
if (id !== undefined && typeof id !== 'string')
|
|
154
|
-
throw new PageAuthoringError(`slot:${kind} fence
|
|
132
|
+
throw new PageAuthoringError(`stored slot:${kind} fence has non-string reserved id`);
|
|
155
133
|
slots.push({ ...(id === undefined ? {} : { id }), kind, step: 0, config, ...(BUILTIN_PAGE_KINDS.includes(kind) ? {} : { unvalidated: true }) });
|
|
156
134
|
projection.push({ kind: 'slot', slotIndex: slots.length - 1 });
|
|
157
135
|
proseStart = lines[closeIndex].end;
|
|
158
136
|
index = closeIndex;
|
|
159
137
|
}
|
|
160
138
|
addProse(projection, document.slice(proseStart));
|
|
161
|
-
return {
|
|
139
|
+
return { steps: 1, slots, projection };
|
|
162
140
|
}
|
|
163
141
|
const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
164
142
|
function tagEnd(html, start) {
|
|
@@ -216,8 +194,12 @@ function parseHtmlElements(html) {
|
|
|
216
194
|
let match = stack.length - 1;
|
|
217
195
|
while (match >= 0 && stack[match].tag !== tag)
|
|
218
196
|
match--;
|
|
219
|
-
if (match >= 0)
|
|
197
|
+
if (match >= 0) {
|
|
198
|
+
const element = stack[match];
|
|
199
|
+
element.closeStart = start;
|
|
200
|
+
element.end = end;
|
|
220
201
|
stack.length = match;
|
|
202
|
+
}
|
|
221
203
|
cursor = end;
|
|
222
204
|
continue;
|
|
223
205
|
}
|
|
@@ -228,7 +210,7 @@ function parseHtmlElements(html) {
|
|
|
228
210
|
}
|
|
229
211
|
const tag = opening[1].toLowerCase();
|
|
230
212
|
const parent = stack.at(-1);
|
|
231
|
-
const element = { tag, attrs: parseAttributes(token.slice(1, -1)), start, ...(parent === undefined ? {} : { parent }), children: [] };
|
|
213
|
+
const element = { tag, attrs: parseAttributes(token.slice(1, -1)), start, openEnd: end, end, ...(parent === undefined ? {} : { parent }), children: [] };
|
|
232
214
|
if (parent === undefined)
|
|
233
215
|
roots.push(element);
|
|
234
216
|
else
|
|
@@ -242,6 +224,8 @@ function parseHtmlElements(html) {
|
|
|
242
224
|
if (close === null)
|
|
243
225
|
throw new PageAuthoringError(`unterminated <script> element at byte ${start}`);
|
|
244
226
|
element.text = html.slice(end, close.index);
|
|
227
|
+
element.closeStart = close.index;
|
|
228
|
+
element.end = closePattern.lastIndex;
|
|
245
229
|
cursor = closePattern.lastIndex;
|
|
246
230
|
continue;
|
|
247
231
|
}
|
|
@@ -321,7 +305,41 @@ function configForHtmlElement(element) {
|
|
|
321
305
|
throw new PageAuthoringError(`<${element.tag}> at byte ${element.start} requires a direct <script type="application/json"> child`);
|
|
322
306
|
return parseJsonObject(script.text ?? '', `<${element.tag}> at byte ${element.start}`);
|
|
323
307
|
}
|
|
324
|
-
|
|
308
|
+
/** Project only body prose: slots remain terminal panels, while the pager's
|
|
309
|
+
* structural tags are invisible in the terminal. The raw fragments flow to
|
|
310
|
+
* the renderer, which downconverts HTML according to the manifest dialect. */
|
|
311
|
+
function htmlProjection(document, elements, slotElements) {
|
|
312
|
+
const body = elements.find((element) => element.tag === 'body');
|
|
313
|
+
if (body === undefined)
|
|
314
|
+
throw new PageAuthoringError('page document has no <body> element');
|
|
315
|
+
const bodyEnd = body.closeStart ?? document.length;
|
|
316
|
+
const excluded = slotElements.map((element) => ({ start: element.start, end: element.end }));
|
|
317
|
+
for (const element of elements) {
|
|
318
|
+
if (element.tag !== 'crtr-pages' && element.tag !== 'crtr-page')
|
|
319
|
+
continue;
|
|
320
|
+
excluded.push({ start: element.start, end: element.openEnd });
|
|
321
|
+
if (element.closeStart !== undefined)
|
|
322
|
+
excluded.push({ start: element.closeStart, end: element.end });
|
|
323
|
+
}
|
|
324
|
+
const ranges = excluded
|
|
325
|
+
.map((range) => ({ start: Math.max(range.start, body.openEnd), end: Math.min(range.end, bodyEnd) }))
|
|
326
|
+
.filter((range) => range.start < range.end)
|
|
327
|
+
.sort((left, right) => left.start - right.start || left.end - right.end);
|
|
328
|
+
const projection = [];
|
|
329
|
+
let cursor = body.openEnd;
|
|
330
|
+
for (const range of ranges) {
|
|
331
|
+
if (range.end <= cursor)
|
|
332
|
+
continue;
|
|
333
|
+
addProse(projection, document.slice(cursor, range.start));
|
|
334
|
+
const element = slotElements.find((slot) => slot.start === range.start && slot.end === range.end);
|
|
335
|
+
if (element !== undefined)
|
|
336
|
+
projection.push({ kind: 'slot', slotIndex: slotElements.indexOf(element) });
|
|
337
|
+
cursor = range.end;
|
|
338
|
+
}
|
|
339
|
+
addProse(projection, document.slice(cursor, bodyEnd));
|
|
340
|
+
return projection;
|
|
341
|
+
}
|
|
342
|
+
function scanHtml(document, productComponents) {
|
|
325
343
|
validateHtmlStructure(document);
|
|
326
344
|
const elements = parseHtmlElements(document);
|
|
327
345
|
const metadata = readHtmlMetadata(elements);
|
|
@@ -347,7 +365,7 @@ function scanHtml(document, productKinds) {
|
|
|
347
365
|
let unvalidated = false;
|
|
348
366
|
if (element.tag === 'crtr-slot') {
|
|
349
367
|
kind = element.attrs.get('kind');
|
|
350
|
-
if (kind === undefined || !
|
|
368
|
+
if (kind === undefined || !isRegisteredPageKind(kind, productComponents) || BUILTIN_PAGE_KINDS.includes(kind))
|
|
351
369
|
throw new PageAuthoringError(`<crtr-slot> at byte ${element.start} names unregistered product kind "${kind ?? ''}"`);
|
|
352
370
|
unvalidated = true;
|
|
353
371
|
}
|
|
@@ -358,6 +376,8 @@ function scanHtml(document, productKinds) {
|
|
|
358
376
|
}
|
|
359
377
|
if (kind === undefined)
|
|
360
378
|
continue;
|
|
379
|
+
if (element.attrs.has('slot-id'))
|
|
380
|
+
throw new PageAuthoringError(`<${element.tag}> at byte ${element.start} has a slot-id attribute; author the slot id as id="…" — slot-id is written by the renderer`);
|
|
361
381
|
const id = element.attrs.get('id');
|
|
362
382
|
const containingPage = ancestor(element, 'crtr-page');
|
|
363
383
|
const step = containingPage === undefined ? 0 : pageIndexes.get(containingPage) ?? 0;
|
|
@@ -365,7 +385,7 @@ function scanHtml(document, productKinds) {
|
|
|
365
385
|
slotElements.push(element);
|
|
366
386
|
}
|
|
367
387
|
const steps = wrapper === undefined ? 1 : pages.length;
|
|
368
|
-
const provisional = validateAuthoredManifest({ schema: 'crtr.page/v1', dialect: 'html', ...metadata, document: 'page.html', steps, slots: rawSlots },
|
|
388
|
+
const provisional = validateAuthoredManifest({ schema: 'crtr.page/v1', dialect: 'html', ...metadata, document: 'page.html', steps, slots: rawSlots }, productComponents);
|
|
369
389
|
for (let index = 0; index < provisional.slots.length; index++) {
|
|
370
390
|
const slot = provisional.slots[index];
|
|
371
391
|
if (!isResponseBearingSlot(slot))
|
|
@@ -375,22 +395,79 @@ function scanHtml(document, productKinds) {
|
|
|
375
395
|
throw new PageAuthoringError(`response-bearing element <${element.tag}> for slot "${slot.id}" is outside any <crtr-page>.\nWrap interactive elements with:\n<crtr-pages><crtr-page>…</crtr-page></crtr-pages>`);
|
|
376
396
|
}
|
|
377
397
|
}
|
|
378
|
-
return { ...metadata, steps, slots: provisional.slots, projection:
|
|
398
|
+
return { ...metadata, steps, slots: provisional.slots, projection: htmlProjection(document, elements, slotElements) };
|
|
379
399
|
}
|
|
380
|
-
|
|
381
|
-
|
|
400
|
+
/** Derive the transport manifest from one authored HTML page document. */
|
|
401
|
+
export function derivePageManifestFromDocument(document, dialect, productComponents = [], source) {
|
|
402
|
+
const scanned = scanHtml(document, productComponents);
|
|
403
|
+
return validateAuthoredManifest({ schema: 'crtr.page/v1', dialect, title: scanned.title, ...(scanned.subtitle === undefined ? {} : { subtitle: scanned.subtitle }), placement: scanned.placement, document: 'page.html', steps: scanned.steps, slots: scanned.slots, ...(source === undefined ? {} : { source }) }, productComponents);
|
|
382
404
|
}
|
|
383
|
-
/** Derive the transport manifest from one authored page
|
|
384
|
-
export function
|
|
385
|
-
|
|
386
|
-
return validateAuthoredManifest({ schema: 'crtr.page/v1', dialect, title: scanned.title, ...(scanned.subtitle === undefined ? {} : { subtitle: scanned.subtitle }), placement: scanned.placement, document: dialect === 'md' ? 'page.md' : 'page.html', steps: scanned.steps, slots: scanned.slots, ...(source === undefined ? {} : { source }) }, productKinds);
|
|
405
|
+
/** Derive the transport manifest from one authored HTML page source file. */
|
|
406
|
+
export function derivePageManifest(sourceFile, dialect, productComponents = [], source) {
|
|
407
|
+
return derivePageManifestFromDocument(readFileSync(sourceFile, 'utf8'), dialect, productComponents, source);
|
|
387
408
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
409
|
+
const SOURCE_EMBED = {
|
|
410
|
+
table: { field: 'rows', schema: tableRowsSchema },
|
|
411
|
+
cards: { field: 'cards', schema: cardsSchema },
|
|
412
|
+
chart: { field: 'data', schema: chartDataRowsSchema },
|
|
413
|
+
};
|
|
414
|
+
function readSourceFile(path, context) {
|
|
415
|
+
if (!existsSync(path))
|
|
416
|
+
throw new PageAuthoringError(`${context} source file does not exist: ${path}`);
|
|
417
|
+
let raw;
|
|
418
|
+
try {
|
|
419
|
+
raw = readFileSync(path, 'utf8');
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
throw new PageAuthoringError(`${context} source file is not readable: ${path} (${error instanceof Error ? error.message : String(error)})`);
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
return JSON.parse(raw);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
throw new PageAuthoringError(`${context} source file is not valid JSON: ${path} (${error instanceof Error ? error.message : String(error)})`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/** Read source files into their built-in slots, returning a self-contained stored manifest. */
|
|
432
|
+
export function snapshotPageSources(manifest, productComponents = []) {
|
|
433
|
+
const slots = manifest.slots.map((slot, index) => {
|
|
434
|
+
const embed = slot.unvalidated === true ? undefined : SOURCE_EMBED[slot.kind];
|
|
435
|
+
const config = slot.config;
|
|
436
|
+
if (embed === undefined || typeof config.source !== 'string')
|
|
437
|
+
return slot;
|
|
438
|
+
const context = `slot ${slot.id === undefined ? `#${index}` : `"${slot.id}"`} (${slot.kind})`;
|
|
439
|
+
const raw = readSourceFile(config.source, context);
|
|
440
|
+
const parsed = embed.schema.safeParse(raw);
|
|
441
|
+
if (!parsed.success)
|
|
442
|
+
throw new PageAuthoringError(`${context} source data is invalid: ${issueText(parsed.error)}`);
|
|
443
|
+
const { source: _drop, ...rest } = config;
|
|
444
|
+
return { ...slot, config: { ...rest, [embed.field]: parsed.data } };
|
|
445
|
+
});
|
|
446
|
+
return validatePageManifest({ ...manifest, slots }, productComponents);
|
|
447
|
+
}
|
|
448
|
+
/** Replace every authored slot config with its stored, self-contained config for runtime rendering. */
|
|
449
|
+
export function embedPageSlotConfigs(document, manifest) {
|
|
450
|
+
const replacements = [];
|
|
451
|
+
let slotIndex = 0;
|
|
452
|
+
for (const element of parseHtmlElements(document)) {
|
|
453
|
+
const isSlot = element.tag === 'crtr-slot' || (element.tag.startsWith('crtr-') && element.tag !== 'crtr-pages' && element.tag !== 'crtr-page');
|
|
454
|
+
if (!isSlot)
|
|
455
|
+
continue;
|
|
456
|
+
const slot = manifest.slots[slotIndex];
|
|
457
|
+
if (slot === undefined)
|
|
458
|
+
throw new PageAuthoringError(`page document has more slots than its manifest at slot #${slotIndex}`);
|
|
459
|
+
const script = element.children.find((child) => child.tag === 'script' && child.attrs.get('type') === 'application/json');
|
|
460
|
+
if (script?.closeStart === undefined)
|
|
461
|
+
throw new PageAuthoringError(`slot #${slotIndex} has no JSON config script`);
|
|
462
|
+
replacements.push({ start: script.openEnd, end: script.closeStart, text: JSON.stringify(slot.config).replace(/</g, '\\u003c') });
|
|
463
|
+
slotIndex += 1;
|
|
464
|
+
}
|
|
465
|
+
if (slotIndex !== manifest.slots.length)
|
|
466
|
+
throw new PageAuthoringError(`page document has ${slotIndex} slots but manifest has ${manifest.slots.length}`);
|
|
467
|
+
return replacements.reduceRight((result, replacement) => `${result.slice(0, replacement.start)}${replacement.text}${result.slice(replacement.end)}`, document);
|
|
391
468
|
}
|
|
392
469
|
/** Read and validate page.json from a ticket directory. */
|
|
393
|
-
export function parsePage(dir,
|
|
470
|
+
export function parsePage(dir, productComponents = []) {
|
|
394
471
|
let raw;
|
|
395
472
|
try {
|
|
396
473
|
raw = JSON.parse(readFileSync(join(dir, 'page.json'), 'utf8'));
|
|
@@ -398,7 +475,7 @@ export function parsePage(dir, productKinds = []) {
|
|
|
398
475
|
catch {
|
|
399
476
|
throw new PageAuthoringError('page.json is not valid JSON');
|
|
400
477
|
}
|
|
401
|
-
return validatePageManifest(raw,
|
|
478
|
+
return validatePageManifest(raw, productComponents);
|
|
402
479
|
}
|
|
403
480
|
/** Key-order-independent value identity, so a re-scan compares by content. */
|
|
404
481
|
function canonicalJson(value) {
|
|
@@ -409,18 +486,29 @@ function canonicalJson(value) {
|
|
|
409
486
|
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => (left < right ? -1 : 1));
|
|
410
487
|
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(',')}}`;
|
|
411
488
|
}
|
|
489
|
+
function comparableConfig(kind, config, sourceProjection) {
|
|
490
|
+
const embed = SOURCE_EMBED[kind];
|
|
491
|
+
if (embed === undefined || !sourceProjection)
|
|
492
|
+
return config;
|
|
493
|
+
const { source: _source, [embed.field]: _data, ...rest } = config;
|
|
494
|
+
return rest;
|
|
495
|
+
}
|
|
412
496
|
/** Project authored source for local rendering and verify its slot references against the manifest. */
|
|
413
|
-
export function parsePageSource(document, manifest,
|
|
414
|
-
const canonical =
|
|
415
|
-
const scanned =
|
|
497
|
+
export function parsePageSource(document, manifest, productComponents = []) {
|
|
498
|
+
const canonical = validatePageManifest(manifest, productComponents);
|
|
499
|
+
const scanned = canonical.dialect === 'md'
|
|
500
|
+
? projectLegacyMarkdownSource(document, productComponents)
|
|
501
|
+
: scanHtml(document, productComponents);
|
|
416
502
|
// Normalize the re-scan through the same schema pass the manifest went through,
|
|
417
503
|
// so per-kind config defaults are on both sides of the comparison.
|
|
418
|
-
const derived =
|
|
504
|
+
const derived = validateAuthoredPageManifest({ schema: 'crtr.page/v1', dialect: canonical.dialect, title: canonical.title, ...(canonical.subtitle === undefined ? {} : { subtitle: canonical.subtitle }), placement: canonical.placement, document: canonical.document, steps: scanned.steps, slots: scanned.slots }, productComponents);
|
|
419
505
|
if (derived.slots.length !== canonical.slots.length)
|
|
420
506
|
throw new PageAuthoringError(`page source has ${derived.slots.length} slots but manifest has ${canonical.slots.length}`);
|
|
421
507
|
for (let index = 0; index < canonical.slots.length; index++) {
|
|
422
508
|
const left = derived.slots[index];
|
|
423
|
-
|
|
509
|
+
const right = canonical.slots[index];
|
|
510
|
+
const sourceProjection = typeof left.config.source === 'string';
|
|
511
|
+
if (canonicalJson({ ...left, config: comparableConfig(left.kind, left.config, sourceProjection) }) !== canonicalJson({ ...right, config: comparableConfig(right.kind, right.config, sourceProjection) }))
|
|
424
512
|
throw new PageAuthoringError(`page source slot #${index} (${left.id ?? left.kind}) does not match manifest slot #${index}`);
|
|
425
513
|
}
|
|
426
514
|
return scanned.projection;
|
|
@@ -10,6 +10,7 @@ export declare const reviewDescriptorSchema: z.ZodObject<{
|
|
|
10
10
|
sessionName: z.ZodOptional<z.ZodString>;
|
|
11
11
|
askedBy: z.ZodOptional<z.ZodString>;
|
|
12
12
|
blockedSince: z.ZodOptional<z.ZodString>;
|
|
13
|
+
profileName: z.ZodOptional<z.ZodString>;
|
|
13
14
|
nodeId: z.ZodOptional<z.ZodString>;
|
|
14
15
|
}, z.core.$strip>;
|
|
15
16
|
blockedSince: z.ZodString;
|
|
@@ -3,7 +3,7 @@ import { basename, dirname, isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { claimPath, pageManifestPath, progressPath, responsePath, reviewPath } from './convention.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { checkReviewableFile } from './visible-paths.js';
|
|
6
|
-
const reviewSourceSchema = z.object({ sessionName: z.string().optional(), askedBy: z.string().optional(), blockedSince: z.string().optional(), nodeId: z.string().optional() });
|
|
6
|
+
const reviewSourceSchema = z.object({ sessionName: z.string().optional(), askedBy: z.string().optional(), blockedSince: z.string().optional(), profileName: z.string().optional(), nodeId: z.string().optional() });
|
|
7
7
|
export const reviewDescriptorSchema = z.object({
|
|
8
8
|
schema: z.literal('humanloop.review/v1'),
|
|
9
9
|
file: z.string().min(1).refine(isAbsolute, 'file must be absolute'),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { ProductPageComponents } from '../../types.js';
|
|
1
2
|
import type { PageTicketSummary, TicketSummary } from './types.js';
|
|
2
3
|
/** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
|
|
3
|
-
export declare function scanInbox(productKinds?:
|
|
4
|
+
export declare function scanInbox(productKinds?: ProductPageComponents): TicketSummary[];
|
|
4
5
|
export interface PageHistoryEntry extends PageTicketSummary {
|
|
5
6
|
state: 'pending' | 'resolved' | 'canceled';
|
|
6
7
|
}
|
|
@@ -9,4 +10,4 @@ export interface PageHistoryEntry extends PageTicketSummary {
|
|
|
9
10
|
* first. This is the durable discovery seam for transcript placement, so it is a
|
|
10
11
|
* separate walk from the pending-only inbox scan and never filters on a terminal file.
|
|
11
12
|
*/
|
|
12
|
-
export declare function scanPageHistory(nodeId: string, productKinds?:
|
|
13
|
+
export declare function scanPageHistory(nodeId: string, productKinds?: ProductPageComponents): PageHistoryEntry[];
|
package/dist/core/human/scan.js
CHANGED
|
@@ -3,8 +3,8 @@ import { basename, join } from 'node:path';
|
|
|
3
3
|
import { claimPath, isResolved, pageManifestPath, reviewPath } from './convention.js';
|
|
4
4
|
import { readJsonOrNull } from '../fs-utils.js';
|
|
5
5
|
import { validateReviewDescriptor } from './review-schema.js';
|
|
6
|
-
import {
|
|
7
|
-
import { pageTicketState } from './tickets.js';
|
|
6
|
+
import { parsePage } from './page.js';
|
|
7
|
+
import { pageTicketState, readTicketResult } from './tickets.js';
|
|
8
8
|
import { ticketsRoot } from './root.js';
|
|
9
9
|
function claimSummary(dir) {
|
|
10
10
|
const claim = readJsonOrNull(claimPath(dir));
|
|
@@ -26,12 +26,9 @@ function reviewSummary(dir, id) {
|
|
|
26
26
|
return { dir, id, kind: 'review', title: review.title, subtitle: review.subtitle, file: review.file, output: review.output, source: review.source, blockedSince: review.blockedSince, claim: claimSummary(dir) };
|
|
27
27
|
}
|
|
28
28
|
function pageSummary(dir, id, productKinds) {
|
|
29
|
-
const raw = readJsonOrNull(pageManifestPath(dir));
|
|
30
|
-
if (raw === null)
|
|
31
|
-
return null;
|
|
32
29
|
let manifest;
|
|
33
30
|
try {
|
|
34
|
-
manifest =
|
|
31
|
+
manifest = parsePage(dir, productKinds);
|
|
35
32
|
}
|
|
36
33
|
catch {
|
|
37
34
|
return null;
|
|
@@ -55,7 +52,7 @@ function pageSummary(dir, id, productKinds) {
|
|
|
55
52
|
placement: manifest.placement,
|
|
56
53
|
steps: manifest.steps,
|
|
57
54
|
slotKinds: manifest.slots.map((slot) => slot.kind),
|
|
58
|
-
awaitsResponse:
|
|
55
|
+
awaitsResponse: readJsonOrNull(join(dir, 'run.json'))?.mode === 'ask',
|
|
59
56
|
source: manifest.source ?? {},
|
|
60
57
|
blockedSince,
|
|
61
58
|
claim: claimSummary(dir),
|
|
@@ -114,7 +111,9 @@ export function scanPageHistory(nodeId, productKinds = []) {
|
|
|
114
111
|
const summary = pageSummary(dir, basename(dir), productKinds);
|
|
115
112
|
if (summary === null || summary.source.nodeId !== nodeId)
|
|
116
113
|
continue;
|
|
117
|
-
|
|
114
|
+
const state = pageTicketState(dir);
|
|
115
|
+
const result = state === 'resolved' ? readTicketResult(dir) : null;
|
|
116
|
+
items.push({ ...summary, state, ...(result?.kind === 'page' ? { answerDigest: result.summary.replace(/\s+/g, ' ').trim() } : {}) });
|
|
118
117
|
}
|
|
119
118
|
return items.sort((a, b) => Date.parse(a.blockedSince) - Date.parse(b.blockedSince) || a.id.localeCompare(b.id));
|
|
120
119
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* summarizes as the empty string.
|
|
6
|
-
*/
|
|
7
|
-
export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses): string;
|
|
1
|
+
import type { ProductPageComponents } from '../../types.js';
|
|
2
|
+
import type { PageManifest, PageResponses } from './page-schema.js';
|
|
3
|
+
/** The stable one-line digest stored in response.json. */
|
|
4
|
+
export declare function buildPageSummary(manifest: PageManifest, responses: PageResponses, productKinds?: ProductPageComponents): string;
|
|
@@ -1,61 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.map((item) => [String(item['id']), typeof item[labelKey] === 'string' ? item[labelKey] : String(item['id'])]));
|
|
6
|
-
return (Array.isArray(ids) ? ids : []).map((id) => byId.get(String(id)) ?? String(id));
|
|
7
|
-
}
|
|
8
|
-
function pickedWithFreetext(picked, freetext) {
|
|
9
|
-
const text = typeof freetext === 'string' && freetext !== '' ? freetext : undefined;
|
|
10
|
-
const selection = picked.length > 0 ? picked.join(', ') : undefined;
|
|
11
|
-
if (selection !== undefined && text !== undefined)
|
|
12
|
-
return `${selection} — ${text}`;
|
|
13
|
-
return selection ?? text ?? '(no selection)';
|
|
14
|
-
}
|
|
15
|
-
function commentSuffix(response) {
|
|
16
|
-
const comments = response['comments'];
|
|
17
|
-
const count = Array.isArray(comments) ? comments.length : 0;
|
|
18
|
-
return count === 0 ? '' : ` (${count} comment${count === 1 ? '' : 's'})`;
|
|
19
|
-
}
|
|
20
|
-
function describeSlotResponse(slot, response) {
|
|
21
|
-
const value = response;
|
|
22
|
-
const config = slot.config;
|
|
23
|
-
switch (slot.kind) {
|
|
24
|
-
case 'options':
|
|
25
|
-
return `${pickedWithFreetext(labelsOf(config['options'], value['selectedOptionIds'], 'label'), value['freetext'])}${commentSuffix(value)}`;
|
|
26
|
-
case 'text': {
|
|
27
|
-
const text = typeof value['text'] === 'string' ? value['text'].replace(/\s+/g, ' ').trim() : '';
|
|
28
|
-
return `${text === '' ? '(empty)' : text}${commentSuffix(value)}`;
|
|
29
|
-
}
|
|
30
|
-
case 'table': {
|
|
31
|
-
const rows = (Array.isArray(value['selectedRowIds']) ? value['selectedRowIds'] : []).map(String);
|
|
32
|
-
const columns = labelsOf(config['columns'], value['selectedColumnIds'], 'label');
|
|
33
|
-
const parts = [...(rows.length > 0 ? [`rows ${rows.join(', ')}`] : []), ...(columns.length > 0 ? [`columns ${columns.join(', ')}`] : [])];
|
|
34
|
-
return `${parts.length === 0 ? '(no selection)' : parts.join('; ')}${commentSuffix(value)}`;
|
|
35
|
-
}
|
|
36
|
-
case 'cards': {
|
|
37
|
-
const picked = labelsOf(config['cards'], value['selectedCardIds'], 'title');
|
|
38
|
-
return `${picked.length === 0 ? '(no selection)' : picked.join(', ')}${commentSuffix(value)}`;
|
|
39
|
-
}
|
|
40
|
-
default:
|
|
41
|
-
// A product kind crtr does not render: its opaque response is the summary.
|
|
42
|
-
return JSON.stringify(value);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Deterministic, no-LLM page resolution summary — one line per answered slot in
|
|
47
|
-
* document order: `"<slot id>: <answer>"`. An acknowledgement (`responses: {}`)
|
|
48
|
-
* summarizes as the empty string.
|
|
49
|
-
*/
|
|
50
|
-
export function buildPageSummary(manifest, responses) {
|
|
51
|
-
const lines = [];
|
|
52
|
-
for (const slot of manifest.slots) {
|
|
53
|
-
if (!isResponseBearingSlot(slot) || slot.id === undefined)
|
|
54
|
-
continue;
|
|
55
|
-
const response = responses[slot.id];
|
|
56
|
-
if (response === undefined)
|
|
57
|
-
continue;
|
|
58
|
-
lines.push(`${slot.id}: ${describeSlotResponse(slot, response)}`);
|
|
59
|
-
}
|
|
60
|
-
return lines.join('\n');
|
|
1
|
+
import { describePageAnswer } from './answer.js';
|
|
2
|
+
/** The stable one-line digest stored in response.json. */
|
|
3
|
+
export function buildPageSummary(manifest, responses, productKinds) {
|
|
4
|
+
return describePageAnswer(manifest, responses, { completedAt: '', productKinds }).digest;
|
|
61
5
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReviewDescriptor, TicketResult } from './types.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { ProductPageComponents } from '../../types.js';
|
|
3
|
+
import { type AuthoredPageDialect } from './page.js';
|
|
3
4
|
import { type PageManifest, type PageResponses, type PageSource } from './page-schema.js';
|
|
4
5
|
/** Strict decoder for the only canonical final marker. */
|
|
5
6
|
export declare function readTicketResult(dir: string): TicketResult | null;
|
|
@@ -7,13 +8,13 @@ export interface SubmitPageOptions {
|
|
|
7
8
|
dir: string;
|
|
8
9
|
/** The authored page source; copied into the ticket directory as its `document`. */
|
|
9
10
|
sourceFile: string;
|
|
10
|
-
dialect:
|
|
11
|
+
dialect: AuthoredPageDialect;
|
|
11
12
|
source?: PageSource;
|
|
12
|
-
productKinds?:
|
|
13
|
+
productKinds?: ProductPageComponents;
|
|
13
14
|
}
|
|
14
15
|
/** One validated page, ready to publish into a ticket directory. */
|
|
15
16
|
export interface PreparedPage {
|
|
16
|
-
dialect:
|
|
17
|
+
dialect: AuthoredPageDialect;
|
|
17
18
|
/** The authored source, verbatim, as the ticket's `document`. */
|
|
18
19
|
document: string;
|
|
19
20
|
manifest: PageManifest;
|
|
@@ -53,13 +54,13 @@ export declare function submitReview(opts: SubmitReviewOptions): {
|
|
|
53
54
|
kind: 'review';
|
|
54
55
|
};
|
|
55
56
|
/** Validate a page resolution against its canonical ticket before claim takeover. */
|
|
56
|
-
export declare function validatePageTicketResponses(dir: string, responses: unknown, productKinds?:
|
|
57
|
+
export declare function validatePageTicketResponses(dir: string, responses: unknown, productKinds?: ProductPageComponents): PageResponses;
|
|
57
58
|
/** The three terminal states of a page ticket, derived from its published result. */
|
|
58
59
|
export declare function pageTicketState(dir: string): 'pending' | 'resolved' | 'canceled';
|
|
59
60
|
/** Autosave partial page work. Every supplied response is validated; omitted slots are permitted. */
|
|
60
|
-
export declare function writePageProgress(dir: string, responses: unknown, productKinds?:
|
|
61
|
+
export declare function writePageProgress(dir: string, responses: unknown, productKinds?: ProductPageComponents): PageResponses;
|
|
61
62
|
/** Saved partial work, or null when none exists. Malformed progress is an error, never silently discarded. */
|
|
62
|
-
export declare function readPageProgress(dir: string, productKinds?:
|
|
63
|
+
export declare function readPageProgress(dir: string, productKinds?: ProductPageComponents): PageResponses | null;
|
|
63
64
|
export interface PageTicket {
|
|
64
65
|
dir: string;
|
|
65
66
|
id: string;
|
|
@@ -70,9 +71,9 @@ export interface PageTicket {
|
|
|
70
71
|
progress: PageResponses | null;
|
|
71
72
|
}
|
|
72
73
|
/** Read one addressable page ticket — pending, resolved or canceled alike. */
|
|
73
|
-
export declare function readPageTicket(dir: string, productKinds?:
|
|
74
|
+
export declare function readPageTicket(dir: string, productKinds?: ProductPageComponents): PageTicket;
|
|
74
75
|
export interface CompletePageOptions {
|
|
75
|
-
productKinds?:
|
|
76
|
+
productKinds?: ProductPageComponents;
|
|
76
77
|
completedAt?: string;
|
|
77
78
|
}
|
|
78
79
|
export declare function completePage(dir: string, responses: unknown, claimToken: string, opts?: CompletePageOptions): {
|