diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml new file mode 100644 index 0000000..e019c9d --- /dev/null +++ b/.github/workflows/render.yml @@ -0,0 +1,52 @@ +name: Deterministic Engineering Render +on: + pull_request: + paths: + - 'packages/render/**' + - 'examples/render/**' + - 'docs/90-99-views/90-architecture-views/**' + - '.github/workflows/render.yml' + push: + branches: [main] + paths: + - 'packages/render/**' + - 'examples/render/**' + - 'docs/90-99-views/90-architecture-views/**' + - '.github/workflows/render.yml' +permissions: + contents: read +jobs: + renderer: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 + with: + bun-version: 1.3.11 + - run: bun install --frozen-lockfile + working-directory: packages/render + - run: bunx playwright install --with-deps chromium + working-directory: packages/render + - run: bun run typecheck + working-directory: packages/render + - run: bun test + working-directory: packages/render + - run: bun run test:export + working-directory: packages/render + - run: bun install --frozen-lockfile + working-directory: examples/render + - run: bun run typecheck + working-directory: examples/render + - run: bun test + working-directory: examples/render + - run: bun run case + working-directory: examples/render + - run: bun run compare + working-directory: examples/render + - run: bun run native + working-directory: examples/render diff --git a/.gitignore b/.gitignore index f390869..0d94c65 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ # Python transport caches are local artifacts. __pycache__/ *.pyc + +# TypeScript packages and local rendering outputs. +node_modules/ +.cache/ diff --git a/README.org b/README.org index a39c3ce..cc49c4b 100644 --- a/README.org +++ b/README.org @@ -513,3 +513,8 @@ example evidence and decisions are hypothetical. The current See the [[file:docs/10-19-design/10-architecture/10.03-upstream-capabilities.org::#inspection-scope][inspection scope]] for pinned sources and limitations. Their capabilities do not automatically qualify the system proposed here. + +* Deterministic Engineering Render +[[file:packages/render/README.org][Functional Deterministic Engineering rendering Library]] composes official D2 compilation and SVG rendering with +independent upstream/default or downstream themes and an optional official +OpenSysML SDK projection. [[file:docs/10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org][Design and ownership]]; [[file:examples/render/library-case.ts][dependency-only consumer example]]. diff --git a/docs/00-09-project/00-documentation/00.01-index.org b/docs/00-09-project/00-documentation/00.01-index.org index cf6379e..ee17c43 100644 --- a/docs/00-09-project/00-documentation/00.01-index.org +++ b/docs/00-09-project/00-documentation/00.01-index.org @@ -344,3 +344,10 @@ source projection, behavioral checks and branch custody. - [[file:../../10-19-design/10-architecture/10.138-native-package-cohort-and-ci-provenance.org][10.138 Native package cohort and CI provenance]] - [[file:../../10-19-design/10-architecture/10.139-native-cohort-source-acquisition.org][10.139 Native cohort and source acquisition]] + +* Shared functional architecture rendering +- [[file:../../10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org][10.140 Functional renderer, themes and OpenSysML SDK]] + +- [[file:../../10-19-design/10-architecture/10.141-cross-language-diagram-renderer-research.org][10.141 Cross-language renderer research and layout correction]]. + +- [[file:../../10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org][10.142 Declarative static render extensibility audit]] — ownership, library reuse and cross-platform qualification. diff --git a/docs/10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org b/docs/10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org new file mode 100644 index 0000000..11658eb --- /dev/null +++ b/docs/10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org @@ -0,0 +1,206 @@ +#+TITLE: 10.140 Question-driven D2 Rendering and OpenSysML Model Views +#+LANGUAGE: en +#+PROPERTY: JD_ID 10.140 +#+OPTIONS: toc:2 num:nil + +* Ownership and decision +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-owner +:END: +The shared functional TypeScript rendering Library belongs to Deterministic +Engineering. Gateway/Weaver consumes it and owns project acquisition, local +presentation, review and publication. This work starts from main253a926 in a new +branch; the original dirty workspace and active downstream checkout are preserved. +The donor was Weaver's uncommitted dev/d2 working snapshot in a checkout at +92f8848640ff53a360430178ba7adc0894793d83, not content published by that commit. + +D2 remains the language and rendering engine. Custom React scene reconstruction, +manual ELK packing and text-outline machinery have been removed. Official D2 +compile/render owns layout and SVG. C4 and Structurizr are +classification and communication references, not additional DSLs or dependencies. +The Gerbil package, compiler and SDK are unchanged. The sole package version +source is packages/render/package.json; namespaces stay stable. + +* Questions, model and evidence associations +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-contracts +:END: +Every diagram answers an explicit question for an audience and scope. A useful +association is Question -> model/obligation selection -> admitted query/evidence +references -> view candidate -> review/publication decision. LLMs propose +questions, model/view candidates and explanations. OpenSysML owns model operations; +ASCENT owns deterministic queries; Core/Scenario own capture/currentness; Aitia +owns review. The renderer cannot issue evidence or authorize publication. + +| Required view field | Meaning | Boundary | +|---------------------+---------+----------| +| question.ref / text | Existing question identity and wording | Included in output and fingerprint | +| basis.captureRef | Existing capture or explicitly labeled design candidate | Does not create or authenticate C | +| basis.modelRefs | Selected model/source references | Does not establish model validation | +| basis.obligationRefs | Existing obligations | No second duty ledger | +| basis.evidenceRefs | Existing evidence references; may be empty | Empty references cannot become a pass | +| source/title/purpose/kind | Explicit view selection | Architecture/workflow kept separate | +| sources/theme | Captured import texts and official D2 options | Full supplied input footprint | + +These TS reference fields project existing contracts. FormationProfile/Scenario +in scenarios/project-formation already own intent, model root, catalog/mapping, +policy and repository capture. Their capture may be blocked. This package does +not invent a native Question Contract or pretend that a rendering snapshot issues +C. Reference resolution, accepted Q, source trust and freshness remain caller +responsibilities. Missing obligations, conflicts and counterevidence must survive +selection; selecting a flattering image cannot discharge an obligation. + +Architecture answers ownership, components, interfaces, dependencies or deployment. +Workflow answers ordering, branches, events and recovery. Explanatory overviews +can refer to both as distinct titled sections. Dependency edges must not silently +become execution order. Architecture kinds are system/data/assurance; workflow +kinds are routing/flow/sequence/state. Kinds classify intended views; native D2 +notation supplies their actual semantics. Sequence diagrams use sequence_diagram, +including its native actor lifelines. No TypeScript card layout replaces it. + +* Functional files and call boundaries +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-functions +:END: +| File under packages/render | Responsibility | +|-------------------------------------------+----------------| +| src/source/types.ts | Readonly QuestionRef, ViewBasis and distinct purpose variants | +| src/source/catalog.ts | Validate paths/selection and freeze supplied snapshot | +| src/pipeline.ts | renderView composes explicit compile/SVG effects; retains native IDs | +| src/worker.ts | Official D2 compile/render effects | +| src/index.ts | Public API; at most two workers, always terminated; failure rejects result | +| src/theme.ts and themes/ | Independent native options and optional D2 profiles | +| src/opensysml.ts | Real SDK call and pure explicit reflow projection | +| examples/render | Actual file-package consumer; project reads and candidate writes | + +The Library has no directory walker, global scene engine, semantic label guessing, +custom compiler, FP runtime, evidence registry or publication database. Snapshots +are immutable; ordinary functions, readonly records, unions and Promise suffice. +Bun is the qualified runtime. No Node-only compatibility is claimed. + +* Independent themes +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-themes +:END: +D2 profiles are optional defaults exposed by standardThemes(). Downstream can +supply its own import closure, classes, styles, icons and layout. The ./theme +export exposes immutable official D2 options; extendTheme composes overrides. +Classes remain open strings rather than a closed semantic-role palette. Theme +changes invalidate render reuse, not model identity or proof meaning. +SVG is primary. The default PNG conversion was removed after real cases exposed font substitution +and text overflow. Raster export requires separately qualified browser rendering. External fonts/images require separately qualified +resource closure for hermetic publication. A digest is not authentication. + +* Actual OpenSysML SDK relationship +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-opensysml +:END: +The optional peer/dev dependency is the published @openmbee/opensysml0.11.0. +Research was pinned at d7e94f69bbb4324e4829afad1a40f67ad5b006d4; only fields +present in the installed package are used. +[[https://github.com/Open-MBEE/OpenSysML/tree/d7e94f69bbb4324e4829afad1a40f67ad5b006d4/client/node][Official TypeScript SDK]]. + +captureOpenSysMLView invokes Connection.renderView(modelHash, viewName, options). +projectOpenSysMLView explicitly reflows supported nodes/edges to D2 and retains +model/view identity, original IDs, ownership, origins, full SDK response and its +fingerprint. Ports, compartments, tables/notes, stand-ins, collapsed nodes, +dangling edges and ownership cycles are refused rather than silently dropped. +SDK errors propagate. There is no local SysML parser or alternative transport. +Tests use the actual public SDK JSON decoder and real D2 rendering. They do not +establish live service/WASM parse or model validation. A supplied modelHash is +only a reference until existing project source checks authenticate its origin. + +* Current repository as the real Case +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-cases +:END: +The cases command renders three original D2 files unchanged, with native ELK, +theme0 and pad24 matching their documented convention. Receipts retain original +source hashes, native element IDs and question/model/obligation links. + +| Original view | Review question | Content status | +|---------------+-----------------+----------------| +| 90.91 engineering-content-contracts | Who publishes, selects and captures architectural/document/decision content? | Existing design; rendering only | +| 90.120 ascent-domain-owned-module-topology | Which domain owns each module and what remains transitional? | Existing target; not a fresh implementation audit | +| 90.84 sysml-interoperability | How do observations reach reasoning and review without granting approval? | Existing design; no live model execution | + +Existing diagrams may mix architectural and workflow concerns. First-pass +rendering preserves their source so a content problem is distinguishable from a +renderer regression. A question-led redesign is a separate reviewed content change. +The new 90.129 component architecture and 90.130 authoring workflow are separate +outputs. Their capture references explicitly name design candidates and evidence +references remain empty; they do not impersonate C, accepted Q or review R. + +* Verification, migration and open gates +:PROPERTIES: +:CUSTOM_ID: architecture-rendering-migration +:END: +Typecheck and tests cover immutable footprints, changed-question rejection, +source/import refusal, nested D2, sequence notation, independent themes, SDK +projection/refusal and worker completion. A real installed file dependency and +an independent packed consumer exercise package boundaries. CI runs package tests +and examples on Linux and macOS. Local and remote receipts must remain distinct. + +Correctness means native D2 semantics, preserved references and refusal of lossy +SDK projection. Whether question-led LLM selection improves explanatory quality +is a research hypothesis requiring human comparison, not a rendering pass claim. +Open gates include live model validation; trusted C/accepted Q integration; +question-to-element evidence coverage; authorized publication; external asset +closure; downstream Gateway migration; and CI acceptance for this revision. +No full automatic LLM orchestration, independent review or delivery is implemented. + +* References +- [[https://d2lang.com/tour/themes][Native D2 themes]] and [[https://d2lang.com/tour/sequence-diagrams][sequence diagrams]]. +- [[https://c4model.com/diagrams][C4 scopes]] and [[https://c4model.com/diagrams/notation][notation guidance]], as references only. +- [[https://docs.structurizr.com/dsl][Structurizr model/view/style separation]], as a reference only. + +* TS visual composition is a first-class responsibility +The generic D2 adapter alone does not satisfy the visual framework requirement. +src/presentation.ts implements composePresentation: pure SVG composition of the +question, answer, native diagram, explanations, evidence context and limits. +The separate presentation-theme export owns typography, surfaces, status colors +and spacing. This is independent of themes/ and native D2 compile/render options. + +Three real cases produce two themes each: upstream blue/purple and downstream +green/warm. Native D2 source and diagram bytes remain identical. These demonstrate +TS-owned visual hierarchy rather than only returning engine SVG. Architecture +and workflow remain separate page purposes. Explanation status is project-authored; +a referenced supported label is not independently authenticated assurance. Current +examples use design or pending. Question-to-element highlighting, further page +compositions, multilingual shaping and verified evidence decoration remain open. +The first compositor is a concrete contract, not every possible explanatory genre. + +* Local qualification receipt +The final native D2 and TS composition suite passes19 cases, with strict typecheck +and actionlint clean. A packed independent consumer passes using only the public +package and presentation-theme exports. Three unchanged historical repository +sources render successfully; three two-theme explanatory pages and one focused +two-theme responsibility page are generated. Browser inspection confirms native +embedded fonts and complete responsive page composition. The saved screenshot +is a browser preview, not a separate runtime PNG export capability. + +Focused view90.131 is explicitly a concise content candidate derived from90.91 +and10.31. Its question is who publishes/selects/captures; its explanation associates +Library, Profile and Scenario responsibilities. It does not infer real evidence +or claim the live OpenSysML/ASCENT/Aitia chain has run. Remote CI is a distinct gate. + +* Icon-led infographic refinement +The initial page compositor was too document-like. The current composition uses +short labels, explicit node icons, visual grouping, icon-led responsibility tiles +and a compact model/obligation/evidence strip. Full references remain in SVG +metadata and sidecar receipts rather than occupying the main visual hierarchy. + +The ./icons export supplies official Lucide static SVG assets and an IconAsset +contract. Project input explicitly binds icons to nodes and explanations; no label +keyword inference assigns semantics. Downstream may replace any asset or the +presentation icon set. Icons are embedded as data URIs, not fetched remotely. +D2 still owns node geometry and connectors; TS owns the surrounding visual +explanation, typography, icon selection and independent presentation theme. +This compact composition replaces the initial prose-heavy report arrangement. + +The icon implementation uses [[https://github.com/lucide-icons/lucide/blob/main/docs/guide/installation.md][official Lucide static SVG exports]] and D2’s [[https://d2lang.com/tour/icons/][native icon support]]. SVG defaults are embedded with explicit bindings; no remote icon fetch is needed. + +* Layout research supersedes prototype acceptance +[[file:10.141-cross-language-diagram-renderer-research.org][10.141]] records the cross-language architecture/renderer survey. The current +compositor is a retained prototype, not the accepted visual architecture. D2 is a +primary authoring language; its ecosystem does not bound rendering research. diff --git a/docs/10-19-design/10-architecture/10.141-cross-language-diagram-renderer-research.org b/docs/10-19-design/10-architecture/10.141-cross-language-diagram-renderer-research.org new file mode 100644 index 0000000..6af2fc1 --- /dev/null +++ b/docs/10-19-design/10-architecture/10.141-cross-language-diagram-renderer-research.org @@ -0,0 +1,360 @@ +#+TITLE: 10.141 Cross-language Diagram Rendering Research and Layout Correction +#+LANGUAGE: en +#+PROPERTY: JD_ID 10.141 +#+OPTIONS: toc:2 num:nil + +* Scope correction +D2 is an authoring language and a primary project input. It does not delimit the +research ecosystem or prescribe every downstream visual rendering decision. +Architecture diagramming, graph visualization, constraint-based drawing, routing, +icon systems and AI-assisted diagram design must be studied across languages. +C4/Structurizr remain viewpoint references. No additional modeling DSL or proof +ledger is selected by this research. + +The c8b95c0 implementation in10.140 is a functional prototype, not an accepted +visual baseline. Its19 tests establish API/input properties, not layout quality. +Its TS compositor primarily surrounds finished D2 geometry. Adding icons or +shrinking margins cannot give that compositor control of graph-internal layout. +The current samples expose poor spatial balance, small labels after fitting, +overlarge fixed panels and repeated responsibility explanations. + +* Sources inspected through GitHub +The inspection used general architecture-diagram and diagram-renderer/layout +queries, not a D2-only query. Stars aided discovery only; they are not selection +criteria. Third-party skill text was read as source material, not installed or +executed as agent instructions. Project claims are not treated as local receipts. + +| Project | Actual inspected surface | Capability to learn | Selection status | +|---------+--------------------------+--------------------+------------------| +| [[https://github.com/antvis/G6][AntV G6]] | README; packages/g6/src/runtime/layout.ts | Separate graph data, layout stages, nodes/edges/combos, rendering and interactions | Candidate; no local runtime qualification | +| [[https://github.com/penrose/penrose][Penrose]] | README Domain/Substance/Style example; official constraint documentation | Separate domain meaning, instance and visual constraints; express containment/overlap/objectives | Research reference; no solver dependency selected | +| [[https://github.com/mingrammer/diagrams][Diagrams]] | README examples and provider structure | Icon-first node vocabulary, clusters and concise architectural labels | Visual reference; Python/Graphviz not automatically introduced | +| [[https://github.com/awslabs/diagram-as-code/tree/4ec965cf8bdb309d714bc6202e7954fc43338bbe][AWS Diagram-as-code]] | doc/best-practices.md; introduction image | Orientations by diagram purpose, nested resource boundaries and grouped redundancy | Visual/layout reference | +| [[https://github.com/likec4/likec4/tree/d1d61155215ceebd909cf217eab32ebeb6a35f1e][LikeC4]] | examples/cloud-system/views.c4; demo-rank-for-better-layout.c4 | Explicit include/exclude, scoped views and rank hints | Projection reference; its DSL is not a new dependency | +| [[https://github.com/lukilabs/beautiful-mermaid/tree/2ac8bbbb060ca0a65a6a21f3200bd99b1587b488][Beautiful Mermaid]] | README; src/theme.ts | Small composable color contract; theme independently applied to SVG primitives | TS theme reference; Mermaid is not a replacement input requirement | +| [[https://github.com/xyflow/xyflow][XYFlow]] | README package boundaries and usage | Custom nodes/edges and interactive inspection | Interactive candidate; editor does not alone solve offline layout | +| [[https://github.com/Agents365-ai/drawio-skill/tree/57270820d3cd71cbdf4fb6b2f7091d4fb37bb019][drawio-skill]] | README; scripts/diagram_ir.py; architecture-studio example inventory | Separate meaning/provenance/geometry, multiple projections and visual self-check | Research reference; no copied source/evidence system | +| [[https://github.com/Cocoon-AI/architecture-diagram-generator/tree/4b9087d55268c79a935105439dbcd37b630fc3f3][Architecture Diagram Generator]] | examples/web-app.html and web-app.png; template inventory | Study actual visual hierarchy and compact layer composition beyond one DSL ecosystem | Visual reference; fixed HTML template is not itself a reusable renderer | + +Penrose's [[https://penrose.ink/docs/tutorial/constraints/][constraint documentation]] describes optimization objectives; that is not a +proof that its solver suits architecture edge routing. G6's documented render +backends do not establish font, export or cold-start acceptance in our runtime. +XYFlow's UI extensibility must be evaluated independently of automatic placement. + +* Correct responsibility split +The intended Library has three independently testable concerns: +1. Question/model projection: use actual OpenSysML and D2 structures plus existing + project query/evidence associations to select what the reader needs to see. +2. Layout and visual grammar: group boundaries, choose topology, size nodes from + icons/labels, route edges, place callouts and keep useful whitespace. +3. Rendering/presentation: draw the placed view through reusable SVG/Canvas + capabilities with downstream themes, icon assets and output profiles. + +The existing D2 compiler remains a real source adapter and a native comparison +baseline. OpenSysML retains model semantics. ASCENT retains deterministic facts +and inference; Aitia retains review. A view projection can carry their original +IDs and references without creating a new modeling language, evidence database +or self-appointed authority. A private renderer may use a mature engine object; +public Library composition can remain functional and immutable. + +No engine is selected by README popularity. G6, existing layout engines and +constraint-oriented alternatives must be compared on actual repository content +and export/runtime constraints before adding a dependency. Pure static output +and interactive inspection are separate requirements and may need distinct +adapters rather than one mandatory browser application. + +* Same-case comparison protocol +Use repository90.91,90.120 and90.84 as frozen semantic baselines. Preserve their +original views for regression. Derive question-scoped architecture, workflow and +explanation candidates explicitly; record any omitted/aggregated element and +keep required obligations reachable. Do not improve density by deleting meaning. + +For each scope, compare: +- The existing native rendering baseline. +- The current TS compositor baseline, retaining its visible shortcomings. +- Candidate visual grammars backed by mature layout/render capabilities. + +Keep the same question, model references, required nodes/relations and evidence +status across a comparison. Theme and engine identity are independent recorded +inputs. A compact static figure, an explanatory infographic and an interactive +inspection view must not all be forced into the same tall report frame. + +* Acceptance and research questions +| Category | Acceptance / observation | +|----------+--------------------------| +| Semantic correctness | Preserve identity, containment, relation direction and required obligations; disclose aggregation and unsupported notation | +| Mechanical drawing checks | Detect clipped text, unintended overlaps, invalid geometry, invisible required content and routes through unrelated nodes | +| Typography | Compare actual output at its intended viewing size; enlarging the canvas cannot compensate for unreadably small labels | +| Routing | Compare crossings, shared paths, label collisions and unnecessarily long connectors; port semantics must survive | +| Space | Compare occupied bounds, group padding and aspect ratio; use these as diagnostics, not a universal maximum-density rule | +| Visual vocabulary | Icons encode actual kinds; shape, grouping, color and short labels have consistent meaning and an explicit legend where needed | +| Evidence | Missing, conflict and pending remain distinct; visual status cannot establish an accepted Q or review R | +| Reproduction | Record actual engine, theme/assets, source closure and layout options/seed; distinguish reproducibility from aesthetic preference | +| Human comparison | Ask whether each view answers its question faster and more accurately; do not label subjective quality as a formal proof | + +LLMs may propose view scope, constraints and alternative candidates, then inspect +rendered results for visual defects. Deterministic validation protects semantics +and mechanical requirements. Human comparison establishes whether an explanation +actually reduces reading burden. ASCENT is not an aesthetic oracle. + +* Implementation order +1. Preserve the current prototype, original sources and screenshots as baselines; + stop treating the fixed report layout as the target framework. +2. Write question/view fixtures with required identities and expected distinctions, + independent of the source language and the chosen renderer. +3. Qualify focused layout/render candidates for nested groups, icon-rich nodes, + edge routing, text metrics and static export. Compare complete figures. +4. Select the smallest reusable capabilities that pass those cases; replace the + prototype composition rather than stacking another wrapper over it. +5. Apply independent themes/icons and connect actual model/evidence references; + qualify downstream use and publish only reviewed outputs. + +Open decisions: renderer/backend selection, exact layout constraint surface, +static versus interactive adapters, reliable font measurement, multilingual text, +asset closure and visual regression policy. No new engine has been installed by +this research and no automatic orchestrator or visual-acceptance claim is issued. + +* D2 source identity observation +The inspected official D2 checkout6a7b4837838a3907604a1d9eec57ee98b6f3cd01 +now declares @d2lang/d2 in d2js/js/package.json. The prototype uses the older +@terrastruct/d2 published package. This is a dependency/API comparison obligation, +not authorization to mix an unqualified version upgrade into visual experiments. +Source-language and rendering-backend choices remain independently reviewable. + +* Visual inspection notes +The actual Cocoon web-app image and HTML use a large fixed SVG canvas with a +horizontal chain and a separate card row. Its color discipline is useful, but +its large unused area and fixed placement are not a solution to our current +layout complaint. It is not selected as the target template. +The AWS example demonstrates icon-based resource identity and nested boundaries. +The Diagrams advanced web-service example illustrates icon-rich clusters and +relationships as the main figure rather than a document shell. These are visual +references, not evidence that their default layout handles our Library models. + +* LLM/MLLM-led architecture and explanatory figures +The next research scope is visual design by language and multimodal models, not +only graph libraries. Historical scores, fixed templates and the current engine +choice are references for comparison; they must not become a universal aesthetic +objective. The user's uploaded infographic is a concrete visual reference for +compact information grouping, meaningful icons and low reading burden. Its exact +six-section format is not mandatory for every future question. + +| Primary project and inspected revision | Actual inspected material | Relevant method and limit | +|---------------------------------------+---------------------------+---------------------------| +| [[https://github.com/cathrynlavery/diagram-design/tree/0cf73c3fcc56667ad91a9a04c439ac6ccfabc9d1][Diagram Design]] | Official site before/after examples; repository type/example inventory and architecture screenshot | LLM-authored self-contained HTML plus inline SVG; choose a visual genre and hierarchy from the content rather than restyling one card layout | +| [[https://github.com/nicobailon/visual-explainer/tree/2a34c38c8bf5d0affbf60700f7cf24245bb8ed07][Visual Explainer]] | templates/page.html and kit/render.mjs | A figure and a concise answer lead; semantic references connect prose to diagram parts; rendered pages receive browser/contact-sheet inspection | +| [[https://github.com/google-research/papervizagent/tree/e088a8fff74cc363b6897c0843631fff76484908][PaperVizAgent / PaperBanana]] | Official repository identity; agents/critic_agent.py; author project pipeline | Reference retrieval, content/style planning and visual-feedback refinement; a research method, not an instruction to add its multi-agent runtime | +| [[https://github.com/ResearAI/AutoFigure-Edit/tree/16f3749e9d512bdf7b7b55c162307bc289750b7a][AutoFigure-Edit]] | Official README, staged reconstruction and reference-style gallery | Use a reference image to guide composition and reconstruct editable SVG; its image generation/segmentation stack is not automatically a dependency | + +The independently maintained llmsresearch/paperbanana implementation is not the +official Google project. Research attribution and locally verified capabilities +must remain distinct. No reference system has been run against our case here. + +** HTML as a drawing artifact +HTML/CSS and inline SVG are valid primary visual authoring surfaces. A standalone +figure can contain iconography, spatial grouping, annotations, comparison panels +and a compact legend without a navigation shell or long prose document. CSS grid +and SVG each serve the visual problem that suits them. D2 remains available for +model relationships or an embedded subview; it does not prescribe the entire +composition. OpenSysML and existing C/Q/evidence references retain their semantic +ownership across every output surface. + +Keep the editable HTML/SVG source. Export the intended figure region to PNG or +other requested outputs through a real browser. [[https://playwright.dev/docs/screenshots][Playwright supports page and element screenshots]]; +this establishes an export capability, not our completed exporter acceptance. +Record viewport, device scale, browser identity, theme/assets and source identity; +wait for fonts/images and a deliberately selected animation state. Screenshot the +figure rather than accidentally capturing controls or the full document. Native +browser font rendering is particularly relevant after the observed resvg fallback. +PDF/SVG export should be qualified separately when required. + +** Applying the method to this Library +1. Start with the question, audience, actual model/evidence and the explanation's + intended takeaway. Keep architecture and workflow as distinct visual scopes. +2. Select relevant reference figures and visual metaphors. LLM proposes compact + layouts, component groupings, icon placement and short annotations; no single + score or fixed card hierarchy defines the solution space. +3. Render candidate HTML/SVG at the intended reading size and inspect its actual + image. MLLM critique addresses reading order, ambiguity, whitespace, collisions + and missing meaning, then the candidate is revised with the user's feedback. +4. Check source/model/evidence fidelity through existing framework owners. These + semantic checks remain separate from optional aesthetic diagnostics. +5. Retain editable sources, export images and record the selected candidate and + its associated references. Publication remains an explicit project decision. + +This is the revised design direction. The current fixed-width, fixed-section TS +compositor is a baseline to replace, not the contract that future LLM-designed +figures must fit. Public TS APIs can remain functional; themes, icons, source +acquisition and export effects stay independently composable. Existing Agent +capabilities should drive design iteration before adding another orchestration +runtime. No implementation change or live backend/API invocation accompanies +this research update. + +Visual inspection of Diagram Design's architecture.png still shows substantial +unused canvas. Its method and output surface are relevant, but this individual +sample is not accepted as our density or iconography baseline. Reference projects +are sources of techniques, not automatic aesthetic winners or fixed score targets. + +* Implementation: Deterministic Engineering Render +The package is now @deterministic-engineering/render in packages/render. The +package version remains1.0.0 and is read only from package.json. The old fixed +composePresentation SVG/card compositor is removed; composeFigure accepts a pure +HTML/SVG composition. The host supplies a Playwright browser to exportFigure; +the Library owns and closes its own context and returns PNG plus rendering facts. +It writes no project files and issues no Core/Aitia authority. + +** D2 owns Questions and authored content +D2 already has the [[https://d2lang.com/tour/vars/][vars.d2-config.data plugin metadata mechanism]]. +The existing compiler exports it through graph.data. The installed SDK returns +null for nested map values; a native JSON block scalar carries the nested figure +declaration correctly. Official D2 parses source/imports; standard JSON.parse +reads that scalar. No second D2 parser or TS Questions registry is introduced. + +The90.132/90.133 D2 sources contain Questions, focus/source associations, explanatory +copy, language, purpose, theme layers and model relations. TS contains visual +composition and source reading adapters. A changed D2 question changes the image +without editing TS. Native relation IDs/directions are retained, and the compositor +refuses requests for undeclared relations. The case is a scoped projection with +explicit model/source links, not a complete SysML semantic validation. + +** Language and own visual system +This case uses English because its source architecture, D2 and SysML are English. +No renderer-wide language translation is applied. Upstream maintains Blueprint, +Field Notes and Graphite theme data plus a Compact spacing layer. Declared ordered +layers compose through pure functions; downstream may supply its own layers. +The previously used Lucide default dependency is removed. The Engineering glyph +pack uses project-authored SVG assets for definitions, selection, capture, model, +document, decision, evidence and source references. It remains independently +replaceable. The uploaded infographic guides density and information grouping, +not the copying of its decorative assets or a fixed universal template. + +** Actual case and remaining trust boundary +The Case reads actual organization Library objects, Profile declarations, Scenario +functions, project.sysml and Org design/dossier sources. Explicit source excerpts +and their line positions are observed; this is not a Scheme or SysML interpreter. +Current prepare-formation-capture still returns capture-blocked and capture=#f, +which remains visible in both views. No C, accepted Q, independent R or delivery +is claimed by this image-generation run. + +Architecture and Workflow are separate HTML/PNG artifacts. The main relationship +region uses content-driven height rather than fitting the whole graph to the full +page width. Browser-measured region bounds are recorded as diagnostics, not a +universal density score or automatic aesthetic acceptance. Positive/negative +checks cover actual D2 metadata, missing/changed questions, source footprint, +undeclared/reversed relations, theme/icon replacement, clipping, invalid images, +HTML modification and browser-context cleanup. + +An initial export-control run stalled at document.fonts.ready before layout in a +script-disabled browser context. The corrected path uses the standard context, +explicitly settles layout, then waits for fonts/images. The same clipping and +invalid-image controls now return the intended refusals. Previous logs are kept +locally; a repeated run alone is not the claimed fix. + +* Visual language, diagram organization and palette are independent +A theme is a visual language: stroke treatment, typography, node/port shapes, +connector appearance, boundary treatment and spacing. Palette layers remain +composable but are not a complete theme on their own. =sketchbook= uses actual +Rough.js strokes and embedded Kalam fonts; =node-editor= adds node ports, +precise borders and a node-canvas treatment. It does not embed React Flow. + +Swimlane is an organization of responsibility and order, not a palette. +The D2 figure declaration owns =flow.lanes=, =flow.steps= and =layout=. +Steps bind existing native D2 node IDs; their consecutive relations must exist. +A missing lane, node or edge refuses composition. Responsibility labels are +explicit project design declarations, not verified human role mandates. +A business view similarly needs its own business entities/relations and Question; +changing a theme cannot invent these semantics. + +Downstream selects ordered theme layers and a layout in the D2 declaration. +Custom layers and functional composers remain replaceable; the package does +not prescribe numbered sections or a universal card layout. Architecture and +Workflow retain independent purposes. Current source/model language stays English. + +HTML export measures settled browser text and routes declared connectors against +actual node boxes. Sketch surfaces are marked explicitly with =data-surface=; +font assets and deterministic Rough.js SVG are baked into offline HTML. +Receipts distinguish original composed HTML from materialized HTML, record font +hashes and actual browser identity. These are rendering receipts, not C/Q/R evidence. + +Native browser controls cover deterministic sketch output, changed drawing +parameters, missing connector endpoints, clipping and broken images. The real +case generates architecture and workflow separately, plus sketchbook, +node-editor and workflow-swimlane variants. Actual current source still returns +=capture-blocked / capture = #f=; no accepted inference or published model is claimed. + +References: [[https://roughjs.com/][Rough.js drawing primitives]] and +[[https://reactflow.dev/learn/customization/custom-nodes][React Flow node customization]]. +The user-provided Zhihu reference returned HTTP 403; its article contents were +not inspected. Its hand-drawn example is used as the requested visual direction. + +* Static style implementation and open view organization +The current package boundary is static rendering. Rough.js supplies sketch +primitives and hachure strokes. XYFlow's public =getSmoothStepPath= supplies +connector geometry; the package does not initialize an editor, interactions or +pan/zoom state. The complete Excalidraw package and React are excluded. There is +no editor-scene adapter, runtime bundler or additional build entry. + +** Three independent choices +| Concern | Owner | Current implementation | +|---------+-------+------------------------| +| Meaning and Questions | D2/SysML/project | Canonical =90.132=/=90.133= declarations and captured sources | +| View organization | Project compositor | Native graph, responsibility swimlane, dependency matrix and open =layout=/=presentation= | +| Visual language | Replaceable theme layers | Independent =styles/sketchbook.ts= and =styles/node-editor.ts=, palettes, spacing, fonts and icons | + +Node and swimlane are examples, not the framework's complete catalogue. Native +D2 classifications retain their existing language/rendering path. New domain +organizations use a functional composer and validate their own presentation data; +there is no mandatory card count or global enumeration of all future diagram kinds. +The new dependency-matrix Case shows the same native architecture relationships +in a genuinely different organization, without converting dependencies to workflow. + +** Source and evidence invariants +Questions, explanatory copy, current language and source links remain declared +in D2. The model association remains the captured =project.sysml= and native D2 +projection. Changing a theme/layout changes the rendering footprint, not the model +or evidence status. Every rendered connector is checked against its captured +relation and direction. Matrix axes must reference native nodes. Workflow lane +assignments are design declarations, not role-authorization evidence. + +Font assets are explicit theme data. Rendering records composed HTML separately +from materialized offline HTML, actual font hashes, drawing/routing package pins, +browser identity and output fingerprints. Rough.js deterministic-seed controls +apply within the qualified browser/font environment; no cross-platform pixel +identity is claimed. The current blocked capture and empty evidence remain visible. + +** Verification and artifacts +The standard package checks cover source/Question capture, native D2 syntax and +notation, saved SDK projection, open custom presentation, immutable style +composition and unknown-layer refusal. Native browser controls cover actual +Rough.js output and font embedding, repeated seeded output, modified inputs, +clipping, broken images, missing endpoints and reversed model relations. +A separate packed-package consumer checks installed worker/font/dependency paths. + +Eight full HTML/PNG candidates plus rendering receipts are generated through +=examples/render/library-case.ts=. =examples/render/comparison.ts= compares only +the main diagram regions, separately for architecture and Workflow, without +stretching a Workflow to fill the whole figure. Document snapshots are explicit +design candidates; rendering does not submit review or publish SysML. + +References: [[https://roughjs.com/][Rough.js official primitives]], +[[https://reactflow.dev/api-reference/utils/get-smooth-step-path][XYFlow public connector utility]], +[[https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation][Excalidraw editor boundary]]. + +** Local qualification recorded on 2026-11-01 +- Package and real consumer native TypeScript checks: passed. +- Bun unit/native D2/saved SDK controls: 29 passed, 0 failed. +- Native Playwright export controls: 7 passed, 0 failed; worker limit remains two. +- Eight complete candidates and two separate comparison sheets: generated and visually inspected. +- Final packed consumer: =PACKED-CONSUMER-OK=, including owned D2 worker, + actual Rough.js SVG, embedded font resources and selected-region PNG export. +- Qualified tarball SHA256: =ded011a886bb9ad47b69340a89bec268d1365a8b0ac79a7fe8ea9883e4f00bf5=. +- Runtime dependencies: D2 0.1.33, Rough.js 4.6.6, Kalam 5.3.0, + XYFlow system 0.0.83. Neither editor nor React remains in the declarations/locks. +- Actual local export browser: Chromium =156.0.8078.4=. +- CI workflow syntax: actionlint passed. Remote Linux/macOS execution is not + established by these local results; no live SysML validation or C/Q/R is issued. + +* Extensibility audit follow-up +[[file:10.142-declarative-render-extensibility-audit.org][10.142]] supersedes the earlier +closed kind selection, framework-owned linear flow helper and programmatic-only +icon selection. It records the current owners and executable positive/negative controls. diff --git a/docs/10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org b/docs/10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org new file mode 100644 index 0000000..1b8ff7f --- /dev/null +++ b/docs/10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org @@ -0,0 +1,102 @@ +#+TITLE: 10.142 Declarative Static Render Extensibility Audit +#+LANGUAGE: en +#+PROPERTY: JD_ID 10.142 + +* Scope and resulting ownership +This audit follows 10.141 and inspects the actual TS package, D2 declarations, +Library/Profile/Scenario Case, native APIs and CI entrypoints. The package remains +=@deterministic-engineering/render=, version 1.0.0 read from =package.json=. +It is a static rendering library. No whiteboard/editor or React runtime is added. + +| Owner | Publishes / consumes | Does not own | +|-------+----------------------+--------------| +| Project D2/SysML | Questions, copy, model IDs/edges, sources, kind, layout, theme/icon selection and variants | Generic render implementation | +| Project composer | Domain presentation schema, selected content organization, source observations | Another HTML/D2 parser, graph layout engine or evidence ledger | +| Render core | Capture/fingerprint, immutable composition context, resource resolution and purpose boundary | Linear-workflow semantics, finite catalogue of all diagram kinds or mandatory page template | +| Mature libraries | D2 compilation/layout/SVG, parse5 HTML parsing, Rough.js strokes/options, XYFlow connector paths, Playwright export | Project truth, evidence issuance or review authority | + +* Findings and disposition +| Finding in previous implementation | Change / exact owner | Verification | +|------------------------------------+----------------------+--------------| +| Seven names acted as the whole diagram taxonomy; purpose checks were repeated | =source/types.ts= has open kind identifiers and one =validatePurposeKind=; reserved standard kinds retain their purpose | Custom kind through actual D2 worker and native SVG render; blank kind/reserved mismatch refuse | +| Framework parsed/validated every workflow as one ordered preparation path with lanes | Moved schema and checks to =examples/render/library-case/workflow-declaration.ts=; D2 places it in project-owned =presentation.flow= | Case lane/node/edge negatives, actual source declarations | +| Compiler forced ELK regardless of project selection | Both worker entry and default native theme leave compile options empty; RenderTheme accepts the complete official CompileOptions | Actual native D2 DAGRE declaration compiles | +| Variant consumer re-resolved themes through upstream defaults and lost downstream resources | =readD2Figure= resolves each variant once against supplied =FigureResources=; =selectFigureVariant= retains it | Downstream theme remains in variant and HTML; missing/duplicate variants refuse | +| Icons were programmatic overrides; D2 could not select packs, metadata always said engineering | Ordered immutable =composeIconPacks=, D2 =icons= and variant overrides; actual SVG hashes in metadata | Custom real Case glyphs; unknown/prototype-name pack and invalid asset refuse | +| Question markers used a double-quote regex | parse5 parses real HTML; browser independently checks actual visibility | Single/unquoted attributes pass; comments/template/hidden/CSS invisibility refuse | +| Captured model and project presentation could be modified by a composer | Deep-freeze cloned inputs before composition | Mutation negatives; caller input remains unchanged | +| Drawing hardcoded rectangle/hachure/pen parameters | Reuse Rough.js =Options= directly; explicitly declared rectangle/ellipse/diamond surfaces call its primitives | Options/geometry change output; unsupported surface refuses | +| HTML connections assumed horizontal endpoints | Declared =data-source-position=/=data-target-position= select XYFlow positions | Vertical connection passes, invalid side and reversed relation refuse | +| Matrix variant replaced part of Case HTML via substring search | =architecture-diagram.ts= is an ordinary Case-owned pure-function table | Real default/matrix/custom resource compositions and unsupported Case layout refusal | +| Source path checking was duplicated | Figure capture reuses =source/catalog.ts= source boundary | Source law and changed/uncaptured input controls | +| Source excerpts were hardcoded in TS | D2 =presentation.observations= declares assertions; Case effect checks actual files | Missing/changed excerpts stop output; observations do not become execution evidence | +| Unused SVG data-URI wrapper duplicated the existing inline SVG path | Removed; =IconAsset= remains the single payload type | Same real glyph/export tests | + +* Downstream declaration contract +=compileFigureSource(sources, inputPath, resources?)= accepts data-only resources: +=themeLayers= and =iconPacks=. Defaults are immutable. A downstream extends them +with ordinary module exports, then D2 selects ordered names. No global registration, +new manifest, cache or automatic discovery mechanism is required. + +A declared variant may select =kind=, =layout=, =themes= and =icons=. Unspecified +resources inherit the view's resolved selection. =selectFigureVariant= preserves +the captured sources, model, Questions and evidence links; it does not recompile +or rewrite the model. Unknown or duplicate variants fail. Explicit icon packs are +complete selected maps, not implicit word-based icon inference. + +=kind= is open. =purpose= remains architecture or workflow. Reserved standard +kinds keep their original purpose, so allowing a new kind cannot silently turn +an existing architecture view into a Workflow. Project =presentation= stays +unknown at the generic boundary and is specifically validated by its compositor. + +The Case's =project-owned= variants declare =responsibility-map= and +=capture-handoff= kinds, compose =field-notes + workbench + compact= and select +=engineering + repo= glyphs. These names are absent from framework source. +The Case remains explicitly English and still displays =capture-blocked= with +=capture = #f= and no issued C/Q/R. Source assertions remain source observations. + +* Intentional retained defaults and limits +Upstream palettes/styles and the Engineering glyph pack are optional maintained +resources. Their existence does not limit downstream kinds or assets. The Case +has an authored explanatory layout; its headings, role placement and section +organization belong to that Case, not to =composeFigure=. + +Native D2 notation keeps D2 layout/SVG. The browser adapter measures explicitly +authored HTML node boxes and delegates only connectors to XYFlow; it does not +replace D2 with another graph layout solver. Rough.js adapters expose the selected +primitives; other explicit SVG markup can be supplied by a compositor. Unknown +primitives do not silently render as rectangles. + +OpenSysML's supported reflow adapter retains capability refusals for unsupported +notation. This audit does not claim live SysML service validation. Static HTML +export still bounds width/scale, closes its browser context, records actual fonts, +backend pins and output hashes, and rejects stale inputs and detected clipping. +Visual quality remains a review judgment separate from correctness controls. + +* Acceptance and execution entrypoints +The normal CI entrypoints run package =bun install --frozen-lockfile=, +=typecheck=, =bun test=, =bun run test:export=; then the actual consumer's frozen +install, typecheck, tests, Case export, comparisons and native audit view. +Playwright owns browser fixtures; tests do not start another test runner. Worker +limits remain two and existing job/resource bounds are unchanged. + +Required positives include native D2 notation, project-only kinds/themes/icons, +variant retention, real Library/Profile/Scenario sources, source fingerprints, +independent architecture/Workflow artifacts and actual font/image export. +Negatives retain unknown resources, hidden Questions, source/model mutation, +invalid lanes/relations/ports, unsupported primitives, stale input and broken images. + +Ten full candidates and two comparison sheets are derived from 90.132/90.133. +90.134 is the native D2 ownership/dependency view for this audit. CI is authoritative +for Linux/macOS and its exact commit; local results alone do not establish both. +No artifact-upload action is added; generated image candidates are tracked as +requested documentation. No model publication, review or delivery is performed. + +* Local qualification before PR +Package typecheck passed; 35 unit/native-D2/saved-SDK tests passed. The native +Playwright entry passed 10 positive/negative controls. The actual consumer passed +its typecheck and two domain integration tests, exported ten full candidate +figures and two comparison sheets, and rendered the native D2 audit view. +Remote Linux/macOS qualification must be read from the new PR's checks; local +results do not substitute for those runs. The workflow keeps its ten-minute job +bound and two-worker configuration and installs only declared packages/browser. diff --git a/docs/90-99-views/90-architecture-views/90.01-index.org b/docs/90-99-views/90-architecture-views/90.01-index.org index 7d5b972..517420a 100644 --- a/docs/90-99-views/90-architecture-views/90.01-index.org +++ b/docs/90-99-views/90-architecture-views/90.01-index.org @@ -508,3 +508,31 @@ Lean proof, benchmark execution or operational effect is implemented by them. - [[file:90.127-native-package-cohort-and-ci-provenance.d2][90.127 Native package cohort and CI provenance]] - [[file:90.128-native-cohort-source-acquisition.d2][90.128 Native cohort and source acquisition]] + +- [[file:90.129-functional-architecture-rendering.d2][90.129 Functional architecture rendering]] — [[file:90.129-functional-architecture-rendering.svg][SVG]]; owner: [[file:../../10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org][10.140]]. + +- [[file:90.130-llm-assisted-model-view-workflow.d2][90.130 Model-to-view authoring workflow]] — [[file:90.130-llm-assisted-model-view-workflow.svg][SVG]]; separate from component architecture; owner: [[file:../../10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org][10.140]]. + +- [[file:90.131-library-responsibility-explanation.d2][90.131 Question-led Library responsibilities]] — concise candidate derived from 90.91; [[file:../../10-19-design/10-architecture/10.140-functional-architecture-rendering-and-opensysml.org][TS page composition design]]. + +- [[file:90.132-library-profile-scenario-architecture.d2][90.132 Library / Profile / Scenario architecture]] — [[file:90.132-library-profile-scenario-architecture.html][editable HTML]] · [[file:90.132-library-profile-scenario-architecture.png][PNG]]. Questions and presentation selection live in D2. +- [[file:90.133-capture-preparation-workflow.d2][90.133 Capture preparation workflow]] — [[file:90.133-capture-preparation-workflow.html][editable HTML]] · [[file:90.133-capture-preparation-workflow.png][PNG]]. Separate workflow, current capture-blocked result preserved. + +** Static visual styles and open view organizations +The following are design candidates of the same captured Library/Profile/Scenario +case, not acceptance of C/Q/R. Full figures retain their Questions and source links. +Comparison sheets show the main diagram regions at readable size. + +- [[file:90.132-library-profile-scenario-architecture-comparison.png][Architecture comparison]] — [[file:90.132-library-profile-scenario-architecture-comparison.html][standalone HTML]]. Hand-drawn, node style and dependency matrix. +- [[file:90.133-capture-preparation-workflow-comparison.png][Workflow comparison]] — [[file:90.133-capture-preparation-workflow-comparison.html][standalone HTML]]. Hand-drawn, node style and responsibility swimlane. +- [[file:90.132-library-profile-scenario-architecture-sketchbook.png][Full hand-drawn architecture]] · [[file:90.132-library-profile-scenario-architecture-node-editor.png][Full node-style architecture]] · [[file:90.132-library-profile-scenario-architecture-dependency-matrix.png][Full dependency matrix]]. +- [[file:90.133-capture-preparation-workflow-sketchbook.png][Full hand-drawn Workflow]] · [[file:90.133-capture-preparation-workflow-node-editor.png][Full node-style Workflow]] · [[file:90.133-capture-preparation-workflow-swimlane.png][Full swimlane Workflow]]. + +Upstream themes are independent visual modules; palettes, typography, drawing, +icons and view organization compose separately. Additional domain layouts use +project-owned declarations and pure composers. Native D2 sequence/state/data and +other supported notation retain their official rendering path. The static package +uses Rough.js and XYFlow's path utility; it includes no interactive whiteboard. + +- [[file:90.132-library-profile-scenario-architecture-project-owned.png][Project-owned architecture style/icons]] · [[file:90.133-capture-preparation-workflow-project-owned.png][Project-owned workflow style/icons]]. Kind, layout, ordered themes and glyph packs are selected in D2. +- [[file:90.134-render-responsibility-boundaries.d2][90.134 Static renderer responsibility boundaries]] · [[file:90.134-render-responsibility-boundaries.svg][native SVG]]. See [[file:../../10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org][10.142 audit]]. diff --git a/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.d2 b/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.d2 new file mode 100644 index 0000000..183c803 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.d2 @@ -0,0 +1,32 @@ +...@../../../packages/render/themes/system +consumer: { + label: "Downstream repositories" + gateway: "Gateway / Weaver\nmodel selection and local themes" + case: "Engineering Library case\nOrg, SysML and decisions" +} +library: { + label: "Engineering Library / renderer package" + api: "Public package exports\nindex.ts / opensysml subpath" + core: "Functional core\ncatalog / references / pipeline" + runtime: "Rendering effects\nworkers / native D2 compile + render" + theme: "Presentation data\ndefaultTheme / custom overrides" + presentation: "TS visual composition\nquestion / explanations / evidence context" + pageTheme: "Independent page themes\ntypography / surfaces / status colors / icons" + model: "Optional model projection\nOpenSysML adapter and source trace" +} +external: { + label: "Official external capabilities" + model: "OpenSysML TS SDK\npublic model and RenderedView types" + engines: "Rendering libraries\nOfficial D2 compile / render" +} +consumer.gateway -> library.api: package dependency +consumer.case -> library.api: same package API +library.api -> library.core: composes functions +library.api -> library.runtime: owns effect scopes +library.core -> library.theme: explicit theme input +library.runtime -> external.engines: public engine APIs +library.model -> external.model: public SDK API +consumer.gateway -> library.model: optional SysML projection + +library.api -> library.presentation: optional explanation page +library.presentation -> library.pageTheme: explicit presentation input diff --git a/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.svg b/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.svg new file mode 100644 index 0000000..046ffff --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.129-functional-architecture-rendering.svg @@ -0,0 +1,118 @@ +Downstream repositoriesEngineering Library / renderer packageOfficial external capabilitiesGateway / Weavermodel selection and local themesEngineering Library caseOrg, SysML and decisionsPublic package exportsindex.ts / opensysml subpathFunctional corecatalog / references / pipelineRendering effectsworkers / native D2 compile + renderPresentation datadefaultTheme / custom overridesTS visual compositionquestion / explanations / evidence contextIndependent page themestypography / surfaces / status colors / iconsOptional model projectionOpenSysML adapter and source traceOpenSysML TS SDKpublic model and RenderedView typesRendering librariesOfficial D2 compile / render package dependencysame package APIcomposes functionsowns effect scopesexplicit theme inputpublic engine APIspublic SDK APIoptional SysML projectionoptional explanation pageexplicit presentation input + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.d2 b/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.d2 new file mode 100644 index 0000000..5c792cc --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.d2 @@ -0,0 +1,12 @@ +...@../../../packages/render/themes/flow +confirmed: {label: "User-confirmed intent\nproject inputs and responsibility"; class: note} +llm: {label: "LLM proposes model and views\narchitecture and workflow kept separate"; class: semantic} +check: {label: "Check obligations and model\nASCENT queries / SysML capability"; class: policy} +selection: {label: "Select view family and theme\nexplicit candidate capture"; class: cloud} +render: {label: "Render candidate\nfunctional core / bounded effects"; class: runtime} +review: {label: "Review and publish\nretain source and decision links"; class: note} +confirmed -> llm: authorized design input +llm -> check: candidate, not evidence +check -> selection: supported candidate +selection -> render: immutable input +render -> review: images and trace diff --git a/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.svg b/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.svg new file mode 100644 index 0000000..3dba384 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.130-llm-assisted-model-view-workflow.svg @@ -0,0 +1,106 @@ +User-confirmed intentproject inputs and responsibilityLLM proposes model and viewsarchitecture and workflow kept separateCheck obligations and modelASCENT queries / SysML capabilitySelect view family and themeexplicit candidate captureRender candidatefunctional core / bounded effectsReview and publishretain source and decision links authorized design inputcandidate, not evidencesupported candidateimmutable inputimages and trace + + + + + + + \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.d2 b/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.d2 new file mode 100644 index 0000000..acf6fac --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.d2 @@ -0,0 +1,25 @@ +# Question-led presentation candidate derived from 90.91 and Org 10.31. +# Short labels are a reviewed content change, not a rewrite of the original source. +direction: right +vars: { d2-config: { theme-id: 0; layout-engine: elk; pad: 28 } } +classes: { + library: {style: {fill: "#EAF1FF"; stroke: "#245FE5"; font-color: "#173E89"; border-radius: 14; font-size: 22}} + profile: {style: {fill: "#F0EBFC"; stroke: "#7753D9"; font-color: "#50368A"; border-radius: 14; font-size: 22}} + scenario: {style: {fill: "#E7F4F0"; stroke: "#138274"; font-color: "#125F55"; border-radius: 14; font-size: 22}} + artifact: {style: {fill: "#FFFFFF"; stroke: "#CCD6E8"; font-color: "#364A68"; border-radius: 10; font-size: 19}} +} +library: {label: "Library\nPublished contracts"; class: library} +profile: {label: "Profile\nSelected configuration"; class: profile} +scenario: {label: "Scenario\nOne captured use"; class: scenario} +content: { + label: "Project-owned content" + style: {fill: "#F7F9FD"; stroke: "#CCD6E8"; border-radius: 16; font-size: 20} + model: {label: "Architecture model"; class: artifact} + docs: {label: "Org documents"; class: artifact} + decisions: {label: "Decision records"; class: artifact} +} +profile -> library: "selects published contracts" +scenario -> profile: "binds selection" +scenario -> content.model: "captures model references" +scenario -> content.docs: "captures document closure" +scenario -> content.decisions: "captures decision context" diff --git a/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.svg b/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.svg new file mode 100644 index 0000000..1287c09 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.131-library-responsibility-explanation.svg @@ -0,0 +1,113 @@ +{"question":{"ref":"10.140/questions/three-responsibilities","text":"Who publishes, who selects, and who captures?"},"basis":{"captureRef":"10.140/question-led-design-candidate","modelRefs":["90.91 / original content-contract view"],"obligationRefs":["10.31 / public contracts"],"evidenceRefs":[]},"findings":[{"icon":{"svg":"\n<svg\n class=\"lucide lucide-library-big\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <rect width=\"8\" height=\"18\" x=\"3\" y=\"3\" rx=\"1\" />\n <path d=\"M7 3v18\" />\n <path d=\"M20.4 18.9c.2.5-.1 1.1-.6 1.3l-1.9.7c-.5.2-1.1-.1-1.3-.6L11.1 5.1c-.2-.5.1-1.1.6-1.3l1.9-.7c.5-.2 1.1.1 1.3.6Z\" />\n</svg>\n","label":"Library"},"title":"Publish once","text":"Models · methods · policies · obligations","status":"design","refs":["10.31 / Library"]},{"icon":{"svg":"\n<svg\n class=\"lucide lucide-sliders-horizontal\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M10 5H3\" />\n <path d=\"M12 19H3\" />\n <path d=\"M14 3v4\" />\n <path d=\"M16 17v4\" />\n <path d=\"M21 12h-9\" />\n <path d=\"M21 19h-5\" />\n <path d=\"M21 5h-7\" />\n <path d=\"M8 10v4\" />\n <path d=\"M8 12H3\" />\n</svg>\n","label":"Selection"},"title":"Select explicitly","text":"Stage · viewpoints · required contracts","status":"design","refs":["10.31 / Profile"]},{"icon":{"svg":"\n<svg\n class=\"lucide lucide-scan-line\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" />\n <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" />\n <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" />\n <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" />\n <path d=\"M7 12h10\" />\n</svg>\n","label":"Capture"},"title":"Capture one use","text":"Architecture · Org · decisions · sources","status":"design","refs":["10.31 / Scenario"]}]}ARCHITECTURE / OWNERSHIPOne Library. Three clear responsibilities.Deterministic Engineering / DE-CASE-01 · question-led architecture explanationQuestionTHE QUESTIONWho publishes, who selects, and who captures?Publish reusable contracts. Select the right configuration. Bind real project content.1Architecture / boundaries and responsibilitiesLibraryPublished contractsProfileSelected configurationScenarioOne captured useProject-owned contentArchitecture modelOrg documentsDecision records selects published contractsbinds selectioncaptures model referencescaptures document closurecaptures decision context + + + + + + +2Three responsibilities / no duplicated ownershipLibraryDESIGNPublish onceModels · methods · policies ·obligationsSelectionDESIGNSelect explicitlyStage · viewpoints · required contractsCaptureDESIGNCapture one useArchitecture · Org · decisions · sources3Model / obligations / evidenceArchitecture modelModel references1 linkedObligationObligation references1 linkedPendingEvidence referencesnone linked / pendingDesign candidate derived from 90.91 / 10.31. Evidence and independent review remain open.SOURCE 23c889168a078687 / DESIGN CLAIMS / ARCHITECTURE \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.html new file mode 100644 index 0000000..743604c --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.html @@ -0,0 +1 @@ +

Library / Profile / Scenario architecture

sketchbook

architecture-sketchbook

node-editor

architecture-node-editor

dependency-matrix

architecture-dependency-matrix

project-owned

architecture-project-owned
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.png new file mode 100644 index 0000000..32bac65 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-comparison.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix-detail.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix-detail.png new file mode 100644 index 0000000..3f2bfa3 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.html new file mode 100644 index 0000000..53260a8 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.html @@ -0,0 +1,32 @@ +Library / Profile / Scenario architecture
+
Repository case / design view
DETERMINISTIC ENGINEERING / DE-CASE-01

One Library. Clear boundaries.

Shared definitions. Independent choices. Traceable project use.

+

1What does this Library make possible?

+

Publish once

Models, methods and policies
Reusable definitions from Library

+

Choose per project

Profile selects the needed capabilities
Original identities stay intact

+

Bind actual use

Scenario acquires project inputs
One use, with source associations

+

2Who publishes, selects and binds?Ownership / arrows point to dependencies

+
LibraryProfileScenario
Library———
Profileselects——
Scenarioreferences definitionsbinds—
+
Model: project.sysml / a scoped responsibility viewASCENT reasoning and Aitia review retain separate owners
+

3Which responsibilities stay independent?

+

One definition owner

Selection and use reference definitions
Contract bodies are not copied

Selection keeps its scope

Retain parents and both policies
Bind actual published methods

Reuse existing admission

POO / Core own admission
Keep normalized return values

Keep review independent

Candidates, evidence and authority
Retain each source association

+

4How do models, docs and decisions connect?

Architecture modelproject.sysml / actual and target
Org design basisContracts, duties and acceptance
Decisions and reassessmentChoices, assumptions and triggers
Design view: issued C, accepted Q and independent review still need actual binding.
+

What does the current source show?

Library / Profile declarations are visible
Objects, both policies, catalog / mapping and method selection
Model and source references are retained
Each figure records captured file fingerprints and reference mappings

What is still blocked?

capture-blocked · capture = #f
Preparation still reports origin and method-footprint gaps
No review or delivery was executed here
Source observation and image export are not execution receipts
+
Library publishes meaning. Profile selects. Scenario binds this use.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.json b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.json new file mode 100644 index 0000000..9e636ac --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.json @@ -0,0 +1,157 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "43c7682996db29b86351df19baaab9457bc0bda81cfc90d157a85ea5263dc393", + "composedHTMLSHA256": "be2fb95a10bb64c2ef5973ddb36e14d44a6cff5de43faf6e5c6a3ddf877d2a7f", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "architecture": "743ef8c25ad8ae50a2c9f4685e410ccf28876a75c53b5eea35920a2493e54c2d" + }, + "htmlSHA256": "82882ad0593060fe7ea0fcac40a7bf3615f52ad386cb6ee46dfcdf591cbac060", + "drawing": "precise", + "routing": "none", + "assetSHA256": {}, + "pngSHA256": "6358f78f73db63e52d69edb30bcfa8ba4e6f964fdeb98860f3bd5ce40ed08958", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2922, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 138.578125 + }, + "purpose": { + "x": 26, + "y": 164.578125, + "width": 1068, + "height": 209 + }, + "architecture": { + "x": 26, + "y": 385.578125, + "width": 1068, + "height": 331 + }, + "principles": { + "x": 26, + "y": 728.578125, + "width": 1068, + "height": 218.171875 + }, + "products": { + "x": 26, + "y": 958.75, + "width": 1068, + "height": 177 + }, + "evidence": { + "x": 26, + "y": 1147.75, + "width": 1068, + "height": 194 + }, + "takeaway": { + "x": 26, + "y": 1341.75, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.png new file mode 100644 index 0000000..4295455 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-dependency-matrix.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-detail.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-detail.png new file mode 100644 index 0000000..730ffe2 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor-detail.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor-detail.png new file mode 100644 index 0000000..d0b5def Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.html new file mode 100644 index 0000000..a00c943 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.html @@ -0,0 +1,41 @@ +Library / Profile / Scenario architecture
+
Repository case / design view
DETERMINISTIC ENGINEERING / DE-CASE-01

One Library. Clear boundaries.

Shared definitions. Independent choices. Traceable project use.

+

1What does this Library make possible?

+

Publish once

Models, methods and policies
Reusable definitions from Library

+

Choose per project

Profile selects the needed capabilities
Original identities stay intact

+

Bind actual use

Scenario acquires project inputs
One use, with source associations

+

2Who publishes, selects and binds?Ownership / arrows point to dependencies

+
+ +
REUSABLE PUBLICATION BOUNDARY

Library

Definitions and contracts
Models / catalog / mapping
Coding and design-review policies
Method bindings and input demands
+
selects
+
PROJECT BOUNDARY / DE-CASE-01
+

Profile

Select parents, policies and methods
Reference catalog / mapping

+
binds
+

Scenario

Acquire actual inputs and origins
Retain choices, checks and gaps

+
Architecture modelOrg documentsDecisions
+ +
Model: project.sysml / a scoped responsibility viewASCENT reasoning and Aitia review retain separate owners
+

3Which responsibilities stay independent?

+

One definition owner

Selection and use reference definitions
Contract bodies are not copied

Selection keeps its scope

Retain parents and both policies
Bind actual published methods

Reuse existing admission

POO / Core own admission
Keep normalized return values

Keep review independent

Candidates, evidence and authority
Retain each source association

+

4How do models, docs and decisions connect?

Architecture modelproject.sysml / actual and target
Org design basisContracts, duties and acceptance
Decisions and reassessmentChoices, assumptions and triggers
Design view: issued C, accepted Q and independent review still need actual binding.
+

What does the current source show?

Library / Profile declarations are visible
Objects, both policies, catalog / mapping and method selection
Model and source references are retained
Each figure records captured file fingerprints and reference mappings

What is still blocked?

capture-blocked · capture = #f
Preparation still reports origin and method-footprint gaps
No review or delivery was executed here
Source observation and image export are not execution receipts
+
Library publishes meaning. Profile selects. Scenario binds this use.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.json b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.json new file mode 100644 index 0000000..0bf991f --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.json @@ -0,0 +1,157 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "ea269761fccf417979154a9901c65398fdaeea3b7386df067063089e210c381a", + "composedHTMLSHA256": "94348cb8d7c5100e146b69d5aee0a215dc0b6bd18a8ab93e40cc016f05283f2f", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "architecture": "f2ad530feaf01bf879665ca2139e0c01201c175de4615a1c98ada8129a880ecd" + }, + "htmlSHA256": "d872a1a2762f4377c773252556898d28feee33c32b02b662031fde4660e621b3", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "4fa7a2d00b0cf73039299da53c9d56fdada4f93f758a3d014b815ae278de155e", + "browser": "156.0.8078.4", + "width": 2240, + "height": 3084, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 138.578125 + }, + "purpose": { + "x": 26, + "y": 164.578125, + "width": 1068, + "height": 209 + }, + "architecture": { + "x": 26, + "y": 385.578125, + "width": 1068, + "height": 412 + }, + "principles": { + "x": 26, + "y": 809.578125, + "width": 1068, + "height": 218.171875 + }, + "products": { + "x": 26, + "y": 1039.75, + "width": 1068, + "height": 177 + }, + "evidence": { + "x": 26, + "y": 1228.75, + "width": 1068, + "height": 194 + }, + "takeaway": { + "x": 26, + "y": 1422.75, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.png new file mode 100644 index 0000000..ffae39c Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-node-editor.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned-detail.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned-detail.png new file mode 100644 index 0000000..f139e32 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.html new file mode 100644 index 0000000..89462ce --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.html @@ -0,0 +1,32 @@ +Library / Profile / Scenario architecture
+
Repository case / design view
DETERMINISTIC ENGINEERING / DE-CASE-01

One Library. Clear boundaries.

Shared definitions. Independent choices. Traceable project use.

+

1What does this Library make possible?

+

Publish once

Models, methods and policies
Reusable definitions from Library

+

Choose per project

Profile selects the needed capabilities
Original identities stay intact

+

Bind actual use

Scenario acquires project inputs
One use, with source associations

+

2Who publishes, selects and binds?Ownership / arrows point to dependencies

+
LibraryProfileScenario
Library———
Profileselects——
Scenarioreferences definitionsbinds—
+
Model: project.sysml / a scoped responsibility viewASCENT reasoning and Aitia review retain separate owners
+

3Which responsibilities stay independent?

+

One definition owner

Selection and use reference definitions
Contract bodies are not copied

Selection keeps its scope

Retain parents and both policies
Bind actual published methods

Reuse existing admission

POO / Core own admission
Keep normalized return values

Keep review independent

Candidates, evidence and authority
Retain each source association

+

4How do models, docs and decisions connect?

Architecture modelproject.sysml / actual and target
Org design basisContracts, duties and acceptance
Decisions and reassessmentChoices, assumptions and triggers
Design view: issued C, accepted Q and independent review still need actual binding.
+

What does the current source show?

Library / Profile declarations are visible
Objects, both policies, catalog / mapping and method selection
Model and source references are retained
Each figure records captured file fingerprints and reference mappings

What is still blocked?

capture-blocked · capture = #f
Preparation still reports origin and method-footprint gaps
No review or delivery was executed here
Source observation and image export are not execution receipts
+
Library publishes meaning. Profile selects. Scenario binds this use.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.json b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.json new file mode 100644 index 0000000..59779e9 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.json @@ -0,0 +1,157 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "e02f7f40d5a990af8bcbea01af155e7dc9c52453bca70d200bd0bf204c4dca08", + "composedHTMLSHA256": "23420808468bb12faacef87bcc5db0cf7998836f4d138e28da6c55a214672121", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "architecture": "879bd6628061ac11fd1fc9a7bcfc461cdf3f778bb7db5e60546c4f8f37c1c4ae" + }, + "htmlSHA256": "26a2107ae92ce435da67f4ee3a44e22b027cc1fdf0e63ee7cd38ada5c30f4b0d", + "drawing": "precise", + "routing": "none", + "assetSHA256": {}, + "pngSHA256": "b14588b159142e280083676a666837ca773449fd4cadbb048125072bc2886d0f", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2790, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 135.578125 + }, + "purpose": { + "x": 26, + "y": 161.578125, + "width": 1068, + "height": 187 + }, + "architecture": { + "x": 26, + "y": 360.578125, + "width": 1068, + "height": 318 + }, + "principles": { + "x": 26, + "y": 690.578125, + "width": 1068, + "height": 194.78125 + }, + "products": { + "x": 26, + "y": 897.359375, + "width": 1068, + "height": 173 + }, + "evidence": { + "x": 26, + "y": 1082.359375, + "width": 1068, + "height": 196 + }, + "takeaway": { + "x": 26, + "y": 1278.359375, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.png new file mode 100644 index 0000000..8b0e560 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-project-owned.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook-detail.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook-detail.png new file mode 100644 index 0000000..79dcbf3 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.html new file mode 100644 index 0000000..844692b --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.html @@ -0,0 +1,42 @@ +Library / Profile / Scenario architecture
+
Repository case / design view
DETERMINISTIC ENGINEERING / DE-CASE-01

One Library. Clear boundaries.

Shared definitions. Independent choices. Traceable project use.

+

1What does this Library make possible?

+

Publish once

Models, methods and policies
Reusable definitions from Library

+

Choose per project

Profile selects the needed capabilities
Original identities stay intact

+

Bind actual use

Scenario acquires project inputs
One use, with source associations

+

2Who publishes, selects and binds?Ownership / arrows point to dependencies

+
+ +
REUSABLE PUBLICATION BOUNDARY

Library

Definitions and contracts
Models / catalog / mapping
Coding and design-review policies
Method bindings and input demands
+
selects
+
PROJECT BOUNDARY / DE-CASE-01
+

Profile

Select parents, policies and methods
Reference catalog / mapping

+
binds
+

Scenario

Acquire actual inputs and origins
Retain choices, checks and gaps

+
Architecture modelOrg documentsDecisions
+ +
Model: project.sysml / a scoped responsibility viewASCENT reasoning and Aitia review retain separate owners
+

3Which responsibilities stay independent?

+

One definition owner

Selection and use reference definitions
Contract bodies are not copied

Selection keeps its scope

Retain parents and both policies
Bind actual published methods

Reuse existing admission

POO / Core own admission
Keep normalized return values

Keep review independent

Candidates, evidence and authority
Retain each source association

+

4How do models, docs and decisions connect?

Architecture modelproject.sysml / actual and target
Org design basisContracts, duties and acceptance
Decisions and reassessmentChoices, assumptions and triggers
Design view: issued C, accepted Q and independent review still need actual binding.
+

What does the current source show?

Library / Profile declarations are visible
Objects, both policies, catalog / mapping and method selection
Model and source references are retained
Each figure records captured file fingerprints and reference mappings

What is still blocked?

capture-blocked · capture = #f
Preparation still reports origin and method-footprint gaps
No review or delivery was executed here
Source observation and image export are not execution receipts
+
Library publishes meaning. Profile selects. Scenario binds this use.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.json b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.json new file mode 100644 index 0000000..c21a436 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.json @@ -0,0 +1,160 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "9ba4283663f34749df5268d17dd8e95f50b5afc8c2cdd7006c80dbf4696e2ac0", + "composedHTMLSHA256": "26a0cca249d1238833ca4010aef3230374c12ee20a533bed3876fbb1a07d3024", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "architecture": "0b7dfc2488f441d245d0862dc80206ef71962fca5c6fa4bc53ae72ee01fd838b" + }, + "htmlSHA256": "dc621e84c1efdc6bb9afc77f5256a321136d6002f11b379f2027eb4d26f99fc1", + "drawing": "roughjs@4.6.6", + "routing": "xyflow@0.0.83", + "assetSHA256": { + "@fontsource/kalam/files/kalam-latin-400-normal.woff2": "954410601a823f37e219f7930b7446f86afa15621326a7078d56fb9c910135cb", + "@fontsource/kalam/files/kalam-latin-700-normal.woff2": "252063af6ade8b9a744cde4ddad0fc21ea53b8ba711eed121a0c2e8610ea9c93" + }, + "pngSHA256": "dde86f1113599a1094887773038a56d77fd2b32b6cda8bd9378c3000f9e779ed", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2876, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 141.578125 + }, + "purpose": { + "x": 26, + "y": 167.578125, + "width": 1068, + "height": 184 + }, + "architecture": { + "x": 26, + "y": 363.578125, + "width": 1068, + "height": 337 + }, + "principles": { + "x": 26, + "y": 712.578125, + "width": 1068, + "height": 199.78125 + }, + "products": { + "x": 26, + "y": 924.359375, + "width": 1068, + "height": 186 + }, + "evidence": { + "x": 26, + "y": 1122.359375, + "width": 1068, + "height": 193 + }, + "takeaway": { + "x": 26, + "y": 1315.359375, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.png new file mode 100644 index 0000000..fbc797f Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture-sketchbook.png differ diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2 b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2 new file mode 100644 index 0000000..3efafa9 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2 @@ -0,0 +1,277 @@ +# Declared Questions and content for the actual DE-CASE-01 case. +# Source observations only; no issued C, accepted Q or review R. +vars: { + d2-config: { + layout-engine: elk + data: { + # Application declarations travel through the official graph.data interface. + figure: |json + { + "title": "Library / Profile / Scenario architecture", + "purpose": "architecture", + "kind": "system", + "language": "en", + "width": 1120, + "primary": "why", + "questions": [ + { + "ref": "why", + "text": "What does this Library make possible?", + "focusRefs": [ + "library", + "profile", + "scenario" + ] + }, + { + "ref": "ownership", + "text": "Who publishes, selects and binds?", + "focusRefs": [ + "library", + "profile", + "scenario", + "model" + ] + }, + { + "ref": "boundaries", + "text": "Which responsibilities stay independent?", + "focusRefs": [ + "library", + "profile", + "types" + ] + }, + { + "ref": "products", + "text": "How do models, docs and decisions connect?", + "focusRefs": [ + "model", + "documents", + "decisions" + ] + }, + { + "ref": "evidence", + "text": "What does the current source show?", + "focusRefs": [ + "library", + "profile", + "capture-stop" + ] + } + ], + "references": { + "library": "library/project-organization/objects.ss", + "profile": "profiles/library-formation.ss", + "scenario": "scenarios/project-formation/func.ss", + "capture-stop": "scenarios/project-formation/func.ss", + "model": "project.sysml", + "documents": "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org", + "decisions": "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org", + "types": "scenarios/project-formation/types.ss" + }, + "basis": { + "captureRef": "design-candidate:DE-CASE-01/rendering", + "modelRefs": [ + "project.sysml" + ], + "obligationRefs": [ + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org" + ], + "evidenceRefs": [] + }, + "copy": { + "badge": "Repository case / design view", + "eyebrow": "DETERMINISTIC ENGINEERING / DE-CASE-01", + "title": "One Library. ", + "emphasis": "Clear boundaries.", + "annotation-01": "Shared definitions. Independent choices. Traceable project use.", + "publish-heading": "Publish once", + "annotation-02": "Models, methods and policies", + "annotation-03": "Reusable definitions from Library", + "select-heading": "Choose per project", + "annotation-04": "Profile selects the needed capabilities", + "annotation-05": "Original identities stay intact", + "capture-heading": "Bind actual use", + "annotation-06": "Scenario acquires project inputs", + "annotation-07": "One use, with source associations", + "annotation-08": "Ownership / arrows point to dependencies", + "library-boundary": "REUSABLE PUBLICATION BOUNDARY", + "library-label": "Library", + "annotation-09": "Definitions and contracts", + "annotation-10": "Models / catalog / mapping", + "annotation-11": "Coding and design-review policies", + "annotation-12": "Method bindings and input demands", + "annotation-13": "selects", + "project-boundary": "PROJECT BOUNDARY / DE-CASE-01", + "profile-label": "Profile", + "annotation-14": "Select parents, policies and methods", + "annotation-15": "Reference catalog / mapping", + "annotation-16": "binds", + "scenario-label": "Scenario", + "annotation-17": "Acquire actual inputs and origins", + "annotation-18": "Retain choices, checks and gaps", + "model-label": "Architecture model", + "document-label": "Org documents", + "decision-label": "Decisions", + "annotation-19": "Model: project.sysml / a scoped responsibility view", + "annotation-20": "ASCENT", + "annotation-21": " reasoning and ", + "annotation-22": "Aitia", + "annotation-23": " review retain separate owners", + "annotation-24": "One definition owner", + "annotation-25": "Selection and use reference definitions", + "annotation-26": "Contract bodies are not copied", + "annotation-27": "Selection keeps its scope", + "annotation-28": "Retain parents and both policies", + "annotation-29": "Bind actual published methods", + "annotation-30": "Reuse existing admission", + "annotation-31": "POO / Core own admission", + "annotation-32": "Keep normalized return values", + "annotation-33": "Keep review independent", + "annotation-34": "Candidates, evidence and authority", + "annotation-35": "Retain each source association", + "annotation-36": "project.sysml / actual and target", + "annotation-37": "Org design basis", + "annotation-38": "Contracts, duties and acceptance", + "annotation-39": "Decisions and reassessment", + "annotation-40": "Choices, assumptions and triggers", + "annotation-41": "Design view: issued C, accepted Q and independent review still need actual binding.", + "annotation-42": "Library / Profile declarations are visible", + "annotation-43": "Objects, both policies, catalog / mapping and method selection", + "annotation-44": "Model and source references are retained", + "annotation-45": "Each figure records captured file fingerprints and reference mappings", + "annotation-46": "What is still blocked?", + "annotation-47": "capture-blocked \u00b7 capture = #f", + "annotation-48": "Preparation still reports origin and method-footprint gaps", + "annotation-49": "No review or delivery was executed here", + "annotation-50": "Source observation and image export are not execution receipts", + "annotation-51": "Library publishes meaning. Profile selects. Scenario binds this use.", + "annotation-52": "Questions / model / source observations / architecture", + "annotation-53": "Design candidate / no verification-pass claim" + }, + "themes": [ + "blueprint", + "compact" + ], + "variants": [ + { + "name": "sketchbook", + "themes": [ + "sketchbook", + "compact" + ] + }, + { + "name": "node-editor", + "themes": [ + "blueprint", + "node-editor", + "compact" + ] + }, + { + "name": "dependency-matrix", + "themes": [ + "blueprint", + "compact" + ], + "layout": "dependency-matrix" + }, + { + "name": "project-owned", + "kind": "responsibility-map", + "layout": "dependency-matrix", + "themes": [ + "field-notes", + "workbench", + "compact" + ], + "icons": [ + "engineering", + "repo" + ] + } + ], + "presentation": { + "matrix": [ + { + "node": "library", + "label": "Library" + }, + { + "node": "project.profile", + "label": "Profile" + }, + { + "node": "project.scenario", + "label": "Scenario" + } + ], + "observations": [ + { + "ref": "library", + "excerpt": "OrganizationDeclaration" + }, + { + "ref": "library", + "excerpt": "catalog-value" + }, + { + "ref": "library", + "excerpt": "mapping-value" + }, + { + "ref": "profile", + "excerpt": "coding-policy:" + }, + { + "ref": "profile", + "excerpt": "design-policy:" + }, + { + "ref": "profile", + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "ref": "scenario", + "excerpt": "(def (select-formation-profile" + }, + { + "ref": "capture-stop", + "excerpt": "status: 'capture-blocked" + }, + { + "ref": "capture-stop", + "excerpt": "capture: #f" + }, + { + "ref": "model", + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "ref": "model", + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] + }, + "icons": [ + "engineering" + ] + } + | + } + } +} +direction: right +library: Library +project: { + profile: Profile + scenario: Scenario + inputs: "Model / Org / decisions" +} +project.profile -> library: selects +project.scenario -> project.profile: binds +project.scenario -> library: references definitions +project.scenario -> project.inputs: binds actual project inputs diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.html b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.html new file mode 100644 index 0000000..cf69ab3 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.html @@ -0,0 +1,41 @@ +Library / Profile / Scenario architecture
+
Repository case / design view
DETERMINISTIC ENGINEERING / DE-CASE-01

One Library. Clear boundaries.

Shared definitions. Independent choices. Traceable project use.

+

1What does this Library make possible?

+

Publish once

Models, methods and policies
Reusable definitions from Library

+

Choose per project

Profile selects the needed capabilities
Original identities stay intact

+

Bind actual use

Scenario acquires project inputs
One use, with source associations

+

2Who publishes, selects and binds?Ownership / arrows point to dependencies

+
+ +
REUSABLE PUBLICATION BOUNDARY

Library

Definitions and contracts
Models / catalog / mapping
Coding and design-review policies
Method bindings and input demands
+
selects
+
PROJECT BOUNDARY / DE-CASE-01
+

Profile

Select parents, policies and methods
Reference catalog / mapping

+
binds
+

Scenario

Acquire actual inputs and origins
Retain choices, checks and gaps

+
Architecture modelOrg documentsDecisions
+ +
Model: project.sysml / a scoped responsibility viewASCENT reasoning and Aitia review retain separate owners
+

3Which responsibilities stay independent?

+

One definition owner

Selection and use reference definitions
Contract bodies are not copied

Selection keeps its scope

Retain parents and both policies
Bind actual published methods

Reuse existing admission

POO / Core own admission
Keep normalized return values

Keep review independent

Candidates, evidence and authority
Retain each source association

+

4How do models, docs and decisions connect?

Architecture modelproject.sysml / actual and target
Org design basisContracts, duties and acceptance
Decisions and reassessmentChoices, assumptions and triggers
Design view: issued C, accepted Q and independent review still need actual binding.
+

What does the current source show?

Library / Profile declarations are visible
Objects, both policies, catalog / mapping and method selection
Model and source references are retained
Each figure records captured file fingerprints and reference mappings

What is still blocked?

capture-blocked · capture = #f
Preparation still reports origin and method-footprint gaps
No review or delivery was executed here
Source observation and image export are not execution receipts
+
Library publishes meaning. Profile selects. Scenario binds this use.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.json b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.json new file mode 100644 index 0000000..bb4c1a7 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.json @@ -0,0 +1,157 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "e2b872efef44f39372eb52be5f7687c2357b6226a1414410b302ccbc43c7554c", + "composedHTMLSHA256": "be49feb8614917a29a63d92dcb36f869ba1e8358abf45f5b16736f4a1cf2de54", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "architecture": "7f61b44ee39d7e7acb65aeecc676488e6a2ad13f2a2e978dc540b126c38e5d18" + }, + "htmlSHA256": "d35548896157ae86aa26eea6d7da243ee0ef6c94018a07e657676a68d86b3546", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "b625cc0a207ad3ad0a699de1d745a28424ebb50d7035ef10604841f3af0e5715", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2988, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 138.578125 + }, + "purpose": { + "x": 26, + "y": 164.578125, + "width": 1068, + "height": 209 + }, + "architecture": { + "x": 26, + "y": 385.578125, + "width": 1068, + "height": 364 + }, + "principles": { + "x": 26, + "y": 761.578125, + "width": 1068, + "height": 218.171875 + }, + "products": { + "x": 26, + "y": 991.75, + "width": 1068, + "height": 177 + }, + "evidence": { + "x": 26, + "y": 1180.75, + "width": 1068, + "height": 194 + }, + "takeaway": { + "x": 26, + "y": 1374.75, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.png b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.png new file mode 100644 index 0000000..b80a4bd Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.html new file mode 100644 index 0000000..53e5d07 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.html @@ -0,0 +1 @@ +

DE-CASE-01 capture preparation workflow

sketchbook

workflow-sketchbook

node-editor

workflow-node-editor

swimlane

workflow-swimlane

project-owned

workflow-project-owned
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.png new file mode 100644 index 0000000..61d79c2 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-comparison.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-detail.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-detail.png new file mode 100644 index 0000000..46d9de7 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor-detail.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor-detail.png new file mode 100644 index 0000000..535c609 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.html new file mode 100644 index 0000000..4c3b734 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.html @@ -0,0 +1,25 @@ +DE-CASE-01 capture preparation workflow
+
Workflow / current stop
DETERMINISTIC ENGINEERING / DE-CASE-01

From project intent to traceable use

Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.

+

1How is a use prepared, and where does it stop?Control order / compact main path

The path shows preparation order; the current source outcome is marked separately.

1
Confirm intentOriginal intent + roles
2
Select ProfileParents / policies
3
Acquire inputsContent / origins / expiry
4
Check bindingScope / parents / origins
5
Prepare CRetain missing inputs
Current return: capture-blocked / capture = #f
+

2What crosses each boundary?

+
BoundaryInputOutput / retained associations
Project confirmationActual intent, account origin and role mandateOriginal sources required for verification; declarations do not authorize
Profile selectionValidated Scope, parent chain and both policiesSelections and actual bindings; retain normalized results
Scenario acquisitionRepository root and project input inventoryActual content, origin observations, lifetime and gaps
Capture preparationSelections, origin authority and method input demandsCurrent output is a blocked report; unique C is not issued
+

What must be checked before proceeding?

Parent and policy associations must agree
Select actual published method bindings
Acquire current project files, origins and lifetime

How are gaps and conflicts retained?

Incomplete original sources: retain origin findings
Missing method footprint: retain required inputs
Do not issue C or promote a candidate to accepted Q
+
Every step needs a basis. Every stop needs an explicit reason.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.json b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.json new file mode 100644 index 0000000..462b45d --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.json @@ -0,0 +1,145 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "df4de44dd388a0b50786ce3917d8c8c6984f9d159d1dca3534a95caa924e0cc8", + "composedHTMLSHA256": "d2a5fa25076f69016232408cb59a60cd0aa57141d196aa7d8af3c1c5e80bf464", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "workflow": "0affd71d0c8777220808552f5c35c98d443c5361c3a354a476da692ff471072a" + }, + "htmlSHA256": "ca0cb4ff849bf512592c9ae771cc6bebd4dcab16c3d3985eddddd60752a4ae5e", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "db56709dd44588f0b68a5ef68f1b146bb24cfa11d42f081214135ab609993f71", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2164, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 133.78125 + }, + "workflow": { + "x": 26, + "y": 159.78125, + "width": 1068, + "height": 322.15625 + }, + "exchange": { + "x": 26, + "y": 493.9375, + "width": 1068, + "height": 275 + }, + "checks": { + "x": 26, + "y": 780.9375, + "width": 1068, + "height": 181.5 + }, + "takeaway": { + "x": 26, + "y": 962.4375, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.png new file mode 100644 index 0000000..7b704b3 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-node-editor.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned-detail.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned-detail.png new file mode 100644 index 0000000..2a5dc74 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.html new file mode 100644 index 0000000..dd8d344 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.html @@ -0,0 +1,25 @@ +DE-CASE-01 capture preparation workflow
+
Workflow / current stop
DETERMINISTIC ENGINEERING / DE-CASE-01

From project intent to traceable use

Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.

+

1How is a use prepared, and where does it stop?Control order / compact main path

The path shows preparation order; the current source outcome is marked separately.

Project intent
1
Confirm intentOriginal intent + roles
Profile selection
2
Select ProfileParents / policies
Scenario preparation
3
Acquire inputsContent / origins / expiry
4
Check bindingScope / parents / origins
5
Prepare CRetain missing inputs
Current return: capture-blocked / capture = #f
+

2What crosses each boundary?

+
BoundaryInputOutput / retained associations
Project confirmationActual intent, account origin and role mandateOriginal sources required for verification; declarations do not authorize
Profile selectionValidated Scope, parent chain and both policiesSelections and actual bindings; retain normalized results
Scenario acquisitionRepository root and project input inventoryActual content, origin observations, lifetime and gaps
Capture preparationSelections, origin authority and method input demandsCurrent output is a blocked report; unique C is not issued
+

What must be checked before proceeding?

Parent and policy associations must agree
Select actual published method bindings
Acquire current project files, origins and lifetime

How are gaps and conflicts retained?

Incomplete original sources: retain origin findings
Missing method footprint: retain required inputs
Do not issue C or promote a candidate to accepted Q
+
Every step needs a basis. Every stop needs an explicit reason.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.json b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.json new file mode 100644 index 0000000..2b75704 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.json @@ -0,0 +1,145 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "4d025f6683786f2566e11f2631f3b5368b951911420915c2e01575872a4c381b", + "composedHTMLSHA256": "53a390413be58f32b644f1302f38bae2a64851f8b70b73d2f1617b4ccb7df052", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "workflow": "6ace530bf29056391eaaab83cd9734a8d93b07bb6efda17fedf674c42020fe3b" + }, + "htmlSHA256": "8126d93e2d2e9bd506ae52e69b678b5b8d896a38760988b3f9b020e5d04d76bb", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "f05898bb3cc37471a4b056d16a223758475968d5c83972853e1a8623f212c454", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2398, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 130.78125 + }, + "workflow": { + "x": 26, + "y": 156.78125, + "width": 1068, + "height": 444 + }, + "exchange": { + "x": 26, + "y": 612.78125, + "width": 1068, + "height": 274 + }, + "checks": { + "x": 26, + "y": 898.78125, + "width": 1068, + "height": 183.5 + }, + "takeaway": { + "x": 26, + "y": 1082.28125, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.png new file mode 100644 index 0000000..03b4da6 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-project-owned.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook-detail.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook-detail.png new file mode 100644 index 0000000..2ae1fb9 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.html new file mode 100644 index 0000000..964dee5 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.html @@ -0,0 +1,26 @@ +DE-CASE-01 capture preparation workflow
+
Workflow / current stop
DETERMINISTIC ENGINEERING / DE-CASE-01

From project intent to traceable use

Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.

+

1How is a use prepared, and where does it stop?Control order / compact main path

The path shows preparation order; the current source outcome is marked separately.

1
Confirm intentOriginal intent + roles
2
Select ProfileParents / policies
3
Acquire inputsContent / origins / expiry
4
Check bindingScope / parents / origins
5
Prepare CRetain missing inputs
Current return: capture-blocked / capture = #f
+

2What crosses each boundary?

+
BoundaryInputOutput / retained associations
Project confirmationActual intent, account origin and role mandateOriginal sources required for verification; declarations do not authorize
Profile selectionValidated Scope, parent chain and both policiesSelections and actual bindings; retain normalized results
Scenario acquisitionRepository root and project input inventoryActual content, origin observations, lifetime and gaps
Capture preparationSelections, origin authority and method input demandsCurrent output is a blocked report; unique C is not issued
+

What must be checked before proceeding?

Parent and policy associations must agree
Select actual published method bindings
Acquire current project files, origins and lifetime

How are gaps and conflicts retained?

Incomplete original sources: retain origin findings
Missing method footprint: retain required inputs
Do not issue C or promote a candidate to accepted Q
+
Every step needs a basis. Every stop needs an explicit reason.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.json b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.json new file mode 100644 index 0000000..84cc6e0 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.json @@ -0,0 +1,148 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "1faf89bbbbb8e325109750caf57189b00056d36912805d199382cba493a8a0ee", + "composedHTMLSHA256": "46cd78508375c6ed0f1333ea66e76cfcd1b8f0671a1c125cf025338f234fe842", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "workflow": "74070938fae301202764372bb238afa9072651962e19019f7f0569ad0c47c890" + }, + "htmlSHA256": "d060b7cb375c43b709895fa43a0098f8fb6983c10a9a4c9059b8970e6b16b961", + "drawing": "roughjs@4.6.6", + "routing": "xyflow@0.0.83", + "assetSHA256": { + "@fontsource/kalam/files/kalam-latin-700-normal.woff2": "252063af6ade8b9a744cde4ddad0fc21ea53b8ba711eed121a0c2e8610ea9c93", + "@fontsource/kalam/files/kalam-latin-400-normal.woff2": "954410601a823f37e219f7930b7446f86afa15621326a7078d56fb9c910135cb" + }, + "pngSHA256": "257dd777938f3accfca5ddabfb115e19d8ccd100ed401e93e765f0b0e11c9e92", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2044, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 136.78125 + }, + "workflow": { + "x": 26, + "y": 162.78125, + "width": 1068, + "height": 254.765625 + }, + "exchange": { + "x": 26, + "y": 429.546875, + "width": 1068, + "height": 277 + }, + "checks": { + "x": 26, + "y": 718.546875, + "width": 1068, + "height": 180.5 + }, + "takeaway": { + "x": 26, + "y": 899.046875, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.png new file mode 100644 index 0000000..289491a Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-sketchbook.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane-detail.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane-detail.png new file mode 100644 index 0000000..4bdea81 Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane-detail.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.html new file mode 100644 index 0000000..dce8b0c --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.html @@ -0,0 +1,25 @@ +DE-CASE-01 capture preparation workflow
+
Workflow / current stop
DETERMINISTIC ENGINEERING / DE-CASE-01

From project intent to traceable use

Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.

+

1How is a use prepared, and where does it stop?Control order / compact main path

The path shows preparation order; the current source outcome is marked separately.

Project intent
1
Confirm intentOriginal intent + roles
Profile selection
2
Select ProfileParents / policies
Scenario preparation
3
Acquire inputsContent / origins / expiry
4
Check bindingScope / parents / origins
5
Prepare CRetain missing inputs
Current return: capture-blocked / capture = #f
+

2What crosses each boundary?

+
BoundaryInputOutput / retained associations
Project confirmationActual intent, account origin and role mandateOriginal sources required for verification; declarations do not authorize
Profile selectionValidated Scope, parent chain and both policiesSelections and actual bindings; retain normalized results
Scenario acquisitionRepository root and project input inventoryActual content, origin observations, lifetime and gaps
Capture preparationSelections, origin authority and method input demandsCurrent output is a blocked report; unique C is not issued
+

What must be checked before proceeding?

Parent and policy associations must agree
Select actual published method bindings
Acquire current project files, origins and lifetime

How are gaps and conflicts retained?

Incomplete original sources: retain origin findings
Missing method footprint: retain required inputs
Do not issue C or promote a candidate to accepted Q
+
Every step needs a basis. Every stop needs an explicit reason.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.json b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.json new file mode 100644 index 0000000..86fde00 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.json @@ -0,0 +1,145 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "934e3d38b160a4e052a36326686824a5f56e5876547783b6db4fb8ea3fffbaef", + "composedHTMLSHA256": "677d83946269348c9c06092ecd0f16e93519f89a2db4194f209305e5d1b5c1ab", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "workflow": "6d430ade2618c98a8628abcb63c17c72a61f1629cf5e846dd6b06edb1081c89b" + }, + "htmlSHA256": "64b276352bbd58217eacce6488ea5af88fa372750d7623bc5f1fc3879c3cf350", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "cecc5a8ba73435572722364ca28ffe58c45566f5d29853a53847d8b566e7206e", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2410, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 133.78125 + }, + "workflow": { + "x": 26, + "y": 159.78125, + "width": 1068, + "height": 445 + }, + "exchange": { + "x": 26, + "y": 616.78125, + "width": 1068, + "height": 275 + }, + "checks": { + "x": 26, + "y": 903.78125, + "width": 1068, + "height": 181.5 + }, + "takeaway": { + "x": 26, + "y": 1085.28125, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.png new file mode 100644 index 0000000..42b1b6f Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow-swimlane.png differ diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2 b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2 new file mode 100644 index 0000000..10ad02b --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2 @@ -0,0 +1,218 @@ +# Declared Questions and content for the actual DE-CASE-01 case. +# Source observations only; no issued C, accepted Q or review R. +vars: { + d2-config: { + layout-engine: elk + data: { + # Application declarations travel through the official graph.data interface. + figure: |json + { + "title": "DE-CASE-01 capture preparation workflow", + "purpose": "workflow", + "kind": "flow", + "language": "en", + "width": 1120, + "primary": "progress", + "questions": [ + { + "ref": "progress", + "text": "How is a use prepared, and where does it stop?", + "focusRefs": [ + "profile", + "scenario", + "capture-stop" + ] + }, + { + "ref": "exchange", + "text": "What crosses each boundary?", + "focusRefs": [ + "profile", + "scenario", + "types" + ] + }, + { + "ref": "stops", + "text": "What must be checked before proceeding?", + "focusRefs": [ + "scenario", + "capture-stop" + ] + } + ], + "references": { + "library": "library/project-organization/objects.ss", + "profile": "profiles/library-formation.ss", + "scenario": "scenarios/project-formation/func.ss", + "capture-stop": "scenarios/project-formation/func.ss", + "model": "project.sysml", + "documents": "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org", + "decisions": "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org", + "types": "scenarios/project-formation/types.ss" + }, + "basis": { + "captureRef": "design-candidate:DE-CASE-01/rendering", + "modelRefs": [ + "project.sysml" + ], + "obligationRefs": [ + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org" + ], + "evidenceRefs": [] + }, + "copy": { + "badge": "Workflow / current stop", + "eyebrow": "DETERMINISTIC ENGINEERING / DE-CASE-01", + "title": "From project intent to ", + "emphasis": "traceable use", + "annotation-01": "Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.", + "annotation-02": "Control order / compact main path", + "annotation-03": "The path shows preparation order; the current source outcome is marked separately.", + "confirm-label": "Confirm intent", + "annotation-04": "Original intent + roles", + "select-label": "Select Profile", + "annotation-05": "Parents / policies", + "acquire-label": "Acquire inputs", + "annotation-06": "Content / origins / expiry", + "check-label": "Check binding", + "annotation-07": "Scope / parents / origins", + "prepare-label": "Prepare C", + "annotation-08": "Retain missing inputs", + "annotation-09": "Current return: capture-blocked / capture = #f", + "annotation-10": "Boundary", + "annotation-11": "Input", + "annotation-12": "Output / retained associations", + "annotation-13": "Project confirmation", + "annotation-14": "Actual intent, account origin and role mandate", + "annotation-15": "Original sources required for verification; declarations do not authorize", + "annotation-16": "Profile selection", + "annotation-17": "Validated Scope, parent chain and both policies", + "annotation-18": "Selections and actual bindings; retain normalized results", + "annotation-19": "Scenario acquisition", + "annotation-20": "Repository root and project input inventory", + "annotation-21": "Actual content, origin observations, lifetime and gaps", + "annotation-22": "Capture preparation", + "annotation-23": "Selections, origin authority and method input demands", + "annotation-24": "Current output is a blocked report; unique C is not issued", + "annotation-25": "Parent and policy associations must agree", + "annotation-26": "Select actual published method bindings", + "annotation-27": "Acquire current project files, origins and lifetime", + "annotation-28": "How are gaps and conflicts retained?", + "annotation-29": "Incomplete original sources: retain origin findings", + "annotation-30": "Missing method footprint: retain required inputs", + "annotation-31": "Do not issue C or promote a candidate to accepted Q", + "annotation-32": "Every step needs a basis. Every stop needs an explicit reason.", + "annotation-33": "Basis: Profile and prepare-formation-capture source / no execution acceptance", + "annotation-34": "Workflow / separate from architecture" + }, + "themes": [ + "blueprint", + "compact" + ], + "variants": [ + { + "name": "sketchbook", + "themes": [ + "sketchbook", + "compact" + ] + }, + { + "name": "node-editor", + "themes": [ + "blueprint", + "node-editor", + "compact" + ] + }, + { + "name": "swimlane", + "themes": [ + "blueprint", + "compact" + ], + "layout": "swimlane" + }, + { + "name": "project-owned", + "kind": "capture-handoff", + "layout": "swimlane", + "themes": [ + "field-notes", + "workbench", + "compact" + ], + "icons": [ + "engineering", + "repo" + ] + } + ], + "icons": [ + "engineering" + ], + "presentation": { + "flow": { + "lanes": [ + { + "id": "intent", + "label": "Project intent" + }, + { + "id": "profile", + "label": "Profile selection" + }, + { + "id": "scenario", + "label": "Scenario preparation" + } + ], + "steps": [ + { + "node": "confirmation", + "lane": "intent", + "title": "confirm-label", + "detail": "annotation-04", + "icon": "question" + }, + { + "node": "selection", + "lane": "profile", + "title": "select-label", + "detail": "annotation-05", + "icon": "selection" + }, + { + "node": "acquisition", + "lane": "scenario", + "title": "acquire-label", + "detail": "annotation-06", + "icon": "document" + }, + { + "node": "association", + "lane": "scenario", + "title": "check-label", + "detail": "annotation-07", + "icon": "reference" + }, + { + "node": "preparation", + "lane": "scenario", + "title": "prepare-label", + "detail": "annotation-08", + "icon": "capture" + } + ] + } + } + } + | + } + } +} +direction: right +confirmation -> selection -> acquisition -> association -> preparation +preparation -> blocked: current source outcome +blocked: "capture-blocked / capture = #f" diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.html b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.html new file mode 100644 index 0000000..d029b19 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.html @@ -0,0 +1,25 @@ +DE-CASE-01 capture preparation workflow
+
Workflow / current stop
DETERMINISTIC ENGINEERING / DE-CASE-01

From project intent to traceable use

Confirm origins, select and bind. Retain inputs, outputs and open items at each boundary.

+

1How is a use prepared, and where does it stop?Control order / compact main path

The path shows preparation order; the current source outcome is marked separately.

1
Confirm intentOriginal intent + roles
2
Select ProfileParents / policies
3
Acquire inputsContent / origins / expiry
4
Check bindingScope / parents / origins
5
Prepare CRetain missing inputs
Current return: capture-blocked / capture = #f
+

2What crosses each boundary?

+
BoundaryInputOutput / retained associations
Project confirmationActual intent, account origin and role mandateOriginal sources required for verification; declarations do not authorize
Profile selectionValidated Scope, parent chain and both policiesSelections and actual bindings; retain normalized results
Scenario acquisitionRepository root and project input inventoryActual content, origin observations, lifetime and gaps
Capture preparationSelections, origin authority and method input demandsCurrent output is a blocked report; unique C is not issued
+

What must be checked before proceeding?

Parent and policy associations must agree
Select actual published method bindings
Acquire current project files, origins and lifetime

How are gaps and conflicts retained?

Incomplete original sources: retain origin findings
Missing method footprint: retain required inputs
Do not issue C or promote a candidate to accepted Q
+
Every step needs a basis. Every stop needs an explicit reason.
\ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.json b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.json new file mode 100644 index 0000000..df5c060 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.json @@ -0,0 +1,145 @@ +{ + "status": "design explanation / source observations only", + "inputSHA256": "d15931e117230b8d46a9bb85d65898975cb4416c75f7c461040a045a5b01ce9c", + "composedHTMLSHA256": "4145e5444f18f6e544e9c6742ccf7c148b05b2c77cf44d02f30c0365d8397034", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "regionSHA256": { + "workflow": "e509d65947c25459579bff4310d278f68f553402994b4ec07c988f9855466bed" + }, + "htmlSHA256": "9f4665ed2b8fe16409b8084423c1239aa0a5faed08df223dc62aefecc749eb75", + "drawing": "precise", + "routing": "xyflow@0.0.83", + "assetSHA256": {}, + "pngSHA256": "badb94fc955f235131fc4e6277f05c28f08ee237af2ef556e3e43acfe7097fb9", + "browser": "156.0.8078.4", + "width": 2240, + "height": 2068, + "scale": 2, + "bounds": { + "header": { + "x": 26, + "y": 26, + "width": 1068, + "height": 133.78125 + }, + "workflow": { + "x": 26, + "y": 159.78125, + "width": 1068, + "height": 274.15625 + }, + "exchange": { + "x": 26, + "y": 445.9375, + "width": 1068, + "height": 275 + }, + "checks": { + "x": 26, + "y": 732.9375, + "width": 1068, + "height": 181.5 + }, + "takeaway": { + "x": 26, + "y": 914.4375, + "width": 1068, + "height": 68 + } + }, + "sources": { + "docs/10-19-design/10-architecture/10.31-architecture-document-and-decision-contracts.org": "33bc8437e78568026a0a2e461d443b308244140c9361adb284255124b7d4cb0a", + "docs/10-19-design/10-architecture/10.34-case-design-review-dossier-draft.org": "0ebcb141c89ec38a2ec9939afbef25f155cbd928a873808a4cac06b8fc1caef8", + "docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2": "7e7d13cfad3dac6a24c943ce465432adf41751458117956f6914fa6086b71aab", + "docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2": "07af3168b220178a5a1fd4f07c908b817e5b924bedab28c1ba1f3cce7d7c002d", + "examples/render/comparison.ts": "3fd41421e40c32a19d2aeca722cf5fd9601a0d7fb83f32e2e0d42ba706f5ebae", + "examples/render/library-case.ts": "c0809672849237329fbf400ebeff6f85c45db27b201a0cb6592cb11772f7c0ea", + "examples/render/library-case/architecture-diagram.ts": "1151bf97fa72974ec845b8c767be8288eaf1070de136f4bf7d0e7ac3fd0f0c7c", + "examples/render/library-case/architecture.ts": "bbf4461173240c31af097e3ee8140dd866fe41be700ac4b8fb135df7db51f370", + "examples/render/library-case/dependency-matrix.ts": "834be569f0e97109668abe6da87f4a24828b3bdd1aa4ca35cf6268a0f7e49019", + "examples/render/library-case/resources.ts": "a3e317c3a0db65c1b20d97c25deee9c096a202408d18e224d3df24a1fcefeaef", + "examples/render/library-case/style.ts": "53f61a137c1c3a117b019f87a9dcf680dedcba8ce13104260490f0a1fab75121", + "examples/render/library-case/workflow-declaration.ts": "97f1bbfaa4f8fcbc98d495fabb87594d9d855dd0e2046bd1f6d9371b8d0d596a", + "examples/render/library-case/workflow-diagram.ts": "3006610f01901c548162b1d321bbde6c4422b266a8c66f15f8fb7df8510d5dd6", + "examples/render/library-case/workflow.ts": "451e1983e491c87232aed23deea19a56b380917d83ec06d1d8dd12b386ff63fa", + "library/project-organization/objects.ss": "6fa8c3559a36fdca255b0ad36384c2d3bde4b9e76bda14990ffcb670fd26e62b", + "profiles/library-formation.ss": "47911fb8d2b9267898dfbe5d96d3f04f32393c4eec9b8f91e771102495f2fac5", + "project.sysml": "2302803c97f52bfdf7c2aee6320f42c7bf0a4291c1a240a2efe331545c41d885", + "scenarios/project-formation/func.ss": "149a749fd7c119b99f078e763b83698627c55f04f2c8c931b3d04bec9a9da8f2", + "scenarios/project-formation/types.ss": "ccda31e92c5c8232cc04a48af36ec232de2574ce75ab8891e1dd3b4ca36ad810" + }, + "observations": [ + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 6, + "excerpt": "OrganizationDeclaration" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 24, + "excerpt": "catalog-value" + }, + { + "kind": "source-excerpt-observation", + "source": "library/project-organization/objects.ss", + "line": 37, + "excerpt": "mapping-value" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 43, + "excerpt": "coding-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 44, + "excerpt": "design-policy:" + }, + { + "kind": "source-excerpt-observation", + "source": "profiles/library-formation.ss", + "line": 45, + "excerpt": "resource-methods: '(native quint lean integration)" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 128, + "excerpt": "(def (select-formation-profile" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 197, + "excerpt": "status: 'capture-blocked" + }, + { + "kind": "source-excerpt-observation", + "source": "scenarios/project-formation/func.ss", + "line": 212, + "excerpt": "capture: #f" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 24, + "excerpt": "part 'selection' : 'IndependentProfile'" + }, + { + "kind": "source-excerpt-observation", + "source": "project.sysml", + "line": 23, + "excerpt": "part 'scenarioUse' : 'ProjectScenario'" + } + ] +} \ No newline at end of file diff --git a/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.png b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.png new file mode 100644 index 0000000..4b13bce Binary files /dev/null and b/docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.png differ diff --git a/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.d2 b/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.d2 new file mode 100644 index 0000000..d631b64 --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.d2 @@ -0,0 +1,58 @@ +vars: {d2-config: {layout-engine: elk; data: {figure: |json +{ + "title": "Static renderer responsibility boundaries", + "purpose": "architecture", + "kind": "library-dependency-map", + "language": "en", + "width": 1120, + "primary": "owners", + "questions": [ + { + "ref": "owners", + "text": "Which responsibilities belong to the project, render core and mature libraries?", + "focusRefs": [ + "source" + ] + } + ], + "references": { + "source": "docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.d2" + }, + "basis": { + "captureRef": "design-candidate:render-audit", + "modelRefs": [], + "obligationRefs": [ + "docs/10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org" + ], + "evidenceRefs": [] + }, + "copy": {} +} +| +}}} +direction: right +project: Project-owned { + declarations: D2 / SysML / Questions / observations + resources: Themes / icons / selected variants + composer: Domain composition / presentation schema +} +core: Render Library { + compile: Declared input / resource resolution + compose: Immutable capture / pure composition + export: Static export / input and visibility checks +} +libraries: Mature libraries { + d2: D2 compiler / layout / SVG + html: parse5 HTML parser + rough: Rough.js Options / primitives + xyflow: XYFlow path utilities + browser: Playwright browser / fonts / PNG +} +project.composer -> project.declarations: interprets +project.composer -> core.compose: uses +core.compile -> project.resources: resolves +core.compile -> libraries.d2: delegates +core.compose -> libraries.html: parses +core.export -> libraries.rough: strokes +core.export -> libraries.xyflow: connectors +core.export -> libraries.browser: renders diff --git a/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.svg b/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.svg new file mode 100644 index 0000000..4f604ac --- /dev/null +++ b/docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.svg @@ -0,0 +1,116 @@ +Project-ownedRender LibraryMature librariesD2 / SysML / Questions / observationsThemes / icons / selected variantsDomain composition / presentation schemaDeclared input / resource resolutionImmutable capture / pure compositionStatic export / input and visibility checksD2 compiler / layout / SVGparse5 HTML parserRough.js Options / primitivesXYFlow path utilitiesPlaywright browser / fonts / PNG interpretsusesresolvesdelegatesparsesstrokesconnectorsrenders + + + + + + + + + + \ No newline at end of file diff --git a/examples/render/bun.lock b/examples/render/bun.lock new file mode 100644 index 0000000..f1d4254 --- /dev/null +++ b/examples/render/bun.lock @@ -0,0 +1,248 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "deterministic-engineering-render-case", + "dependencies": { + "@deterministic-engineering/render": "file:../../packages/render", + }, + "devDependencies": { + "@types/bun": "1.4.4", + "playwright": "1.64.1", + "typescript": "7.0.2", + }, + }, + }, + "packages": { + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.14.0", "", {}, "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w=="], + + "@connectrpc/connect": ["@connectrpc/connect@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0" } }, "sha512-MXkBijtcX09R10Eb6sFeIetc6w6746eio6xtfuyVOH7oQAacT1X0GzMIQFux6Qy8cq3W/T5qX5Bei8YbFtmRGA=="], + + "@connectrpc/connect-node": ["@connectrpc/connect-node@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", "@connectrpc/connect": "2.1.2" } }, "sha512-+i/aAOpsI8sIx1mbYp6d99zvxaUSF6t/jP9Ux9maAmjsZPgmIQ3JuIeYi0zJIP9zlCnBlJjkpPosshCgdRuThQ=="], + + "@connectrpc/connect-web": ["@connectrpc/connect-web@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", "@connectrpc/connect": "2.1.2" } }, "sha512-1tfaK85MU+gJjwwmL31d2rzdf0XCYX99chZf63uG89SGBUd4XuZ4ZzhGo2u79TPXOE6nLIZQ2okrpyey42PYdg=="], + + "@deterministic-engineering/render": ["@deterministic-engineering/render@file:../../packages/render", { "dependencies": { "@fontsource/kalam": "5.3.0", "@terrastruct/d2": "0.1.33", "@xyflow/system": "0.0.83", "parse5": "8.0.1", "roughjs": "4.6.6" }, "devDependencies": { "@openmbee/opensysml": "0.11.0", "@types/bun": "1.4.4", "fast-check": "4.10.2", "playwright": "1.64.1", "typescript": "7.0.2" }, "peerDependencies": { "@openmbee/opensysml": "0.11.0", "playwright": "1.64.1" }, "optionalPeers": ["@openmbee/opensysml", "playwright"] }], + + "@fontsource/kalam": ["@fontsource/kalam@5.3.0", "", {}, "sha512-FDkVoDfPDCSN/eO81FOYtUI67eMgpVBJmjBtAqSL0PmEhJyeFY2biC+CR+yEttksgXR9+swpFdYmQTyjwBq80Q=="], + + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + + "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@openmbee/opensysml": ["@openmbee/opensysml@0.11.0", "", { "dependencies": { "@bufbuild/protobuf": "2.14.0", "@connectrpc/connect": "2.1.2", "@connectrpc/connect-node": "2.1.2", "@connectrpc/connect-web": "2.1.2" }, "optionalDependencies": { "@openmbee/opensysml-sysml-grpc-darwin-arm64": "0.11.0", "@openmbee/opensysml-sysml-grpc-darwin-x64": "0.11.0", "@openmbee/opensysml-sysml-grpc-linux-arm64": "0.11.0", "@openmbee/opensysml-sysml-grpc-linux-x64": "0.11.0", "@openmbee/opensysml-sysml-grpc-win32-x64": "0.11.0", "@sigstore/bundle": "4.0.0", "@sigstore/protobuf-specs": "0.5.1", "@sigstore/tuf": "4.0.2", "@sigstore/verify": "3.1.1" }, "peerDependencies": { "@openmbee/opensysml-wasm": "0.11.0" }, "optionalPeers": ["@openmbee/opensysml-wasm"], "bin": { "opensysml-generate": "dist/node/generate-cli.js" } }, "sha512-W9VtruoAbxV2RuKuqIVHCdsv5efgNPnCOktLK4M5nNhNrvyckR/AsenX+mmxG+Ry9r0RnNB/UKIcVS3gQHgLbg=="], + + "@openmbee/opensysml-sysml-grpc-darwin-arm64": ["@openmbee/opensysml-sysml-grpc-darwin-arm64@0.11.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-82KnOccVp765a8Yza4TvTnnayVpZlL81jkUyu7dD4o12imeM+UKZE6babI8JQmnL5JdrznNxg6miUO2BoMDS8w=="], + + "@openmbee/opensysml-sysml-grpc-darwin-x64": ["@openmbee/opensysml-sysml-grpc-darwin-x64@0.11.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fp4YBfzUZGDng2bIGHJbFs7edaKlUQXQlzC7d0x4UQ8Yf+v0Z9Bb2DevtfKjW9COcsUlsJcObbUGPwF2I7rdRQ=="], + + "@openmbee/opensysml-sysml-grpc-linux-arm64": ["@openmbee/opensysml-sysml-grpc-linux-arm64@0.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pkv9ptdZy1L6DRgh3rWia/bYdNKaMBR8CSLOPIVwBwhQTpXdfoVcHZ/ZaKZjXx0VOSFxwE/my8wQsf95ygw1fA=="], + + "@openmbee/opensysml-sysml-grpc-linux-x64": ["@openmbee/opensysml-sysml-grpc-linux-x64@0.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-VTXZF52YiEZFuMBeuYS76PTwKEeuDF4wHdEjHRUbsTCMuVoo8IYDFtkf1+IysZ3PV0efeq67sGVAjLN37DJQpQ=="], + + "@openmbee/opensysml-sysml-grpc-win32-x64": ["@openmbee/opensysml-sysml-grpc-win32-x64@0.11.0", "", { "os": "win32", "cpu": "x64" }, "sha512-1/3fOoozKNouzGHCEnEyDLgioOaynF2Evpn3uw21AZopxQ+pzRA9h5wijtKcP0diz7w+OFa5Zz1E3pF+sOUANA=="], + + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.1", "", {}, "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + + "@terrastruct/d2": ["@terrastruct/d2@0.1.33", "", {}, "sha512-eK5hyfGIJFolC7sUsiKvWdY9xGFctTe3d+PSijo09IYDso8psztC+A4SammizXtlwYZpnnW0AtDjfBYauceSeA=="], + + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + + "@types/bun": ["@types/bun@1.4.4", "", { "dependencies": { "bun-types": "1.4.4" } }, "sha512-9wkRDXUXK124pEnAutE6n/l18j+i9GC5IAHK8TmX/2nlySETmky0OU6yL5U8pW1AoKh++AN9auDCr647BfurjQ=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.12", "", {}, "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.9", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA=="], + + "@types/node": ["@types/node@26.6.6", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@xyflow/system": ["@xyflow/system@0.0.83", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-a//eDjez3WZF47aaPTfVUDD0OINOq+WVwBZCRTqG+m0a5euX8EwlJ4jMY1Wxz0Ocj4xOeekJ70V22Pq0ll8RJw=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "bun-types": ["bun-types@1.4.4", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-yjrJh5oGX7mMPKGd19YXyIjmEuj4G4zNC30bs7PM9Az7NQ3ndF3j6hbeRdvJTRQEhJ/FYQwW0a2moweEi2hMxQ=="], + + "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "entities": ["entities@8.1.0", "", {}, "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA=="], + + "fast-check": ["fast-check@4.10.2", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "http-cache-semantics": ["http-cache-semantics@4.3.0", "", {}, "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + + "ip-address": ["ip-address@10.7.5", "", {}, "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA=="], + + "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], + + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + + "p-map": ["p-map@7.1.1", "", {}, "sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g=="], + + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "playwright": ["playwright@1.64.1", "", { "dependencies": { "playwright-core": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA=="], + + "playwright-core": ["playwright-core@1.64.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + + "undici-types": ["undici-types@8.12.3", "", {}, "sha512-EXE4ydL3S76dFb27UFjNVZHTyv/TLdATj/KUe29ptdcg6qXL48J2tZ3126E5kICj5jn781OR+XlVH1N6Mz8jbA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + } +} diff --git a/examples/render/comparison.ts b/examples/render/comparison.ts new file mode 100644 index 0000000..8255071 --- /dev/null +++ b/examples/render/comparison.ts @@ -0,0 +1,36 @@ +import {readFile, writeFile} from 'node:fs/promises'; +import {fileURLToPath} from 'node:url'; +import path from 'node:path'; +import {createHash} from 'node:crypto'; +import {chromium} from 'playwright'; +import {resources} from './library-case/resources'; +import {compileFigureSource} from '@deterministic-engineering/render'; +const root = fileURLToPath(new URL('../../', import.meta.url)); +const directory = path.join(root, '.cache/render/library-case'); +const files = ['docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2', 'docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2']; +const sources = Object.fromEntries(await Promise.all(files.map(async file => [file, await readFile(path.join(root, file), 'utf8')]))); +const declarations = await Promise.all(files.map(file => compileFigureSource(sources, file, resources))); +const esc = (s: string) => s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!)); +const hash = (data: Uint8Array|string) => createHash('sha256').update(data).digest('hex'); +const browser = await chromium.launch(); +try { + for (const declaration of declarations) { + const purpose = declaration.view.purpose; + const previews = await Promise.all((declaration.variants ?? []).map(async variant => { + const name = `${purpose}-${variant.name}`, png = await readFile(path.join(directory, `${name}-detail.png`)); + const receipt: {regionSHA256: Record; sources: Record} = JSON.parse(await readFile(path.join(directory, `${name}.json`), 'utf8')); + if (receipt.regionSHA256[purpose] !== hash(png)) throw new Error('Comparison region differs from rendering receipt'); + for (const [source, fingerprint] of Object.entries(receipt.sources)) if (hash(await readFile(path.join(root, source))) !== fingerprint) throw new Error(`Stale comparison source: ${source}`); + return `

${esc(variant.name)}

${esc(name)}
`; + })); + const html = `

${esc(declaration.view.title)}

${previews.join('')}
`; + const page = await browser.newPage({viewport:{width:1600,height:1000},deviceScaleFactor:1}); + try { + await page.setContent(html); await page.evaluate(async () => { await document.fonts.ready; await Promise.all(Array.from(document.images, image => image.decode())); }); + const png = await page.locator('main').screenshot({type:'png'}); + await writeFile(path.join(directory, `${purpose}-comparison.html`), html); + await writeFile(path.join(directory, `${purpose}-comparison.png`), png); + console.log(`${purpose}-comparison: ${png.readUInt32BE(16)}x${png.readUInt32BE(20)}`); + } finally {await page.close();} + } +} finally {await browser.close();} diff --git a/examples/render/library-case.ts b/examples/render/library-case.ts new file mode 100644 index 0000000..24ebabc --- /dev/null +++ b/examples/render/library-case.ts @@ -0,0 +1,59 @@ +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import { chromium } from 'playwright'; +import { composeFigure, compileFigureSource, selectFigureVariant, type FigureInput } from '@deterministic-engineering/render'; +import { exportFigure } from '@deterministic-engineering/render/export'; +import {resources} from './library-case/resources'; +import { architecture } from './library-case/architecture'; +import { workflow } from './library-case/workflow'; + +const root = fileURLToPath(new URL('../../', import.meta.url)); +const declarationFiles = ['docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2', 'docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2']; +const d2Sources = Object.fromEntries(await Promise.all(declarationFiles.map(async file => [file, await readFile(path.join(root, file), 'utf8')]))); +const [architectureDeclaration, workflowDeclaration] = await Promise.all(declarationFiles.map(file => compileFigureSource(d2Sources, file, resources))); +if (!architectureDeclaration || !workflowDeclaration) throw new Error('Missing D2 declarations'); +const references = architectureDeclaration.references; +const sourceFor = (ref: string): string => { const value = references[ref]; if (!value) throw new Error(`Undeclared source: ${ref}`); return value; }; +const compositionFiles = ['examples/render/library-case.ts', 'examples/render/library-case/architecture.ts', 'examples/render/library-case/architecture-diagram.ts', 'examples/render/library-case/dependency-matrix.ts', 'examples/render/library-case/workflow.ts', 'examples/render/library-case/workflow-diagram.ts', 'examples/render/library-case/workflow-declaration.ts', 'examples/render/library-case/resources.ts', 'examples/render/library-case/style.ts', 'examples/render/comparison.ts']; +const sourcePaths = [...new Set([...Object.values(references), ...Object.values(workflowDeclaration.references), ...compositionFiles, ...declarationFiles])]; +const sources = Object.fromEntries(await Promise.all(sourcePaths.map(async file => [file, await readFile(path.join(root, file), 'utf8')]))); +// Explicit source-excerpt observations, not a Scheme/SysML parser, execution or C issuance. +const presentation = architectureDeclaration.presentation; +if (!presentation || typeof presentation !== 'object' || !('observations' in presentation) || !Array.isArray(presentation.observations) || !presentation.observations.length) throw new Error('Case source observations must be declared'); +const anchors = presentation.observations.map((value: unknown) => { + if (!value || typeof value !== 'object' || !('ref' in value) || !('excerpt' in value) || typeof value.ref !== 'string' || typeof value.excerpt !== 'string' || !value.excerpt.trim()) throw new Error('Invalid declared source observation'); + return [value.ref, value.excerpt] as const; +}); +const observations = anchors.map(([ref, excerpt]) => { + const file = sourceFor(ref), source = sources[file]!; + const index = source.indexOf(excerpt); + if (index < 0) throw new Error(`Case source changed; update its explanation: ${file} / ${excerpt}`); + return {kind: 'source-excerpt-observation', source: file, line: source.slice(0, index).split('\n').length, excerpt}; +}); +const architectureInput: FigureInput = {...architectureDeclaration, sources}; +const workflowInput: FigureInput = {...workflowDeclaration, sources}; +const candidates = [ + {name: 'architecture', figure: composeFigure(architectureInput, architecture)}, + {name: 'workflow', figure: composeFigure(workflowInput, workflow)}, + ...[{input: architectureInput, composer: architecture, prefix: 'architecture'}, {input: workflowInput, composer: workflow, prefix: 'workflow'}].flatMap(({input, composer, prefix}) => (input.variants ?? []).map(variant => { + if (!/^[a-z0-9-]+$/.test(variant.name)) throw new Error('Invalid variant file name'); + return {name: `${prefix}-${variant.name}`, figure: composeFigure(selectFigureVariant(input, variant.name), composer)}; + })), +]; +const output = path.join(root, '.cache/render/library-case'); +await mkdir(output, {recursive: true}); +const browser = await chromium.launch(); +try { + for (const {name, figure} of candidates) { + const region = figure.purpose === 'architecture' ? 'architecture' : 'workflow'; + const image = await exportFigure(figure, browser, {scale: 2, regions: [region]}); + for (const file of sourcePaths) if (await readFile(path.join(root, file), 'utf8') !== sources[file]) throw new Error(`Source changed during export: ${file}`); + await writeFile(path.join(output, `${name}.html`), image.html); + await writeFile(path.join(output, `${name}.png`), image.png); + await writeFile(path.join(output, `${name}-detail.png`), image.regionImages[region]!); + await writeFile(path.join(output, `${name}.json`), JSON.stringify({status: 'design explanation / source observations only', ...image.receipt, sources: figure.sourceSHA256, observations}, null, 2)); + console.log(`${name}: ${image.receipt.width}x${image.receipt.height}; browser ${image.receipt.browser}`); + } +} finally { await browser.close(); } +console.log(`Editable HTML and browser-exported PNG: ${output}`); diff --git a/examples/render/library-case/architecture-diagram.ts b/examples/render/library-case/architecture-diagram.ts new file mode 100644 index 0000000..e7df0dd --- /dev/null +++ b/examples/render/library-case/architecture-diagram.ts @@ -0,0 +1,18 @@ +import type {FigureContext} from '@deterministic-engineering/render'; +import {dependencyMatrix} from './dependency-matrix'; +const ownership = ({reference: ref, icon, text, copy, relation}: FigureContext) => `
+ +
${icon('reference')}${text(copy("library-boundary"))}
${icon('library')}

${text(copy("library-label"))}

${text(copy("annotation-09"))}
${icon('model')}${text(copy("annotation-10"))}
${icon('obligation')}${text(copy("annotation-11"))}
${icon('document')}${text(copy("annotation-12"))}
+
${text(relation('project.profile', 'library').label)}
+
${icon('model')}${text(copy("project-boundary"))}
+
${icon('selection')}

${text(copy("profile-label"))}

${text(copy("annotation-14"))}
${text(copy("annotation-15"))}

+
${text(relation('project.scenario', 'project.profile').label)}
+
${icon('capture')}

${text(copy("scenario-label"))}

${text(copy("annotation-17"))}
${text(copy("annotation-18"))}

+
${icon('model')}${text(copy("model-label"))}${icon('document')}${text(copy("document-label"))}${icon('decision')}${text(copy("decision-label"))}
+`; +/** Case-owned composition table; no global registration or HTML string surgery. */ +const diagrams: Readonly string>> = {default: ownership, 'dependency-matrix': dependencyMatrix}; +export function architectureDiagram(context: FigureContext): string { + if (!Object.hasOwn(diagrams, context.layout)) throw new Error(`Unsupported case architecture layout: ${context.layout}`); + return diagrams[context.layout]!(context); +} diff --git a/examples/render/library-case/architecture.ts b/examples/render/library-case/architecture.ts new file mode 100644 index 0000000..c17ef7b --- /dev/null +++ b/examples/render/library-case/architecture.ts @@ -0,0 +1,22 @@ +import type { FigureComposer } from '@deterministic-engineering/render'; +import { caseStyle } from './style'; +import {architectureDiagram} from './architecture-diagram'; + +export const architecture: FigureComposer = context => { +const {question, reference: ref, icon, text, copy, relation} = context; +const result = {css: caseStyle, markup: ` +
${text(copy("badge"))}
${text(copy("eyebrow"))}

${text(copy("title"))}${text(copy("emphasis"))}

${text(copy("annotation-01"))}

+

1${text(question('why').text)}

+
${icon('library')}

${text(copy("publish-heading"))}

${text(copy("annotation-02"))}
${text(copy("annotation-03"))}

+
${icon('selection')}

${text(copy("select-heading"))}

${text(copy("annotation-04"))}
${text(copy("annotation-05"))}

+
${icon('capture')}

${text(copy("capture-heading"))}

${text(copy("annotation-06"))}
${text(copy("annotation-07"))}

+

2${text(question('ownership').text)}${text(copy("annotation-08"))}

+${architectureDiagram(context)} +
${text(copy("annotation-19"))}${text(copy("annotation-20"))}${text(copy("annotation-21"))}${text(copy("annotation-22"))}${text(copy("annotation-23"))}
+

3${text(question('boundaries').text)}

+
${icon('library')}

${text(copy("annotation-24"))}

${text(copy("annotation-25"))}
${text(copy("annotation-26"))}

${icon('selection')}

${text(copy("annotation-27"))}

${text(copy("annotation-28"))}
${text(copy("annotation-29"))}

${icon('reference')}

${text(copy("annotation-30"))}

${text(copy("annotation-31"))}
${text(copy("annotation-32"))}

${icon('evidence')}

${text(copy("annotation-33"))}

${text(copy("annotation-34"))}
${text(copy("annotation-35"))}

+

4${text(question('products').text)}

${icon('model')}
${text(copy("model-label"))}${text(copy("annotation-36"))}
${icon('document')}
${text(copy("annotation-37"))}${text(copy("annotation-38"))}
${icon('decision')}
${text(copy("annotation-39"))}${text(copy("annotation-40"))}
${icon('pending')}${text(copy("annotation-41"))}
+

${icon('reference')}${text(question('evidence').text)}

${icon('reference')}
${text(copy("annotation-42"))}
${text(copy("annotation-43"))}
${icon('reference')}
${text(copy("annotation-44"))}
${text(copy("annotation-45"))}

${icon('pending')}${text(copy("annotation-46"))}

${icon('pending')}
${text(copy("annotation-47"))}
${text(copy("annotation-48"))}
${icon('pending')}
${text(copy("annotation-49"))}
${text(copy("annotation-50"))}
+
${icon('question')}${text(copy("annotation-51"))}
${text(copy("annotation-52"))}${text(copy("annotation-53"))}
`}; +return result; +}; diff --git a/examples/render/library-case/dependency-matrix.ts b/examples/render/library-case/dependency-matrix.ts new file mode 100644 index 0000000..9d3dee7 --- /dev/null +++ b/examples/render/library-case/dependency-matrix.ts @@ -0,0 +1,15 @@ +import type {FigureContext} from '@deterministic-engineering/render'; +/** A second architecture organization: exact native dependency directions, not workflow order. */ +export function dependencyMatrix({presentation, model, relation, text}: FigureContext): string { + if (!presentation || typeof presentation !== 'object' || !('matrix' in presentation) || !Array.isArray(presentation.matrix)) throw new Error('Declared matrix axes are missing'); + const axes = presentation.matrix.map((value: unknown) => { + if (!value || typeof value !== 'object' || !('node' in value) || !('label' in value) || typeof value.node !== 'string' || typeof value.label !== 'string') throw new Error('Invalid matrix axis'); + if (!model?.subjects.some(subject => subject.id === value.node)) throw new Error('Undeclared matrix node'); + return {node: value.node, label: value.label}; + }); + if (!axes.length || new Set(axes.map(axis => axis.node)).size !== axes.length) throw new Error('Invalid matrix axes'); + return `${axes.map(axis => ``).join('')}${axes.map(source => `${axes.map(target => { + const edge = model?.relations.find(edge => edge.src === source.node && edge.dst === target.node); + return ``; + }).join('')}`).join('')}
${text(axis.label)}
${text(source.label)}${edge ? text(relation(source.node, target.node).label) : '—'}
`; +} diff --git a/examples/render/library-case/resources.ts b/examples/render/library-case/resources.ts new file mode 100644 index 0000000..08b7647 --- /dev/null +++ b/examples/render/library-case/resources.ts @@ -0,0 +1,15 @@ +import {presentationThemeLayers} from '@deterministic-engineering/render/presentation-theme'; +import {iconPacks} from '@deterministic-engineering/render/icons'; +import type {FigureResources} from '@deterministic-engineering/render'; +/** Downstream-owned visual resources. Selection and content remain in D2. */ +export const resources: FigureResources = { + themeLayers: {...presentationThemeLayers, workbench: { + fontFamily: '"Georgia",serif', colors: {accent: '#246B59', secondary: '#8E6034'}, spacing: {radius: 2, gap: 16}, + stylesheet: '[data-heading]{font-weight:600;letter-spacing:-.6px}[data-panel]{border-radius:2px!important;border-top:3px solid var(--accent)}[data-question-marker]{border-radius:2px!important}[data-badge]{border-radius:0!important;background:transparent!important;border-bottom:2px solid var(--accent)}', + }}, + iconPacks: {...iconPacks, repo: { + library: {label: 'Repository publication', svg: ''}, + selection: {label: 'Repository selection', svg: ''}, + capture: {label: 'Repository binding', svg: ''}, + }}, +}; diff --git a/examples/render/library-case/style.ts b/examples/render/library-case/style.ts new file mode 100644 index 0000000..341a0ac --- /dev/null +++ b/examples/render/library-case/style.ts @@ -0,0 +1,17 @@ +/** Case-specific editorial composition; the Library does not prescribe these sections. */ +export const caseStyle = ` +.dependency-matrix{width:100%;table-layout:fixed;margin:12px 0 18px;font-size:16px}.dependency-matrix th,.dependency-matrix td{padding:15px;border:1px solid var(--border);text-align:center}.dependency-matrix th{background:var(--tint);font-weight:700}.dependency-matrix td{color:var(--accent)} + +[data-canvas]{position:relative;isolation:isolate}.diagram-connections{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;overflow:visible}.diagram-connections path{stroke:var(--accent);stroke-width:1.5;fill:none;stroke-linejoin:round}[data-node]{position:relative;z-index:3} + +.step-index{font-size:10px;color:var(--muted);align-self:flex-start}.lanes{display:flex;flex-direction:column;gap:0}.lane{display:grid;grid-template-columns:130px 1fr;border-top:1px solid var(--border);min-height:94px}.lane:last-of-type{border-bottom:1px solid var(--border)}.lane-label{display:flex;align-items:center;padding:10px 14px;font-size:14px;font-weight:700;background:var(--tint);border-right:1px solid var(--border)}.lane-track{display:grid;grid-template-columns:repeat(var(--step-count),minmax(0,1fr));align-items:center;padding:10px 8px;gap:7px;background:color-mix(in srgb,var(--surface) 65%,transparent)}.lane-slot .step{padding:7px;gap:5px}.lane-slot .step .icon{width:20px;height:20px}.lane-slot .step b{font-size:13px}.lane-slot .step small{font-size:11px}.lane-order{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin:10px 0 0 138px} + +[data-figure]{padding:var(--gutter);background:linear-gradient(145deg,var(--tint),var(--background) 46%)} +.mast{padding:5px 12px 18px}.eyebrow{font-size:13px;font-weight:800;letter-spacing:2px;color:var(--accent);margin-bottom:10px}.mast h1{font-size:42px;line-height:1.2;letter-spacing:-1.3px;font-weight:900}.mast h1 em{font-style:normal;color:var(--accent)}.dek{font-size:17px;color:var(--muted);margin-top:10px;line-height:1.6}.badge{float:right;border-radius:24px;background:var(--secondaryTint);color:var(--secondary);padding:8px 14px;font-size:13px;font-weight:750;letter-spacing:0} +.section{background:var(--surface);padding:17px 20px;border-radius:17px;margin-bottom:12px;border:1px solid color-mix(in srgb,var(--border) 60%,transparent)}.section h2,.half h2{font-size:23px;line-height:32px;display:flex;align-items:center;gap:12px;margin-bottom:14px;letter-spacing:-.3px}.num{border-radius:50%;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;color:var(--onAccent);background:var(--accent);font-size:18px;flex:none}.sub{font-size:13px;color:var(--muted);font-weight:400;margin-left:auto}.lead{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.lead-item{display:flex;align-items:center;gap:14px;padding:12px 14px;background:var(--tint);border-radius:10px}.lead-item:nth-child(2){background:var(--secondaryTint)}.lead-item:nth-child(3){background:color-mix(in srgb,var(--supported) 9%,var(--surface))}.lead-item .icon{width:36px;height:36px;color:var(--accent)}.lead-item:nth-child(2) .icon{color:var(--secondary)}.lead-item:nth-child(3) .icon{color:var(--supported)}.lead-item h3{font-size:20px;line-height:1.3;margin-bottom:5px}.lead-item p{font-size:15px;line-height:1.6;color:var(--muted)} +.architecture{display:grid;grid-template-columns:310px 44px 1fr;align-items:stretch;position:relative}.domain{border:1px solid var(--border);border-radius:12px;padding:14px}.domain.published{background:var(--tint);border-color:color-mix(in srgb,var(--accent) 26%,var(--surface))}.domain.project{background:var(--background)}.boundary{font-size:12px;letter-spacing:1px;color:var(--muted);font-weight:800;display:flex;align-items:center;gap:8px;margin-bottom:10px}.boundary .icon{width:17px;height:17px}.owner-heading{display:flex;align-items:center;gap:12px;margin-bottom:12px}.owner-heading .icon{width:34px;height:34px;color:var(--accent)}.owner-heading h3{font-size:22px}.owner-heading small{display:block;font-size:13px;color:var(--muted);margin-top:2px}.asset-row{display:flex;align-items:center;gap:10px;font-size:15px;line-height:1.6;margin:7px 0}.asset-row .icon{width:21px;height:21px;color:var(--accent)}.project-owners{display:grid;grid-template-columns:1fr 34px 1fr;align-items:start}.owner{padding:12px;border-radius:10px;background:var(--secondaryTint)}.owner.scenario{background:color-mix(in srgb,var(--supported) 9%,var(--surface))}.owner .owner-heading{margin-bottom:8px}.owner .owner-heading .icon{width:30px;height:30px;color:var(--secondary)}.owner.scenario .icon{color:var(--supported)}.owner h3{font-size:20px}.owner p{font-size:14px;color:var(--muted);line-height:1.7}.relation{display:flex;align-items:center;justify-content:center;flex-direction:column;font-size:11px;color:var(--muted);gap:4px}.relation svg{width:30px;height:14px;overflow:visible}.relation.outer{align-self:center;transform:translateY(-18px)}.relation.inner{margin-top:24px}.inputs{display:flex;align-items:center;justify-content:space-between;gap:8px;border-top:1px dashed var(--border);padding-top:10px;margin-top:12px;font-size:13px}.input-item{display:flex;align-items:center;gap:6px}.input-item .icon{width:20px;height:20px;color:var(--accent)}.arch-note{margin-top:13px;display:flex;justify-content:space-between;align-items:center;font-size:13px;color:var(--muted)}.arch-note b{color:var(--secondary)} +.principles{display:grid;grid-template-columns:repeat(4,1fr);gap:0}.principle{padding:4px 15px;border-right:1px solid var(--border)}.principle:first-child{padding-left:0}.principle:last-child{border:0}.principle .icon{width:26px;height:26px;color:var(--accent);margin-bottom:7px}.principle h3{font-size:17px;margin-bottom:5px}.principle p{font-size:14px;line-height:1.6;color:var(--muted)} +.products{display:flex;align-items:center;justify-content:space-between;gap:16px}.product{display:flex;align-items:center;gap:12px}.product .icon{width:30px;height:30px;color:var(--accent)}.product strong{font-size:17px;display:block;margin-bottom:3px}.product small{font-size:13px;color:var(--muted)}.annotation{font-size:13px;background:color-mix(in srgb,var(--pending) 8%,var(--surface));padding:8px 12px;border-radius:6px;margin-top:13px;color:var(--pending);display:flex;align-items:center;gap:8px}.annotation .icon{height:18px;width:18px} +.split{display:grid;grid-template-columns:1fr 1fr;gap:12px}.half{border:1px solid var(--border);border-radius:16px;padding:17px 20px;background:var(--surface);margin-bottom:12px}.half h2{font-size:21px}.facts{display:flex;flex-direction:column;gap:10px}.fact{display:flex;align-items:start;gap:9px;font-size:15px;line-height:1.5}.fact .icon{width:19px;height:19px;margin-top:2px;color:var(--supported)}.pending .fact .icon{color:var(--pending)}.fact small{color:var(--muted);font-size:13px}.fact b{font-weight:750}.pending h2{color:var(--pending)}.takeaway{border-radius:13px;padding:17px 22px;display:flex;gap:15px;align-items:center;background:linear-gradient(110deg,var(--accent),var(--secondary));color:var(--onAccent);font-size:18px;font-weight:650;line-height:1.6}.takeaway .icon{width:34px;height:34px}.fineprint{display:flex;justify-content:space-between;padding:10px 4px 0;font-size:11px;color:var(--muted)} +.flow{display:grid;grid-template-columns:repeat(calc(var(--step-count) - 1),1fr 28px) 1fr;align-items:center;gap:0}.step{padding:12px 10px;border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;gap:9px;background:var(--tint)}.step .icon{width:29px;height:29px;color:var(--accent)}.step b{font-size:16px;display:block;line-height:1.4}.step small{display:block;font-size:12px;color:var(--muted);line-height:1.6;margin-top:2px}.forward{font-size:23px;text-align:center;color:var(--accent)}.flow-output{display:flex;justify-content:flex-end;margin-top:10px}.stop{background:color-mix(in srgb,var(--pending) 10%,var(--surface));border:1px solid color-mix(in srgb,var(--pending) 25%,var(--surface));border-radius:8px;padding:9px 13px;font-size:14px;color:var(--pending);display:flex;align-items:center;gap:8px}.stop .icon{width:21px;height:21px}.exchange{width:100%;font-size:14px}.exchange th{text-align:left;padding:9px 12px;background:var(--tint);font-size:13px;color:var(--muted)}.exchange td{padding:9px 12px;border-bottom:1px solid var(--border);line-height:1.5}.exchange tr:last-child td{border:0}.exchange td:first-child{font-weight:700;white-space:nowrap}.stages-note{font-size:13px;color:var(--muted);margin-bottom:14px}.workflow .section{padding:16px 20px}.workflow .mast h1{font-size:38px}.workflow .half h2{font-size:20px} +`; diff --git a/examples/render/library-case/workflow-declaration.ts b/examples/render/library-case/workflow-declaration.ts new file mode 100644 index 0000000..1b2a372 --- /dev/null +++ b/examples/render/library-case/workflow-declaration.ts @@ -0,0 +1,27 @@ +import type {FigureContext} from '@deterministic-engineering/render'; +interface Workflow { + readonly lanes: readonly {id: string; label: string}[]; + readonly steps: readonly {node: string; lane: string; title: string; detail: string; icon: string}[]; +} +const record = (value: unknown): Record => { + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid case workflow declaration'); + return value as Record; +}; +const text = (value: unknown): string => {if (typeof value !== 'string' || !value.trim()) throw new Error('Invalid workflow text'); return value;}; +/** The linear preparation path is this Case's contract, not a framework-wide rule. */ +export function readWorkflow(context: FigureContext): Workflow { + if (context.view.purpose !== 'workflow') throw new Error('Case preparation needs a workflow view'); + const value = record(record(context.presentation).flow); + if (!Array.isArray(value.lanes) || !Array.isArray(value.steps)) throw new Error('Expected case lanes and steps'); + const lanes = value.lanes.map(item => {const lane = record(item); return {id: text(lane.id), label: text(lane.label)};}); + const steps = value.steps.map(item => {const step = record(item); return {node: text(step.node), lane: text(step.lane), title: text(step.title), detail: text(step.detail), icon: text(step.icon)};}); + if (!lanes.length || new Set(lanes.map(lane => lane.id)).size !== lanes.length) throw new Error('Invalid workflow lanes'); + if (!steps.length || new Set(steps.map(step => step.node)).size !== steps.length) throw new Error('Invalid workflow steps'); + steps.forEach((step, index) => { + if (!lanes.some(lane => lane.id === step.lane)) throw new Error('Undeclared flow lane'); + if (!context.model?.subjects.some(subject => subject.id === step.node)) throw new Error('Undeclared flow node'); + context.copy(step.title); context.copy(step.detail); context.icon(step.icon); + if (index) context.relation(steps[index - 1]!.node, step.node); + }); + return {lanes, steps}; +} diff --git a/examples/render/library-case/workflow-diagram.ts b/examples/render/library-case/workflow-diagram.ts new file mode 100644 index 0000000..2583ee5 --- /dev/null +++ b/examples/render/library-case/workflow-diagram.ts @@ -0,0 +1,13 @@ +import {readWorkflow} from './workflow-declaration'; +import type { FigureContext } from '@deterministic-engineering/render'; +/** Layouts consume the same declared steps and native model edges. No invented owners. */ +export function workflowDiagram(context: FigureContext): string { + const {layout, text, copy, icon} = context; + const flow = readWorkflow(context); + if (!flow) throw new Error('Workflow needs declared steps and responsibility lanes'); + if (!['default', 'swimlane'].includes(layout)) throw new Error(`Unsupported case layout: ${layout}`); + const node = (step: typeof flow.steps[number], index: number) => `
${index + 1}${icon(step.icon)}
${text(copy(step.title))}${text(copy(step.detail))}
`; + const links = ``; + if (layout === 'swimlane') return `
${links}${flow.lanes.map(lane => `
${text(lane.label)}
${flow.steps.map((step, i) => step.lane === lane.id ? `
${node(step, i)}
` : '').join('')}
`).join('')}
`; + return `
${links}${flow.steps.map((step, i) => `${i ? '
' : ''}${node(step, i)}`).join('')}
`; +} diff --git a/examples/render/library-case/workflow.ts b/examples/render/library-case/workflow.ts new file mode 100644 index 0000000..2fafd91 --- /dev/null +++ b/examples/render/library-case/workflow.ts @@ -0,0 +1,14 @@ +import type { FigureComposer } from '@deterministic-engineering/render'; +import { caseStyle } from './style'; +import { workflowDiagram } from './workflow-diagram'; +export const workflow: FigureComposer = (context) => { +const {question, reference: ref, icon, text, copy, relation} = context; +for (const [from, to] of [['confirmation','selection'],['selection','acquisition'],['acquisition','association'],['association','preparation'],['preparation','blocked']] as const) relation(from, to); +return {css: caseStyle, markup: `
+
${text(copy("badge"))}
${text(copy("eyebrow"))}

${text(copy("title"))}${text(copy("emphasis"))}

${text(copy("annotation-01"))}

+

1${text(question('progress').text)}${text(copy("annotation-02"))}

${text(copy("annotation-03"))}

${workflowDiagram(context)}
${icon('pending')}${text(copy("annotation-09"))}
+

2${text(question('exchange').text)}

+
${text(copy("annotation-10"))}${text(copy("annotation-11"))}${text(copy("annotation-12"))}
${text(copy("annotation-13"))}${text(copy("annotation-14"))}${text(copy("annotation-15"))}
${text(copy("annotation-16"))}${text(copy("annotation-17"))}${text(copy("annotation-18"))}
${text(copy("annotation-19"))}${text(copy("annotation-20"))}${text(copy("annotation-21"))}
${text(copy("annotation-22"))}${text(copy("annotation-23"))}${text(copy("annotation-24"))}
+

${icon('reference')}${text(question('stops').text)}

${icon('reference')}
${text(copy("annotation-25"))}
${icon('reference')}
${text(copy("annotation-26"))}
${icon('reference')}
${text(copy("annotation-27"))}

${icon('pending')}${text(copy("annotation-28"))}

${icon('pending')}
${text(copy("annotation-29"))}
${icon('pending')}
${text(copy("annotation-30"))}
${icon('pending')}
${text(copy("annotation-31"))}
+
${icon('capture')}${text(copy("annotation-32"))}
${text(copy("annotation-33"))}${text(copy("annotation-34"))}
`}; +}; diff --git a/examples/render/native.ts b/examples/render/native.ts new file mode 100644 index 0000000..40cb20d --- /dev/null +++ b/examples/render/native.ts @@ -0,0 +1,13 @@ +import {readFile, writeFile, mkdir} from 'node:fs/promises'; +import {fileURLToPath} from 'node:url'; +import path from 'node:path'; +import {compileFigureSource, renderCatalog} from '@deterministic-engineering/render'; +const root = fileURLToPath(new URL('../../', import.meta.url)); +const file = 'docs/90-99-views/90-architecture-views/90.134-render-responsibility-boundaries.d2'; +const text = await readFile(path.join(root,file),'utf8'); +const input = await compileFigureSource({[file]:text},file); +const result = await renderCatalog({sources:input.sources,views:[input.view],presentation:{project:input.view.title}}); +if (await readFile(path.join(root,file),'utf8') !== text) throw new Error('Native view source changed'); +const directory = path.join(root,'.cache/render/library-case'); await mkdir(directory,{recursive:true}); +await writeFile(path.join(directory,'render-responsibility-boundaries.svg'),result.images[0]!.svg); +console.log(`native ownership view: ${result.images[0]!.nodes} nodes / ${result.images[0]!.edges} edges`); diff --git a/examples/render/package.json b/examples/render/package.json new file mode 100644 index 0000000..09d7f66 --- /dev/null +++ b/examples/render/package.json @@ -0,0 +1,20 @@ +{ + "name": "deterministic-engineering-render-case", + "private": true, + "type": "module", + "scripts": { + "case": "bun library-case.ts", + "compare": "bun comparison.ts", + "typecheck": "tsc --noEmit", + "test": "bun test", + "native": "bun native.ts" + }, + "dependencies": { + "@deterministic-engineering/render": "file:../../packages/render" + }, + "devDependencies": { + "playwright": "1.64.1", + "typescript": "7.0.2", + "@types/bun": "1.4.4" + } +} diff --git a/examples/render/test/declarations.test.ts b/examples/render/test/declarations.test.ts new file mode 100644 index 0000000..7950276 --- /dev/null +++ b/examples/render/test/declarations.test.ts @@ -0,0 +1,23 @@ +import {test, expect} from 'bun:test'; +import {readFile} from 'node:fs/promises'; +import {compileFigureSource, selectFigureVariant, composeFigure} from '@deterministic-engineering/render'; +import {resources} from '../library-case/resources'; +import {architecture} from '../library-case/architecture'; +import {workflow} from '../library-case/workflow'; +const root = new URL('../../../', import.meta.url); +const declarations = ['docs/90-99-views/90-architecture-views/90.132-library-profile-scenario-architecture.d2','docs/90-99-views/90-architecture-views/90.133-capture-preparation-workflow.d2']; +test('real Library/Profile/Scenario D2 selects downstream kinds, themes and icons without framework edits', async () => { + for (const file of declarations) { + const bytes = await readFile(new URL(file, root), 'utf8'); + const input = await compileFigureSource({[file]:bytes},file,resources); + const sources = {...input.sources, ...Object.fromEntries(await Promise.all(Object.values(input.references).map(async source => [source, await readFile(new URL(source,root),'utf8')])))}; + const variant = selectFigureVariant({...input,sources},'project-owned'); + expect(variant.view.kind).toBe(input.view.purpose === 'architecture' ? 'responsibility-map' : 'capture-handoff'); + expect(variant.questions).toEqual(input.questions); expect(variant.model).toEqual(input.model); + expect(variant.theme!.colors.accent).toBe('#246B59'); + expect(variant.icons!.library!.label).toBe('Repository publication'); + const figure = composeFigure(variant, input.view.purpose === 'architecture' ? architecture : workflow); + expect(figure.html).toContain('capture-blocked'); expect(figure.html).toContain('"evidenceRefs":[]'); + expect(() => composeFigure({...variant, layout:'not-declared'}, input.view.purpose === 'architecture' ? architecture : workflow)).toThrow('Unsupported case'); + } +},30000); diff --git a/examples/render/test/workflow.test.ts b/examples/render/test/workflow.test.ts new file mode 100644 index 0000000..4477556 --- /dev/null +++ b/examples/render/test/workflow.test.ts @@ -0,0 +1,17 @@ +import {test, expect} from 'bun:test'; +import {composeFigure, type FigureComposer} from '@deterministic-engineering/render'; +import {readWorkflow} from '../library-case/workflow-declaration'; +test('flow layout validates responsibility membership and the native model path', async () => { + + const input = { + view: {source: 'flow.d2', title: 'Flow', purpose: 'workflow' as const, kind: 'flow' as const, question: {ref: 'q', text: 'Who?'}, basis: {captureRef: 'candidate', modelRefs: [], obligationRefs: [], evidenceRefs: []}}, + language: 'en', width: 720, sources: {'flow.d2': 'a -> b'}, references: {source: 'flow.d2'}, questions: [{ref: 'q', text: 'Who?', focusRefs: ['source']}], + copy: {a: 'Acquire', b: 'Bind', detail: 'Observed source'}, layout: 'swimlane', + presentation: {flow: {lanes: [{id: 'scenario', label: 'Scenario'}], steps: [{node: 'a', lane: 'scenario', title: 'a', detail: 'detail', icon: 'model'}, {node: 'b', lane: 'scenario', title: 'b', detail: 'detail', icon: 'model'}]}}, + model: {subjects: [{id: 'a', type: 'rectangle'}, {id: 'b', type: 'rectangle'}], relations: [{id: 'edge', src: 'a', dst: 'b', label: 'binds'}]}, + }; + const composer: FigureComposer = context => {readWorkflow(context); return {markup: '
Who?
', css: ''};}; + expect(composeFigure(input, composer).purpose).toBe('workflow'); + expect(() => composeFigure({...input, presentation: {flow: {...input.presentation.flow, lanes: [{id: 'other', label: 'Invented'}]}}}, composer)).toThrow('Undeclared flow lane'); + expect(() => composeFigure({...input, model: {...input.model, relations: []}}, composer)).toThrow('Undeclared model relation'); +}); diff --git a/examples/render/tsconfig.json b/examples/render/tsconfig.json new file mode 100644 index 0000000..6c65a35 --- /dev/null +++ b/examples/render/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../packages/render/tsconfig.json", + "include": [ + "*.ts", + "library-case/*.ts", + "test/*.ts" + ] +} diff --git a/packages/render/LICENSE b/packages/render/LICENSE new file mode 100644 index 0000000..8000a6f --- /dev/null +++ b/packages/render/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/packages/render/README.org b/packages/render/README.org new file mode 100644 index 0000000..4edf1bc --- /dev/null +++ b/packages/render/README.org @@ -0,0 +1,146 @@ +#+TITLE: Deterministic Engineering Render +#+LANGUAGE: en + +* Ownership +=@deterministic-engineering/render= is a functional static-rendering package. +D2/SysML and their project adapters own Questions, model relationships, explanatory +content and source associations. TS composes selected content, applies declared +visual styles, and exports HTML/SVG/PNG. The package version is read from +=package.json=; the namespace contains no version. + +The framework does not prescribe numbered panels, a fixed card template, or a +closed list of layouts. Node and swimlane presentations are examples. Domain +compositions can present a dependency matrix, hierarchy, business relation, +timeline or another explanation using the same capture and export boundary. +Native D2 system/data/assurance/routing/flow/sequence/state notation continues +through the existing official compiler/render API; no replacement graph language +or layout engine is introduced. + +* Declaration and pure composition +=compileFigureSource(sources, inputPath, resources?)= uses the existing owned D2 +worker. D2 parses syntax/imports; its =graph.data= carries the figure declaration. +The installed SDK requires nested metadata in a native =|json= block scalar; +standard =JSON.parse= reads it. No second parser or TS Questions registry exists. + +The declaration owns =purpose=, =kind=, =language=, Questions/focus, copy, references, +basis links, ordered =themes=, open =layout= and project-owned =presentation= data. +The compositor receives these and the compiled model through =FigureContext=. +=composeFigure(input, composer)= invokes a pure function returning markup/CSS. +Every selected Question must have a visible region; invented source associations +and requested model relations refuse. Architecture and Workflow remain separate. + +=layout= is an open project selector, not a package enum of allowed diagram kinds. +=FigureInput.presentation= stays unknown until the domain compositor validates it. +The dependency-matrix example checks declared axes against native model node IDs. +The Case-owned linear workflow helper validates step/lane membership and actual +consecutive model edges; the framework does not parse or constrain its schema. +Use the model/native D2 or a domain compositor for other organizations. + +* Complete visual styles +| Independent concern | Declaration / owner | +|---------------------+---------------------| +| Typography and fonts | =fontFamily= and explicit =fonts= assets | +| Marks and boundaries | Immutable semantic CSS rules in independent style modules | +| Stroke and fill | Rough.js mode, seed, roughness and surface treatment | +| Nodes and connectors | Node/port grammar plus XYFlow's public =getSmoothStepPath= | +| Palette and spacing | Composable color/spacing layers | +| Icons | Replaceable SVG assets, separate from theme and language | +| Diagram organization | Project =layout=/=presentation= and functional composer | + +=./presentation-theme= exports =composePresentationTheme=, +=extendPresentationTheme= and immutable upstream layers. For example: + +#+begin_src typescript +const theme = composePresentationTheme(['graphite', 'node-editor', 'compact']); +const custom = composePresentationTheme(['sketchbook', 'brand'], { + ...presentationThemeLayers, + brand: {colors: {accent: '#356B64'}, spacing: {gap: 16}}, +}); +#+end_src + +D2 selects equivalent ordered layers and output variants. =sketchbook= uses actual +Rough.js SVG strokes, hachure fill and embedded Kalam fonts. =node-editor= changes +node outlines, ports, connectors and canvas treatment. =blueprint=, =field-notes= +and =graphite= provide palette/typography choices; =compact= adjusts spacing. +Downstream can supply additional layers, icons and composers without editing a +global registry. Font resources are declared by the selected layer, not hardcoded +inside the exporter. Current Case content remains English, matching its sources. + +The package imports Rough.js and the framework-independent XYFlow system utility. +It does not mount a React/Svelte flow editor, pan/zoom runtime or whiteboard. +Excalidraw is a visual reference; its complete editor, React dependencies and +scene/bundle adapters are excluded from this static package. + +* Export and provenance +=exportFigure(figure, browser, {scale})= uses the host's Playwright Browser. +It owns and closes its context, settles actual fonts/images, measures nodes, +uses XYFlow for selected connectors, and bakes Rough.js geometry into offline HTML. +Rendered connectors must match captured native model relations, including direction. +It rejects modified input HTML/drawing/fonts, missing/duplicate endpoints, invalid +images and detected clipping. The host owns artifact writes and publication. + +The result includes PNG, materialized HTML and a rendering receipt: input/composed +HTML/materialized HTML/PNG fingerprints, actual drawing/routing dependencies, +font hashes, browser identity, dimensions, scale and region bounds. Captured +sources, Questions, model, project presentation, style and icons enter the input +fingerprint. Pixel equality across platforms or browser releases is not claimed. +These are rendering facts, not issued C, accepted Q, review R or authority. + +* Existing adapters +=renderCatalog= retains official D2-to-SVG rendering with one or two owned workers. +=./opensysml= consumes the actual optional OpenSysML SDK, retaining model identity +and origins in its explicit supported projection. Unsupported SDK notation refuses. +Saved SDK response tests do not establish live service/model validation. + +* Case and verification +=examples/render= consumes the package and reads canonical D2 declarations +=90.132= and =90.133=. It captures actual Library/Profile/Scenario, =project.sysml= +and Org sources. Source-excerpt observations remain labeled observations. +Current =prepare-formation-capture= still reports =capture-blocked / capture = #f=. + +The Case exports ten independent figures: architecture/workflow baselines, +architecture/workflow sketchbook, architecture/workflow node-editor, +workflow swimlane, architecture dependency matrix and two project-owned resource variants. A separate comparison script +exports their main diagram regions for visual comparison; full figures remain +available. No automatic aesthetic approval or model publication is implied. + +#+begin_src sh +bun install --frozen-lockfile +bunx playwright install chromium +bun run typecheck +bun test +bun run test:export +# In examples/render: +bun install --frozen-lockfile +bun run case +bun run compare +#+end_src + +Playwright's native runner owns browser tests, with at most two workers. Core +imports launch no browser. SVG-only D2 consumers need no browser. Generated +artifacts are in =.cache/render/library-case=; documented candidate snapshots live +beside the D2 view sources. Package installation and a separate packed consumer +verify that styles/fonts are included without the editor or workspace dependencies. + +* Downstream resource selection and open kinds +=FigureResources= contains immutable =themeLayers= and =iconPacks=. D2 =icons= is +an ordered list of selected packs. Each variant may declare a kind, layout, +theme layers and icon packs. =selectFigureVariant(input, name)= consumes the +already-resolved variant, preserving custom resources and the same captured +model/Questions. Unknown/duplicate declarations refuse. No mutable global registry +or framework edit is needed for the =workbench=/=repo= Case resources. + +A kind is an open identifier; standard native profiles are suggestions rather +than the complete taxonomy. Purpose stays explicit architecture/workflow. Domain +schemas (such as the current Case's =presentation.flow=) belong in domain modules. +Rough.js =Options= directly configure stroke/fill; selected surfaces delegate to +its primitives. Connector sides may be declared through data attributes and use +XYFlow paths. HTML marker parsing uses parse5; actual visibility uses the browser. +See [[../../docs/10-19-design/10-architecture/10.142-declarative-render-extensibility-audit.org][10.142 audit]] +for exact findings, retained defaults, negative controls and responsibility boundaries. + +The native D2 theme accepts its full =CompileOptions=, including supplied font +bytes, and leaves layout unset by default. D2 source configuration or an explicit +caller override selects the engine. HTML drawing uses Rough.js =Options= rather +than a second pen configuration schema. Source assertions are now declared in +D2 =presentation.observations= and observed by the Case, not hardcoded in core. diff --git a/packages/render/bun.lock b/packages/render/bun.lock new file mode 100644 index 0000000..7cd6b5e --- /dev/null +++ b/packages/render/bun.lock @@ -0,0 +1,260 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@deterministic-engineering/render", + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6", + }, + "devDependencies": { + "@openmbee/opensysml": "0.11.0", + "@types/bun": "1.4.4", + "fast-check": "4.10.2", + "playwright": "1.64.1", + "typescript": "7.0.2", + }, + "peerDependencies": { + "@openmbee/opensysml": "0.11.0", + "playwright": "1.64.1", + }, + "optionalPeers": [ + "@openmbee/opensysml", + "playwright", + ], + }, + }, + "packages": { + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.14.0", "", {}, "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w=="], + + "@connectrpc/connect": ["@connectrpc/connect@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0" } }, "sha512-MXkBijtcX09R10Eb6sFeIetc6w6746eio6xtfuyVOH7oQAacT1X0GzMIQFux6Qy8cq3W/T5qX5Bei8YbFtmRGA=="], + + "@connectrpc/connect-node": ["@connectrpc/connect-node@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", "@connectrpc/connect": "2.1.2" } }, "sha512-+i/aAOpsI8sIx1mbYp6d99zvxaUSF6t/jP9Ux9maAmjsZPgmIQ3JuIeYi0zJIP9zlCnBlJjkpPosshCgdRuThQ=="], + + "@connectrpc/connect-web": ["@connectrpc/connect-web@2.1.2", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", "@connectrpc/connect": "2.1.2" } }, "sha512-1tfaK85MU+gJjwwmL31d2rzdf0XCYX99chZf63uG89SGBUd4XuZ4ZzhGo2u79TPXOE6nLIZQ2okrpyey42PYdg=="], + + "@fontsource/kalam": ["@fontsource/kalam@5.3.0", "", {}, "sha512-FDkVoDfPDCSN/eO81FOYtUI67eMgpVBJmjBtAqSL0PmEhJyeFY2biC+CR+yEttksgXR9+swpFdYmQTyjwBq80Q=="], + + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + + "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@openmbee/opensysml": ["@openmbee/opensysml@0.11.0", "", { "dependencies": { "@bufbuild/protobuf": "2.14.0", "@connectrpc/connect": "2.1.2", "@connectrpc/connect-node": "2.1.2", "@connectrpc/connect-web": "2.1.2" }, "optionalDependencies": { "@openmbee/opensysml-sysml-grpc-darwin-arm64": "0.11.0", "@openmbee/opensysml-sysml-grpc-darwin-x64": "0.11.0", "@openmbee/opensysml-sysml-grpc-linux-arm64": "0.11.0", "@openmbee/opensysml-sysml-grpc-linux-x64": "0.11.0", "@openmbee/opensysml-sysml-grpc-win32-x64": "0.11.0", "@sigstore/bundle": "4.0.0", "@sigstore/protobuf-specs": "0.5.1", "@sigstore/tuf": "4.0.2", "@sigstore/verify": "3.1.1" }, "peerDependencies": { "@openmbee/opensysml-wasm": "0.11.0" }, "optionalPeers": ["@openmbee/opensysml-wasm"], "bin": { "opensysml-generate": "dist/node/generate-cli.js" } }, "sha512-W9VtruoAbxV2RuKuqIVHCdsv5efgNPnCOktLK4M5nNhNrvyckR/AsenX+mmxG+Ry9r0RnNB/UKIcVS3gQHgLbg=="], + + "@openmbee/opensysml-sysml-grpc-darwin-arm64": ["@openmbee/opensysml-sysml-grpc-darwin-arm64@0.11.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-82KnOccVp765a8Yza4TvTnnayVpZlL81jkUyu7dD4o12imeM+UKZE6babI8JQmnL5JdrznNxg6miUO2BoMDS8w=="], + + "@openmbee/opensysml-sysml-grpc-darwin-x64": ["@openmbee/opensysml-sysml-grpc-darwin-x64@0.11.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fp4YBfzUZGDng2bIGHJbFs7edaKlUQXQlzC7d0x4UQ8Yf+v0Z9Bb2DevtfKjW9COcsUlsJcObbUGPwF2I7rdRQ=="], + + "@openmbee/opensysml-sysml-grpc-linux-arm64": ["@openmbee/opensysml-sysml-grpc-linux-arm64@0.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pkv9ptdZy1L6DRgh3rWia/bYdNKaMBR8CSLOPIVwBwhQTpXdfoVcHZ/ZaKZjXx0VOSFxwE/my8wQsf95ygw1fA=="], + + "@openmbee/opensysml-sysml-grpc-linux-x64": ["@openmbee/opensysml-sysml-grpc-linux-x64@0.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-VTXZF52YiEZFuMBeuYS76PTwKEeuDF4wHdEjHRUbsTCMuVoo8IYDFtkf1+IysZ3PV0efeq67sGVAjLN37DJQpQ=="], + + "@openmbee/opensysml-sysml-grpc-win32-x64": ["@openmbee/opensysml-sysml-grpc-win32-x64@0.11.0", "", { "os": "win32", "cpu": "x64" }, "sha512-1/3fOoozKNouzGHCEnEyDLgioOaynF2Evpn3uw21AZopxQ+pzRA9h5wijtKcP0diz7w+OFa5Zz1E3pF+sOUANA=="], + + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.1", "", {}, "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + + "@terrastruct/d2": ["@terrastruct/d2@0.1.33", "", {}, "sha512-eK5hyfGIJFolC7sUsiKvWdY9xGFctTe3d+PSijo09IYDso8psztC+A4SammizXtlwYZpnnW0AtDjfBYauceSeA=="], + + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + + "@types/bun": ["@types/bun@1.4.4", "", { "dependencies": { "bun-types": "1.4.4" } }, "sha512-9wkRDXUXK124pEnAutE6n/l18j+i9GC5IAHK8TmX/2nlySETmky0OU6yL5U8pW1AoKh++AN9auDCr647BfurjQ=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.12", "", {}, "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.9", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA=="], + + "@types/node": ["@types/node@26.6.6", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@xyflow/system": ["@xyflow/system@0.0.83", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-a//eDjez3WZF47aaPTfVUDD0OINOq+WVwBZCRTqG+m0a5euX8EwlJ4jMY1Wxz0Ocj4xOeekJ70V22Pq0ll8RJw=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "bun-types": ["bun-types@1.4.4", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-yjrJh5oGX7mMPKGd19YXyIjmEuj4G4zNC30bs7PM9Az7NQ3ndF3j6hbeRdvJTRQEhJ/FYQwW0a2moweEi2hMxQ=="], + + "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "entities": ["entities@8.1.0", "", {}, "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA=="], + + "fast-check": ["fast-check@4.10.2", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "http-cache-semantics": ["http-cache-semantics@4.3.0", "", {}, "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + + "ip-address": ["ip-address@10.7.5", "", {}, "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA=="], + + "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], + + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + + "p-map": ["p-map@7.1.1", "", {}, "sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g=="], + + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "playwright": ["playwright@1.64.1", "", { "dependencies": { "playwright-core": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA=="], + + "playwright-core": ["playwright-core@1.64.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + + "undici-types": ["undici-types@8.12.3", "", {}, "sha512-EXE4ydL3S76dFb27UFjNVZHTyv/TLdATj/KUe29ptdcg6qXL48J2tZ3126E5kICj5jn781OR+XlVH1N6Mz8jbA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + } +} diff --git a/packages/render/package.json b/packages/render/package.json new file mode 100644 index 0000000..58d7175 --- /dev/null +++ b/packages/render/package.json @@ -0,0 +1,55 @@ +{ + "name": "@deterministic-engineering/render", + "private": false, + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit", + "test": "bun test", + "test:export": "playwright test" + }, + "dependencies": { + "@fontsource/kalam": "5.3.0", + "@terrastruct/d2": "0.1.33", + "@xyflow/system": "0.0.83", + "parse5": "8.0.1", + "roughjs": "4.6.6" + }, + "devDependencies": { + "@openmbee/opensysml": "0.11.0", + "@types/bun": "1.4.4", + "fast-check": "4.10.2", + "playwright": "1.64.1", + "typescript": "7.0.2" + }, + "version": "1.0.0", + "exports": { + ".": "./src/index.ts", + "./themes/*": "./themes/*", + "./theme": "./src/theme.ts", + "./opensysml": "./src/opensysml.ts", + "./presentation-theme": "./src/presentation-theme.ts", + "./icons": "./src/icons.ts", + "./export": "./src/export.ts" + }, + "files": [ + "src", + "themes", + "README.org" + ], + "license": "LGPL-2.1-only", + "engines": { + "bun": ">=1.3.11" + }, + "peerDependencies": { + "@openmbee/opensysml": "0.11.0", + "playwright": "1.64.1" + }, + "peerDependenciesMeta": { + "@openmbee/opensysml": { + "optional": true + }, + "playwright": { + "optional": true + } + } +} diff --git a/packages/render/playwright.config.ts b/packages/render/playwright.config.ts new file mode 100644 index 0000000..493a6c1 --- /dev/null +++ b/packages/render/playwright.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'playwright/test'; +export default defineConfig({ + testDir: './test', + testMatch: '**/*.browser.ts', + workers: 2, + fullyParallel: false, + reporter: 'line', + outputDir: '../../.cache/render/playwright', +}); diff --git a/packages/render/src/d2-figure.ts b/packages/render/src/d2-figure.ts new file mode 100644 index 0000000..f0f6089 --- /dev/null +++ b/packages/render/src/d2-figure.ts @@ -0,0 +1,63 @@ +import {composeIconPacks, type IconPacks} from './icons'; +import { composePresentationTheme, type ThemeLayers } from './presentation-theme'; +import type { Diagram } from '@terrastruct/d2'; +import type { FigureInput, FigureQuestion } from './figure'; +import {validatePurposeKind, type ViewSelection} from './source/types'; +const object = (value: unknown, name: string): Record => { + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Expected ${name} object`); + return value as Record; +}; +const text = (value: unknown, name: string): string => { + if (typeof value !== 'string' || !value.trim()) throw new Error(`Expected ${name} text`); return value; +}; +const texts = (value: unknown, name: string): string[] => { + if (!Array.isArray(value)) throw new Error(`Expected ${name} array`); + return value.map(item => text(item, name)); +}; +/** Official D2 handles syntax/imports. Its standard plugin data owns the declaration. */ +export interface D2FigureData { readonly data: unknown; readonly diagram?: Pick; readonly inputPath: string; readonly fs: Readonly>; } +export interface FigureResources { readonly themeLayers?: ThemeLayers; readonly iconPacks?: IconPacks; } +export function readD2Figure(compiled: D2FigureData, captured: Readonly>, resources: FigureResources = {}): FigureInput { + const data = compiled.data; + const encoded = object(data, 'D2 graph.data').figure; + // A native JSON block scalar preserves nested data in the installed D2 SDK. + if (typeof encoded !== 'string') throw new Error('D2 vars.d2-config.data.figure must be a JSON block scalar'); + const declaration = object(JSON.parse(encoded) as unknown, 'figure declaration'); + const primary = text(declaration.primary, 'primary question'); + if (!Array.isArray(declaration.questions)) throw new Error('Expected declared questions'); + const questions: FigureQuestion[] = declaration.questions.map(value => { + const question = object(value, 'question'); + return {ref: text(question.ref, 'question ref'), text: text(question.text, 'question'), focusRefs: texts(question.focusRefs, 'focus refs')}; + }); + const question = questions.find(item => item.ref === primary); + if (!question) throw new Error('Declared primary question is missing'); + const purpose = text(declaration.purpose, 'purpose'), kind = text(declaration.kind, 'kind'); + validatePurposeKind(purpose, kind); + const width = declaration.width; + if (typeof width !== 'number') throw new Error('Expected declared width'); + const referenceData = object(declaration.references, 'references'); + const references = Object.fromEntries(Object.entries(referenceData).map(([key, value]) => [key, text(value, `reference ${key}`)])); + const copyData = object(declaration.copy, 'copy'); + const copy = Object.fromEntries(Object.entries(copyData).map(([key, value]) => [key, text(value, `copy ${key}`)])); + const basis = object(declaration.basis, 'basis'); + const view = {source: compiled.inputPath, title: text(declaration.title, 'title'), purpose, kind, question, + basis: {captureRef: text(basis.captureRef, 'capture ref'), modelRefs: texts(basis.modelRefs, 'models'), obligationRefs: texts(basis.obligationRefs, 'obligations'), evidenceRefs: texts(basis.evidenceRefs, 'evidence')}} as ViewSelection; + const model = compiled.diagram ? {subjects: compiled.diagram.shapes.map(({id, type}) => ({id, type})), relations: compiled.diagram.connections.map(({id, src, dst, label}) => ({id, src, dst, label}))} : undefined; + const theme = composePresentationTheme(declaration.themes === undefined ? ['blueprint'] : texts(declaration.themes, 'theme layers'), resources.themeLayers); + const icons = composeIconPacks(declaration.icons === undefined ? ['engineering'] : texts(declaration.icons, 'icon packs'), resources.iconPacks); + const variants = declaration.variants === undefined ? [] : (() => { + if (!Array.isArray(declaration.variants)) throw new Error('Expected declared variants'); + const result = declaration.variants.map(value => { + const variant = object(value, 'variant'); + const variantKind = variant.kind === undefined ? undefined : text(variant.kind, 'variant kind'); + if (variantKind !== undefined) validatePurposeKind(purpose, variantKind); + return {name: text(variant.name, 'variant name'), kind: variantKind, layout: variant.layout === undefined ? undefined : text(variant.layout, 'layout'), + theme: variant.themes === undefined ? theme : composePresentationTheme(texts(variant.themes, 'variant themes'), resources.themeLayers), + icons: variant.icons === undefined ? icons : composeIconPacks(texts(variant.icons, 'variant icon packs'), resources.iconPacks)}; + }); + if (new Set(result.map(variant => variant.name)).size !== result.length) throw new Error('Duplicate declared figure variant'); + return result; + })(); + return {view, model, theme, variants, icons, presentation: declaration.presentation, layout: declaration.layout === undefined ? undefined : text(declaration.layout, 'layout'), language: text(declaration.language, 'language'), width, questions, references, copy, + sources: {...captured, ...compiled.fs}}; +} diff --git a/packages/render/src/export.ts b/packages/render/src/export.ts new file mode 100644 index 0000000..c4f1561 --- /dev/null +++ b/packages/render/src/export.ts @@ -0,0 +1,162 @@ +import {getSmoothStepPath, Position} from '@xyflow/system'; +import rough from 'roughjs'; +import packageInfo from '../package.json' with {type: 'json'}; +import { readFile } from 'node:fs/promises'; +import type { Browser } from 'playwright'; +import type { ComposedFigure } from './figure'; +import { digest } from './source/catalog'; +export interface FigureExportOptions { readonly scale?: number; readonly regions?: readonly string[]; } +export interface FigureImage { + readonly png: Uint8Array; + readonly regionImages: Readonly>; + /** Fully materialized, offline HTML, including embedded fonts and measured sketch strokes. */ + readonly html: string; + readonly receipt: { + readonly inputSHA256: string; readonly composedHTMLSHA256: string; readonly htmlSHA256: string; readonly pngSHA256: string; + readonly drawing: string; readonly routing: string; readonly dependencies: Readonly>; readonly assetSHA256: Readonly>; + readonly regionSHA256: Readonly>; + readonly browser: string; readonly width: number; readonly height: number; readonly scale: number; + readonly bounds: Readonly>; + }; +} +/** Host owns the official browser; this effect owns and closes just its context. */ +export async function exportFigure(figure: ComposedFigure, browser: Browser, options: FigureExportOptions = {}): Promise { + if (digest(figure.html) !== figure.htmlSHA256) throw new Error('Figure HTML differs from its captured fingerprint'); + const scale = options.scale ?? 1; + if (!Number.isFinite(scale) || scale < 0.5 || scale > 4) throw new Error('Invalid export scale'); + const context = await browser.newContext({viewport: {width: figure.width, height: 900}, deviceScaleFactor: scale, reducedMotion: 'reduce'}); + try { + const page = await context.newPage(); + page.setDefaultTimeout(15000); + await page.setContent(figure.html, {waitUntil: 'load', timeout: 15000}); + const provenance = await page.evaluate(() => { const value: unknown = JSON.parse(document.getElementById('figure-provenance')!.textContent!); if (!value || typeof value !== 'object' || !('drawing' in value) || !('fonts' in value)) throw new Error('Invalid rendering provenance'); return {drawing: value.drawing, fonts: value.fonts}; }); + if (JSON.stringify(provenance.drawing) !== JSON.stringify(figure.drawing)) throw new Error('Drawing differs from captured HTML'); + if (JSON.stringify(provenance.fonts) !== JSON.stringify(figure.fonts)) throw new Error('Fonts differ from captured HTML'); + const assetSHA256: Record = {}; + const faces = await Promise.all(figure.fonts.map(async ({family, weight, source}) => { + if (!/^[a-zA-Z0-9 -]+$/.test(family) || !Number.isInteger(weight) || weight < 100 || weight > 900) throw new Error('Invalid declared font'); + const font = await readFile(new URL(import.meta.resolve(source))); + assetSHA256[source] = digest(font); + return `@font-face{font-family:"${family}";font-style:normal;font-weight:${weight};src:url(data:font/woff2;base64,${font.toString('base64')}) format('woff2')}`; + })); + if (faces.length) await page.addStyleTag({content: faces.join('\n')}); + // Settle actual text layout before waiting on its FontFaceSet. + await page.locator('[data-figure]').boundingBox(); + await page.evaluate(async () => { + await document.fonts.ready; + for (const image of document.images) { + if (!image.complete || !image.naturalWidth) throw new Error('Figure contains an unloaded or invalid image'); + await image.decode(); + } + }); + // Route declared connectors against actual font-settled node boxes, then bake to SVG. + const connectors = await page.evaluate(() => { + type Side = 'top' | 'bottom' | 'left' | 'right'; + const result: {index: number; sourceX: number; sourceY: number; targetX: number; targetY: number; sourceSide: Side; targetSide: Side}[] = []; + const side = (value: string | undefined, fallback: Side): Side => { + if (value === undefined) return fallback; + if (value !== 'top' && value !== 'bottom' && value !== 'left' && value !== 'right') throw new Error('Invalid declared connector position'); + return value; + }; + const anchor = (rect: DOMRect, position: Side, distance: number) => ({ + x: position === 'left' ? rect.left - distance : position === 'right' ? rect.right + distance : rect.left + rect.width / 2, + y: position === 'top' ? rect.top - distance : position === 'bottom' ? rect.bottom + distance : rect.top + rect.height / 2, + }); + for (const svg of document.querySelectorAll('svg[data-connections]')) { + const captured: {model?: {relations: {src: string; dst: string}[]}} = JSON.parse(document.getElementById('figure-provenance')!.textContent!); + const canvas = svg.parentElement!, root = canvas.getBoundingClientRect(); + const ns = 'http://www.w3.org/2000/svg', defs = document.createElementNS(ns, 'defs'); + const marker = document.createElementNS(ns, 'marker'); + const id = `arrow-${Array.from(document.querySelectorAll('svg[data-connections]')).indexOf(svg)}`; + marker.setAttribute('id', id); marker.setAttribute('viewBox', '0 0 10 10'); marker.setAttribute('refX', '9'); marker.setAttribute('refY', '5'); marker.setAttribute('markerWidth', '5'); marker.setAttribute('markerHeight', '5'); marker.setAttribute('orient', 'auto-start-reverse'); + const arrow = document.createElementNS(ns, 'path'); arrow.setAttribute('d', 'M 1 1 L 9 5 L 1 9'); arrow.setAttribute('style', 'stroke:var(--accent);fill:none'); marker.append(arrow); defs.append(marker); svg.prepend(defs); + for (const path of svg.querySelectorAll('path[data-from][data-to]')) { + const find = (id: string | undefined) => Array.from(canvas.querySelectorAll('[data-node]')).filter(node => node.dataset.node === id); + const sources = find(path.dataset.from), targets = find(path.dataset.to); + if (sources.length !== 1 || targets.length !== 1) throw new Error('Connector needs unique rendered endpoints'); + if (!captured.model?.relations.some(edge => edge.src === path.dataset.from && edge.dst === path.dataset.to)) throw new Error('Undeclared rendered model relation'); + const a = sources[0]!.getBoundingClientRect(), b = targets[0]!.getBoundingClientRect(); + const backwards = b.right < a.left; + path.dataset.connectionIndex = String(result.length); + const sourceSide = side(path.dataset.sourcePosition, backwards ? 'left' : 'right'), targetSide = side(path.dataset.targetPosition, backwards ? 'right' : 'left'); + const start = anchor(a, sourceSide, 2), end = anchor(b, targetSide, 3); + result.push({index: result.length, sourceX: start.x - root.left, sourceY: start.y - root.top, targetX: end.x - root.left, targetY: end.y - root.top, sourceSide, targetSide}); + path.setAttribute('marker-end', `url(#${id})`); + } + } + return result; + }); + const positions = {top: Position.Top, bottom: Position.Bottom, left: Position.Left, right: Position.Right}; + const routes = connectors.map(({index, sourceSide, targetSide, ...coordinates}) => ({index, path: getSmoothStepPath({...coordinates, + sourcePosition: positions[sourceSide], targetPosition: positions[targetSide], borderRadius: 7, offset: 8})[0]})); + await page.evaluate(routes => { for (const route of routes) document.querySelector(`[data-connection-index="${route.index}"]`)!.setAttribute('d', route.path); }, routes); + if (figure.drawing.mode === 'sketch') { + const surfaces = await page.locator('[data-surface]').evaluateAll(elements => elements.map(element => { + const rect = element.getBoundingClientRect(), style = getComputedStyle(element); + return {shape: element.getAttribute('data-surface') || 'rectangle', width: rect.width, height: rect.height, stroke: style.getPropertyValue('--accent').trim(), fill: style.getPropertyValue('--tint').trim()}; + })); + const generator = rough.generator(); + const sketches = surfaces.map((surface, index) => { + const options = {stroke: surface.stroke, fill: surface.fill, ...figure.drawing.options, seed: figure.drawing.options.seed + index}; + const width = surface.width, height = surface.height; + const drawable = surface.shape === 'rectangle' ? generator.rectangle(2, 2, width - 4, height - 4, options) + : surface.shape === 'ellipse' ? generator.ellipse(width / 2, height / 2, width - 4, height - 4, options) + : surface.shape === 'diamond' ? generator.polygon([[width / 2, 2], [width - 2, height / 2], [width / 2, height - 2], [2, height / 2]], options) + : undefined; + if (!drawable) throw new Error(`Unsupported declared sketch surface: ${surface.shape}`); + const paths = generator.toPaths(drawable); + return {width: surface.width, height: surface.height, paths}; + }); + await page.locator('[data-surface]').evaluateAll((elements, sketches) => { + const ns = 'http://www.w3.org/2000/svg'; + elements.forEach((element, index) => { + const sketch = sketches[index]!, svg = document.createElementNS(ns, 'svg'); + svg.setAttribute('class', 'sketch-surface'); svg.setAttribute('aria-hidden', 'true'); + svg.setAttribute('width', String(sketch.width)); svg.setAttribute('height', String(sketch.height)); + svg.setAttribute('viewBox', `0 0 ${sketch.width} ${sketch.height}`); + for (const path of sketch.paths) { + const node = document.createElementNS(ns, 'path'); + node.setAttribute('d', path.d); node.setAttribute('stroke', path.stroke); node.setAttribute('stroke-width', String(path.strokeWidth)); node.setAttribute('fill', path.fill || 'none'); + svg.append(node); + } + element.prepend(svg); + }); + }, sketches); + } + const report = await page.evaluate(() => { + const figure = document.querySelector('[data-figure]')!; + const root = figure.getBoundingClientRect(); + const bounds: Record = {}; + const issues: string[] = []; + const captured: {questions: {ref: string}[]} = JSON.parse(document.getElementById('figure-provenance')!.textContent!); + for (const question of captured.questions) { + const shown = Array.from(figure.querySelectorAll('[data-question]')).some(element => element.dataset.question === question.ref && element.checkVisibility({opacityProperty: true, visibilityProperty: true}) && element.getBoundingClientRect().width > 0 && element.getBoundingClientRect().height > 0); + if (!shown) issues.push(`Question has no visible region: ${question.ref}`); + } + + for (const element of figure.querySelectorAll('[data-region]')) { + const rect = element.getBoundingClientRect(); + const name = element.dataset.region!; + if (name in bounds) issues.push(`duplicate region: ${name}`); + bounds[name] = {x: rect.x - root.x, y: rect.y - root.y, width: rect.width, height: rect.height}; + if (rect.left < root.left - 1 || rect.right > root.right + 1 || rect.bottom > root.bottom + 1) issues.push(`out of figure: ${name}`); + } + for (const element of figure.querySelectorAll('[data-fit]')) { + if (element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1) issues.push(`clipped text: ${element.textContent?.slice(0, 60)}`); + } + if (figure.scrollWidth > figure.clientWidth + 1) issues.push('figure horizontal overflow'); + return {bounds, issues}; + }); + if (report.issues.length) throw new Error(report.issues.join('; ')); + const html = await page.content(); + const png = await page.locator('[data-figure]').screenshot({type: 'png', animations: 'disabled'}); + const regionImages: Record = {}; + for (const name of options.regions ?? []) { + if (!/^[a-zA-Z0-9_-]+$/.test(name) || !(name in report.bounds)) throw new Error(`Unknown requested figure region: ${name}`); + regionImages[name] = await page.locator(`[data-region="${name}"]`).screenshot({type: 'png', animations: 'disabled'}); + } + const buffer = Buffer.from(png); + return {png, html, regionImages, receipt: {inputSHA256: figure.inputSHA256, composedHTMLSHA256: figure.htmlSHA256, dependencies: packageInfo.dependencies, regionSHA256: Object.fromEntries(Object.entries(regionImages).map(([name, bytes]) => [name, digest(bytes)])), htmlSHA256: digest(html), drawing: figure.drawing.mode === "sketch" ? `roughjs@${packageInfo.dependencies.roughjs}` : "precise", routing: connectors.length ? `xyflow@${packageInfo.dependencies["@xyflow/system"]}` : "none", assetSHA256, pngSHA256: digest(png), browser: browser.version(), + width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20), scale, bounds: report.bounds}}; + } finally { await context.close(); } +} diff --git a/packages/render/src/figure.ts b/packages/render/src/figure.ts new file mode 100644 index 0000000..59f0a9d --- /dev/null +++ b/packages/render/src/figure.ts @@ -0,0 +1,141 @@ +import {parseFragment, type DefaultTreeAdapterTypes} from 'parse5'; +import type { Shape, Connection } from '@terrastruct/d2'; +import packageInfo from '../package.json' with {type: 'json'}; +import { digest, relativeSource } from './source/catalog'; +import { validatePurposeKind, type QuestionRef, type ViewSelection } from './source/types'; +import { defaultIcons, type IconAsset } from './icons'; +import { defaultPresentationTheme, type PresentationTheme } from './presentation-theme'; + +export interface FigureQuestion extends QuestionRef { readonly focusRefs: readonly string[]; } +export interface FigureModel { + readonly subjects: readonly Readonly>[]; + readonly relations: readonly Readonly>[]; +} +export interface FigureInput { + /** Project-owned declaration, interpreted by its functional composition. */ + readonly presentation?: unknown; + readonly layout?: string; + readonly view: ViewSelection; + readonly language: string; + readonly width: number; + readonly questions: readonly FigureQuestion[]; + /** Captured project/model/composition sources; no new model or evidence ledger. */ + readonly sources: Readonly>; + readonly references: Readonly>; + readonly variants?: readonly {readonly name: string; readonly theme: PresentationTheme; readonly kind?: string; readonly layout?: string; readonly icons?: Readonly>}[]; + readonly model?: FigureModel; + readonly copy?: Readonly>; + readonly theme?: PresentationTheme; + readonly icons?: Readonly>; +} +export interface FigureContext { + readonly presentation?: unknown; + readonly model?: FigureModel; + readonly layout: string; + readonly view: ViewSelection; + readonly question: (ref: string) => FigureQuestion; + readonly reference: (ref: string) => string; + readonly icon: (name: string) => string; + readonly text: (value: string) => string; + readonly copy: (key: string) => string; + readonly relation: (from: string, to: string) => Readonly>; +} +export interface FigureComposition { readonly markup: string; readonly css: string; } +export type FigureComposer = (context: FigureContext) => FigureComposition; +export interface ComposedFigure { + readonly html: string; + readonly drawing: PresentationTheme["drawing"]; + readonly fonts: PresentationTheme["fonts"]; + readonly inputSHA256: string; + readonly htmlSHA256: string; + readonly width: number; + readonly purpose: ViewSelection['purpose']; + readonly sourceSHA256: Readonly>; +} +export const escapeHTML = (value: string): string => value.replace(/[&<>"']/g, character => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[character]!)); +/** Capture first, then compose. No fixed page structure or automatic graph enlargement. */ +export function composeFigure(input: FigureInput, compose: FigureComposer): ComposedFigure { + const snapshot = structuredClone(input); + if (!Number.isInteger(snapshot.width) || snapshot.width < 320 || snapshot.width > 4096) throw new Error('Invalid figure width'); + if (!snapshot.questions.length || !snapshot.questions.some(question => question.ref === snapshot.view.question.ref)) throw new Error('Primary question is missing'); + validatePurposeKind(snapshot.view.purpose, snapshot.view.kind); + if (!snapshot.view.basis.captureRef.trim()) throw new Error('Existing capture or labeled candidate reference is required'); + const sources = Object.fromEntries(Object.entries(snapshot.sources).sort(([a], [b]) => a.localeCompare(b))); + if (!Object.hasOwn(sources, snapshot.view.source)) throw new Error('Composition source must be captured'); + for (const [name, source] of Object.entries(sources)) { + relativeSource(name); + if (typeof source !== 'string') throw new Error('Invalid captured source'); + } + const references = Object.fromEntries(Object.entries(snapshot.references).sort(([a], [b]) => a.localeCompare(b))); + for (const source of Object.values(references)) if (!Object.hasOwn(sources, source)) throw new Error(`Uncaptured reference source: ${source}`); + const questions = new Map(snapshot.questions.map(question => [question.ref, question])); + if (questions.size !== snapshot.questions.length) throw new Error('Duplicate question'); + for (const question of questions.values()) { + if (!question.ref.trim() || !question.text.trim() || !question.focusRefs.length) throw new Error('Question needs wording and model/source focus'); + for (const ref of question.focusRefs) if (!Object.hasOwn(references, ref)) throw new Error(`Unknown question focus: ${ref}`); + Object.freeze(question.focusRefs); Object.freeze(question); + } + const theme = snapshot.theme ?? defaultPresentationTheme; + const icons = Object.fromEntries(Object.entries(snapshot.icons ?? defaultIcons).sort(([a], [b]) => a.localeCompare(b))); + const question = (ref: string): FigureQuestion => { + const value = questions.get(ref); if (!value) throw new Error(`Unselected question: ${ref}`); return value; + }; + const reference = (ref: string): string => { + if (!Object.hasOwn(references, ref)) throw new Error(`Unselected reference: ${ref}`); + return `data-ref="${escapeHTML(ref)}"`; + }; + const icon = (name: string): string => { + const value = icons[name as keyof typeof icons]; if (!Object.hasOwn(icons, name) || !value) throw new Error(`Unselected icon: ${name}`); + return ``; + }; + const view = Object.freeze({...snapshot.view, question: Object.freeze({...snapshot.view.question}), basis: Object.freeze({...snapshot.view.basis, + modelRefs: Object.freeze([...snapshot.view.basis.modelRefs]), obligationRefs: Object.freeze([...snapshot.view.basis.obligationRefs]), evidenceRefs: Object.freeze([...snapshot.view.basis.evidenceRefs])})}); + const copy = (key: string): string => { + const value = snapshot.copy?.[key]; if (typeof value !== 'string') throw new Error(`Undeclared copy: ${key}`); return value; + }; + const relation = (from: string, to: string) => { + const found = snapshot.model?.relations.find(edge => edge.src === from && edge.dst === to); + if (!found) throw new Error(`Undeclared model relation: ${from} -> ${to}`); return Object.freeze({...found}); + }; + if (!['precise', 'sketch'].includes(theme.drawing.mode)) throw new Error('Unknown drawing mode'); + const {seed, roughness} = theme.drawing.options; + if (theme.drawing.mode === 'sketch' && (!Number.isInteger(seed) || seed <= 0 || (roughness !== undefined && (!Number.isFinite(roughness) || roughness < 0)))) throw new Error('Invalid sketch drawing parameters'); + // Freeze captured model and project declarations before calling a domain compositor. + const freeze = (value: unknown, seen = new WeakSet()): void => { + if (!value || typeof value !== 'object' || seen.has(value)) return; + seen.add(value); for (const item of Object.values(value)) freeze(item, seen); Object.freeze(value); + }; + freeze(snapshot.model); freeze(snapshot.presentation); + const composition = compose(Object.freeze({view, model: snapshot.model, presentation: snapshot.presentation, layout: snapshot.layout ?? "default", question, reference, icon, copy, relation, text: escapeHTML})); + const usedQuestions = new Set(); + const nodes: DefaultTreeAdapterTypes.Node[] = [...parseFragment(composition.markup).childNodes]; + while (nodes.length) { + const node = nodes.pop()!; + if ('tagName' in node) { + if (node.tagName === 'template' || node.attrs.some(attribute => attribute.name === 'hidden')) continue; + const marker = node.attrs.find(attribute => attribute.name === 'data-question'); + if (marker) usedQuestions.add(marker.value); + } + if ('childNodes' in node) nodes.push(...node.childNodes); + } + for (const id of usedQuestions) if (!questions.has(id)) throw new Error(`Unselected rendered question: ${id}`); + for (const id of questions.keys()) if (!usedQuestions.has(id)) throw new Error(`Question has no visible region: ${id}`); + const sourceSHA256 = Object.fromEntries(Object.entries(sources).map(([name, source]) => [name, digest(source)])); + const renderer = {package: packageInfo.name, version: packageInfo.version, icons: Object.fromEntries(Object.entries(icons).map(([name, asset]) => [name, digest(asset.svg)]))}; + const footprint = {renderer, presentation: snapshot.presentation, layout: snapshot.layout, copy: snapshot.copy, model: snapshot.model, view, questions: snapshot.questions, references, sourceSHA256, theme, icons, width: snapshot.width, language: snapshot.language, composition}; + const inputSHA256 = digest(JSON.stringify(footprint)); + const variables = Object.entries(theme.colors).map(([key, value]) => `--${key}:${value}`).join(';') + ';' + Object.entries(theme.spacing).map(([key, value]) => `--${key}:${value}px`).join(';'); + const metadata = JSON.stringify({renderer, layout: snapshot.layout, presentation: snapshot.presentation, drawing: theme.drawing, fonts: theme.fonts, inputSHA256, model: snapshot.model, view, questions: snapshot.questions, references, sourceSHA256}).replaceAll('<', '\\u003c'); + const html = `${escapeHTML(view.title)}
${composition.markup}
`; + return Object.freeze({html, drawing: Object.freeze({...theme.drawing}), fonts: theme.fonts, inputSHA256, htmlSHA256: digest(html), width: snapshot.width, purpose: view.purpose, sourceSHA256: Object.freeze(sourceSHA256)}); +} + +/** Resolve one already-declared variant without re-reading global defaults. */ +export function selectFigureVariant(input: FigureInput, name: string): FigureInput { + const variant = input.variants?.find(variant => variant.name === name); + if (!variant) throw new Error(`Undeclared figure variant: ${name}`); + return {...input, view: {...input.view, kind: variant.kind ?? input.view.kind}, layout: variant.layout ?? input.layout, + theme: variant.theme, icons: variant.icons ?? input.icons}; +} diff --git a/packages/render/src/icons.ts b/packages/render/src/icons.ts new file mode 100644 index 0000000..8232190 --- /dev/null +++ b/packages/render/src/icons.ts @@ -0,0 +1,34 @@ +/** Project-owned engineering glyphs. Assets and rendering are independently replaceable. */ +export interface IconAsset { readonly svg: string; readonly label: string; } +const glyph = (label: string, body: string): IconAsset => Object.freeze({label, + svg: `${body}`}); +export const engineeringIcons = Object.freeze({ + library: glyph('Published definitions', ''), + selection: glyph('Selected configuration', ''), + capture: glyph('Bound project occurrence', ''), + model: glyph('Architecture model', ''), + document: glyph('Document with source anchors', ''), + decision: glyph('Decision and alternatives', ''), + evidence: glyph('Linked evidence', ''), + question: glyph('Design question', ''), + pending: glyph('Open obligation', ''), + reference: glyph('Source reference', ''), + obligation: glyph('Verification obligation', ''), +}); +export const defaultIcons = engineeringIcons; +export type IconPack = Readonly>; +export type IconPacks = Readonly>; +export const iconPacks: IconPacks = Object.freeze({engineering: engineeringIcons}); +/** Ordered, immutable pack composition; no runtime registration or editor dependency. */ +export function composeIconPacks(names: readonly string[], packs: IconPacks = iconPacks): IconPack { + const result: Record = {}; + if (!names.length) throw new Error('At least one declared icon pack is required'); + for (const name of names) { + if (!Object.hasOwn(packs, name)) throw new Error(`Unknown declared icon pack: ${name}`); + for (const [key, asset] of Object.entries(packs[name]!)) { + if (!asset || typeof asset.svg !== 'string' || !asset.svg.trim() || typeof asset.label !== 'string' || !asset.label.trim()) throw new Error(`Invalid icon asset: ${key}`); + result[key] = Object.freeze({...asset}); + } + } + return Object.freeze(result); +} diff --git a/packages/render/src/index.ts b/packages/render/src/index.ts new file mode 100644 index 0000000..d7dd996 --- /dev/null +++ b/packages/render/src/index.ts @@ -0,0 +1,72 @@ +import { Worker } from 'node:worker_threads'; +import { createCatalog, catalogDigest, type CatalogInput } from './source/catalog'; +import type { RenderResponse } from './worker'; +import type { RenderedView } from './pipeline'; +export { createCatalog, catalogDigest } from './source/catalog'; +export type { CatalogInput, ViewSelection } from './source/catalog'; +export type { ViewKind, Presentation, QuestionRef, ViewBasis } from './source/types'; +export { renderView } from './pipeline'; +export type { RenderedView, RenderEffects } from './pipeline'; +export type { SourceCatalog, ViewEntry } from './source/types'; +export interface RenderOptions { readonly workers?: 1 | 2; } +export interface RenderResult { readonly inputSHA256: string; readonly workers: number; readonly images: readonly RenderedView[]; } + +/** Render a captured input. Publication and source revalidation belong to the caller. */ +export async function renderCatalog(input: CatalogInput, options: RenderOptions = {}): Promise { + const configured = options.workers ?? 2; + if (configured !== 1 && configured !== 2) throw new Error('Renderer supports one or two workers'); + const catalog = createCatalog(input); + const count = Math.min(configured, catalog.views.length); + const workers: Worker[] = []; + const images: RenderedView[] = []; + const errors: Error[] = []; + let cursor = 0; + try { + await Promise.all(Array.from({ length: count }, () => new Promise((resolve, reject) => { + const worker = new Worker(new URL('./worker.ts', import.meta.url), { workerData: catalog }); + workers.push(worker); + let finished = false; + const next = () => { + const index = cursor++; + if (index >= catalog.views.length) { finished = true; resolve(); } + else worker.postMessage({ index }); + }; + worker.on('error', reject); + worker.on('exit', code => { if (!finished) reject(new Error(`Renderer worker exited before completion: ${code}`)); }); + worker.on('message', (message: RenderResponse) => { + if ('error' in message) errors.push(new Error(`${catalog.views[message.index]?.source}: ${message.error}`)); + else images.push(message.image); + next(); + }); + next(); + }))); + } finally { + await Promise.all(workers.map(worker => worker.terminate())); + } + if (errors.length) throw new AggregateError(errors, 'D2 rendering failed; no result was published'); + if (images.length !== catalog.views.length) throw new Error('Incomplete renderer result'); + return { inputSHA256: catalogDigest(catalog), workers: count, images: images.sort((a, b) => a.source.localeCompare(b.source)) }; +} +export { standardThemes } from './themes'; + +export { defaultTheme, extendTheme } from './theme'; +export type { RenderTheme, ThemeOverrides } from './theme'; + +export { composeFigure, selectFigureVariant } from './figure'; +export type { FigureInput, FigureModel, FigureQuestion, FigureContext, FigureComposer, ComposedFigure } from './figure'; + +export { readD2Figure } from './d2-figure'; + +/** Compile declaration/imports through the existing owned D2 worker, then read its data. */ +export async function compileFigureSource(sources: Readonly>, inputPath: string, resources?: import('./d2-figure').FigureResources): Promise { + const worker = new Worker(new URL('./worker.ts', import.meta.url), {workerData: {kind: 'figure', fs: {...sources}, inputPath, resources} satisfies import('./worker').FigureRequest}); + try { + return await new Promise((resolve, reject) => { + worker.once('error', reject); + worker.once('exit', code => reject(new Error(`D2 figure worker exited before result: ${code}`))); + worker.once('message', (message: import('./worker').FigureResponse) => 'error' in message ? reject(new Error(message.error)) : resolve(message.figure)); + }); + } finally { await worker.terminate(); } +} + +export type {FigureResources} from './d2-figure'; diff --git a/packages/render/src/opensysml.ts b/packages/render/src/opensysml.ts new file mode 100644 index 0000000..76dc540 --- /dev/null +++ b/packages/render/src/opensysml.ts @@ -0,0 +1,100 @@ +import type { Connection, RenderedView, RenderSpan } from '@openmbee/opensysml'; +import { digest } from './source/catalog'; +import type { ViewSelection } from './source/catalog'; + +export type OpenSysMLRenderOptions = Parameters[2]; +export interface OpenSysMLCapture { + readonly modelHash: string; + readonly requestedView: string; + readonly rendering: RenderedView; +} +export interface SysMLProjection { + readonly selection: ViewSelection; + readonly layout: 'reflow'; + readonly roles?: Readonly>; +} +export interface ModelNodeTrace { readonly id: string; readonly d2Id: string; readonly parent: string; readonly origin?: RenderSpan; } +export interface ModelEdgeTrace { readonly index: number; readonly from: string; readonly to: string; readonly kind: string; readonly origin?: RenderSpan; } +export interface SysMLProjectionResult { + readonly source: string; + readonly text: string; + readonly view: ViewSelection; + readonly trace: { + readonly modelHash: string; readonly requestedView: string; readonly resolvedView: string; + readonly renderingSHA256: string; readonly capturedRenderingJSON: string; + readonly nodes: readonly ModelNodeTrace[]; readonly edges: readonly ModelEdgeTrace[]; + }; +} +/** Effect adapter: use the SDK's capability/error/lifecycle implementation unchanged. */ +export async function captureOpenSysMLView( + connection: Pick, modelHash: string, viewName: string, options?: OpenSysMLRenderOptions, +): Promise { + if (!modelHash.trim() || !viewName.trim()) throw new Error('Model hash and view name are required'); + const rendering = await connection.renderView(modelHash, viewName, options); + // Decouple the selected SDK result from subsequent mutation by the caller. + return Object.freeze({ modelHash, requestedView: viewName, rendering: structuredClone(rendering) }); +} +/** Pure projection of the SDK's rendering, not a SysML parser or validator. */ +export function projectOpenSysMLView(capture: OpenSysMLCapture, profile: SysMLProjection): SysMLProjectionResult { + if (!capture.modelHash.trim() || !capture.requestedView.trim()) throw new Error('Model/view binding is required'); + if (profile.layout !== 'reflow') throw new Error('D2 projection requires explicit reflow'); + const rendering = capture.rendering; + if (!rendering.view) throw new Error('SDK rendering has no resolved view'); + if (rendering.columns.length || rendering.rows.length || rendering.notes.length) + throw new Error('Table/notes notation is not supported by this D2 projection'); + const nodes = [...rendering.nodes].sort((a, b) => a.id.localeCompare(b.id)); + const byId = new Map(nodes.map(node => [node.id, node])); + if (!nodes.length || byId.size !== nodes.length || nodes.some(node => !node.id)) throw new Error('Empty or duplicate SDK node identity'); + if (nodes.some(node => node.ports.length || node.compartment || node.standIn || node.geometry?.collapsed)) + throw new Error('Ports, compartment rows, stand-ins and collapsed nodes need a supported notation adapter'); + for (const id of Object.keys(profile.roles ?? {})) { + if (!byId.has(id)) throw new Error(`Role selection references a missing SDK node: ${id}`); + } + for (const node of nodes) { + const seen = new Set([node.id]); + let owner = node.parent; + while (owner) { + if (!byId.has(owner)) throw new Error(`Missing SDK owner: ${owner}`); + if (seen.has(owner)) throw new Error(`Cyclic SDK ownership: ${node.id}`); + seen.add(owner); + owner = byId.get(owner)!.parent; + } + } + for (const edge of rendering.edges) { + if (!byId.has(edge.from) || !byId.has(edge.to)) throw new Error('Missing SDK edge endpoint'); + if (edge.fromPort || edge.toPort) throw new Error('Port edges need a supported notation adapter'); + } + const aliases = new Map(nodes.map((node, index) => [node.id, `n${index}`])); + const fullId = (id: string): string => { + const node = byId.get(id)!; + return node.parent ? `${fullId(node.parent)}.${aliases.get(id)!}` : aliases.get(id)!; + }; + const capturedRenderingJSON = JSON.stringify({ modelHash: capture.modelHash, requestedView: capture.requestedView, rendering }); + const renderingSHA256 = digest(capturedRenderingJSON); + const lines = [`# OpenSysML rendering SHA256: ${renderingSHA256}`, '# Explicit D2 reflow; source model semantics remain with OpenSysML.']; + const emit = (parent: string, depth: number) => { + for (const node of nodes.filter(node => node.parent === parent)) { + const pad = ' '.repeat(depth); + const label = [node.name, node.type, node.detail, node.text].filter(Boolean).join('\n'); + lines.push(`${pad}${aliases.get(node.id)!}: {`, `${pad} label: ${JSON.stringify(label || node.id)}`); + const role = profile.roles?.[node.id]; + if (role && !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(role)) throw new Error('Expected a D2 class identifier'); + if (role) lines.push(`${pad} class: ${role}`); + emit(node.id, depth + 1); + lines.push(`${pad}}`); + } + }; + emit('', 0); + for (const edge of rendering.edges) { + lines.push(`${fullId(edge.from)} -> ${fullId(edge.to)}: ${JSON.stringify(edge.label || edge.name || edge.kind)}`); + } + return { + source: profile.selection.source, text: lines.join('\n') + '\n', view: { ...profile.selection }, + trace: { + modelHash: capture.modelHash, requestedView: capture.requestedView, resolvedView: rendering.view, + renderingSHA256, capturedRenderingJSON, + nodes: nodes.map(node => ({ id: node.id, d2Id: fullId(node.id), parent: node.parent, ...(node.origin ? { origin: { ...node.origin } } : {}) })), + edges: rendering.edges.map((edge, index) => ({ index, from: edge.from, to: edge.to, kind: edge.kind, ...(edge.origin ? { origin: { ...edge.origin } } : {}) })), + }, + }; +} diff --git a/packages/render/src/pipeline.ts b/packages/render/src/pipeline.ts new file mode 100644 index 0000000..29c430c --- /dev/null +++ b/packages/render/src/pipeline.ts @@ -0,0 +1,32 @@ +import type { CompileResponse } from '@terrastruct/d2'; +import { digest } from './source/catalog'; +import type { SourceCatalog, ViewEntry } from './source/types'; + +export interface RenderedView { + readonly source: string; readonly purpose: ViewEntry['purpose']; readonly kind: ViewEntry['kind']; + readonly question: ViewEntry['question']; readonly basis: ViewEntry['basis']; + readonly nodeIds: readonly string[]; readonly edgeIds: readonly string[]; + readonly sourceSHA256: string; readonly svg: string; + readonly svgSHA256: string; + readonly nodes: number; readonly edges: number; +} +/** Official D2 owns its complete diagram; TS composes effects without redrawing it. */ +export interface RenderEffects { + readonly compile: (catalog: SourceCatalog, view: ViewEntry) => Promise; + readonly svg: (compiled: CompileResponse) => Promise; +} +export async function renderView(catalog: SourceCatalog, view: ViewEntry, effects: RenderEffects): Promise { + const selected = catalog.views.find(candidate => candidate.source === view.source); + if (!selected || JSON.stringify(selected) !== JSON.stringify(view)) + throw new Error('View is not part of the captured catalog'); + const compiled = await effects.compile(catalog, selected); + if (compiled.inputPath !== selected.source) throw new Error('Compile effect replaced the captured source'); + const svg = await effects.svg(compiled); + return { + question: selected.question, basis: selected.basis, + nodeIds: compiled.diagram.shapes.map(node => node.id), edgeIds: compiled.diagram.connections.map(edge => edge.id), + source: view.source, purpose: view.purpose, kind: view.kind, sourceSHA256: view.sha256, svg, + svgSHA256: digest(svg), + nodes: compiled.diagram.shapes.length, edges: compiled.diagram.connections.length, + }; +} diff --git a/packages/render/src/presentation-theme.ts b/packages/render/src/presentation-theme.ts new file mode 100644 index 0000000..9aa248f --- /dev/null +++ b/packages/render/src/presentation-theme.ts @@ -0,0 +1,55 @@ +import type {Options} from 'roughjs/bin/core'; +import {sketchbook} from './styles/sketchbook'; +import {nodeEditor} from './styles/node-editor'; +/** Declarative visual layers. No process, registry mutation or language translation. */ +export interface PresentationTheme { + readonly fontFamily: string; + readonly stylesheet: string; + readonly fonts: readonly Readonly<{family: string; weight: number; source: string}>[]; + readonly drawing: Readonly<{mode: "precise" | "sketch"; options: Readonly}>; + readonly colors: Readonly<{ + background: string; surface: string; ink: string; muted: string; border: string; + accent: string; secondary: string; tint: string; secondaryTint: string; onAccent: string; + supported: string; pending: string; conflict: string; + }>; + readonly spacing: Readonly<{ gutter: number; gap: number; radius: number }>; +} +export interface PresentationThemeOverrides { + readonly fontFamily?: string; + readonly stylesheet?: string; + readonly fonts?: PresentationTheme["fonts"]; + readonly drawing?: {readonly mode?: PresentationTheme["drawing"]["mode"]; readonly options?: Partial}; + readonly colors?: Partial; + readonly spacing?: Partial; +} +export const defaultPresentationTheme: PresentationTheme = Object.freeze({ + stylesheet: "", fonts: Object.freeze([]), drawing: Object.freeze({mode: "precise", options: Object.freeze({seed: 17, roughness: 1, strokeWidth: 1.2, fillStyle: "hachure", hachureGap: 9, fillWeight: .6})}), + fontFamily: '"Avenir Next","Segoe UI","Noto Sans",sans-serif', + colors: Object.freeze({background: '#F0F5F7', surface: '#FFFFFF', ink: '#193348', muted: '#627787', border: '#D5E1E7', + accent: '#246E86', secondary: '#686084', tint: '#E7F1F5', secondaryTint: '#F0EDF5', onAccent: '#FFFFFF', + supported: '#237C69', pending: '#9A6C25', conflict: '#A84C60'}), + spacing: Object.freeze({gutter: 26, gap: 12, radius: 15}), +}); +export function extendPresentationTheme(base: PresentationTheme, overrides: PresentationThemeOverrides = {}): PresentationTheme { + return Object.freeze({stylesheet: overrides.stylesheet === undefined ? base.stylesheet : [base.stylesheet, overrides.stylesheet].filter(Boolean).join("\n"), + fonts: Object.freeze([...(overrides.fonts ?? base.fonts)].map(font => Object.freeze({...font}))), + drawing: Object.freeze({...base.drawing, ...overrides.drawing, options: Object.freeze({...base.drawing.options, ...overrides.drawing?.options})}), fontFamily: overrides.fontFamily ?? base.fontFamily, + colors: Object.freeze({...base.colors, ...overrides.colors}), spacing: Object.freeze({...base.spacing, ...overrides.spacing})}); +} +export type ThemeLayers = Readonly>; +export const presentationThemeLayers: ThemeLayers = Object.freeze({ + blueprint: defaultPresentationTheme, + 'field-notes': Object.freeze({fontFamily: '"Charter","Georgia",serif', colors: Object.freeze({background: '#F4F0E7', surface: '#FFFEF9', ink: '#39382F', muted: '#797365', border: '#E0D8C5', accent: '#8D6738', secondary: '#546D5B', tint: '#F4EAD7', secondaryTint: '#EAF0E5', onAccent: '#FFFFFF', supported: '#3B785A', pending: '#A67127', conflict: '#A04F4B'})}), + graphite: Object.freeze({colors: Object.freeze({background: '#131E28', surface: '#1C2935', ink: '#E5EEF5', muted: '#A3B6C5', border: '#354956', accent: '#8BCADD', secondary: '#BFB4DD', tint: '#263D4B', secondaryTint: '#343449', onAccent: '#16222C', supported: '#8BCEAE', pending: '#E0BE81', conflict: '#ECA3AC'})}), + sketchbook, + 'node-editor': nodeEditor, + compact: Object.freeze({spacing: Object.freeze({gutter: 26, gap: 12, radius: 13})}), +}); +/** Ordered composition is explicit and immutable. Downstream may supply its own layers. */ +export function composePresentationTheme(names: readonly string[], layers: ThemeLayers = presentationThemeLayers): PresentationTheme { + if (!names.length) throw new Error('At least one declared theme layer is required'); + return names.reduce((theme, name) => { + const layer = layers[name]; if (!Object.hasOwn(layers, name) || !layer) throw new Error(`Unknown declared theme layer: ${name}`); + return extendPresentationTheme(theme, layer); + }, defaultPresentationTheme); +} diff --git a/packages/render/src/source/catalog.ts b/packages/render/src/source/catalog.ts new file mode 100644 index 0000000..09548a3 --- /dev/null +++ b/packages/render/src/source/catalog.ts @@ -0,0 +1,56 @@ +import { createHash } from 'node:crypto'; +import path from 'node:path'; +import { defaultTheme, extendTheme, type RenderTheme } from '../theme'; +import { validatePurposeKind, type Presentation, type SourceCatalog, type ViewSelection } from './types'; + +export type { ViewSelection } from './types'; +export interface CatalogInput { + readonly sources: Readonly>; + readonly views: readonly ViewSelection[]; + readonly presentation: Presentation; + readonly theme?: RenderTheme; +} +export const digest = (bytes: string | Uint8Array): string => createHash('sha256').update(bytes).digest('hex'); +export function relativeSource(source: string): void { + if (!source || source.split('/').some(part => !part || part === '.' || part === '..') || source.includes('\\') || source.startsWith('/') || source.startsWith('../') || path.posix.normalize(source) !== source) + throw new Error(`Expected a normalized repository-relative source: ${source}`); +} +/** Capture text supplied by the repository owner; no directory discovery or writes. */ +export function createCatalog(input: CatalogInput): SourceCatalog { + if (input.theme?.render.ascii) throw new Error('Image rendering requires native SVG output'); + if (!input.presentation.project.trim()) throw new Error('Project label is required'); + const fs: Record = {}; + for (const source of Object.keys(input.sources).sort()) { + relativeSource(source); + if (!source.endsWith('.d2')) throw new Error(`Expected a D2 source: ${source}`); + const text = input.sources[source]; + if (typeof text !== 'string') throw new Error(`Expected source text: ${source}`); + fs[source] = text; + } + const seen = new Set(); + const views = input.views.map(view => { + relativeSource(view.source); + if (seen.has(view.source)) throw new Error(`Duplicate view: ${view.source}`); + seen.add(view.source); + validatePurposeKind(view.purpose, view.kind); + if (!view.question?.ref.trim() || !view.question.text.trim() || !view.basis?.captureRef.trim()) + throw new Error('A question and existing project capture reference are required'); + for (const references of [view.basis.modelRefs, view.basis.obligationRefs, view.basis.evidenceRefs]) { + if (!Array.isArray(references) || references.some(reference => !reference.trim())) throw new Error('Invalid basis references'); + } + if (!view.title.trim()) throw new Error(`View title is required: ${view.source}`); + const text = fs[view.source]; + if (text === undefined) throw new Error(`Missing view source: ${view.source}`); + return { ...view, question: Object.freeze({ ...view.question }), basis: Object.freeze({ + ...view.basis, modelRefs: Object.freeze([...view.basis.modelRefs]), + obligationRefs: Object.freeze([...view.basis.obligationRefs]), evidenceRefs: Object.freeze([...view.basis.evidenceRefs]), + }), id: view.source, sha256: digest(text) }; + }).sort((a, b) => a.source.localeCompare(b.source)); + if (!views.length) throw new Error('At least one view is required'); + return Object.freeze({ + views: Object.freeze(views.map(view => Object.freeze(view))), fs: Object.freeze(fs), + theme: extendTheme(input.theme ?? defaultTheme), + presentation: Object.freeze({ ...input.presentation }), + }); +} +export function catalogDigest(catalog: SourceCatalog): string { return digest(JSON.stringify(catalog)); } diff --git a/packages/render/src/source/types.ts b/packages/render/src/source/types.ts new file mode 100644 index 0000000..bccf7aa --- /dev/null +++ b/packages/render/src/source/types.ts @@ -0,0 +1,29 @@ +import type { RenderTheme } from '../theme'; +export const viewKinds = ['system', 'routing', 'flow', 'sequence', 'state', 'data', 'assurance'] as const; +/** Standard profiles are suggestions; project diagram kinds are open identifiers. */ +export type ViewKind = string; +export function validatePurposeKind(purpose: string, kind: string): void { + if (!['architecture', 'workflow'].includes(purpose) || !kind.trim()) throw new Error('Invalid view purpose/kind'); + const standardPurpose = {system: 'architecture', data: 'architecture', assurance: 'architecture', routing: 'workflow', flow: 'workflow', sequence: 'workflow', state: 'workflow'} as const; + if (Object.hasOwn(standardPurpose, kind) && standardPurpose[kind as keyof typeof standardPurpose] !== purpose) throw new Error('Architecture and workflow purpose/kind contracts must be separate'); +} +export interface QuestionRef { readonly ref: string; readonly text: string; } +/** Links into the project's existing capture/obligation/evidence system, not a second ledger. */ +export interface ViewBasis { + readonly captureRef: string; + readonly modelRefs: readonly string[]; + readonly obligationRefs: readonly string[]; + readonly evidenceRefs: readonly string[]; +} +export interface ViewSelection { + readonly source: string; readonly title: string; readonly question: QuestionRef; readonly basis: ViewBasis; + readonly purpose: 'architecture' | 'workflow'; readonly kind: ViewKind; +} +export type ViewEntry = ViewSelection & { readonly id: string; readonly sha256: string }; +export interface Presentation { readonly project: string; readonly footer?: string; } +export interface SourceCatalog { + readonly views: readonly ViewEntry[]; + readonly fs: Readonly>; + readonly presentation: Presentation; + readonly theme: RenderTheme; +} diff --git a/packages/render/src/styles/node-editor.ts b/packages/render/src/styles/node-editor.ts new file mode 100644 index 0000000..8944c03 --- /dev/null +++ b/packages/render/src/styles/node-editor.ts @@ -0,0 +1,2 @@ +import type {PresentationThemeOverrides} from '../presentation-theme'; +export const nodeEditor: PresentationThemeOverrides = Object.freeze({stylesheet: '[data-canvas]{background-image:radial-gradient(var(--border) 1px,transparent 1px);background-size:16px 16px;padding:24px 15px!important;border-radius:8px}[data-node]{position:relative;border-radius:5px!important;background:var(--surface)!important;border:1px solid var(--accent)!important;box-shadow:0 3px 0 color-mix(in srgb,var(--accent) 12%,transparent)}[data-node]:before,[data-node]:after{content:"";position:absolute;top:50%;width:8px;height:8px;background:var(--surface);border:2px solid var(--accent);border-radius:50%;transform:translateY(-50%)}[data-node]:before{left:-5px}[data-node]:after{right:-5px}[data-flow-gap]{font-family:monospace;color:var(--accent)}[data-boundary]{border-style:dashed!important}[data-panel]{border-radius:6px!important}[data-question-marker]{border-radius:4px!important}' }); diff --git a/packages/render/src/styles/sketchbook.ts b/packages/render/src/styles/sketchbook.ts new file mode 100644 index 0000000..863d961 --- /dev/null +++ b/packages/render/src/styles/sketchbook.ts @@ -0,0 +1,4 @@ +import type {PresentationThemeOverrides} from '../presentation-theme'; +export const sketchbook: PresentationThemeOverrides = Object.freeze({fonts: Object.freeze([400, 700].map(weight => Object.freeze({family: 'Kalam', weight, source: `@fontsource/kalam/files/kalam-latin-${weight}-normal.woff2`}))), fontFamily: '"Kalam","Noto Sans",sans-serif', drawing: Object.freeze({mode: 'sketch' as const, options: Object.freeze({seed: 29, roughness: 1.3})}), + colors: Object.freeze({background: '#FAF7EE', surface: '#FFFDF7', ink: '#283E43', muted: '#63716F', border: '#ADBBB5', accent: '#287F79', secondary: '#946B42', tint: '#EDF4E9', secondaryTint: '#F6EDD8'}), + stylesheet: '[data-figure]{background:var(--background)!important}[data-panel]{border-radius:2px!important;background:transparent!important;border:0!important;border-top:1px dashed var(--border)!important}[data-heading]{letter-spacing:0!important}[data-summary]{background:var(--tint)!important;color:var(--ink)!important;border-radius:0!important}[data-surface]{position:relative;isolation:isolate;border-color:transparent!important;border-radius:0!important;background:transparent!important}[data-surface]>*{position:relative;z-index:1}[data-surface]>.sketch-surface{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:visible}[data-badge]{background:transparent!important;border-bottom:2px solid var(--secondary);border-radius:0!important}' }); diff --git a/packages/render/src/theme.ts b/packages/render/src/theme.ts new file mode 100644 index 0000000..09f28c7 --- /dev/null +++ b/packages/render/src/theme.ts @@ -0,0 +1,21 @@ +import type { CompileOptions, RenderOptions } from '@terrastruct/d2'; + +/** Only official D2 options; D2 source profiles own classes, shapes and style. */ +export interface RenderTheme { + readonly compile: Readonly; + readonly render: Readonly; +} +export interface ThemeOverrides { + readonly compile?: Partial; + readonly render?: Partial; +} +export const defaultTheme: RenderTheme = Object.freeze({ + compile: Object.freeze({}), render: Object.freeze({}), +}); +/** Compose optional upstream defaults; caller may also supply a complete theme. */ +export function extendTheme(base: RenderTheme, overrides: ThemeOverrides = {}): RenderTheme { + return Object.freeze({ + compile: Object.freeze({ ...base.compile, ...overrides.compile }), + render: Object.freeze({ ...base.render, ...overrides.render }), + }); +} diff --git a/packages/render/src/themes.ts b/packages/render/src/themes.ts new file mode 100644 index 0000000..84954b5 --- /dev/null +++ b/packages/render/src/themes.ts @@ -0,0 +1,9 @@ +import { readFileSync } from 'node:fs'; +import { viewKinds } from './source/types'; +/** Packaged presentation sources, mounted at a stable virtual D2 path. */ +export function standardThemes(): Record { + return Object.fromEntries(['base', ...viewKinds].map(name => [ + `architecture/themes/${name}.d2`, + readFileSync(new URL(`../themes/${name}.d2`, import.meta.url), 'utf8'), + ])); +} diff --git a/packages/render/src/worker.ts b/packages/render/src/worker.ts new file mode 100644 index 0000000..12ef578 --- /dev/null +++ b/packages/render/src/worker.ts @@ -0,0 +1,38 @@ +import { parentPort, workerData } from 'node:worker_threads'; +import { D2 } from '@terrastruct/d2'; +import { readD2Figure } from './d2-figure'; +import type { FigureInput } from './figure'; +import { renderView, type RenderedView, type RenderEffects } from './pipeline'; +import type { SourceCatalog } from './source/types'; + +export type RenderResponse = { index: number; image: RenderedView } | { index: number; error: string }; +export interface FigureRequest { readonly resources?: import('./d2-figure').FigureResources; readonly kind: 'figure'; readonly fs: Readonly>; readonly inputPath: string; } +export type FigureResponse = { readonly figure: FigureInput } | { readonly error: string }; +const input = workerData as SourceCatalog | FigureRequest; +const engine = new D2(); +if ('kind' in input) { + try { + const compiled = await engine.compile({fs: {...input.fs}, inputPath: input.inputPath, options: {}}); + parentPort?.postMessage({figure: readD2Figure({data: compiled.graph.data as unknown, diagram: compiled.diagram, inputPath: compiled.inputPath, fs: compiled.fs}, input.fs, input.resources)} satisfies FigureResponse); + } catch (error: unknown) { parentPort?.postMessage({error: error instanceof Error ? error.message : String(error)} satisfies FigureResponse); } +} else { +const catalog = input; +const effects: RenderEffects = { + compile: (input, view) => engine.compile({ fs: { ...input.fs }, inputPath: view.source, + options: { ...input.theme.compile, ...input.theme.render }, + }), + svg: compiled => engine.render(compiled.diagram, compiled.renderOptions), + +}; +if (!parentPort) throw new Error('Renderer requires an owned worker port'); +parentPort.on('message', async ({ index }: { index: number }) => { + try { + const view = catalog.views[index]; + if (!view) throw new Error(`Unknown view index: ${index}`); + parentPort?.postMessage({ index, image: await renderView(catalog, view, effects) } satisfies RenderResponse); + } catch (error: unknown) { + parentPort?.postMessage({ index, error: error instanceof Error ? error.message : String(error) } satisfies RenderResponse); + } +}); + +} diff --git a/packages/render/test/d2-figure.test.ts b/packages/render/test/d2-figure.test.ts new file mode 100644 index 0000000..0ef1521 --- /dev/null +++ b/packages/render/test/d2-figure.test.ts @@ -0,0 +1,59 @@ +import { test, expect } from 'bun:test'; +import { compileFigureSource, composeFigure, selectFigureVariant, type FigureContext } from '../src'; +import { readD2Figure } from '../src/d2-figure'; +const declaration = (wording: string) => ({title: 'Declared view', purpose: 'architecture', kind: 'system', language: 'en', width: 800, primary: 'ownership', + questions: [{ref: 'ownership', text: wording, focusRefs: ['model']}], references: {model: 'project.sysml'}, + basis: {captureRef: 'design-candidate:test', modelRefs: ['project.sysml'], obligationRefs: [], evidenceRefs: []}, copy: {answer: 'Declared answer'}}); +const source = (wording: string): string => `vars: {d2-config: {data: {figure: |json\n${JSON.stringify(declaration(wording))}\n|\n}}}\nlibrary -> project: publishes`; +test('official D2 data owns Questions; changing D2 changes HTML without editing TS', async () => { + const compose = ({question, copy, text}: FigureContext) => ({markup: `
${text(question('ownership').text)} / ${text(copy('answer'))}
`, css: ''}); + const a = await compileFigureSource({'view.d2': source('Who owns this?'), 'project.sysml': 'model'}, 'view.d2'); + const b = await compileFigureSource({'view.d2': source('Who publishes this?'), 'project.sysml': 'model'}, 'view.d2'); + expect(a.model?.relations[0]?.label).toBe('publishes'); + expect(() => composeFigure(a, context => { context.relation('project', 'library'); return {markup: '', css: ''}; })).toThrow('Undeclared model relation'); + expect(composeFigure(a, compose).html).toContain('Who owns this?'); + expect(composeFigure(b, compose).html).toContain('Who publishes this?'); + expect(composeFigure(a, compose).inputSHA256).not.toBe(composeFigure(b, compose).inputSHA256); +}, 30000); +test('native syntax errors refuse through the owned worker', async () => { + await expect(compileFigureSource({'view.d2': 'node: {'}, 'view.d2')).rejects.toThrow(); +}, 30000); +test('missing metadata, malformed JSON and purpose mismatch are refused', () => { + const fs = {'view.d2': 'source'}; + expect(() => readD2Figure({inputPath: 'view.d2', fs, data: {figure: null}}, fs)).toThrow('JSON block'); + expect(() => readD2Figure({inputPath: 'view.d2', fs, data: {figure: '{bad'}}, fs)).toThrow(); + expect(() => readD2Figure({inputPath: 'view.d2', fs, data: {figure: JSON.stringify({...declaration('Who?'), purpose: 'workflow'})}}, fs)).toThrow('purpose'); +}); + + +test('D2 selects downstream kinds, composed themes and icon packs through the actual worker', async () => { + const {presentationThemeLayers} = await import('../src/presentation-theme'); + const {iconPacks} = await import('../src/icons'); + const svg = ''; + const metadata = {...declaration('Who owns this?'), kind: 'domain-responsibility', themes: ['blueprint', 'brand'], icons: ['engineering'], + variants: [{name: 'domain', kind: 'dependency-table', themes: ['field-notes', 'brand'], icons: ['engineering', 'brand'], layout: 'domain-matrix'}]}; + const fs = {'view.d2': `vars: {d2-config: {layout-engine: dagre; data: {figure: |json\n${JSON.stringify(metadata)}\n|\n}}}\na -> b`, 'project.sysml': 'captured model'}; + const resources = {themeLayers: {...presentationThemeLayers, brand: {colors: {accent: '#123456'}}}, iconPacks: {...iconPacks, brand: {library: {svg, label: 'Domain publication'}}}}; + const input = await compileFigureSource(fs, 'view.d2', resources); + const variant = selectFigureVariant(input, 'domain'); + expect(input.view.kind).toBe('domain-responsibility'); + expect(variant.view.kind).toBe('dependency-table'); + expect(variant.theme!.colors.accent).toBe('#123456'); + expect(variant.icons!.library!.svg).toBe(svg); + expect(variant.model).toEqual(input.model); + expect(variant.questions).toEqual(input.questions); + expect(() => selectFigureVariant(input, 'missing')).toThrow('Undeclared figure variant'); + const compose = ({icon}: FigureContext) => ({markup: `
${icon('library')}
`, css: ''}); + const html = composeFigure(variant, compose).html; + expect(html).toContain(svg); expect(html).toContain('#123456'); + expect(html).not.toContain('"icons":"engineering"'); +}, 30000); +test('declaration rejects unknown resources, duplicate variants and reserved-purpose mismatch', () => { + const read = (extra: Record) => readD2Figure({inputPath:'view.d2', fs:{'view.d2':'source'}, data:{figure:JSON.stringify({...declaration('Who?'), ...extra})}}, {'view.d2':'source','project.sysml':'model'}); + expect(() => read({icons: ['missing']})).toThrow('Unknown declared icon pack'); + expect(() => read({icons: ['constructor']})).toThrow('Unknown declared icon pack'); + expect(() => read({themes: ['toString']})).toThrow('Unknown declared theme'); + expect(() => read({variants:[{name:'same'}, {name:'same'}]})).toThrow('Duplicate declared'); + expect(() => read({variants:[{name:'bad', kind:'flow'}]})).toThrow('purpose'); + expect(() => read({kind:''})).toThrow(); +}); diff --git a/packages/render/test/export.browser.ts b/packages/render/test/export.browser.ts new file mode 100644 index 0000000..6c25ec7 --- /dev/null +++ b/packages/render/test/export.browser.ts @@ -0,0 +1,83 @@ +import {composePresentationTheme} from '../src/presentation-theme'; +import { test, expect } from 'playwright/test'; +import { composeFigure, type FigureInput, type FigureComposer } from '../src/figure'; +import { exportFigure } from '../src/export'; +const input: FigureInput = { + view: {source: 'case.ts', title: 'Browser export', purpose: 'architecture', kind: 'system', question: {ref: 'q', text: 'Which source?'}, basis: {captureRef: 'candidate', modelRefs: [], obligationRefs: [], evidenceRefs: []}}, + width: 720, language: 'en', questions: [{ref: 'q', text: 'Which source?', focusRefs: ['source']}], sources: {'case.ts': 'case'}, references: {source: 'case.ts'}, +}; +const normal: FigureComposer = ({icon}) => ({markup: `

Source & model

${icon('model')}

Actual browser fonts.

`, css: '[data-figure]{padding:20px}'}); +test('exports just the figure with real fonts/icons and records actual browser and dimensions', async ({browser}) => { + const figure = composeFigure(input, normal), count = browser.contexts().length; + const result = await exportFigure(figure, browser, {scale: 1.5, regions: ['diagram']}); + expect([...result.png.slice(0, 8)]).toEqual([137,80,78,71,13,10,26,10]); + expect(result.receipt.width).toBe(1080); + expect(result.receipt.height).toBeGreaterThan(50); + expect(result.receipt.bounds.diagram!.width).toBeGreaterThan(0); + expect(result.receipt.browser).toBe(browser.version()); + expect(result.regionImages.diagram!.length).toBeGreaterThan(100); + expect(result.receipt.regionSHA256.diagram).toHaveLength(64); + await expect(exportFigure(figure, browser, {regions: ['missing']})).rejects.toThrow('Unknown requested figure region'); + expect(browser.contexts()).toHaveLength(count); +}); +test('modified HTML is rejected before browser rendering', async ({browser}) => { + const figure = composeFigure(input, normal); + await expect(exportFigure({...figure, html: figure.html + 'changed'}, browser)).rejects.toThrow('fingerprint'); +}); +test('text clipping refuses and cleans its own browser context', async ({browser}) => { + const figure = composeFigure(input, () => ({markup: '
Unacceptably clipped text
', css: ''})); + const count = browser.contexts().length; + await expect(exportFigure(figure, browser)).rejects.toThrow('clipped text'); + expect(browser.contexts()).toHaveLength(count); +}); +test('missing image fails rather than exporting a misleading completed figure', async ({browser}) => { + const figure = composeFigure(input, () => ({markup: '
', css: ''})); + await expect(exportFigure(figure, browser)).rejects.toThrow(); +}); + +test('sketch materialization embeds real font and repeatable Rough.js SVG rather than a color swap', async ({browser}) => { + const figure = composeFigure({...input, theme: composePresentationTheme(['sketchbook'])}, () => ({markup: '
Handwritten model
', css: '[data-surface]{position:relative}.sketch-surface{position:absolute;inset:0;pointer-events:none}'})); + const a = await exportFigure(figure, browser), b = await exportFigure(figure, browser); + expect(a.html).toContain('class="sketch-surface"'); + expect(a.html).toContain('data:font/woff2;base64,'); + expect(a.receipt.drawing).toContain('roughjs@'); + expect(Object.keys(a.receipt.assetSHA256)).toHaveLength(2); + expect(a.receipt.htmlSHA256).toBe(b.receipt.htmlSHA256); + expect(a.receipt.pngSHA256).toBe(b.receipt.pngSHA256); + await expect(exportFigure({...figure, drawing: {...figure.drawing, options: {...figure.drawing.options, seed: 1}}}, browser)).rejects.toThrow('Drawing differs'); +}); +test('measured connectors refuse missing rendered endpoints', async ({browser}) => { + const figure = composeFigure(input, () => ({markup: '
', css: ''})); + await expect(exportFigure(figure, browser)).rejects.toThrow('unique rendered endpoints'); +}); + +test('rendered edges must exist in the captured model, including their direction', async ({browser}) => { + const withModel = {...input, model: {subjects: [{id: 'a', type: 'rectangle'}, {id: 'b', type: 'rectangle'}], relations: [{id: 'ab', src: 'a', dst: 'b', label: 'selects'}]}}; + const diagram = (from: string, to: string): FigureComposer => () => ({markup: `
A
B
`, css: ''}); + const good = await exportFigure(composeFigure(withModel, diagram('a', 'b')), browser); + expect(good.receipt.routing).toContain('xyflow@'); + expect(good.html).toContain('data-connection-index="0"'); + await expect(exportFigure(composeFigure(withModel, diagram('b', 'a')), browser)).rejects.toThrow('Undeclared rendered model relation'); +}); + +test('declared Rough.js options and surfaces replace fixed rectangle/fill defaults', async ({browser}) => { + const theme = composePresentationTheme(['sketchbook']); + const make = (shape: string) => composeFigure({...input, theme: {...theme, drawing: {...theme.drawing, options: {...theme.drawing.options, fillStyle: 'solid', roughness: .5}}}}, () => ({markup: `
Declared primitive
`, css: '.sketch-surface{position:absolute;inset:0}'})); + const ellipse = await exportFigure(make('ellipse'), browser), diamond = await exportFigure(make('diamond'), browser); + expect(ellipse.receipt.pngSHA256).not.toBe(diamond.receipt.pngSHA256); + expect(ellipse.html).toContain('sketch-surface'); + await expect(exportFigure(make('unknown'), browser)).rejects.toThrow('Unsupported declared sketch surface'); +}); + +test('theme CSS cannot hide a selected Question and still qualify the export', async ({browser}) => { + const figure = composeFigure(input, () => ({markup:'
Required question
',css:'[data-question]{visibility:hidden}'})); + await expect(exportFigure(figure, browser)).rejects.toThrow('Question has no visible region'); +}); + +test('vertical connector sides are declared rather than forced into a horizontal workflow', async ({browser}) => { + const model = {subjects:[{id:'a',type:'rectangle'},{id:'b',type:'rectangle'}],relations:[{id:'ab',src:'a',dst:'b',label:'binds'}]}; + const figure = (side: string) => composeFigure({...input,model}, () => ({markup:`
Publisher
Consumer
`,css:''})); + const output = await exportFigure(figure('bottom'), browser); + expect(output.receipt.routing).toContain('xyflow@'); expect(output.html).toContain('data-source-position="bottom"'); + await expect(exportFigure(figure('unknown'), browser)).rejects.toThrow('Invalid declared connector position'); +}); diff --git a/packages/render/test/figure.test.ts b/packages/render/test/figure.test.ts new file mode 100644 index 0000000..a333ddb --- /dev/null +++ b/packages/render/test/figure.test.ts @@ -0,0 +1,71 @@ +import { test, expect } from 'bun:test'; +import { composeFigure, type FigureInput, type FigureComposer } from '../src/figure'; +import { defaultPresentationTheme, extendPresentationTheme } from '../src/presentation-theme'; +const input = (): FigureInput => ({ + view: {source: 'case/view.ts', title: 'Case', purpose: 'architecture', kind: 'system', question: {ref: 'ownership', text: 'Who owns this?'}, basis: {captureRef: 'design-candidate:case', modelRefs: ['project.sysml'], obligationRefs: [], evidenceRefs: []}}, + language: 'en', width: 800, questions: [{ref: 'ownership', text: 'Who owns this?', focusRefs: ['model']}], + sources: {'case/view.ts': 'composition', 'project.sysml': 'model source'}, references: {model: 'project.sysml'}, +}); +export const minimal: FigureComposer = ({question, icon, text}) => ({markup: `

${text(question('ownership').text)}

${icon('library')}
`, css: '[data-figure]{padding:24px}h1{font-size:32px}'}); +export { input as fixture }; +test('HTML composition retains questions, captured sources and empty evidence without a fixed template', () => { + const original = input(), before = JSON.stringify(original), figure = composeFigure(original, minimal); + expect(figure.html).toContain('Who owns this?'); + expect(figure.html).toContain('figure-provenance'); + expect(figure.html).toContain('"evidenceRefs":[]'); + expect(figure.html).not.toContain('Design at a glance'); + expect(JSON.stringify(original)).toBe(before); +}); +test('a question selects its composition and must have a visible region', () => { + expect(() => composeFigure(input(), () => ({markup: '
hidden questions
', css: ''}))).toThrow('no visible region'); + expect(() => composeFigure(input(), () => ({markup: '
', css: ''}))).toThrow('Unselected rendered question'); + expect(() => composeFigure(input(), context => { context.question('invented'); return {markup: '', css: ''}; })).toThrow('Unselected question'); +}); +test('unknown or stale source associations refuse before composition', () => { + const original = input(); + expect(() => composeFigure({...original, references: {model: 'missing.sysml'}}, minimal)).toThrow('Uncaptured'); + expect(() => composeFigure({...original, sources: {'../outside': 'source'}}, minimal)).toThrow('Composition source'); + expect(() => composeFigure({...original, questions: [{...original.questions[0]!, focusRefs: ['missing']}]}, minimal)).toThrow('Unknown question focus'); +}); +test('architecture and workflow are independent purposes', () => { + const original = input(); + expect(() => composeFigure({...original, view: {...original.view, purpose: 'workflow'} as FigureInput['view']}, minimal)).toThrow('separate'); + expect(composeFigure({...original, view: {...original.view, purpose: 'workflow', kind: 'flow'}}, minimal).purpose).toBe('workflow'); +}); +test('source/theme/icon changes invalidate rendering footprint without rewriting the model', () => { + const original = input(), a = composeFigure(original, minimal); + const changedSource = composeFigure({...original, sources: {...original.sources, 'project.sysml': 'new source'}}, minimal); + const custom = {label: 'Custom icon', svg: ''}; + const themed = composeFigure({...original, theme: extendPresentationTheme(defaultPresentationTheme, {colors: {accent: '#006655'}}), icons: {library: custom}}, minimal); + expect(changedSource.inputSHA256).not.toBe(a.inputSHA256); + expect(themed.inputSHA256).not.toBe(a.inputSHA256); + expect(themed.html).toContain('#006655'); + expect(themed.html).toContain(custom.svg); + expect(themed.sourceSHA256).toEqual(a.sourceSHA256); +}); + +test('project-owned view organizations stay open beyond node and lane examples', () => { + const original = {...input(), layout: 'domain-owned-view', presentation: {axis: 'responsibility'}}; + const figure = composeFigure(original, ({layout, presentation, question, text}) => ({ + markup: `

${text(question('ownership').text)}

${text(JSON.stringify({layout, presentation}))}
`, css: 'article{padding:20px}', + })); + expect(figure.html).toContain('domain-owned-view'); + expect(figure.html).toContain('responsibility'); + expect(figure.html).not.toContain('class="step"'); + const changed = composeFigure({...original, presentation: {axis: 'dependency'}}, minimal); + expect(changed.inputSHA256).not.toBe(figure.inputSHA256); +}); + +test('custom diagrams cannot mutate captured model or project presentation', () => { + const original = {...input(), view: {...input().view, kind:'domain-custom'}, model:{subjects:[{id:'a',type:'rectangle'}],relations:[]}, presentation:{role:'owner'}}; + expect(() => composeFigure(original, context => { Object.assign(context.model!.subjects[0]!, {id:'forged'}); return {markup:'
',css:''}; })).toThrow(); + expect(() => composeFigure(original, context => { Object.assign(context.presentation!, {role:'forged'}); return {markup:'
',css:''}; })).toThrow(); + expect(original.model.subjects[0]!.id).toBe('a'); +}); + +test('standard HTML parsing accepts native quoting and rejects inert question lookalikes', () => { + const original = input(); + expect(composeFigure(original, () => ({markup:"
Answer
",css:''})).html).toContain("data-question='ownership'"); + expect(composeFigure(original, () => ({markup:'
Answer
',css:''})).html).toContain('data-question=ownership'); + for (const markup of ['', '', '']) expect(() => composeFigure(original, () => ({markup,css:''}))).toThrow('no visible region'); +}); diff --git a/packages/render/test/opensysml.test.ts b/packages/render/test/opensysml.test.ts new file mode 100644 index 0000000..865eb9c --- /dev/null +++ b/packages/render/test/opensysml.test.ts @@ -0,0 +1,42 @@ +import { test, expect } from 'bun:test'; +import { renderedViewFromJson } from '@openmbee/opensysml'; +import { captureOpenSysMLView, projectOpenSysMLView } from '../src/opensysml'; +import { renderCatalog } from '../src'; + +const selection = { source: 'models/context.d2', title: 'SDK model projection', purpose: 'architecture' as const, kind: 'system' as const, question: { ref: 'questions/model-ownership', text: 'Which model elements are related?' }, basis: { captureRef: 'capture/model', modelRefs: ['model-capture-1'], obligationRefs: [], evidenceRefs: [] } }; +function model() { + // Decode a protojson response through the actual SDK, not a local model parser. + return renderedViewFromJson({ view: 'Demo::Context', kind: 'interconnection', stated: 'candidate', nodes: [ + { id: 'Demo::Control', name: 'Control', kind: 'part', origin: { file: 'demo.sysml', startLine: 2 } }, + { id: 'Demo::Worker', name: 'Worker', kind: 'part' }, + ], edges: [{ from: 'Demo::Control', to: 'Demo::Worker', kind: 'connection', label: 'dispatch' }] }); +} +test('SDK decoded view -> pure D2 projection -> actual image, with model identity', async () => { + const rendering = model(); + const capture = { modelHash: 'model-capture-1', requestedView: 'Demo::Context', rendering }; + const prior = JSON.stringify(capture); + const projection = projectOpenSysMLView(capture, { selection, layout: 'reflow', roles: { 'Demo::Control': 'semantic' } }); + expect(JSON.stringify(capture)).toBe(prior); + expect(projection.trace.nodes[0]!.origin?.file).toBe('demo.sysml'); + expect(projection.trace.edges[0]!.from).toBe('Demo::Control'); + const image = await renderCatalog({ sources: { [projection.source]: projection.text }, views: [projection.view], presentation: { project: 'SDK consumer' } }); + expect(image.images[0]!.nodes).toBe(2); + expect(image.images[0]!.edges).toBe(1); + const changed = projectOpenSysMLView({ ...capture, modelHash: 'model-capture-2' }, { selection, layout: 'reflow' }); + expect(changed.trace.renderingSHA256).not.toBe(projection.trace.renderingSHA256); +}, 30000); +test('unsupported SDK notation, dangling endpoints and cyclic ownership refuse', () => { + const input = { modelHash: 'm', requestedView: 'v', rendering: model() }; + input.rendering.nodes[0]!.ports = [{ id: 'p', name: 'p', type: '', direction: '' }]; + expect(() => projectOpenSysMLView(input, { selection, layout: 'reflow' })).toThrow('notation adapter'); + input.rendering = model(); + input.rendering.edges[0]!.to = 'missing'; + expect(() => projectOpenSysMLView(input, { selection, layout: 'reflow' })).toThrow('endpoint'); + input.rendering = model(); + input.rendering.nodes[0]!.parent = input.rendering.nodes[0]!.id; + expect(() => projectOpenSysMLView(input, { selection, layout: 'reflow' })).toThrow('Cyclic'); +}); +test('SDK effect failures propagate; the adapter does not fake capability or service acceptance', async () => { + const refusal = new Error('render_view capability missing'); + await expect(captureOpenSysMLView({ renderView: async () => { throw refusal; } }, 'm', 'v')).rejects.toBe(refusal); +}); diff --git a/packages/render/test/renderer.test.ts b/packages/render/test/renderer.test.ts new file mode 100644 index 0000000..10907ac --- /dev/null +++ b/packages/render/test/renderer.test.ts @@ -0,0 +1,130 @@ +import { describe, test, expect } from 'bun:test'; +import fc from 'fast-check'; +import { createCatalog, catalogDigest, renderCatalog, standardThemes, defaultTheme, extendTheme, renderView, type RenderEffects, type ViewSelection, type ViewKind } from '../src'; + +const question = { ref: 'questions/component-ownership', text: 'Which component owns execution?' }; +const basis = { captureRef: 'capture/example', modelRefs: ['model/components'], obligationRefs: ['obligations/owner'], evidenceRefs: [] as string[] }; +function selection(kind: ViewKind, source: string): ViewSelection { + const base = { source, title: `${kind} view`, question, basis }; + return kind === 'system' || kind === 'data' || kind === 'assurance' + ? { ...base, purpose: 'architecture', kind } + : { ...base, purpose: 'workflow', kind }; +} +function fixture(): { sources: Record; views: ViewSelection[]; presentation: { project: string } } { + return { + sources: { ...standardThemes(), 'docs/ownership.d2': '...@../architecture/themes/system\ncontrol: "Project control"\nrunner: "Method executor"\ncontrol -> runner: selected input\n' }, + views: [selection('system', 'docs/ownership.d2')], presentation: { project: 'Example repository' }, + }; +} +describe('question-driven captured inputs', () => { + test('canonical source order and theme/import footprint', () => { + const a = fixture(), b = fixture(); + b.sources = Object.fromEntries(Object.entries(b.sources).reverse()); + expect(catalogDigest(createCatalog(a))).toBe(catalogDigest(createCatalog(b))); + b.sources['architecture/themes/base.d2'] += '\n# changed\n'; + expect(catalogDigest(createCatalog(a))).not.toBe(catalogDigest(createCatalog(b))); + }); + test('snapshot and existing evidence references are copied; evidence changes invalidate reuse', () => { + const input = fixture(), captured = createCatalog(input); + input.sources['docs/ownership.d2'] = 'changed'; + expect(captured.fs['docs/ownership.d2']).toContain('control'); + const changed = fixture(); + changed.views = [{ ...changed.views[0]!, basis: { ...basis, evidenceRefs: ['evidence/new-observation'] } }]; + expect(catalogDigest(createCatalog(changed))).not.toBe(catalogDigest(captured)); + expect(Object.isFrozen(captured.views[0]!.basis.evidenceRefs)).toBe(true); + }); + test('functional entry refuses a different question/capture before running an effect', async () => { + const catalog = createCatalog(fixture()); + let invoked = false; + const effects: RenderEffects = { + compile: async () => { invoked = true; throw new Error('must not execute'); }, + svg: async () => '', + }; + await expect(renderView(catalog, { ...catalog.views[0]!, question: { ...question, ref: 'other' } }, effects)).rejects.toThrow('captured catalog'); + expect(invoked).toBe(false); + }); + test('architecture and workflow remain distinct contracts', () => { + const input = fixture(); + expect(() => createCatalog({ ...input, views: [{ ...input.views[0]!, purpose: 'workflow' } as ViewSelection] })).toThrow('separate'); + }); + test('a missing question/capture, duplicate or missing source refuses', () => { + const input = fixture(); + expect(() => createCatalog({ ...input, views: [{ ...input.views[0]!, question: { ref: '', text: '' } }] })).toThrow('question'); + delete input.sources['docs/ownership.d2']; + expect(() => createCatalog(input)).toThrow('Missing view source'); + const duplicate = fixture(); + duplicate.views.push({ ...duplicate.views[0]! }); + expect(() => createCatalog(duplicate)).toThrow('Duplicate view'); + }); + test('repository-relative source law', () => { + fc.assert(fc.property(fc.stringMatching(/^[a-z]{1,16}$/), name => { + const input = fixture(); input.sources[`../${name}.d2`] = 'a -> b'; + expect(() => createCatalog(input)).toThrow('repository-relative'); + }), { numRuns: 64 }); + }); +}); +describe('official D2 compile/render, without TS redrawing', () => { + test('preserves source IDs, question links and produces native SVG', async () => { + const image = (await renderCatalog(fixture())).images[0]!; + expect(image.nodeIds).toEqual(['control', 'runner']); + expect(image.edges).toBe(1); + expect(image.svg).toContain('Project control'); + expect(image.question.ref).toBe(question.ref); + expect(image.basis.captureRef).toBe(basis.captureRef); + expect(image.svg).toContain(' { + const input = fixture(); + const kinds: ViewKind[] = ['system', 'routing', 'flow', 'sequence', 'state', 'data', 'assurance']; + input.views = kinds.map(kind => selection(kind, `docs/${kind}.d2`)); + for (const kind of kinds) input.sources[`docs/${kind}.d2`] = `...@../architecture/themes/${kind}\na -> b: relation`; + const before = JSON.stringify(input); + const result = await renderCatalog(input, { workers: 2 }); + expect(result.images).toHaveLength(7); + expect(result.images.every(image => image.nodes === 2 && image.edges === 1)).toBe(true); + expect(JSON.stringify(input)).toBe(before); + }, 30000); + test('native nested component ownership and endpoint selection survive', async () => { + const input = fixture(); + input.sources['docs/ownership.d2'] = 'project: {caller: Gateway}\nlibrary: {api: API; core: Core}\nproject.caller -> library.api: dependency\nlibrary.api -> library.core: composition'; + const image = (await renderCatalog(input)).images[0]!; + expect(image.nodes).toBe(5); + expect(image.nodeIds).toContain('library.api'); + expect(image.edges).toBe(2); + }, 30000); + test('D2 sequence notation stays native rather than becoming TS cards', async () => { + const input = fixture(); + input.views = [selection('sequence', 'docs/ownership.d2')]; + input.sources['docs/ownership.d2'] = 'shape: sequence_diagram\nclient -> server: request\nserver -> client: response'; + const image = (await renderCatalog(input)).images[0]!; + // Native D2 adds two actor lifelines to the two messages. + expect(image.edgeIds).toEqual(['(client -> server)[0]', '(server -> client)[0]', '(client -- )[0]', '(server -- )[0]']); + expect(image.svg).toContain('request'); + expect(image.svg).toContain('response'); + }, 30000); + test('downstream D2 profiles and official theme options replace defaults without changing the model', async () => { + const input = fixture(); + input.sources['docs/ownership.d2'] += '\ncontrol.style.fill: "#123456"'; + const theme = extendTheme(defaultTheme, { render: { pad: 48, themeID: 200 } }); + const before = JSON.stringify(defaultTheme); + const result = await renderCatalog({ ...input, theme }); + expect(result.images[0]!.svg.toLowerCase()).toContain('#123456'); + expect(result.images[0]!.nodeIds).toEqual(['control', 'runner']); + expect(JSON.stringify(defaultTheme)).toBe(before); + expect(catalogDigest(createCatalog({ ...input, theme }))).not.toBe(catalogDigest(createCatalog(input))); + }, 30000); + test('syntax and import failures return no partial result; next independent scope still works', async () => { + const input = fixture(); input.sources['docs/ownership.d2'] = 'node: {'; + await expect(renderCatalog(input)).rejects.toThrow('D2 rendering failed'); + input.sources['docs/ownership.d2'] = '...@missing'; + await expect(renderCatalog(input)).rejects.toThrow('D2 rendering failed'); + expect((await renderCatalog(fixture())).images).toHaveLength(1); + }, 30000); +}); + +test('native D2 rendering accepts a downstream architecture classification without a new framework profile', async () => { + const input = fixture(); input.views = [{...input.views[0]!,kind:'domain-ownership-map'}]; + const image = (await renderCatalog(input)).images[0]!; + expect(image.kind).toBe('domain-ownership-map'); expect(image.svg).toContain('Project control'); +}); diff --git a/packages/render/test/themes.test.ts b/packages/render/test/themes.test.ts new file mode 100644 index 0000000..87d98d8 --- /dev/null +++ b/packages/render/test/themes.test.ts @@ -0,0 +1,55 @@ +import { test, expect } from 'bun:test'; +import { composePresentationTheme, defaultPresentationTheme, presentationThemeLayers } from '../src/presentation-theme'; +import { engineeringIcons } from '../src/icons'; +test('declarative upstream themes compose without mutating their source', () => { + const before = JSON.stringify(presentationThemeLayers); + const blueprint = composePresentationTheme(['blueprint', 'compact']); + const paper = composePresentationTheme(['field-notes', 'compact']); + const dark = composePresentationTheme(['graphite', 'compact']); + expect(blueprint.colors.accent).not.toBe(paper.colors.accent); + expect(dark.colors.onAccent).not.toBe(blueprint.colors.onAccent); + expect(paper.fontFamily).not.toBe(blueprint.fontFamily); + expect(JSON.stringify(presentationThemeLayers)).toBe(before); +}); +test('downstream layers override selected properties and unknown declarations refuse', () => { + const custom = composePresentationTheme(['blueprint', 'downstream'], {...presentationThemeLayers, downstream: {colors: {accent: '#13579B'}, spacing: {gap: 18}}}); + expect(custom.colors.accent).toBe('#13579B'); + expect(custom.colors.ink).toBe(defaultPresentationTheme.colors.ink); + expect(custom.spacing.gap).toBe(18); + expect(() => composePresentationTheme(['missing'])).toThrow('Unknown declared'); +}); +test('project glyph assets are distinct semantic shapes with shared dimensions', () => { + expect(new Set(Object.values(engineeringIcons).map(icon => icon.svg)).size).toBe(Object.keys(engineeringIcons).length); + expect(Object.values(engineeringIcons).every(icon => icon.svg.includes('viewBox="0 0 32 32"'))).toBe(true); +}); + +test('visual languages change drawing and node grammar, independently of palette', () => { + const sketch = composePresentationTheme(['sketchbook', 'compact']); + const editor = composePresentationTheme(['blueprint', 'node-editor']); + expect(sketch.drawing.mode).toBe('sketch'); + expect(sketch.fontFamily).toContain('Kalam'); + expect(editor.drawing.mode).toBe('precise'); + expect(editor.stylesheet).toContain('[data-node]:before'); + expect(editor.colors).toEqual(defaultPresentationTheme.colors); + const combined = composePresentationTheme(['graphite', 'node-editor']); + expect(combined.colors.background).toBe(presentationThemeLayers.graphite!.colors!.background!); + expect(combined.stylesheet).toBe(editor.stylesheet); +}); + +test('downstream icon packs compose immutably and unknown/invalid assets refuse', async () => { + const {composeIconPacks, iconPacks} = await import('../src/icons'); + const custom = {label:'Domain asset',svg:''}; + const before = JSON.stringify(iconPacks); + const result = composeIconPacks(['engineering','domain'], {...iconPacks, domain:{library:custom}}); + expect(result.library).toEqual(custom); expect(result.capture).toEqual(iconPacks.engineering!.capture!); + expect(JSON.stringify(iconPacks)).toBe(before); + expect(() => composeIconPacks(['toString'])).toThrow('Unknown declared'); + expect(() => composeIconPacks(['bad'], {bad:{asset:{label:'',svg:''}}})).toThrow('Invalid icon'); +}); + +test('native D2 options are not limited to layout or replaced by forced defaults', async () => { + const {defaultTheme, extendTheme} = await import('../src/theme'); + expect(defaultTheme.compile.layout).toBeUndefined(); + const theme = extendTheme(defaultTheme, {compile:{layout:'dagre',fontRegular:new Uint8Array([1,2,3])}}); + expect(theme.compile.fontRegular).toEqual(new Uint8Array([1,2,3])); +}); diff --git a/packages/render/themes/assurance.d2 b/packages/render/themes/assurance.d2 new file mode 100644 index 0000000..8f9c757 --- /dev/null +++ b/packages/render/themes/assurance.d2 @@ -0,0 +1,7 @@ +# Assurance view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 104; layout-engine: elk; pad: 64 } } +direction: down +style.fill: "#F8FBF8" +classes.card.style: { fill: "#E4F2E8"; stroke: "#39735C"; font-color: "#39735C"; border-radius: 10 } +classes.title.style.font-color: "#39735C" diff --git a/packages/render/themes/base.d2 b/packages/render/themes/base.d2 new file mode 100644 index 0000000..8d64c6a --- /dev/null +++ b/packages/render/themes/base.d2 @@ -0,0 +1,23 @@ +# Shared typography and semantic status colors only. Views choose their own profile. +classes: { + column: { direction: down } + row: { direction: right } + semantic: { style: { fill: "#E5F5EE"; stroke: "#35846B"; font-color: "#195C49"; border-radius: 12; stroke-width: 1 } } + cloud: { style: { fill: "#E8F2FC"; stroke: "#5486BB"; font-color: "#254D78"; border-radius: 12; stroke-width: 1 } } + runtime: { style: { fill: "#EEEAF9"; stroke: "#8371B1"; font-color: "#514078"; border-radius: 12; stroke-width: 1 } } + ledger: { style: { fill: "#EAF0F5"; stroke: "#6A8298"; font-color: "#334F67"; border-radius: 8; stroke-width: 1 } } + policy: { style: { fill: "#FFF3DB"; stroke: "#BD9652"; font-color: "#73551F"; border-radius: 12; stroke-width: 1 } } + deny: { style: { fill: "#FCE8EC"; stroke: "#B4546A"; font-color: "#83374B"; border-radius: 12; stroke-width: 2 } } + unknown: { style: { fill: "#FFF0D9"; stroke: "#B88335"; font-color: "#79521C"; border-radius: 12; stroke-width: 2 } } + verified: { style: { fill: "#E4F4E9"; stroke: "#4B9272"; font-color: "#275F46"; border-radius: 12; stroke-width: 1 } } + note: { style: { fill: "#F0F3F7"; stroke: "#D3DBE4"; font-color: "#56657A"; font-size: 15; border-radius: 8 } } + title: { shape: text; style: { font-color: "#24334E"; font-size: 30; bold: true } } + card: { label.near: center-center; style: { font-size: 20; border-radius: 14; stroke-width: 1 } } +} +**.style.font-size: 17 +(** -> **)[*].style: { + stroke: "#7A8BA3" + stroke-width: 1 + font-color: "#53667E" + font-size: 15 +} diff --git a/packages/render/themes/data.d2 b/packages/render/themes/data.d2 new file mode 100644 index 0000000..8bf04cf --- /dev/null +++ b/packages/render/themes/data.d2 @@ -0,0 +1,7 @@ +# Data view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 103; layout-engine: elk; pad: 64 } } +direction: down +style.fill: "#FCFAF5" +classes.card.style: { fill: "#EAF0E5"; stroke: "#546F61"; font-color: "#546F61"; border-radius: 6 } +classes.title.style.font-color: "#546F61" diff --git a/packages/render/themes/flow.d2 b/packages/render/themes/flow.d2 new file mode 100644 index 0000000..ba10df6 --- /dev/null +++ b/packages/render/themes/flow.d2 @@ -0,0 +1,7 @@ +# Flow view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 4; layout-engine: elk; pad: 64 } } +direction: right +style.fill: "#F7FCFA" +classes.card.style: { fill: "#E1F5F0"; stroke: "#087F8C"; font-color: "#087F8C"; border-radius: 20 } +classes.title.style.font-color: "#087F8C" diff --git a/packages/render/themes/routing.d2 b/packages/render/themes/routing.d2 new file mode 100644 index 0000000..4f47307 --- /dev/null +++ b/packages/render/themes/routing.d2 @@ -0,0 +1,7 @@ +# Routing view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 101; layout-engine: elk; pad: 64 } } +direction: right +style.fill: "#FFFCF6" +classes.card.style: { fill: "#FFF0D7"; stroke: "#A85E20"; font-color: "#A85E20"; border-radius: 12 } +classes.title.style.font-color: "#A85E20" diff --git a/packages/render/themes/sequence.d2 b/packages/render/themes/sequence.d2 new file mode 100644 index 0000000..5847ba5 --- /dev/null +++ b/packages/render/themes/sequence.d2 @@ -0,0 +1,7 @@ +# Sequence view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 5; layout-engine: elk; pad: 64 } } +direction: down +style.fill: "#FAFBFF" +classes.card.style: { fill: "#E8EEFF"; stroke: "#3560A5"; font-color: "#3560A5"; border-radius: 6 } +classes.title.style.font-color: "#3560A5" diff --git a/packages/render/themes/state.d2 b/packages/render/themes/state.d2 new file mode 100644 index 0000000..805dfef --- /dev/null +++ b/packages/render/themes/state.d2 @@ -0,0 +1,12 @@ +# State view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 6; layout-engine: elk; pad: 64 } } +direction: right +style.fill: "#FBF9FE" +classes.card.style: { fill: "#F0E6FA"; stroke: "#7451A1"; font-color: "#7451A1"; border-radius: 30 } +classes.title.style.font-color: "#7451A1" +classes.ledger.style.border-radius: 30 +classes.policy.style.border-radius: 30 +classes.runtime.style.border-radius: 30 +classes.semantic.style.border-radius: 30 +classes.unknown.style.border-radius: 30 diff --git a/packages/render/themes/system.d2 b/packages/render/themes/system.d2 new file mode 100644 index 0000000..8b3686a --- /dev/null +++ b/packages/render/themes/system.d2 @@ -0,0 +1,7 @@ +# System view: presentation only; no protocol or proof authority. +...@base +vars: { d2-config: { theme-id: 100; layout-engine: elk; pad: 64 } } +direction: right +style.fill: "#F7F9FC" +classes.card.style: { fill: "#EAF0FB"; stroke: "#273B73"; font-color: "#273B73"; border-radius: 14 } +classes.title.style.font-color: "#273B73" diff --git a/packages/render/tsconfig.json b/packages/render/tsconfig.json new file mode 100644 index 0000000..f1ff467 --- /dev/null +++ b/packages/render/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "types": [ + "bun" + ], + "resolveJsonModule": true + }, + "include": [ + "src", + "test" + ] +}