@north-light/crouter 0.3.198 → 0.3.200
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 +18 -18
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +81 -34
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +6 -6
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +26 -1
- package/dist/clients/attach/render/chat-view.js +61 -11
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -993
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +1 -3
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +71 -27
- package/dist/clients/inbox/tui/slots.d.ts +3 -1
- package/dist/clients/inbox/tui/slots.js +16 -12
- package/dist/clients/inbox/tui/types.d.ts +2 -7
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +71 -114
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +13 -5
- package/dist/commands/human/prompts.js +160 -97
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +2 -12
- package/dist/commands/human/shared.js +17 -11
- package/dist/commands/human.js +29 -21
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/helpers/harness.js +17 -55
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +31 -26
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +4 -5
- package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +37 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +2 -1
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +137 -4
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.js +4 -0
- package/dist/core/human/__tests__/page-tickets.test.js +65 -52
- package/dist/core/human/__tests__/page.test.js +56 -98
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
- package/dist/core/human/answer-text.js +3 -3
- package/dist/core/human/answer.d.ts +6 -4
- package/dist/core/human/answer.js +4 -10
- package/dist/core/human/component-docs.d.ts +11 -7
- package/dist/core/human/component-docs.js +361 -88
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +40 -5
- package/dist/core/human/page-catalog.js +102 -30
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +23 -191
- package/dist/core/human/page-schema.js +60 -89
- package/dist/core/human/page-synth.d.ts +1 -1
- package/dist/core/human/page-synth.js +15 -18
- package/dist/core/human/page.d.ts +12 -27
- package/dist/core/human/page.js +142 -474
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -4
- package/dist/core/human/scan.js +16 -15
- package/dist/core/human/summary.d.ts +1 -2
- package/dist/core/human/summary.js +2 -2
- package/dist/core/human/tickets.d.ts +20 -14
- package/dist/core/human/tickets.js +91 -31
- package/dist/core/human/types.d.ts +10 -2
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +3 -3
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +17 -6
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +21 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +39 -115
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +14 -16
- package/dist/daemon/human/finish.d.ts +12 -7
- package/dist/daemon/human/finish.js +101 -38
- package/dist/daemon/human/sweep.js +5 -1
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +3 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.js +19 -5
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +44 -2
- package/dist/types.js +7 -2
- package/package.json +6 -5
- package/runtime.lock.json +5 -33
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/commands/human/doc.d.ts +0 -2
- package/dist/commands/human/doc.js +0 -91
- package/dist/commands/human/html.js +0 -81
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/html-markdown.test.js +0 -52
- package/dist/core/human/__tests__/page-render.test.js +0 -66
- package/dist/core/human/html-markdown.d.ts +0 -3
- package/dist/core/human/html-markdown.js +0 -288
- package/dist/core/human/page-render.d.ts +0 -4
- package/dist/core/human/page-render.js +0 -105
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1778
- package/dist/pages/comments.d.ts +0 -72
- package/dist/pages/comments.js +0 -176
- package/dist/pages/controls.d.ts +0 -98
- package/dist/pages/controls.js +0 -261
- package/dist/pages/elements/cards.d.ts +0 -28
- package/dist/pages/elements/cards.js +0 -915
- package/dist/pages/elements/chart.d.ts +0 -23
- package/dist/pages/elements/chart.js +0 -664
- package/dist/pages/elements/options.d.ts +0 -30
- package/dist/pages/elements/options.js +0 -742
- package/dist/pages/elements/pages.d.ts +0 -30
- package/dist/pages/elements/pages.js +0 -377
- package/dist/pages/elements/slot.d.ts +0 -26
- package/dist/pages/elements/slot.js +0 -112
- package/dist/pages/elements/table.d.ts +0 -36
- package/dist/pages/elements/table.js +0 -1043
- package/dist/pages/elements/text.d.ts +0 -31
- package/dist/pages/elements/text.js +0 -1175
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -134
- package/dist/pages/host.js +0 -165
- package/dist/pages/readonly.d.ts +0 -24
- package/dist/pages/readonly.js +0 -31
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/responses.d.ts +0 -37
- package/dist/pages/responses.js +0 -56
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
|
-
import { stateBlock } from '../../core/help.js';
|
|
3
|
-
import { InputError } from '../../core/io.js';
|
|
4
|
-
import { readConfig } from '../../core/config.js';
|
|
5
|
-
import { componentKinds, renderComponentCatalog, renderComponentContract } from '../../core/human/component-docs.js';
|
|
6
|
-
import { pageComponentKinds } from '../../core/human/page-catalog.js';
|
|
7
|
-
function productComponents() {
|
|
8
|
-
return readConfig('user').page_components;
|
|
9
|
-
}
|
|
10
|
-
function productKinds() {
|
|
11
|
-
return pageComponentKinds(productComponents());
|
|
12
|
-
}
|
|
13
|
-
function componentKindState() {
|
|
14
|
-
const kinds = componentKinds(productKinds());
|
|
15
|
-
return stateBlock('kinds', { count: kinds.length }, kinds.join('\n'));
|
|
16
|
-
}
|
|
17
|
-
const humanHtmlList = defineLeaf({
|
|
18
|
-
name: 'list',
|
|
19
|
-
description: 'list page components for selection',
|
|
20
|
-
whenToUse: 'choosing which page component to put in an authored HTML page.',
|
|
21
|
-
help: {
|
|
22
|
-
name: 'human html list',
|
|
23
|
-
summary: 'list page components for selection',
|
|
24
|
-
inputNote: 'No input parameters.',
|
|
25
|
-
output: [
|
|
26
|
-
{ name: 'catalog', type: 'markdown', required: true, constraint: 'One concise selection entry per built-in and product-registered component; no component field schemas.' },
|
|
27
|
-
{ name: 'follow_up', type: 'string', required: true, constraint: 'Points to `crtr human html show <kind>` for one selected component contract.' },
|
|
28
|
-
],
|
|
29
|
-
outputKind: 'object',
|
|
30
|
-
effects: ['None. Read-only.'],
|
|
31
|
-
},
|
|
32
|
-
run: async () => ({
|
|
33
|
-
catalog: renderComponentCatalog(productComponents()),
|
|
34
|
-
follow_up: 'Use `crtr human html show <kind>` for one component contract.',
|
|
35
|
-
}),
|
|
36
|
-
render: (result) => `${String(result['catalog'])}\n\n${String(result['follow_up'])}`,
|
|
37
|
-
});
|
|
38
|
-
const humanHtmlShow = defineLeaf({
|
|
39
|
-
name: 'show',
|
|
40
|
-
description: 'show one page component contract',
|
|
41
|
-
whenToUse: 'authoring or validating one selected page component and needing its exact config or response contract.',
|
|
42
|
-
help: {
|
|
43
|
-
name: 'human html show',
|
|
44
|
-
summary: 'show one page component contract',
|
|
45
|
-
params: [
|
|
46
|
-
{ kind: 'positional', name: 'kind', type: 'string', required: true, constraint: 'Component kind from the live <kinds> catalog below.' },
|
|
47
|
-
],
|
|
48
|
-
output: [
|
|
49
|
-
{ name: 'kind', type: 'string', required: true, constraint: 'The selected component kind.' },
|
|
50
|
-
{ name: 'contract', type: 'markdown', required: true, constraint: 'Hand-written config, response, and display/response-bearing contract for that component.' },
|
|
51
|
-
],
|
|
52
|
-
outputKind: 'object',
|
|
53
|
-
effects: ['None. Read-only.'],
|
|
54
|
-
dynamicState: componentKindState,
|
|
55
|
-
},
|
|
56
|
-
run: async (input) => {
|
|
57
|
-
const kind = input['kind'];
|
|
58
|
-
const contract = renderComponentContract(kind, productComponents());
|
|
59
|
-
if (contract === undefined) {
|
|
60
|
-
throw new InputError({
|
|
61
|
-
error: 'unknown_component',
|
|
62
|
-
message: `unknown page component kind "${kind}"`,
|
|
63
|
-
field: 'kind',
|
|
64
|
-
next: 'Run `crtr human html show -h` and choose a kind from its live catalog.',
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
return { kind, contract };
|
|
68
|
-
},
|
|
69
|
-
render: (result) => String(result['contract']),
|
|
70
|
-
});
|
|
71
|
-
export const humanHtml = defineBranch({
|
|
72
|
-
name: 'html',
|
|
73
|
-
description: 'discover HTML page components and contracts',
|
|
74
|
-
whenToUse: 'authoring an HTML page and choosing a component or needing one component’s exact contract.',
|
|
75
|
-
help: {
|
|
76
|
-
name: 'human html',
|
|
77
|
-
summary: 'discover HTML page components and contracts',
|
|
78
|
-
model: 'List is the selection catalog; show owns the full config and response contract for one selected component.',
|
|
79
|
-
},
|
|
80
|
-
children: [humanHtmlList, humanHtmlShow],
|
|
81
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { RICH_PAGE_PLACEHOLDER, renderHtmlProseAsMarkdown } from '../html-markdown.js';
|
|
4
|
-
test('downconverts bounded semantic HTML page prose without raw tag leakage', () => {
|
|
5
|
-
const markdown = renderHtmlProseAsMarkdown(`<!doctype html><html><head><title>ignored</title></head><body>
|
|
6
|
-
<h2>Release <em>notes</em></h2>
|
|
7
|
-
<p>Read <strong>this</strong>, <code>npm run build</code>, and <a href="https://example.test/a">details</a>.</p>
|
|
8
|
-
<ul><li>First</li><li>Second<ul><li>Nested</li></ul></li></ul>
|
|
9
|
-
<ol><li>One</li></ol>
|
|
10
|
-
<blockquote><p>Quoted</p></blockquote>
|
|
11
|
-
<pre><code class="language-ts">const x = 1;</code></pre>
|
|
12
|
-
<table><thead><tr><th>Name</th><th>State</th></tr></thead><tbody><tr><td>A</td><td>Ready</td></tr></tbody></table>
|
|
13
|
-
<p>Safe <span style="color:red">text</span>.</p>
|
|
14
|
-
<div class="custom-chrome">Widget controls</div>
|
|
15
|
-
<crtr-pages><crtr-page><p>Before <span>slot</span>.</p><crtr-options>ignored config</crtr-options><p>After.</p></crtr-page></crtr-pages>
|
|
16
|
-
<p>Use <span>x</span> literally.</p>
|
|
17
|
-
<p># Not heading<br>> Not quote<br>- Not list<br>1. Not ordered</p>
|
|
18
|
-
</body></html>`);
|
|
19
|
-
const expected = `## Release *notes*
|
|
20
|
-
|
|
21
|
-
Read **this**, \`npm run build\`, and [details](<https://example.test/a>).
|
|
22
|
-
|
|
23
|
-
- First
|
|
24
|
-
- Second
|
|
25
|
-
- Nested
|
|
26
|
-
|
|
27
|
-
1. One
|
|
28
|
-
|
|
29
|
-
> Quoted
|
|
30
|
-
|
|
31
|
-
\`\`\`ts
|
|
32
|
-
const x = 1;
|
|
33
|
-
\`\`\`
|
|
34
|
-
|
|
35
|
-
| Name | State |
|
|
36
|
-
| --- | --- |
|
|
37
|
-
| A | Ready |
|
|
38
|
-
|
|
39
|
-
Safe text.
|
|
40
|
-
|
|
41
|
-
${RICH_PAGE_PLACEHOLDER}
|
|
42
|
-
|
|
43
|
-
Before slot.
|
|
44
|
-
|
|
45
|
-
After.
|
|
46
|
-
|
|
47
|
-
Use <span>x</span> literally.
|
|
48
|
-
|
|
49
|
-
` + ['\\# Not heading ', '> Not quote ', '\\- Not list ', '1\\. Not ordered'].join('\n');
|
|
50
|
-
assert.equal(markdown, expected);
|
|
51
|
-
assert.doesNotMatch(markdown, /<(?:span|div)\b/i);
|
|
52
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// An authored HTML slot id must reach the runtime's `slot-id` attribute. Without
|
|
2
|
-
// this pin every element renders but no response reaches the host.
|
|
3
|
-
import assert from 'node:assert/strict';
|
|
4
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import test from 'node:test';
|
|
8
|
-
import { derivePageManifest } from '../page.js';
|
|
9
|
-
import { renderPageHtml } from '../page-render.js';
|
|
10
|
-
import { preparePage } from '../tickets.js';
|
|
11
|
-
function render(source) {
|
|
12
|
-
const dir = mkdtempSync(join(tmpdir(), 'crtr-page-render-test-'));
|
|
13
|
-
try {
|
|
14
|
-
const path = join(dir, 'page.html');
|
|
15
|
-
writeFileSync(path, source);
|
|
16
|
-
return renderPageHtml(derivePageManifest(path, 'html'), readFileSync(path, 'utf8'));
|
|
17
|
-
}
|
|
18
|
-
finally {
|
|
19
|
-
rmSync(dir, { recursive: true, force: true });
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
test('source-backed slots render embedded config, never a filesystem path', () => {
|
|
23
|
-
const dir = mkdtempSync(join(tmpdir(), 'crtr-page-render-source-'));
|
|
24
|
-
try {
|
|
25
|
-
const data = join(dir, 'rows.json');
|
|
26
|
-
const page = join(dir, 'page.html');
|
|
27
|
-
writeFileSync(data, JSON.stringify([{ id: 'one', cells: { name: '</script><script>window.pwned=1</script>' } }]));
|
|
28
|
-
writeFileSync(page, `<!doctype html><html><head><meta name="crtr-page-title" content="Rows"></head><body><crtr-table><script type="application/json">${JSON.stringify({ columns: [{ id: 'name', label: '</scripture>' }], source: data })}</script></crtr-table></body></html>`);
|
|
29
|
-
const prepared = preparePage({ sourceFile: page, dialect: 'html' });
|
|
30
|
-
const rendered = renderPageHtml(prepared.manifest, prepared.document);
|
|
31
|
-
assert.match(rendered, /"rows":\[{"id":"one","cells":\{"name":"\\u003c\/script>\\u003cscript>window\.pwned=1\\u003c\/script>"}}]/);
|
|
32
|
-
assert.match(rendered, /"label":"\\u003c\/scripture>"/);
|
|
33
|
-
assert.doesNotMatch(rendered, /"source"|rows\.json|<script>window\.pwned/);
|
|
34
|
-
}
|
|
35
|
-
finally {
|
|
36
|
-
rmSync(dir, { recursive: true, force: true });
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
test('render replacement ignores comments and non-config scripts', () => {
|
|
40
|
-
const dir = mkdtempSync(join(tmpdir(), 'crtr-page-render-scripts-'));
|
|
41
|
-
try {
|
|
42
|
-
const data = join(dir, 'rows.json');
|
|
43
|
-
const page = join(dir, 'page.html');
|
|
44
|
-
writeFileSync(data, JSON.stringify([{ id: 'one', cells: { name: 'One' } }]));
|
|
45
|
-
writeFileSync(page, `<!doctype html><html><head><meta name="crtr-page-title" content="Rows"></head><body><!-- > <crtr-table><script type="application/json">{}</script></crtr-table> --><crtr-table><script data-type="application/json" type="text/plain">KEEP</script><script type="application/json">${JSON.stringify({ columns: [{ id: 'name', label: 'Name' }], source: data })}</script></crtr-table></body></html>`);
|
|
46
|
-
const prepared = preparePage({ sourceFile: page, dialect: 'html' });
|
|
47
|
-
const rendered = renderPageHtml(prepared.manifest, prepared.document);
|
|
48
|
-
assert.match(rendered, /<script data-type="application\/json" type="text\/plain">KEEP<\/script>/);
|
|
49
|
-
assert.match(rendered, /"rows":\[{"id":"one","cells":\{"name":"One"}}]/);
|
|
50
|
-
assert.doesNotMatch(rendered, /"source"|rows\.json/);
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
rmSync(dir, { recursive: true, force: true });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
test('an authored html slot id is mirrored to the slot-id the runtime reads', () => {
|
|
57
|
-
const html = render([
|
|
58
|
-
'<!doctype html><html><head><meta name="crtr-page-title" content="Sign-off"></head><body>',
|
|
59
|
-
'<crtr-pages><crtr-page label="Window">',
|
|
60
|
-
'<crtr-options id="window"><script type="application/json">{"mode":"single","options":[{"id":"sat","label":"Saturday"}]}</script></crtr-options>',
|
|
61
|
-
'</crtr-page></crtr-pages>',
|
|
62
|
-
'</body></html>',
|
|
63
|
-
].join('\n'));
|
|
64
|
-
assert.match(html, /<crtr-options id="window" slot-id="window">/);
|
|
65
|
-
assert.match(html, /<crtr-page label="Window">/);
|
|
66
|
-
});
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
// Bounded semantic HTML → Markdown for terminal page prose. This is deliberately
|
|
2
|
-
// not an HTML renderer: slots keep their own terminal panels, while custom chrome
|
|
3
|
-
// is reduced to text or one clear Northlight handoff.
|
|
4
|
-
export const RICH_PAGE_PLACEHOLDER = 'rich page — open in Northlight';
|
|
5
|
-
const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
|
|
6
|
-
const RAW_TEXT_TAGS = new Set(['script', 'style', 'template']);
|
|
7
|
-
const RICH_TAGS = new Set(['audio', 'button', 'canvas', 'dialog', 'embed', 'form', 'iframe', 'img', 'input', 'math', 'object', 'picture', 'select', 'svg', 'textarea', 'video']);
|
|
8
|
-
const BLOCK_TAGS = new Set(['article', 'aside', 'blockquote', 'body', 'div', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'li', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul']);
|
|
9
|
-
const INLINE_BREAK = '\uE001';
|
|
10
|
-
/** Downconvert the semantic prose in one HTML fragment or complete document. */
|
|
11
|
-
export function renderHtmlProseAsMarkdown(html) {
|
|
12
|
-
const root = { type: 'element', tag: '#root', attrs: new Map(), children: parseHtml(html) };
|
|
13
|
-
const body = findElement(root, 'body');
|
|
14
|
-
return renderBlocks(body?.children ?? root.children).trim();
|
|
15
|
-
}
|
|
16
|
-
function parseHtml(html) {
|
|
17
|
-
const root = { type: 'element', tag: '#root', attrs: new Map(), children: [] };
|
|
18
|
-
const stack = [root];
|
|
19
|
-
let cursor = 0;
|
|
20
|
-
const appendText = (value) => { if (value !== '')
|
|
21
|
-
stack.at(-1).children.push({ type: 'text', value }); };
|
|
22
|
-
while (cursor < html.length) {
|
|
23
|
-
const start = html.indexOf('<', cursor);
|
|
24
|
-
if (start === -1) {
|
|
25
|
-
appendText(html.slice(cursor));
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
appendText(html.slice(cursor, start));
|
|
29
|
-
if (html.startsWith('<!--', start)) {
|
|
30
|
-
const end = html.indexOf('-->', start + 4);
|
|
31
|
-
cursor = end === -1 ? html.length : end + 3;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const end = tagEnd(html, start);
|
|
35
|
-
if (end === -1) {
|
|
36
|
-
appendText(html.slice(start));
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
const token = html.slice(start, end);
|
|
40
|
-
if (/^<!|^<\?/i.test(token)) {
|
|
41
|
-
cursor = end;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const closing = /^<\s*\/\s*([^\s>]+)/.exec(token);
|
|
45
|
-
if (closing !== null) {
|
|
46
|
-
const tag = closing[1].toLowerCase();
|
|
47
|
-
for (let index = stack.length - 1; index > 0; index--) {
|
|
48
|
-
if (stack[index].tag === tag) {
|
|
49
|
-
stack.length = index;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
cursor = end;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const opening = /^<\s*([^\s/>]+)/.exec(token);
|
|
57
|
-
if (opening === null) {
|
|
58
|
-
appendText(token);
|
|
59
|
-
cursor = end;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const tag = opening[1].toLowerCase();
|
|
63
|
-
const element = { type: 'element', tag, attrs: parseAttributes(token), children: [] };
|
|
64
|
-
stack.at(-1).children.push(element);
|
|
65
|
-
const selfClosing = /\/\s*>$/.test(token) || VOID_TAGS.has(tag);
|
|
66
|
-
if (RAW_TEXT_TAGS.has(tag) && !selfClosing) {
|
|
67
|
-
const close = new RegExp(`<\\s*/\\s*${escapeRegExp(tag)}\\s*>`, 'ig');
|
|
68
|
-
close.lastIndex = end;
|
|
69
|
-
const match = close.exec(html);
|
|
70
|
-
if (match !== null) {
|
|
71
|
-
element.children.push({ type: 'text', value: html.slice(end, match.index) });
|
|
72
|
-
cursor = close.lastIndex;
|
|
73
|
-
}
|
|
74
|
-
else
|
|
75
|
-
cursor = html.length;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (!selfClosing)
|
|
79
|
-
stack.push(element);
|
|
80
|
-
cursor = end;
|
|
81
|
-
}
|
|
82
|
-
return root.children;
|
|
83
|
-
}
|
|
84
|
-
function tagEnd(html, start) {
|
|
85
|
-
let quote;
|
|
86
|
-
for (let index = start + 1; index < html.length; index++) {
|
|
87
|
-
const character = html[index];
|
|
88
|
-
if (quote !== undefined) {
|
|
89
|
-
if (character === quote)
|
|
90
|
-
quote = undefined;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (character === '"' || character === "'") {
|
|
94
|
-
quote = character;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (character === '>')
|
|
98
|
-
return index + 1;
|
|
99
|
-
}
|
|
100
|
-
return -1;
|
|
101
|
-
}
|
|
102
|
-
function parseAttributes(token) {
|
|
103
|
-
const attrs = new Map();
|
|
104
|
-
const source = token.slice(1, -1);
|
|
105
|
-
const firstSpace = source.search(/\s/);
|
|
106
|
-
if (firstSpace === -1)
|
|
107
|
-
return attrs;
|
|
108
|
-
const body = source.slice(firstSpace);
|
|
109
|
-
const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
110
|
-
for (const match of body.matchAll(pattern))
|
|
111
|
-
attrs.set(match[1].toLowerCase(), match[2] ?? match[3] ?? match[4] ?? '');
|
|
112
|
-
return attrs;
|
|
113
|
-
}
|
|
114
|
-
function findElement(node, tag) {
|
|
115
|
-
if (node.tag === tag)
|
|
116
|
-
return node;
|
|
117
|
-
for (const child of node.children)
|
|
118
|
-
if (child.type === 'element') {
|
|
119
|
-
const found = findElement(child, tag);
|
|
120
|
-
if (found !== undefined)
|
|
121
|
-
return found;
|
|
122
|
-
}
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
function renderBlocks(nodes) {
|
|
126
|
-
const blocks = [];
|
|
127
|
-
for (const node of nodes) {
|
|
128
|
-
const block = renderBlock(node).trim();
|
|
129
|
-
if (block !== '')
|
|
130
|
-
blocks.push(block);
|
|
131
|
-
}
|
|
132
|
-
return blocks.join('\n\n');
|
|
133
|
-
}
|
|
134
|
-
function renderBlock(node) {
|
|
135
|
-
if (node.type === 'text')
|
|
136
|
-
return markdownText(node.value).trim();
|
|
137
|
-
if (isInteractiveSlot(node))
|
|
138
|
-
return '';
|
|
139
|
-
if (isStructuralWrapper(node))
|
|
140
|
-
return renderBlocks(node.children);
|
|
141
|
-
if (isRichBlock(node))
|
|
142
|
-
return RICH_PAGE_PLACEHOLDER;
|
|
143
|
-
if (/^h[1-6]$/.test(node.tag))
|
|
144
|
-
return `${'#'.repeat(Number(node.tag[1]))} ${renderInline(node.children)}`;
|
|
145
|
-
if (node.tag === 'p')
|
|
146
|
-
return renderInline(node.children);
|
|
147
|
-
if (node.tag === 'blockquote')
|
|
148
|
-
return quote(renderBlocks(node.children));
|
|
149
|
-
if (node.tag === 'pre')
|
|
150
|
-
return fencedCode(node);
|
|
151
|
-
if (node.tag === 'ul' || node.tag === 'ol')
|
|
152
|
-
return renderList(node);
|
|
153
|
-
if (node.tag === 'table')
|
|
154
|
-
return renderTable(node);
|
|
155
|
-
if (node.tag === 'hr')
|
|
156
|
-
return '---';
|
|
157
|
-
if (node.tag === 'br')
|
|
158
|
-
return '';
|
|
159
|
-
if (BLOCK_TAGS.has(node.tag))
|
|
160
|
-
return node.children.some((child) => child.type === 'element' && BLOCK_TAGS.has(child.tag)) ? renderBlocks(node.children) : renderInline(node.children);
|
|
161
|
-
return renderInline(node.children);
|
|
162
|
-
}
|
|
163
|
-
function renderInline(nodes) {
|
|
164
|
-
return nodes.map(renderInlineNode).join('').replace(/\s+/g, ' ').replaceAll(INLINE_BREAK, ' \n').trim();
|
|
165
|
-
}
|
|
166
|
-
function renderInlineNode(node) {
|
|
167
|
-
if (node.type === 'text')
|
|
168
|
-
return markdownText(node.value);
|
|
169
|
-
if (isInteractiveSlot(node) || RAW_TEXT_TAGS.has(node.tag))
|
|
170
|
-
return '';
|
|
171
|
-
const content = renderInline(node.children);
|
|
172
|
-
if (node.tag === 'br')
|
|
173
|
-
return INLINE_BREAK;
|
|
174
|
-
if (isStructuralWrapper(node))
|
|
175
|
-
return content;
|
|
176
|
-
if (node.tag === 'strong' || node.tag === 'b')
|
|
177
|
-
return content === '' ? '' : `**${content}**`;
|
|
178
|
-
if (node.tag === 'em' || node.tag === 'i')
|
|
179
|
-
return content === '' ? '' : `*${content}*`;
|
|
180
|
-
if (node.tag === 'code')
|
|
181
|
-
return codeSpan(textContent(node));
|
|
182
|
-
if (node.tag === 'a') {
|
|
183
|
-
const href = node.attrs.get('href');
|
|
184
|
-
return href === undefined || href === '' || content === '' ? content : `[${content}](${markdownUrl(href)})`;
|
|
185
|
-
}
|
|
186
|
-
if (BLOCK_TAGS.has(node.tag))
|
|
187
|
-
return content;
|
|
188
|
-
// Unknown inline markup is prose, not terminal UI. Keeping its text prevents
|
|
189
|
-
// raw `<span>`-style tags from leaking through termrender.
|
|
190
|
-
return content;
|
|
191
|
-
}
|
|
192
|
-
function renderList(element) {
|
|
193
|
-
const items = element.children.filter((child) => child.type === 'element' && child.tag === 'li');
|
|
194
|
-
return items.map((item, index) => {
|
|
195
|
-
const nested = item.children.filter((child) => child.type === 'element' && (child.tag === 'ul' || child.tag === 'ol'));
|
|
196
|
-
const content = item.children.filter((child) => !(child.type === 'element' && (child.tag === 'ul' || child.tag === 'ol')));
|
|
197
|
-
const marker = element.tag === 'ol' ? `${index + 1}.` : '-';
|
|
198
|
-
const first = renderListItem(content);
|
|
199
|
-
const children = nested.map(renderList).filter(Boolean).map((list) => list.split('\n').map((line) => ` ${line}`).join('\n'));
|
|
200
|
-
return [first === '' ? marker : `${marker} ${first}`, ...children].join('\n');
|
|
201
|
-
}).join('\n');
|
|
202
|
-
}
|
|
203
|
-
function renderListItem(nodes) {
|
|
204
|
-
const blocks = renderBlocks(nodes);
|
|
205
|
-
return blocks.replace(/\n\n+/g, '\n').replace(/\n/g, '\n ');
|
|
206
|
-
}
|
|
207
|
-
function renderTable(table) {
|
|
208
|
-
const rows = findElements(table, 'tr').map((row) => row.children.filter((child) => child.type === 'element' && (child.tag === 'th' || child.tag === 'td')).map((cell) => renderInline(cell.children).replace(/\|/g, '\\|'))).filter((row) => row.length > 0);
|
|
209
|
-
if (rows.length === 0)
|
|
210
|
-
return '';
|
|
211
|
-
const width = Math.max(...rows.map((row) => row.length));
|
|
212
|
-
const normalized = rows.map((row) => Array.from({ length: width }, (_, index) => row[index] ?? ''));
|
|
213
|
-
const header = normalized[0];
|
|
214
|
-
const data = normalized.slice(1);
|
|
215
|
-
return [`| ${header.join(' | ')} |`, `| ${header.map(() => '---').join(' | ')} |`, ...data.map((row) => `| ${row.join(' | ')} |`)].join('\n');
|
|
216
|
-
}
|
|
217
|
-
function fencedCode(pre) {
|
|
218
|
-
const code = pre.children.find((child) => child.type === 'element' && child.tag === 'code');
|
|
219
|
-
const source = textContent(code ?? pre).replace(/\r\n?/g, '\n').replace(/\n$/, '');
|
|
220
|
-
const fence = source.includes('```') ? '~~~' : '```';
|
|
221
|
-
const language = code?.attrs.get('class')?.match(/(?:^|\s)language-([^\s]+)/)?.[1] ?? '';
|
|
222
|
-
return `${fence}${language}\n${source}\n${fence}`;
|
|
223
|
-
}
|
|
224
|
-
function quote(markdown) {
|
|
225
|
-
return markdown.split('\n').map((line) => line === '' ? '>' : `> ${line}`).join('\n');
|
|
226
|
-
}
|
|
227
|
-
function textContent(node) {
|
|
228
|
-
if (node.type === 'text')
|
|
229
|
-
return decodeEntities(node.value);
|
|
230
|
-
return node.children.map(textContent).join('');
|
|
231
|
-
}
|
|
232
|
-
function markdownText(value) {
|
|
233
|
-
return decodeEntities(value)
|
|
234
|
-
.replace(/\\/g, '\\\\')
|
|
235
|
-
.replace(/[<>]/g, (character) => character === '<' ? '<' : '>')
|
|
236
|
-
.replace(/(^|\n)(?=(?:#{1,6}\s|[-+]\s))/g, '$1\\')
|
|
237
|
-
.replace(/(^|\n)(\d+)([.)])(?=\s)/g, '$1$2\\$3')
|
|
238
|
-
.replace(/([\[\]*_`|])/g, '\\$1');
|
|
239
|
-
}
|
|
240
|
-
function codeSpan(value) {
|
|
241
|
-
const source = value.replace(/\r?\n/g, ' ');
|
|
242
|
-
const delimiter = source.includes('`') ? '``' : '`';
|
|
243
|
-
return `${delimiter}${source}${delimiter}`;
|
|
244
|
-
}
|
|
245
|
-
function markdownUrl(value) {
|
|
246
|
-
return `<${decodeEntities(value).replace(/>/g, '%3E')}>`;
|
|
247
|
-
}
|
|
248
|
-
function isStructuralWrapper(node) {
|
|
249
|
-
return node.tag === 'crtr-pages' || node.tag === 'crtr-page';
|
|
250
|
-
}
|
|
251
|
-
function isInteractiveSlot(node) {
|
|
252
|
-
return !isStructuralWrapper(node) && (node.tag === 'crtr-slot' || node.tag.startsWith('crtr-'));
|
|
253
|
-
}
|
|
254
|
-
function isRichBlock(node) {
|
|
255
|
-
if (RAW_TEXT_TAGS.has(node.tag) || RICH_TAGS.has(node.tag) || node.tag.includes('-'))
|
|
256
|
-
return true;
|
|
257
|
-
if (!BLOCK_TAGS.has(node.tag))
|
|
258
|
-
return false;
|
|
259
|
-
return [...node.attrs.keys()].some((attribute) => attribute === 'class' || attribute === 'style' || attribute === 'id' || attribute.startsWith('on'));
|
|
260
|
-
}
|
|
261
|
-
function findElements(node, tag) {
|
|
262
|
-
const found = [];
|
|
263
|
-
for (const child of node.children)
|
|
264
|
-
if (child.type === 'element') {
|
|
265
|
-
if (child.tag === tag)
|
|
266
|
-
found.push(child);
|
|
267
|
-
found.push(...findElements(child, tag));
|
|
268
|
-
}
|
|
269
|
-
return found;
|
|
270
|
-
}
|
|
271
|
-
function decodeEntities(value) {
|
|
272
|
-
return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[0-9a-f]+|\d+);/gi, (entity) => {
|
|
273
|
-
const named = { '&': '&', ''': "'", '>': '>', '<': '<', '"': '"' };
|
|
274
|
-
const lower = entity.toLowerCase();
|
|
275
|
-
if (named[lower] !== undefined)
|
|
276
|
-
return named[lower];
|
|
277
|
-
const numeric = lower.startsWith('&#x') ? Number.parseInt(lower.slice(3, -1), 16) : Number.parseInt(lower.slice(2, -1), 10);
|
|
278
|
-
try {
|
|
279
|
-
return Number.isFinite(numeric) ? String.fromCodePoint(numeric) : entity;
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
return entity;
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
function escapeRegExp(value) {
|
|
287
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
288
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ProductPageComponents } from '../../types.js';
|
|
2
|
-
import type { PageManifest } from './page-schema.js';
|
|
3
|
-
/** Render one page ticket's authored document as the runtime's HTML payload. */
|
|
4
|
-
export declare function renderPageHtml(manifest: PageManifest, document: string, productKinds?: ProductPageComponents): string;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// One authored HTML document → the body-level HTML payload the page runtime
|
|
2
|
-
// (`src/pages`) boots against.
|
|
3
|
-
//
|
|
4
|
-
// What the payload carries: in-document chrome only — the page's own HTML and
|
|
5
|
-
// `crtr-*` slot elements.
|
|
6
|
-
//
|
|
7
|
-
// What it deliberately does NOT carry: the runtime bundle's `<script>`/`<link>`
|
|
8
|
-
// tags (the consuming host injects its own bundle URLs), the page title and
|
|
9
|
-
// subtitle (host-rendered from the manifest), and any dismiss/save chrome
|
|
10
|
-
// (host-owned). The payload is a fragment, not a document: the host wraps it.
|
|
11
|
-
import { embedPageSlotConfigs, parsePageSource } from './page.js';
|
|
12
|
-
/** Render one page ticket's authored document as the runtime's HTML payload. */
|
|
13
|
-
export function renderPageHtml(manifest, document, productKinds = []) {
|
|
14
|
-
if (manifest.dialect !== 'html') {
|
|
15
|
-
throw new Error('legacy md pages render through the terminal inbox only');
|
|
16
|
-
}
|
|
17
|
-
// Re-projects the document and re-verifies it against the manifest, so the
|
|
18
|
-
// payload can never disagree with the slots the manifest publishes.
|
|
19
|
-
parsePageSource(document, manifest, productKinds);
|
|
20
|
-
return renderAuthoredHtml(embedPageSlotConfigs(document, manifest));
|
|
21
|
-
}
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// html dialect — normalized passthrough
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
/** The authored document's body, after stored slot configs replace authored configs,
|
|
26
|
-
* `<style>` elements from `<head>` are hoisted in front of it, and every `crtr-*`
|
|
27
|
-
* slot element carrying an authored `id` gains the matching `slot-id` that the
|
|
28
|
-
* runtime reads. Authored `<head>` `<link>` and `<script>` elements do not survive:
|
|
29
|
-
* the page runtime is the only script a page needs. */
|
|
30
|
-
function renderAuthoredHtml(document) {
|
|
31
|
-
const styles = headStyles(document);
|
|
32
|
-
const body = normalizeSlotIds(bodyInnerHtml(document)).trim();
|
|
33
|
-
return styles.length === 0 ? body : `${styles.join('\n')}\n${body}`;
|
|
34
|
-
}
|
|
35
|
-
/** Index just past the `>` closing the tag that starts at `start`. Quote-aware,
|
|
36
|
-
* so an attribute value containing `>` cannot end the tag early. */
|
|
37
|
-
function tagEndIndex(html, start) {
|
|
38
|
-
let quote = null;
|
|
39
|
-
for (let index = start + 1; index < html.length; index++) {
|
|
40
|
-
const character = html[index];
|
|
41
|
-
if (quote !== null) {
|
|
42
|
-
if (character === quote)
|
|
43
|
-
quote = null;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (character === '"' || character === "'") {
|
|
47
|
-
quote = character;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (character === '>')
|
|
51
|
-
return index + 1;
|
|
52
|
-
}
|
|
53
|
-
throw new Error(`unterminated HTML tag beginning at byte ${start}`);
|
|
54
|
-
}
|
|
55
|
-
function bodyInnerHtml(document) {
|
|
56
|
-
const open = /<body(?=[\s>])/i.exec(document);
|
|
57
|
-
if (open === null)
|
|
58
|
-
throw new Error('page document has no <body> element');
|
|
59
|
-
const start = tagEndIndex(document, open.index);
|
|
60
|
-
const close = document.toLowerCase().lastIndexOf('</body');
|
|
61
|
-
return close === -1 ? document.slice(start) : document.slice(start, close);
|
|
62
|
-
}
|
|
63
|
-
function headStyles(document) {
|
|
64
|
-
const open = /<head(?=[\s>])/i.exec(document);
|
|
65
|
-
if (open === null)
|
|
66
|
-
return [];
|
|
67
|
-
const start = tagEndIndex(document, open.index);
|
|
68
|
-
const close = document.toLowerCase().indexOf('</head', start);
|
|
69
|
-
const head = document.slice(start, close === -1 ? document.length : close);
|
|
70
|
-
return [...head.matchAll(/<style\b[^>]*>[\s\S]*?<\/style>/gi)].map((match) => match[0]);
|
|
71
|
-
}
|
|
72
|
-
const PAGER_TAGS = new Set(['crtr-pages', 'crtr-page']);
|
|
73
|
-
function escapeHtmlAttribute(value) {
|
|
74
|
-
return value.replace(/[&<>"]/g, (character) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[character]);
|
|
75
|
-
}
|
|
76
|
-
function normalizeSlotIds(html) {
|
|
77
|
-
let out = '';
|
|
78
|
-
let cursor = 0;
|
|
79
|
-
for (;;) {
|
|
80
|
-
const at = html.toLowerCase().indexOf('<crtr-', cursor);
|
|
81
|
-
if (at === -1) {
|
|
82
|
-
out += html.slice(cursor);
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
const end = tagEndIndex(html, at);
|
|
86
|
-
const tag = html.slice(at, end);
|
|
87
|
-
out += html.slice(cursor, at) + withSlotId(tag);
|
|
88
|
-
cursor = end;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function withSlotId(tag) {
|
|
92
|
-
const name = /^<([A-Za-z][A-Za-z0-9-]*)/.exec(tag)?.[1]?.toLowerCase();
|
|
93
|
-
if (name === undefined || PAGER_TAGS.has(name))
|
|
94
|
-
return tag;
|
|
95
|
-
const attributes = new Map();
|
|
96
|
-
for (const match of tag.slice(1 + name.length, -1).matchAll(/([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g)) {
|
|
97
|
-
attributes.set(match[1].toLowerCase(), match[2] ?? match[3] ?? match[4] ?? '');
|
|
98
|
-
}
|
|
99
|
-
const id = attributes.get('id');
|
|
100
|
-
if (id === undefined || id === '' || attributes.has('slot-id'))
|
|
101
|
-
return tag;
|
|
102
|
-
const selfClosing = /\/\s*>$/.test(tag);
|
|
103
|
-
const head = tag.slice(0, tag.lastIndexOf(selfClosing ? '/' : '>')).trimEnd();
|
|
104
|
-
return `${head} slot-id="${escapeHtmlAttribute(id)}"${selfClosing ? ' />' : '>'}`;
|
|
105
|
-
}
|
package/dist/pages/bundle.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:where(:root,:host){color-scheme:var(--crtr-color-scheme, light dark);--crtr-color-background: #ffffff;--crtr-color-surface: #f7f7f8;--crtr-color-surface-hover: #eeeeef;--crtr-color-text: #1c1c1f;--crtr-color-text-muted: #68686f;--crtr-color-border: #d9d9de;--crtr-color-accent: #5753c9;--crtr-color-accent-contrast: #ffffff;--crtr-color-danger: #c73535;--crtr-color-focus: #706bea;--crtr-chart-1: #5753c9;--crtr-chart-2: #2c8c75;--crtr-chart-3: #cf7c2a;--crtr-chart-4: #b84c84;--crtr-chart-5: #477eb8;--crtr-font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--crtr-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--crtr-font-size: .875rem;--crtr-line-height: 1.5;--crtr-radius-small: .375rem;--crtr-radius-medium: .625rem;--crtr-space-1: .25rem;--crtr-space-2: .5rem;--crtr-space-3: .75rem;--crtr-space-4: 1rem;--crtr-shadow: 0 1px 2px rgb(0 0 0 / .08)}:where(crtr-options){color:var(--crtr-options-color, var(--crtr-color-text));font-family:var(--crtr-options-font-family, var(--crtr-font-family));font-size:var(--crtr-options-font-size, var(--crtr-font-size));line-height:var(--crtr-options-line-height, var(--crtr-line-height))}:where(crtr-text){color:var(--crtr-text-color, var(--crtr-color-text));font-family:var(--crtr-text-font-family, var(--crtr-font-family));font-size:var(--crtr-text-font-size, var(--crtr-font-size));line-height:var(--crtr-text-line-height, var(--crtr-line-height))}:where(crtr-table){color:var(--crtr-table-color, var(--crtr-color-text));font-family:var(--crtr-table-font-family, var(--crtr-font-family));font-size:var(--crtr-table-font-size, var(--crtr-font-size));line-height:var(--crtr-table-line-height, var(--crtr-line-height))}:where(crtr-cards){color:var(--crtr-cards-color, var(--crtr-color-text));font-family:var(--crtr-cards-font-family, var(--crtr-font-family));font-size:var(--crtr-cards-font-size, var(--crtr-font-size));line-height:var(--crtr-cards-line-height, var(--crtr-line-height))}:where(crtr-chart){color:var(--crtr-chart-color, var(--crtr-color-text));font-family:var(--crtr-chart-font-family, var(--crtr-font-family));font-size:var(--crtr-chart-font-size, var(--crtr-font-size));line-height:var(--crtr-chart-line-height, var(--crtr-line-height))}:where(crtr-pages,crtr-page){color:var(--crtr-page-color, var(--crtr-color-text));font-family:var(--crtr-page-font-family, var(--crtr-font-family));font-size:var(--crtr-page-font-size, var(--crtr-font-size));line-height:var(--crtr-page-line-height, var(--crtr-line-height))}:where(crtr-slot){color:var(--crtr-slot-color, var(--crtr-color-text));font-family:var(--crtr-slot-font-family, var(--crtr-font-family));font-size:var(--crtr-slot-font-size, var(--crtr-font-size));line-height:var(--crtr-slot-line-height, var(--crtr-line-height))}@media(prefers-color-scheme:dark){:where(:root,:host){--crtr-color-background: #161618;--crtr-color-surface: #222225;--crtr-color-surface-hover: #2d2d31;--crtr-color-text: #f2f2f3;--crtr-color-text-muted: #aaaab1;--crtr-color-border: #414147;--crtr-color-accent: #9692ff;--crtr-color-accent-contrast: #18181b;--crtr-color-danger: #f06a6a;--crtr-color-focus: #aaa7ff;--crtr-shadow: 0 1px 2px rgb(0 0 0 / .45);--crtr-chart-1: #9692ff;--crtr-chart-2: #4fc3a4;--crtr-chart-3: #eba05a;--crtr-chart-4: #e281b4;--crtr-chart-5: #71b0e6}}
|