@stacksjs/defaults 0.75.361 → 0.75.366
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +5 -5
- package/ai/skills/stacks-payments/SKILL.md +10 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ai/skills/stacks-testing/SKILL.md +8 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,31 +1,90 @@
|
|
|
1
|
+
import { softwareNoticesHtml } from './software-notices.mjs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
1
3
|
import fs from 'node:fs';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { applyTemplate, renderCards, esc } from './utils.mjs';
|
|
4
6
|
import { validateSchema } from './validator.mjs';
|
|
5
7
|
import { verifyRepositoryEvidence } from './repository-evidence.mjs';
|
|
6
|
-
import { installRendererDiagnosticBoundary, throwDiagnosticProblems } from './diagnostics.mjs';
|
|
8
|
+
import { installRendererDiagnosticBoundary, throwDiagnosticError, throwDiagnosticProblems, recordDiagnostic } from './diagnostics.mjs';
|
|
7
9
|
import { validateEngineeringProfile } from './engineering-profiles.mjs';
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
resolveOutputPath,
|
|
12
|
+
validateAuthoredOutputPath,
|
|
13
|
+
} from './output-path.mjs';
|
|
14
|
+
import {
|
|
15
|
+
captureAtomicOutput,
|
|
16
|
+
captureRegularFileBinding,
|
|
17
|
+
publishRegularFileBinding,
|
|
18
|
+
releaseRegularFileBinding,
|
|
19
|
+
removeOwnedRegularFile,
|
|
20
|
+
verifyAtomicOutput,
|
|
21
|
+
} from './atomic-output.mjs';
|
|
22
|
+
import { resolveLocale, translateMessage, registerLocale, localeDiagnostics } from './i18n.mjs';
|
|
9
23
|
import { prepareDiagramBrandMarks } from './brand-marks.mjs';
|
|
10
|
-
import { resolveLocale, translateMessage } from './i18n.mjs';
|
|
11
|
-
|
|
12
|
-
installRendererDiagnosticBoundary();
|
|
13
24
|
|
|
14
25
|
const outputPathGuards = new Map();
|
|
26
|
+
let renderCandidateSequence = 0;
|
|
27
|
+
|
|
28
|
+
// meta.locale is renderer-owned Viewer UI, not authored content. Each message
|
|
29
|
+
// resolves as: valid meta.translations value → bundled catalog enrolled in
|
|
30
|
+
// locales/manifest.json → English (see i18n.mjs). Every fallback is disclosed
|
|
31
|
+
// by human renderers on stderr. Diagnostic JSON mode records warnings without
|
|
32
|
+
// mixing text into failure receipts; validate/deliver disclose them publicly.
|
|
33
|
+
function applyLocaleTranslations(diagramType, diagram) {
|
|
34
|
+
const locale = diagram.meta?.locale;
|
|
35
|
+
if (!locale) return;
|
|
36
|
+
registerLocale(locale, diagram.meta.translations);
|
|
37
|
+
for (const diagnostic of localeDiagnostics(diagramType, diagram.meta)) {
|
|
38
|
+
recordDiagnostic(diagnostic);
|
|
39
|
+
if (process.env.ARCHIFY_DIAGNOSTIC_FORMAT !== 'json') {
|
|
40
|
+
console.warn(`archify: ${diagnostic.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
15
44
|
|
|
16
45
|
// Common CLI head: node render-<type>.mjs [input.json] [output.html]
|
|
17
46
|
// Keep this synchronous because callers also use it to establish the guarded
|
|
18
47
|
// output path before testing a last-moment filesystem alias change.
|
|
19
48
|
export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = process.argv }) {
|
|
49
|
+
// Compilers also import this module for SVG helpers. Only CLI execution
|
|
50
|
+
// should install a process-level handler, before reading or validating input.
|
|
51
|
+
installRendererDiagnosticBoundary();
|
|
20
52
|
const skillRoot = path.resolve(rendererDir, '../..');
|
|
21
53
|
const inputPath = path.resolve(argv[2] || path.join(skillRoot, 'examples', defaultExample));
|
|
22
|
-
|
|
54
|
+
let input;
|
|
55
|
+
try {
|
|
56
|
+
input = fs.readFileSync(inputPath, 'utf8');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!isFilesystemError(error)) throw error;
|
|
59
|
+
const message = `Input could not be read: ${error.message}`;
|
|
60
|
+
throwDiagnosticError(message, [{
|
|
61
|
+
code: 'input/read', message,
|
|
62
|
+
subject: { input: inputPath },
|
|
63
|
+
evidence: { systemCode: error.code, reason: error.message },
|
|
64
|
+
supportedFixes: ['provide one readable JSON input file'],
|
|
65
|
+
}]);
|
|
66
|
+
}
|
|
67
|
+
let diagram;
|
|
68
|
+
try {
|
|
69
|
+
diagram = JSON.parse(input);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
72
|
+
const message = `Input JSON could not be parsed: ${error.message}`;
|
|
73
|
+
throwDiagnosticError(message, [{
|
|
74
|
+
code: 'input/json-parse', message,
|
|
75
|
+
subject: { input: inputPath },
|
|
76
|
+
evidence: { reason: error.message },
|
|
77
|
+
supportedFixes: ['repair the JSON syntax and run validation again'],
|
|
78
|
+
}]);
|
|
79
|
+
}
|
|
80
|
+
const authoredOutput = diagram?.meta?.output;
|
|
81
|
+
if (authoredOutput !== undefined) validateAuthoredOutputPath(authoredOutput);
|
|
23
82
|
validateSchema(diagramType, diagram);
|
|
24
|
-
|
|
25
|
-
|
|
83
|
+
applyLocaleTranslations(diagramType, diagram);
|
|
84
|
+
validateCrossCollectionContracts(diagramType, diagram);
|
|
26
85
|
validateEngineeringProfile(diagramType, diagram);
|
|
27
86
|
const sourceEvidence = verifyRepositoryEvidence(diagramType, diagram, process.env.ARCHIFY_REPO_ROOT);
|
|
28
|
-
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8');
|
|
87
|
+
const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8').replace('</head>', `${softwareNoticesHtml}\n</head>`);
|
|
29
88
|
const outputRequest = {
|
|
30
89
|
requestedOutput: argv[3],
|
|
31
90
|
authoredOutput: diagram.meta?.output,
|
|
@@ -33,7 +92,12 @@ export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = p
|
|
|
33
92
|
inputPaths: [inputPath],
|
|
34
93
|
cwd: process.cwd(),
|
|
35
94
|
};
|
|
36
|
-
|
|
95
|
+
let outPath;
|
|
96
|
+
try {
|
|
97
|
+
({ outputPath: outPath } = resolveOutputPath(outputRequest));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throwOutputError(error, path.resolve(outputRequest.requestedOutput || outputRequest.authoredOutput || outputRequest.defaultOutput));
|
|
100
|
+
}
|
|
37
101
|
outputPathGuards.set(outPath, outputRequest);
|
|
38
102
|
return { diagram, template, outPath, sourceEvidence };
|
|
39
103
|
}
|
|
@@ -47,25 +111,194 @@ export async function loadDiagramWithBrandMarks(options) {
|
|
|
47
111
|
return loaded;
|
|
48
112
|
}
|
|
49
113
|
|
|
50
|
-
const START_TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);
|
|
114
|
+
const START_TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle', 'erd', 'tree', 'class', 'timeline', 'waterfall']);
|
|
115
|
+
|
|
116
|
+
function isFilesystemError(error) {
|
|
117
|
+
return typeof error?.code === 'string'
|
|
118
|
+
&& typeof error?.syscall === 'string'
|
|
119
|
+
&& typeof error?.errno === 'number';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function throwOutputError(error, output) {
|
|
123
|
+
if (error?.archifyDiagnostics || !isFilesystemError(error)) throw error;
|
|
124
|
+
const message = `Output could not be written: ${error.message}`;
|
|
125
|
+
throwDiagnosticError(message, [{
|
|
126
|
+
code: 'output/write', message,
|
|
127
|
+
subject: { output },
|
|
128
|
+
evidence: { systemCode: error.code, reason: error.message },
|
|
129
|
+
supportedFixes: ['choose a writable HTML file path and ensure its parent directories can be created'],
|
|
130
|
+
}]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function throwAtomicOutputFailure(result, output) {
|
|
134
|
+
const reason = result.reason || { code: 'unclassified' };
|
|
135
|
+
const changed = result.status === 'different';
|
|
136
|
+
const candidate = reason.code.startsWith('candidate-');
|
|
137
|
+
const nonRegular = ['target-not-regular-file', 'candidate-not-regular-file'].includes(reason.code);
|
|
138
|
+
const hardlinked = ['target-hardlinked', 'candidate-hardlinked'].includes(reason.code);
|
|
139
|
+
const message = changed
|
|
140
|
+
? 'Output target changed while the rendered artifact was being prepared.'
|
|
141
|
+
: nonRegular
|
|
142
|
+
? candidate
|
|
143
|
+
? 'Temporary output candidate is no longer a regular file.'
|
|
144
|
+
: 'Output already exists and is not a regular file.'
|
|
145
|
+
: hardlinked
|
|
146
|
+
? candidate
|
|
147
|
+
? 'Temporary output candidate has multiple hard-link names.'
|
|
148
|
+
: 'Output already exists through multiple hard-link names.'
|
|
149
|
+
: 'Output target stability could not be determined safely before commit.';
|
|
150
|
+
throwDiagnosticError(message, [{
|
|
151
|
+
code: changed
|
|
152
|
+
? 'output/target-changed'
|
|
153
|
+
: nonRegular
|
|
154
|
+
? 'output/target-not-regular-file'
|
|
155
|
+
: hardlinked
|
|
156
|
+
? 'output/target-hardlinked'
|
|
157
|
+
: 'output/target-indeterminate',
|
|
158
|
+
message,
|
|
159
|
+
subject: { output },
|
|
160
|
+
evidence: { relation: reason },
|
|
161
|
+
supportedFixes: [hardlinked
|
|
162
|
+
? 'choose a non-hardlinked output path; atomic replacement cannot update every hard-link name'
|
|
163
|
+
: 'retry after other processes stop replacing or redirecting the output path'],
|
|
164
|
+
}]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function stageRenderedHtml(outputPath, html, mode) {
|
|
168
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
169
|
+
renderCandidateSequence += 1;
|
|
170
|
+
const candidatePath = path.join(
|
|
171
|
+
path.dirname(outputPath),
|
|
172
|
+
`.archify-render-${process.pid}-${Date.now().toString(36)}-${renderCandidateSequence}.tmp`,
|
|
173
|
+
);
|
|
174
|
+
let descriptor;
|
|
175
|
+
let identity;
|
|
176
|
+
try {
|
|
177
|
+
const noFollow = process.platform === 'win32' ? 0 : (fs.constants.O_NOFOLLOW || 0);
|
|
178
|
+
descriptor = fs.openSync(
|
|
179
|
+
candidatePath,
|
|
180
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow,
|
|
181
|
+
mode ?? 0o666,
|
|
182
|
+
);
|
|
183
|
+
let metadata;
|
|
184
|
+
try {
|
|
185
|
+
metadata = fs.fstatSync(descriptor, { bigint: true });
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// A transient first inspection failure must not strand the exclusive
|
|
188
|
+
// candidate. A successful retry binds cleanup to the still-open file;
|
|
189
|
+
// if both inspections fail, preserving the unknown entry is safer.
|
|
190
|
+
try {
|
|
191
|
+
const retry = fs.fstatSync(descriptor, { bigint: true });
|
|
192
|
+
if (retry.isFile() && retry.ino !== 0n) {
|
|
193
|
+
identity = { device: retry.dev, inode: retry.ino };
|
|
194
|
+
}
|
|
195
|
+
} catch {}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
if (!metadata.isFile() || metadata.ino === 0n) {
|
|
199
|
+
throw new Error('Temporary render candidate identity could not be verified safely.');
|
|
200
|
+
}
|
|
201
|
+
identity = { device: metadata.dev, inode: metadata.ino };
|
|
202
|
+
fs.writeFileSync(descriptor, html);
|
|
203
|
+
// Creation modes are filtered through the process umask. An atomic
|
|
204
|
+
// replacement must retain the exact permissions of an existing target,
|
|
205
|
+
// while a brand-new target should keep normal umask behavior.
|
|
206
|
+
if (mode !== null) fs.fchmodSync(descriptor, mode);
|
|
207
|
+
fs.closeSync(descriptor);
|
|
208
|
+
descriptor = undefined;
|
|
209
|
+
return { candidatePath, identity };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (descriptor !== undefined) {
|
|
212
|
+
try { fs.closeSync(descriptor); } catch {}
|
|
213
|
+
}
|
|
214
|
+
if (error.code === 'EEXIST') continue;
|
|
215
|
+
if (identity) {
|
|
216
|
+
const cleanup = removeOwnedRegularFile(candidatePath, identity);
|
|
217
|
+
if (!['removed', 'absent', 'preserved'].includes(cleanup.status)) {
|
|
218
|
+
const cleanupError = new Error(`${error.message}; temporary render candidate cleanup also failed.`);
|
|
219
|
+
cleanupError.cause = error;
|
|
220
|
+
throw cleanupError;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const error = new Error(`Could not reserve a temporary render candidate beside "${outputPath}".`);
|
|
227
|
+
error.code = 'EEXIST';
|
|
228
|
+
error.errno = -17;
|
|
229
|
+
error.syscall = 'open';
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
51
232
|
|
|
52
233
|
// Common CLI tail: fill the template and write the standalone HTML file.
|
|
53
234
|
export function writeDiagram({ outPath, template, diagramType, meta, svg, cards, sourceEvidence = null }) {
|
|
54
235
|
if (!START_TYPES.has(diagramType)) throw new Error(`writeDiagram: unknown diagram type ${JSON.stringify(diagramType)}`);
|
|
55
236
|
const outputGuard = outputPathGuards.get(outPath);
|
|
56
|
-
|
|
57
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
58
|
-
fs.writeFileSync(outPath, applyTemplate(template, {
|
|
237
|
+
const html = applyTemplate(template, {
|
|
59
238
|
title: meta.title,
|
|
60
239
|
subtitle: meta.subtitle,
|
|
61
240
|
svg,
|
|
62
241
|
cards: renderCards(cards),
|
|
63
242
|
locale: meta.locale,
|
|
64
243
|
visualPreset: meta.visual_preset || 'classic',
|
|
65
|
-
guidedViews: meta.views || [],
|
|
66
244
|
sourceEvidence,
|
|
67
|
-
})
|
|
68
|
-
|
|
245
|
+
});
|
|
246
|
+
let candidatePath;
|
|
247
|
+
let candidateIdentity;
|
|
248
|
+
let candidateBinding;
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
251
|
+
const outputCapture = captureAtomicOutput(outPath);
|
|
252
|
+
if (outputCapture.status !== 'captured') throwAtomicOutputFailure(outputCapture, outPath);
|
|
253
|
+
const beforeStage = verifyAtomicOutput(outputCapture.snapshot);
|
|
254
|
+
if (beforeStage.status !== 'match') throwAtomicOutputFailure(beforeStage, outPath);
|
|
255
|
+
({ candidatePath, identity: candidateIdentity } = stageRenderedHtml(
|
|
256
|
+
outputCapture.commitPath,
|
|
257
|
+
html,
|
|
258
|
+
outputCapture.mode,
|
|
259
|
+
));
|
|
260
|
+
|
|
261
|
+
// The renderer may spend substantial time building HTML after loadDiagram
|
|
262
|
+
// establishes the guard. Re-run it after staging so a last-moment alias
|
|
263
|
+
// cannot redirect the commit onto an input file.
|
|
264
|
+
if (outputGuard) resolveOutputPath(outputGuard);
|
|
265
|
+
const candidateCapture = captureRegularFileBinding(candidatePath, {
|
|
266
|
+
subject: 'candidate',
|
|
267
|
+
expectedSha256: createHash('sha256').update(html).digest('hex'),
|
|
268
|
+
expectedBytes: Buffer.byteLength(html),
|
|
269
|
+
expectedIdentity: candidateIdentity,
|
|
270
|
+
...(outputCapture.mode === null ? {} : { expectedMode: outputCapture.mode }),
|
|
271
|
+
});
|
|
272
|
+
if (candidateCapture.status !== 'captured') throwAtomicOutputFailure(candidateCapture, outPath);
|
|
273
|
+
candidateBinding = candidateCapture.binding;
|
|
274
|
+
const beforeCommit = verifyAtomicOutput(outputCapture.snapshot);
|
|
275
|
+
if (beforeCommit.status !== 'match') throwAtomicOutputFailure(beforeCommit, outPath);
|
|
276
|
+
const publication = publishRegularFileBinding(
|
|
277
|
+
candidateBinding,
|
|
278
|
+
candidatePath,
|
|
279
|
+
outputCapture.snapshot,
|
|
280
|
+
{ subject: 'candidate' },
|
|
281
|
+
);
|
|
282
|
+
if (!['committed', 'committed-with-warning'].includes(publication.status)) {
|
|
283
|
+
throwAtomicOutputFailure(publication, outPath);
|
|
284
|
+
}
|
|
285
|
+
const releasedCandidate = releaseRegularFileBinding(candidateBinding);
|
|
286
|
+
candidateBinding = undefined;
|
|
287
|
+
if (releasedCandidate.status !== 'released') throwAtomicOutputFailure(releasedCandidate, outPath);
|
|
288
|
+
candidatePath = undefined;
|
|
289
|
+
candidateIdentity = undefined;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throwOutputError(error, outPath);
|
|
292
|
+
} finally {
|
|
293
|
+
outputPathGuards.delete(outPath);
|
|
294
|
+
if (candidateBinding) releaseRegularFileBinding(candidateBinding);
|
|
295
|
+
if (candidatePath && candidateIdentity) {
|
|
296
|
+
const cleanup = removeOwnedRegularFile(candidatePath, candidateIdentity);
|
|
297
|
+
if (!['removed', 'absent', 'preserved'].includes(cleanup.status)) {
|
|
298
|
+
throwAtomicOutputFailure(cleanup, outPath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
69
302
|
console.log(outPath);
|
|
70
303
|
}
|
|
71
304
|
|
|
@@ -75,6 +308,11 @@ const SEMANTIC_COLLECTIONS = {
|
|
|
75
308
|
sequence: 'participants',
|
|
76
309
|
dataflow: 'nodes',
|
|
77
310
|
lifecycle: 'states',
|
|
311
|
+
erd: 'entities',
|
|
312
|
+
tree: 'nodes',
|
|
313
|
+
class: 'types',
|
|
314
|
+
timeline: 'events',
|
|
315
|
+
waterfall: 'spans',
|
|
78
316
|
};
|
|
79
317
|
|
|
80
318
|
const RELATIONSHIP_COLLECTIONS = {
|
|
@@ -83,6 +321,8 @@ const RELATIONSHIP_COLLECTIONS = {
|
|
|
83
321
|
sequence: 'messages',
|
|
84
322
|
dataflow: 'flows',
|
|
85
323
|
lifecycle: 'transitions',
|
|
324
|
+
erd: 'relationships',
|
|
325
|
+
class: 'relationships',
|
|
86
326
|
};
|
|
87
327
|
|
|
88
328
|
// Relationship IDs are optional for backwards compatibility, but once an
|
|
@@ -111,48 +351,21 @@ export function validateRelationshipIds(diagramType, diagram) {
|
|
|
111
351
|
}
|
|
112
352
|
}
|
|
113
353
|
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const views = diagram.meta?.views;
|
|
119
|
-
if (!Array.isArray(views) || views.length === 0) return;
|
|
120
|
-
const collection = SEMANTIC_COLLECTIONS[diagramType];
|
|
121
|
-
const semanticIds = new Set((diagram[collection] || []).map((item) => item.id));
|
|
122
|
-
const seen = new Set();
|
|
123
|
-
const problems = [];
|
|
124
|
-
|
|
125
|
-
views.forEach((view, index) => {
|
|
126
|
-
if (seen.has(view.id)) problems.push(`/meta/views/${index}/id duplicates view id ${JSON.stringify(view.id)}`);
|
|
127
|
-
seen.add(view.id);
|
|
128
|
-
const seenFocus = new Set();
|
|
129
|
-
(view.focus || []).forEach((id, focusIndex) => {
|
|
130
|
-
if (seenFocus.has(id)) {
|
|
131
|
-
problems.push(`/meta/views/${index}/focus/${focusIndex} duplicates semantic id ${JSON.stringify(id)}`);
|
|
132
|
-
}
|
|
133
|
-
seenFocus.add(id);
|
|
134
|
-
if (!semanticIds.has(id)) {
|
|
135
|
-
problems.push(`/meta/views/${index}/focus/${focusIndex} references unknown semantic id ${JSON.stringify(id)}`);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
if (problems.length) {
|
|
141
|
-
throwDiagnosticProblems('Guided view validation failed', problems, {
|
|
142
|
-
code: 'guided-view/invalid',
|
|
143
|
-
subject: { diagramType, collection: 'meta.views' },
|
|
144
|
-
});
|
|
145
|
-
}
|
|
354
|
+
// Share relationship-ID semantic checks between the loader
|
|
355
|
+
// and workflow compiler without performing filesystem operations (see #429).
|
|
356
|
+
export function validateCrossCollectionContracts(diagramType, diagram) {
|
|
357
|
+
validateRelationshipIds(diagramType, diagram);
|
|
146
358
|
}
|
|
147
359
|
|
|
148
360
|
// Accessible name for the generated diagram SVG.
|
|
149
|
-
export function svgRootAttrs(meta) {
|
|
150
|
-
|
|
361
|
+
export function svgRootAttrs(meta, explicitQualityProfile) {
|
|
362
|
+
// Motion is a Viewer capability for every diagram; Live/Still controls playback.
|
|
363
|
+
const animation = ' data-animation="trace"';
|
|
151
364
|
const preset = ` data-preset="${esc(meta.visual_preset || 'classic')}"`;
|
|
152
365
|
const engineeringProfile = meta.engineering_profile
|
|
153
366
|
? ` data-engineering-profile="${esc(meta.engineering_profile)}"`
|
|
154
367
|
: '';
|
|
155
|
-
const requestedProfile = process.env.ARCHIFY_QUALITY_PROFILE || meta.quality_profile;
|
|
368
|
+
const requestedProfile = explicitQualityProfile || process.env.ARCHIFY_QUALITY_PROFILE || meta.quality_profile;
|
|
156
369
|
const qualityProfile = requestedProfile === 'showcase' ? 'showcase' : 'standard';
|
|
157
370
|
const advisory = requestedProfile ? '' : ' data-quality-gates="advisory"';
|
|
158
371
|
return `role="img" lang="${esc(resolveLocale(meta.locale))}" aria-labelledby="archify-diagram-title archify-diagram-description"${animation}${preset}${engineeringProfile} data-quality-profile="${esc(qualityProfile)}"${advisory}`;
|
|
@@ -167,10 +380,9 @@ export function svgAccessibleText(meta, kind) {
|
|
|
167
380
|
}
|
|
168
381
|
|
|
169
382
|
export function animateAttr(meta, kind, step) {
|
|
170
|
-
if (meta.animation !== 'trace') return '';
|
|
171
383
|
// Ambient trace must finish inside the fixed six-second WebM capture. The
|
|
172
384
|
// cap affects visual delay only; authored order and semantic identity stay
|
|
173
|
-
// untouched in the JSON, DOM,
|
|
385
|
+
// untouched in the JSON, DOM, and relationship contracts.
|
|
174
386
|
const safeStep = Number.isFinite(step) && step >= 0 ? Math.min(12, Math.floor(step)) : 0;
|
|
175
387
|
return ` data-animate="${kind}" style="--step:${safeStep}"`;
|
|
176
388
|
}
|
|
@@ -3,6 +3,11 @@ export const DESKTOP_READER_MIN_WIDTH = 960;
|
|
|
3
3
|
export const DESKTOP_READER_HORIZONTAL_CHROME = 30;
|
|
4
4
|
export const DESKTOP_READER_DIAGRAM_WIDTH = DESKTOP_READER_MIN_WIDTH - DESKTOP_READER_HORIZONTAL_CHROME;
|
|
5
5
|
export const MIN_PROJECTED_NODE_TEXT_PX = 6;
|
|
6
|
+
export const DECLARED_WIDE_READER_CONTRACT = 'declared-wide-v1';
|
|
7
|
+
export const DECLARED_WIDE_READER_RATIO = 1.55;
|
|
8
|
+
export const DECLARED_WIDE_READER_MAX_WIDTH = 1920;
|
|
9
|
+
export const DECLARED_WIDE_REFERENCE_BODY_HORIZONTAL_PX = 64;
|
|
10
|
+
export const DECLARED_WIDE_REFERENCE_DIAGRAM_HORIZONTAL_PX = 30;
|
|
6
11
|
|
|
7
12
|
export function projectedNodeTextPx(sourceFontPx, viewBoxWidth, diagramWidth = DESKTOP_READER_DIAGRAM_WIDTH) {
|
|
8
13
|
if (![sourceFontPx, viewBoxWidth, diagramWidth].every(Number.isFinite) || viewBoxWidth <= 0 || diagramWidth <= 0) {
|
|
@@ -24,3 +29,104 @@ export function minimumReadableSourceTextPx(
|
|
|
24
29
|
}
|
|
25
30
|
return minimumProjectedPx / Math.min(1, diagramWidth / viewBoxWidth);
|
|
26
31
|
}
|
|
32
|
+
|
|
33
|
+
// This is deliberately separate from the legacy 930px projection. Architecture
|
|
34
|
+
// boundary convergence depends on that legacy default, while only a recognized
|
|
35
|
+
// v2 wide Reader may use this declared-width proof.
|
|
36
|
+
export function declaredWideReadabilityBudget({
|
|
37
|
+
viewBoxWidth,
|
|
38
|
+
viewBoxHeight,
|
|
39
|
+
minimumSourceTextPx,
|
|
40
|
+
requestedMinimumTextPx,
|
|
41
|
+
viewportWidth = DESKTOP_READABILITY_VIEWPORT.width,
|
|
42
|
+
bodyHorizontalPx = DECLARED_WIDE_REFERENCE_BODY_HORIZONTAL_PX,
|
|
43
|
+
diagramHorizontalPx = DECLARED_WIDE_REFERENCE_DIAGRAM_HORIZONTAL_PX,
|
|
44
|
+
minimumReaderWidth = DESKTOP_READER_MIN_WIDTH,
|
|
45
|
+
maximumReaderWidth = DECLARED_WIDE_READER_MAX_WIDTH,
|
|
46
|
+
} = {}) {
|
|
47
|
+
const values = [
|
|
48
|
+
viewBoxWidth, viewBoxHeight, minimumSourceTextPx, requestedMinimumTextPx,
|
|
49
|
+
viewportWidth, bodyHorizontalPx, diagramHorizontalPx, minimumReaderWidth, maximumReaderWidth,
|
|
50
|
+
];
|
|
51
|
+
if (!values.every(Number.isFinite) || viewBoxWidth <= 0 || viewBoxHeight <= 0
|
|
52
|
+
|| minimumSourceTextPx <= 0 || requestedMinimumTextPx <= 0 || viewportWidth <= 0
|
|
53
|
+
|| bodyHorizontalPx < 0 || diagramHorizontalPx < 0 || minimumReaderWidth <= 0
|
|
54
|
+
|| maximumReaderWidth < minimumReaderWidth || viewBoxWidth / viewBoxHeight < DECLARED_WIDE_READER_RATIO) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const requestedTargetPx = Math.max(MIN_PROJECTED_NODE_TEXT_PX, requestedMinimumTextPx);
|
|
58
|
+
const requestedScale = Math.min(1, requestedTargetPx / minimumSourceTextPx);
|
|
59
|
+
const desiredReaderWidth = Math.max(minimumReaderWidth, viewBoxWidth * requestedScale + diagramHorizontalPx);
|
|
60
|
+
const viewportCap = Math.max(0, viewportWidth - bodyHorizontalPx);
|
|
61
|
+
const cap = Math.min(maximumReaderWidth, viewportCap);
|
|
62
|
+
const actualReaderWidth = Math.min(desiredReaderWidth, cap);
|
|
63
|
+
const guaranteedSvgWidth = Math.max(0, actualReaderWidth - diagramHorizontalPx);
|
|
64
|
+
const projectedMinimumTextPx = projectedNodeTextPx(minimumSourceTextPx, viewBoxWidth, guaranteedSvgWidth);
|
|
65
|
+
const limit = actualReaderWidth < desiredReaderWidth
|
|
66
|
+
? (viewportCap <= maximumReaderWidth ? 'viewport-cap' : 'reader-cap')
|
|
67
|
+
: 'source-size';
|
|
68
|
+
return {
|
|
69
|
+
requestedTargetPx,
|
|
70
|
+
requestedScale,
|
|
71
|
+
desiredReaderWidth,
|
|
72
|
+
viewportCap,
|
|
73
|
+
maximumReaderWidth,
|
|
74
|
+
actualReaderWidth,
|
|
75
|
+
guaranteedSvgWidth,
|
|
76
|
+
projectedMinimumTextPx,
|
|
77
|
+
hardFloorPx: MIN_PROJECTED_NODE_TEXT_PX,
|
|
78
|
+
hardFloorMet: projectedMinimumTextPx >= MIN_PROJECTED_NODE_TEXT_PX,
|
|
79
|
+
requestedTargetMet: projectedMinimumTextPx >= requestedMinimumTextPx,
|
|
80
|
+
limit,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Vertical chrome that always stacks with the SVG at the 1440x900 desktop
|
|
85
|
+
// viewport, measured from the delivered Viewer with the shortest one-line
|
|
86
|
+
// header and no cards: body padding 12, header 39, diagram padding/border 75.
|
|
87
|
+
// Cards are excluded so the prediction stays a lower bound.
|
|
88
|
+
export const DESKTOP_FIXED_VERTICAL_CHROME_PX = Object.freeze({ body: 12, header: 39, diagram: 75 });
|
|
89
|
+
|
|
90
|
+
// A canvas the Reader can neither narrow (viewBox ratio below the wide
|
|
91
|
+
// threshold) nor scroll readably (no automatic Reader fit) renders at the full
|
|
92
|
+
// reader width, so its page height is a function of the viewBox alone. Returns
|
|
93
|
+
// null when the Reader has a way to fit the page; otherwise the certain
|
|
94
|
+
// overflow at 1440x900 before any cards are counted.
|
|
95
|
+
export function predictedFixedWidthOverflow({
|
|
96
|
+
viewBoxWidth,
|
|
97
|
+
viewBoxHeight,
|
|
98
|
+
readerFit,
|
|
99
|
+
diagramType,
|
|
100
|
+
viewport = DESKTOP_READABILITY_VIEWPORT,
|
|
101
|
+
bodyHorizontalPx = DECLARED_WIDE_REFERENCE_BODY_HORIZONTAL_PX,
|
|
102
|
+
diagramHorizontalPx = DECLARED_WIDE_REFERENCE_DIAGRAM_HORIZONTAL_PX,
|
|
103
|
+
chrome = DESKTOP_FIXED_VERTICAL_CHROME_PX,
|
|
104
|
+
} = {}) {
|
|
105
|
+
if (![viewBoxWidth, viewBoxHeight].every(Number.isFinite) || viewBoxWidth <= 0 || viewBoxHeight <= 0) return null;
|
|
106
|
+
const ratio = viewBoxWidth / viewBoxHeight;
|
|
107
|
+
if (readerFit === 'intrinsic-height' || readerFit === 'width-first'
|
|
108
|
+
|| (readerFit === 'authored-height' && diagramType === 'architecture')
|
|
109
|
+
|| ratio >= DECLARED_WIDE_READER_RATIO) return null;
|
|
110
|
+
const svgWidthPx = viewport.width - bodyHorizontalPx - diagramHorizontalPx;
|
|
111
|
+
const svgHeightPx = Math.round(svgWidthPx * viewBoxHeight / viewBoxWidth);
|
|
112
|
+
const fixedChromePx = chrome.body + chrome.header + chrome.diagram;
|
|
113
|
+
const pageHeightPx = svgHeightPx + fixedChromePx;
|
|
114
|
+
if (pageHeightPx <= viewport.height) return null;
|
|
115
|
+
return {
|
|
116
|
+
viewportWidth: viewport.width,
|
|
117
|
+
viewportHeight: viewport.height,
|
|
118
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
119
|
+
wideRatio: DECLARED_WIDE_READER_RATIO,
|
|
120
|
+
svgWidthPx,
|
|
121
|
+
svgHeightPx,
|
|
122
|
+
fixedChromePx,
|
|
123
|
+
pageHeightPx,
|
|
124
|
+
overflowPx: pageHeightPx - viewport.height,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function describeFixedWidthOverflow(issue) {
|
|
129
|
+
const maximumViewBoxHeight = Math.floor(issue.viewBoxWidth / issue.wideRatio);
|
|
130
|
+
const wideViewBoxWidth = Math.ceil(issue.viewBoxHeight * issue.wideRatio);
|
|
131
|
+
return `Preserve every node, relationship, and label. This ${issue.viewBoxWidth}x${issue.viewBoxHeight} canvas (ratio ${issue.ratio}) declares no intrinsic-height fit and is below the ${issue.wideRatio} wide ratio, so the desktop Reader can neither narrow it nor accept vertical scroll: it renders ${issue.svgHeightPx}px tall at the full ${issue.svgWidthPx}px width and the page reaches ${issue.pageHeightPx}px before cards against ${issue.viewportHeight}px, a certain visual-check failure. Either compact vertical spacing so meta.viewBox height is at most ${maximumViewBoxHeight} at this width, or spread content sideways so the width is at least ${wideViewBoxWidth} at this height; for architecture and readable-v2 workflow, omitting meta.viewBox lets the renderer size the canvas and declare the fit.`;
|
|
132
|
+
}
|
|
Binary file
|