@geraldmaron/construct 1.1.1 → 1.2.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/README.md +29 -19
- package/bin/construct +311 -81
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +112 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +87 -18
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +245 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +129 -15
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/schema.mjs +36 -5
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +360 -36
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/cli.mjs +2 -2
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/worker.mjs +2 -2
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +4 -5
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +27 -97
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +84 -9
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +252 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +228 -7
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +480 -0
- package/specialists/artifact-manifest.schema.json +53 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- package/templates/docs/test-plan.md +7 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* templates/demos/specs/_helpers/scroll-artifact.ts — shared Act 2 scroll helpers.
|
|
3
|
+
*
|
|
4
|
+
* Used by Construct cockpit demos and copied into project .cx/demos/specs/ on init.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type Page, expect } from '@playwright/test';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
type RevealArtifactOpts = {
|
|
12
|
+
file?: string;
|
|
13
|
+
basePath?: string;
|
|
14
|
+
artifactDir?: string;
|
|
15
|
+
mode?: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function scrollPdfViewer(page: Page) {
|
|
20
|
+
await page.waitForTimeout(1500);
|
|
21
|
+
const viewer = page.locator('embed[type="application/pdf"], #viewer, pdf-viewer');
|
|
22
|
+
if (await viewer.count()) {
|
|
23
|
+
await viewer.first().click({ timeout: 5000 }).catch(() => page.mouse.click(640, 360));
|
|
24
|
+
} else {
|
|
25
|
+
await page.mouse.click(640, 360);
|
|
26
|
+
}
|
|
27
|
+
await page.waitForTimeout(800);
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < 8; i += 1) {
|
|
30
|
+
await page.keyboard.press('PageDown');
|
|
31
|
+
await page.waitForTimeout(900);
|
|
32
|
+
}
|
|
33
|
+
await page.waitForTimeout(1500);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function scrollPage(page: Page, { steps = 10, delta = 500, pauseMs = 700 } = {}) {
|
|
37
|
+
await page.waitForTimeout(1000);
|
|
38
|
+
for (let i = 0; i < steps; i += 1) {
|
|
39
|
+
await page.mouse.wheel(0, delta);
|
|
40
|
+
await page.waitForTimeout(pauseMs);
|
|
41
|
+
}
|
|
42
|
+
await page.waitForTimeout(1000);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function revealArtifact(page: Page, {
|
|
46
|
+
file,
|
|
47
|
+
basePath = '/demo-preview',
|
|
48
|
+
artifactDir = process.env.CONSTRUCT_DEMO_ARTIFACT_DIR || '',
|
|
49
|
+
mode = process.env.DEMO_ARTIFACT_REVEAL_MODE || 'constructPreview',
|
|
50
|
+
baseUrl = process.env.BASE_URL || '',
|
|
51
|
+
}: RevealArtifactOpts = {}) {
|
|
52
|
+
const artifactFile = file || process.env.DEMO_ARTIFACT_FILE || '';
|
|
53
|
+
const dir = artifactDir;
|
|
54
|
+
const artifactPath = dir && artifactFile ? path.join(dir, artifactFile) : '';
|
|
55
|
+
if (!artifactFile || !dir || !fs.existsSync(artifactPath)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const encoded = encodeURIComponent(artifactFile);
|
|
60
|
+
const url = mode === 'sameOrigin' && baseUrl
|
|
61
|
+
? `${baseUrl.replace(/\/$/, '')}/${encoded}`
|
|
62
|
+
: `${basePath}/${encoded}`;
|
|
63
|
+
|
|
64
|
+
await page.goto(url);
|
|
65
|
+
await page.waitForLoadState('domcontentloaded');
|
|
66
|
+
|
|
67
|
+
let usedPdf = true;
|
|
68
|
+
try {
|
|
69
|
+
await expect(page).toHaveURL(new RegExp(artifactFile.replace('.', '\\.')));
|
|
70
|
+
await scrollPdfViewer(page);
|
|
71
|
+
} catch {
|
|
72
|
+
usedPdf = false;
|
|
73
|
+
const htmlName = artifactFile.replace(/\.pdf$/i, '.html');
|
|
74
|
+
const htmlPath = path.join(dir, htmlName);
|
|
75
|
+
if (!fs.existsSync(htmlPath)) {
|
|
76
|
+
throw new Error(`PDF viewer scroll failed and HTML fallback missing: ${htmlPath}`);
|
|
77
|
+
}
|
|
78
|
+
const htmlUrl = mode === 'sameOrigin' && baseUrl
|
|
79
|
+
? `${baseUrl.replace(/\/$/, '')}/${encodeURIComponent(htmlName)}`
|
|
80
|
+
: `${basePath}/${encodeURIComponent(htmlName)}`;
|
|
81
|
+
await page.goto(htmlUrl);
|
|
82
|
+
await page.waitForLoadState('domcontentloaded');
|
|
83
|
+
await scrollPage(page);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!usedPdf && process.env.CI) {
|
|
87
|
+
throw new Error('PDF viewer automation failed on CI; HTML fallback used — fix PDF scroll selectors');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Require node
|
|
2
|
+
Require vhs
|
|
3
|
+
# construct demo: publish guardrails via construct chat cockpit (violet theme)
|
|
4
|
+
Output .cx/demos/agentic-platforms-prd.mp4
|
|
5
|
+
Set FontSize 18
|
|
6
|
+
Set Width 1200
|
|
7
|
+
Set Height 720
|
|
8
|
+
Set Theme {"name":"Construct Cockpit","background":"#0c1018","foreground":"#e8eaed","cursor":"#8b5cf6","cursorAccent":"#0c1018","selection":"#1e1b4b","black":"#0c1018","red":"#e06c75","green":"#98c379","yellow":"#e5c07b","blue":"#8b5cf6","magenta":"#c4b5fd","cyan":"#a78bfa","white":"#e8eaed","brightBlack":"#6b7280","brightRed":"#f87171","brightGreen":"#86efac","brightYellow":"#fbbf24","brightBlue":"#a78bfa","brightMagenta":"#ddd6fe","brightCyan":"#c4b5fd","brightWhite":"#ffffff"}
|
|
9
|
+
Set TypingSpeed 42ms
|
|
10
|
+
|
|
11
|
+
Type "node bin/construct chat --demo=agentic-platforms-prd --plain"
|
|
12
|
+
Sleep 800ms
|
|
13
|
+
Enter
|
|
14
|
+
Sleep 3s
|
|
15
|
+
|
|
16
|
+
Type "/demo steps"
|
|
17
|
+
Sleep 400ms
|
|
18
|
+
Enter
|
|
19
|
+
Sleep 2s
|
|
20
|
+
|
|
21
|
+
Type "/demo next"
|
|
22
|
+
Sleep 400ms
|
|
23
|
+
Enter
|
|
24
|
+
Sleep 2s
|
|
25
|
+
|
|
26
|
+
Type "/demo next"
|
|
27
|
+
Sleep 400ms
|
|
28
|
+
Enter
|
|
29
|
+
Sleep 2s
|
|
30
|
+
|
|
31
|
+
Type "/demo next"
|
|
32
|
+
Sleep 400ms
|
|
33
|
+
Enter
|
|
34
|
+
Sleep 2s
|
|
35
|
+
|
|
36
|
+
Type "/demo next"
|
|
37
|
+
Sleep 400ms
|
|
38
|
+
Enter
|
|
39
|
+
Sleep 2s
|
|
40
|
+
|
|
41
|
+
Type "/demo next"
|
|
42
|
+
Sleep 400ms
|
|
43
|
+
Enter
|
|
44
|
+
Sleep 2s
|
|
45
|
+
|
|
46
|
+
Type "/exit"
|
|
47
|
+
Sleep 400ms
|
|
48
|
+
Enter
|
|
49
|
+
Sleep 1s
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Require node
|
|
2
|
+
Require vhs
|
|
3
|
+
# construct demo: resource guard rails — disk budget walkthrough
|
|
4
|
+
Output .cx/demos/resource-guard-rails.mp4
|
|
5
|
+
Set FontSize 18
|
|
6
|
+
Set Width 1200
|
|
7
|
+
Set Height 680
|
|
8
|
+
Set Theme "Dracula"
|
|
9
|
+
Set TypingSpeed 55ms
|
|
10
|
+
|
|
11
|
+
Type "# 1) Show .cx/ usage vs cap in doctor"
|
|
12
|
+
Sleep 400ms
|
|
13
|
+
Enter
|
|
14
|
+
Sleep 800ms
|
|
15
|
+
|
|
16
|
+
Type "node bin/construct doctor 2>&1 | tail -25"
|
|
17
|
+
Sleep 400ms
|
|
18
|
+
Enter
|
|
19
|
+
Sleep 3s
|
|
20
|
+
|
|
21
|
+
Type "# 2) Preview retention prune (dry-run)"
|
|
22
|
+
Sleep 400ms
|
|
23
|
+
Enter
|
|
24
|
+
Sleep 800ms
|
|
25
|
+
|
|
26
|
+
Type "node bin/construct prune --dry-run"
|
|
27
|
+
Sleep 400ms
|
|
28
|
+
Enter
|
|
29
|
+
Sleep 2s
|
|
30
|
+
|
|
31
|
+
Type "# 3) Run resource budget functional test"
|
|
32
|
+
Sleep 400ms
|
|
33
|
+
Enter
|
|
34
|
+
Sleep 800ms
|
|
35
|
+
|
|
36
|
+
Type "node --test tests/functional/resource-write-guard.functional.test.mjs"
|
|
37
|
+
Sleep 400ms
|
|
38
|
+
Enter
|
|
39
|
+
Sleep 3s
|
|
40
|
+
|
|
41
|
+
Type "# 4) User-scope cleanup (logs + pressure)"
|
|
42
|
+
Sleep 400ms
|
|
43
|
+
Enter
|
|
44
|
+
Sleep 800ms
|
|
45
|
+
|
|
46
|
+
Type "node bin/construct cleanup --disk-only --dry-run"
|
|
47
|
+
Sleep 400ms
|
|
48
|
+
Enter
|
|
49
|
+
Sleep 2s
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Construct Cockpit",
|
|
3
|
+
"background": "#0a0c10",
|
|
4
|
+
"foreground": "#e8eaed",
|
|
5
|
+
"cursor": "#ffffff",
|
|
6
|
+
"cursorAccent": "#0a0c10",
|
|
7
|
+
"selection": "#23272e",
|
|
8
|
+
"black": "#0a0c10",
|
|
9
|
+
"red": "#e06c75",
|
|
10
|
+
"green": "#98c379",
|
|
11
|
+
"yellow": "#e5c07b",
|
|
12
|
+
"blue": "#8a93a3",
|
|
13
|
+
"magenta": "#b8bec8",
|
|
14
|
+
"cyan": "#aab2bd",
|
|
15
|
+
"white": "#e8eaed",
|
|
16
|
+
"brightBlack": "#565c66",
|
|
17
|
+
"brightRed": "#f87171",
|
|
18
|
+
"brightGreen": "#86efac",
|
|
19
|
+
"brightYellow": "#fbbf24",
|
|
20
|
+
"brightBlue": "#cdd0d6",
|
|
21
|
+
"brightMagenta": "#e3e4e8",
|
|
22
|
+
"brightCyan": "#d6dae0",
|
|
23
|
+
"brightWhite": "#ffffff"
|
|
24
|
+
}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* templates/distribution/construct-brand.typ — Construct distribution brand system.
|
|
3
|
+
*
|
|
4
|
+
* Single source of typographic, spacing, and color tokens for every bundled PDF
|
|
5
|
+
* layout (prd, research, decision, fallback). The system is monochrome: black,
|
|
6
|
+
* white, and a grey ink ramp carry all document chrome and accents, so printed
|
|
7
|
+
* artifacts read as one consistent family regardless of type. Color belongs to
|
|
8
|
+
* diagrams, not to the page furniture.
|
|
9
|
+
*
|
|
10
|
+
* Space Grotesk is referenced by explicit numeric weight (400/500/600/700) and ships
|
|
11
|
+
* as a single weight-axis variable TTF registered under the family name "Space
|
|
12
|
+
* Grotesk"; named weights like "Space Grotesk SemiBold" must never be used — they
|
|
13
|
+
* fall back to a serif. Document-wide #set/#show rules sit at module scope so the Pandoc $body$
|
|
14
|
+
* inherits them; exported helpers keep stable signatures for the layout templates.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Monochrome ink ramp. `ink` is the single strong accent (rules, ticks, header
|
|
18
|
+
// underlines, emphasis); the greys below it carry hierarchy; hairlines and
|
|
19
|
+
// surfaces stay near-white so panels read as paper, not color.
|
|
20
|
+
|
|
21
|
+
#let ink = rgb("#0a0c10")
|
|
22
|
+
#let ink-strong = rgb("#16191f")
|
|
23
|
+
#let ink-body = rgb("#23272e")
|
|
24
|
+
#let ink-muted = rgb("#565c66")
|
|
25
|
+
#let ink-faint = rgb("#9499a2")
|
|
26
|
+
#let hairline = rgb("#e3e4e8")
|
|
27
|
+
#let hairline-strong = rgb("#cdd0d6")
|
|
28
|
+
#let surface = rgb("#fafafa")
|
|
29
|
+
#let surface-alt = rgb("#f3f4f6")
|
|
30
|
+
#let paper = rgb("#ffffff")
|
|
31
|
+
|
|
32
|
+
// Back-compatible token names kept for any external reference, remapped onto the
|
|
33
|
+
// monochrome ramp so legacy callers inherit the new palette automatically.
|
|
34
|
+
|
|
35
|
+
#let brand-accent = ink
|
|
36
|
+
#let brand-accent-deep = ink
|
|
37
|
+
#let brand-violet-soft = ink-muted
|
|
38
|
+
#let brand-warm = ink
|
|
39
|
+
#let brand-warm-deep = ink-strong
|
|
40
|
+
#let brand-info = ink-muted
|
|
41
|
+
#let brand-success = ink
|
|
42
|
+
#let brand-navy = rgb("#0c1018")
|
|
43
|
+
#let brand-ink = ink-strong
|
|
44
|
+
#let brand-ink-soft = ink-body
|
|
45
|
+
#let brand-muted = ink-muted
|
|
46
|
+
#let brand-faint = ink-faint
|
|
47
|
+
#let brand-surface = surface
|
|
48
|
+
#let brand-surface-alt = surface-alt
|
|
49
|
+
#let brand-surface-warm = surface
|
|
50
|
+
#let brand-surface-info = surface-alt
|
|
51
|
+
#let brand-hairline = hairline
|
|
52
|
+
#let brand-rule = hairline-strong
|
|
53
|
+
|
|
54
|
+
#let construct-font-sans = ("Space Grotesk",)
|
|
55
|
+
#let construct-font-display = ("Space Grotesk",)
|
|
56
|
+
#let construct-font-mono = ("JetBrains Mono",)
|
|
57
|
+
|
|
58
|
+
// Type scale. One modular ramp shared by every document type so sizing never
|
|
59
|
+
// drifts between a PRD, a research brief, and a decision record.
|
|
60
|
+
|
|
61
|
+
#let fs-micro = 8pt
|
|
62
|
+
#let fs-small = 8.5pt
|
|
63
|
+
#let fs-meta = 9pt
|
|
64
|
+
#let fs-body = 10pt
|
|
65
|
+
#let fs-h4 = 8.5pt
|
|
66
|
+
#let fs-h3 = 11pt
|
|
67
|
+
#let fs-h2 = 13pt
|
|
68
|
+
#let fs-h1 = 17pt
|
|
69
|
+
#let fs-subtitle = 11.5pt
|
|
70
|
+
#let fs-title = 24pt
|
|
71
|
+
|
|
72
|
+
// Weight scale. Numeric only; these resolve to the bundled Space Grotesk variable face.
|
|
73
|
+
|
|
74
|
+
#let wt-regular = 400
|
|
75
|
+
#let wt-medium = 500
|
|
76
|
+
#let wt-semibold = 600
|
|
77
|
+
#let wt-bold = 700
|
|
78
|
+
|
|
79
|
+
#let construct-figure-max-width = 58%
|
|
80
|
+
#let construct-figure-max-height = 2.35in
|
|
81
|
+
|
|
82
|
+
// A document-type word maps to a compact badge so the masthead reads as a
|
|
83
|
+
// labelled artifact rather than a bare title.
|
|
84
|
+
|
|
85
|
+
#let construct-badge-label(artifact-type) = {
|
|
86
|
+
if artifact-type == "" [Document]
|
|
87
|
+
else {
|
|
88
|
+
let lower = lower(artifact-type)
|
|
89
|
+
if lower.contains("prd") [PRD]
|
|
90
|
+
else if lower.contains("research") or lower.contains("brief") or lower.contains("finding") [Research]
|
|
91
|
+
else if lower.contains("adr") [ADR]
|
|
92
|
+
else if lower.contains("rfc") [RFC]
|
|
93
|
+
else if lower.contains("strategy") [Strategy]
|
|
94
|
+
else if lower.contains("runbook") [Runbook]
|
|
95
|
+
else [#upper(artifact-type)]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Status pill, monochrome. Settled states (approved/shipped) read as a solid
|
|
100
|
+
// black chip; active states as a black outline; everything else as a faint
|
|
101
|
+
// outline. Color is never used to encode status.
|
|
102
|
+
|
|
103
|
+
#let construct-status-pill(status) = {
|
|
104
|
+
let s = lower(status).trim()
|
|
105
|
+
let settled = s == "approved" or s == "shipped"
|
|
106
|
+
let active = s.contains("review") or s.contains("progress")
|
|
107
|
+
let fill-c = if settled { ink } else { paper }
|
|
108
|
+
let stroke-c = if settled { ink } else if active { ink } else { hairline-strong }
|
|
109
|
+
let text-c = if settled { paper } else if active { ink } else { ink-muted }
|
|
110
|
+
box(
|
|
111
|
+
fill: fill-c,
|
|
112
|
+
inset: (x: 6pt, y: 2.5pt),
|
|
113
|
+
radius: 3pt,
|
|
114
|
+
stroke: 0.6pt + stroke-c,
|
|
115
|
+
baseline: 0.16em,
|
|
116
|
+
)[
|
|
117
|
+
#text(font: construct-font-sans, size: 7pt, weight: wt-bold, fill: text-c, tracking: 0.05em)[#upper(status)]
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Kicker chips above the title: artifact badge, doc id, version, classification,
|
|
122
|
+
// separated by a faint dot.
|
|
123
|
+
|
|
124
|
+
#let construct-meta-chips(
|
|
125
|
+
artifact-type,
|
|
126
|
+
doc-id: "",
|
|
127
|
+
version: "",
|
|
128
|
+
classification: "",
|
|
129
|
+
) = {
|
|
130
|
+
let chips = ()
|
|
131
|
+
if artifact-type != "" { chips.push(construct-badge-label(artifact-type)) }
|
|
132
|
+
if doc-id != "" { chips.push(doc-id) }
|
|
133
|
+
if version != "" { chips.push("v" + version) }
|
|
134
|
+
if classification != "" { chips.push(upper(classification)) }
|
|
135
|
+
if chips.len() == 0 { none }
|
|
136
|
+
else {
|
|
137
|
+
set text(font: construct-font-sans, size: fs-micro, weight: wt-bold, fill: ink, tracking: 0.08em)
|
|
138
|
+
chips.join([#text(fill: ink-faint, weight: wt-regular)[ #sym.dot.c ]])
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Byline pairs the status pill with owner and date in muted text.
|
|
143
|
+
|
|
144
|
+
#let construct-editorial-line(status, owner, date) = {
|
|
145
|
+
let parts = ()
|
|
146
|
+
if owner != "" { parts.push(owner) }
|
|
147
|
+
if date != "" { parts.push(date) }
|
|
148
|
+
let tail = if parts.len() == 0 { none } else {
|
|
149
|
+
set text(font: construct-font-sans, size: fs-small, fill: ink-muted)
|
|
150
|
+
parts.join([#text(fill: ink-faint)[ #sym.dot.c ]])
|
|
151
|
+
}
|
|
152
|
+
if status == "" and tail == none { none }
|
|
153
|
+
else {
|
|
154
|
+
box[
|
|
155
|
+
#if status != "" [#construct-status-pill(status)#h(7pt)]
|
|
156
|
+
#if tail != none [#tail]
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Masthead: a cover-grade title block. Kicker chips, large display title, an
|
|
162
|
+
// optional subtitle, a byline, and a full hairline pinned by a short black tick
|
|
163
|
+
// at the left margin.
|
|
164
|
+
|
|
165
|
+
#let construct-masthead(
|
|
166
|
+
title,
|
|
167
|
+
subtitle,
|
|
168
|
+
status,
|
|
169
|
+
owner,
|
|
170
|
+
date,
|
|
171
|
+
artifact-type,
|
|
172
|
+
version: "",
|
|
173
|
+
doc-id: "",
|
|
174
|
+
classification: "",
|
|
175
|
+
) = {
|
|
176
|
+
let chips = construct-meta-chips(
|
|
177
|
+
artifact-type,
|
|
178
|
+
doc-id: doc-id,
|
|
179
|
+
version: version,
|
|
180
|
+
classification: classification,
|
|
181
|
+
)
|
|
182
|
+
if chips != none {
|
|
183
|
+
chips
|
|
184
|
+
v(0.62em)
|
|
185
|
+
}
|
|
186
|
+
text(font: construct-font-display, size: fs-title, weight: wt-semibold, fill: ink, tracking: -0.02em)[#title]
|
|
187
|
+
if subtitle != "" {
|
|
188
|
+
v(0.45em)
|
|
189
|
+
text(font: construct-font-sans, size: fs-subtitle, fill: ink-muted, tracking: -0.005em)[#subtitle]
|
|
190
|
+
}
|
|
191
|
+
let byline = construct-editorial-line(status, owner, date)
|
|
192
|
+
if byline != none {
|
|
193
|
+
v(0.78em)
|
|
194
|
+
byline
|
|
195
|
+
}
|
|
196
|
+
v(0.95em)
|
|
197
|
+
block(width: 100%, height: 2pt, {
|
|
198
|
+
place(left + horizon, line(length: 100%, stroke: 0.5pt + hairline))
|
|
199
|
+
place(left + horizon, line(length: 46pt, stroke: 2pt + ink))
|
|
200
|
+
})
|
|
201
|
+
v(1.05em)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Shared callout shell: a grey panel with a black left bar and a small bold
|
|
205
|
+
// uppercase label. Variants differ only by label and tint, never by color, so
|
|
206
|
+
// the differentiator is the word, not a hue.
|
|
207
|
+
|
|
208
|
+
#let construct-callout(label, body, tint: surface, bar: 2.5pt) = {
|
|
209
|
+
block(
|
|
210
|
+
fill: tint,
|
|
211
|
+
stroke: (left: bar + ink, rest: none),
|
|
212
|
+
inset: (left: 14pt, top: 11pt, bottom: 11pt, right: 13pt),
|
|
213
|
+
width: 100%,
|
|
214
|
+
radius: (right: 3pt),
|
|
215
|
+
)[
|
|
216
|
+
#set par(leading: 0.66em, spacing: 0.72em)
|
|
217
|
+
#set text(font: construct-font-sans, size: fs-body, fill: ink-body)
|
|
218
|
+
#text(size: 7.5pt, weight: wt-bold, fill: ink, tracking: 0.09em)[#upper(label)]
|
|
219
|
+
#v(0.42em)
|
|
220
|
+
#body
|
|
221
|
+
]
|
|
222
|
+
v(0.85em)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#let construct-at-a-glance(body) = construct-callout("At a glance", body, tint: surface-alt)
|
|
226
|
+
|
|
227
|
+
#let construct-note(body) = construct-callout("Note", body, tint: surface)
|
|
228
|
+
|
|
229
|
+
#let construct-decision-callout(body) = construct-callout("Decision", body, tint: surface-alt, bar: 3.5pt)
|
|
230
|
+
|
|
231
|
+
// Key metrics reads as a measured card: clean surface, a top black rule, a bold
|
|
232
|
+
// label, then the metrics table.
|
|
233
|
+
|
|
234
|
+
#let construct-key-metrics(body) = {
|
|
235
|
+
block(
|
|
236
|
+
fill: surface,
|
|
237
|
+
stroke: (top: 1.5pt + ink, rest: 0.5pt + hairline),
|
|
238
|
+
inset: 13pt,
|
|
239
|
+
width: 100%,
|
|
240
|
+
radius: (bottom: 3pt),
|
|
241
|
+
)[
|
|
242
|
+
#text(font: construct-font-sans, size: 7.5pt, weight: wt-bold, fill: ink, tracking: 0.09em)[KEY METRICS]
|
|
243
|
+
#v(0.48em)
|
|
244
|
+
#body
|
|
245
|
+
]
|
|
246
|
+
v(0.85em)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Running header appears from page two: short title at left, doc id and page at
|
|
250
|
+
// right, closed by a hairline.
|
|
251
|
+
|
|
252
|
+
#let construct-running-header(title, doc-id: "", version: "") = context {
|
|
253
|
+
if counter(page).get().first() > 1 [
|
|
254
|
+
#set text(font: construct-font-sans, size: fs-micro, fill: ink-muted)
|
|
255
|
+
#let short-title = if title.len() > 52 { title.slice(0, 49) + "…" } else { title }
|
|
256
|
+
#text(fill: ink-muted)[#short-title]
|
|
257
|
+
#h(1fr)
|
|
258
|
+
#if doc-id != "" [
|
|
259
|
+
#text(fill: ink, weight: wt-medium)[#doc-id]
|
|
260
|
+
#text(fill: ink-faint)[ #sym.dot.c ]
|
|
261
|
+
]
|
|
262
|
+
#if version != "" [
|
|
263
|
+
#text(fill: ink-faint)[v#version]
|
|
264
|
+
#text(fill: ink-faint)[ #sym.dot.c ]
|
|
265
|
+
]
|
|
266
|
+
#text(fill: ink-muted)[#counter(page).display()]
|
|
267
|
+
#v(0.3em)
|
|
268
|
+
#line(length: 100%, stroke: 0.4pt + hairline)
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#let construct-running-footer(footer-label, classification: "") = context {
|
|
273
|
+
if counter(page).get().first() > 1 [
|
|
274
|
+
#v(0.3em)
|
|
275
|
+
#line(length: 100%, stroke: 0.4pt + hairline)
|
|
276
|
+
#v(0.28em)
|
|
277
|
+
#set text(font: construct-font-sans, size: fs-micro, fill: ink-faint)
|
|
278
|
+
#align(center)[
|
|
279
|
+
#text(weight: wt-bold, fill: ink, tracking: 0.04em)[Construct]
|
|
280
|
+
#text[ #sym.dot.c #footer-label]
|
|
281
|
+
#if classification != "" [ #sym.dot.c #upper(classification)]
|
|
282
|
+
]
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// The theme function carries every body-level #set/#show rule. Module-scope set
|
|
287
|
+
// rules do NOT propagate through `#import`, so each layout template applies this
|
|
288
|
+
// with `#show: construct-theme` to wrap the Pandoc $body$ in the styled scope.
|
|
289
|
+
// Space Grotesk is referenced by numeric weight throughout to avoid serif fallback.
|
|
290
|
+
|
|
291
|
+
#let construct-theme(body) = {
|
|
292
|
+
set text(font: construct-font-sans, size: fs-body, fill: ink-body, lang: "en", tracking: 0.002em)
|
|
293
|
+
set par(justify: false, leading: 0.82em, spacing: 1.15em)
|
|
294
|
+
set heading(numbering: none, outlined: true)
|
|
295
|
+
|
|
296
|
+
show strong: set text(font: construct-font-sans, weight: wt-semibold, fill: ink)
|
|
297
|
+
show emph: set text(style: "italic")
|
|
298
|
+
|
|
299
|
+
// Lists inherit the open body leading; a per-item block gap keeps a wrapped
|
|
300
|
+
// bullet from reading as two bullets, with the gap clearly wider than the
|
|
301
|
+
// line leading within an item.
|
|
302
|
+
|
|
303
|
+
show list.item: it => block(below: 0.6em, it)
|
|
304
|
+
show enum.item: it => block(below: 0.6em, it)
|
|
305
|
+
set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 2pt, body-indent: 8pt, spacing: 0.9em)
|
|
306
|
+
set enum(indent: 2pt, body-indent: 8pt, spacing: 0.9em)
|
|
307
|
+
|
|
308
|
+
// Links carry ink with a hairline underline so citations stay legible without
|
|
309
|
+
// introducing color.
|
|
310
|
+
|
|
311
|
+
show link: it => {
|
|
312
|
+
set text(fill: ink)
|
|
313
|
+
underline(offset: 1.5pt, stroke: 0.5pt + hairline-strong, it)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Headings. Level 2 is the working section header (the H1 title is rendered by
|
|
317
|
+
// the masthead and stripped from the body), so it carries a short black rule.
|
|
318
|
+
|
|
319
|
+
show heading.where(level: 1): it => {
|
|
320
|
+
v(1.2em, weak: true)
|
|
321
|
+
block(sticky: true, {
|
|
322
|
+
text(font: construct-font-display, size: fs-h1, weight: wt-semibold, fill: ink, tracking: -0.015em)[#it.body]
|
|
323
|
+
v(0.22em)
|
|
324
|
+
line(length: 100%, stroke: 0.5pt + hairline)
|
|
325
|
+
v(0.55em, weak: true)
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
show heading.where(level: 2): it => {
|
|
329
|
+
v(1.65em, weak: true)
|
|
330
|
+
block(sticky: true, {
|
|
331
|
+
text(font: construct-font-display, size: fs-h2, weight: wt-semibold, fill: ink, tracking: -0.01em)[#it.body]
|
|
332
|
+
v(0.4em)
|
|
333
|
+
line(length: 26pt, stroke: 1.5pt + ink)
|
|
334
|
+
v(0.62em, weak: true)
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
show heading.where(level: 3): it => {
|
|
338
|
+
v(1.15em, weak: true)
|
|
339
|
+
block(sticky: true, {
|
|
340
|
+
text(font: construct-font-sans, size: fs-h3, weight: wt-semibold, fill: ink-strong)[#it.body]
|
|
341
|
+
v(0.38em, weak: true)
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
show heading.where(level: 4): it => {
|
|
345
|
+
v(0.75em, weak: true)
|
|
346
|
+
block(sticky: true, {
|
|
347
|
+
text(font: construct-font-sans, size: fs-h4, weight: wt-bold, fill: ink-muted, tracking: 0.07em)[#upper(it.body)]
|
|
348
|
+
v(0.22em, weak: true)
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Inline code sits in a faint grey chip; block code in a bordered panel.
|
|
353
|
+
|
|
354
|
+
show raw.where(block: false): it => box(
|
|
355
|
+
fill: surface-alt,
|
|
356
|
+
inset: (x: 3.5pt, y: 0pt),
|
|
357
|
+
outset: (y: 2.5pt),
|
|
358
|
+
radius: 2.5pt,
|
|
359
|
+
)[#text(font: construct-font-mono, size: 8.8pt, fill: ink-strong)[#it]]
|
|
360
|
+
|
|
361
|
+
show raw.where(block: true): it => block(
|
|
362
|
+
fill: surface,
|
|
363
|
+
stroke: 0.5pt + hairline,
|
|
364
|
+
inset: 11pt,
|
|
365
|
+
radius: 4pt,
|
|
366
|
+
width: 100%,
|
|
367
|
+
)[#text(font: construct-font-mono, size: fs-meta, fill: ink-body)[#it]]
|
|
368
|
+
|
|
369
|
+
// Tables use a horizontal-only rule system: a strong black line under the bold
|
|
370
|
+
// header, light hairlines between rows, no vertical chrome. The first column
|
|
371
|
+
// carries semibold weight for scanning.
|
|
372
|
+
|
|
373
|
+
set table(
|
|
374
|
+
stroke: (x, y) => (
|
|
375
|
+
bottom: if y == 0 { 1pt + ink } else { 0.5pt + hairline },
|
|
376
|
+
top: none, left: none, right: none,
|
|
377
|
+
),
|
|
378
|
+
inset: (x: 9pt, y: 7.5pt),
|
|
379
|
+
fill: none,
|
|
380
|
+
)
|
|
381
|
+
show table.cell: it => {
|
|
382
|
+
if it.y == 0 {
|
|
383
|
+
set text(font: construct-font-sans, weight: wt-bold, fill: ink, size: fs-small)
|
|
384
|
+
it
|
|
385
|
+
} else {
|
|
386
|
+
set text(
|
|
387
|
+
font: construct-font-sans,
|
|
388
|
+
size: fs-meta,
|
|
389
|
+
fill: if it.x == 0 { ink-strong } else { ink-body },
|
|
390
|
+
weight: if it.x == 0 { wt-semibold } else { wt-regular },
|
|
391
|
+
)
|
|
392
|
+
it
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Pandoc wraps markdown tables in a centered figure; lift the table out and
|
|
397
|
+
// left-align it so it reads as a clean data block aligned to the text column.
|
|
398
|
+
|
|
399
|
+
show figure.where(kind: table): it => {
|
|
400
|
+
if it.body.has("body") { align(left, it.body.body) } else { align(left, it.body) }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Figures: a light bordered frame, a constrained width, and a caption that
|
|
404
|
+
// leads with a bold figure number.
|
|
405
|
+
|
|
406
|
+
set figure(supplement: "Figure")
|
|
407
|
+
show figure.where(kind: image): it => {
|
|
408
|
+
v(0.95em, weak: true)
|
|
409
|
+
block(
|
|
410
|
+
stroke: 0.5pt + hairline,
|
|
411
|
+
fill: white,
|
|
412
|
+
inset: 9pt,
|
|
413
|
+
width: 100%,
|
|
414
|
+
radius: 4pt,
|
|
415
|
+
)[
|
|
416
|
+
#align(center)[
|
|
417
|
+
#box(width: construct-figure-max-width)[
|
|
418
|
+
#show image: img => image(img, width: 100%, height: construct-figure-max-height, fit: "contain")
|
|
419
|
+
#it.body
|
|
420
|
+
]
|
|
421
|
+
]
|
|
422
|
+
]
|
|
423
|
+
if it.caption != none {
|
|
424
|
+
v(0.35em)
|
|
425
|
+
align(center)[
|
|
426
|
+
#text(font: construct-font-sans, size: fs-micro, weight: wt-bold, fill: ink, tracking: 0.03em)[#it.supplement #it.counter.display()]
|
|
427
|
+
#text(font: construct-font-sans, size: fs-micro, fill: ink-muted)[ — #it.caption.body]
|
|
428
|
+
]
|
|
429
|
+
}
|
|
430
|
+
v(0.95em, weak: true)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Blockquotes route to callouts: a quote carrying a Metric/Baseline table
|
|
434
|
+
// becomes the key-metrics card, anything else becomes the at-a-glance aside.
|
|
435
|
+
|
|
436
|
+
show quote: it => {
|
|
437
|
+
let r = repr(it.body)
|
|
438
|
+
if r.contains("Metric") and r.contains("Baseline") {
|
|
439
|
+
construct-key-metrics(it.body)
|
|
440
|
+
} else {
|
|
441
|
+
construct-at-a-glance(it.body)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
body
|
|
446
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* templates/distribution/construct-decision.typ — Construct distribution PDF.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#import "construct-brand.typ": *
|
|
6
|
+
|
|
7
|
+
#show: construct-theme
|
|
8
|
+
|
|
9
|
+
#set page(
|
|
10
|
+
paper: "a4",
|
|
11
|
+
margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
|
|
12
|
+
numbering: "1",
|
|
13
|
+
header: construct-running-header(
|
|
14
|
+
"$if(title)$$title$$endif$",
|
|
15
|
+
doc-id: "$if(docId)$$docId$$endif$",
|
|
16
|
+
version: "$if(version)$$version$$endif$",
|
|
17
|
+
),
|
|
18
|
+
footer: construct-running-footer(
|
|
19
|
+
"Decision record",
|
|
20
|
+
classification: "$if(classification)$$classification$$endif$",
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
$if(title)$
|
|
25
|
+
#construct-masthead(
|
|
26
|
+
"$title$",
|
|
27
|
+
"$if(subtitle)$$subtitle$$endif$",
|
|
28
|
+
"$if(status)$$status$$endif$",
|
|
29
|
+
"$if(owner)$$owner$$endif$",
|
|
30
|
+
"$if(date)$$date$$endif$",
|
|
31
|
+
"$if(artifactType)$$artifactType$$endif$",
|
|
32
|
+
version: "$if(version)$$version$$endif$",
|
|
33
|
+
doc-id: "$if(docId)$$docId$$endif$",
|
|
34
|
+
classification: "$if(classification)$$classification$$endif$",
|
|
35
|
+
)
|
|
36
|
+
$endif$
|
|
37
|
+
|
|
38
|
+
$body$
|