@inflexa-ai/harness 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/report-builder.d.ts +2 -0
- package/dist/agents/report-builder.js +2 -0
- package/dist/agents/report-session-agent.d.ts +56 -0
- package/dist/agents/report-session-agent.js +72 -0
- package/dist/app/report-session-runtime.d.ts +72 -0
- package/dist/app/report-session-runtime.js +157 -0
- package/dist/app/spawn-report-session.d.ts +85 -0
- package/dist/app/spawn-report-session.js +83 -0
- package/dist/contracts/chat-events.d.ts +9 -0
- package/dist/contracts/index.d.ts +6 -2
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/organ-system.d.ts +49 -0
- package/dist/contracts/organ-system.js +67 -0
- package/dist/contracts/report-blocks.d.ts +564 -0
- package/dist/contracts/report-blocks.js +117 -0
- package/dist/contracts/report-reference.d.ts +459 -0
- package/dist/contracts/report-reference.js +256 -0
- package/dist/contracts/schemas/chat-events.d.ts +2 -0
- package/dist/contracts/schemas/chat-events.js +2 -0
- package/dist/contracts/severity.d.ts +15 -0
- package/dist/contracts/severity.js +10 -0
- package/dist/contracts/target-dossier.d.ts +8675 -12972
- package/dist/contracts/target-dossier.js +534 -591
- package/dist/data/safety-panel-schema.d.ts +18 -5
- package/dist/data/safety-panel-schema.js +4 -4
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/report-runner.js +1 -3
- package/dist/execution/run-synthesis.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -0
- package/dist/lib/async-utils.d.ts +16 -4
- package/dist/lib/async-utils.js +20 -15
- package/dist/lib/host-concurrency.js +1 -0
- package/dist/loop/__fixtures__/scripted-provider.js +23 -2
- package/dist/loop/run-agent.js +58 -10
- package/dist/loop/tool-detail.d.ts +40 -0
- package/dist/loop/tool-detail.js +35 -3
- package/dist/loop/types.d.ts +13 -0
- package/dist/memory/thread-history.d.ts +10 -0
- package/dist/memory/thread-history.js +12 -1
- package/dist/memory/thread-store.d.ts +4 -4
- package/dist/memory/thread-store.js +18 -9
- package/dist/prompts/report-session.d.ts +1 -0
- package/dist/prompts/report-session.js +89 -0
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
- package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
- package/dist/prompts/target-assessment/investigation/index.js +3 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
- package/dist/providers/__fixtures__/session.d.ts +5 -4
- package/dist/providers/__fixtures__/session.js +4 -4
- package/dist/providers/ai-sdk.d.ts +77 -0
- package/dist/providers/ai-sdk.js +149 -19
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +103 -18
- package/dist/providers/types.d.ts +16 -0
- package/dist/providers/types.js +10 -1
- package/dist/report-model/block-walk.d.ts +56 -0
- package/dist/report-model/block-walk.js +89 -0
- package/dist/report-model/draft-finish.d.ts +60 -0
- package/dist/report-model/draft-finish.js +56 -0
- package/dist/report-model/draft-operations.d.ts +125 -0
- package/dist/report-model/draft-operations.js +341 -0
- package/dist/report-model/draft-read.d.ts +59 -0
- package/dist/report-model/draft-read.js +109 -0
- package/dist/report-model/draft.d.ts +51 -0
- package/dist/report-model/draft.js +32 -0
- package/dist/report-model/fixture-resolver.d.ts +8 -0
- package/dist/report-model/fixture-resolver.js +252 -0
- package/dist/report-model/pin-snapshot.d.ts +30 -0
- package/dist/report-model/pin-snapshot.js +40 -0
- package/dist/report-model/reference-resolver.d.ts +113 -0
- package/dist/report-model/reference-resolver.js +60 -0
- package/dist/report-model/structural-validation.d.ts +22 -0
- package/dist/report-model/structural-validation.js +68 -0
- package/dist/report-model/validate.d.ts +72 -0
- package/dist/report-model/validate.js +95 -0
- package/dist/report-render/chart.d.ts +33 -0
- package/dist/report-render/chart.js +538 -0
- package/dist/report-render/page.d.ts +127 -0
- package/dist/report-render/page.js +307 -0
- package/dist/report-render/references.d.ts +32 -0
- package/dist/report-render/references.js +44 -0
- package/dist/report-render/render.d.ts +25 -0
- package/dist/report-render/render.js +125 -0
- package/dist/report-render/script-json.d.ts +13 -0
- package/dist/report-render/script-json.js +15 -0
- package/dist/report-render/types.d.ts +39 -0
- package/dist/report-render/types.js +10 -0
- package/dist/report-render/value-bridge.d.ts +62 -0
- package/dist/report-render/value-bridge.js +82 -0
- package/dist/report-render/views/chart-view.d.ts +11 -0
- package/dist/report-render/views/chart-view.js +23 -0
- package/dist/report-render/views/page-view.d.ts +20 -0
- package/dist/report-render/views/page-view.js +49 -0
- package/dist/report-render/views/prose.d.ts +28 -0
- package/dist/report-render/views/prose.js +75 -0
- package/dist/report-render/views/references-view.d.ts +20 -0
- package/dist/report-render/views/references-view.js +64 -0
- package/dist/report-render/views/values.d.ts +42 -0
- package/dist/report-render/views/values.js +43 -0
- package/dist/runtime/assemble.d.ts +15 -0
- package/dist/runtime/assemble.js +28 -6
- package/dist/runtime/boot.d.ts +3 -3
- package/dist/runtime/boot.js +15 -4
- package/dist/runtime/dbos.d.ts +4 -4
- package/dist/state/artifacts.d.ts +16 -0
- package/dist/state/artifacts.js +18 -0
- package/dist/state/init.js +54 -0
- package/dist/state/purge-analysis.js +2 -0
- package/dist/state/report-session-state.d.ts +112 -0
- package/dist/state/report-session-state.js +150 -0
- package/dist/state/report-versions.d.ts +129 -0
- package/dist/state/report-versions.js +167 -0
- package/dist/state/snapshot-parse.d.ts +34 -0
- package/dist/state/snapshot-parse.js +82 -0
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/ad-hoc-router.d.ts +7 -2
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/keys.d.ts +3 -2
- package/dist/tools/bio/keys.js +3 -2
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.d.ts +3 -3
- package/dist/tools/bio/opentargets.js +4 -3
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/target-safety.d.ts +4 -6
- package/dist/tools/bio/target-safety.js +7 -6
- package/dist/tools/define-tool.d.ts +19 -12
- package/dist/tools/define-tool.js +4 -2
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/lib/chembl-client.d.ts +2 -0
- package/dist/tools/lib/chembl-client.js +1 -0
- package/dist/tools/lib/monarch-client.d.ts +42 -0
- package/dist/tools/lib/monarch-client.js +100 -0
- package/dist/tools/report/build-report.js +1 -0
- package/dist/tools/report/mint-preview-url.js +1 -0
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report/submit-report.js +1 -0
- package/dist/tools/report/version-fs.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +241 -0
- package/dist/tools/report-authoring/authoring-tools.js +460 -0
- package/dist/tools/report-session/index.d.ts +8 -0
- package/dist/tools/report-session/index.js +8 -0
- package/dist/tools/report-session/preview-report.d.ts +90 -0
- package/dist/tools/report-session/preview-report.js +245 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +1 -0
- package/dist/tools/research/generate-plan.js +7 -2
- package/dist/tools/research/inspect-data-profile.js +4 -0
- package/dist/tools/research/literature-reviewer.js +1 -0
- package/dist/tools/research/search-arxiv.js +1 -0
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.js +1 -0
- package/dist/tools/sandbox/list-available-packages.js +23 -0
- package/dist/tools/sandbox/list-available-refs.js +36 -0
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/workspace/file-stat.js +1 -0
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +5 -1
- package/dist/workflows/execute-analysis.js +5 -5
- package/dist/workflows/execute-target-assessment.d.ts +13 -2
- package/dist/workflows/execute-target-assessment.js +78 -16
- package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
- package/dist/workflows/target-assessment/assemblers/index.js +1 -1
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
- package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
- package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
- package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
- package/dist/workflows/target-assessment/collectors/index.js +48 -5
- package/dist/workflows/target-assessment/coverage.d.ts +43 -27
- package/dist/workflows/target-assessment/coverage.js +33 -29
- package/dist/workflows/target-assessment/fanout/index.js +1 -0
- package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
- package/dist/workflows/target-assessment/investigation/index.js +419 -0
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
- package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
- package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
- package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
- package/dist/workflows/target-assessment/metrics.d.ts +1 -6
- package/dist/workflows/target-assessment/metrics.js +1 -13
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
- package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
- package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
- package/dist/workflows/target-assessment/phase5-persist.js +56 -173
- package/dist/workflows/target-assessment/schemas.d.ts +46 -1
- package/dist/workflows/target-assessment/schemas.js +19 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
- package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
- package/dist/workflows/target-assessment/synthesis/index.js +4 -1
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
- package/package.json +4 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page walk: the document walk, the value-map validation, and the problem collection.
|
|
3
|
+
*
|
|
4
|
+
* `renderReportPage` walks the sections in document order. It dispatches each block to its kind renderer,
|
|
5
|
+
* and it validates the bound value against the shape that the block needs. A missing entry, or an entry of
|
|
6
|
+
* the wrong shape, becomes one typed problem. The walk collects every problem, thus one render reports each
|
|
7
|
+
* fault at one time. A page with no problem assembles to one HTML string.
|
|
8
|
+
*
|
|
9
|
+
* One shared `ReferenceLedger` threads through the whole walk. Thus a claim marker and a citation marker
|
|
10
|
+
* count by first appearance across the page, and a reference that two blocks share keeps one number and one
|
|
11
|
+
* list entry.
|
|
12
|
+
*
|
|
13
|
+
* The output is a pure function of the two inputs. The renderer reads no clock, no random value, and no
|
|
14
|
+
* locale. Thus the same document and the same values give the same bytes.
|
|
15
|
+
*/
|
|
16
|
+
import { err, ok } from "neverthrow";
|
|
17
|
+
import { renderChart } from "./views/chart-view.js";
|
|
18
|
+
import { deriveChartOption } from "./chart.js";
|
|
19
|
+
import { assemblePage, renderReferenceSection } from "./views/page-view.js";
|
|
20
|
+
import { renderClaim, renderNav, renderSection, renderText } from "./views/prose.js";
|
|
21
|
+
import { ReferenceLedger } from "./references.js";
|
|
22
|
+
import { renderCitation, renderFigure, renderMetric, renderTable } from "./views/values.js";
|
|
23
|
+
/**
|
|
24
|
+
* Render a report document and its value map to one HTML string.
|
|
25
|
+
*
|
|
26
|
+
* The walk collects every value problem. When any problem exists, the render returns the problems and no
|
|
27
|
+
* HTML. When no problem exists, the render assembles the page and returns the string.
|
|
28
|
+
*/
|
|
29
|
+
export function renderReportPage(document, values) {
|
|
30
|
+
const problems = [];
|
|
31
|
+
const ledger = new ReferenceLedger();
|
|
32
|
+
const content = [];
|
|
33
|
+
for (const section of document.sections) {
|
|
34
|
+
content.push(renderBlock(section, values, ledger, 0, problems));
|
|
35
|
+
}
|
|
36
|
+
if (problems.length > 0) {
|
|
37
|
+
return err(problems);
|
|
38
|
+
}
|
|
39
|
+
const nav = renderNav(document.sections);
|
|
40
|
+
const references = renderReferenceSection(ledger);
|
|
41
|
+
return ok(assemblePage(document.title, nav, content.join(""), references));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Render one block to its HTML, and collect a value problem into `problems`. A block whose value is missing
|
|
45
|
+
* or of the wrong shape adds one problem and renders nothing. The empty result drops out, because the whole
|
|
46
|
+
* render fails when any problem exists.
|
|
47
|
+
*/
|
|
48
|
+
function renderBlock(block, values, ledger, depth, problems) {
|
|
49
|
+
switch (block.kind) {
|
|
50
|
+
case "text":
|
|
51
|
+
return renderText(block);
|
|
52
|
+
case "claim":
|
|
53
|
+
return renderClaim(block, ledger);
|
|
54
|
+
case "citation":
|
|
55
|
+
return renderCitation(block, ledger);
|
|
56
|
+
case "metric": {
|
|
57
|
+
const entry = values[block.id];
|
|
58
|
+
if (entry === undefined) {
|
|
59
|
+
problems.push(missingValue(block.id, "scalar"));
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
if (entry.type !== "scalar") {
|
|
63
|
+
problems.push(wrongShape(block.id, entry.type, "scalar"));
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
return renderMetric(block, entry);
|
|
67
|
+
}
|
|
68
|
+
case "table": {
|
|
69
|
+
const entry = values[block.id];
|
|
70
|
+
if (entry === undefined) {
|
|
71
|
+
problems.push(missingValue(block.id, "table"));
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
if (entry.type !== "table") {
|
|
75
|
+
problems.push(wrongShape(block.id, entry.type, "table"));
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
return renderTable(block, entry);
|
|
79
|
+
}
|
|
80
|
+
case "figure": {
|
|
81
|
+
const entry = values[block.id];
|
|
82
|
+
if (entry === undefined) {
|
|
83
|
+
problems.push(missingValue(block.id, "figure"));
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
if (entry.type !== "figure") {
|
|
87
|
+
problems.push(wrongShape(block.id, entry.type, "figure"));
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
return renderFigure(block, entry);
|
|
91
|
+
}
|
|
92
|
+
case "chart": {
|
|
93
|
+
const entry = values[block.id];
|
|
94
|
+
if (entry === undefined) {
|
|
95
|
+
problems.push(missingValue(block.id, "table"));
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
if (entry.type !== "table") {
|
|
99
|
+
problems.push(wrongShape(block.id, entry.type, "table"));
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
const option = deriveChartOption(block, entry.rows, entry.columns);
|
|
103
|
+
if (option.isErr()) {
|
|
104
|
+
problems.push(option.error);
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
return renderChart(block, option.value);
|
|
108
|
+
}
|
|
109
|
+
case "section": {
|
|
110
|
+
const parts = [];
|
|
111
|
+
for (const child of block.blocks) {
|
|
112
|
+
parts.push(renderBlock(child, values, ledger, depth + 1, problems));
|
|
113
|
+
}
|
|
114
|
+
return renderSection(block, depth, parts.join(""));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** A `missing-value` problem that names the block and the shape that it needs. */
|
|
119
|
+
function missingValue(blockId, expected) {
|
|
120
|
+
return { blockId, kind: "missing-value", detail: `The block needs a ${expected} value entry.` };
|
|
121
|
+
}
|
|
122
|
+
/** A `wrong-shape` problem that names the block, the shape that arrived, and the shape that it needs. */
|
|
123
|
+
function wrongShape(blockId, actual, expected) {
|
|
124
|
+
return { blockId, kind: "wrong-shape", detail: `The block needs a ${expected} value entry, but the entry is a ${actual}.` };
|
|
125
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one script sink helper.
|
|
3
|
+
*
|
|
4
|
+
* The markup runtime escapes each child and each attribute value. A `<script>` sink is the exception,
|
|
5
|
+
* because the JSON inside it goes to the page through `raw()`, and `raw()` adds no protection. Thus this
|
|
6
|
+
* helper is the sole guard of the two JSON sinks.
|
|
7
|
+
*
|
|
8
|
+
* A `</script` sequence inside a data cell can close the element too soon. The helper serializes the value,
|
|
9
|
+
* then it replaces every `<` with `\u003c`. The replacement makes a `</script` sequence unrepresentable,
|
|
10
|
+
* and the JSON value stays identical, because the JSON parser reads `\u003c` as `<`.
|
|
11
|
+
*/
|
|
12
|
+
/** Serialize a value to JSON that is safe inside a `<script>` element. */
|
|
13
|
+
export declare function scriptJson(value: unknown): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one script sink helper.
|
|
3
|
+
*
|
|
4
|
+
* The markup runtime escapes each child and each attribute value. A `<script>` sink is the exception,
|
|
5
|
+
* because the JSON inside it goes to the page through `raw()`, and `raw()` adds no protection. Thus this
|
|
6
|
+
* helper is the sole guard of the two JSON sinks.
|
|
7
|
+
*
|
|
8
|
+
* A `</script` sequence inside a data cell can close the element too soon. The helper serializes the value,
|
|
9
|
+
* then it replaces every `<` with `\u003c`. The replacement makes a `</script` sequence unrepresentable,
|
|
10
|
+
* and the JSON value stays identical, because the JSON parser reads `\u003c` as `<`.
|
|
11
|
+
*/
|
|
12
|
+
/** Serialize a value to JSON that is safe inside a `<script>` element. */
|
|
13
|
+
export function scriptJson(value) {
|
|
14
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value model that the renderer takes, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The renderer declares its own value union. It does not take `ResolvedValue`, because a `ResolvedValue`
|
|
5
|
+
* can be a file echo of a path and a hash. A page cannot show a path and a hash. The caller owns the
|
|
6
|
+
* policy that turns the echo into a source string. The string is a data URI, or a URL that the host
|
|
7
|
+
* serves. Thus a `figure` here carries a ready `src` string, and the renderer holds no policy about the
|
|
8
|
+
* image bytes.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The value that one block resolves to, as a closed union. A `scalar` gives one number or one string. A
|
|
12
|
+
* `table` gives the rows and the optional column order. A `figure` gives a ready source string. A
|
|
13
|
+
* `citation` echoes one external id.
|
|
14
|
+
*/
|
|
15
|
+
export type RenderValue = {
|
|
16
|
+
type: "scalar";
|
|
17
|
+
value: string | number;
|
|
18
|
+
} | {
|
|
19
|
+
type: "table";
|
|
20
|
+
rows: Array<Record<string, string | number>>;
|
|
21
|
+
columns?: string[];
|
|
22
|
+
} | {
|
|
23
|
+
type: "figure";
|
|
24
|
+
src: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: "citation";
|
|
27
|
+
id: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* One typed fault of a render. `blockId` names the block. `kind` names the class of the fault. `detail`
|
|
31
|
+
* names what the block expected. The render collects every problem, and it returns them at once.
|
|
32
|
+
*/
|
|
33
|
+
export interface RenderProblem {
|
|
34
|
+
blockId: string;
|
|
35
|
+
kind: "missing-value" | "wrong-shape" | "invalid-chart-input";
|
|
36
|
+
detail: string;
|
|
37
|
+
}
|
|
38
|
+
/** The value map that the renderer takes, keyed by block id. */
|
|
39
|
+
export type RenderValues = Record<string, RenderValue>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value model that the renderer takes, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The renderer declares its own value union. It does not take `ResolvedValue`, because a `ResolvedValue`
|
|
5
|
+
* can be a file echo of a path and a hash. A page cannot show a path and a hash. The caller owns the
|
|
6
|
+
* policy that turns the echo into a source string. The string is a data URI, or a URL that the host
|
|
7
|
+
* serves. Thus a `figure` here carries a ready `src` string, and the renderer holds no policy about the
|
|
8
|
+
* image bytes.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value bridge: a resolved value maps onto a render value, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The resolver gives one `ResolvedValue` for each reference. The renderer takes a `RenderValues` map. The
|
|
5
|
+
* two models agree on a scalar, a table, and a citation, but they part on a figure. A `ResolvedValue`
|
|
6
|
+
* file echo carries a path and a hash, and a page needs a source string. Thus the bridge takes a
|
|
7
|
+
* caller-supplied policy that turns a file echo into a `src` string. The preview tool gives the concrete
|
|
8
|
+
* policy, because the page directory and its asset access are a session concern.
|
|
9
|
+
*
|
|
10
|
+
* The bridge is pure. It reads no file, it resolves no reference, and it imports nothing from the state
|
|
11
|
+
* layer or the tool layer. A block kind that carries no render value maps to no entry. A block kind whose
|
|
12
|
+
* resolved value has the wrong type becomes one typed mismatch. The bridge collects every mismatch, thus
|
|
13
|
+
* one call reports each fault at one time.
|
|
14
|
+
*/
|
|
15
|
+
import { type Result } from "neverthrow";
|
|
16
|
+
import type { ResolvedValue } from "../report-model/reference-resolver.js";
|
|
17
|
+
import type { RenderValues } from "./types.js";
|
|
18
|
+
/** The block kinds that carry no render value. Each one renders from the block and the ledger alone. */
|
|
19
|
+
type NoValueKind = "text" | "claim" | "citation" | "section";
|
|
20
|
+
/** The block kinds that carry one render value under the block id in the map. */
|
|
21
|
+
type ValueBearingKind = "metric" | "table" | "chart" | "figure";
|
|
22
|
+
/** The file echo that a figure binding resolves to: a whole-file pin of a path and a hash. */
|
|
23
|
+
export type ResolvedFile = Extract<ResolvedValue, {
|
|
24
|
+
type: "file";
|
|
25
|
+
}>;
|
|
26
|
+
/** The policy that turns a resolved file echo into a figure `src` string. The caller owns the policy. */
|
|
27
|
+
export type FigureSourcePolicy = (file: ResolvedFile) => string;
|
|
28
|
+
/**
|
|
29
|
+
* The resolution result of one block: the block id, the block kind, and the resolved value of its binding.
|
|
30
|
+
*
|
|
31
|
+
* A value-bearing kind carries the resolved value. A no-value kind carries none, thus a text block with
|
|
32
|
+
* no binding is representable and a value on it is not.
|
|
33
|
+
*/
|
|
34
|
+
export type BlockResolution = {
|
|
35
|
+
blockId: string;
|
|
36
|
+
kind: NoValueKind;
|
|
37
|
+
} | {
|
|
38
|
+
blockId: string;
|
|
39
|
+
kind: ValueBearingKind;
|
|
40
|
+
resolved: ResolvedValue;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* One typed mismatch between a block kind and a resolved value type. `blockId` names the block, and
|
|
44
|
+
* `blockKind` names the kind that sets the expectation. `expected` names the resolved type that the kind
|
|
45
|
+
* needs, and `actual` names the resolved type that arrived.
|
|
46
|
+
*/
|
|
47
|
+
export interface BridgeMismatch {
|
|
48
|
+
blockId: string;
|
|
49
|
+
blockKind: ValueBearingKind;
|
|
50
|
+
expected: ResolvedValue["type"];
|
|
51
|
+
actual: ResolvedValue["type"];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Map the resolved values of the blocks onto a `RenderValues` map.
|
|
55
|
+
*
|
|
56
|
+
* The bridge walks each resolution in order. A no-value kind adds no entry. A value-bearing kind whose
|
|
57
|
+
* resolved type matches adds one entry. A value-bearing kind whose resolved type does not match adds one
|
|
58
|
+
* mismatch. A run with a mismatch returns the mismatch list and no map. A run with no mismatch returns the
|
|
59
|
+
* complete map.
|
|
60
|
+
*/
|
|
61
|
+
export declare function bridgeValues(resolutions: readonly BlockResolution[], figureSrc: FigureSourcePolicy): Result<RenderValues, BridgeMismatch[]>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value bridge: a resolved value maps onto a render value, keyed by block id.
|
|
3
|
+
*
|
|
4
|
+
* The resolver gives one `ResolvedValue` for each reference. The renderer takes a `RenderValues` map. The
|
|
5
|
+
* two models agree on a scalar, a table, and a citation, but they part on a figure. A `ResolvedValue`
|
|
6
|
+
* file echo carries a path and a hash, and a page needs a source string. Thus the bridge takes a
|
|
7
|
+
* caller-supplied policy that turns a file echo into a `src` string. The preview tool gives the concrete
|
|
8
|
+
* policy, because the page directory and its asset access are a session concern.
|
|
9
|
+
*
|
|
10
|
+
* The bridge is pure. It reads no file, it resolves no reference, and it imports nothing from the state
|
|
11
|
+
* layer or the tool layer. A block kind that carries no render value maps to no entry. A block kind whose
|
|
12
|
+
* resolved value has the wrong type becomes one typed mismatch. The bridge collects every mismatch, thus
|
|
13
|
+
* one call reports each fault at one time.
|
|
14
|
+
*/
|
|
15
|
+
import { err, ok } from "neverthrow";
|
|
16
|
+
/**
|
|
17
|
+
* Map the resolved values of the blocks onto a `RenderValues` map.
|
|
18
|
+
*
|
|
19
|
+
* The bridge walks each resolution in order. A no-value kind adds no entry. A value-bearing kind whose
|
|
20
|
+
* resolved type matches adds one entry. A value-bearing kind whose resolved type does not match adds one
|
|
21
|
+
* mismatch. A run with a mismatch returns the mismatch list and no map. A run with no mismatch returns the
|
|
22
|
+
* complete map.
|
|
23
|
+
*/
|
|
24
|
+
export function bridgeValues(resolutions, figureSrc) {
|
|
25
|
+
const values = {};
|
|
26
|
+
const mismatches = [];
|
|
27
|
+
for (const resolution of resolutions) {
|
|
28
|
+
switch (resolution.kind) {
|
|
29
|
+
case "text":
|
|
30
|
+
case "claim":
|
|
31
|
+
case "citation":
|
|
32
|
+
case "section":
|
|
33
|
+
break;
|
|
34
|
+
case "metric": {
|
|
35
|
+
const value = resolution.resolved;
|
|
36
|
+
if (value.type !== "scalar") {
|
|
37
|
+
mismatches.push(mismatch(resolution.blockId, "metric", "scalar", value.type));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
values[resolution.blockId] = { type: "scalar", value: value.value };
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "table":
|
|
44
|
+
case "chart": {
|
|
45
|
+
const value = resolution.resolved;
|
|
46
|
+
if (value.type !== "table") {
|
|
47
|
+
mismatches.push(mismatch(resolution.blockId, resolution.kind, "table", value.type));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
values[resolution.blockId] = tableValue(value);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "figure": {
|
|
54
|
+
const value = resolution.resolved;
|
|
55
|
+
if (value.type !== "file") {
|
|
56
|
+
mismatches.push(mismatch(resolution.blockId, "figure", "file", value.type));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
values[resolution.blockId] = { type: "figure", src: figureSrc(value) };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (mismatches.length > 0) {
|
|
65
|
+
return err(mismatches);
|
|
66
|
+
}
|
|
67
|
+
return ok(values);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Map a resolved table onto a render table. The explicit column order carries through when it is present,
|
|
71
|
+
* and it stays absent otherwise, thus the renderer keeps its first-row order as the default.
|
|
72
|
+
*/
|
|
73
|
+
function tableValue(value) {
|
|
74
|
+
if (value.columns !== undefined) {
|
|
75
|
+
return { type: "table", rows: value.rows, columns: value.columns };
|
|
76
|
+
}
|
|
77
|
+
return { type: "table", rows: value.rows };
|
|
78
|
+
}
|
|
79
|
+
/** Make one mismatch that names the block, its kind, the resolved type that it needs, and the type that arrived. */
|
|
80
|
+
function mismatch(blockId, blockKind, expected, actual) {
|
|
81
|
+
return { blockId, blockKind, expected, actual };
|
|
82
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chart container markup.
|
|
3
|
+
*
|
|
4
|
+
* The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
|
|
5
|
+
* `<script type="application/json">` element MUST follow the container div with no element between them.
|
|
6
|
+
* The title renders above the container, and the caption renders below.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChartBlock } from "../../contracts/report-blocks.js";
|
|
9
|
+
import type { EchartOption } from "../chart.js";
|
|
10
|
+
/** Render the container markup for a chart. */
|
|
11
|
+
export declare function renderChart(block: ChartBlock, option: EchartOption): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The chart container markup.
|
|
4
|
+
*
|
|
5
|
+
* The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
|
|
6
|
+
* `<script type="application/json">` element MUST follow the container div with no element between them.
|
|
7
|
+
* The title renders above the container, and the caption renders below.
|
|
8
|
+
*/
|
|
9
|
+
import { raw } from "hono/html";
|
|
10
|
+
import { scriptJson } from "../script-json.js";
|
|
11
|
+
/** Render the container markup for a chart. */
|
|
12
|
+
export function renderChart(block, option) {
|
|
13
|
+
const containerId = chartContainerId(block.id);
|
|
14
|
+
// The JSON goes to the page through `raw()`, thus `scriptJson` is the sole guard of this sink. It
|
|
15
|
+
// replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
|
|
16
|
+
// element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
|
|
17
|
+
const json = scriptJson(option);
|
|
18
|
+
return String(_jsxs(_Fragment, { children: [block.title !== undefined ? _jsx("p", { class: "chart-title", children: block.title }) : null, _jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) }), block.caption !== undefined ? _jsx("p", { class: "chart-caption", children: block.caption }) : null] }));
|
|
19
|
+
}
|
|
20
|
+
/** The container id derives from the block id, thus the id stays stable across renders. */
|
|
21
|
+
function chartContainerId(blockId) {
|
|
22
|
+
return `chart-${blockId}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page assembly.
|
|
3
|
+
*
|
|
4
|
+
* The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
|
|
5
|
+
* The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
|
|
6
|
+
* navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
|
|
7
|
+
* `raw()` inserts them byte for byte.
|
|
8
|
+
*/
|
|
9
|
+
import type { ReferenceLedger } from "../references.js";
|
|
10
|
+
/**
|
|
11
|
+
* Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
|
|
12
|
+
* no empty reference frame.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderReferenceSection(ledger: ReferenceLedger): string;
|
|
15
|
+
/**
|
|
16
|
+
* Assemble the page from the title, the navigation, the main content, and the reference frame. The title
|
|
17
|
+
* passes through the runtime as text, and the runtime escapes it. The reference frame is optional, thus
|
|
18
|
+
* an empty frame adds no markup.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assemblePage(title: string, nav: string, content: string, references: string): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The page assembly.
|
|
4
|
+
*
|
|
5
|
+
* The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
|
|
6
|
+
* The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
|
|
7
|
+
* navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
|
|
8
|
+
* `raw()` inserts them byte for byte.
|
|
9
|
+
*/
|
|
10
|
+
import { raw } from "hono/html";
|
|
11
|
+
import { CDN_HEAD, CHART_BOOTSTRAP, ECHARTS_THEME, ECHARTS_THEME_NAME, PAGE_CSS } from "../page.js";
|
|
12
|
+
import { renderReferenceList } from "./references-view.js";
|
|
13
|
+
import { scriptJson } from "../script-json.js";
|
|
14
|
+
/**
|
|
15
|
+
* The theme object as script-safe JSON. `scriptJson` replaces every `<` with `\u003c`, thus a later edit
|
|
16
|
+
* that adds a `<` to the theme stays safe inside the script sink. The JSON parser reads `\u003c` as `<`, thus
|
|
17
|
+
* the theme value stays exact.
|
|
18
|
+
*/
|
|
19
|
+
const THEME_JSON = scriptJson(ECHARTS_THEME);
|
|
20
|
+
/**
|
|
21
|
+
* The registration script for the ECharts theme. It runs before the chart bootstrap, thus each chart
|
|
22
|
+
* reads the registered theme by its name. The guard skips the call when the ECharts runtime did not load.
|
|
23
|
+
*/
|
|
24
|
+
const THEME_REGISTRATION = `(function () {
|
|
25
|
+
if (typeof echarts === "undefined") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
echarts.registerTheme(${JSON.stringify(ECHARTS_THEME_NAME)}, ${THEME_JSON});
|
|
29
|
+
})();`;
|
|
30
|
+
/**
|
|
31
|
+
* Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
|
|
32
|
+
* no empty reference frame.
|
|
33
|
+
*/
|
|
34
|
+
export function renderReferenceSection(ledger) {
|
|
35
|
+
const list = renderReferenceList(ledger);
|
|
36
|
+
if (list === "") {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return String(_jsxs("section", { class: "report-references-section mt-16 pt-8 border-t border-slate-200", children: [_jsx("h2", { class: "text-xl font-semibold tracking-tight text-slate-900 mb-4", children: "References" }), raw(list)] }));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Assemble the page from the title, the navigation, the main content, and the reference frame. The title
|
|
43
|
+
* passes through the runtime as text, and the runtime escapes it. The reference frame is optional, thus
|
|
44
|
+
* an empty frame adds no markup.
|
|
45
|
+
*/
|
|
46
|
+
export function assemblePage(title, nav, content, references) {
|
|
47
|
+
return ("<!doctype html>" +
|
|
48
|
+
String(_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), raw(CDN_HEAD), _jsx("style", { children: raw(PAGE_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsxs("main", { class: "report-main mx-auto max-w-4xl px-6 py-10", children: [_jsx("h1", { class: "report-title text-4xl font-bold tracking-tight text-slate-900 mb-8", children: title }), raw(content), references.length > 0 ? raw(references) : null] }), _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(CHART_BOOTSTRAP) })] })] })));
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prose markup: the text block, the claim block, the section wrapper, and the page navigation.
|
|
3
|
+
*
|
|
4
|
+
* The runtime escapes every interpolated string, thus hostile prose reaches the page as text. A text
|
|
5
|
+
* block and a claim block split the prose on a blank line, and each part becomes one paragraph. A claim
|
|
6
|
+
* adds one marker for each binding, and the markers number through the shared ledger.
|
|
7
|
+
*/
|
|
8
|
+
import type { ClaimBlock, SectionBlock, TextBlock } from "../../contracts/report-blocks.js";
|
|
9
|
+
import type { ReferenceLedger } from "../references.js";
|
|
10
|
+
/** Render a text block as escaped paragraphs. */
|
|
11
|
+
export declare function renderText(block: TextBlock): string;
|
|
12
|
+
/**
|
|
13
|
+
* Render a claim block as escaped paragraphs plus evidence markers. The markers attach to the last
|
|
14
|
+
* paragraph, thus they sit at the end of the claim text. The ledger marks each binding, thus a shared
|
|
15
|
+
* reference keeps one number across the page.
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderClaim(block: ClaimBlock, ledger: ReferenceLedger): string;
|
|
18
|
+
/**
|
|
19
|
+
* Render a section wrapper around its rendered children. The heading level is `h2` at depth 0, `h3` at
|
|
20
|
+
* depth 1, and `h4` at depth 2 or deeper. The walk renders the children first, and it passes the markup
|
|
21
|
+
* as one already-escaped string, thus `raw()` inserts it byte for byte.
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderSection(section: SectionBlock, depth: number, childrenHtml: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
|
|
26
|
+
* numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderNav(topSections: SectionBlock[]): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The prose markup: the text block, the claim block, the section wrapper, and the page navigation.
|
|
4
|
+
*
|
|
5
|
+
* The runtime escapes every interpolated string, thus hostile prose reaches the page as text. A text
|
|
6
|
+
* block and a claim block split the prose on a blank line, and each part becomes one paragraph. A claim
|
|
7
|
+
* adds one marker for each binding, and the markers number through the shared ledger.
|
|
8
|
+
*/
|
|
9
|
+
import { raw } from "hono/html";
|
|
10
|
+
import { Marker } from "./references-view.js";
|
|
11
|
+
/** The paragraph class of a text block and a claim block. */
|
|
12
|
+
const PARAGRAPH_CLASS = "report-paragraph mb-4 leading-relaxed text-slate-600";
|
|
13
|
+
/**
|
|
14
|
+
* Split prose into paragraphs on a blank line. A run of blank lines makes one split. An empty part drops
|
|
15
|
+
* out, thus a leading or a trailing blank line adds no empty paragraph.
|
|
16
|
+
*/
|
|
17
|
+
function splitParagraphs(prose) {
|
|
18
|
+
const parts = prose.split(/\n[ \t]*\n/);
|
|
19
|
+
const paragraphs = [];
|
|
20
|
+
for (const part of parts) {
|
|
21
|
+
const trimmed = part.trim();
|
|
22
|
+
if (trimmed.length > 0) {
|
|
23
|
+
paragraphs.push(trimmed);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return paragraphs;
|
|
27
|
+
}
|
|
28
|
+
/** Render a text block as escaped paragraphs. */
|
|
29
|
+
export function renderText(block) {
|
|
30
|
+
const paragraphs = splitParagraphs(block.content.prose);
|
|
31
|
+
return String(_jsx(_Fragment, { children: paragraphs.map((paragraph) => (_jsx("p", { class: PARAGRAPH_CLASS, children: paragraph }))) }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render a claim block as escaped paragraphs plus evidence markers. The markers attach to the last
|
|
35
|
+
* paragraph, thus they sit at the end of the claim text. The ledger marks each binding, thus a shared
|
|
36
|
+
* reference keeps one number across the page.
|
|
37
|
+
*/
|
|
38
|
+
export function renderClaim(block, ledger) {
|
|
39
|
+
const markerNumbers = block.bindings.map((reference) => ledger.mark(reference));
|
|
40
|
+
const markers = markerNumbers.map((n) => _jsx(Marker, { n: n }));
|
|
41
|
+
const paragraphs = splitParagraphs(block.content.prose);
|
|
42
|
+
if (paragraphs.length === 0) {
|
|
43
|
+
return String(_jsx("p", { class: PARAGRAPH_CLASS, children: markers }));
|
|
44
|
+
}
|
|
45
|
+
const last = paragraphs.length - 1;
|
|
46
|
+
return String(_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
|
|
47
|
+
}
|
|
48
|
+
/** The heading class of a section, by heading level. */
|
|
49
|
+
function headingClass(level) {
|
|
50
|
+
switch (level) {
|
|
51
|
+
case 2:
|
|
52
|
+
return "text-3xl font-semibold tracking-tight text-slate-900 mb-4";
|
|
53
|
+
case 3:
|
|
54
|
+
return "text-2xl font-semibold tracking-tight text-slate-900 mb-3";
|
|
55
|
+
default:
|
|
56
|
+
return "text-xl font-semibold tracking-tight text-slate-900 mb-2";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Render a section wrapper around its rendered children. The heading level is `h2` at depth 0, `h3` at
|
|
61
|
+
* depth 1, and `h4` at depth 2 or deeper. The walk renders the children first, and it passes the markup
|
|
62
|
+
* as one already-escaped string, thus `raw()` inserts it byte for byte.
|
|
63
|
+
*/
|
|
64
|
+
export function renderSection(section, depth, childrenHtml) {
|
|
65
|
+
const level = Math.min(depth + 2, 4);
|
|
66
|
+
const Heading = `h${level}`;
|
|
67
|
+
return String(_jsxs("section", { id: section.id, class: "report-section mb-10", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
|
|
71
|
+
* numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
|
|
72
|
+
*/
|
|
73
|
+
export function renderNav(topSections) {
|
|
74
|
+
return String(_jsxs("aside", { id: "report-sidebar", class: "fixed top-0 left-0 z-40 h-screen w-60 flex flex-col border-r border-slate-200 bg-white/95 backdrop-blur", "aria-label": "Report navigation", children: [_jsx("div", { class: "flex items-center gap-2 px-5 py-5 border-b border-slate-200", children: _jsx("span", { class: "font-mono text-sm font-semibold tracking-wider text-primary-500", children: "inflexa" }) }), _jsx("nav", { class: "flex-1 overflow-y-auto py-3", "aria-label": "Report sections", children: topSections.map((section, index) => (_jsxs("a", { href: `#${section.id}`, "data-sidebar-link": "", class: "sidebar-link flex items-center gap-3 px-5 py-2 text-sm text-slate-500 hover:text-slate-900 hover:bg-slate-50 transition-colors", children: [_jsx("span", { class: "font-mono text-[10px] font-semibold tracking-wider text-slate-300 shrink-0 w-4 text-right", children: index + 1 }), _jsx("span", { class: "truncate", children: section.title })] }))) })] }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference markup: the evidence marker and the ordered reference list.
|
|
3
|
+
*
|
|
4
|
+
* The runtime escapes each child and each attribute value, thus a hostile path, a hostile id, or a
|
|
5
|
+
* hostile operation reaches the page as text. The list keeps the first-appearance order of the ledger,
|
|
6
|
+
* thus the ordinal of each item matches its marker.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReferenceLedger } from "../references.js";
|
|
9
|
+
/**
|
|
10
|
+
* One evidence marker as a superscript that links to the list entry. The number comes from the ledger as
|
|
11
|
+
* an integer.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Marker({ n }: {
|
|
14
|
+
n: number;
|
|
15
|
+
}): import("hono/jsx/jsx-dev-runtime").JSX.Element;
|
|
16
|
+
/**
|
|
17
|
+
* The ordered reference list for the end of the page. An empty ledger gives an empty string, thus the
|
|
18
|
+
* page shows no empty list.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderReferenceList(ledger: ReferenceLedger): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* One evidence marker as a superscript that links to the list entry. The number comes from the ledger as
|
|
4
|
+
* an integer.
|
|
5
|
+
*/
|
|
6
|
+
export function Marker({ n }) {
|
|
7
|
+
return (_jsx("sup", { class: "report-marker font-mono text-[10px] text-primary-500", children: _jsx("a", { href: `#ref-${n}`, children: n }) }));
|
|
8
|
+
}
|
|
9
|
+
/** The kind label of one reference entry. */
|
|
10
|
+
function RefKind({ children }) {
|
|
11
|
+
return _jsx("span", { class: "report-ref-kind font-mono text-[11px] uppercase tracking-wider text-slate-400", children: children });
|
|
12
|
+
}
|
|
13
|
+
/** A path in a monospace code span. */
|
|
14
|
+
function PathCode({ children }) {
|
|
15
|
+
return _jsx("code", { class: "font-mono text-primary-700", children: children });
|
|
16
|
+
}
|
|
17
|
+
/** A plain detail such as a locator or an external id. */
|
|
18
|
+
function Detail({ children }) {
|
|
19
|
+
return _jsx("span", { class: "text-slate-500", children: children });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Describe a locator as plain text: the column and the one row selector. The refine of the schema admits
|
|
23
|
+
* exactly one of `row` or `rowFilter`, thus the two branches cover a valid locator.
|
|
24
|
+
*/
|
|
25
|
+
function describeLocator(locator) {
|
|
26
|
+
if (locator.row !== undefined) {
|
|
27
|
+
return `column ${locator.column}, row ${locator.row}`;
|
|
28
|
+
}
|
|
29
|
+
const filter = locator.rowFilter;
|
|
30
|
+
if (filter !== undefined) {
|
|
31
|
+
return `column ${locator.column}, row where ${filter.column} ${filter.op} ${String(filter.value)}`;
|
|
32
|
+
}
|
|
33
|
+
return `column ${locator.column}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The inner markup of one list entry. An artifact entry names its path, and it adds the locator when the
|
|
37
|
+
* reference pins one cell. A citation entry names its identifier space and its id. A derivation entry
|
|
38
|
+
* names its operation and its two inputs, each by path and locator.
|
|
39
|
+
*/
|
|
40
|
+
function ReferenceEntry({ reference }) {
|
|
41
|
+
switch (reference.kind) {
|
|
42
|
+
case "artifact-value":
|
|
43
|
+
return (_jsxs(_Fragment, { children: [_jsx(RefKind, { children: "Artifact value" }), " ", _jsx(PathCode, { children: reference.path }), " ", _jsx(Detail, { children: describeLocator(reference.locator) })] }));
|
|
44
|
+
case "artifact-table":
|
|
45
|
+
return (_jsxs(_Fragment, { children: [_jsx(RefKind, { children: "Artifact table" }), " ", _jsx(PathCode, { children: reference.path })] }));
|
|
46
|
+
case "artifact-file":
|
|
47
|
+
return (_jsxs(_Fragment, { children: [_jsx(RefKind, { children: "Artifact file" }), " ", _jsx(PathCode, { children: reference.path })] }));
|
|
48
|
+
case "citation":
|
|
49
|
+
return (_jsxs(_Fragment, { children: [_jsx(RefKind, { children: "Citation" }), " ", _jsx(Detail, { children: `${reference.idKind}:${reference.id}` })] }));
|
|
50
|
+
case "derivation":
|
|
51
|
+
return (_jsxs(_Fragment, { children: [_jsx(RefKind, { children: `Derivation (${reference.op})` }), " ", reference.inputs.map((input, index) => (_jsxs(_Fragment, { children: [index > 0 ? " and " : "", _jsx(PathCode, { children: input.path }), " ", _jsx(Detail, { children: describeLocator(input.locator) })] })))] }));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The ordered reference list for the end of the page. An empty ledger gives an empty string, thus the
|
|
56
|
+
* page shows no empty list.
|
|
57
|
+
*/
|
|
58
|
+
export function renderReferenceList(ledger) {
|
|
59
|
+
const entries = ledger.entries();
|
|
60
|
+
if (entries.length === 0) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
return String(_jsx("ol", { class: "report-references list-decimal pl-6", children: entries.map((reference, index) => (_jsx("li", { id: `ref-${index + 1}`, class: "report-ref-item mb-2 text-sm text-slate-600", children: _jsx(ReferenceEntry, { reference: reference }) }))) }));
|
|
64
|
+
}
|