@uniqbit/mate-core 0.18.5 → 1.0.0-canary.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/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +177 -138
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +176 -3
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +5 -13
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +4 -0
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +9 -17
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +48 -40
- package/src/lib/orchestrator/adapters/opencode.ts +2 -0
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
- package/src/lib/orchestrator/projection-entries.ts +45 -14
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -0
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +45 -5
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -1
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +15 -1
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -11
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -2
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +151 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +97 -21
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +35 -0
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +284 -8
- package/src/tools/setup/providers/opencode.ts +180 -1
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +126 -10
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/tools/setup/runtime-documents.ts +0 -291
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { reportDataToDocument } from "./adapter";
|
|
2
2
|
import { validateReportDocument } from "./contract";
|
|
3
3
|
import { highlightInline, highlightStyleBlock } from "./highlight";
|
|
4
|
+
import { escapeHTML, renderMarkdownSubset } from "./markdown";
|
|
5
|
+
import { DARK_PALETTE, LIGHT_PALETTE, paletteCss, type ReportPalette } from "./palette";
|
|
4
6
|
import type {
|
|
5
7
|
ReportData,
|
|
6
8
|
ReportDiagramSection,
|
|
7
9
|
ReportDiffSection,
|
|
8
10
|
ReportDocument,
|
|
9
11
|
ReportKeyValue,
|
|
12
|
+
ReportMetric,
|
|
10
13
|
ReportSection,
|
|
14
|
+
ReportStatus,
|
|
15
|
+
ReportStatusTone,
|
|
16
|
+
ReportTextSection,
|
|
11
17
|
ReportValue,
|
|
12
18
|
} from "./types";
|
|
13
19
|
|
|
@@ -24,11 +30,20 @@ const MERMAID_RUNTIME_ID = "mermaid-runtime";
|
|
|
24
30
|
|
|
25
31
|
const CODE_BLOCK_SELECTOR = "pre.report-code";
|
|
26
32
|
|
|
33
|
+
/** A report with more sections than this carries an index linking to each one. */
|
|
34
|
+
const INDEX_THRESHOLD = 4;
|
|
35
|
+
|
|
27
36
|
const carriesMermaid = (sections: ReportSection[]): boolean =>
|
|
28
37
|
sections.some((section) => section.type === "diagram" && section.mermaid !== undefined);
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
/** Diagrams, diffs, and Markdown text can all carry highlighted code. */
|
|
40
|
+
const carriesCode = (sections: ReportSection[]): boolean =>
|
|
41
|
+
sections.some(
|
|
42
|
+
(section) =>
|
|
43
|
+
section.type === "diagram" ||
|
|
44
|
+
section.type === "diff" ||
|
|
45
|
+
(section.type === "text" && section.format === "markdown"),
|
|
46
|
+
);
|
|
32
47
|
|
|
33
48
|
type RenderInput = ReportDocument | ReportData;
|
|
34
49
|
|
|
@@ -45,6 +60,70 @@ function valueToString(value: ReportValue): string {
|
|
|
45
60
|
return String(value);
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
const TONE_WORDS: Record<Exclude<ReportStatusTone, "neutral">, readonly string[]> = {
|
|
64
|
+
ok: [
|
|
65
|
+
"ok",
|
|
66
|
+
"pass",
|
|
67
|
+
"passed",
|
|
68
|
+
"passing",
|
|
69
|
+
"success",
|
|
70
|
+
"succeeded",
|
|
71
|
+
"done",
|
|
72
|
+
"complete",
|
|
73
|
+
"completed",
|
|
74
|
+
"healthy",
|
|
75
|
+
"ready",
|
|
76
|
+
"up",
|
|
77
|
+
"yes",
|
|
78
|
+
],
|
|
79
|
+
warn: [
|
|
80
|
+
"warn",
|
|
81
|
+
"warning",
|
|
82
|
+
"stale",
|
|
83
|
+
"degraded",
|
|
84
|
+
"pending",
|
|
85
|
+
"partial",
|
|
86
|
+
"skipped",
|
|
87
|
+
"outdated",
|
|
88
|
+
"deprecated",
|
|
89
|
+
],
|
|
90
|
+
error: [
|
|
91
|
+
"error",
|
|
92
|
+
"fail",
|
|
93
|
+
"failed",
|
|
94
|
+
"failing",
|
|
95
|
+
"failure",
|
|
96
|
+
"missing",
|
|
97
|
+
"broken",
|
|
98
|
+
"down",
|
|
99
|
+
"invalid",
|
|
100
|
+
"blocked",
|
|
101
|
+
"critical",
|
|
102
|
+
"no",
|
|
103
|
+
],
|
|
104
|
+
info: ["info", "new", "changed", "running", "in progress", "queued", "note"],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** The explicit tone when present, else the tone of a well-known status word, else neutral. */
|
|
108
|
+
export function inferTone(item: ReportStatus): ReportStatusTone {
|
|
109
|
+
if (item.tone !== undefined) return item.tone;
|
|
110
|
+
const word = item.status.trim().toLowerCase();
|
|
111
|
+
for (const [tone, words] of Object.entries(TONE_WORDS)) {
|
|
112
|
+
if (words.includes(word)) return tone as ReportStatusTone;
|
|
113
|
+
}
|
|
114
|
+
return "neutral";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Period, generation time, context, then metadata, as one line of parts. */
|
|
118
|
+
function headerParts(data: ReportDocument): string[] {
|
|
119
|
+
return [
|
|
120
|
+
...(data.period ? [data.period] : []),
|
|
121
|
+
`Generated ${data.generatedAt}`,
|
|
122
|
+
...(data.context ? [data.context] : []),
|
|
123
|
+
...data.metadata.map((item) => `${item.label}: ${valueToString(item.value)}`),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
48
127
|
function renderTable(headers: string[], rows: string[][]): string {
|
|
49
128
|
if (rows.length === 0) return `| ${headers.join(" | ")} |`;
|
|
50
129
|
|
|
@@ -57,17 +136,6 @@ function renderTable(headers: string[], rows: string[][]): string {
|
|
|
57
136
|
return [renderRow(headers), divider, ...rows.map(renderRow)].join("\n");
|
|
58
137
|
}
|
|
59
138
|
|
|
60
|
-
function renderKeyValueTable(rows: ReportKeyValue[]): string {
|
|
61
|
-
return renderTable(
|
|
62
|
-
["Field", "Value"],
|
|
63
|
-
rows.map((row) => [row.label, valueToString(row.value)]),
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function printSection(title: string, body: string): string {
|
|
68
|
-
return `${title}\n\n${body}\n`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
139
|
function emptyTableMessage(title: string): string {
|
|
72
140
|
const normalized = title.toLowerCase();
|
|
73
141
|
return normalized === "spending" || normalized === "savings"
|
|
@@ -75,16 +143,34 @@ function emptyTableMessage(title: string): string {
|
|
|
75
143
|
: "No data available.";
|
|
76
144
|
}
|
|
77
145
|
|
|
146
|
+
const withDetail = (value: string, detail: string | undefined): string =>
|
|
147
|
+
detail ? `${value} — ${detail}` : value;
|
|
148
|
+
|
|
149
|
+
function markdownList(lines: string[]): string {
|
|
150
|
+
return lines.length ? lines.map((line) => `- ${line}`).join("\n") : "No data available.";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function markdownKeyValues(items: ReportKeyValue[]): string {
|
|
154
|
+
return markdownList(items.map((item) => `**${item.label}:** ${valueToString(item.value)}`));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function markdownMetrics(items: ReportMetric[]): string {
|
|
158
|
+
return markdownList(
|
|
159
|
+
items.map((item) => `**${item.label}:** ${withDetail(valueToString(item.value), item.detail)}`),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function withCaption(body: string, caption: string | undefined): string {
|
|
164
|
+
return caption ? `${body}\n\n${caption}` : body;
|
|
165
|
+
}
|
|
166
|
+
|
|
78
167
|
function sectionMarkdown(section: ReportSection): string {
|
|
79
168
|
switch (section.type) {
|
|
80
169
|
case "metadata":
|
|
81
170
|
case "key-value":
|
|
82
|
-
return
|
|
171
|
+
return markdownKeyValues(section.items);
|
|
83
172
|
case "metrics":
|
|
84
|
-
return
|
|
85
|
-
["Metric", "Value"],
|
|
86
|
-
section.items.map((item) => [item.label, valueToString(item.value)]),
|
|
87
|
-
);
|
|
173
|
+
return markdownMetrics(section.items);
|
|
88
174
|
case "table":
|
|
89
175
|
return section.rows.length
|
|
90
176
|
? renderTable(
|
|
@@ -93,23 +179,24 @@ function sectionMarkdown(section: ReportSection): string {
|
|
|
93
179
|
)
|
|
94
180
|
: emptyTableMessage(section.title);
|
|
95
181
|
case "statuses":
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]),
|
|
103
|
-
)
|
|
104
|
-
: "No data available.";
|
|
182
|
+
return markdownList(
|
|
183
|
+
section.items.map(
|
|
184
|
+
(item) =>
|
|
185
|
+
`[${inferTone(item)}] **${item.label}:** ${withDetail(item.status, item.detail)}`,
|
|
186
|
+
),
|
|
187
|
+
);
|
|
105
188
|
case "text":
|
|
106
189
|
return section.content;
|
|
107
190
|
case "diagram":
|
|
108
|
-
return
|
|
109
|
-
? fence("", section.text)
|
|
110
|
-
|
|
191
|
+
return withCaption(
|
|
192
|
+
section.mermaid === undefined ? fence("", section.text) : fence("mermaid", section.mermaid),
|
|
193
|
+
section.caption,
|
|
194
|
+
);
|
|
111
195
|
case "diff":
|
|
112
|
-
return
|
|
196
|
+
return withCaption(
|
|
197
|
+
section.patch.trim() ? fence("diff", section.patch) : "No data available.",
|
|
198
|
+
section.caption,
|
|
199
|
+
);
|
|
113
200
|
}
|
|
114
201
|
}
|
|
115
202
|
|
|
@@ -119,25 +206,12 @@ function fence(language: string, content: string): string {
|
|
|
119
206
|
|
|
120
207
|
export function renderMarkdown(input: RenderInput): string {
|
|
121
208
|
const data = normalize(input);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
if (data.period) metadata.unshift({ label: "Period", value: data.period });
|
|
125
|
-
if (data.context) metadata.push({ label: "Context", value: data.context });
|
|
126
|
-
sections.push(renderKeyValueTable(metadata));
|
|
127
|
-
sections.push(
|
|
128
|
-
"\n" +
|
|
129
|
-
printSection(
|
|
130
|
-
"## Summary",
|
|
131
|
-
renderTable(
|
|
132
|
-
["Metric", "Value"],
|
|
133
|
-
data.summary.map((item) => [item.label, valueToString(item.value)]),
|
|
134
|
-
),
|
|
135
|
-
),
|
|
136
|
-
);
|
|
209
|
+
const parts: string[] = [`# ${data.title}`, headerParts(data).join(" · ")];
|
|
210
|
+
if (data.summary.length) parts.push(`## Summary\n\n${markdownMetrics(data.summary)}`);
|
|
137
211
|
for (const section of data.sections) {
|
|
138
|
-
|
|
212
|
+
parts.push(`## ${section.title}\n\n${sectionMarkdown(section)}`);
|
|
139
213
|
}
|
|
140
|
-
return
|
|
214
|
+
return `${parts.join("\n\n")}\n`;
|
|
141
215
|
}
|
|
142
216
|
|
|
143
217
|
export function renderJSON(input: RenderInput): string {
|
|
@@ -145,44 +219,86 @@ export function renderJSON(input: RenderInput): string {
|
|
|
145
219
|
return JSON.stringify(normalize(input), null, 2);
|
|
146
220
|
}
|
|
147
221
|
|
|
148
|
-
function
|
|
149
|
-
return
|
|
150
|
-
switch (character) {
|
|
151
|
-
case "&":
|
|
152
|
-
return "&";
|
|
153
|
-
case "<":
|
|
154
|
-
return "<";
|
|
155
|
-
case ">":
|
|
156
|
-
return ">";
|
|
157
|
-
case '"':
|
|
158
|
-
return """;
|
|
159
|
-
default:
|
|
160
|
-
return "'";
|
|
161
|
-
}
|
|
162
|
-
});
|
|
222
|
+
function emptyHTML(message: string): string {
|
|
223
|
+
return `<p class="empty">${escapeHTML(message)}</p>`;
|
|
163
224
|
}
|
|
164
225
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
226
|
+
/** Every non-empty cell is a number, or a numeric-looking string such as `$12.50` or `1.2M`. */
|
|
227
|
+
const NUMERIC_TEXT = /^[-+]?[$€£]?\d[\d,.]*\s?[KMBT%]?(?:\/[a-z]+)?$/i;
|
|
228
|
+
const PLACEHOLDER_TEXT = new Set(["", "N/A", "n/a", "-", "—"]);
|
|
229
|
+
|
|
230
|
+
function isNumericColumn(rows: ReportValue[][], column: number): boolean {
|
|
231
|
+
const cells = rows
|
|
232
|
+
.map((row) => row[column])
|
|
233
|
+
.filter(
|
|
234
|
+
(cell) => cell !== null && !(typeof cell === "string" && PLACEHOLDER_TEXT.has(cell.trim())),
|
|
235
|
+
);
|
|
236
|
+
if (cells.length === 0) return false;
|
|
237
|
+
return (
|
|
238
|
+
cells.every((cell) => typeof cell === "number") ||
|
|
239
|
+
cells.every((cell) => typeof cell === "string" && NUMERIC_TEXT.test(cell.trim()))
|
|
240
|
+
);
|
|
173
241
|
}
|
|
174
242
|
|
|
175
|
-
function
|
|
176
|
-
|
|
243
|
+
function renderHTMLTable(columns: string[], rows: ReportValue[][]): string {
|
|
244
|
+
const numeric = columns.map((_, index) => isNumericColumn(rows, index));
|
|
245
|
+
const numClass = (index: number): string => (numeric[index] ? ' class="num"' : "");
|
|
246
|
+
const headerHTML = columns
|
|
247
|
+
.map((column, index) => `<th scope="col"${numClass(index)}>${escapeHTML(column)}</th>`)
|
|
248
|
+
.join("");
|
|
249
|
+
const rowsHTML = rows
|
|
250
|
+
.map(
|
|
251
|
+
(row) =>
|
|
252
|
+
`<tr>${row.map((value, index) => `<td${numClass(index)}>${escapeHTML(valueToString(value))}</td>`).join("")}</tr>`,
|
|
253
|
+
)
|
|
254
|
+
.join("");
|
|
255
|
+
return `<div class="table-wrap"><table><thead><tr>${headerHTML}</tr></thead><tbody>${rowsHTML}</tbody></table></div>`;
|
|
177
256
|
}
|
|
178
257
|
|
|
179
258
|
function renderHTMLKeyValues(items: ReportKeyValue[]): string {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
259
|
+
if (items.length === 0) return emptyHTML("No metadata available.");
|
|
260
|
+
const rows = items
|
|
261
|
+
.map(
|
|
262
|
+
(item) =>
|
|
263
|
+
`<div><dt>${escapeHTML(item.label)}</dt><dd>${escapeHTML(valueToString(item.value))}</dd></div>`,
|
|
264
|
+
)
|
|
265
|
+
.join("");
|
|
266
|
+
return `<dl class="report-kv">${rows}</dl>`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderHTMLTiles(items: ReportMetric[]): string {
|
|
270
|
+
const tiles = items
|
|
271
|
+
.map((item) => {
|
|
272
|
+
const detail = item.detail ? `<p class="tile-detail">${escapeHTML(item.detail)}</p>` : "";
|
|
273
|
+
return `<div class="tile"><p class="tile-value">${escapeHTML(valueToString(item.value))}</p><p class="tile-label">${escapeHTML(item.label)}</p>${detail}</div>`;
|
|
274
|
+
})
|
|
275
|
+
.join("");
|
|
276
|
+
return `<div class="report-tiles">${tiles}</div>`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function renderHTMLStatuses(items: ReportStatus[]): string {
|
|
280
|
+
const rows = items
|
|
281
|
+
.map((item) => {
|
|
282
|
+
const detail = item.detail
|
|
283
|
+
? `<span class="status-detail">${escapeHTML(item.detail)}</span>`
|
|
284
|
+
: "";
|
|
285
|
+
return `<li class="status" data-tone="${inferTone(item)}"><span class="tone-marker" aria-hidden="true"></span><span class="status-label">${escapeHTML(item.label)}</span><span class="status-value">${escapeHTML(item.status)}</span>${detail}</li>`;
|
|
286
|
+
})
|
|
287
|
+
.join("");
|
|
288
|
+
return `<ul class="report-statuses">${rows}</ul>`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function renderHTMLText(section: ReportTextSection): string {
|
|
292
|
+
if (!section.content) return emptyHTML("No text available.");
|
|
293
|
+
return section.format === "markdown"
|
|
294
|
+
? `<div class="report-text report-markdown">${renderMarkdownSubset(section.content)}</div>`
|
|
295
|
+
: `<p class="report-text">${escapeHTML(section.content).replaceAll("\n", "<br>")}</p>`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function renderHTMLCaption(caption: string | undefined): string {
|
|
299
|
+
return caption
|
|
300
|
+
? `<div class="report-caption report-markdown">${renderMarkdownSubset(caption)}</div>`
|
|
301
|
+
: "";
|
|
186
302
|
}
|
|
187
303
|
|
|
188
304
|
type DiffLineKind = "add" | "del" | "context" | "hunk";
|
|
@@ -302,11 +418,27 @@ function renderHTMLDiff(section: ReportDiffSection): string {
|
|
|
302
418
|
.join("");
|
|
303
419
|
}
|
|
304
420
|
|
|
421
|
+
/** A `classDef`, `style`, or `linkStyle` line that paints with a fixed colour. */
|
|
422
|
+
const FIXED_COLOUR_LINE = /^\s*(?:classDef|style|linkStyle)\s.*\b(?:fill|stroke|color)\s*:/;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The dark copy drops fixed colours: a light fill authored for a white page would
|
|
426
|
+
* otherwise sit under the dark scheme's light text and hide every label.
|
|
427
|
+
*/
|
|
428
|
+
function darkMermaidSource(source: string): string {
|
|
429
|
+
return source
|
|
430
|
+
.split("\n")
|
|
431
|
+
.filter((line) => !FIXED_COLOUR_LINE.test(line))
|
|
432
|
+
.join("\n");
|
|
433
|
+
}
|
|
434
|
+
|
|
305
435
|
function renderHTMLDiagram(section: ReportDiagramSection): string {
|
|
306
436
|
if (section.mermaid === undefined) {
|
|
307
437
|
return `<pre class="report-code report-diagram-text">${escapeHTML(section.text)}</pre>`;
|
|
308
438
|
}
|
|
309
|
-
|
|
439
|
+
const dark = darkMermaidSource(section.mermaid);
|
|
440
|
+
const darkAttribute = dark === section.mermaid ? "" : ` data-dark-source="${escapeHTML(dark)}"`;
|
|
441
|
+
return `<div class="report-diagram"><pre class="mermaid"${darkAttribute}>${escapeHTML(section.mermaid)}</pre></div>`;
|
|
310
442
|
}
|
|
311
443
|
|
|
312
444
|
function renderHTMLSectionBody(section: ReportSection): string {
|
|
@@ -316,36 +448,22 @@ function renderHTMLSectionBody(section: ReportSection): string {
|
|
|
316
448
|
return renderHTMLKeyValues(section.items);
|
|
317
449
|
case "metrics":
|
|
318
450
|
return section.items.length
|
|
319
|
-
?
|
|
320
|
-
["Metric", "Value"],
|
|
321
|
-
section.items.map((item) => [item.label, valueToString(item.value)]),
|
|
322
|
-
)
|
|
451
|
+
? renderHTMLTiles(section.items)
|
|
323
452
|
: emptyHTML("No metrics available.");
|
|
324
453
|
case "table":
|
|
325
454
|
return section.rows.length
|
|
326
|
-
? renderHTMLTable(
|
|
327
|
-
section.columns,
|
|
328
|
-
section.rows.map((row) => row.map(valueToString)),
|
|
329
|
-
)
|
|
455
|
+
? renderHTMLTable(section.columns, section.rows)
|
|
330
456
|
: emptyHTML(emptyTableMessage(section.title));
|
|
331
457
|
case "statuses":
|
|
332
458
|
return section.items.length
|
|
333
|
-
?
|
|
334
|
-
["Status", "Value"],
|
|
335
|
-
section.items.map((item) => [
|
|
336
|
-
item.label,
|
|
337
|
-
item.detail ? `${item.status}: ${item.detail}` : item.status,
|
|
338
|
-
]),
|
|
339
|
-
)
|
|
459
|
+
? renderHTMLStatuses(section.items)
|
|
340
460
|
: emptyHTML("No status data available.");
|
|
341
461
|
case "text":
|
|
342
|
-
return section
|
|
343
|
-
? `<p class="report-text">${escapeHTML(section.content).replaceAll("\n", "<br>")}</p>`
|
|
344
|
-
: emptyHTML("No text available.");
|
|
462
|
+
return renderHTMLText(section);
|
|
345
463
|
case "diagram":
|
|
346
|
-
return renderHTMLDiagram(section);
|
|
464
|
+
return renderHTMLDiagram(section) + renderHTMLCaption(section.caption);
|
|
347
465
|
case "diff":
|
|
348
|
-
return renderHTMLDiff(section);
|
|
466
|
+
return renderHTMLDiff(section) + renderHTMLCaption(section.caption);
|
|
349
467
|
}
|
|
350
468
|
}
|
|
351
469
|
|
|
@@ -353,28 +471,136 @@ function renderHTMLSection(section: ReportSection): string {
|
|
|
353
471
|
return `<section id="${escapeHTML(section.id)}"><h2>${escapeHTML(section.title)}</h2>${renderHTMLSectionBody(section)}</section>`;
|
|
354
472
|
}
|
|
355
473
|
|
|
474
|
+
function renderHTMLIndex(sections: ReportSection[]): string {
|
|
475
|
+
if (sections.length <= INDEX_THRESHOLD) return "";
|
|
476
|
+
const links = sections
|
|
477
|
+
.map(
|
|
478
|
+
(section) => `<li><a href="#${escapeHTML(section.id)}">${escapeHTML(section.title)}</a></li>`,
|
|
479
|
+
)
|
|
480
|
+
.join("");
|
|
481
|
+
return `\n <nav class="report-index interactive-only" aria-label="Sections"><ol>${links}</ol></nav>`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** The kit's symbol: the Ink bar follows the text colour, the three blocks the accent. */
|
|
485
|
+
const MATE_SYMBOL = `<svg class="report-symbol" viewBox="0 0 256 256" width="28" height="28" aria-hidden="true" focusable="false"><path fill="currentColor" d="M36 16H144V240H36A20 20 0 0 1 16 220V36A20 20 0 0 1 36 16Z"/><path class="report-symbol-accent" d="M160 16H220A20 20 0 0 1 240 36V80H160Z M160 96H240V160H160Z M160 176H240V220A20 20 0 0 1 220 240H160Z"/></svg>`;
|
|
486
|
+
|
|
487
|
+
const PRINT_ICON = `<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9V3h12v6"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><path d="M6 14h12v7H6z"/></svg>`;
|
|
488
|
+
|
|
489
|
+
const PRINT_LABEL = "Print / Save as PDF";
|
|
490
|
+
|
|
491
|
+
/** Mermaid theme variables for one report palette; notes stay neutral, not mermaid yellow. */
|
|
492
|
+
function mermaidThemeVariables(palette: ReportPalette, darkMode: boolean): Record<string, unknown> {
|
|
493
|
+
return {
|
|
494
|
+
darkMode,
|
|
495
|
+
background: palette.bg,
|
|
496
|
+
primaryColor: palette.surface,
|
|
497
|
+
primaryTextColor: palette.ink,
|
|
498
|
+
primaryBorderColor: palette.accent,
|
|
499
|
+
secondaryColor: palette.hunkBg,
|
|
500
|
+
tertiaryColor: palette.bg,
|
|
501
|
+
lineColor: palette.muted,
|
|
502
|
+
textColor: palette.ink,
|
|
503
|
+
noteBkgColor: palette.hunkBg,
|
|
504
|
+
noteBorderColor: palette.rule,
|
|
505
|
+
noteTextColor: palette.ink,
|
|
506
|
+
fontFamily: "system-ui, -apple-system, Segoe UI, sans-serif",
|
|
507
|
+
fontSize: "14px",
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const NODE_SHAPES = ["rect", "polygon", "circle", "ellipse", "path"];
|
|
512
|
+
|
|
513
|
+
/** Node classes an author can apply with `class` or `:::` instead of fixing a colour. */
|
|
514
|
+
function mermaidClassCss(palette: ReportPalette): string {
|
|
515
|
+
const classes: [name: string, fill: string, stroke: string][] = [
|
|
516
|
+
["added", palette.addBg, palette.ok],
|
|
517
|
+
["changed", palette.hunkBg, palette.info],
|
|
518
|
+
["removed", palette.delBg, palette.error],
|
|
519
|
+
];
|
|
520
|
+
return classes
|
|
521
|
+
.map(([name, fill, stroke]) => {
|
|
522
|
+
const selector = NODE_SHAPES.map((shape) => `.node.${name} ${shape}`).join(", ");
|
|
523
|
+
return `${selector} { fill: ${fill} !important; stroke: ${stroke} !important; }`;
|
|
524
|
+
})
|
|
525
|
+
.join(" ");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Draws every diagram twice, in the light and the dark palette, one diagram at a time.
|
|
530
|
+
* CSS then shows the copy for the active scheme and always the light copy in print, so
|
|
531
|
+
* neither a scheme switch nor printing has to wait for a redraw. A diagram that fails
|
|
532
|
+
* to render keeps showing its escaped source.
|
|
533
|
+
*/
|
|
534
|
+
const MERMAID_INIT = `(() => {
|
|
535
|
+
const mermaid = window.mermaid;
|
|
536
|
+
if (!mermaid) return;
|
|
537
|
+
const variants = ${JSON.stringify([
|
|
538
|
+
{
|
|
539
|
+
scheme: "light",
|
|
540
|
+
themeVariables: mermaidThemeVariables(LIGHT_PALETTE, false),
|
|
541
|
+
themeCSS: mermaidClassCss(LIGHT_PALETTE),
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
scheme: "dark",
|
|
545
|
+
themeVariables: mermaidThemeVariables(DARK_PALETTE, true),
|
|
546
|
+
themeCSS: mermaidClassCss(DARK_PALETTE),
|
|
547
|
+
},
|
|
548
|
+
])};
|
|
549
|
+
const draw = async (sources, id) => {
|
|
550
|
+
const drawn = [];
|
|
551
|
+
for (const { scheme, themeVariables, themeCSS } of variants) {
|
|
552
|
+
mermaid.initialize({ startOnLoad: false, securityLevel: "strict", theme: "base", themeVariables, themeCSS });
|
|
553
|
+
const { svg } = await mermaid.render(id + "-" + scheme, sources[scheme]);
|
|
554
|
+
const figure = document.createElement("div");
|
|
555
|
+
figure.className = "diagram-" + scheme;
|
|
556
|
+
figure.innerHTML = svg;
|
|
557
|
+
drawn.push(figure);
|
|
558
|
+
}
|
|
559
|
+
return drawn;
|
|
560
|
+
};
|
|
561
|
+
(async () => {
|
|
562
|
+
const nodes = [...document.querySelectorAll(".report-diagram pre.mermaid")];
|
|
563
|
+
for (const [index, node] of nodes.entries()) {
|
|
564
|
+
const id = "report-diagram-" + index;
|
|
565
|
+
try {
|
|
566
|
+
const sources = { light: node.textContent, dark: node.dataset.darkSource ?? node.textContent };
|
|
567
|
+
node.after(...(await draw(sources, id)));
|
|
568
|
+
node.hidden = true;
|
|
569
|
+
} catch {
|
|
570
|
+
for (const scheme of ["light", "dark"]) document.getElementById("d" + id + "-" + scheme)?.remove();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
})();
|
|
574
|
+
})();`;
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Runs before the styles apply: `?scheme=light` or `?scheme=dark` pins the colour scheme
|
|
578
|
+
* through `data-scheme` on `<html>`; any other value leaves the browser preference in charge.
|
|
579
|
+
*/
|
|
580
|
+
const SCHEME_SCRIPT = `(function () {
|
|
581
|
+
var scheme = new URLSearchParams(location.search).get("scheme");
|
|
582
|
+
if (scheme === "light" || scheme === "dark") document.documentElement.dataset.scheme = scheme;
|
|
583
|
+
})();`;
|
|
584
|
+
|
|
356
585
|
export function renderHTML(input: RenderInput): string {
|
|
357
586
|
const data = normalize(input);
|
|
358
587
|
const legacy = isReportDocument(input) ? undefined : input;
|
|
359
|
-
const metadata = [...data.metadata];
|
|
360
|
-
if (data.period) metadata.unshift({ label: "Period", value: data.period });
|
|
361
|
-
if (data.context) metadata.push({ label: "Context", value: data.context });
|
|
362
|
-
const summary: ReportMetricsSection = {
|
|
363
|
-
id: "summary",
|
|
364
|
-
title: "Summary",
|
|
365
|
-
type: "metrics",
|
|
366
|
-
items: data.summary,
|
|
367
|
-
};
|
|
368
588
|
|
|
369
589
|
const repositoryAttributes = legacy
|
|
370
590
|
? ` data-working-repository="${escapeHTML(legacy.workingRepoPath)}" data-companion-repository="${escapeHTML(legacy.companionRepoPath)}"`
|
|
371
591
|
: "";
|
|
372
592
|
|
|
373
|
-
const
|
|
593
|
+
const meta = headerParts(data)
|
|
594
|
+
.map((part) => `<span>${escapeHTML(part)}</span>`)
|
|
595
|
+
.join("");
|
|
596
|
+
const summary = data.summary.length
|
|
597
|
+
? `\n <section id="summary" class="report-summary" aria-label="Summary">${renderHTMLTiles(data.summary)}</section>`
|
|
598
|
+
: "";
|
|
599
|
+
const codeStyles = carriesCode(data.sections)
|
|
374
600
|
? `\n <style>${highlightStyleBlock(CODE_BLOCK_SELECTOR)}${VISUAL_SECTION_CSS}</style>`
|
|
375
601
|
: "";
|
|
376
602
|
const diagramRuntime = carriesMermaid(data.sections)
|
|
377
|
-
? `\n <script id="${MERMAID_RUNTIME_ID}" src="${MERMAID_RUNTIME_URL}" integrity="${MERMAID_RUNTIME_INTEGRITY}" crossorigin="anonymous"></script>\n <script
|
|
603
|
+
? `\n <script id="${MERMAID_RUNTIME_ID}" src="${MERMAID_RUNTIME_URL}" integrity="${MERMAID_RUNTIME_INTEGRITY}" crossorigin="anonymous"></script>\n <script>${MERMAID_INIT}</script>`
|
|
378
604
|
: "";
|
|
379
605
|
|
|
380
606
|
return `<!doctype html>
|
|
@@ -382,67 +608,144 @@ export function renderHTML(input: RenderInput): string {
|
|
|
382
608
|
<head>
|
|
383
609
|
<meta charset="utf-8">
|
|
384
610
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
611
|
+
<meta name="color-scheme" content="light dark">
|
|
612
|
+
<script id="report-scheme">${SCHEME_SCRIPT}</script>
|
|
385
613
|
<title>${escapeHTML(data.title)}</title>
|
|
386
614
|
<style>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
header, section { background: #fff; border: 1px solid #dce2e8; border-radius: 14px; margin-bottom: 20px; padding: 24px; box-shadow: 0 8px 24px rgb(24 33 43 / 6%); }
|
|
391
|
-
h1, h2 { margin-top: 0; }
|
|
392
|
-
h1 { margin-bottom: 8px; }
|
|
393
|
-
h2 { font-size: 1.15rem; }
|
|
394
|
-
.eyebrow { color: #536273; font-size: .8rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
|
395
|
-
.print-control { background: #18212b; border: 0; border-radius: 8px; color: #fff; cursor: pointer; padding: 10px 14px; }
|
|
396
|
-
.table-wrap { overflow-x: auto; }
|
|
397
|
-
table { border-collapse: collapse; min-width: 100%; }
|
|
398
|
-
th, td { border-bottom: 1px solid #e8edf1; padding: 10px 12px; text-align: left; vertical-align: top; }
|
|
399
|
-
th { color: #536273; font-size: .8rem; text-transform: uppercase; }
|
|
400
|
-
.empty { color: #536273; margin: 0; }
|
|
401
|
-
@media (max-width: 640px) { .report { padding: 16px 12px 32px; } header, section { padding: 16px; } }
|
|
402
|
-
@media print {
|
|
403
|
-
:root, body { background: #fff; color: #000; }
|
|
404
|
-
.report { max-width: none; padding: 0; }
|
|
405
|
-
header, section { border: 0; border-radius: 0; box-shadow: none; margin-bottom: 16px; padding: 0; }
|
|
406
|
-
.print-control, .interactive-only { display: none !important; }
|
|
407
|
-
.table-wrap { overflow: visible; }
|
|
408
|
-
table { width: 100%; }
|
|
409
|
-
th, td { color: #000; }
|
|
410
|
-
}
|
|
411
|
-
</style>${visualStyles}
|
|
615
|
+
${paletteCss()}
|
|
616
|
+
${REPORT_CSS}
|
|
617
|
+
</style>${codeStyles}
|
|
412
618
|
</head>
|
|
413
619
|
<body>
|
|
414
620
|
<main class="report" data-report-version="${data.version}"${repositoryAttributes}>
|
|
415
|
-
<header>
|
|
416
|
-
<
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
621
|
+
<header class="report-header">
|
|
622
|
+
<div class="report-heading">
|
|
623
|
+
${MATE_SYMBOL}
|
|
624
|
+
<h1>${escapeHTML(data.title)}</h1>
|
|
625
|
+
<button class="print-control interactive-only" type="button" aria-label="${PRINT_LABEL}" title="${PRINT_LABEL}" onclick="window.print()">${PRINT_ICON}</button>
|
|
626
|
+
</div>
|
|
627
|
+
<p class="report-meta">${meta}</p>
|
|
628
|
+
</header>${summary}${renderHTMLIndex(data.sections)}
|
|
422
629
|
${data.sections.map(renderHTMLSection).join("\n ")}
|
|
423
630
|
</main>${diagramRuntime}
|
|
424
631
|
</body>
|
|
425
632
|
</html>`;
|
|
426
633
|
}
|
|
427
634
|
|
|
635
|
+
const MONO = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
636
|
+
|
|
637
|
+
const REPORT_CSS = `
|
|
638
|
+
html { background: var(--bg); color: var(--ink); font-family: system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5; }
|
|
639
|
+
body { margin: 0; }
|
|
640
|
+
a { color: var(--accent); }
|
|
641
|
+
.report { max-width: 1040px; margin: 0 auto; padding: 40px 24px 64px; }
|
|
642
|
+
.report-heading { align-items: center; display: flex; gap: 12px; }
|
|
643
|
+
.report-symbol { color: var(--ink); flex: none; }
|
|
644
|
+
.report-symbol-accent { fill: var(--accent); }
|
|
645
|
+
h1 { flex: 1; font-size: 1.6rem; font-weight: 650; letter-spacing: -.01em; line-height: 1.2; margin: 0; }
|
|
646
|
+
.report-meta { color: var(--muted); font-size: .85rem; margin: 8px 0 0 40px; }
|
|
647
|
+
.report-meta span + span::before { content: "·"; padding: 0 .5em; }
|
|
648
|
+
.print-control { align-items: center; background: transparent; border: 0; border-radius: 8px; color: var(--muted); cursor: pointer; display: inline-flex; flex: none; height: 36px; justify-content: center; padding: 0; width: 36px; }
|
|
649
|
+
.print-control:hover, .print-control:focus-visible { color: var(--accent); }
|
|
650
|
+
.print-control:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
651
|
+
section { margin-top: 40px; }
|
|
652
|
+
h2 { border-bottom: 1px solid var(--rule); color: var(--muted); font-size: .75rem; font-variant: small-caps; font-weight: 650; letter-spacing: .08em; margin: 0 0 16px; padding-bottom: 8px; text-transform: uppercase; }
|
|
653
|
+
.report-tiles { display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
|
|
654
|
+
.tile { border-top: 1px solid var(--rule); padding-top: 12px; }
|
|
655
|
+
.tile p { margin: 0; }
|
|
656
|
+
.tile-value { font-size: 1.6rem; font-variant-numeric: tabular-nums; font-weight: 650; line-height: 1.2; }
|
|
657
|
+
.tile-label { color: var(--muted); font-size: .85rem; margin-top: 4px !important; }
|
|
658
|
+
.tile-detail { color: var(--muted); font-size: .8rem; }
|
|
659
|
+
.report-index ol { display: flex; flex-wrap: wrap; gap: 4px 20px; list-style: none; margin: 32px 0 0; padding: 0; }
|
|
660
|
+
.report-index a { font-size: .85rem; text-decoration: none; }
|
|
661
|
+
.report-index a:hover { text-decoration: underline; }
|
|
662
|
+
.report-kv { display: grid; gap: 8px 24px; grid-template-columns: minmax(140px, max-content) 1fr; margin: 0; }
|
|
663
|
+
.report-kv div { display: contents; }
|
|
664
|
+
.report-kv dt { color: var(--muted); }
|
|
665
|
+
.report-kv dd { margin: 0; overflow-wrap: anywhere; }
|
|
666
|
+
.report-statuses { list-style: none; margin: 0; padding: 0; }
|
|
667
|
+
.status { align-items: baseline; border-bottom: 1px solid var(--rule); display: flex; flex-wrap: wrap; gap: 4px 12px; padding: 8px 0; }
|
|
668
|
+
.status:last-child { border-bottom: 0; }
|
|
669
|
+
.status-label { font-weight: 600; }
|
|
670
|
+
.status-value { color: var(--tone); margin-left: auto; text-align: right; }
|
|
671
|
+
.status-detail { color: var(--muted); flex-basis: 100%; font-size: .85rem; padding-left: 22px; }
|
|
672
|
+
.tone-marker { align-self: center; background: var(--tone); display: inline-block; flex: none; height: 10px; width: 10px; }
|
|
673
|
+
.status[data-tone="ok"] { --tone: var(--ok); }
|
|
674
|
+
.status[data-tone="ok"] .tone-marker { border-radius: 50%; }
|
|
675
|
+
.status[data-tone="neutral"] { --tone: var(--neutral); }
|
|
676
|
+
.status[data-tone="neutral"] .tone-marker { background: transparent; border: 2px solid var(--tone); border-radius: 50%; box-sizing: border-box; }
|
|
677
|
+
.status[data-tone="warn"] { --tone: var(--warn); }
|
|
678
|
+
.status[data-tone="warn"] .tone-marker { clip-path: polygon(50% 0, 100% 100%, 0 100%); }
|
|
679
|
+
.status[data-tone="error"] { --tone: var(--error); }
|
|
680
|
+
.status[data-tone="info"] { --tone: var(--info); }
|
|
681
|
+
.status[data-tone="info"] .tone-marker { transform: rotate(45deg) scale(.85); }
|
|
682
|
+
.table-wrap { overflow-x: auto; }
|
|
683
|
+
table { border-collapse: collapse; min-width: 100%; }
|
|
684
|
+
th, td { border-bottom: 1px solid var(--rule); padding: 8px 12px; text-align: left; vertical-align: top; }
|
|
685
|
+
th:first-child, td:first-child { padding-left: 0; }
|
|
686
|
+
th:last-child, td:last-child { padding-right: 0; }
|
|
687
|
+
th { color: var(--muted); font-size: .75rem; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; }
|
|
688
|
+
tbody tr:last-child td { border-bottom: 0; }
|
|
689
|
+
.num { font-variant-numeric: tabular-nums; text-align: right; }
|
|
690
|
+
.report-text { margin: 0; max-width: 72ch; }
|
|
691
|
+
.report-markdown > :first-child { margin-top: 0; }
|
|
692
|
+
.report-markdown > :last-child { margin-bottom: 0; }
|
|
693
|
+
.report-markdown h3, .report-markdown h4, .report-markdown h5, .report-markdown h6 { font-size: 1rem; margin: 20px 0 8px; }
|
|
694
|
+
.report-markdown code { background: var(--surface); border-radius: 4px; font-family: ${MONO}; font-size: .88em; padding: .1em .3em; }
|
|
695
|
+
.report-markdown pre code { background: none; padding: 0; }
|
|
696
|
+
.report-caption { color: var(--muted); font-size: .9rem; margin-top: 12px; max-width: 72ch; }
|
|
697
|
+
.empty { color: var(--muted); margin: 0; }
|
|
698
|
+
@media (max-width: 640px) {
|
|
699
|
+
.report { padding: 24px 16px 40px; }
|
|
700
|
+
.report-meta { margin-left: 0; }
|
|
701
|
+
.report-kv { grid-template-columns: 1fr; gap: 2px; }
|
|
702
|
+
.report-kv dd { margin-bottom: 8px; }
|
|
703
|
+
}
|
|
704
|
+
@media print {
|
|
705
|
+
.report { max-width: none; padding: 0; }
|
|
706
|
+
section { margin-top: 24px; }
|
|
707
|
+
.report-header, h2, .diff-file { break-after: avoid-page; }
|
|
708
|
+
.report-markdown :is(h3, h4, h5, h6) { break-after: avoid-page; }
|
|
709
|
+
.report-markdown :is(ul, ol) { break-inside: avoid-page; }
|
|
710
|
+
.report-caption { break-before: avoid-page; }
|
|
711
|
+
tr, .tile, .status, .report-kv dt, .report-kv dd, .report-markdown li, .report-diagram { break-inside: avoid-page; }
|
|
712
|
+
.report-kv dt { break-after: avoid-page; }
|
|
713
|
+
thead { display: table-header-group; }
|
|
714
|
+
p, li { orphans: 3; widows: 3; }
|
|
715
|
+
.print-control, .interactive-only, .report-index { display: none !important; }
|
|
716
|
+
.table-wrap { overflow: visible; }
|
|
717
|
+
table { width: 100%; }
|
|
718
|
+
.tone-marker { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
719
|
+
}`;
|
|
720
|
+
|
|
428
721
|
const VISUAL_SECTION_CSS = `
|
|
429
|
-
pre.report-code { background:
|
|
722
|
+
pre.report-code { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; font-family: ${MONO}; font-size: .82rem; margin: 0; overflow-x: auto; padding: 14px 16px; }
|
|
723
|
+
.report-markdown pre.report-code { margin: 12px 0; }
|
|
430
724
|
.report-diagram { max-width: 100%; overflow-x: auto; }
|
|
431
725
|
.report-diagram svg { height: auto; max-width: 100%; }
|
|
432
|
-
.report-diagram
|
|
726
|
+
.report-diagram svg * { filter: none !important; }
|
|
727
|
+
.report-diagram .diagram-dark { display: none; }
|
|
728
|
+
@media screen and (prefers-color-scheme: dark) {
|
|
729
|
+
:root:not([data-scheme="light"]) .report-diagram .diagram-light { display: none; }
|
|
730
|
+
:root:not([data-scheme="light"]) .report-diagram .diagram-dark { display: block; }
|
|
731
|
+
}
|
|
732
|
+
:root[data-scheme="dark"] .report-diagram .diagram-light { display: none; }
|
|
733
|
+
:root[data-scheme="dark"] .report-diagram .diagram-dark { display: block; }
|
|
734
|
+
.report-diagram .mermaid { font-family: ${MONO}; font-size: .82rem; margin: 0; white-space: pre; }
|
|
433
735
|
.diff-file-group + .diff-file-group { margin-top: 18px; }
|
|
434
|
-
.diff-file { color:
|
|
435
|
-
.report-diff { border: 1px solid
|
|
736
|
+
.diff-file { color: var(--muted); font-family: ${MONO}; font-size: .8rem; font-weight: 650; margin: 0 0 8px; }
|
|
737
|
+
.report-diff { border: 1px solid var(--rule); border-radius: 8px; font-family: ${MONO}; font-size: .82rem; overflow-x: auto; padding: 6px 0; }
|
|
436
738
|
.diff-line { -webkit-print-color-adjust: exact; min-width: max-content; padding: 0 12px; print-color-adjust: exact; white-space: pre; }
|
|
437
739
|
.diff-glyph { display: inline-block; user-select: none; width: 1ch; }
|
|
438
|
-
.diff-add { background:
|
|
439
|
-
.diff-del { background:
|
|
440
|
-
.diff-hunk { background:
|
|
740
|
+
.diff-add { background: var(--add-bg); }
|
|
741
|
+
.diff-del { background: var(--del-bg); }
|
|
742
|
+
.diff-hunk { background: var(--hunk-bg); color: var(--muted); }
|
|
441
743
|
@media print {
|
|
442
744
|
pre.report-code, .report-diff { border-color: #999; }
|
|
745
|
+
.report-diagram { background: #fff; }
|
|
746
|
+
.report-diagram svg { max-height: 90vh; }
|
|
747
|
+
.report-diagram .diagram-light { display: block !important; }
|
|
748
|
+
.report-diagram .diagram-dark { display: none !important; }
|
|
443
749
|
.report-diagram, .report-diff, pre.report-code { overflow: visible; }
|
|
444
|
-
.diff-line { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
445
750
|
.diff-hunk { color: #333; }
|
|
446
751
|
}`;
|
|
447
|
-
|
|
448
|
-
type ReportMetricsSection = Extract<ReportSection, { type: "metrics" }>;
|