diff --git a/.cspell.yaml b/.cspell.yaml index df8867b..da32335 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -28,6 +28,7 @@ words: - SARIF - sarifmark - sonarmark + - hotspots - versionmark - Weasy - WeasyPrint diff --git a/.github/agents/developer.agent.md b/.github/agents/developer.agent.md index a95c562..5f2b988 100644 --- a/.github/agents/developer.agent.md +++ b/.github/agents/developer.agent.md @@ -14,8 +14,12 @@ Perform software development tasks by determining and applying appropriate stand 2. **Read relevant standards** using the selection matrix in AGENTS.md 3. **Pre-flight verification** before making any changes: - List files that will be created, modified, or deleted + - For each file to be **created**, check whether a counterpart exists in the + template (URL in the `# Reference Template` section of `AGENTS.md`). + If one exists, fetch it as the starting point; adjust placeholder names and heading + depth to match the target path before writing the file - For each modified file, identify which companion artifacts need updating - (requirements, design docs, tests, review-sets) + (requirements, design docs, verification docs, tests, review-sets, README.md, user guides) - Include companion artifact updates in the work plan 4. **Execute work** following standards requirements and quality checks 5. **Formatting**: Run `pwsh ./fix.ps1` to silently apply all @@ -31,8 +35,7 @@ Perform software development tasks by determining and applying appropriate stand # Developer Agent Report **Result**: (SUCCEEDED|FAILED) - -## Work Summary +**Report**: `.agent-logs/developer-{subject}-{unique-id}.md` - **Files Modified**: {List of files created/modified/deleted} - **Languages Detected**: {Languages identified} diff --git a/.github/agents/formal-review.agent.md b/.github/agents/formal-review.agent.md index 7dd8e84..cb78ddf 100644 --- a/.github/agents/formal-review.agent.md +++ b/.github/agents/formal-review.agent.md @@ -31,12 +31,14 @@ standards from the selection matrix in AGENTS.md. 1. Download the review checklist from . If the download fails, report the failure rather than proceeding without the template. -2. Use `dotnet reviewmark --elaborate {review-set}` to get the files to review -3. Review all files holistically, checking for cross-file consistency and - compliance with the review checklist -4. Save the populated review checklist to `.agent-logs/reviews/review-report-{review-set}.md`. +2. Run `dotnet reviewmark --elaborate {review-set}`. Read all files listed under + `## Context` first — these are reference material, not under review — then review + all files listed under `## Files` holistically, using the context to understand + the intended role and scope within the broader system, and checking for cross-file + consistency and compliance with the review checklist. +3. Save the populated review checklist to `.agent-logs/reviews/review-report-{review-set}.md`. This directory holds formal review artifacts, not agent logs. -5. Generate a completion report per the AGENTS.md reporting requirements. +4. Generate a completion report per the AGENTS.md reporting requirements. # Report Template @@ -44,6 +46,7 @@ standards from the selection matrix in AGENTS.md. # Formal Review Report **Result**: (SUCCEEDED|FAILED) +**Report**: `.agent-logs/formal-review-{subject}-{unique-id}.md` ## Review Summary diff --git a/.github/agents/implementation.agent.md b/.github/agents/implementation.agent.md index 7cc0352..d94e880 100644 --- a/.github/agents/implementation.agent.md +++ b/.github/agents/implementation.agent.md @@ -28,36 +28,18 @@ The state-transitions include retrying a limited number of times: ## PLANNING State (start) -Call the **explore** agent as a sub-agent (built-in agent type) with: +Call the **planning** agent as a sub-agent (custom agent from `.github/agents/`) with: - **context**: the user's request + any previous quality findings + retry context -- **goal**: produce a verified implementation plan through these steps: - - 1. Investigate the codebase and develop a concrete implementation plan that - addresses the request - 2. **Identify companion artifact deliverables**: for every code change in the - plan, list the requirements files, design documents, and review-set entries - that must be created or updated - traceability must flow requirements → - design → code, so these are mandatory deliverables, not optional extras - 3. Review the plan for assumptions, weaknesses, and gaps - identify up to 5 - key assumptions and rate each as: - - **VERIFIED**: confirmed by codebase evidence - - **LIKELY**: consistent with codebase patterns but not directly confirmed - - **UNVERIFIED**: not confirmed by any evidence - 4. For any assumption rated UNVERIFIED or LIKELY, attempt to resolve it - through additional investigation and revise the plan to address identified - weaknesses - repeat the critique-and-strengthen cycle up to 2 additional - times if unresolved issues remain, but stop as soon as the plan is stable - 5. List up to 5 risks to the implementation - 6. Assess feasibility: can this be implemented in a single development pass? - 7. State a **recommendation**: GO or INCOMPLETE - GO if the plan is sound, or - INCOMPLETE if critical unknowns remain that only the user can resolve - -Once the explore sub-agent finishes: - -- IF recommendation is INCOMPLETE: Transition to REPORT with Result: INCOMPLETE, +- **goal**: produce a verified implementation plan, or a targeted plan to address + the identified quality issues if this is a retry + +Once the planning sub-agent finishes: + +- IF Result is FAILED: Transition to REPORT with Result: FAILED +- IF Result is INCOMPLETE: Transition to REPORT with Result: INCOMPLETE, listing the unknowns and what CAN be implemented once they are resolved -- OTHERWISE (GO): Transition to DEVELOPMENT +- OTHERWISE (SUCCEEDED): Transition to DEVELOPMENT ## DEVELOPMENT State @@ -76,7 +58,8 @@ Once the developer sub-agent finishes: Call the **quality** agent as a sub-agent (custom agent from `.github/agents/`) with: -- **context**: the user's request + development summary + files changed + previous issues (if any) +- **context**: the user's request + development summary + files changed + planning companion artifact table + + previous issues (if any) - **goal**: check the quality of the work performed for any issues Once the quality sub-agent finishes: @@ -92,6 +75,9 @@ Once the quality sub-agent finishes: this agent may report INCOMPLETE when the request cannot be implemented without information only the user can provide. +For full planning details (assumptions, risks, feasibility), read the planning +report file referenced in the planning agent's response. + Generate the completion report using the template below, then save it to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` per the AGENTS.md reporting requirements, and return the summary to the caller. @@ -102,19 +88,20 @@ requirements, and return the summary to the caller. # Implementation Orchestration Report **Result**: (SUCCEEDED|FAILED|INCOMPLETE) -**Final State**: (PLANNING|DEVELOPMENT|QUALITY|REPORT) +**Report**: `.agent-logs/implementation-{subject}-{unique-id}.md` +**Last Active State**: (PLANNING|DEVELOPMENT|QUALITY) **Retry Count**: ## State Machine Execution -- **Planning Results**: {Implementation plan, assumption ratings, risks, and recommendation} +- **Planning Results**: {Planning report path; plan summary and SUCCEEDED/INCOMPLETE/FAILED result} - **Development Results**: {Summary of developer agent results} - **Quality Results**: {Summary of quality agent results} - **State Transitions**: {Log of state changes and decisions} ## Sub-Agent Coordination -- **Explore Agent (Planning)**: {Plan, assumption verdicts, top risks, GO/INCOMPLETE recommendation} +- **Planning Agent**: {Report file path, SUCCEEDED/INCOMPLETE/FAILED result, plan summary} - **Developer Agent**: {Development status and files modified} - **Quality Agent**: {Validation results and compliance status} @@ -123,4 +110,9 @@ requirements, and return the summary to the caller. - **Implementation Success**: {Overall completion status} - **Quality Compliance**: {Final quality validation status} - **Issues Resolved**: {Problems encountered and resolution attempts} + +## Unknowns (only when Result is INCOMPLETE) + +- **Unresolved Questions**: {List each question the user must answer} +- **What Can Proceed**: {Work that can be done without the missing information} ``` diff --git a/.github/agents/lint-fix.agent.md b/.github/agents/lint-fix.agent.md index 549e751..36d3ca1 100644 --- a/.github/agents/lint-fix.agent.md +++ b/.github/agents/lint-fix.agent.md @@ -68,8 +68,7 @@ submission, not during normal development. # Lint Fix Report **Result**: (SUCCEEDED|FAILED) - -## Summary +**Report**: `.agent-logs/lint-fix-{subject}-{unique-id}.md` - **Iterations**: {Number of fix-loop iterations performed} - **Files Modified**: {List of all files changed} diff --git a/.github/agents/planning.agent.md b/.github/agents/planning.agent.md new file mode 100644 index 0000000..20e75ee --- /dev/null +++ b/.github/agents/planning.agent.md @@ -0,0 +1,134 @@ +--- +name: planning +description: Planning agent that investigates the codebase, develops a verified implementation plan, and identifies all companion artifact deliverables. +user-invocable: true +--- + +# Planning Agent + +Investigate the codebase and produce a verified implementation plan with all +companion artifact deliverables. + +## Step 1 — Load Standards + +Read the relevant standards from `.github/standards/` using the selection matrix +in `AGENTS.md` based on the artifact types in scope for the request (requirements, +design, verification, documentation, code). + +## Step 2 — Investigate and Plan + +Read `docs/design/introduction.md` first (if present), then investigate the +codebase to develop a concrete implementation plan: + +- Identify all files to create, modify, or delete +- Describe the change required for each file + +## Step 3 — Identify Companion Artifact Deliverables + +For each planned change, assess the mandatory companion artifacts below (create/update/N/A +with justification): + +- **Requirements** — functional changes require a requirement entry +- **Design Documentation** — new or changed components require design docs +- **Verification Documentation** — new or changed components require verification docs +- **Tests** — functional changes require test coverage +- **Review Sets** — changes to the software item hierarchy (units or subsystems + added, removed, or reorganized) require review-set updates +- **README.md** — user-facing changes require README updates +- **User Guide** — user-facing features require user guide updates + +## Step 4 — Critique and Strengthen + +Identify up to 5 key assumptions and rate each: + +- **VERIFIED**: confirmed by codebase evidence +- **LIKELY**: consistent with codebase patterns but not directly confirmed +- **UNVERIFIED**: not confirmed by any evidence + +For UNVERIFIED or LIKELY assumptions, investigate further and revise the plan. +Repeat up to 2 more times, stopping when the plan is stable. + +## Step 5 — Risk Assessment + +List up to 5 risks with a brief mitigation for each. + +## Step 6 — Feasibility Assessment + +State whether this can be implemented in a single development pass and any +preconditions that affect feasibility. + +## Step 7 — Recommendation + +- **SUCCEEDED** — the plan is sound and the developer agent can proceed +- **INCOMPLETE** — critical unknowns remain that only the user can resolve; + list each unknown explicitly +- **FAILED** — investigation could not produce a viable plan + +# REPORT Phase + +Save the full analysis to `.agent-logs/planning-{subject}-{unique-id}.md` per +the AGENTS.md reporting requirements. + +Then respond to the caller with ONLY the lean structured summary below. + +# Report Template + +```markdown +# Planning Report + +**Result**: (SUCCEEDED|INCOMPLETE|FAILED) +**Request Summary**: {Brief restatement of the task as understood} +**Report**: `.agent-logs/planning-{subject}-{unique-id}.md` + +## Implementation Plan + +| File | Action | Description | +|------|--------|-------------| +| {path} | create/modify/delete | {what changes and why} | + +## Companion Artifact Deliverables + +| Category | File | Action | +|----------|------|--------| +| Requirements | {path} | create/update/N/A — {justification} | +| Design Documentation | {path} | create/update/N/A — {justification} | +| Verification Documentation | {path} | create/update/N/A — {justification} | +| Tests | {path} | create/update/N/A — {justification} | +| Review Sets | {path} | create/update/N/A — {justification} | +| README.md | {path} | create/update/N/A — {justification} | +| User Guide | {path} | create/update/N/A — {justification} | + +## Assumption Analysis + +| # | Assumption | Rating | Resolution | +|---|-----------|--------|------------| +| 1 | {assumption} | VERIFIED/LIKELY/UNVERIFIED | {resolution or N/A} | + +## Risk Assessment + +1. **[severity]** {risk} — {mitigation} + +## Feasibility Assessment + +{Single-pass or not, and why. Any preconditions.} + +## Unknowns + +{Only present when Result is INCOMPLETE. List each question the user must +resolve before implementation can proceed.} +``` + +# Lean Structured Response (returned to caller) + +```markdown +**Result**: (SUCCEEDED|INCOMPLETE|FAILED) +**Report**: `.agent-logs/planning-{subject}-{unique-id}.md` + +**Plan**: +{Repeat the Implementation Plan table} + +**Companion Artifacts**: +{Repeat the Companion Artifact Deliverables table} + +**Unknowns**: {Only if INCOMPLETE — list questions for the user} +``` diff --git a/.github/agents/quality.agent.md b/.github/agents/quality.agent.md index da467d4..26fd251 100644 --- a/.github/agents/quality.agent.md +++ b/.github/agents/quality.agent.md @@ -13,14 +13,23 @@ Grade and validate software development work by ensuring compliance with project 1. **Analyze the task request AND completed work** to determine scope: identify which artifact categories were changed, and which *should have been changed* given the task - new user-visible features always require requirements, - design, and review-set coverage regardless of whether those files were touched; - test-only additions (corner-case tests, defensive boundary tests, regression - tests) do not require a corresponding requirement + design, verification docs, and README/user guide updates regardless of + whether those files were touched; Review Sets are always in scope when + the software item hierarchy changes (units or subsystems added, removed, or + reorganized); test-only additions (corner-case tests, defensive boundary + tests, regression tests) do not require a corresponding requirement; if a + planning companion artifact table is provided in context, cross-reference it + — any artifact listed as create/update must be covered in the evaluation and + FAIL if the artifact was not produced 2. **Read relevant standards** using the selection matrix in AGENTS.md 3. **Evaluate all in-scope categories** - N/A only when the task genuinely cannot affect a category; if the task introduces new user-visible features or - structural changes then Requirements, Design Documentation, and Review - Management are always in scope and FAIL if the artifacts were not updated + structural changes then Requirements, Design Documentation, and Verification + Documentation are always in scope and FAIL if the artifacts were not updated; + Documentation (README/user guide) is always in scope for user-facing changes + and FAIL if not updated; Review Sets are always in scope when the + software item hierarchy changes (units or subsystems added, removed, or + reorganized) and FAIL if review-sets were not updated 4. **Validate tool compliance** using ReqStream, ReviewMark, and build tools 5. **Generate focused quality report** per the AGENTS.md reporting requirements - save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` and return the summary to the caller @@ -36,6 +45,7 @@ For each checklist item in the template below, record as `(PASS|FAIL|N/A) - {one # Quality Assessment Report **Result**: (SUCCEEDED|FAILED) +**Report**: `.agent-logs/quality-{subject}-{unique-id}.md` **Overall Grade**: (PASS|FAIL) ## Required Fixes (only when Result is FAILED) @@ -50,25 +60,17 @@ Priority-ordered list of issues that MUST be resolved for the next retry: - **Evaluated**: {List sections assessed and why} - **Skipped**: {One-line per skipped section with reason, e.g., "Design - Documentation: N/A - no design files modified"} + Documentation: N/A - no component behavior, structure, or interface changed"} ## Requirements Compliance: (PASS|FAIL|N/A) -- Were requirements updated to reflect functional changes? -- Were new requirements created for new features? -- Do requirement IDs follow semantic naming standards? -- Do requirement files follow kebab-case naming convention? -- Are requirement files organized under `docs/reqstream/` with proper folder structure? -- Are OTS requirements properly placed in `docs/reqstream/ots/` subfolder? -- Were source filters applied appropriately for platform-specific requirements? -- Is requirements traceability maintained to tests? +- Were requirements created/updated for all functional changes? +- Were source filters applied for platform-specific requirements? +- Is forward traceability from requirements to verification artifacts preserved? ## Design Documentation Compliance: (PASS|FAIL|N/A) -- Were design documents updated for architectural changes? -- Were new design artifacts created for new components? -- Do design folder names use kebab-case convention matching source structure? -- Are design files properly named ({subsystem-name}.md, {unit-name}.md patterns)? +- Were design artifacts created/updated for all new or changed components? - Is `docs/design/introduction.md` present with required Software Structure section? - Are design decisions documented with rationale? - Is system/subsystem/unit categorization maintained? @@ -76,55 +78,57 @@ Priority-ordered list of issues that MUST be resolved for the next retry: ## Code Quality Compliance: (PASS|FAIL|N/A) -- Are language-specific standards followed (from applicable standards files)? -- Are quality checks from standards files satisfied? -- Is code properly categorized (system/subsystem/unit/OTS)? -- Is appropriate separation of concerns maintained? -- Was language-specific build tooling executed and passing? +- Do language-specific quality checks from loaded standards pass? +- Is code properly categorized (system/subsystem/unit/OTS/Shared Package)? +- Does the build pass? ## Testing Compliance: (PASS|FAIL|N/A) - Were tests created/updated for all functional changes? - Is test coverage maintained for all requirements? -- Are testing standards followed (AAA pattern, etc.)? -- Do tests respect software item hierarchy boundaries (System/Subsystem/Unit scope)? +- Do tests respect software item hierarchy boundaries? - Are cross-hierarchy test dependencies documented in design docs? -- Does test categorization align with code structure? -- Do all tests pass without failures? +- Do all tests pass? -## Review Management Compliance: (PASS|FAIL|N/A) +## Verification Documentation Compliance: (PASS|FAIL|N/A) -- Were review-sets updated for structural changes (new/deleted systems, subsystems, or units)? -- Do file patterns follow include-then-exclude approach? +- Were verification documents created/updated for all new or changed components? +- Do verification documents include all mandatory sections (Verification Approach, Test Environment, + Acceptance Criteria, Test Scenarios)? +- Is requirements-to-test coverage tracked via the ReqStream trace matrix (not embedded in verification docs)? + +## Review Sets Compliance: (PASS|FAIL|N/A) + +- Were review-sets updated for structural changes? - Is review scope appropriate for change magnitude? -- Was ReviewMark tooling executed and passing? -- Were review artifacts generated correctly? +- Does ReviewMark pass? ## Documentation Compliance: (PASS|FAIL|N/A) -- Was README.md updated for user-facing changes? -- Were user guides updated for feature changes? +- Were README.md and user guides updated for user-facing changes? - Does API documentation reflect code changes? - Was compliance documentation generated? -- Does documentation follow standards formatting? -- Is documentation organized under `docs/` following standard folder structure? -- Do Pandoc collections include proper `introduction.md` with Purpose and Scope sections? - Are auto-generated markdown files left unmodified? -- Do README.md files use absolute URLs and include concrete examples? -- Is documentation integrated into ReviewMark review-sets for formal review? +- Is documentation integrated into ReviewMark review-sets? ## Software Item Completeness: (PASS|FAIL|N/A) +- Load `software-items.md` before evaluating this section. + - Does every identified software unit have its own requirements file? - Does every identified software unit have its own design document? - Does every identified subsystem have its own requirements file? - Does every identified subsystem have its own design document? +## Repository Structure Compliance: (PASS|FAIL|N/A) + +- Load `repository-map.md` from the template URL in the `# Reference Template` + section of `AGENTS.md` before evaluating this section. + +- Are parallel artifact trees in sync (reqstream/design/verification/src/test)? +- Does the repository conform to the template `repository-map.md`? + ## Process Compliance: (PASS|FAIL|N/A) -- Was Continuous Compliance workflow followed? -- Did all quality gates execute successfully? -- Were appropriate tools used for validation? -- Were standards consistently applied across work? -- Was compliance evidence generated and preserved? +- Was compliance evidence (test results, review artifacts, generated docs) generated and preserved? ``` diff --git a/.github/agents/software-architect.agent.md b/.github/agents/software-architect.agent.md index 494568d..de5efa2 100644 --- a/.github/agents/software-architect.agent.md +++ b/.github/agents/software-architect.agent.md @@ -13,7 +13,7 @@ Interview the user and produce evolving architecture documentation with prioriti # Standards Read `.github/standards/software-items.md` before starting. Use its definitions -(Software Package, System, Subsystem, Unit, OTS) as vocabulary throughout. +(Software Package, System, Subsystem, Unit, OTS, Shared Package) as vocabulary throughout. # Approach diff --git a/.github/agents/template-sync.agent.md b/.github/agents/template-sync.agent.md new file mode 100644 index 0000000..df4d488 --- /dev/null +++ b/.github/agents/template-sync.agent.md @@ -0,0 +1,140 @@ +--- +name: template-sync +description: Audits or synchronizes repository files against the canonical template. + Supports four modes - Audit, Sync, Scaffold, and Recreate. +user-invocable: true +--- + +# Template Sync Agent + +This agent is an orchestrator supporting four modes: + +- **Audit** - report structural deviations; no changes +- **Sync** - patch missing sections into existing files +- **Scaffold** - create files that do not yet exist; skip existing files +- **Recreate** - rebuild existing files from the template, migrating old content + +Read the template URL and `repository-map.md` from the `# Reference Template` +section in `AGENTS.md`, then map the requested scope onto the work groups below. +Delegate each group to a sub-agent. + +# Work Groups + +- **Root config files** - all non-collection files at the repository root +- **One group per flat `docs/` folder** - e.g. `docs/build_notes/`, `docs/user_guide/` +- **One group for root files in each of `docs/design/`, `docs/verification/`, + `docs/reqstream/`** - e.g. `docs/design/introduction.md` — separate from the + system subtrees beneath them +- **One group per system subtree** in `docs/design/`, `docs/verification/`, `docs/reqstream/` - + each subtree and all its descendants is one group + +# Orchestration + +For Audit mode, call an **explore** sub-agent (built-in) per group. +For Sync, Scaffold, and Recreate modes, call a **general-purpose** sub-agent (built-in) per group. + +For each group intersecting the requested scope, call the appropriate sub-agent with: + +- **context**: + - Group scope and template URL from the `# Reference Template` section in `AGENTS.md` + - Applicable standards from the `# Standards Application` matrix in `AGENTS.md` + for the file types in the group scope + - Project-specific names substitute for placeholders at matching path depth + (e.g. `MySystem` → `{SystemName}`, `my-system` → `{system-name}`) + - For files within `{system-name}/` subtrees in `docs/design/`, `docs/verification/`, + and `docs/reqstream/`: consult `docs/design/introduction.md` to determine whether + each item is a subsystem or unit, then select the appropriate template + (`subsystem-name.*` or `unit-name.*`) regardless of the item's folder depth — + do not infer item type from path depth alone + - If a file has no template counterpart, skip it and report it as + "No template found" — this is not a failure + - If a file appears in `repository-map.md` but its template cannot be fetched, + report Result: FAILED and list the affected files +- **goal**: + - Based on the given mode: + - **Audit** - fetch each template counterpart; compare headings; report missing + sections and depth mismatches; no changes + - **Sync** - as Audit, then insert each missing section; run `pwsh ./fix.ps1` + - **Scaffold** - fetch `repository-map.md` from the template URL in `AGENTS.md` + to identify files that should exist but don't; for each, fetch the template, + populate all sections, write the file; run `pwsh ./fix.ps1` + - **Recreate** - fetch the template and use it as the blueprint for a + freshly authored document: + - Work through the template section by section; for each section, find + any `TEMPLATE-DIRECTIVE` blocks (both `` + in markdown and `# ` in YAML) — execute + each directive (read specified standards, apply structural guidance, + substitute content), then **remove the directive block entirely** from + the output; gather the relevant technical details from all available + sources — the old file, README, related docs, sibling files, and any + other repo context — to populate that section correctly; the old file's + structure and headings are irrelevant; only its factual content is mined + as a source + - **Gap-check**: after all template sections are filled, scan the old + file once more for any technical information not yet captured; if + found, preserve it by appending new relevant sections at the end + - **Before writing**: do a mandatory self-check — for every section that + has a `TEMPLATE-DIRECTIVE` block in the template, explicitly state what + format the directive requires, then verify the drafted content matches + that format exactly (e.g. if the directive says "no sub-headings", + confirm there are no `###` headings inside that section; if it says + "bold-name paragraph blocks", confirm each entry is `**Name**: prose` + with no sub-heading); fix any mismatches before writing the file + - Write the rebuilt file; run `pwsh ./fix.ps1` + - When writing any section: `TEMPLATE-DIRECTIVE` blocks are directives — + execute them (read specified standards, apply structural guidance, substitute + content) and **remove the block entirely** from the written file; inline + `TODO:` placeholders in YAML string values (e.g. `title:`, `justification:`) + are content placeholders — always resolve them to real content; infer from + README, related files, sibling docs, and path; if confident write directly; + if ambiguous, **do not ask interactively** — return the unresolved questions + in the result so the orchestrator can ask the user and re-invoke; never leave + a TODO or TEMPLATE-DIRECTIVE in the output unless the user explicitly requests it + - Return results in this format for each file in the group: + + ```markdown + ### {file-path} + + - **Template**: {template path or "not found"} + - **Missing sections**: {list or "none"} + - **Heading depth issues**: {list or "none"} + - **Content format issues**: {list or "none"} *(Recreate only)* + - **Action**: (Reported | Sections added | Created | Rebuilt | No template found) + - **Unresolved Questions**: {list or "none"} + ``` + +If any sub-agent returns unresolved questions, collect them, ask the user, then +re-invoke the affected sub-agent(s) with the answers before assembling the final report. +If questions remain unresolved after asking the user, report Result: INCOMPLETE. + +Collect sub-agent results and assemble the final report. + +# Report Template + +```markdown +# Template Sync Report + +**Result**: (SUCCEEDED|FAILED|INCOMPLETE) +**Report**: `.agent-logs/template-sync-{subject}-{unique-id}.md` +**Mode**: (Audit|Sync|Scaffold|Recreate) + +## Files + +### {file-path} + +- **Template**: {template path} +- **Missing sections**: {list or "none"} +- **Heading depth issues**: {list or "none"} +- **Content format issues**: {list of sections where intra-section content did not + match the template comment's prescribed format, or "none"} *(Recreate only)* +- **Action**: (Reported | Sections added | Created | Rebuilt | No template found) +- **Unresolved Questions**: {list or "none"} + +## Summary + +- **Conformant**: {count} | **Deviations**: {count} | **Updated**: {count} + +## Unknowns (only when Result is INCOMPLETE) + +- **Unresolved Questions**: {List each placeholder or ambiguity the user must resolve} +``` diff --git a/.github/standards/coding-principles.md b/.github/standards/coding-principles.md index 9e67fbb..6797c61 100644 --- a/.github/standards/coding-principles.md +++ b/.github/standards/coding-principles.md @@ -3,11 +3,6 @@ name: Coding Principles description: Follow these standards when developing any software code. --- -# Coding Principles Standards - -This document defines universal coding principles and quality standards for software development within -Continuous Compliance environments. - # Core Principles ## Literate Coding @@ -20,10 +15,9 @@ All code MUST follow literate programming principles: matches design intent without reading the full codebase - **Logical Separation**: Complex functions use block comments to separate and describe logical steps within the implementation -- **Full Symbol Documentation**: ALL symbols have comprehensive documentation - because reviewers and auditors must verify every implementation detail, not - just the public interface - access-level specifics (public, protected, - private, internal, etc.) vary by language; see the language-specific standard +- **Full Symbol Documentation**: ALL symbols have comprehensive documentation — + not just the public interface, because reviewers and auditors must verify every + implementation detail. Access-level specifics vary by language; see the language-specific standard. - **Clarity Over Cleverness**: Code should be immediately understandable by team members ## API Documentation @@ -79,13 +73,13 @@ interface correctly without reading the implementation: ## Universal Anti-Patterns -- **Skip Literate Coding**: Don't skip literate programming comments - they are required for maintainability -- **Ignore Compiler Warnings**: Don't ignore compiler warnings - they exist for quality enforcement +- **Skip Literate Coding**: Don't skip literate programming comments +- **Ignore Compiler Warnings**: Don't ignore compiler warnings - **Hidden Dependencies**: Don't create untestable code with hidden dependencies - **Hidden Functionality**: Don't implement functionality without requirement traceability because untraced functionality cannot be validated during audits - **Monolithic Functions**: Don't write monolithic functions with multiple responsibilities -- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - favor simplicity and clarity +- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - **Premature Optimization**: Don't optimize for performance before establishing correctness - **Copy-Paste Programming**: Don't duplicate logic - extract common functionality into reusable components - **Magic Numbers**: Don't use unexplained constants - either name them or add clear comments diff --git a/.github/standards/csharp-language.md b/.github/standards/csharp-language.md index 6df39cd..ec05a25 100644 --- a/.github/standards/csharp-language.md +++ b/.github/standards/csharp-language.md @@ -12,9 +12,6 @@ Read these standards first before applying this standard: # API Documentation and Literate Coding Example -The example below demonstrates good XmlDoc API documentation combined with -literate coding comments. - ```csharp /// /// Converts a raw sensor reading into a validated measurement ready for downstream consumers. diff --git a/.github/standards/csharp-testing.md b/.github/standards/csharp-testing.md index 181de02..1f93b72 100644 --- a/.github/standards/csharp-testing.md +++ b/.github/standards/csharp-testing.md @@ -66,8 +66,6 @@ These are non-obvious v3 behaviors that differ from v2 or common assumptions: # Quality Checks -Before submitting C# tests, verify: - - [ ] All tests follow AAA pattern with clear section comments - [ ] Test names follow hierarchical naming pattern above - [ ] Each test verifies single, specific behavior (no shared state between tests) diff --git a/.github/standards/design-documentation.md b/.github/standards/design-documentation.md index 3b448f3..4d413c6 100644 --- a/.github/standards/design-documentation.md +++ b/.github/standards/design-documentation.md @@ -4,196 +4,117 @@ description: Follow these standards when creating design documentation. globs: ["docs/design/**/*.md"] --- -# Design Documentation Standards - -This document defines standards for design documentation within Continuous -Compliance environments, extending the general technical documentation -standards with specific requirements for software design artifacts. - -## Required Standards - -Read these standards first before applying this standard: +# Required Standards - **`technical-documentation.md`** - General technical documentation standards -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) - -# Core Principles - -Design documentation serves as the bridge between requirements and -implementation, providing detailed technical specifications that enable: - -- **Formal Code Review**: Reviewers can verify implementation matches design -- **Compliance Evidence**: Auditors can trace requirements through design to code -- **Maintenance Support**: Developers can understand system structure and interactions -- **Quality Assurance**: Testing teams can validate against detailed specifications - -# Required Structure and Documents +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) -Design documentation must be organized under `docs/design/` with folder structure -mirroring source code organization because reviewers need clear navigation from -design to implementation: +# Folder Structure ```text docs/design/ -├── introduction.md # Design overview with software structure -└── {system-name}/ # System-level design folder (one per system) - ├── {system-name}.md # System-level design documentation - ├── {subsystem-name}/ # Subsystem (kebab-case); may nest recursively - │ ├── {subsystem-name}.md # Subsystem overview and design - │ ├── {child-subsystem}/ # Child subsystem (same structure as parent) - │ └── {unit-name}.md # Unit-level design documents - └── {unit-name}.md # Top-level unit design documents (if not in subsystem) +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## ``` -## introduction.md (MANDATORY) +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. -The `introduction.md` file serves as the design entry point and MUST include -these sections because auditors need clear scope boundaries and architectural -overview: +# introduction.md (MANDATORY) -### Purpose Section +Must include: -Clear statement of the design document's purpose, audience, and regulatory -or compliance drivers. +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Software Structure**: text tree showing all Systems/Subsystems/Units/OTS/Shared items +- **Folder Layout**: text tree showing source folder structure +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` -### Scope Section +# System Design (MANDATORY) -Define what software items are covered and what is explicitly excluded. -Design documentation must NOT include test projects, test classes, or test -infrastructure because design documentation documents the architecture of -shipping product code, not ancillary content used to validate it. +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: -### Software Structure Section (MANDATORY) +- **Architecture**: software items, relationships, and collaboration +- **External Interfaces**: name, direction, format, constraints +- **Dependencies**: OTS and Shared Packages used; cross-reference their design docs +- **Risk Control Measures**: segregation required for risk control (IEC 62304 §5.3.3) +- **Data Flow**: inputs to outputs +- **Design Constraints**: platform, performance, security, regulatory -Include a text-based tree diagram showing the software organization across -System, Subsystem, and Unit levels. Agents MUST read `software-items.md` -to understand these classifications before creating this section. +# Subsystem Design (MANDATORY) -Example format: +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: -```text -Project1Name (System) -├── ComponentA (Subsystem) -│ ├── SubComponentP (Subsystem) -│ │ └── ClassW (Unit) -│ ├── ClassX (Unit) -│ └── ClassY (Unit) -├── ComponentB (Subsystem) -│ └── ClassZ (Unit) -└── UtilityClass (Unit) - -Project2Name (System) -└── HelperClass (Unit) -``` +- **Overview**: responsibility, boundaries, contained units +- **Interfaces**: what it exposes and consumes +- **Design**: how internal units collaborate -### Folder Layout Section (MANDATORY) +# Unit Design (MANDATORY) -Include a text-based tree diagram showing how the source code folders -mirror the software structure, with file paths and brief descriptions. +Place `{unit-name}.md` in the **parent** folder: -Example format: +- **Purpose**: single responsibility +- **Data Model**: fields, properties, types, invariants (IEC 62304 §5.4.2) +- **Key Methods**: name, purpose, algorithm, preconditions, postconditions, parameter types +- **Error Handling**: detection and handling; what is propagated vs. handled locally +- **Dependencies**: other units, subsystems, OTS items, and shared packages used +- **Callers**: units or subsystems that call or consume this unit -```text -src/Project1Name/ -├── ComponentA/ -│ ├── SubComponentP/ -│ │ └── ClassW.cs - Specialized processing engine -│ ├── ClassX.cs - Core business logic handler -│ └── ClassY.cs - Data validation service -├── ComponentB/ -│ └── ClassZ.cs - Integration interface -└── UtilityClass.cs - Common utility functions - -src/Project2Name/ -└── HelperClass.cs - Helper functions -``` +# OTS Integration Design (when OTS items exist) -### References Section (RECOMMENDED) +Create `docs/design/ots.md` (`#` heading) covering the overall OTS integration strategy. -If the design references external documents (standards, specifications), include -a `## References` section in `introduction.md`. This is the **only** place in the -design document collection where a References section should appear - do not add -one to any other design file. +For each OTS item, create `docs/design/ots/{ots-name}.md` (`##` heading) with sections: -### Companion Artifact Structure (RECOMMENDED) +- **Purpose**: why chosen and what it provides to the local system +- **Features Used**: which specific features, APIs, or capabilities are consumed +- **Integration Pattern**: how it is consumed; initialization, configuration, disposal requirements -Include a brief note explaining that each software item has parallel artifacts -across the repository, so agents and reviewers can navigate from any one -artifact to all related files: +# Shared Package Integration Design (when Shared Packages exist) -Example format: +Create `docs/design/shared.md` (`#` heading) covering the overall consumption strategy. -```text -Each software item in the structure above has corresponding artifacts in -parallel directory trees: - -- Requirements: `docs/reqstream/{system}/.../{item}.yaml` (kebab-case) -- Design docs: `docs/design/{system}/.../{item}.md` (kebab-case) -- Verification design: `docs/verification/{system}/.../{item}.md` (kebab-case) -- Source code: `src/{System}/.../{Item}.{ext}` (cased per language - see `software-items.md`) -- Tests: `test/{System}.Tests/.../{Item}Tests.{ext}` (cased per language - see `software-items.md`) -- Review-sets: defined in `.reviewmark.yaml` -``` +For each Shared Package, create `docs/design/shared/{package-name}.md` (`##` heading) with sections: -## System Design Documentation (MANDATORY) - -For each system identified in the repository: - -- Create a kebab-case folder matching the system name -- Include `{system-name}.md` with system-level design documentation such as: - - System architecture and major components - - External interfaces and dependencies - - Data flow and control flow - - System-wide design constraints and decisions - - Integration patterns and communication protocols - -## Subsystem and Unit Design Documents - -For each subsystem identified in the software structure: - -- Create a kebab-case folder matching the subsystem name (enables automated tooling) -- Include `{subsystem-name}.md` with subsystem overview and design -- Include unit design documents for ALL units within the subsystem - -For every unit identified in the software structure: - -- Document data models, algorithms, and key methods -- Describe interactions with other units -- Include sufficient detail for formal code review -- Place in appropriate subsystem folder or at design root level - -# Software Items Integration (CRITICAL) - -Read `software-items.md` before creating design documentation - correct -System/Subsystem/Unit categorization is required for software structure -diagrams and folder layout. +- **Advertised Features Consumed**: which features the local system relies on +- **Integration Pattern**: how the package is referenced, initialized, and consumed +- **Assumptions**: any assumptions the local system makes about the package's behavior # Writing Guidelines -Design documentation must be technical and specific because it serves as the -implementation specification for formal code review: - -- **Implementation Detail**: Provide sufficient detail for code review and implementation -- **Architectural Clarity**: Clearly define component boundaries and interfaces -- **Traceability**: Link to requirements where applicable using ReqStream patterns -- **Verbal Cross-References**: Reference other parts of the design by name (e.g., - "See *Parser Design* for more details") - do not use markdown hyperlinks, which - break in compiled PDFs - -# Mermaid Diagram Integration - -Use Mermaid diagrams to supplement text descriptions (diagrams must not replace text content). +- Use Mermaid diagrams to supplement (not replace) text +- Use verbal cross-references ("see _Parser Design_") - not markdown hyperlinks (break in PDF) +- Provide sufficient detail for formal code review +- Do not record version numbers in design documentation — they go stale with dependency updates and + are managed in SBOMs. Version numbers are pinned release versions (e.g., `1.2.3`, `v2.0.1`). + The following are **not** version numbers and are permitted: + - Language/platform standards: `netstandard2.0`, `net10.0`, `C++20`, `C# 12` (stable standard identifiers) + - Protocol standards: `TLS 1.3`, `HTTP/2` (stable specifications) + - Placeholders: `0.0.0` (signals "not yet assigned") # Quality Checks -Before submitting design documentation, verify: - -- [ ] `introduction.md` includes both Software Structure and Folder Layout sections -- [ ] Software structure correctly categorizes items as System/Subsystem/Unit per `software-items.md` -- [ ] Folder layout mirrors software structure organization -- [ ] Design documents provide sufficient detail for code review -- [ ] System documentation provides comprehensive system-level design -- [ ] Subsystem documentation folders use kebab-case names while mirroring source subsystem names and structure -- [ ] All documents follow technical documentation formatting standards -- [ ] Content is current with implementation and requirements -- [ ] Documents are integrated into ReviewMark review-sets for formal review +- [ ] `introduction.md` includes Software Structure, Folder Layout, and Companion Artifact Structure +- [ ] Software structure correctly categorizes items per `software-items.md` +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] System design includes all mandatory sections (Architecture, External Interfaces, Dependencies, + Risk Control Measures, Data Flow, Design Constraints) +- [ ] Subsystem design includes all mandatory sections (Overview, Interfaces, Design) +- [ ] Unit design includes all mandatory sections (Purpose, Data Model, Key Methods, Error Handling, Dependencies, Callers) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] `docs/design/ots.md` and `docs/design/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/design/shared.md` and `docs/design/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.github/standards/reqstream-usage.md b/.github/standards/reqstream-usage.md index 58b08b4..2371164 100644 --- a/.github/standards/reqstream-usage.md +++ b/.github/standards/reqstream-usage.md @@ -9,7 +9,7 @@ globs: ["requirements.yaml", "docs/reqstream/**/*.yaml"] Read these standards first before applying this standard: - **`requirements-principles.md`** - Requirements principles and unidirectionality -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) # Requirements Organization @@ -18,54 +18,83 @@ because ReqStream discovers files via the includes chain in `requirements.yaml` and organizes report output by this hierarchy: ```text -requirements.yaml # Root file (includes only) +requirements.yaml # Root file (includes only) docs/reqstream/ -├── {system-name}/ # System-level requirements folder (one per system) -│ ├── {system-name}.yaml # System-level requirements +├── {system-name}.yaml # System-level requirements +├── {system-name}/ # System folder (one per system) │ ├── platform-requirements.yaml # Platform support requirements -│ ├── {subsystem-name}/ # Subsystem (kebab-case); may nest recursively -│ │ ├── {subsystem-name}.yaml # Requirements for this subsystem -│ │ ├── {child-subsystem}/ # Child subsystem (same structure as parent) -│ │ └── {unit-name}.yaml # Requirements for units within this subsystem -│ └── {unit-name}.yaml # Requirements for top-level units (outside subsystems) -└── ots/ # OTS items appear as a distinct section in reports - └── {ots-name}.yaml # Requirements for OTS components +│ ├── {subsystem-name}.yaml # Subsystem requirements +│ ├── {subsystem-name}/ # Subsystem folder (kebab-case); may nest recursively +│ │ ├── {subsystem-name}.yaml # Child subsystem requirements +│ │ ├── {subsystem-name}/ # Child subsystem folder +│ │ └── {unit-name}.yaml # Unit requirements +│ └── {unit-name}.yaml # System-level unit requirements +├── ots/ # OTS items appear as a distinct section in reports +│ └── {ots-name}.yaml # Requirements for OTS components +└── shared/ # Shared Packages appear as a distinct section in reports + └── {package-name}.yaml # Requirements for Shared Package dependencies ``` +Local items have matching relative paths across `docs/reqstream/`, `docs/design/`, and `docs/verification/`: + +- Requirements: `{system-name}[/{subsystem-name}...]/{item-name}.yaml` +- Design: `{system-name}[/{subsystem-name}...]/{item-name}.md` +- Verification: `{system-name}[/{subsystem-name}...]/{item-name}.md` + # Requirements File Format -```yaml -sections: - - title: Functional Requirements - requirements: - - id: System-Component-Feature # Used as-is in all reports - make it readable - title: The system shall perform the required function. - justification: | - Business rationale and any regulatory references. - # ReqStream extracts this field into the justifications report (--justifications) - children: # ReqStream validates this decomposition chain - - ChildSystem-Feature-Behavior # Downward links only (see requirements-principles.md) - tests: # ReqStream matches these by method name in test results - - TestMethodName - - windows@PlatformSpecificTest # Only test runs on Windows count as evidence -``` +Each file adds requirements at exactly one level of the hierarchy. The file spells out +its full ancestry as nested `{ItemName} Requirements` sections down to that level, then +places requirements there. ReqStream merges identical section title paths across included +files automatically. Always determine item classification from `docs/design/introduction.md` - +folder depth does not determine whether an item is a subsystem or unit. + +Valid section nestings (names in `{braces}` are placeholders): -# OTS Software Requirements +```text +{SystemName} Requirements # system-level requirements +├── {SubsystemName} Requirements # root subsystem requirements +│ ├── {SubsystemName} Requirements # nested subsystem (may recurse) +│ │ └── {UnitName} Requirements # unit under a nested subsystem +│ └── {UnitName} Requirements # unit under a root subsystem +└── {UnitName} Requirements # unit directly under the system +OTS Software Requirements # OTS root section (fixed title) +└── {OtsName} Requirements # requirements for one OTS item +Shared Package Requirements # shared package root section (fixed title) +└── {PackageName} Requirements # requirements for one shared package +``` -Use nested sections in `docs/reqstream/ots/` because ReqStream renders the `ots/` -subtree as a distinct section in generated reports, separate from in-house -system requirements: +Each file implements one path through this tree: ```yaml sections: - - title: OTS Software Requirements + - title: '{SystemName} Requirements' sections: - - title: System.Text.Json + - title: '{SubsystemName} Requirements' requirements: - - id: TemplateTool-SystemTextJson-ReadJson - title: System.Text.Json shall be able to read JSON files. - tests: - - JsonReaderTests.TestReadValidJson + - id: System-Subsystem-Feature # Used as-is in all reports - make it readable + title: The subsystem shall perform the required function. + justification: | # ReqStream extracts this into the justifications report (--justifications) + Business rationale and any regulatory references. + tags: # Optional: categorize for filtering with --filter + - security + children: # Optional: ReqStream validates this decomposition chain + - System-Subsystem-Unit-Feat # Downward links only (see requirements-principles.md) + tests: # ReqStream matches these by method name in test results + - TestMethodName + - windows@PlatformSpecificTest # Only test runs on Windows count as evidence +``` + +# Tags (OPTIONAL) + +Tags are free-form - no mandatory vocabulary. Common tags: `security`, `safety`, `performance`, +`compliance`, `reliability`, `critical`. Use `--filter` to selectively export or enforce subsets +(OR logic across comma-separated tags): + +```bash +dotnet reqstream --requirements requirements.yaml \ + --filter security,critical \ + --report docs/requirements_doc/generated/security_requirements.md ``` # Semantic IDs (MANDATORY) @@ -120,12 +149,9 @@ dotnet reqstream --requirements requirements.yaml \ Before submitting requirements, verify: -- [ ] All requirements have semantic IDs (`System-Section-Feature` pattern) -- [ ] Every requirement links to at least one passing test +- [ ] All requirements have semantic IDs (`System-Component-Feature` pattern) +- [ ] Every requirement has a justification explaining business/regulatory need +- [ ] Every requirement links to at least one test - [ ] Platform-specific requirements use source filters (`platform@TestName`) -- [ ] Comprehensive justification explains business/regulatory need -- [ ] Files organized under `docs/reqstream/` following folder structure patterns -- [ ] Subsystem folders use kebab-case naming matching source code -- [ ] OTS requirements placed in `ots/` subfolder -- [ ] Valid YAML syntax passes yamllint validation -- [ ] Test result formats compatible (TRX, JUnit XML) +- [ ] All files and folders use kebab-case names matching source code structure +- [ ] All files are organized under `docs/reqstream/` following the folder structure above diff --git a/.github/standards/reviewmark-usage.md b/.github/standards/reviewmark-usage.md index 2f778dc..ce28dd9 100644 --- a/.github/standards/reviewmark-usage.md +++ b/.github/standards/reviewmark-usage.md @@ -9,7 +9,7 @@ description: Follow these standards when configuring file reviews with ReviewMar Read these standards first before applying this standard: -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) ## Purpose @@ -21,169 +21,226 @@ review, organizes them into review-sets, and generates review plans and reports. - **Lint Configuration**: `dotnet reviewmark --lint` - **Elaborate Review-Set**: `dotnet reviewmark --elaborate {review-set}` - **Generate Plan**: `dotnet reviewmark --plan docs/code_review_plan/generated/plan.md --enforce` - -> **Note**: `--enforce` causes the plan to fail with a non-zero exit code if any repository -> files are not covered by a review-set. Uncovered files indicate a gap in review-set -> configuration that should be addressed. + (exits non-zero if any files are uncovered) ## Repository Structure -Required repository items for ReviewMark operation: - - `.reviewmark.yaml` - Configuration for review-sets, file-patterns, and review evidence-source. -- `docs/code_review_plan/generated/` - Generated review plan (build output, do not edit) -- `docs/code_review_report/generated/` - Generated review report (build output, do not edit) # Review Definition Structure Configure reviews in `.reviewmark.yaml` at repository root: ```yaml -# Patterns identifying all files that require review needs-review: - # Include source code (adjust file extensions for your repo) - - "**/*.cs" # C# source files - - "**/*.cpp" # C++ source files - - "**/*.hpp" # C++ header files - - "!**/bin/**" # Generated source in build outputs - - "!**/obj/**" # Generated source in build intermediates - - # Include requirement files - - "requirements.yaml" # Root requirements file - - "docs/reqstream/**/*.yaml" # Requirements files - - # Include critical documentation files - - "README.md" # Root level README - - "docs/user_guide/**/*.md" # User guide - - "docs/design/**/*.md" # Design documentation - - "docs/verification/**/*.md" # Verification design documentation - -# Source of review evidence + - "**/*.cs" + - "**/*.cpp" + - "**/*.hpp" + - "!**/bin/**" + - "!**/obj/**" + - "requirements.yaml" + - "docs/reqstream/**/*.yaml" + - "README.md" + - "docs/user_guide/**/*.md" + - "docs/design/**/*.md" + - "docs/verification/**/*.md" + evidence-source: type: none + +context: + - docs/design/introduction.md + +reviews: + - id: Purpose + title: Review that README and User Guide are Coherent and Complete + paths: + - "README.md" + - "docs/user_guide/**/*.md" + - id: Decomposition + title: Review that {SystemName} Decomposition Addresses the Stated Purpose + context: + - "README.md" + - "docs/user_guide/**/*.md" + paths: + - "requirements.yaml" + - "docs/design/introduction.md" ``` -# Review-Set Design Principles +For a complete annotated example with template directives, see `.reviewmark.yaml` in the +reference template (`{template-url}/.reviewmark.yaml` per `AGENTS.md`). -When constructing review-sets, follow these principles to maintain manageable scope and effective compliance evidence: +# Review-Set Design Principles - **Hierarchical Scope**: Higher-level reviews exclude lower-level implementation details, relying instead on design documents to describe what components they use. System reviews exclude subsystem/unit details, subsystem reviews exclude unit source code, only unit reviews include actual implementation. - **Single Focus**: Each review-set proves one specific compliance question (user promises, system architecture, design consistency, etc.) +- **Parent Context**: Unit and subsystem reviews include parent design and requirements as + context so reviewers understand the intended role and scope; see the Context Files section. - **Context Management**: Keep file counts manageable to prevent context overflow while maintaining complete coverage through the hierarchy -# Review-Set Organization +# Context Files -Organize review-sets using these standard patterns to ensure comprehensive coverage -while keeping each review manageable in scope: +Context files are shown to reviewers for orientation but not fingerprinted. Add a top-level +`context:` key for global context (every reviewer) and a per-review-set `context:` between +`title:` and `paths:` for review-specific context. Always include `docs/design/introduction.md` +as global context. -**Naming conventions**: See `software-items.md` - kebab-case placeholders -(e.g., `{system-name}`) are always kebab-case; cased placeholders -(e.g., `{SystemName}`) follow your language's convention. +| Review Type | Context to add | +| :---------- | :------------- | +| `Decomposition` | `README.md`, `docs/user_guide/**/*.md` | +| `{SystemName}-Architecture` | `README.md`, `docs/user_guide/**/*.md` | +| `{SystemName}-Design` | `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-Verification` | `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-AllRequirements` | Parent system design doc + `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-{UnitName}` (direct unit) | Parent system design doc + parent system requirements | +| `{SystemName}-{SubsystemName}` (subsystem) | Parent system design doc + parent system requirements | +| `{SystemName}-{SubsystemName}-{UnitName}` (unit under subsystem) | System + subsystem design docs + requirements | -## `Purpose` Review (only one per repository) +# Review-Set Organization -Reviews user-facing capabilities and system promises: +**Naming conventions**: Placeholders in documentation, requirements, design, and +verification file paths are kebab-case (e.g., `{system-name}`). Placeholders in +source and test file paths may use the casing conventional for the project's +source language or repository layout (e.g., `{SystemName}`). Review-set name +placeholders are always PascalCase (e.g., `{SystemName}`). + +## `Purpose` Review (only one per repository) -- **Purpose**: Proves that the systems provide the capabilities the user is being told about -- **Title**: "Review that Advertised Features Match System Design" -- **Scope**: Excludes subsystem and unit files, relying on system-level design documents - to describe what subsystems and units they use +- **Purpose**: Proves that the user-facing docs are coherent and complete — the north-star for the hierarchy +- **Title**: "Review that README and User Guide are Coherent and Complete" +- **ID**: `Purpose` (no system prefix — one per repository) +- **Scope**: README and user_guide only; no requirements or design files - **File Path Patterns**: - README: `README.md` - User guide: `docs/user_guide/**/*.md` - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` - - Design introduction: `docs/design/introduction.md` - - System design: `docs/design/{system-name}/{system-name}.md` -## `{System}-Architecture` Review (one per system) +## `Decomposition` Review (only one per repository) + +- **Purpose**: Proves that the software items tree breakdown logically addresses the user-facing + promise; the structural mirror of the decomposition decision +- **Title**: "Review that {SystemName} Decomposition Addresses the Stated Purpose" +- **ID**: `Decomposition` (no system prefix — one per repository) +- **Scope**: introduction.md (the decomposition narrative) and requirements.yaml + (the structural tree); no system-level detail +- **File Path Patterns**: + - Root requirements: `requirements.yaml` + - Design introduction: `docs/design/introduction.md` +- **Context Files**: `README.md`, `docs/user_guide/**/*.md` -Reviews system architecture and operational validation: +## `{SystemName}-Architecture` Review (one per system) - **Purpose**: Proves that the system is designed and tested to satisfy its requirements -- **Title**: "Review that {System} Architecture Satisfies Requirements" +- **Title**: "Review that {SystemName} Architecture Satisfies Requirements" - **Scope**: Excludes subsystem and unit files, relying on system-level design to describe what subsystems and units it uses - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` + - System requirements: `docs/reqstream/{system-name}.yaml` - Design introduction: `docs/design/introduction.md` - - System design: `docs/design/{system-name}/{system-name}.md` + - System design: `docs/design/{system-name}.md` - Verification introduction: `docs/verification/introduction.md` - - System verification design: `docs/verification/{system-name}/{system-name}.md` + - System verification design: `docs/verification/{system-name}.md` - System integration tests: `test/{SystemName}.Tests/{SystemName}Tests.{ext}` +- **Context Files**: `README.md`, `docs/user_guide/**/*.md` -## `{System}-Design` Review (one per system) - -Reviews architectural and design consistency: +## `{SystemName}-Design` Review (one per system) - **Purpose**: Proves the system design is consistent and complete -- **Title**: "Review that {System} Design is Consistent and Complete" +- **Title**: "Review that {SystemName} Design is Consistent and Complete" - **Scope**: Only brings in top-level requirements and relies on brevity of design documentation +- **Context Files**: `docs/reqstream/{system-name}.yaml` - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` - Platform requirements: `docs/reqstream/{system-name}/platform-requirements.yaml` - Design introduction: `docs/design/introduction.md` + - System design: `docs/design/{system-name}.md` - System design files: `docs/design/{system-name}/**/*.md` + - OTS overview: `docs/design/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/design/shared.md` _(only if Shared Package items exist)_ -## `{System}-AllRequirements` Review (one per system) +## `{SystemName}-Verification` Review (one per system) -Reviews requirements quality and traceability: +- **Purpose**: Proves the system verification design is consistent and covers all requirements +- **Title**: "Review that {SystemName} Verification is Consistent and Complete" +- **Scope**: Only brings in top-level requirements and all verification docs for the system +- **Context Files**: `docs/reqstream/{system-name}.yaml` +- **File Path Patterns**: + - Verification introduction: `docs/verification/introduction.md` + - System verification: `docs/verification/{system-name}.md` + - System verification files: `docs/verification/{system-name}/**/*.md` + - OTS overview: `docs/verification/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/verification/shared.md` _(only if Shared Package items exist)_ + +## `{SystemName}-AllRequirements` Review (one per system) - **Purpose**: Proves the requirements are consistent and complete -- **Title**: "Review that All {System} Requirements are Complete" +- **Title**: "Review that All {SystemName} Requirements are Complete" - **Scope**: Only brings in requirements files to keep review manageable - **File Path Patterns**: - - Root requirements: `requirements.yaml` - - System requirements: `docs/reqstream/{system-name}/**/*.yaml` - - OTS requirements: `docs/reqstream/ots/**/*.yaml` (if applicable) + - Subsystem/unit requirements: `docs/reqstream/{system-name}/**/*.yaml` +- **Context Files**: `docs/design/{system-name}.md`, `docs/reqstream/{system-name}.yaml` -## `{System}-{Subsystem[-Child...]}` Review (one per subsystem at any depth) - -Reviews subsystem architecture and interfaces: +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]` Review (one per subsystem at any depth) - **Purpose**: Proves that the subsystem is designed and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} Satisfies Subsystem Requirements" +- **Title**: "Review that {SystemName} {SubsystemName} Satisfies Subsystem Requirements" - **Scope**: Excludes units under the subsystem, relying on subsystem design to describe what units it uses - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{subsystem-name}/{subsystem-name}.yaml` - - Design: `docs/design/{system-name}/.../{subsystem-name}/{subsystem-name}.md` - - Verification design: `docs/verification/{system-name}/.../{subsystem-name}/{subsystem-name}.md` - - Tests: `test/{SystemName}.Tests/.../{SubsystemName}/{SubsystemName}Tests.{ext}` - -## `{System}-{Subsystem[-Child...]}-{Unit}` Review (one per unit) + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{subsystem-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Tests: `test/{SystemName}.Tests[/{SubsystemName}...]/{SubsystemName}Tests.{ext}` +- **Context Files**: `docs/design/{system-name}.md`, `docs/reqstream/{system-name}.yaml` -Reviews individual software unit implementation: +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]-{UnitName}` Review (one per unit) - **Purpose**: Proves the unit is designed, implemented, and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} {Unit} Implementation is Correct" +- **Title**: "Review that {SystemName} {SubsystemName} {UnitName} Implementation is Correct" - **Scope**: Complete unit review including all artifacts - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{unit-name}.yaml` - - Design: `docs/design/{system-name}/.../{unit-name}.md` - - Verification design: `docs/verification/{system-name}/.../{unit-name}.md` - - Source: `src/{SystemName}/.../{UnitName}.{ext}` - - Tests: `test/{SystemName}.Tests/.../{UnitName}Tests.{ext}` + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{unit-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Source (C# example): `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` + - Tests (C# example): `test/{SystemName}.Tests[/{SubsystemName}...]/{UnitName}Tests.cs` + - Source (snake_case C++ example): `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` + - Tests (snake_case C++ example): `test/{system_name}_tests[/{subsystem_name}...]/{unit_name}_tests.cpp` +- **Context Files**: Parent system design + requirements; add subsystem design + + requirements for each subsystem level above the unit. + +## `OTS-{OtsName}` Review (one per OTS item) + +- **Purpose**: Proves that the OTS item provides the required functionality and is correctly integrated +- **Title**: "Review that {OtsName} Provides Required Functionality" +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence +- **File Path Patterns**: + - OTS requirements: `docs/reqstream/ots/{ots-name}.yaml` + - OTS integration design: `docs/design/ots/{ots-name}.md` + - OTS verification: `docs/verification/ots/{ots-name}.md` + - Tests (if applicable): `test/OtsSoftwareTests/...` (C#) or `test/ots_software_tests/...` + (Python/other) — fixed repo-level name, no system prefix + +## `Shared-{PackageName}` Review (one per Shared Package) + +- **Purpose**: Proves that the Shared Package provides the required advertised features and is correctly integrated +- **Title**: "Review that {PackageName} Provides Required Features" +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence +- **File Path Patterns**: + - Shared Package requirements: `docs/reqstream/shared/{package-name}.yaml` + - Shared Package integration design: `docs/design/shared/{package-name}.md` + - Shared Package verification: `docs/verification/shared/{package-name}.md` **Note**: File path patterns use `{ext}` as a placeholder for language-specific extensions (`.cs`, `.cpp`/`.hpp`, `.py`, etc.). Adapt to your repository's languages. # Quality Checks -Before submitting ReviewMark configuration, verify: - - [ ] `.reviewmark.yaml` exists at repository root with proper structure - [ ] Review-set organization follows the standard hierarchy patterns -- [ ] Purpose review-set includes README.md, user guide, system requirements, design introduction, and system design files -- [ ] System-level reviews follow hierarchical scope principle (exclude subsystem/unit details) -- [ ] Subsystem reviews follow hierarchical scope principle (exclude unit source code) -- [ ] Only unit reviews include actual source code files -- [ ] Architecture review-sets include system verification design alongside system design -- [ ] Subsystem review-sets include subsystem verification design -- [ ] Unit review-sets include unit verification design - [ ] Each review-set focuses on a single compliance question (single focus principle) - [ ] File patterns use correct glob syntax and match intended files - [ ] Review-set file counts remain manageable (context management principle) +- [ ] Context configured per the Context Files section diff --git a/.github/standards/software-items.md b/.github/standards/software-items.md index 4e5c90e..6c29525 100644 --- a/.github/standards/software-items.md +++ b/.github/standards/software-items.md @@ -3,20 +3,16 @@ name: Software Items description: Follow these standards when categorizing software components. --- -# Software Items Definition Standards - -This document defines standards for categorizing software items within -Continuous Compliance environments because proper categorization determines -requirements management approach, testing strategy, and review scope. - # Software Item Categories -Categorize all software into five primary groups: +Categorize all software into six primary groups: - **Software Package**: Distributable unit delivered to end users or dependent systems, containing one software system with all its components. All software - systems are delivered as a software package. When consumed by another system, - our software package is treated as an OTS Software Item by that system. + systems are delivered as a software package. When consumed by a system outside + the producing program, our software package is treated as an OTS Software Item + by that system. When consumed by another repository within the same program, + it is treated as a Shared Package. - **Software System**: Complete deliverable product including all components and external interfaces, contained within a software package - **Software Subsystem**: Major architectural component with well-defined @@ -24,7 +20,11 @@ Categorize all software into five primary groups: - **Software Unit**: Individual class, function, or tightly coupled set of functions that can be tested in isolation - **OTS Software Item**: Third-party component (library, framework, tool, or - published software package) providing functionality not developed in-house + published software package) providing functionality not developed within the program +- **Shared Package**: A software package produced by a different repository within + the same program, consumed as a dependency. Referenced by its advertised features + rather than internal design; traceability to program-level requirements runs + through the top-level project. **Naming**: When names collide in hierarchy, add descriptive suffix to higher-level entity: @@ -34,17 +34,28 @@ Categorize all software into five primary groups: # Naming Conventions in File Path Patterns -Two placeholder styles appear in path patterns across these standards: +Three placeholder forms appear in path patterns across these standards: -- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case - - used in documentation and requirements paths -- **Cased** (`{SystemName}`, `{UnitName}`): follow your language's convention - - `PascalCase` for C#/Java, `snake_case` for C++/Python - - used in source and test file paths +- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case — + documentation and requirements file paths +- **PascalCase IDs** (`{SystemName}`, `{UnitName}`): always PascalCase — + requirements IDs, ReviewMark IDs, and other documentation identifiers +- **Language-cased** (`{SystemName}` or `{system_name}`): follow your language's + convention — `PascalCase` for C#/Java, `snake_case` for C++/Python — + source and test file/folder names -# Categorization Guidelines +## Nesting Depth Notation -Choose the appropriate category based on scope and testability: +Subsystems nest to any depth. Patterns use bracket-ellipsis to express this without +enumerating levels — `[/{subsystem-name}...]` in paths, `[-{SubsystemName}...]` in +dash-separated IDs. Examples covering all three forms: + +- `{SystemName}[-{SubsystemName}...]-{UnitName}-Feature` (PascalCase ID) +- `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` (kebab-case doc path) +- `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` (C# source path) +- `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` (C++/Python source path) + +# Categorization Guidelines ## Software Package @@ -75,12 +86,37 @@ Choose the appropriate category based on scope and testability: ## OTS Software Item -- External dependency not developed in-house - typically a third-party published +- External dependency from outside the program - typically a third-party published software package (NuGet, npm, etc.), hosted service, or tool -- Our own published software package becomes an OTS item to any system that - consumes it +- A package produced by an unrelated program (inside or outside the organization) + is treated as OTS by any consuming system - Tested through integration tests proving required functionality works - Examples: System.Text.Json, Entity Framework, third-party APIs +- **Artifact locations** (OTS items have no internal design documentation): + - Requirements: `docs/reqstream/ots/{ots-name}.yaml` + - Design: `docs/design/ots/{ots-name}.md` (integration/usage design) + - Verification: `docs/verification/ots/{ots-name}.md` + - These folders sit parallel to system folders (not inside any system folder) +- System design documentation records which OTS items each system depends on +- **OTS test project**: If no other verification evidence is available (e.g., vendor test results, + published compliance reports), a dedicated test project holds OTS integration tests - one test + file per OTS item requiring tests. OTS items are repo-level (not per-system), so the project + uses a fixed repo-level name: `test/OtsSoftwareTests/` (C#) or `test/ots_software_tests/` + (Python/other) — never prefixed with a system or project name. + +## Shared Package + +- A software package produced by a different repository within the same program +- The consuming repository references advertised features, not internal design or source +- Traceability to program-level requirements runs through the top-level project, + not directly between repositories +- Verified through any appropriate approach in the consuming repository - most commonly + downstream integration tests that transitively prove the advertised features are functional +- **Artifact locations** (no internal design documentation in the consuming repository): + - Requirements: `docs/reqstream/shared/{package-name}.yaml` + - Design: `docs/design/shared/{package-name}.md` (integration/usage design) + - Verification: `docs/verification/shared/{package-name}.md` + - These folders sit parallel to system and OTS folders # Software Item Artifact Model @@ -89,7 +125,8 @@ unit - because reviewing any one artifact in isolation cannot determine whether the item is correct, well-designed, and proven to work: - **Requirements** - WHAT the item must do (drives all other artifacts; applies to all item types) -- **Design** - HOW the item satisfies its requirements (in-house items only: system, subsystem, unit) +- **Design** - HOW the item satisfies its requirements (full design for local items: system, + subsystem, unit; integration/usage design for OTS and Shared Package) - **Verification Design** - HOW the requirements will be tested (applies to all item types) -- **Source code** - The implementation of the design (in-house units only) +- **Source code** - The implementation of the design (local units only; not applicable to OTS or Shared Package) - **Tests** - PROOF the item does WHAT it is required to do (applies to all item types) diff --git a/.github/standards/technical-documentation.md b/.github/standards/technical-documentation.md index 7ff5b5a..23893bd 100644 --- a/.github/standards/technical-documentation.md +++ b/.github/standards/technical-documentation.md @@ -6,9 +6,6 @@ globs: ["docs/**/*.md", "README.md", "!docs/**/generated/**"] # Technical Documentation Standards -This document defines standards for technical documentation within Continuous -Compliance environments. - # Core Principles Technical documentation serves as compliance evidence and must be structured @@ -40,8 +37,11 @@ docs/{collection}/ Without `title.txt` and `definition.yaml` the pipeline cannot generate the document. When creating a new document collection, create these three files together and use -the existing collections under `docs/` as templates - they share a consistent -structure across all collections. +the existing collections under `docs/` as templates. + +The `generated/` folder is **never committed** to the repository - it is created +locally and in CI by the build pipeline. Do not flag its absence as a conformance +issue. **`title.txt`** - YAML front matter with document metadata. Use the existing files under `docs/` as a pattern and keep fields consistent with the rest of @@ -81,23 +81,34 @@ elsewhere causes duplicate sections in the compiled PDF. ## Document Ordering -List documents in logical reading order in Pandoc configuration because -readers need coherent information flow from general to specific topics. +List documents in logical reading order in `definition.yaml`. + +## Heading Depth Rule (MANDATORY) + +A file's top-level heading depth must equal its folder depth under the document +collection root - this ensures Pandoc can concatenate all files in `definition.yaml` +order and produce a coherent outline with no heading-shift configuration: + +| Folder depth | Top heading | +| --- | --- | +| 0 - collection root | `#` | +| 1 - one subfolder deep | `##` | +| 2 - two subfolders deep | `###` | +| N - N subfolders deep | `#` × (N+1) | + +Internal sections use the next heading level down (e.g. a `##` file uses `###` +for *Overview*, *Interfaces*, etc.). Deeply nested files have fewer heading levels +available - keep internal structure flat to avoid excessive nesting. # Writing Guidelines Write technical documentation for clarity and compliance verification: - **Clear and Concise**: Use direct language and avoid unnecessary complexity. - Regulatory reviewers must understand content quickly. -- **Structured Sections**: Use consistent heading hierarchy and section - organization. Enables automated processing and review. -- **Specific Examples**: Include concrete examples with actual values rather - than placeholders. Supports implementation verification. +- **Structured Sections**: Use consistent heading hierarchy and section organization. +- **Specific Examples**: Include concrete examples with actual values rather than placeholders. - **Current Information**: Keep documentation synchronized with code changes. - Outdated documentation invalidates compliance evidence. -- **Traceable Content**: Link documentation to requirements and implementation - where applicable for audit trails. +- **Traceable Content**: Link documentation to requirements and implementation where applicable. ## References Sections @@ -117,26 +128,16 @@ Instead use **verbal references** - plain prose that identifies the target by na > > Refer to the *System Requirements* document for the full specification. -Verbal references are readable by both AI agents and humans in any rendering environment. - # Markdown Format Requirements -Markdown documentation in this repository must follow the formatting standards -defined in `.markdownlint-cli2.yaml` (subject to any exclusions configured there) -for consistency and professional presentation: - -- **120 Character Line Limit**: Keep lines 120 characters or fewer for readability. - Break long lines naturally at punctuation or logical breaks. -- **No Trailing Whitespace**: Remove all trailing spaces and tabs from line - endings to prevent formatting inconsistencies. -- **Blank Lines Around Headings**: Include a blank line both before and after - each heading to improve document structure and readability. -- **Blank Lines Around Lists**: Include a blank line both before and after - numbered and bullet lists to ensure proper rendering and visual separation. -- **ATX-Style Headers**: Use `#` syntax for headers instead of underline style - for consistency across all documentation. -- **Consistent List Indentation**: Use 2-space indentation for nested list - items to maintain uniform formatting. +Follow `.markdownlint-cli2.yaml` formatting standards: + +- **120 Character Line Limit**: Keep lines 120 characters or fewer; break at punctuation or logical breaks. +- **No Trailing Whitespace**: Remove all trailing spaces and tabs. +- **Blank Lines Around Headings**: Include a blank line before and after each heading. +- **Blank Lines Around Lists**: Include a blank line before and after numbered and bullet lists. +- **ATX-Style Headers**: Use `#` syntax, not underline style. +- **Consistent List Indentation**: Use 2-space indentation for nested list items. # Auto-Generated Content (CRITICAL) @@ -147,8 +148,6 @@ build outputs that are overwritten on every CI run: respective `docs/` sections, or in `docs/generated/` for final release artifacts - **Source Modification**: Update source files (requirements YAML, `.reviewmark.yaml`, tool configuration) instead of generated output -- **Tool Integration**: Generated content integrates with CI/CD pipelines and - manual changes disrupt automation # README.md Best Practices @@ -171,20 +170,12 @@ Structure README.md for both human readers and AI agent processing: - **Code Block Languages**: Specify language for syntax highlighting and tool processing - **Clear Prerequisites**: List exact version requirements and dependencies -## Quality Guidelines - -- **Scannable Structure**: Use bullet points, headings, and short paragraphs -- **Current Examples**: Verify all code examples work with current version -- **Link Validation**: Ensure all external links are accessible and current -- **Consistent Tone**: Professional, helpful tone appropriate for technical audience - # Quality Checks Before submitting technical documentation, verify: - [ ] Documentation organized under `docs/` following standard folder structure - [ ] Pandoc collections include `introduction.md` with Purpose and Scope sections -- [ ] Content follows clear and concise writing guidelines with specific examples - [ ] No modifications made to auto-generated markdown files in compliance folders - [ ] README.md includes all required sections with absolute URLs and concrete examples - [ ] Documentation integrated into ReviewMark review-sets for formal review diff --git a/.github/standards/testing-principles.md b/.github/standards/testing-principles.md index 73974ff..917463e 100644 --- a/.github/standards/testing-principles.md +++ b/.github/standards/testing-principles.md @@ -3,11 +3,6 @@ name: Testing Principles description: Follow these standards when developing any software tests. --- -# Testing Principles Standards - -This document defines universal testing principles and quality standards for test development within -Continuous Compliance environments. - # Test Dependency Boundaries (MANDATORY) Respect software item hierarchy boundaries to ensure review-sets can validate proper architectural scope. diff --git a/.github/standards/verification-documentation.md b/.github/standards/verification-documentation.md index f6f407f..494e40f 100644 --- a/.github/standards/verification-documentation.md +++ b/.github/standards/verification-documentation.md @@ -6,123 +6,96 @@ globs: ["docs/verification/**/*.md"] # Required Standards -Read these standards first before applying this standard: - - **`technical-documentation.md`** - General technical documentation standards -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) - -# Core Principles - -Verification design is the bridge between requirements and tests - it documents HOW -requirements will be verified, enabling reviewers to confirm test completeness without -reading implementation code. +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) -# Required Structure and Documents - -Organize under `docs/verification/` mirroring the software item hierarchy: +# Folder Structure ```text docs/verification/ -├── introduction.md # Verification overview -├── {system-name}/ # System-level verification folder (one per system) -│ ├── {system-name}.md # System-level verification design -│ ├── {subsystem-name}/ # Subsystem (kebab-case); may nest recursively -│ │ ├── {subsystem-name}.md # Subsystem verification design -│ │ ├── {child-subsystem}/ # Child subsystem (same structure as parent) -│ │ └── {unit-name}.md # Unit-level verification design documents -│ └── {unit-name}.md # Top-level unit verification documents (if not in subsystem) -└── ots/ # OTS items (one verification file per OTS item) - └── {ots-name}.md # Verification evidence for each OTS item +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## ``` -## introduction.md (MANDATORY) +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. -Follow the standard `introduction.md` format from `technical-documentation.md`. Scope -covers all software items including OTS items (via self-validation if appropriate). +# introduction.md (MANDATORY) -Include a Companion Artifact Structure note so agents and reviewers can navigate from any -artifact to all related files: +Must include: -```text -In-house items have parallel artifacts in: -- Requirements: `docs/reqstream/{system}/.../{item}.yaml` (kebab-case) -- Design: `docs/design/{system}/.../{item}.md` (kebab-case) -- Verification: `docs/verification/{system}/.../{item}.md` (kebab-case) -- Source: `src/{System}/.../{Item}.{ext}` (cased per language) -- Tests: `test/{System}.Tests/.../{Item}Tests.{ext}` (cased per language) - -OTS items have parallel artifacts in: -- Requirements: `docs/reqstream/ots/{ots-name}.yaml` (kebab-case) -- Verification: `docs/verification/ots/{ots-name}.md` (kebab-case) -- Tests: `test/{OtsName}.Tests/...` (cased per language, if required) - -Review-sets: defined in `.reviewmark.yaml` -``` +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` -If the verification design references external documents (standards, specifications), include -a `## References` section in `introduction.md` only - do not add one to any other verification file. +# System Verification Design (MANDATORY) -## System Verification Design (MANDATORY) +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: -For each system, create a kebab-case folder and `{system-name}.md` covering: +- **Verification Approach**: test types (unit, integration, end-to-end), framework, project structure +- **Test Environment**: OS, runtime, external services, files, or configuration required +- **Acceptance Criteria**: what constitutes a passing system test (IEC 62304 §5.7.2) +- **Test Scenarios**: named scenarios for each system requirement -- System verification strategy and overall test approach -- Test environments and configuration required -- External interface simulation and test-harness design -- End-to-end and integration test scenarios covering system requirements -- Acceptance criteria and pass/fail conditions at the system boundary -- Coverage mapping of system requirements to system-level test scenarios +# Subsystem Verification Design (MANDATORY) -## Subsystem Verification Design (MANDATORY) +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: -For each subsystem, create a kebab-case folder and `{subsystem-name}.md` covering: +- **Verification Approach**: integration test approach and mocking at subsystem boundary +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes a passing subsystem test (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary conditions, error paths, and normal operation -- Subsystem verification strategy and integration test approach -- Dependencies that must be mocked or stubbed at the subsystem boundary -- Integration test scenarios covering subsystem requirements -- Coverage mapping of subsystem requirements to subsystem-level test scenarios +# Unit Verification Design (MANDATORY) -## Unit Verification Design (MANDATORY) +Place `{unit-name}.md` in the **parent** folder: -For each unit, create `{unit-name}.md` covering: +- **Verification Approach**: what is mocked/stubbed and why; injected vs. real dependencies +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes passing unit tests (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary values, error paths, and normal operation -- Verification approach for each unit requirement -- Named test scenarios including boundary conditions, error paths, and normal-operation cases -- Which dependencies are mocked and how they are configured -- Coverage mapping of every unit requirement to at least one named test scenario +# OTS Verification Evidence (when OTS items exist) -## OTS Verification Evidence (when OTS items are used) +Create `docs/verification/ots.md` (`#` heading) covering the overall OTS verification strategy. -For each OTS item, create `docs/verification/ots/{ots-name}.md` covering: +For each OTS item, create `docs/verification/ots/{ots-name}.md` (`##` heading) covering: +verification approach (self-validation, integration tests, vendor evidence). -- The OTS item's required functionality (reference `docs/reqstream/ots/{ots-name}.yaml`) -- Verification of each requirement (using self-validation evidence if appropriate) -- Coverage mapping of OTS requirements to test scenarios +# Shared Package Verification Evidence (when Shared Packages exist) -# Writing Guidelines +Create `docs/verification/shared.md` (`#` heading) covering the overall Shared Package verification strategy. -- **Test Coverage**: Map every requirement to at least one named test scenario so - reviewers can verify completeness without reading test code -- **Scenario Clarity**: Name each scenario clearly - "Valid input returns parsed result" not "Test 1" -- **Boundary Conditions**: Call out boundary values, error inputs, and edge cases explicitly -- **Isolation Strategy**: Describe what is mocked or stubbed and why at each level -- **Traceability**: Link to requirements where applicable using ReqStream patterns -- **Verbal Cross-References**: Reference other documents by name - do not use markdown - hyperlinks, which break in compiled PDFs +For each Shared Package, create `docs/verification/shared/{package-name}.md` (`##` heading) covering: +verification approach. + +# Writing Guidelines -Mermaid diagrams may supplement text descriptions where test flow benefits from visual -representation, but must not replace text content. +- Name scenarios clearly ("Valid input returns parsed result", not "Test 1") +- Use verbal cross-references - not markdown hyperlinks (break in PDF) +- Use Mermaid diagrams to supplement (not replace) text # Quality Checks -Before submitting verification documentation, verify: - -- [ ] Every requirement at each level is mapped to at least one named test scenario -- [ ] System verification documents cover end-to-end and integration scenarios -- [ ] Subsystem verification documents identify mocked boundaries and integration scenarios -- [ ] Unit verification documents identify individual scenarios including boundary and error paths -- [ ] Subsystem documentation folders use kebab-case names mirroring the source subsystem structure -- [ ] All documents follow technical documentation formatting standards -- [ ] Content is current with requirements and test implementation -- [ ] Every OTS item has `docs/verification/ots/{ots-name}.md` with requirement coverage -- [ ] Documents are integrated into ReviewMark review-sets for formal review +- [ ] `introduction.md` includes Companion Artifact Structure +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] Each system/subsystem/unit file includes all mandatory sections (Verification Approach, + Test Environment, Acceptance Criteria, Test Scenarios) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] Requirements-to-test coverage is tracked via the ReqStream trace matrix, not in these documents +- [ ] `docs/verification/ots.md` and `docs/verification/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/verification/shared.md` and `docs/verification/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.reviewmark.yaml b/.reviewmark.yaml index 0c82d5f..ef23f2f 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -26,38 +26,32 @@ evidence-source: type: url location: https://raw.githubusercontent.com/demaconsulting/Rendering/reviews/index.json +# Global context: shown to every reviewer for orientation, not fingerprinted. +context: + - "docs/design/introduction.md" + # Review sets following hierarchical scope principles. # Each review-set focuses on a single compliance question with manageable file counts. reviews: - # Purpose + # Purpose (only one per repository) - id: Purpose - title: Review of user-facing capabilities and system promises + title: Review that README and User Guide are Coherent and Complete paths: - "README.md" - "docs/user_guide/**/*.md" + context: + - "!docs/design/introduction.md" + + # Decomposition (only one per repository) + - id: Decomposition + title: Review that Rendering Decomposition Addresses the Stated Purpose + paths: + - "requirements.yaml" - "docs/design/introduction.md" - - "docs/verification/introduction.md" - # NamespaceDoc.cs files are namespace-level documentation carriers, not - # functional code: each is an `internal static class NamespaceDoc` whose - # XML ApiMark renders as the namespace landing-page description. - # They advertise the packages' capabilities and "start here" orientation, - # so they are reviewed here alongside the other user-facing promises. - # (They are also caught by `needs-review: **/*.cs`; this groups them into - # the correct compliance question.) - - "src/**/NamespaceDoc.cs" - # Per the ReviewMark standard, the Purpose review also carries the system-level - # requirements and system-level design so the advertised capabilities can be - # checked against what each system promises (unit detail is excluded). - - "docs/reqstream/rendering/rendering.yaml" - - "docs/reqstream/rendering-abstractions/rendering-abstractions.yaml" - - "docs/reqstream/rendering-layout/rendering-layout.yaml" - - "docs/reqstream/rendering-svg/rendering-svg.yaml" - - "docs/reqstream/rendering-skia/rendering-skia.yaml" - - "docs/design/rendering/rendering.md" - - "docs/design/rendering-abstractions/rendering-abstractions.md" - - "docs/design/rendering-layout/rendering-layout.md" - - "docs/design/rendering-svg/rendering-svg.md" - - "docs/design/rendering-skia/rendering-skia.md" + - "docs/design/ots.md" + context: + - "README.md" + - "docs/user_guide/**/*.md" # ===================================================================== # Rendering (Model) system @@ -65,26 +59,45 @@ reviews: - id: Rendering-Model-Architecture title: Review that Rendering Model Architecture Satisfies Requirements paths: - - "docs/reqstream/rendering/rendering.yaml" + - "docs/reqstream/rendering.yaml" - "docs/design/introduction.md" - - "docs/design/rendering/rendering.md" + - "docs/design/rendering.md" - "docs/verification/introduction.md" - - "docs/verification/rendering/rendering.md" + - "docs/verification/rendering.md" + # NamespaceDoc.cs (ApiMark namespace landing-page doc) reviewed here as part + # of system architecture, not under the repo-wide Purpose review. + - "src/DemaConsulting.Rendering/NamespaceDoc.cs" + context: + - "README.md" + - "docs/user_guide/**/*.md" - id: Rendering-Model-Design title: Review that Rendering Model Design is Consistent and Complete paths: - - "docs/reqstream/rendering/rendering.yaml" - "docs/reqstream/rendering/platform-requirements.yaml" - "docs/design/introduction.md" - "docs/design/rendering/**/*.md" + - "docs/design/ots.md" + context: + - "docs/reqstream/rendering.yaml" + + - id: Rendering-Model-Verification + title: Review that Rendering Model Verification is Consistent and Complete + paths: + - "docs/verification/introduction.md" + - "docs/verification/rendering.md" + - "docs/verification/rendering/**/*.md" + - "docs/verification/ots.md" + context: + - "docs/reqstream/rendering.yaml" - id: Rendering-Model-AllRequirements title: Review that All Rendering Model Requirements are Complete paths: - - "requirements.yaml" - "docs/reqstream/rendering/**/*.yaml" - - "docs/reqstream/ots/**/*.yaml" + context: + - "docs/design/rendering.md" + - "docs/reqstream/rendering.yaml" - id: Rendering-Model-LayoutTree title: Review that Rendering Model LayoutTree Implementation is Correct @@ -95,6 +108,9 @@ reviews: - "src/DemaConsulting.Rendering/LayoutTree/**/*.cs" - "src/DemaConsulting.Rendering/Rect.cs" - "test/DemaConsulting.Rendering.Tests/LayoutTests.cs" + context: + - "docs/design/rendering.md" + - "docs/reqstream/rendering.yaml" - id: Rendering-Model-Options title: Review that Rendering Model Options Implementation is Correct @@ -104,6 +120,9 @@ reviews: - "docs/verification/rendering/options.md" - "src/DemaConsulting.Rendering/Options/**/*.cs" - "test/DemaConsulting.Rendering.Tests/PropertyHolderTests.cs" + context: + - "docs/design/rendering.md" + - "docs/reqstream/rendering.yaml" - id: Rendering-Model-LayoutGraph title: Review that Rendering Model LayoutGraph Implementation is Correct @@ -113,6 +132,9 @@ reviews: - "docs/verification/rendering/layout-graph.md" - "src/DemaConsulting.Rendering/Graph/**/*.cs" - "test/DemaConsulting.Rendering.Tests/LayoutGraphTests.cs" + context: + - "docs/design/rendering.md" + - "docs/reqstream/rendering.yaml" # ===================================================================== # Rendering.Abstractions system @@ -120,25 +142,42 @@ reviews: - id: Rendering-Abstractions-Architecture title: Review that Rendering Abstractions Architecture Satisfies Requirements paths: - - "docs/reqstream/rendering-abstractions/rendering-abstractions.yaml" + - "docs/reqstream/rendering-abstractions.yaml" - "docs/design/introduction.md" - - "docs/design/rendering-abstractions/rendering-abstractions.md" + - "docs/design/rendering-abstractions.md" - "docs/verification/introduction.md" - - "docs/verification/rendering-abstractions/rendering-abstractions.md" + - "docs/verification/rendering-abstractions.md" + - "src/DemaConsulting.Rendering.Abstractions/NamespaceDoc.cs" + context: + - "README.md" + - "docs/user_guide/**/*.md" - id: Rendering-Abstractions-Design title: Review that Rendering Abstractions Design is Consistent and Complete paths: - - "docs/reqstream/rendering-abstractions/rendering-abstractions.yaml" - "docs/design/introduction.md" - "docs/design/rendering-abstractions/**/*.md" + - "docs/design/ots.md" + context: + - "docs/reqstream/rendering-abstractions.yaml" + + - id: Rendering-Abstractions-Verification + title: Review that Rendering Abstractions Verification is Consistent and Complete + paths: + - "docs/verification/introduction.md" + - "docs/verification/rendering-abstractions.md" + - "docs/verification/rendering-abstractions/**/*.md" + - "docs/verification/ots.md" + context: + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-AllRequirements title: Review that All Rendering Abstractions Requirements are Complete paths: - - "requirements.yaml" - "docs/reqstream/rendering-abstractions/**/*.yaml" - - "docs/reqstream/ots/**/*.yaml" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-RenderingContracts title: Review that Rendering Abstractions RenderingContracts Implementation is Correct @@ -151,6 +190,9 @@ reviews: - "src/DemaConsulting.Rendering.Abstractions/RenderOptions.cs" - "src/DemaConsulting.Rendering.Abstractions/RenderOutput.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-Registries title: Review that Rendering Abstractions Registries Implementation is Correct @@ -161,6 +203,9 @@ reviews: - "src/DemaConsulting.Rendering.Abstractions/LayoutAlgorithmRegistry.cs" - "src/DemaConsulting.Rendering.Abstractions/RendererRegistry.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-Theme title: Review that Rendering Abstractions Theme Implementation is Correct @@ -170,6 +215,9 @@ reviews: - "docs/verification/rendering-abstractions/theme.md" - "src/DemaConsulting.Rendering.Abstractions/Theme.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/ThemeTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-NotationMetrics title: Review that Rendering Abstractions NotationMetrics Implementation is Correct @@ -179,6 +227,9 @@ reviews: - "docs/verification/rendering-abstractions/notation-metrics.md" - "src/DemaConsulting.Rendering.Abstractions/NotationMetrics.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/NotationMetricsTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-BoxMetrics title: Review that Rendering Abstractions BoxMetrics Implementation is Correct @@ -188,6 +239,9 @@ reviews: - "docs/verification/rendering-abstractions/box-metrics.md" - "src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/BoxMetricsTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" - id: Rendering-Abstractions-ConnectorLabelPlacer title: Review that Rendering Abstractions ConnectorLabelPlacer Implementation is Correct @@ -197,6 +251,9 @@ reviews: - "docs/verification/rendering-abstractions/connector-label-placer.md" - "src/DemaConsulting.Rendering.Abstractions/ConnectorLabelPlacer.cs" - "test/DemaConsulting.Rendering.Abstractions.Tests/ConnectorLabelPlacerTests.cs" + context: + - "docs/design/rendering-abstractions.md" + - "docs/reqstream/rendering-abstractions.yaml" # ===================================================================== # Rendering.Layout system (contains the Engine subsystem) @@ -204,25 +261,43 @@ reviews: - id: Rendering-Layout-Architecture title: Review that Rendering Layout Architecture Satisfies Requirements paths: - - "docs/reqstream/rendering-layout/rendering-layout.yaml" + - "docs/reqstream/rendering-layout.yaml" - "docs/design/introduction.md" - - "docs/design/rendering-layout/rendering-layout.md" + - "docs/design/rendering-layout.md" - "docs/verification/introduction.md" - - "docs/verification/rendering-layout/rendering-layout.md" + - "docs/verification/rendering-layout.md" + - "src/DemaConsulting.Rendering.Layout/NamespaceDoc.cs" + context: + - "README.md" + - "docs/user_guide/**/*.md" - id: Rendering-Layout-Design title: Review that Rendering Layout Design is Consistent and Complete paths: - - "docs/reqstream/rendering-layout/rendering-layout.yaml" - "docs/design/introduction.md" + - "docs/design/rendering-layout.md" - "docs/design/rendering-layout/**/*.md" + - "docs/design/ots.md" + context: + - "docs/reqstream/rendering-layout.yaml" + + - id: Rendering-Layout-Verification + title: Review that Rendering Layout Verification is Consistent and Complete + paths: + - "docs/verification/introduction.md" + - "docs/verification/rendering-layout.md" + - "docs/verification/rendering-layout/**/*.md" + - "docs/verification/ots.md" + context: + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-AllRequirements title: Review that All Rendering Layout Requirements are Complete paths: - - "requirements.yaml" - "docs/reqstream/rendering-layout/**/*.yaml" - - "docs/reqstream/ots/**/*.yaml" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" # Engine subsystem (excludes unit source; only its units below carry source) - id: Rendering-Layout-Engine @@ -231,6 +306,9 @@ reviews: - "docs/reqstream/rendering-layout/engine/engine.yaml" - "docs/design/rendering-layout/engine/engine.md" - "docs/verification/rendering-layout/engine/engine.md" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-Engine-OrthogonalEdgeRouter title: Review that Rendering Layout Engine OrthogonalEdgeRouter Implementation is Correct @@ -240,6 +318,11 @@ reviews: - "docs/verification/rendering-layout/engine/orthogonal-edge-router.md" - "src/DemaConsulting.Rendering.Layout/Engine/OrthogonalEdgeRouter.cs" - "test/DemaConsulting.Rendering.Layout.Tests/Engine/OrthogonalEdgeRouterTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/design/rendering-layout/engine/engine.md" + - "docs/reqstream/rendering-layout.yaml" + - "docs/reqstream/rendering-layout/engine/engine.yaml" - id: Rendering-Layout-Engine-ContainmentPacker title: Review that Rendering Layout Engine ContainmentPacker Implementation is Correct @@ -249,6 +332,11 @@ reviews: - "docs/verification/rendering-layout/engine/containment-packer.md" - "src/DemaConsulting.Rendering.Layout/Engine/ContainmentPacker.cs" - "test/DemaConsulting.Rendering.Layout.Tests/Engine/ContainmentPackerTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/design/rendering-layout/engine/engine.md" + - "docs/reqstream/rendering-layout.yaml" + - "docs/reqstream/rendering-layout/engine/engine.yaml" - id: Rendering-Layout-Engine-InterconnectionLayoutEngine title: Review that Rendering Layout Engine InterconnectionLayoutEngine Implementation is Correct @@ -258,6 +346,11 @@ reviews: - "docs/verification/rendering-layout/engine/interconnection-layout-engine.md" - "src/DemaConsulting.Rendering.Layout/Engine/InterconnectionLayoutEngine.cs" - "test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/design/rendering-layout/engine/engine.md" + - "docs/reqstream/rendering-layout.yaml" + - "docs/reqstream/rendering-layout/engine/engine.yaml" - id: Rendering-Layout-Engine-LayeredPipeline title: Review that Rendering Layout Engine LayeredPipeline Implementation is Correct @@ -267,6 +360,11 @@ reviews: - "docs/verification/rendering-layout/engine/layered-pipeline.md" - "src/DemaConsulting.Rendering.Layout/Engine/Layered/**/*.cs" - "test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/**/*.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/design/rendering-layout/engine/engine.md" + - "docs/reqstream/rendering-layout.yaml" + - "docs/reqstream/rendering-layout/engine/engine.yaml" # Rendering.Layout system-level units - id: Rendering-Layout-LayeredLayoutAlgorithm @@ -277,6 +375,9 @@ reviews: - "docs/verification/rendering-layout/layered-layout-algorithm.md" - "src/DemaConsulting.Rendering.Layout/LayeredLayoutAlgorithm.cs" - "test/DemaConsulting.Rendering.Layout.Tests/LayeredLayoutAlgorithmTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-ConnectorRouter title: Review that Rendering Layout ConnectorRouter Implementation is Correct @@ -286,11 +387,12 @@ reviews: - "docs/verification/rendering-layout/connector-router.md" - "src/DemaConsulting.Rendering.Layout/ConnectorRouter.cs" - "test/DemaConsulting.Rendering.Layout.Tests/ConnectorRouterTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - # The EdgeRouting option unit realizes the CoreOptions.EdgeRouting behavior. The - # EdgeRouting enum itself is declared in the Rendering model (covered by the model - # Options unit) and consumed by ConnectorRouter (covered above); this unit carries - # the option-realization requirements/design/verification and its own tests. + # See docs/design/rendering-layout/edge-routing-option.md for why this unit has + # no source path of its own (it realizes behavior declared/consumed elsewhere). - id: Rendering-Layout-EdgeRoutingOption title: Review that Rendering Layout EdgeRoutingOption Implementation is Correct paths: @@ -298,6 +400,9 @@ reviews: - "docs/design/rendering-layout/edge-routing-option.md" - "docs/verification/rendering-layout/edge-routing-option.md" - "test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-ContainmentLayout title: Review that Rendering Layout ContainmentLayout Implementation is Correct @@ -307,6 +412,9 @@ reviews: - "docs/verification/rendering-layout/containment-layout.md" - "src/DemaConsulting.Rendering.Layout/ContainmentLayout.cs" - "test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-ContainmentLayoutAlgorithm title: Review that Rendering Layout ContainmentLayoutAlgorithm Implementation is Correct @@ -316,6 +424,9 @@ reviews: - "docs/verification/rendering-layout/containment-layout-algorithm.md" - "src/DemaConsulting.Rendering.Layout/ContainmentLayoutAlgorithm.cs" - "test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-HierarchicalLayoutAlgorithm title: Review that Rendering Layout HierarchicalLayoutAlgorithm Implementation is Correct @@ -325,6 +436,9 @@ reviews: - "docs/verification/rendering-layout/hierarchical-layout-algorithm.md" - "src/DemaConsulting.Rendering.Layout/HierarchicalLayoutAlgorithm.cs" - "test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" - id: Rendering-Layout-DefaultLayout title: Review that Rendering Layout DefaultLayout Implementation is Correct @@ -336,6 +450,9 @@ reviews: - "src/DemaConsulting.Rendering.Layout/LayoutAlgorithms.cs" - "test/DemaConsulting.Rendering.Layout.Tests/LayoutEngineTests.cs" - "test/DemaConsulting.Rendering.Layout.Tests/LayoutAlgorithmsTests.cs" + context: + - "docs/design/rendering-layout.md" + - "docs/reqstream/rendering-layout.yaml" # ===================================================================== # Rendering.Svg system @@ -343,25 +460,42 @@ reviews: - id: Rendering-Svg-Architecture title: Review that Rendering Svg Architecture Satisfies Requirements paths: - - "docs/reqstream/rendering-svg/rendering-svg.yaml" + - "docs/reqstream/rendering-svg.yaml" - "docs/design/introduction.md" - - "docs/design/rendering-svg/rendering-svg.md" + - "docs/design/rendering-svg.md" - "docs/verification/introduction.md" - - "docs/verification/rendering-svg/rendering-svg.md" + - "docs/verification/rendering-svg.md" + - "src/DemaConsulting.Rendering.Svg/NamespaceDoc.cs" + context: + - "README.md" + - "docs/user_guide/**/*.md" - id: Rendering-Svg-Design title: Review that Rendering Svg Design is Consistent and Complete paths: - - "docs/reqstream/rendering-svg/rendering-svg.yaml" - "docs/design/introduction.md" - "docs/design/rendering-svg/**/*.md" + - "docs/design/ots.md" + context: + - "docs/reqstream/rendering-svg.yaml" + + - id: Rendering-Svg-Verification + title: Review that Rendering Svg Verification is Consistent and Complete + paths: + - "docs/verification/introduction.md" + - "docs/verification/rendering-svg.md" + - "docs/verification/rendering-svg/**/*.md" + - "docs/verification/ots.md" + context: + - "docs/reqstream/rendering-svg.yaml" - id: Rendering-Svg-AllRequirements title: Review that All Rendering Svg Requirements are Complete paths: - - "requirements.yaml" - "docs/reqstream/rendering-svg/**/*.yaml" - - "docs/reqstream/ots/**/*.yaml" + context: + - "docs/design/rendering-svg.md" + - "docs/reqstream/rendering-svg.yaml" - id: Rendering-Svg-SvgRenderer title: Review that Rendering Svg SvgRenderer Implementation is Correct @@ -371,6 +505,9 @@ reviews: - "docs/verification/rendering-svg/svg-renderer.md" - "src/DemaConsulting.Rendering.Svg/SvgRenderer.cs" - "test/DemaConsulting.Rendering.Svg.Tests/**/*.cs" + context: + - "docs/design/rendering-svg.md" + - "docs/reqstream/rendering-svg.yaml" # ===================================================================== # Rendering.Skia system @@ -378,25 +515,43 @@ reviews: - id: Rendering-Skia-Architecture title: Review that Rendering Skia Architecture Satisfies Requirements paths: - - "docs/reqstream/rendering-skia/rendering-skia.yaml" + - "docs/reqstream/rendering-skia.yaml" - "docs/design/introduction.md" - - "docs/design/rendering-skia/rendering-skia.md" + - "docs/design/rendering-skia.md" - "docs/verification/introduction.md" - - "docs/verification/rendering-skia/rendering-skia.md" + - "docs/verification/rendering-skia.md" + - "src/DemaConsulting.Rendering.Skia/NamespaceDoc.cs" + context: + - "README.md" + - "docs/user_guide/**/*.md" - id: Rendering-Skia-Design title: Review that Rendering Skia Design is Consistent and Complete paths: - - "docs/reqstream/rendering-skia/rendering-skia.yaml" - "docs/design/introduction.md" + - "docs/design/rendering-skia.md" - "docs/design/rendering-skia/**/*.md" + - "docs/design/ots.md" + context: + - "docs/reqstream/rendering-skia.yaml" + + - id: Rendering-Skia-Verification + title: Review that Rendering Skia Verification is Consistent and Complete + paths: + - "docs/verification/introduction.md" + - "docs/verification/rendering-skia.md" + - "docs/verification/rendering-skia/**/*.md" + - "docs/verification/ots.md" + context: + - "docs/reqstream/rendering-skia.yaml" - id: Rendering-Skia-AllRequirements title: Review that All Rendering Skia Requirements are Complete paths: - - "requirements.yaml" - "docs/reqstream/rendering-skia/**/*.yaml" - - "docs/reqstream/ots/**/*.yaml" + context: + - "docs/design/rendering-skia.md" + - "docs/reqstream/rendering-skia.yaml" - id: Rendering-Skia-SkiaRasterRenderer title: Review that Rendering Skia SkiaRasterRenderer Implementation is Correct @@ -407,6 +562,9 @@ reviews: - "src/DemaConsulting.Rendering.Skia/SkiaRasterRenderer.cs" - "test/DemaConsulting.Rendering.Skia.Tests/PngRendererPortedTests.cs" - "test/DemaConsulting.Rendering.Skia.Tests/PngEndMarkerTests.cs" + context: + - "docs/design/rendering-skia.md" + - "docs/reqstream/rendering-skia.yaml" - id: Rendering-Skia-PngRenderer title: Review that Rendering Skia PngRenderer Implementation is Correct @@ -417,6 +575,9 @@ reviews: - "src/DemaConsulting.Rendering.Skia/PngRenderer.cs" - "test/DemaConsulting.Rendering.Skia.Tests/PngRendererTests.cs" - "test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs" + context: + - "docs/design/rendering-skia.md" + - "docs/reqstream/rendering-skia.yaml" - id: Rendering-Skia-JpegRenderer title: Review that Rendering Skia JpegRenderer Implementation is Correct @@ -426,6 +587,9 @@ reviews: - "docs/verification/rendering-skia/jpeg-renderer.md" - "src/DemaConsulting.Rendering.Skia/JpegRenderer.cs" - "test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs" + context: + - "docs/design/rendering-skia.md" + - "docs/reqstream/rendering-skia.yaml" - id: Rendering-Skia-WebpRenderer title: Review that Rendering Skia WebpRenderer Implementation is Correct @@ -435,6 +599,9 @@ reviews: - "docs/verification/rendering-skia/webp-renderer.md" - "src/DemaConsulting.Rendering.Skia/WebpRenderer.cs" - "test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs" + context: + - "docs/design/rendering-skia.md" + - "docs/reqstream/rendering-skia.yaml" # Rendering gallery showcase - id: Rendering-Gallery @@ -450,61 +617,78 @@ reviews: # OTS Items - id: OTS-BuildMark - title: Review of BuildMark OTS verification evidence + title: Review that BuildMark Provides Required Functionality paths: - "docs/reqstream/ots/buildmark.yaml" + - "docs/design/ots/buildmark.md" - "docs/verification/ots/buildmark.md" - id: OTS-FileAssert - title: Review of FileAssert OTS verification evidence + title: Review that FileAssert Provides Required Functionality paths: - "docs/reqstream/ots/fileassert.yaml" + - "docs/design/ots/fileassert.md" - "docs/verification/ots/fileassert.md" - id: OTS-Pandoc - title: Review of Pandoc OTS verification evidence + title: Review that Pandoc Provides Required Functionality paths: - "docs/reqstream/ots/pandoc.yaml" + - "docs/design/ots/pandoc.md" - "docs/verification/ots/pandoc.md" - id: OTS-ReqStream - title: Review of ReqStream OTS verification evidence + title: Review that ReqStream Provides Required Functionality paths: - "docs/reqstream/ots/reqstream.yaml" + - "docs/design/ots/reqstream.md" - "docs/verification/ots/reqstream.md" - id: OTS-ReviewMark - title: Review of ReviewMark OTS verification evidence + title: Review that ReviewMark Provides Required Functionality paths: - "docs/reqstream/ots/reviewmark.yaml" + - "docs/design/ots/reviewmark.md" - "docs/verification/ots/reviewmark.md" - id: OTS-SarifMark - title: Review of SarifMark OTS verification evidence + title: Review that SarifMark Provides Required Functionality paths: - "docs/reqstream/ots/sarifmark.yaml" + - "docs/design/ots/sarifmark.md" - "docs/verification/ots/sarifmark.md" - id: OTS-SonarMark - title: Review of SonarMark OTS verification evidence + title: Review that SonarMark Provides Required Functionality paths: - "docs/reqstream/ots/sonarmark.yaml" + - "docs/design/ots/sonarmark.md" - "docs/verification/ots/sonarmark.md" - id: OTS-VersionMark - title: Review of VersionMark OTS verification evidence + title: Review that VersionMark Provides Required Functionality paths: - "docs/reqstream/ots/versionmark.yaml" + - "docs/design/ots/versionmark.md" - "docs/verification/ots/versionmark.md" - id: OTS-WeasyPrint - title: Review of WeasyPrint OTS verification evidence + title: Review that WeasyPrint Provides Required Functionality paths: - "docs/reqstream/ots/weasyprint.yaml" + - "docs/design/ots/weasyprint.md" - "docs/verification/ots/weasyprint.md" - id: OTS-xUnit - title: Review of xUnit OTS verification evidence + title: Review that xUnit Provides Required Functionality paths: - "docs/reqstream/ots/xunit.yaml" + - "docs/design/ots/xunit.md" - "docs/verification/ots/xunit.md" + + - id: OTS-SkiaSharp + title: Review that SkiaSharp Provides Required Functionality + paths: + - "docs/reqstream/ots/skiasharp.yaml" + - "docs/design/ots/skiasharp.md" + - "docs/verification/ots/skiasharp.md" diff --git a/AGENTS.md b/AGENTS.md index 7a629b5..26a32bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,18 +1,17 @@ # Project Overview -> **Downstream customization required**: Replace the `TODO` values below with -> values specific to the target repository. - -- **name**: TODO -- **description**: TODO -- **languages**: TODO -- **technologies**: TODO +- **project-name**: Rendering +- **organization**: DEMA Consulting +- **project-tagline**: General-purpose diagram layout and rendering for .NET +- **description**: General-purpose diagram layout and rendering for .NET. Describe a diagram as a + graph, lay it out with a pluggable algorithm, and render it to SVG, PNG, JPEG, or WEBP. The design + is inspired by the Eclipse Layout Kernel (ELK): layout and rendering are configured through an + open, extensible property system, and new algorithms, renderers, and options are added additively. +- **languages**: C# +- **technologies**: .NET, SkiaSharp # Project Structure -> **Downstream customization required**: Replace `{project}` and -> `{test-project}` with the actual source and test project folder names. - ```text ├── docs/ │ ├── build_notes/ @@ -26,11 +25,33 @@ │ ├── user_guide/ │ └── verification/ ├── src/ -│ └── {project}/ +│ ├── DemaConsulting.Rendering/ (layout model: LayoutTree IR, property system, LayoutGraph) +│ ├── DemaConsulting.Rendering.Abstractions/ (SPI: ILayoutAlgorithm/IRenderer, registries, Theme, metrics) +│ ├── DemaConsulting.Rendering.Layout/ (layout algorithms: layered pipeline, LayeredLayoutAlgorithm) +│ ├── DemaConsulting.Rendering.Svg/ (SVG renderer, zero external dependencies) +│ └── DemaConsulting.Rendering.Skia/ (SkiaSharp raster renderers: PNG, JPEG, WEBP) └── test/ - └── {test-project}/ + ├── DemaConsulting.Rendering.Tests/ + ├── DemaConsulting.Rendering.Abstractions.Tests/ + ├── DemaConsulting.Rendering.Layout.Tests/ + ├── DemaConsulting.Rendering.Svg.Tests/ + ├── DemaConsulting.Rendering.Skia.Tests/ + └── DemaConsulting.Rendering.Gallery/ (visual gallery/sample output generator) ``` +# Language and Spelling (ALL Agents) + +Always use **US English** spelling in all output (code, comments, documentation, +commit messages, and reports). + +# Reference Template + +This repository follows a reference template for structure and file conventions. + +- **template-url**: `https://github.com/demaconsulting/Agents/raw/refs/heads/template` +- **Repository map**: `{template-url}/repository-map.md` +- **Template files**: `{template-url}/{file-path}` for files described in the map + # Codebase Navigation (ALL Agents) When working with source code, design, or requirements artifacts, read @@ -59,17 +80,15 @@ before searching the filesystem. Before performing any work, agents must read and apply the relevant standards from `.github/standards/`. Use this matrix to determine which to load: -| Work involves... | Load these standards | -|----------------------|------------------------------------------------------------------------------------| -| Any code | `coding-principles.md` | -| C# code | `coding-principles.md`, `csharp-language.md` | -| Any tests | `testing-principles.md` | -| C# tests | `testing-principles.md`, `csharp-testing.md` | -| Requirements | `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` | -| Design docs | `software-items.md`, `design-documentation.md`, `technical-documentation.md` | -| Verification docs | `software-items.md`, `verification-documentation.md`, `technical-documentation.md` | -| Review configuration | `software-items.md`, `reviewmark-usage.md` | -| Any documentation | `technical-documentation.md` | +- **Any code**: `coding-principles.md` +- **C# code**: `coding-principles.md`, `csharp-language.md` +- **Any tests**: `testing-principles.md` +- **C# tests**: `testing-principles.md`, `csharp-testing.md` +- **Requirements**: `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` +- **Design docs**: `software-items.md`, `design-documentation.md`, `technical-documentation.md` +- **Verification docs**: `software-items.md`, `verification-documentation.md`, `technical-documentation.md` +- **Review configuration**: `software-items.md`, `reviewmark-usage.md` +- **Any documentation**: `technical-documentation.md` Load only the standards relevant to your specific task scope. @@ -79,11 +98,15 @@ The default agent should handle simple, straightforward tasks directly. Delegate to specialized agents only for specific scenarios: - **Pre-PR lint cleanup** (fix all lint issues before pull request) → Call the lint-fix agent -- **Light development work** (small fixes, simple features) → Call the developer agent +- **Scoped fixes with no new user-visible behavior** (PR review comments, doc + corrections, known bug fixes with defined root cause) → Call the developer agent - **Light quality checking** (basic validation) → Call the quality agent -- **Formal feature implementation** (complex, multi-step) → Call the implementation agent -- **Formal bug resolution** (complex debugging, systematic fixes) → Call the implementation agent +- **Any change introducing new user-visible behavior** (features, enhancements, + new commands or options) → Call the implementation agent +- **Formal bug resolution** (complex debugging, unknown root cause) → Call the implementation agent - **Formal reviews** (compliance verification, detailed analysis) → Call the formal-review agent +- **Structural audit**: (repository layout vs. template) → Call the template-sync agent +- **Implementation planning only** (review a plan before committing to implementation) → Call the planning agent # Agent Reporting (Specialized Agents Must Follow) @@ -92,21 +115,21 @@ Specialized agents MUST generate a completion report: 1. Save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` where `{subject}` is a kebab-case task summary (max 5 words) and `{unique-id}` is a short unique suffix (e.g., 8-char hex or timestamp) -2. Start with `**Result**: (SUCCEEDED|FAILED)` as the first metadata field +2. Start with `**Result**: (SUCCEEDED|FAILED|INCOMPLETE)` as the first metadata field 3. Include the agent-specific report sections defined in each agent's prompt 4. Return the summary to the caller Result semantics for orchestrator decision-making: -- **SUCCEEDED**: Work completed and all applicable quality gates met +- **SUCCEEDED**: Work completed and all quality gates applicable to that agent's scope met - **FAILED**: Work could not be completed or quality gates not met - **INCOMPLETE**: Work cannot proceed without information only the user can - provide (implementation agent only) + provide (implementation, planning, and template-sync agents) # Formatting (After Making Changes) After making changes, run the auto-fix pass. This applies all available fixers -silently and **always exits 0** — agents do not need to respond to its output. +silently and **always exits 0** - agents do not need to respond to its output. ```pwsh pwsh ./fix.ps1 @@ -114,7 +137,7 @@ pwsh ./fix.ps1 This automatically handles: `dotnet format`, markdown formatting, and YAML formatting. Full lint compliance is a **pre-PR responsibility**, not an agent -responsibility — invoke the lint-fix agent once before submitting a pull request. +responsibility - invoke the lint-fix agent once before submitting a pull request. ## CI Quality Tools @@ -124,7 +147,7 @@ reqstream, versionmark, and reviewmark. # Scope Discipline (ALL Agents Must Follow) - **No generated file access**: Files inside any `generated/` folder are build - outputs — do not read, lint, or modify them + outputs - do not read, lint, or modify them - **Minimum necessary changes**: Only modify files directly required by the task - **No speculative refactoring**: Do not refactor code adjacent to the change unless the task explicitly requests it diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..dc896a7 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,32 @@ +# Roadmap + +This file tracks well-known options and behaviors that are accepted by the API today but not +yet honored by any bundled algorithm or renderer, so they are not forgotten between sessions. +It is not part of the reference template's structure — it is an additive, repo-local tracking +aid — and is not itself a compliance artifact (no reqstream/design/verification obligations +attach to entries here until they are actually implemented). + +## Unimplemented `CoreOptions` behavior + +All six `CoreOptions` properties now cascade correctly through the layout hierarchy (nearest-ancestor +inheritance via `PropertyHolder.OverlayOnto`, landed alongside this file). The following properties are +accepted and cascade correctly, but are not yet *read* by any bundled algorithm's layout logic: + +- **`CoreOptions.HierarchyHandling`** — only `HierarchyHandling.SeparateChildren` exists and is + implicitly what the bundled `HierarchicalLayoutAlgorithm` does; no algorithm branches on this + property's value. Additional modes (e.g. an ELK-style "include children in the parent's own + layout space" mode) are not implemented. +- **`CoreOptions.NodeSpacing`** — advisory; the bundled `layered` algorithm uses fixed engine + metrics instead of this value. +- **`CoreOptions.LayerSpacing`** — advisory; same as `NodeSpacing`, fixed engine metrics are used + instead. + +Because these properties are ordinary `LayoutProperty` values stored via `PropertyHolder`, the +generalized cascading mechanism already applies to them automatically — implementing any of the +above only requires an algorithm to start reading the (already-cascaded) effective value; no new +option-resolution plumbing is required. + +## Process note + +When a future session identifies further deferred/advisory work, add it here rather than letting +it live only in conversation history or scattered XML doc comments, so it survives across sessions. diff --git a/docs/design/definition.yaml b/docs/design/definition.yaml index 2a52644..e1ecc62 100644 --- a/docs/design/definition.yaml +++ b/docs/design/definition.yaml @@ -5,11 +5,23 @@ resource-path: input-files: - docs/design/title.txt - docs/design/introduction.md - - docs/design/rendering/rendering.md - - docs/design/rendering-abstractions/rendering-abstractions.md - - docs/design/rendering-layout/rendering-layout.md - - docs/design/rendering-svg/rendering-svg.md - - docs/design/rendering-skia/rendering-skia.md + - docs/design/rendering.md + - docs/design/rendering-abstractions.md + - docs/design/rendering-layout.md + - docs/design/rendering-svg.md + - docs/design/rendering-skia.md + - docs/design/ots.md + - docs/design/ots/buildmark.md + - docs/design/ots/fileassert.md + - docs/design/ots/pandoc.md + - docs/design/ots/reqstream.md + - docs/design/ots/reviewmark.md + - docs/design/ots/sarifmark.md + - docs/design/ots/sonarmark.md + - docs/design/ots/versionmark.md + - docs/design/ots/weasyprint.md + - docs/design/ots/xunit.md + - docs/design/ots/skiasharp.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/design/introduction.md b/docs/design/introduction.md index ac0e48a..01983c7 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -38,9 +38,23 @@ software items: - **Rendering.Svg (System)** — the SVG renderer. - **Rendering.Skia (System)** — the SkiaSharp raster renderers (PNG, JPEG, and WEBP). +The integration design for the following OTS build, compliance, and documentation tools is also +covered (their internal design is out of scope): + +- **BuildMark** — build-notes documentation tool +- **FileAssert** — document assertion tool +- **Pandoc** — Markdown-to-HTML conversion tool +- **ReqStream** — requirements traceability tool +- **ReviewMark** — file review enforcement tool +- **SarifMark** — SARIF report conversion tool +- **SonarMark** — SonarCloud quality report tool +- **VersionMark** — tool-version documentation tool +- **WeasyPrint** — HTML-to-PDF conversion tool +- **xUnit** — unit-testing framework + The following topics are explicitly excluded: -- External library internals and third-party OTS components +- Internal design or source of third-party OTS components (only their integration/usage design is covered) - Build pipeline configuration and CI/CD processes - Deployment, packaging, and distribution mechanisms - Test projects and test infrastructure @@ -116,8 +130,27 @@ Rendering.Skia (System) ├── PngRenderer (Unit) — lossless PNG output ├── JpegRenderer (Unit) — JPEG output └── WebpRenderer (Unit) — WEBP output + +OTS Software Items +├── BuildMark — build-notes documentation from GitHub Actions metadata +├── FileAssert — generated-document assertion tool +├── Pandoc — Markdown-to-HTML conversion +├── ReqStream — requirements traceability and enforcement +├── ReviewMark — file-review plan, report, and enforcement +├── SarifMark — CodeQL SARIF-to-Markdown conversion +├── SkiaSharp — raster graphics library (bitmap drawing and PNG/JPEG/WEBP encoding) +├── SonarMark — SonarCloud quality-report generation +├── VersionMark — tool-version capture and publishing +├── WeasyPrint — HTML-to-PDF (PDF/A) conversion +└── xUnit — unit-testing framework ``` +Most OTS software items are compliance, build, and documentation tooling consumed while building, +verifying, and documenting the Rendering libraries, and are not linked into the delivered packages. +SkiaSharp is the one exception: it is a runtime library linked into the delivered +`DemaConsulting.Rendering.Skia` package. Their integration design is described in the OTS +Integration Design section and the per-item documents under `docs/design/ots/`. + Package dependencies form an acyclic graph: `Abstractions` and `Layout` depend on the `Rendering` model; `Svg` and `Skia` depend on the model and `Abstractions`; the model depends on nothing. @@ -129,15 +162,21 @@ decomposed into a slim system-level file plus one file per unit (and per subsyst exists, such as the Layout `engine/` subsystem), so a system-level review excludes unit detail and each unit review carries only its own slice: -- Requirements: `docs/reqstream/{system}/{system}.yaml` (system-level) plus +- Requirements: `docs/reqstream/{system}.yaml` (system-level) plus `docs/reqstream/{system}/[{subsystem}/]{unit}.yaml` per unit (kebab-case) -- Design docs: `docs/design/{system}/{system}.md` plus `docs/design/{system}/[{subsystem}/]{unit}.md` -- Verification design: `docs/verification/{system}/{system}.md` plus +- Design docs: `docs/design/{system}.md` plus `docs/design/{system}/[{subsystem}/]{unit}.md` +- Verification design: `docs/verification/{system}.md` plus `docs/verification/{system}/[{subsystem}/]{unit}.md` - Source code: `src/{System}/.../{Item}.cs` (PascalCase for C#) - Tests: `test/{System}.Tests/.../{Item}Tests.cs` (PascalCase for C#) - Review-sets: defined in `.reviewmark.yaml` +OTS items sit parallel to the system folders and have no source code: + +- Requirements: `docs/reqstream/ots/{ots-name}.yaml` (kebab-case) +- Design docs: `docs/design/ots.md` (integration index) plus `docs/design/ots/{ots-name}.md` +- Verification: `docs/verification/ots/{ots-name}.md` (kebab-case) + The five systems map to these kebab-case folders: | NuGet Package | kebab-case system folder | diff --git a/docs/design/ots.md b/docs/design/ots.md new file mode 100644 index 0000000..511fd20 --- /dev/null +++ b/docs/design/ots.md @@ -0,0 +1,70 @@ +# OTS Integration Design + +This document describes the overall strategy for integrating the Off-The-Shelf (OTS) software items +that the Rendering repository depends on. Most of these OTS items are compliance, build, and +documentation tooling that are consumed while building, verifying, and documenting the Rendering +libraries, and do not ship inside the `DemaConsulting.Rendering*` NuGet packages. SkiaSharp is the +one exception: it is a runtime library linked into the delivered `DemaConsulting.Rendering.Skia` +package. + +## OTS Items + +The repository integrates twelve OTS items: + +```text +OTS Software Items +├── BuildMark — build-notes documentation from GitHub Actions metadata +├── FileAssert — generated-document assertion tool +├── Pandoc — Markdown-to-HTML conversion +├── ReqStream — requirements traceability and enforcement +├── ReviewMark — file-review plan, report, and enforcement +├── SarifMark — CodeQL SARIF-to-Markdown conversion +├── SkiaSharp — raster graphics library (bitmap drawing and PNG/JPEG/WEBP encoding) +├── SonarScanner for .NET — SonarCloud static-analysis scanner wrapper +├── SonarMark — SonarCloud quality-report generation +├── VersionMark — tool-version capture and publishing +├── WeasyPrint — HTML-to-PDF (PDF/A) conversion +└── xUnit — unit-testing framework +``` + +Each item has its own integration design under `docs/design/ots/{ots-name}.md` describing its Purpose, +Features Used, and Integration Pattern. This document covers the shared integration strategy. + +## Integration Strategy + +The OTS items fall into three consumption models: + +- **.NET local tools** — BuildMark, FileAssert, Pandoc, ReqStream, ReviewMark, SarifMark, + SonarScanner for .NET, SonarMark, VersionMark, and WeasyPrint are installed as local .NET tools + through the `.config/dotnet-tools.json` manifest and restored with `dotnet tool restore`. They are + invoked as `dotnet {tool}` commands from the CI workflows (`.github/workflows/build.yaml` and + `release.yaml`) and, for the linting subset, from `lint.ps1`. The Pandoc and WeasyPrint tools are + DemaConsulting distributions + (`DemaConsulting.PandocTool`, `DemaConsulting.WeasyPrintTool`) that package the underlying converters + as .NET tools. +- **Test framework** — xUnit is referenced as a NuGet test-framework dependency by the test projects + and is exercised by `dotnet test`; it discovers and runs the repository's own test methods and + records TRX results. +- **Runtime library** — SkiaSharp is referenced as a NuGet package dependency (plus + platform-specific native asset packages) by `DemaConsulting.Rendering.Skia.csproj` and is linked + into the delivered package; it provides the bitmap canvas, drawing primitives, and image encoders + that the Skia raster renderer tier uses at run time. + +Configuration is file-driven: the tools read repository configuration such as `requirements.yaml`, +`.reviewmark.yaml`, `.versionmark.yaml`, and the per-collection Pandoc `definition.yaml` manifests, and +they write their output into `generated/` folders and `artifacts/` TRX files. Compliance tools that +support an `--enforce` mode (ReqStream, ReviewMark, SarifMark) can fail the build when a compliance +condition is not met. + +## Verification Strategy + +Because these OTS items are compliance/build tooling, each is verified through a combination of its own +self-validation suite and its observable effect in the CI document-generation chain. Most DemaConsulting +tools expose a `--validate` mode that runs a built-in self-validation suite and writes a TRX result +(for example, `dotnet reqstream --validate --results artifacts/reqstream-self-validation.trx`); ReqStream +then traces those TRX results against the OTS requirements in `requirements.yaml`. Tools without a +self-validation suite in this pipeline (Pandoc, WeasyPrint) are verified indirectly: FileAssert asserts +that their generated HTML and PDF outputs exist and contain expected content. xUnit and SkiaSharp are +verified by the repository's own passing tests — xUnit as the framework that discovers, executes, and +records them, and SkiaSharp as the raster library those renderer tests exercise directly. The per-item +verification evidence is documented under `docs/verification/ots/{ots-name}.md`. diff --git a/docs/design/ots/buildmark.md b/docs/design/ots/buildmark.md new file mode 100644 index 0000000..9020548 --- /dev/null +++ b/docs/design/ots/buildmark.md @@ -0,0 +1,28 @@ +## BuildMark Integration Design + +### Purpose + +BuildMark (`DemaConsulting.BuildMark`) generates a build-notes document for each release of the +Rendering libraries. It queries the GitHub API to capture workflow run details, version tags, commit +history, and issue/pull-request activity, and renders them as a Markdown build-notes document that is +compiled into the release artifacts. It provides the repository's automated release-notes evidence +without hand-maintained changelogs. + +### Features Used + +- **Self-validation** (`--validate --results`) — runs BuildMark's built-in self-validation suite and + writes a TRX result consumed by ReqStream for OTS traceability. +- **Build-notes report generation** (`--build-version`, `--report`, `--report-depth`) — produces + `docs/build_notes/generated/build_notes.md` from the current build version and GitHub Actions + metadata, including Git tag/commit integration, GitHub issue and pull-request tracking, known-issue + reporting, and label/type-based routing of items into report sections. + +### Integration Pattern + +BuildMark is installed as a .NET local tool via `.config/dotnet-tools.json` and restored with +`dotnet tool restore`. It is invoked as `dotnet buildmark` from the documentation-build job of +`.github/workflows/build.yaml`. Self-validation runs first (writing +`artifacts/buildmark-self-validation.trx`), then the report step generates the build-notes Markdown, +which Pandoc converts to HTML and WeasyPrint renders to a PDF/A artifact. It reads GitHub Actions +environment metadata and the repository's Git history as input and writes into the +`docs/build_notes/generated/` folder. BuildMark is not referenced by the delivered Rendering packages. diff --git a/docs/design/ots/fileassert.md b/docs/design/ots/fileassert.md new file mode 100644 index 0000000..f431863 --- /dev/null +++ b/docs/design/ots/fileassert.md @@ -0,0 +1,26 @@ +## FileAssert Integration Design + +### Purpose + +FileAssert (`DemaConsulting.FileAssert`) validates the documents produced during the build. It asserts +that each generated HTML and PDF document exists, has a non-trivial size, and contains expected content. +It provides the OTS verification evidence for Pandoc and WeasyPrint (whose output correctness is proven +by FileAssert assertions) and independently confirms that document generation succeeded before ReqStream +consumes the results. + +### Features Used + +- **Self-validation** (`--validate --results`) — runs FileAssert's built-in self-validation suite + (result, file, text, HTML, and PDF assertions) and writes a TRX result for ReqStream. +- **Document assertion** (`--results {trx} {assertion-set}`) — runs a named assertion set (for example + `build-notes`, `code-quality`) that checks the corresponding generated documents exist, are + non-empty, and contain expected text; results are written to a TRX file per document set. + +### Integration Pattern + +FileAssert is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet fileassert` from the documentation-build job of `.github/workflows/build.yaml`. After Pandoc and +WeasyPrint generate each document collection's HTML and PDF, FileAssert asserts those outputs and writes +`artifacts/fileassert-*.trx` files. ReqStream then traces those TRX results against the FileAssert, +Pandoc, and WeasyPrint OTS requirements. FileAssert is not referenced by the delivered Rendering +packages. diff --git a/docs/design/ots/pandoc.md b/docs/design/ots/pandoc.md new file mode 100644 index 0000000..80d316c --- /dev/null +++ b/docs/design/ots/pandoc.md @@ -0,0 +1,29 @@ +## Pandoc Integration Design + +### Purpose + +Pandoc (`DemaConsulting.PandocTool`) converts the repository's Markdown documentation collections into +HTML as the first stage of the document-generation pipeline. It is used to compile the build notes, code +quality, code review plan and report, design, verification, requirements, and user guide collections +into single HTML documents that WeasyPrint then renders to PDF. + +### Features Used + +- **Defaults-driven conversion** (`--defaults docs/{collection}/definition.yaml`) — reads each + collection's Pandoc build definition (input files, resource paths, template, table of contents, and + section numbering) and concatenates the listed Markdown files into one HTML document. +- **Mermaid filtering** (`--filter node_modules/.bin/mermaid-filter.cmd`) — renders embedded Mermaid + diagrams during conversion. +- **Metadata injection** (`--metadata version=...`, `--metadata date=...`) and HTML output + (`--output docs/{collection}/generated/{collection}.html`). + +### Integration Pattern + +Pandoc is installed as a .NET local tool (a DemaConsulting distribution of Pandoc) via +`.config/dotnet-tools.json` and invoked as `dotnet pandoc` from `.github/workflows/build.yaml`, once per +document collection. Its input is a collection's `definition.yaml` manifest and the checked-in Markdown +source files; its output HTML is written to the collection's `generated/` folder and passed to +WeasyPrint. Because the heading-depth rule keeps every file's top heading aligned to its folder depth, +Pandoc can concatenate the files in `definition.yaml` order into a coherent outline. Pandoc has no +self-validation suite in this pipeline; its correct operation is verified indirectly by FileAssert +assertions on the generated HTML. Pandoc is not referenced by the delivered Rendering packages. diff --git a/docs/design/ots/reqstream.md b/docs/design/ots/reqstream.md new file mode 100644 index 0000000..026ffa0 --- /dev/null +++ b/docs/design/ots/reqstream.md @@ -0,0 +1,31 @@ +## ReqStream Integration Design + +### Purpose + +ReqStream (`DemaConsulting.ReqStream`) is the requirements-traceability tool for the repository. It +processes `requirements.yaml` and its include chain together with the TRX test-result files to produce a +requirements report, a justifications document, and a traceability matrix, and — in enforcement mode — +fails the build when any requirement lacks passing test evidence. It is the tool that ties requirements, +tests, and OTS self-validation results together into audit evidence. + +### Features Used + +- **Lint** (`--lint --requirements requirements.yaml`) — checks the requirements YAML files for + structural and semantic issues (duplicate IDs, missing fields, broken references); also run from + `lint.ps1`. +- **Self-validation** (`--validate --results`) — runs ReqStream's built-in self-validation suite and + writes a TRX result. +- **Report, justifications, and trace matrix generation with enforcement** + (`--requirements`, `--tests "artifacts/**/*.trx"`, `--report`, `--justifications`, `--matrix`, + `--enforce`) — consumes all TRX evidence, generates the compliance documents, and exits non-zero if + any requirement is unproven. + +### Integration Pattern + +ReqStream is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet reqstream`. Lint runs in `lint.ps1` (and CI quality checks); self-validation and the +report/matrix/enforce step run in the documentation-build job of `.github/workflows/build.yaml` after +all other TRX evidence has been produced. Its inputs are `requirements.yaml` (with the +`docs/reqstream/**` include chain) and the collected `artifacts/**/*.trx` files; its outputs are written +to the requirements-document and requirements-report `generated/` folders. ReqStream is not referenced +by the delivered Rendering packages. diff --git a/docs/design/ots/reviewmark.md b/docs/design/ots/reviewmark.md new file mode 100644 index 0000000..c075131 --- /dev/null +++ b/docs/design/ots/reviewmark.md @@ -0,0 +1,30 @@ +## ReviewMark Integration Design + +### Purpose + +ReviewMark (`DemaConsulting.ReviewMark`) enforces formal file review across the repository. It reads the +`.reviewmark.yaml` configuration and the review-evidence store to determine which files require review, +group them into named review-sets, produce a review plan and review report, and — in enforcement mode — +fail the build when a reviewed file has changed since its last review. It provides the review-currency +evidence required by the compliance process. + +### Features Used + +- **Lint** (`--lint`) — validates the `.reviewmark.yaml` review definitions for structural and semantic + issues; also run from `lint.ps1`. +- **Self-validation** (`--validate --results`) — runs ReviewMark's built-in self-validation suite and + writes a TRX result. +- **Plan and report generation** (`--plan`, `--plan-depth`, `--report`, `--report-depth`) — produces the + code review plan and report Markdown documents. +- **Additional capabilities available** — evidence index scanning (`--index`), enforcement + (`--enforce`), review-set elaboration (`--elaborate`), and working-directory override (`--dir`). + +### Integration Pattern + +ReviewMark is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet reviewmark`. Lint runs in `lint.ps1` (and CI quality checks); self-validation and plan/report +generation run in the code-review job of `.github/workflows/build.yaml`. Its inputs are `.reviewmark.yaml` +(review-sets and `needs-review` patterns) and the review-evidence store referenced by +`evidence-source`; its outputs are the plan and report Markdown files in their `generated/` folders. +Enforcement (`--enforce`) is planned once the reviews branch is populated with evidence. ReviewMark is +not referenced by the delivered Rendering packages. diff --git a/docs/design/ots/sarifmark.md b/docs/design/ots/sarifmark.md new file mode 100644 index 0000000..abc63a5 --- /dev/null +++ b/docs/design/ots/sarifmark.md @@ -0,0 +1,26 @@ +## SarifMark Integration Design + +### Purpose + +SarifMark (`DemaConsulting.SarifMark`) converts the SARIF output produced by CodeQL code scanning into a +human-readable Markdown quality report that is compiled into the release artifacts. In enforcement mode +it also gates the build on detected issues, letting CI fail when static analysis reports code-quality +violations. + +### Features Used + +- **Self-validation** (`--validate --results`) — runs SarifMark's built-in self-validation suite and + writes a TRX result. +- **SARIF-to-Markdown report generation** (`--sarif artifacts/csharp.sarif`, `--report`, `--heading`, + `--report-depth`) — reads the CodeQL SARIF file and renders a Markdown report into + `docs/code_quality/generated/`. +- **Enforcement** — returns a non-zero exit code when the SARIF input contains reported issues. + +### Integration Pattern + +SarifMark is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet sarifmark` from the code-quality steps of `.github/workflows/build.yaml`. Self-validation runs +first (writing `artifacts/sarifmark-self-validation.trx`), then the report step consumes the CodeQL +SARIF artifact and emits the CodeQL analysis Markdown, which Pandoc and WeasyPrint compile into the code +quality document. Its input is the SARIF file produced by CodeQL scanning; its output is written to the +code-quality `generated/` folder. SarifMark is not referenced by the delivered Rendering packages. diff --git a/docs/design/ots/skiasharp.md b/docs/design/ots/skiasharp.md new file mode 100644 index 0000000..aa8d7ff --- /dev/null +++ b/docs/design/ots/skiasharp.md @@ -0,0 +1,77 @@ +## SkiaSharp Integration Design + +### Purpose + +SkiaSharp is the raster-graphics library that backs the `DemaConsulting.Rendering.Skia` renderer +tier. Unlike the other OTS items in this document set, it is not build-time or documentation +tooling: it is a runtime NuGet dependency linked into the delivered `DemaConsulting.Rendering.Skia` +package, providing the bitmap canvas, drawing primitives, typeface loading, and image encoders that +`SkiaRasterRenderer` and its concrete formats (`PngRenderer`, `JpegRenderer`, `WebpRenderer`) use to +turn a placed `LayoutTree` into PNG, JPEG, and WEBP output. + +### Features Used + +- **Bitmap canvas** — `SKBitmap` and `SKCanvas` provide the in-memory raster surface that + `SkiaRasterRenderer` draws layout boxes, connectors, and labels onto. +- **Shape and text drawing** — `SKPaint`, `SKPaintStyle`, and `SKColor` draw filled/stroked + rectangles, lines, and other primitives; `SKTypeface` (loaded from embedded Noto Sans font + resources) and `SKPaint` text APIs render node titles and labels. +- **Image encoding** — `SKBitmap`/`SKCanvas` output is encoded by each concrete renderer into its + target container format: `PngRenderer` (PNG), `JpegRenderer` (JPEG), and `WebpRenderer` (WEBP). + +### Integration Pattern + +SkiaSharp is referenced as a NuGet package dependency (`SkiaSharp`, plus the platform-specific +`SkiaSharp.NativeAssets.Win32`/`.Linux.NoDependencies`/`.macOS` native asset packages selected by +MSBuild OS conditions) in `DemaConsulting.Rendering.Skia.csproj`. It is a compile-time and run-time +dependency of the shipped `DemaConsulting.Rendering.Skia` NuGet package, not a local .NET tool or a +build/lint-time utility. `SkiaRasterRenderer` is the abstract base class that wraps the SkiaSharp +APIs; `PngRenderer`, `JpegRenderer`, and `WebpRenderer` each configure it for their target image +format and implement the `DemaConsulting.Rendering.Abstractions.IRenderer` contract so callers can +render a `LayoutTree` without depending on SkiaSharp types directly. + +**Initialization.** SkiaSharp requires no explicit initialization: the platform-specific native +asset packages are loaded on first use by the SkiaSharp NuGet package. The embedded Noto Sans font +resource is loaded once per typeface at renderer startup by opening the manifest resource stream, +wrapping it in an `SKData` created via `SKData.Create(stream)`, and turning it into an `SKTypeface`; +the stream and the intermediate `SKData` are disposed with `using` declarations immediately after +the typeface has been created. + +**Configuration.** Each concrete renderer configures SkiaSharp only through its overrides of +`EncodedFormat` (`SKEncodedImageFormat.Png`/`Jpeg`/`Webp`) and `EncodingQuality`. No SkiaSharp +global state is mutated; every render call constructs its own drawing objects. + +**Resource lifecycle and disposal.** All SkiaSharp objects used by `SkiaRasterRenderer` are managed +wrappers around unmanaged Skia handles and implement `IDisposable`. They are therefore created and +disposed deterministically with C# `using` declarations or `using` blocks so that native memory, +GPU/CPU raster buffers, and encoded-byte buffers are released before `Render` returns: + +- **`SKBitmap`** — allocated inside `Render` (`using var bitmap = new SKBitmap(w, h, …)`) and + disposed at method exit; it holds the raster pixel buffer for the whole drawing pass. +- **`SKCanvas`** — created over the bitmap (`using var canvas = new SKCanvas(bitmap)`) and disposed + at method exit so the canvas releases its reference to the bitmap before the bitmap itself is + disposed. +- **`SKImage`** — created from the bitmap for encoding (`using var image = SKImage.FromBitmap(bitmap)`) + and disposed at method exit. +- **`SKData`** — produced by `image.Encode(EncodedFormat, EncodingQuality)` and disposed after its + bytes have been copied to the caller-owned output `Stream` via `data.SaveTo(output)`. The + transient `SKData` used to wrap the embedded font resource stream is likewise disposed with a + `using` declaration. +- **`SKPaint`, `SKPath`, `SKPathEffect`** — drawing primitives created per node (fills, strokes, + text paints, dashed/cornered path effects, marker path shapes) are each wrapped in `using` + declarations or `using` blocks so their unmanaged handles are freed before the drawing helper + returns; none of them outlive a single `Render` call. +- **`SKTypeface`** — the embedded Noto Sans typefaces are the one long-lived Skia resource: + they are loaded once at renderer initialization and cached for the lifetime of the process, + because loading a typeface is expensive and the font bytes never change. +- **Manifest resource streams** — the `System.IO.Stream` returned by + `Assembly.GetManifestResourceStream` for the embedded font is opened with a `using` declaration + during typeface loading and disposed as soon as the `SKData` wrapping it has been consumed. +- **Caller-owned output `Stream`** — the `Stream` argument to `Render` is written to via + `SKData.SaveTo(output)` but is neither flushed, closed, nor disposed by the renderer; ownership + of that stream, and any decision to flush or dispose it, stays with the caller. + +**Threading.** `SkiaRasterRenderer` treats each `Render` call as a self-contained operation: the +bitmap, canvas, paints, and encoder are all local to the call, so concurrent renders on different +threads with different renderer instances (or even the same instance) do not share mutable +SkiaSharp state. diff --git a/docs/design/ots/sonarmark.md b/docs/design/ots/sonarmark.md new file mode 100644 index 0000000..f7490fb --- /dev/null +++ b/docs/design/ots/sonarmark.md @@ -0,0 +1,27 @@ +## SonarMark Integration Design + +### Purpose + +SonarMark (`DemaConsulting.SonarMark`) retrieves quality-gate status, metrics, issues, and security +hotspots from SonarCloud for the repository's project and renders them as a Markdown quality report that +is compiled into the release artifacts. It turns the SonarCloud analysis into an offline, reviewable +compliance document. + +### Features Used + +- **Self-validation** (`--validate --results`) — runs SonarMark's built-in self-validation suite and + writes a TRX result. +- **SonarCloud quality-report generation** (`--server https://sonarcloud.io`, `--project-key`, + `--branch`, `--token`, `--report`, `--report-depth`) — queries the SonarCloud quality gate, issues, + and hotspots and renders `docs/code_quality/generated/sonar-quality.md`. + +### Integration Pattern + +SonarMark is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet sonarmark` from the code-quality steps of `.github/workflows/build.yaml`. It runs after the +SonarScanner analysis has published results to SonarCloud; self-validation runs first (writing +`artifacts/sonarmark-self-validation.trx`), then the report step retrieves the analysis for the current +branch using the `SONAR_TOKEN` secret and emits the Sonar quality Markdown, which Pandoc and WeasyPrint +compile into the code quality document. Its inputs are the SonarCloud project key, branch, and API +token; its output is written to the code-quality `generated/` folder. SonarMark is not referenced by the +delivered Rendering packages. diff --git a/docs/design/ots/versionmark.md b/docs/design/ots/versionmark.md new file mode 100644 index 0000000..677ab2d --- /dev/null +++ b/docs/design/ots/versionmark.md @@ -0,0 +1,29 @@ +## VersionMark Integration Design + +### Purpose + +VersionMark (`DemaConsulting.VersionMark`) captures and publishes the versions of the tools used across +the build pipeline. It records the version of each dotnet tool (and supporting programs such as `git`, +`node`, and `npm`) per CI job and publishes a consolidated tool-versions Markdown document included in +the release artifacts, providing a reproducible record of the toolchain that produced each build. + +### Features Used + +- **Version capture** (`--capture --job-id {id} --output {json} -- {tools...}`) — records the versions + of the listed tools for a CI job into a JSON file. +- **Self-validation** (`--validate --results`) — runs VersionMark's built-in self-validation suite and + writes a TRX result. +- **Publish** (`--publish --report docs/build_notes/generated/versions.md --report-depth 1 -- + "artifacts/**/versionmark-*.json"`) — merges the captured per-job JSON files into a single versions + Markdown report. +- **Lint** — validates the `.versionmark.yaml` configuration for structural and semantic errors. + +### Integration Pattern + +VersionMark is installed as a .NET local tool via `.config/dotnet-tools.json` and invoked as +`dotnet versionmark`. Each CI job in `.github/workflows/build.yaml` runs a capture step that writes an +`artifacts/versionmark-{job}.json` file and a self-validation step that writes a TRX result; the +documentation-build job then runs the publish step to merge all captured JSON files into the versions +report. Its inputs are the per-job capture JSON files; its output is written to the build-notes +`generated/` folder and compiled by Pandoc and WeasyPrint. VersionMark is not referenced by the +delivered Rendering packages. diff --git a/docs/design/ots/weasyprint.md b/docs/design/ots/weasyprint.md new file mode 100644 index 0000000..9b215a6 --- /dev/null +++ b/docs/design/ots/weasyprint.md @@ -0,0 +1,25 @@ +## WeasyPrint Integration Design + +### Purpose + +WeasyPrint (`DemaConsulting.WeasyPrintTool`) converts the HTML documents produced by Pandoc into +archival PDF/A files. It is the final rendering stage of the document-generation pipeline, producing the +release PDF artifacts for the build notes, code quality, code review plan and report, design, +verification, requirements, and user guide collections. + +### Features Used + +- **HTML-to-PDF conversion** (`dotnet weasyprint {input.html} {output.pdf}`) — renders a Pandoc-produced + HTML document to PDF. +- **PDF/A variant selection** (`--pdf-variant pdf/a-3u`) — produces an archival PDF/A-3u document + suitable for compliance retention. + +### Integration Pattern + +WeasyPrint is installed as a .NET local tool (a DemaConsulting distribution of WeasyPrint) via +`.config/dotnet-tools.json` and invoked as `dotnet weasyprint` from `.github/workflows/build.yaml`, once +per document collection after Pandoc has produced the HTML. Its input is the collection's generated HTML +file; its output is the archival PDF written into `docs/generated/` (for example +`docs/generated/Rendering Build Notes.pdf`). WeasyPrint has no self-validation suite in this pipeline; +its correct operation is verified indirectly by FileAssert assertions on the generated PDFs. WeasyPrint +is not referenced by the delivered Rendering packages. diff --git a/docs/design/ots/xunit.md b/docs/design/ots/xunit.md new file mode 100644 index 0000000..484842e --- /dev/null +++ b/docs/design/ots/xunit.md @@ -0,0 +1,24 @@ +## xUnit Integration Design + +### Purpose + +xUnit is the unit-testing framework used by the Rendering test projects. Unlike the DemaConsulting +compliance tools, it is not a build-time .NET tool but the test framework that discovers, executes, and +records the repository's own tests. Those tests provide the passing evidence that ReqStream traces +against every requirement, so xUnit underpins the entire verification pipeline. + +### Features Used + +- **Test discovery and execution** — xUnit v3 (`xunit.v3`) discovers and runs all methods annotated with + `[Fact]` or `[Theory]` across the model, layout, and renderer test projects when `dotnet test` runs. +- **TRX result reporting** — the `xunit.runner.visualstudio` runner writes TRX result files that feed + coverage reporting and requirements traceability through ReqStream. + +### Integration Pattern + +xUnit is referenced as a NuGet test-framework dependency (`xunit.v3` and `xunit.runner.visualstudio`) by +each `test/DemaConsulting.Rendering*.Tests` project rather than installed as a .NET local tool. It is +invoked by `dotnet test` from `build.ps1` and from the test job of `.github/workflows/build.yaml` across +the supported target frameworks (.NET 8, 9, and 10). Its inputs are the compiled test assemblies; its +outputs are the TRX result files under `artifacts/` that ReqStream consumes as compliance evidence. +xUnit is a test-time dependency only and is not referenced by the delivered Rendering packages. diff --git a/docs/design/rendering-abstractions.md b/docs/design/rendering-abstractions.md new file mode 100644 index 0000000..d7cf983 --- /dev/null +++ b/docs/design/rendering-abstractions.md @@ -0,0 +1,92 @@ +# Rendering Abstractions Design + +## Architecture + +`DemaConsulting.Rendering.Abstractions` is the service provider interface (SPI) that sits between the +rendering model (*Rendering Model* system, `DemaConsulting.Rendering`) and the concrete layout and +renderer implementations. It defines the pluggable `ILayoutAlgorithm` and `IRenderer` contracts, the +registries that resolve an implementation by identifier, media type, or file extension, the visual +`Theme` model, and the single-source geometry helpers that let every renderer draw identical +decorations. + +The system is composed of six units: + +```text +DemaConsulting.Rendering.Abstractions (System) +├── RenderingContracts (Unit) +├── Registries (Unit) +├── Theme (Unit) +├── NotationMetrics (Unit) +├── BoxMetrics (Unit) +└── ConnectorLabelPlacer (Unit) +``` + +- **Rendering Contracts** — `ILayoutAlgorithm`, `IRenderer`, `RenderOptions`, `RenderOutput`. Detailed + in Rendering Contracts Unit Design. +- **Registries** — `LayoutAlgorithmRegistry`, `RendererRegistry`. Detailed in Registries Unit Design. +- **Theme** — `Theme` and the built-in `Themes`. Detailed in Theme Unit Design. +- **Notation Metrics** — `NotationMetrics` and `MarkerVertex`. Detailed in Notation Metrics Unit + Design. +- **Box Metrics** — `BoxMetrics`. Detailed in Box Metrics Unit Design. +- **Connector Label Placer** — `ConnectorLabelPlacer`. Detailed in Connector Label Placer Unit Design. + +The contracts and registries collaborate to make the pipeline extensible: callers resolve an +`ILayoutAlgorithm` from `LayoutAlgorithmRegistry` by its configured identifier and an `IRenderer` from +`RendererRegistry` by media type or output file extension, so additional diagram families and output +formats are introduced purely additively. The `Theme` and the three geometry helpers (`NotationMetrics`, +`BoxMetrics`, `ConnectorLabelPlacer`) are the single source of truth that keeps SVG and raster outputs +visually consistent. Concrete algorithm and renderer implementations live in the downstream +Rendering.Layout, Rendering.Svg, and Rendering.Skia systems. + +## External Interfaces + +- **`ILayoutAlgorithm`** — inbound extension point implemented by layout systems; accepts a + `LayoutGraph` plus `LayoutOptions` and returns a placed `LayoutTree`. Each implementation advertises + an identifier used for registry lookup. +- **`IRenderer`** — inbound extension point implemented by renderer systems; accepts a `LayoutTree`, + `RenderOptions`, and an output `Stream`, and advertises a media type and file extensions used for + registry lookup. +- **`LayoutAlgorithmRegistry` / `RendererRegistry`** — outbound resolution APIs; register and resolve + implementations by identifier, media type, or advertised file extension. +- **`RenderOptions` / `RenderOutput`** — the render invocation parameters and result descriptor, + including the selected `Theme`. +- **`Theme` / `Themes`** — outbound value model consumed by renderers to color and size decorations. + +All interfaces are in-process .NET APIs. + +## Dependencies + +The system references the *Rendering Model* package (`DemaConsulting.Rendering`) for the graph, option, +and tree types it names in its contracts. It has no runtime NuGet package dependencies beyond the .NET +base class library; all NuGet references are build-time-only private assets (SBOM, SourceLink, API +documentation, and `Polyfill`). No OTS runtime component or Shared Package is consumed. + +## Risk Control Measures + +N/A - general-purpose rendering libraries carry no safety-related risk controls requiring +architectural segregation (IEC 62304 §5.3.3). + +## Data Flow + +```text +LayoutGraph + LayoutOptions ──► ILayoutAlgorithm ──► LayoutTree + │ + RenderOptions (Theme) ▼ + └────────► IRenderer ──► output Stream +``` + +A `LayoutGraph` plus `LayoutOptions` from the *Rendering Model* system is the input consumed by a +selected `ILayoutAlgorithm`; the algorithm produces a placed `LayoutTree`, which a selected `IRenderer` +draws to a caller-supplied stream. The renderer receives `RenderOptions` (including a `Theme`) and +reads the shared notation, box, and connector-label geometry so every output format draws the same +decorations. This system defines the contracts and shared geometry; it moves no data itself. + +## Design Constraints + +- **Target frameworks**: `net8.0`, `net9.0`, and `net10.0`. +- **Additive extensibility**: new algorithms and renderers are added by implementing a contract and + registering it; existing contracts do not change, so consumers remain source-compatible. +- **Single-source geometry**: notation, box, and connector-label geometry are defined once here and + reused by every renderer to guarantee visual consistency across SVG and raster output. +- **Model-only runtime dependency**: the package depends only on the rendering model and the .NET base + class library, keeping the SPI free of algorithm- or renderer-specific coupling. diff --git a/docs/design/rendering-abstractions/box-metrics.md b/docs/design/rendering-abstractions/box-metrics.md index e3de445..421769f 100644 --- a/docs/design/rendering-abstractions/box-metrics.md +++ b/docs/design/rendering-abstractions/box-metrics.md @@ -1,18 +1,18 @@ -# Box Metrics Unit Design +## Box Metrics Unit Design Part of the Rendering Abstractions system. -## Box Metrics Overview +### Box Metrics Purpose The Box Metrics unit provides the shared formulas that compute a box's title-area height and folder-tab height from a `Theme`, so that the space the layout strategies reserve equals the space the renderers draw. -## Box Metrics Data Model +### Box Metrics Data Model - `BoxMetrics` (static class) — `FolderTabHeight(Theme)` and `TitleAreaHeight(Theme, bool, bool)`. -## Box Metrics Key Methods +### Box Metrics Key Methods `double FolderTabHeight(Theme theme)` — returns `theme.FontSizeBody + 2 * theme.LabelPadding`. @@ -20,20 +20,50 @@ renderers draw. reserved at the top of a box: zero when the box has neither a name nor a keyword; otherwise a leading padding plus, conditionally, a keyword line and a name line, each followed by a padding. -## Box Metrics Design Constraints +### Box Metrics Error Handling + +Both helpers validate their `theme` argument with `ArgumentNullException.ThrowIfNull` and propagate +the resulting `ArgumentNullException` to the caller — the same fail-fast contract used by the +sibling `NotationMetrics` unit. Callers therefore receive a clear, documented exception (rather than +an undocumented `NullReferenceException`) when a `null` theme is supplied. Beyond that guard, both +helpers are pure arithmetic over the fields of the supplied `Theme`: they do not catch or transform +any exception raised by `Theme` property accessors, and no logging is performed. No other error +paths exist: the helpers never throw for boundary combinations of `hasLabel` and `hasKeyword`, and +the empty-title case (`hasLabel == false && hasKeyword == false`) is handled explicitly by +returning `0.0`. + +### Box Metrics Dependencies + +- **Theme Unit** (same system) — reads `Theme.FontSizeBody`, `Theme.FontSizeTitle`, and + `Theme.LabelPadding`. +- **.NET base class library** — only `System.Double` arithmetic. No third-party runtime packages + are consumed. + +### Box Metrics Callers + +- **Rendering.Svg `SvgRenderer` unit** — calls `FolderTabHeight` when drawing folder-shaped boxes + and `TitleAreaHeight` when placing the title text of every box. +- **Rendering.Skia raster renderers** — call the same helpers for the raster output so drawn + geometry matches the SVG. +- **Rendering.Layout box layout strategies (`ContainmentPacker`, `LayeredPipeline`, + `HierarchicalLayoutAlgorithm`)** — call both helpers to reserve space for the folder tab and title + area when computing box sizes. + +### Box Metrics Design Constraints - `TitleAreaHeight` shall reserve no space when a box has neither a name label nor a keyword line. - Both the layout strategies and the renderers shall compute box title and folder-tab heights from these formulas, so reserved space and drawn space always agree. -## Box Metrics Interactions +### Box Metrics Interactions `BoxMetrics` reads `Theme.FontSizeBody`, `FontSizeTitle`, and `LabelPadding`. It is called by the renderers (SVG and PNG systems) and by the box layout strategies (*Rendering Layout* system). -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | | Rendering-Abstractions-BoxMetrics-FolderTabHeight | `BoxMetrics.FolderTabHeight` | | Rendering-Abstractions-BoxMetrics-TitleAreaHeight | `BoxMetrics.TitleAreaHeight` | +| Rendering-Abstractions-BoxMetrics-RejectNullTheme | `FolderTabHeight`/`TitleAreaHeight` null-argument guard | diff --git a/docs/design/rendering-abstractions/connector-label-placer.md b/docs/design/rendering-abstractions/connector-label-placer.md index 43010b5..2976ae1 100644 --- a/docs/design/rendering-abstractions/connector-label-placer.md +++ b/docs/design/rendering-abstractions/connector-label-placer.md @@ -1,8 +1,8 @@ -# Connector Label Placer Unit Design +## Connector Label Placer Unit Design Part of the Rendering Abstractions system. -## Connector Label Placer Overview +### Connector Label Placer Purpose The Connector Label Placer unit computes non-overlapping screen positions for connector midpoint labels. Each labelled line prefers the midpoint of its longest segment; when two labels would collide, @@ -10,30 +10,58 @@ the placer falls back to a shorter segment or nudges the label perpendicular to longer overlaps an already-placed label. Lines are processed in the supplied order so the result is deterministic, and both renderers share this logic so their label layouts match. -## Connector Label Placer Data Model +### Connector Label Placer Data Model - `ConnectorLabelPlacer` (static class) — `Place(IEnumerable, double)`. -## Connector Label Placer Key Methods +### Connector Label Placer Key Methods `IReadOnlyDictionary Place(IEnumerable lines, double fontSize)` — returns a chosen label centre for every line that has a `MidpointLabel`. Lines without a label, and lines with no waypoints, are omitted. The method estimates each label box from `fontSize`, places the label at the longest clear segment midpoint, and nudges perpendicular to avoid overlap. -## Connector Label Placer Design Constraints +### Connector Label Placer Error Handling + +`Place` validates its `lines` argument with `ArgumentNullException.ThrowIfNull` and propagates the +resulting `ArgumentNullException` to the caller. All other input shapes are treated as normal cases +rather than errors: a line whose `MidpointLabel` is `null` is omitted from the result, a line whose +`Waypoints` collection is empty is likewise omitted, and a line with a single waypoint yields the +degenerate midpoint of that waypoint. The overlap-avoidance search is bounded — after exhausting the +segment fallback and a fixed number of perpendicular nudges, the label is dropped just beneath every +already-placed label, guaranteeing no overlap even when the bounded nudges above are exhausted (for +example where many connectors cross at a single point). The method has no side effects on its inputs +and performs no logging. + +### Connector Label Placer Dependencies + +- **Rendering Model system (`DemaConsulting.Rendering`)** — reads `LayoutLine.MidpointLabel` and + `LayoutLine.Waypoints` from the layout tree types. +- **.NET base class library** — `System.Collections.Generic.IEnumerable`, + `IReadOnlyDictionary`, and standard geometry arithmetic. No third-party runtime + packages are consumed. + +### Connector Label Placer Callers + +- **Rendering.Svg `SvgRenderer` unit** — calls `Place` once per render pass to compute the label + positions for the connector labels it writes as `` elements. +- **Rendering.Skia raster renderers (`SkiaRasterRenderer`, `PngRenderer`, `JpegRenderer`, + `WebpRenderer`)** — call `Place` for the same purpose so that the SVG and raster outputs agree on + label positions. + +### Connector Label Placer Design Constraints - A line without a `MidpointLabel` shall be omitted from the result. - A label shall be placed at the midpoint of its line's longest segment unless doing so would overlap an already-placed label, in which case it shall be moved to a shorter segment or nudged aside. - Placement shall be deterministic for a given input order so the SVG and PNG renderers agree. -## Connector Label Placer Interactions +### Connector Label Placer Interactions `ConnectorLabelPlacer` reads `LayoutLine.MidpointLabel` and `Waypoints` from the rendering model and is called by the renderers (SVG and PNG systems) before drawing connector labels. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-abstractions/notation-metrics.md b/docs/design/rendering-abstractions/notation-metrics.md index 3133f52..2e5acd4 100644 --- a/docs/design/rendering-abstractions/notation-metrics.md +++ b/docs/design/rendering-abstractions/notation-metrics.md @@ -1,8 +1,8 @@ -# Notation Metrics Unit Design +## Notation Metrics Unit Design Part of the Rendering Abstractions system. -## Notation Metrics Overview +### Notation Metrics Purpose The Notation Metrics unit is the single home for all intrinsic, theme-independent notation geometry shared by the SVG and PNG renderers: end-marker (arrowhead) shapes and sizes, the port square, @@ -11,14 +11,14 @@ label-background inset. Every value is either a documented primitive constant or derivation of those primitives, so a geometry literal never appears more than once in the rendering path. `MarkerVertex` expresses one vertex in tip-relative units. -## Notation Metrics Data Model +### Notation Metrics Data Model - `NotationMetrics` (static class) — the end-marker, port, folder, note, badge, and label constants plus the `AlongLineLength`, `TriangleVertices`, `DiamondVertices`, and `RoundedRectRadius` helpers. - `MarkerVertex` (readonly record struct) — `Along` (distance back from the tip) and `Across` (perpendicular offset). -## Notation Metrics Key Methods +### Notation Metrics Key Methods `double AlongLineLength(EndMarkerStyle style)` — returns the along-line length consumed by an end-marker decoration; zero for `EndMarkerStyle.None`. @@ -33,7 +33,37 @@ units, shared by the hollow and filled diamonds. The far point lands exactly on `double RoundedRectRadius(Theme theme)` — returns the theme corner radius scaled by `RoundedRectCornerFactor`. -## Notation Metrics Design Constraints +### Notation Metrics Error Handling + +The constants and static helpers on `NotationMetrics` are pure functions. `RoundedRectRadius(Theme)` +validates its `theme` argument with `ArgumentNullException.ThrowIfNull` and propagates the resulting +`ArgumentNullException` to the caller. `AlongLineLength(EndMarkerStyle)` returns `0.0` for +`EndMarkerStyle.None` and for any other value returns the documented marker box length; it does not +throw for out-of-range enum values, treating them the same as `None`. The `TriangleVertices` and +`DiamondVertices` helpers take no arguments, cannot fail, and return the same immutable +`IReadOnlyList` on every call. `MarkerVertex` is a readonly record struct with two +`double` fields and has no failure modes. No logging is performed. + +### Notation Metrics Dependencies + +- **Theme Unit** (same system) — `RoundedRectRadius(Theme)` reads `Theme.LineCornerRadius`; no other + helper on `NotationMetrics` depends on `Theme`. +- **Rendering Model system (`DemaConsulting.Rendering`)** — the `EndMarkerStyle` enum is defined in + the rendering model and is the parameter type for `AlongLineLength`. +- **.NET base class library** — `System.Collections.Generic.IReadOnlyList` for the vertex lists. + No third-party runtime packages are consumed. + +### Notation Metrics Callers + +- **Rendering.Svg `SvgRenderer` unit** — reads the marker constants and calls the vertex helpers to + emit the `` elements and box decorations. +- **Rendering.Skia renderers** — read the same constants and helpers to draw identical decorations on + the raster surface. +- **Rendering.Layout edge routers (`OrthogonalEdgeRouter`, `InterconnectionLayoutEngine`)** — call + `AlongLineLength(EndMarkerStyle)` to reserve clean approach length for the end marker before the + final endpoint. + +### Notation Metrics Design Constraints - The canonical marker values shall be the historical SVG marker dimensions (triangle 10x7 refX 9, diamond 14x8 refX 13, circle radius 4, bar 4x12); every renderer shall derive its markers from these @@ -41,19 +71,27 @@ units, shared by the hollow and filled diamonds. The far point lands exactly on - Each derived constant shall be documented as a derivation of a named primitive so no geometry literal is duplicated. -## Notation Metrics Interactions +### Notation Metrics Interactions `NotationMetrics.RoundedRectRadius` reads `Theme.LineCornerRadius`. The end-marker helpers are called by both renderers (SVG and PNG systems) and by the layout strategies (*Rendering Layout* system) that reserve a clean approach using `AlongLineLength`. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | -| Rendering-Abstractions-NotationMetrics-TriangleGeometry | `NotationMetrics.TriangleVertices` and triangle constants | -| Rendering-Abstractions-NotationMetrics-DiamondGeometry | `NotationMetrics.DiamondVertices` and diamond constants | +| Rendering-Abstractions-NotationMetrics-TriangleDimensions | `NotationMetrics.TriangleVertices` and triangle values | +| Rendering-Abstractions-NotationMetrics-TriangleApexOvershoot | `NotationMetrics.TriangleVertices` apex overshoot | +| Rendering-Abstractions-NotationMetrics-DiamondDimensions | `NotationMetrics.DiamondVertices` and diamond constants | +| Rendering-Abstractions-NotationMetrics-DiamondFarPointOnEndpoint | `NotationMetrics.DiamondVertices` far point | | Rendering-Abstractions-NotationMetrics-CircleBarGeometry | `NotationMetrics` circle and bar constants | | Rendering-Abstractions-NotationMetrics-Crossbar | `NotationMetrics.CrossbarX` | | Rendering-Abstractions-NotationMetrics-AlongLineLength | `NotationMetrics.AlongLineLength` | -| Rendering-Abstractions-NotationMetrics-BoxDecorations | `NotationMetrics` decoration constants | +| Rendering-Abstractions-NotationMetrics-PortSquare | `NotationMetrics` port square constants | +| Rendering-Abstractions-NotationMetrics-FolderTab | `NotationMetrics` folder-tab constants | +| Rendering-Abstractions-NotationMetrics-NoteFold | `NotationMetrics` note-fold constants | +| Rendering-Abstractions-NotationMetrics-RoundedRectCorner | `NotationMetrics.RoundedRectRadius` | +| Rendering-Abstractions-NotationMetrics-RejectNullTheme | `NotationMetrics.RoundedRectRadius` null-theme guard | +| Rendering-Abstractions-NotationMetrics-Badge | `NotationMetrics` badge constants | +| Rendering-Abstractions-NotationMetrics-LabelBackground | `NotationMetrics` label-background constants | diff --git a/docs/design/rendering-abstractions/registries.md b/docs/design/rendering-abstractions/registries.md index 9f77a5e..3541792 100644 --- a/docs/design/rendering-abstractions/registries.md +++ b/docs/design/rendering-abstractions/registries.md @@ -1,8 +1,8 @@ -# Registries Unit Design +## Registries Unit Design Part of the Rendering Abstractions system. -## Registries Overview +### Registries Purpose The Registries unit provides two service-provider lookups. `LayoutAlgorithmRegistry` keys algorithms by their `Id`; `RendererRegistry` keys renderers by their `MediaType` and by every advertised @@ -10,13 +10,13 @@ by their `Id`; `RendererRegistry` keys renderers by their `MediaType` and by eve resolve one at run time by algorithm identifier, output media type, or output file extension. Neither registry is thread-safe for concurrent registration. -## Registries Data Model +### Registries Data Model - `LayoutAlgorithmRegistry` (sealed class) — `Ids`, `Register`, `Contains`, `TryResolve`, `Resolve`. - `RendererRegistry` (sealed class) — `MediaTypes`, `FileExtensions`, `Register`, `Contains`, `ContainsExtension`, `TryResolve`, `TryResolveByExtension`, `Resolve`, and `ResolveByExtension`. -## Registries Key Methods +### Registries Key Methods `LayoutAlgorithmRegistry Register(ILayoutAlgorithm algorithm)` — stores the algorithm keyed by its `Id`, replacing any previous algorithm with the same identifier, and returns the registry for fluent @@ -35,7 +35,43 @@ adding an optional leading dot when needed, and lower-casing it for lookup. It r registered for that extension or throws `KeyNotFoundException`; `TryResolveByExtension` performs the same lookup without throwing. -## Registries Design Constraints +### Registries Error Handling + +Both registries validate their string and reference parameters with `ArgumentNullException.ThrowIfNull` +and propagate the resulting `ArgumentNullException` to the caller. `Resolve(string)` and +`ResolveByExtension(string)` throw `KeyNotFoundException` when no algorithm or renderer is registered +under the requested id, media type, or (normalized) file extension; the message includes the +requested key so a configuration mistake is immediately diagnosable. The non-throwing variants +(`TryResolve`, `TryResolveByExtension`) return `false` and set the `out` parameter to `null` in the +same missing-entry cases. No exceptions are caught internally: any exception raised by the underlying +`Dictionary` (for example from an invalid key type at registration time) surfaces to the +caller unchanged. The registries are documented as not thread-safe for concurrent registration; the +callers are responsible for building each registry on a single thread before publishing it. + +### Registries Dependencies + +- **Rendering Contracts Unit** (same system) — `ILayoutAlgorithm` and `IRenderer` are the value types + stored and returned by the two registries; `IRenderer.MediaType` and `IRenderer.FileExtensions` + supply the keys used by `RendererRegistry`. +- **.NET base class library** — `System.Collections.Generic.Dictionary` for the backing + storage and `System.Collections.ObjectModel.ReadOnlyCollection` for the `Ids`, `MediaTypes`, + and `FileExtensions` snapshots. + +No OTS runtime component or Shared Package is consumed. + +### Registries Callers + +- **Rendering.Layout `DefaultLayout` unit** — builds a `LayoutAlgorithmRegistry` populated with the + layered, containment, and hierarchical algorithms and resolves the algorithm identified by + `CoreOptions.Algorithm` before invoking `ILayoutAlgorithm.Apply`. +- **Rendering.Svg and Rendering.Skia systems** — register their `IRenderer` implementations in a + shared `RendererRegistry` so callers can resolve a renderer by media type (for example + `image/svg+xml`, `image/png`) or by an output file extension (`.svg`, `.png`, `.jpg`, `.webp`). +- **End-user applications** that host the rendering pipeline resolve algorithms and renderers by + identifier or extension when translating CLI arguments or filename hints into concrete + implementations. + +### Registries Design Constraints - `Resolve` shall raise `KeyNotFoundException` when the requested identifier or media type is not registered, so a configuration mistake surfaces immediately rather than as a later null-reference @@ -45,14 +81,14 @@ same lookup without throwing. - `RendererRegistry` shall index every advertised renderer extension, and extension lookup shall ignore case and tolerate an omitted leading dot so file-driven callers can pass user-supplied suffixes. -## Registries Interactions +### Registries Interactions The registries hold `ILayoutAlgorithm` and `IRenderer` instances from the Rendering Contracts unit. A caller resolves an algorithm using the identifier from `CoreOptions.Algorithm` and resolves a renderer using either the desired output media type or an output filename extension such as `.svg`, `.png`, or `webp`. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-abstractions/rendering-abstractions.md b/docs/design/rendering-abstractions/rendering-abstractions.md deleted file mode 100644 index cafa4c9..0000000 --- a/docs/design/rendering-abstractions/rendering-abstractions.md +++ /dev/null @@ -1,61 +0,0 @@ -# Rendering Abstractions Design - -## Overview - -`DemaConsulting.Rendering.Abstractions` is the service provider interface (SPI) that sits between the -rendering model (*Rendering Model* system, `DemaConsulting.Rendering`) and the concrete layout and -renderer implementations. It defines the pluggable `ILayoutAlgorithm` and `IRenderer` contracts, the -registries that resolve an implementation by identifier, media type, or file extension, the visual -`Theme` model, and the single-source geometry helpers (`NotationMetrics`, `BoxMetrics`, -`ConnectorLabelPlacer`) that let every renderer draw identical decorations. The package depends only -on the rendering model and the .NET base class library. - -The ELK-inspired flow is: a `LayoutGraph` plus `LayoutOptions` is passed to an `ILayoutAlgorithm`, -which produces a placed `LayoutTree`; an `IRenderer` then draws that tree to an output stream. -Algorithms and renderers are selected at run time through the registries, so additional diagram -families and output formats are introduced purely additively. - -## Software Structure - -```text -DemaConsulting.Rendering.Abstractions (System) -├── RenderingContracts (Unit) -├── Registries (Unit) -├── Theme (Unit) -├── NotationMetrics (Unit) -├── BoxMetrics (Unit) -└── ConnectorLabelPlacer (Unit) -``` - -- **Rendering Contracts** — `ILayoutAlgorithm`, `IRenderer`, `RenderOptions`, `RenderOutput`. - Detailed in Rendering Contracts Unit Design. -- **Registries** — `LayoutAlgorithmRegistry`, `RendererRegistry`. Detailed in - Registries Unit Design. -- **Theme** — `Theme` and the built-in `Themes`. Detailed in Theme Unit Design. -- **Notation Metrics** — `NotationMetrics` and `MarkerVertex`. Detailed in - Notation Metrics Unit Design. -- **Box Metrics** — `BoxMetrics`. Detailed in Box Metrics Unit Design. -- **Connector Label Placer** — `ConnectorLabelPlacer`. Detailed in - Connector Label Placer Unit Design. - -## System Interactions - -A `LayoutGraph` plus `LayoutOptions` from the *Rendering Model* system is the input consumed by a -selected `ILayoutAlgorithm`; the algorithm produces a placed `LayoutTree`, which a selected -`IRenderer` draws to a concrete stream. The selected renderer receives `RenderOptions`, including a -`Theme`, and uses the shared notation, box, and connector-label geometry helpers to keep SVG and raster -outputs consistent. - -Callers resolve algorithms from `LayoutAlgorithmRegistry` by the configured algorithm identifier. -Callers resolve renderers from `RendererRegistry` by media type or by output file extension. Concrete -algorithm and renderer implementations live in the downstream Rendering.Layout, Rendering.Svg, and -Rendering.Skia systems; this system defines only the contracts, registries, and shared geometry those -implementations use. - -## Requirements Traceability - -| Requirement ID | Satisfied by | -| --- | --- | -| Rendering-Abstractions-Extensibility | The contracts and registries units | -| Rendering-Abstractions-Theming | The `Theme` and `Themes` types (see Theme Unit Design) | -| Rendering-Abstractions-SharedGeometry | The notation, box, and label geometry units | diff --git a/docs/design/rendering-abstractions/rendering-contracts.md b/docs/design/rendering-abstractions/rendering-contracts.md index 4a83447..6f3d5a9 100644 --- a/docs/design/rendering-abstractions/rendering-contracts.md +++ b/docs/design/rendering-abstractions/rendering-contracts.md @@ -1,8 +1,8 @@ -# Rendering Contracts Unit Design +## Rendering Contracts Unit Design Part of the Rendering Abstractions system. -## Contracts Overview +### Contracts Purpose The Rendering Contracts unit defines the two extension-point interfaces and the value types that flow across them. `ILayoutAlgorithm` is the high-level extension point that turns an unplaced graph into a @@ -10,7 +10,7 @@ placed tree; `IRenderer` is the low-level extension point that turns a placed tr stream. `RenderOptions` carries the theme and sizing for a render, and `RenderOutput` bundles one rendered stream with its metadata. -## Contracts Data Model +### Contracts Data Model - `ILayoutAlgorithm` (interface) — `Id` and `Apply(LayoutGraph, LayoutOptions)`. - `IRenderer` (interface) — `MediaType`, `DefaultExtension`, `FileExtensions`, and @@ -18,7 +18,56 @@ rendered stream with its metadata. - `RenderOptions` (sealed record) — `Theme`, `Scale`, `Dpi`, `DepthLimit`. - `RenderOutput` (sealed record) — `SuggestedFileName`, `MediaType`, `Data`, `Warnings`. -## Contracts Design Constraints +### Contracts Key Methods + +`LayoutTree ILayoutAlgorithm.Apply(LayoutGraph graph, LayoutOptions options)` — turns an unplaced +graph plus caller-supplied options into a placed layout tree. Preconditions: `graph` and `options` +are non-null; the implementation is free to interpret any subset of `options` it understands and to +ignore the remainder. Postcondition: the returned `LayoutTree` describes the placed graph and is +independent of the input `graph` reference. + +`void IRenderer.Render(LayoutTree layout, RenderOptions options, Stream output)` — writes the +rendered artefact for `layout` to `output` using the theme, scale, DPI, and depth limit supplied by +`options`. Preconditions: `layout`, `options`, and `output` are non-null and `output` is writable. +Postcondition: all rendered bytes have been written to `output`; the renderer performs no +filesystem access itself. + +The identity members — `ILayoutAlgorithm.Id`, `IRenderer.MediaType`, `IRenderer.DefaultExtension`, +and `IRenderer.FileExtensions` — expose the stable keys used by the Registries unit to resolve +implementations at run time. + +### Contracts Error Handling + +The contracts do not define custom exception types. Implementers are expected to validate reference +parameters (`ArgumentNullException`) and to write only to the caller-supplied `Stream`; the +registries unit adds `KeyNotFoundException` when an id, media type, or file extension is not +registered. `RenderOptions` and `RenderOutput` are `sealed record` types whose properties are +initialised by their primary constructors and require no additional validation. No logging is +performed at the contract level; renderers surface non-fatal issues by populating +`RenderOutput.Warnings` rather than by throwing. + +### Contracts Dependencies + +- **Rendering Model system (`DemaConsulting.Rendering`)** — `LayoutGraph`, `LayoutOptions`, and + `LayoutTree` are the data types that flow across `ILayoutAlgorithm.Apply` and `IRenderer.Render`. +- **Theme Unit** (same system) — `RenderOptions.Theme` references `Theme`. +- **.NET base class library** — `System.IO.Stream`, `System.Collections.Generic.IReadOnlyList`. + No third-party runtime packages are consumed. + +### Contracts Callers + +- **Registries Unit** (same system) — `LayoutAlgorithmRegistry` and `RendererRegistry` store, + index, and resolve implementations of `ILayoutAlgorithm` and `IRenderer` by their identity + members. +- **Rendering.Layout system** — every layout algorithm (`LayeredLayoutAlgorithm`, + `ContainmentLayoutAlgorithm`, `HierarchicalLayoutAlgorithm`, and the `DefaultLayout` facade) + implements `ILayoutAlgorithm`. +- **Rendering.Svg and Rendering.Skia systems** — `SvgRenderer`, `PngRenderer`, `JpegRenderer`, and + `WebpRenderer` implement `IRenderer` and produce `RenderOutput` records. +- **Host applications** — construct `RenderOptions`, pass a `Stream` to `IRenderer.Render`, and + consume the produced `RenderOutput` metadata. + +### Contracts Design Constraints - An `ILayoutAlgorithm` shall expose a stable `Id` that matches the value read from `CoreOptions.Algorithm`, and shall ignore options it does not understand so callers may pass options @@ -28,15 +77,17 @@ rendered stream with its metadata. - Adding a new algorithm or renderer shall be an additive change: a new implementation of these interfaces requires no change to the existing contracts. -## Contracts Interactions +### Contracts Interactions `ILayoutAlgorithm.Apply` consumes a `LayoutGraph` and `LayoutOptions` from the rendering model and produces a `LayoutTree`. `IRenderer.Render` consumes that `LayoutTree` and a `RenderOptions` (whose `Theme` comes from the Theme unit). Instances are registered in and resolved from the Registries unit. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | -| Rendering-Abstractions-Contracts-Algorithm | `ILayoutAlgorithm.Id` and `ILayoutAlgorithm.Apply` | -| Rendering-Abstractions-Contracts-Renderer | `IRenderer` output contract members | +| Rendering-Abstractions-Contracts-AlgorithmIdentifier | `ILayoutAlgorithm.Id` | +| Rendering-Abstractions-Contracts-AlgorithmApply | `ILayoutAlgorithm.Apply` | +| Rendering-Abstractions-Contracts-RendererIdentity | `IRenderer.MediaType`, `DefaultExtension`, `FileExtensions` | +| Rendering-Abstractions-Contracts-RendererWrite | `IRenderer.Render` | diff --git a/docs/design/rendering-abstractions/theme.md b/docs/design/rendering-abstractions/theme.md index 00bddbd..42e55bf 100644 --- a/docs/design/rendering-abstractions/theme.md +++ b/docs/design/rendering-abstractions/theme.md @@ -1,21 +1,21 @@ -# Theme Unit Design +## Theme Unit Design Part of the Rendering Abstractions system. -## Theme Overview +### Theme Purpose The Theme unit defines the visual parameters for rendering — depth-indexed fill colors, stroke color and width, corner radius, font sizes, padding, and connector-approach geometry — and provides three ready-made themes (`Light`, `Dark`, `Print`). Font choice is intentionally not part of the theme; each renderer hardcodes its own typeface for consistent output. -## Theme Data Model +### Theme Data Model - `Theme` (sealed record) — `DepthFillColors`, `StrokeColor`, `StrokeWidth`, `LineCornerRadius`, `FontSizeTitle`, `FontSizeBody`, `LabelPadding`, `ConnectorStub`, `BendRadius`, `CleanLegMargin`. - `Themes` (static class) — the built-in `Light`, `Dark`, and `Print` themes. -## Theme Key Methods +### Theme Key Methods `double ConnectorApproachZone(double connectorClearance)` — returns the clear distance a connector needs off a box face before it can bend, computed as `ConnectorStub + BendRadius + connectorClearance`. @@ -23,19 +23,52 @@ needs off a box face before it can bend, computed as `ConnectorStub + BendRadius `string BackgroundColor` — the depth-0 fill color, used to occlude a connector line behind a hollow enclosing end marker. -## Theme Design Constraints +### Theme Error Handling + +`Theme` is a `sealed record` whose values are supplied through its primary constructor; the compiler +enforces that every property is initialized at construction time and no runtime validation is +performed by the type itself. `ConnectorApproachZone(double)` and the `BackgroundColor` accessor are +pure arithmetic and indexed reads over the record's own state, so they cannot fail for a +well-formed `Theme`. Callers that supply their own themes are expected to populate +`DepthFillColors` with at least one entry (indexed as `[0]` by `BackgroundColor`); an empty +collection would surface as `IndexOutOfRangeException` from the underlying list access. The built-in +`Themes.Light`, `Themes.Dark`, and `Themes.Print` instances satisfy this precondition by +construction. No logging is performed. + +### Theme Dependencies + +- **.NET base class library** — `System.Collections.Generic.IReadOnlyList` for + `DepthFillColors`. No other runtime dependency. +- No dependency on other units, OTS runtime components, or Shared Packages. + +### Theme Callers + +- **Rendering Contracts Unit** (same system) — `RenderOptions.Theme` carries a `Theme` reference into + the render invocation. +- **NotationMetrics Unit** (same system) — `NotationMetrics.RoundedRectRadius(Theme)` reads + `Theme.LineCornerRadius`. +- **BoxMetrics Unit** (same system) — `BoxMetrics.FolderTabHeight(Theme)` and + `BoxMetrics.TitleAreaHeight(Theme, bool, bool)` read `FontSizeBody`, `FontSizeTitle`, and + `LabelPadding`. +- **Rendering.Svg and Rendering.Skia renderer systems** — read the theme's colors, stroke, font + sizes, and padding directly, and call `ConnectorApproachZone` when reserving connector approach + space. +- **Rendering.Layout engines** — read `ConnectorStub`, `BendRadius`, and `CleanLegMargin` when + reserving space for connector routing. + +### Theme Design Constraints - `ConnectorApproachZone` shall equal the sum of the perpendicular stub, the corner bend radius, and the caller-supplied clearance, so that space reserved by layout matches geometry drawn by renderers. - The `Light` and `Dark` themes shall carry identical connector geometry; the `Print` theme shall use a tighter stub and a zero bend radius suited to monochrome output. -## Theme Interactions +### Theme Interactions `Theme` is carried by `RenderOptions` (Rendering Contracts unit) and read by `NotationMetrics` and `BoxMetrics` when deriving box and marker geometry. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout.md b/docs/design/rendering-layout.md new file mode 100644 index 0000000..73f9998 --- /dev/null +++ b/docs/design/rendering-layout.md @@ -0,0 +1,102 @@ +# Rendering.Layout Design + +## Architecture + +`DemaConsulting.Rendering.Layout` is the placement system for the Rendering stack. A caller supplies a +`LayoutGraph` plus `LayoutOptions`; the system returns a placed `LayoutTree` of boxes and orthogonally +routed connectors that downstream renderers can draw without layout knowledge. The system exposes +bundled algorithms for layered, containment, and hierarchical layout, built on reusable geometric +engines. + +The system is composed of one subsystem and a set of public algorithm and facade units: + +```text +DemaConsulting.Rendering.Layout (System) +├── Engine (Subsystem) +│ ├── OrthogonalEdgeRouter (Unit) +│ ├── ContainmentPacker (Unit) +│ ├── InterconnectionLayoutEngine (Unit) +│ └── LayeredPipeline (Unit) +├── EdgeRoutingOption (Unit) +├── ConnectorRouter (Unit) +├── ContainmentLayout (Unit) +├── ContainmentLayoutAlgorithm (Unit) +├── HierarchicalLayoutAlgorithm (Unit) +├── DefaultLayout (Unit) +└── LayeredLayoutAlgorithm (Unit) +``` + +- **Engine** — reusable, model-agnostic geometric engines (orthogonal routing, containment packing, + interconnection placement, and the ELK-style layered stage pipeline). Detailed in Engine Subsystem + Design. +- **EdgeRoutingOption** — routing-style configuration keys. Detailed in EdgeRoutingOption Unit Design. +- **ConnectorRouter** — routes connectors among already placed boxes. Detailed in ConnectorRouter Unit + Design. +- **ContainmentLayout** — packs already sized model boxes into a container region. Detailed in + ContainmentLayout Unit Design. +- **ContainmentLayoutAlgorithm** — public containment algorithm. Detailed in ContainmentLayoutAlgorithm + Unit Design. +- **HierarchicalLayoutAlgorithm** — recursive compound-graph algorithm. Detailed in + HierarchicalLayoutAlgorithm Unit Design. +- **DefaultLayout** — bundled registry factory and `LayoutEngine` facade. Detailed in DefaultLayout Unit + Design. +- **LayeredLayoutAlgorithm** — public layered algorithm. Detailed in LayeredLayoutAlgorithm Unit + Design. + +The public algorithms implement `ILayoutAlgorithm` from Rendering.Abstractions and compose the Engine +subsystem's model-agnostic geometry. The `DefaultLayout` facade resolves the requested algorithm from +the bundled registry. The hierarchical algorithm composes leaf algorithms per container and routes +cross-container edges at the owning scope. + +## External Interfaces + +- **`ILayoutAlgorithm` implementations** — outbound; `LayeredLayoutAlgorithm`, + `ContainmentLayoutAlgorithm`, and `HierarchicalLayoutAlgorithm` each realize the Abstractions layout + contract, accepting a `LayoutGraph` plus `LayoutOptions` and returning a placed `LayoutTree`. Each + advertises an identifier (`layered`, `containment`, `hierarchical`) for registry resolution. +- **`LayoutEngine` facade / bundled registry (DefaultLayout)** — outbound entry points; resolve and run + the requested (or default) algorithm. +- **`EdgeRoutingOption`** — outbound `LayoutProperty` keys that select connector routing style. +- **Engine APIs (`OrthogonalEdgeRouter`, `ContainmentPacker`, `InterconnectionLayoutEngine`, + `LayeredPipeline`)** — internal geometric services composed by the public algorithms; not intended + for direct renderer use. + +## Dependencies + +The system references the *Rendering Model* package (`DemaConsulting.Rendering`) for `LayoutGraph`, +`LayoutOptions`, `CoreOptions`, and `LayoutTree`, and the *Rendering Abstractions* package +(`DemaConsulting.Rendering.Abstractions`) for the `ILayoutAlgorithm` contract and registries. It has no +runtime NuGet package dependencies beyond the .NET base class library; all NuGet references are +build-time-only private assets (SBOM, SourceLink, API documentation, and `Polyfill`). No OTS runtime +component or Shared Package is consumed. + +## Risk Control Measures + +N/A - general-purpose rendering libraries carry no safety-related risk controls requiring +architectural segregation (IEC 62304 §5.3.3). + +## Data Flow + +```text +LayoutGraph + LayoutOptions + │ + ▼ selected ILayoutAlgorithm (layered / containment / hierarchical) + Engine geometry (layered pipeline, packing, routing) + │ + ▼ + LayoutTree (placed boxes + orthogonally routed connectors) ──► renderer +``` + +A caller passes an input graph and options; the selected algorithm drives the Engine subsystem to +assign positions, pack containers, and route connectors, then returns a placed `LayoutTree`. Renderers +consume only that placed tree and never call the geometric engines directly. + +## Design Constraints + +- **Target frameworks**: `net8.0`, `net9.0`, and `net10.0`. +- **Determinism**: algorithms and engines are stateless between calls and produce reproducible + geometry for the same input, which the byte-identity and legacy-oracle tests pin. +- **Orthogonal routing**: connectors are routed orthogonally through channels so downstream renderers + draw axis-aligned paths without further computation. +- **Model and Abstractions dependency only**: the package depends on the rendering model and the SPI, + with no other runtime component, keeping layout replaceable and independent of any renderer. diff --git a/docs/design/rendering-layout/connector-router.md b/docs/design/rendering-layout/connector-router.md index d6445ac..a8059a9 100644 --- a/docs/design/rendering-layout/connector-router.md +++ b/docs/design/rendering-layout/connector-router.md @@ -1,8 +1,8 @@ -# ConnectorRouter Unit Design +## ConnectorRouter Unit Design Part of the Rendering Layout system. -## ConnectorRouter Purpose +### ConnectorRouter Purpose `ConnectorRouter` is the public routing-orchestration entry point for connecting boxes that some caller has already placed. Given the placed boxes and a list of connections, it produces one routed @@ -12,7 +12,7 @@ delegating the actual path to the router selected by the `EdgeRouting` style. It routes connectors among boxes whose positions are already fixed (for example a containment or free-form placement produced outside the layered pipeline). -## ConnectorRouter Data Model +### ConnectorRouter Data Model The unit comprises three public types plus the `EdgeRouting` option: @@ -30,7 +30,7 @@ It mirrors ELK's `elk.edgeRouting` and today carries the single value `Orthogona exposed on the open property system as `CoreOptions.EdgeRouting` (id `rendering.edgerouting`, default `Orthogonal`), so routing can be selected per scope alongside `CoreOptions.Algorithm`. -## ConnectorRouter Methods +### ConnectorRouter Methods `Route(boxes, connection, options)` rejects null arguments (including a null `From` or `To`) with `ArgumentNullException`, then: @@ -51,13 +51,38 @@ exposed on the open property system as `CoreOptions.EdgeRouting` (id `rendering. The batch overload applies the single-connection routine to each connection and returns one line per connection in input order. -## ConnectorRouter Error Handling +### ConnectorRouter Error Handling Null `boxes`, `connections`, `connection`, `options`, or a connection's `From`/`To` throw `ArgumentNullException`. An `EdgeRouting` value with no shipped router throws `NotSupportedException`; this is unreachable today because `Orthogonal` is the only enum value, but guards future additions. -## ConnectorRouter Interactions +### ConnectorRouter Dependencies + +`ConnectorRouter` depends on the following items: + +- **Rendering model** (`DemaConsulting.Rendering`) — the `LayoutBox`, `LayoutLine`, `Point2D`, `Rect`, + `PortSide`, `EndMarkerStyle`, `LineStyle`, and `EdgeRouting` value types used to describe placed + boxes and produced route lines. +- **Engine subsystem** (`OrthogonalEdgeRouter` unit) — the internal orthogonal path-finding engine + invoked by `RouteWithStatus` for the `EdgeRouting.Orthogonal` style. See _OrthogonalEdgeRouter Unit + Design_. + +No OTS runtime component or shared package is consumed. + +### ConnectorRouter Callers + +`ConnectorRouter` is used by units that have already placed boxes and need to draw connectors among +them: + +- **HierarchicalLayoutAlgorithm** — calls `ConnectorRouter.Route` at each hierarchical scope to route + cross-container edges around sibling containers after the leaf algorithms have placed the + containers themselves. See _HierarchicalLayoutAlgorithm Unit Design_. +- **External application code** — any caller that supplies its own placed `LayoutBox` list (for + example from a containment or free-form placement produced outside the layered pipeline) and needs + routed `LayoutLine` connectors to drop into a `LayoutTree`. + +### ConnectorRouter Interactions `ConnectorRouter` consumes the `LayoutBox`, `LayoutLine`, `Point2D`, `Rect`, `PortSide`, `EndMarkerStyle`, `LineStyle`, and `EdgeRouting` model types and the internal `OrthogonalEdgeRouter` @@ -65,7 +90,7 @@ engine. It produces `LayoutLine` nodes that a caller drops into a `LayoutTree` a `LayoutBox` nodes for a renderer to draw. It is independent of the layered pipeline and can be used on any set of placed boxes. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/containment-layout-algorithm.md b/docs/design/rendering-layout/containment-layout-algorithm.md index 0f9b8c4..4d34a85 100644 --- a/docs/design/rendering-layout/containment-layout-algorithm.md +++ b/docs/design/rendering-layout/containment-layout-algorithm.md @@ -1,8 +1,8 @@ -# ContainmentLayoutAlgorithm Unit Design +## ContainmentLayoutAlgorithm Unit Design Part of the Rendering Layout system. -## ContainmentLayoutAlgorithm Purpose +### ContainmentLayoutAlgorithm Purpose `ContainmentLayoutAlgorithm` is a second public `ILayoutAlgorithm` implementation alongside `LayeredLayoutAlgorithm`. Where the layered algorithm arranges nodes by their connectivity into @@ -13,7 +13,7 @@ the `ConnectorRouter` orchestration — rather than the layered pipeline, and su group as peers inside a container rather than flowing along a directed spine. It is additive: adding it changes no existing output and leaves the layered algorithm untouched. -## ContainmentLayoutAlgorithm Data Model +### ContainmentLayoutAlgorithm Data Model The class is stateless and sealed. It exposes the `AlgorithmId` constant (`"containment"`) and returns it from the `Id` property, the stable identifier under which the algorithm is selected and registered. @@ -24,7 +24,7 @@ single behavior is `Apply(LayoutGraph graph, LayoutOptions options)`, which retu carrying the packed region size and a flat list of `LayoutNode` items (`LayoutBox` per top-level node followed by `LayoutLine` per routed edge). -## ContainmentLayoutAlgorithm Methods +### ContainmentLayoutAlgorithm Methods `Apply(graph, options)` rejects null `graph` or `options` with `ArgumentNullException`, then: @@ -43,27 +43,48 @@ followed by `LayoutLine` per routed edge). represents each endpoint. Edges referencing a node outside the graph's top-level nodes are skipped, mirroring the layered algorithm's handling of out-of-graph endpoints. 5. **Routing.** Routes the connections around the packed boxes via `ConnectorRouter.Route`, selecting - the routing style from `CoreOptions.EdgeRouting` on the supplied options (default `Orthogonal`). + the routing style with `ResolveEdgeRouting`: the graph's own explicit `CoreOptions.EdgeRouting` + override takes precedence, then the supplied options' value, falling back to the property's default + (`Orthogonal`) only when neither declares one — mirroring `LayeredLayoutAlgorithm.ResolveDirection`'s + graph-then-options-then-default resolution of `CoreOptions.Direction`. This lets a graph-level + override be honored whether the algorithm is invoked directly or as a scope's leaf algorithm inside + `HierarchicalLayoutAlgorithm` (which passes each scope's already-cascaded effective options). 6. **Assembly.** Returns a `LayoutTree` with the region `Width`/`Height` and the packed boxes followed by the routed lines. An empty graph yields an empty `LayoutTree` with a positive-size canvas, because the packer returns a padding-only region for no children and no connections are routed. -## ContainmentLayoutAlgorithm Error Handling +### ContainmentLayoutAlgorithm Error Handling Null `graph` or `options` throw `ArgumentNullException`. Edges with an out-of-graph endpoint are skipped rather than treated as errors. All other behavior is inherited from the composed `ContainmentLayout` and `ConnectorRouter` units. -## ContainmentLayoutAlgorithm Interactions +### ContainmentLayoutAlgorithm Dependencies -`ContainmentLayoutAlgorithm` depends on the `ILayoutAlgorithm`, `LayoutGraph`, `LayoutTree`, -`CoreOptions`, and related model types, and composes the public `ContainmentLayout` and -`ConnectorRouter` units of this same system. It is resolvable by renderers and callers through the -layout registry under the `"containment"` identifier, selected via `CoreOptions.Algorithm`. +- `DemaConsulting.Rendering.Abstractions` — `ILayoutAlgorithm` defines the service-provider contract + this public algorithm implements. +- `DemaConsulting.Rendering` — `LayoutGraph`, `LayoutTree`, `LayoutBox`, `LayoutLine`, and the graph + node and edge model types carry the input graph and returned placed layout. +- `CoreOptions` and `LayoutOptions` (Rendering model system) — supply the selected edge-routing style + and the algorithm-selection option that chooses this unit. +- `ContainmentLayout` (same system) — packs the top-level boxes into rows within the derived width + budget. +- `ConnectorRouter` (same system) — routes the top-level connections around the packed boxes. -## Requirements Traceability +### ContainmentLayoutAlgorithm Callers + +Renderers and other consumers do not construct routing geometry directly; they resolve this unit +through the layout registry under the `"containment"` identifier and select it through +`CoreOptions.Algorithm` when a reading-order containment layout is requested. + +### ContainmentLayoutAlgorithm Interactions + +`ContainmentLayoutAlgorithm` composes `ContainmentLayout` for packing and `ConnectorRouter` for edge +routing, adapting their combined output to the public `ILayoutAlgorithm` contract. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | @@ -74,3 +95,4 @@ layout registry under the `"containment"` identifier, selected via `CoreOptions. | Rendering-Layout-ContainmentAlgorithm-EmptyGraph | ContainmentLayoutAlgorithm behavior described above | | Rendering-Layout-ContainmentAlgorithm-SkipsOutOfGraphEdges | ContainmentLayoutAlgorithm behavior described above | | Rendering-Layout-ContainmentAlgorithm-Validation | ContainmentLayoutAlgorithm behavior described above | +| Rendering-Layout-ContainmentAlgorithm-HonorsScopeEdgeRouting | ContainmentLayoutAlgorithm behavior described above | diff --git a/docs/design/rendering-layout/containment-layout.md b/docs/design/rendering-layout/containment-layout.md index 80439d5..1d6f9e9 100644 --- a/docs/design/rendering-layout/containment-layout.md +++ b/docs/design/rendering-layout/containment-layout.md @@ -1,8 +1,8 @@ -# ContainmentLayout Unit Design +## ContainmentLayout Unit Design Part of the Rendering Layout system. -## ContainmentLayout Purpose +### ContainmentLayout Purpose `ContainmentLayout` is the public, model-speaking containment building block: it packs a set of already-sized `LayoutBox` children into a single container region, arranging them into rows within a @@ -13,7 +13,7 @@ layout (for example the members of a package or the contents of a folder). It is primitive; multi-level folder/canvas assembly is composed from it by higher layers rather than provided here. -## ContainmentLayout Data Model +### ContainmentLayout Data Model The unit comprises the public static class plus two records: @@ -26,7 +26,7 @@ The unit comprises the public static class plus two records: coordinates, in input order. - `ContainmentLayout` — a stateless static class exposing `Pack(children, options)`. -## ContainmentLayout Methods +### ContainmentLayout Methods `Pack(children, options)` rejects null arguments — a null `children` list, null `options`, or any null child element — with `ArgumentNullException`, then: @@ -46,13 +46,13 @@ within the reported region (which includes the outer padding on every side), pla the content width alone on its own row while widening the region to contain it, and returns a padding-only region for an empty input. -## ContainmentLayout Error Handling +### ContainmentLayout Error Handling Null `children`, `options`, or a null child element throw `ArgumentNullException`. Packing behavior for degenerate sizes (zero or negative dimensions) follows the underlying `ContainmentPacker`; the public operation adds no further validation beyond null rejection. -## ContainmentLayout Interactions +### ContainmentLayout Interactions `ContainmentLayout` consumes the `LayoutBox` model type and the internal `ContainmentPacker`, `PackItem`, `PackedRect`, and `PackResult` engine types, which remain internal to the Layout system — @@ -60,7 +60,7 @@ the public API speaks only `LayoutBox`. It produces `LayoutBox` children that a container box (offsetting by the container's placement) and drops into a `LayoutTree` for a renderer to draw. It is independent of the layered pipeline and can be used on any set of sized boxes. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/default-layout.md b/docs/design/rendering-layout/default-layout.md index a3d8904..c294c8f 100644 --- a/docs/design/rendering-layout/default-layout.md +++ b/docs/design/rendering-layout/default-layout.md @@ -1,8 +1,8 @@ -# DefaultLayout Unit Design +## DefaultLayout Unit Design Part of the Rendering Layout system. -## DefaultLayout Purpose +### DefaultLayout Purpose `LayoutAlgorithms` and `LayoutEngine` form the batteries-included happy path: the smallest possible way to lay out a graph with the algorithm it declares. `LayoutAlgorithms` is a factory for a @@ -12,7 +12,7 @@ whatever algorithm it declares" into one call that correctly handles both flat a no registry assembly or engine choice required of the caller. Both units are additive: they compose the existing algorithms and change no existing behavior. -## DefaultLayout Data Model +### DefaultLayout Data Model Both units are static and hold no per-call state. `LayoutAlgorithms.CreateDefaultRegistry()` builds a fresh `LayoutAlgorithmRegistry` and registers `LayeredLayoutAlgorithm` (`"layered"`), @@ -22,7 +22,7 @@ returning a new, independently mutable instance on each call. `LayoutEngine` exp built once from `CreateDefaultRegistry()`; because the bundled algorithms are stateless, that shared registry is safe to read (resolve) concurrently. -## DefaultLayout Methods +### DefaultLayout Methods `LayoutEngine.Layout(graph, options)` resolves against the shared default registry; `LayoutEngine.Layout(graph, options, registry)` resolves against a caller-supplied registry. Both reject @@ -43,7 +43,7 @@ container nodes the engine returns output byte-for-byte identical to the selecte (default `"layered"`) applied directly. A flat graph therefore lays out exactly as the layered algorithm would, while a nested graph is composed correctly — with no decision required from the caller. -## DefaultLayout Design Constraints +### DefaultLayout Design Constraints - The factory shall live in the Layout package, not in Abstractions, because it references the concrete bundled algorithms; the `LayoutAlgorithmRegistry` it populates remains in Abstractions. This keeps the @@ -54,20 +54,52 @@ would, while a nested graph is composed correctly — with no decision required - The facade shall consult only explicit algorithm declarations when resolving, so an unset graph and options reach the hierarchical default rather than the layered property default. -## DefaultLayout Error Handling +### DefaultLayout Error Handling Null `graph`, `options`, or (three-argument overload) `registry` throw `ArgumentNullException`. A declared algorithm identifier absent from the resolving registry surfaces the registry's `KeyNotFoundException`. -## DefaultLayout Interactions +### DefaultLayout Dependencies + +`LayoutAlgorithms` and `LayoutEngine` depend on the following items: + +- **Rendering.Abstractions** (`LayoutAlgorithmRegistry`, `ILayoutAlgorithm`) — the registry type + populated by `CreateDefaultRegistry` and the algorithm contract resolved and invoked by + `LayoutEngine.Layout`. +- **Rendering model** (`DemaConsulting.Rendering`) — the `LayoutGraph`, `LayoutOptions`, and + `LayoutTree` types on the public `Layout` signature, plus `CoreOptions.Algorithm` used for + algorithm-identifier resolution. +- **Layout units** (`LayeredLayoutAlgorithm`, `ContainmentLayoutAlgorithm`, + `HierarchicalLayoutAlgorithm`) — the three bundled algorithms registered by the default registry. + See the respective Unit Design documents. + +No OTS runtime component or shared package is consumed. + +### DefaultLayout Callers + +`LayoutAlgorithms` and `LayoutEngine` are consumed by: + +- **External application code** — the primary caller. Applications invoke `LayoutEngine.Layout(graph, + options)` (or the three-argument overload with a custom registry) as the batteries-included happy + path for going from `LayoutGraph` to placed `LayoutTree` with a single call. +- **Renderer host code** (for example downstream of `SvgRenderer` / `PngRenderer`) — callers that + pair `LayoutEngine.Layout(...)` with an `IRenderer` to go from graph to rendered output in two + calls. +- **Test host code** — the `LayoutAlgorithms.CreateDefaultRegistry()` factory is also consumed + directly by tests that need a pre-populated, independently mutable registry. + +No other Rendering.Layout unit calls into DefaultLayout; the dependency direction is always +application → `LayoutEngine` → bundled algorithms. + +### DefaultLayout Interactions `LayoutAlgorithms` depends on `LayoutAlgorithmRegistry` and the three bundled algorithm units. `LayoutEngine` depends on `LayoutAlgorithms`, `LayoutAlgorithmRegistry`, `LayoutGraph`, `LayoutOptions`, `LayoutTree`, and `CoreOptions`. Callers typically pair `LayoutEngine.Layout(...)` with an `IRenderer` (for example `SvgRenderer`) to go from graph to rendered output in two calls. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/edge-routing-option.md b/docs/design/rendering-layout/edge-routing-option.md index 688195f..b000c66 100644 --- a/docs/design/rendering-layout/edge-routing-option.md +++ b/docs/design/rendering-layout/edge-routing-option.md @@ -1,8 +1,8 @@ -# EdgeRouting Option Unit Design +## EdgeRouting Option Unit Design Part of the Rendering Layout system. -## EdgeRouting Option Overview +### EdgeRouting Option Overview The EdgeRouting option is the Layout-owned routing-selection behavior used when connectors are routed among already-placed boxes. The closed `EdgeRouting` enum and the `CoreOptions.EdgeRouting` property key @@ -10,7 +10,7 @@ are declared in the Rendering model so the open option system can carry the valu or standalone option scope. Rendering.Layout realizes the behavior by reading that option and dispatching `ConnectorRouter` to the corresponding routing implementation. -## EdgeRouting Option Data Model +### EdgeRouting Option Data Model - `CoreOptions.EdgeRouting` — the open property key with id `rendering.edgerouting`, defaulting to `EdgeRouting.Orthogonal`. @@ -19,7 +19,7 @@ or standalone option scope. Rendering.Layout realizes the behavior by reading th - `ConnectorRouteOptions(EdgeRouting, Clearance)` — the Layout-side route options record consumed by `ConnectorRouter`, defaulting to orthogonal routing and twelve logical pixels of clearance. -## EdgeRouting Option Behavior +### EdgeRouting Option Behavior A caller can set `CoreOptions.EdgeRouting` on any property holder and read the selected routing style back through the same open option system. An unset scope returns the declared orthogonal default. @@ -27,13 +27,59 @@ back through the same open option system. An unset scope returns the declared or single shipped value dispatches to the internal orthogonal router. The switch is structured for additive future routing styles while preserving the current default behavior. -## EdgeRouting Option Scope Note +### EdgeRouting Option Key Methods + +The unit has no methods of its own; it is a configuration realization. Behavior is composed from: + +- `IPropertyHolder.Get(CoreOptions.EdgeRouting)` — reads the effective routing style from any option + scope (graph, node, edge, or standalone `LayoutOptions`), falling back to the declared + `EdgeRouting.Orthogonal` default when no scope has set the key. +- `IPropertyHolder.Set(CoreOptions.EdgeRouting, value)` — writes an explicit routing style at a + scope. +- `new ConnectorRouteOptions(EdgeRouting, Clearance)` — constructs the Layout-side route options + record consumed by `ConnectorRouter.Route`; the no-argument constructor with default values produces + `Orthogonal` routing with a `Clearance` of `12.0` logical pixels. + +Preconditions: the property key `CoreOptions.EdgeRouting` is declared exactly once by the Rendering +model. Post-conditions: reads of any scope return either the caller-set value or the declared default; +`ConnectorRouter.Route` dispatches to the router realizing the returned `EdgeRouting` value. + +### EdgeRouting Option Error Handling + +The option itself performs no I/O and raises no errors: reads always succeed with either a +caller-set value or the declared default, and writes accept any `EdgeRouting` enum value defined by +the model. Runtime dispatch errors are surfaced downstream by `ConnectorRouter`, which throws +`NotSupportedException` if a shipped router is not available for the effective `EdgeRouting` value +(see _ConnectorRouter Unit Design_). + +### EdgeRouting Option Dependencies + +- **Rendering model** (`DemaConsulting.Rendering`) — declares the closed `EdgeRouting` enum and the + `CoreOptions.EdgeRouting` property key with id `rendering.edgerouting`. This unit does not own + those types; it owns the Layout-side behavior of consuming the option. +- **Options system** (`IPropertyHolder`, `LayoutProperty`, `LayoutOptions`) — the open property + system that carries the value at any scope. +- **ConnectorRouter unit** — consumes the effective value through `ConnectorRouteOptions` and + dispatches to the corresponding router. + +No OTS runtime component or shared package is consumed. + +### EdgeRouting Option Callers + +- **ConnectorRouter** — reads the effective `EdgeRouting` value from a `ConnectorRouteOptions` + record to select the routing implementation on each `Route` call. +- **HierarchicalLayoutAlgorithm** — reads the option from the current options scope when routing + cross-container edges via `ConnectorRouter`. +- **External application code** — sets `CoreOptions.EdgeRouting` on a graph, node, edge, or + standalone `LayoutOptions` to select the routing style per scope. + +### EdgeRouting Option Scope Note The enum declaration lives in the Rendering model project because `CoreOptions` belongs to the shared configuration vocabulary. This Layout unit therefore owns the behavior of consuming the option for routing, not the model file that declares the enum. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/engine/containment-packer.md b/docs/design/rendering-layout/engine/containment-packer.md index 0e5a052..7669f5e 100644 --- a/docs/design/rendering-layout/engine/containment-packer.md +++ b/docs/design/rendering-layout/engine/containment-packer.md @@ -1,15 +1,15 @@ -# ContainmentPacker Unit Design +### ContainmentPacker Unit Design Part of the Rendering Layout system. -## ContainmentPacker Purpose +#### ContainmentPacker Purpose `ContainmentPacker` arranges a sequence of variable-size items into rows within a width budget. It places items left to right, wraps to a new row when the next item would exceed the maximum content width, and sizes the enclosing region to fit all items plus uniform outer padding. It is used to pack child elements inside a containing box in a compact, ordered grid. -## ContainmentPacker Data Model +#### ContainmentPacker Data Model `ContainmentPacker` is a static class with no instance state. Inputs are a list of `PackItem` records (each a `Width` and `Height`), a `maxContentWidth`, a `horizontalGap`, a `verticalGap`, and @@ -17,7 +17,7 @@ a `padding`. The result is a `PackResult` record carrying the region `Width`, `H ordered list of `PackedRect` rectangles, one per input item in input order, each positioned relative to the region origin `(0, 0)`. -## ContainmentPacker Methods +#### ContainmentPacker Methods `Pack(items, maxContentWidth, horizontalGap, verticalGap, padding)` computes the packing as a single left-to-right shelf (row) pass: @@ -38,20 +38,20 @@ left-to-right shelf (row) pass: Input order is preserved, and the left-to-right, no-backtracking placement is what guarantees that no two rectangles overlap and that every rectangle stays within the reported region. -## ContainmentPacker Error Handling +#### ContainmentPacker Error Handling A null `items` argument throws `ArgumentNullException`. An empty item list returns a padding-only region. No other input causes a throw; an oversized item is handled by the first-in-row exemption rather than by an error. -## ContainmentPacker Interactions +#### ContainmentPacker Interactions `ContainmentPacker` depends only on the `PackItem`, `PackedRect`, and `PackResult` value types declared alongside it. It is a leaf engine invoked by callers that pack child elements inside a containing box, using the returned rectangles to position children and the region size to size the container. -## Requirements Traceability +#### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/engine/engine.md b/docs/design/rendering-layout/engine/engine.md index 3601a9c..e2c1bb4 100644 --- a/docs/design/rendering-layout/engine/engine.md +++ b/docs/design/rendering-layout/engine/engine.md @@ -1,15 +1,15 @@ -# Engine Subsystem Design +### Engine Subsystem Design Part of the Rendering Layout system. -## Engine Subsystem Overview +#### Engine Subsystem Overview The Engine subsystem holds the reusable geometric components. Each is independent of any semantic model and operates purely on sizes, edges, anchors, and rectangles. The `Rect` value type they consume is the public axis-aligned rectangle in logical pixels defined by the `DemaConsulting.Rendering` model and returned by the placement engines. -## Engine Units +#### Engine Units ```text Engine (Subsystem) @@ -31,7 +31,53 @@ Engine (Subsystem) This subsystem design intentionally does not restate unit internals; those details live in the unit design documents listed above. -## Requirements Traceability +#### Engine Interfaces + +The Engine subsystem is a Layout-internal collection of geometric services; it exposes no public +`ILayoutAlgorithm` or `IRenderer` interface of its own. Its interfaces are: + +- **Inbound (composed by the public Layout algorithms and by `ConnectorRouter`):** + - `OrthogonalEdgeRouter.RouteWithStatus(source, target, obstacles, clearance, sourceSide?, + targetSide?, costBands?)` and its thin `Route` wrapper — single-connector orthogonal routing + returning ordered `Point2D` waypoints and a `Crossed` flag. + - `ContainmentPacker.Pack(items, options)` — shelf-packs sized items into rows within a content + width and returns their placed rectangles plus the enclosing region. + - `InterconnectionLayoutEngine.Place(graph, options)` — placement adapter that runs the + `LayeredPipeline` and returns the placed rectangles and routed connectors for a graph. + - `LayeredLayoutPipeline.RunDefaultStages(graph)` (and the composable `AddStage` / `Run` pair) — + the ELK-style staged pipeline over a `LayeredGraph`. +- **Consumed (from other software items):** + - The Rendering-model geometric value types (`Point2D`, `Rect`, `PortSide`, `CostBand`) and the + `LayeredGraph` internal representation used by the pipeline stages. + +The subsystem exposes no external SPI: renderers and application code never call these engines +directly; they reach them only through the public Layout algorithms (`LayeredLayoutAlgorithm`, +`ContainmentLayoutAlgorithm`, `HierarchicalLayoutAlgorithm`) and `ConnectorRouter`. + +#### Engine Design + +The subsystem is organized as four independent geometric leaves that the public algorithms +compose: + +- **OrthogonalEdgeRouter** is a stateless static class that performs A\*-style search over a grid + derived from endpoint and obstacle coordinates, with a clearance-retry ladder and a turn penalty; + every orthogonal single-connector route in the system passes through it. +- **ContainmentPacker** shelf-packs variable-size items into rows for a content-width budget; it + underpins `ContainmentLayout` and the layered-pipeline `ComponentPacker` stage. +- **InterconnectionLayoutEngine** adapts a `LayoutGraph` into the pipeline's `LayeredGraph`, runs + the default pipeline, and translates the pipeline output back to placed rectangles and routed + connectors that the public algorithms wrap in `LayoutBox` / `LayoutLine` nodes. +- **LayeredPipeline** owns the ELK-style Sugiyama sequence — cycle breaking, layer assignment, + long-edge splitting, crossing minimization, port distribution, Brandes-Köpf coordinate + assignment, orthogonal routing, long-edge joining, component packing, and the axis transform. + +Each engine is deterministic and stateless between calls: no engine mutates its inputs. Data flows +one direction only — the public algorithms drive the engines; engines never call back into the +public algorithms. This keeps the subsystem replaceable and independently testable, and lets the +same `OrthogonalEdgeRouter` and `ContainmentPacker` implementations back both the layered pipeline +and the free-form `ConnectorRouter` / `ContainmentLayout` entry points. + +#### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/engine/interconnection-layout-engine.md b/docs/design/rendering-layout/engine/interconnection-layout-engine.md index a6d7e31..e4c5941 100644 --- a/docs/design/rendering-layout/engine/interconnection-layout-engine.md +++ b/docs/design/rendering-layout/engine/interconnection-layout-engine.md @@ -1,8 +1,8 @@ -# InterconnectionLayoutEngine Unit Design +### InterconnectionLayoutEngine Unit Design Part of the Rendering Layout system. -## InterconnectionLayoutEngine Purpose +#### InterconnectionLayoutEngine Purpose `InterconnectionLayoutEngine` places directed graphs and routes all connector lines using a full Sugiyama-style pipeline. It is a thin facade that assembles and runs the reusable @@ -12,7 +12,7 @@ are the stable internal contract; for the default Right direction the facade pro identical geometry to the previous monolithic implementation, proven by an equivalence test against a legacy oracle. -## InterconnectionLayoutEngine Data Model +#### InterconnectionLayoutEngine Data Model `InterconnectionLayoutEngine` is a static class with no instance state. Input is an `IReadOnlyList` (width and height per node) and an `IReadOnlyList` (directed @@ -21,7 +21,7 @@ order, the bounding-box totals, a `NodeLayers` list of longest-path layer indice `ConnectorWaypoints` list of orthogonal waypoints, and the `AcyclicEdges` set that is index-aligned with `ConnectorWaypoints`. -## InterconnectionLayoutEngine Methods +#### InterconnectionLayoutEngine Methods `Place(nodes, edges, direction)` builds a `LayeredGraph` from the inputs, assembles a `LayeredLayoutPipeline` with the default stages for the requested `direction` (defaulting to Right), @@ -39,26 +39,30 @@ coordinates, then assigns them to `TotalWidth`/`TotalHeight` per direction: for along-axis is the width and for Down/Up it is the height. The Right path is unchanged and remains byte-identical. -## InterconnectionLayoutEngine Error Handling +#### InterconnectionLayoutEngine Error Handling Null `nodes` or `edges` arguments throw `ArgumentNullException`. An empty `nodes` list returns a minimal-size `LayerResult` with empty lists without performing any computation. Out-of-range edge indices and self-loops are ignored by the pipeline stages. -## InterconnectionLayoutEngine Interactions +#### InterconnectionLayoutEngine Interactions `InterconnectionLayoutEngine` depends on `LayeredLayoutPipeline` and `LayeredGraph` (the staged pipeline it assembles and runs), the `Rect` value type, and the `Point2D` point type used for waypoints. It is called by the public `LayeredLayoutAlgorithm` and by the interconnection view strategy to obtain a placement result. -## Requirements Traceability +#### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | | Rendering-Layout-InterconnectionEngine-Layering | InterconnectionLayoutEngine behavior described above | | Rendering-Layout-InterconnectionEngine-NonOverlapping | InterconnectionLayoutEngine behavior described above | | Rendering-Layout-InterconnectionEngine-DummyNodes | InterconnectionLayoutEngine behavior described above | -| Rendering-Layout-InterconnectionEngine-Waypoints | InterconnectionLayoutEngine behavior described above | -| Rendering-Layout-InterconnectionEngine-Direction | InterconnectionLayoutEngine behavior described above | +| Rendering-Layout-InterconnectionEngine-Waypoints-AcyclicMapping | See above | +| Rendering-Layout-InterconnectionEngine-Waypoints-StraightSpanOne | See above | +| Rendering-Layout-InterconnectionEngine-Waypoints-LongEdgeRouting | See above | +| Rendering-Layout-InterconnectionEngine-Direction-RequestedFlow | See above | +| Rendering-Layout-InterconnectionEngine-Direction-TransposedTotals | See above | +| Rendering-Layout-InterconnectionEngine-Direction-DefaultsToRight | See above | | Rendering-Layout-InterconnectionEngine-Deterministic | InterconnectionLayoutEngine behavior described above | diff --git a/docs/design/rendering-layout/engine/layered-pipeline.md b/docs/design/rendering-layout/engine/layered-pipeline.md index 81c0afc..fc7ca33 100644 --- a/docs/design/rendering-layout/engine/layered-pipeline.md +++ b/docs/design/rendering-layout/engine/layered-pipeline.md @@ -1,8 +1,8 @@ -# Layered Pipeline Unit Design +### Layered Pipeline Unit Design Part of the Rendering Layout system. -## Layered Pipeline Overview +#### Layered Pipeline Overview The layered pipeline is a reusable, composable layered-layout engine that reproduces ELK's layered (Sugiyama-style) algorithm. It replaces a single monolithic placement method with an ordered @@ -12,7 +12,7 @@ time. It was produced by a behavior-preserving extraction: for every input it pr same rectangles, totals, layer assignments, and connector waypoints as the previous implementation, verified byte for byte by the pipeline-equivalence tests. -## Layered Pipeline Data Model +#### Layered Pipeline Data Model `LayeredGraph` is the mutable shared state threaded through every stage. Construction takes the input `LayerNode` list, the `LayerEdge` list, and a `LayoutDirection`, rejecting null nodes or edges with @@ -32,7 +32,7 @@ previous monolithic engine so the pipeline reproduces its output exactly. The `L selects Right, Down, Left, or Up flow; `HierarchyHandling` selects Flat (supported) or Recursive (reserved). -## Layered Pipeline Assembly +#### Layered Pipeline Assembly A pipeline is assembled through the fluent `LayeredLayoutPipeline.PipelineBuilder` returned by `Builder()`. The builder exposes `Direction`, `Hierarchy`, `AddStage`, `AddDefaultStages`, and @@ -41,7 +41,7 @@ A pipeline is assembled through the fluent `LayeredLayoutPipeline.PipelineBuilde `AxisTransform.NormalizeInputAxes` to normalize the input node axes for the requested direction, then applies every stage in order. `Run` rejects a null graph with `ArgumentNullException`. -## Layered Pipeline Stages +#### Layered Pipeline Stages The default stage sequence added by `AddDefaultStages` runs in this order: @@ -75,16 +75,31 @@ The default stage sequence added by `AddDefaultStages` runs in this order: `ComponentPacker` is an optional composite stage added explicitly by callers that lay out potentially disconnected graphs. It splits the graph into connected components, runs an inner stage sequence on each, and packs the results without overlap in a deterministic order, translating each component's -boxes and waypoints together. +boxes and waypoints together. Each component is laid out through a freshly constructed child +`LayeredGraph`, which copies the parent graph's `BackEdgeEntryApproach` so a caller-customized +reversed-edge clearance is honored consistently whether the input graph is packed into one component +or several. -## Layered Pipeline Interactions +#### Layered Pipeline Dependencies -All types are internal and consume only the geometric value types of the Layout system (`Point2D`, -`Rect`) plus the internal `LayerNode`, `LayerEdge`, `AugNode`, and `AugEdge` records; no stage -references any semantic model. The pipeline is assembled and run by `InterconnectionLayoutEngine` -and, transitively, by the public `LayeredLayoutAlgorithm`. +All pipeline types are internal and consume only the geometric value types of the Layout system +(`Point2D`, `Rect`) plus the internal `LayerNode`, `LayerEdge`, `AugNode`, and `AugEdge` records. +No stage depends on the semantic `LayoutGraph` model, any OTS runtime component, or any Shared +Package. -## Requirements Traceability +#### Layered Pipeline Callers + +The pipeline is assembled and run directly by `InterconnectionLayoutEngine`, and the public +`LayeredLayoutAlgorithm` consumes it transitively through that engine when the layered algorithm is +selected. + +#### Layered Pipeline Interactions + +The stage sequence collaborates only through the shared `LayeredGraph` state, with +`InterconnectionLayoutEngine` adapting the final rectangles, dimensions, and waypoints to the +public layout result contract. + +#### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | @@ -103,5 +118,6 @@ and, transitively, by the public `LayeredLayoutAlgorithm`. | Rendering-Layout-LayeredPipeline-BackEdgeApproach | Layered pipeline behavior described above | | Rendering-Layout-LayeredPipeline-LongEdgeJoining | Layered pipeline behavior described above | | Rendering-Layout-LayeredPipeline-ComponentPacking | Layered pipeline behavior described above | +| Rendering-Layout-LayeredPipeline-PackedComponentsBackEdgeApproach | Layered pipeline behavior described above | | Rendering-Layout-LayeredPipeline-SharedState | Layered pipeline behavior described above | | Rendering-Layout-LayeredPipeline-InputValidation | Layered pipeline behavior described above | diff --git a/docs/design/rendering-layout/engine/orthogonal-edge-router.md b/docs/design/rendering-layout/engine/orthogonal-edge-router.md index 22bae0f..53fa34c 100644 --- a/docs/design/rendering-layout/engine/orthogonal-edge-router.md +++ b/docs/design/rendering-layout/engine/orthogonal-edge-router.md @@ -1,21 +1,21 @@ -# OrthogonalEdgeRouter Unit Design +### OrthogonalEdgeRouter Unit Design Part of the Rendering Layout system. -## OrthogonalEdgeRouter Purpose +#### OrthogonalEdgeRouter Purpose `OrthogonalEdgeRouter` routes a single orthogonal connector between a source anchor and a target anchor, steering around obstacle rectangles and keeping a requested clearance. It is the engine through which all single-connector routing quality flows. -## OrthogonalEdgeRouter Data Model +#### OrthogonalEdgeRouter Data Model `OrthogonalEdgeRouter` is a static class with no instance state. Inputs are the source and target `Point2D` anchors, a list of obstacle `Rect`, a clearance distance, optional source and target `PortSide` values, and an optional list of `CostBand` records. The result is a `RouteResult` record carrying the ordered `Waypoints` and a `Crossed` flag. -## OrthogonalEdgeRouter Methods +#### OrthogonalEdgeRouter Methods `RouteWithStatus(source, target, obstacles, clearance, sourceSide?, targetSide?, costBands?)` computes the route and reports whether it had to cross an obstacle. The algorithm is: @@ -40,19 +40,43 @@ segment's length is scaled by the cheapest band covering its midpoint, so a disc attracts wires into shared corridors while a null band list leaves cost neutral. The thin `Route` wrapper returns only the `Waypoints` for callers that do not need the crossing status. -## OrthogonalEdgeRouter Error Handling +#### OrthogonalEdgeRouter Error Handling Null `source`, `target`, or `obstacles` arguments throw `ArgumentNullException`. Degenerate geometry never throws: when no clean route exists the router returns a crossing route with `Crossed = true` rather than failing, leaving the decision to surface a warning to the caller. -## OrthogonalEdgeRouter Interactions +#### OrthogonalEdgeRouter Dependencies + +`OrthogonalEdgeRouter` depends on the following items: + +- **Rendering model** (`DemaConsulting.Rendering`) — the `Point2D` value type for anchors, the + `Rect` value type for obstacles, the `PortSide` enumeration for perpendicular stub direction, and + the `CostBand` record for corridor cost biasing. +- **.NET base class library** — no other runtime dependency. + +No OTS runtime component or shared package is consumed. + +#### OrthogonalEdgeRouter Callers + +`OrthogonalEdgeRouter` is a leaf engine invoked wherever a single connector must be routed +orthogonally through an obstacle field: + +- **ConnectorRouter** — dispatches to `RouteWithStatus` for every connection routed under the + `EdgeRouting.Orthogonal` style. See _ConnectorRouter Unit Design_. +- **LayeredPipeline** (`OrthogonalRouter` stage) — routes individual layered-pipeline edges through + the same engine so pipeline routes and free-form routes share one implementation. See _Layered + Pipeline Unit Design_. + +The `Crossed` flag returned by `RouteWithStatus` feeds each caller's layout-warning handling. + +#### OrthogonalEdgeRouter Interactions `OrthogonalEdgeRouter` depends only on the `Point2D` and `Rect` geometric value types and the `PortSide` enumeration for perpendicular-stub direction. It is a leaf engine invoked directly by callers that route individual connectors; the `Crossed` flag feeds their layout-warning handling. -## Requirements Traceability +#### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/hierarchical-layout-algorithm.md b/docs/design/rendering-layout/hierarchical-layout-algorithm.md index 6fe322d..9200398 100644 --- a/docs/design/rendering-layout/hierarchical-layout-algorithm.md +++ b/docs/design/rendering-layout/hierarchical-layout-algorithm.md @@ -1,8 +1,8 @@ -# HierarchicalLayoutAlgorithm Unit Design +## HierarchicalLayoutAlgorithm Unit Design Part of the Rendering Layout system. -## HierarchicalLayoutAlgorithm Purpose +### HierarchicalLayoutAlgorithm Purpose `HierarchicalLayoutAlgorithm` is a third public `ILayoutAlgorithm` implementation: the recursive hierarchical layout engine, analogous to ELK's `RecursiveGraphLayoutEngine`. Where the layered and @@ -13,7 +13,7 @@ selects a bundled *leaf* algorithm per scope and delegates the actual placement container and composes the results. It is additive: it changes no existing output and is honored only when a caller selects it by name. -## HierarchicalLayoutAlgorithm Data Model +### HierarchicalLayoutAlgorithm Data Model The class is sealed and stateless with respect to any single layout. It exposes the `AlgorithmId` constant (`"hierarchical"`) and returns it from `Id`. Two private constants govern container framing: @@ -27,28 +27,33 @@ caller-supplied registry (rejecting null). The engine treats a scope that explic `"hierarchical"` identifier as selecting the default leaf algorithm, so recursion always terminates in a bundled leaf algorithm. -## HierarchicalLayoutAlgorithm Methods +### HierarchicalLayoutAlgorithm Methods -`Apply(graph, options)` rejects null `graph` or `options` with `ArgumentNullException`, resolves the -root scope's algorithm (the graph's explicit `CoreOptions.Algorithm` override if present, otherwise the -options' algorithm — default `"layered"`), and calls the recursive `LayoutScope`. `LayoutScope(graph, -algoId, options)` performs: +`Apply(graph, options)` rejects null `graph` or `options` with `ArgumentNullException`, builds the +root scope's cascaded effective options (`graph.OverlayOnto(options)` — the graph's own explicit +overrides win over the supplied options, exactly like every nested scope), and calls the recursive +`LayoutScope`. `LayoutScope(graph, effective)` performs: 1. **Flat fast path (equivalence guarantee).** If no direct node of the scope is a container - (`HasChildren` is false for all), the engine delegates straight to the resolved leaf algorithm and - returns its `LayoutTree` unchanged — no cloning, no post-processing, no mutation. This guarantees a - flat graph is placed byte-for-byte identically to invoking the leaf algorithm directly. -2. **Post-order recursion.** For each container child, the engine resolves the child's algorithm (the - node's `CoreOptions.Algorithm` override, else the inherited scope algorithm), recursively lays out - the child's subgraph, and records both the sub-layout and the container's effective size — the - sub-layout size grown by `ContainerPadding` on every side plus a title band when the container is - labelled. + (`HasChildren` is false for all), the engine delegates straight to the leaf algorithm resolved from + `effective` and returns its `LayoutTree` unchanged — no cloning, no post-processing, no mutation. + This guarantees a flat graph is placed byte-for-byte identically to invoking the leaf algorithm + directly. +2. **Post-order recursion.** For each container child, the engine builds that child's own cascaded + effective options by overlaying, in order, the container node's own overrides (`CoreOptions.Algorithm` + lives here by convention) and then its `Children` graph's own overrides (every other `CoreOptions` + property lives here by convention) onto the parent scope's already-resolved `effective` snapshot, via + `PropertyHolder.OverlayOnto`. It then recursively lays out the child's subgraph with that snapshot and + records both the sub-layout and the container's effective size — the sub-layout size grown by + `ContainerPadding` on every side plus a title band when the container is labelled. 3. **Sized view.** The engine builds an internal, side-effect-free *view* graph with the same nodes in - the same order (container nodes carrying their effective size, leaves their own size, labels copied) - and only the edges whose endpoints are both direct members of this scope. The caller's input graph - is never mutated. -4. **Placement.** The resolved leaf algorithm places the sized view, emitting one box per node in input - order followed by routed lines for the in-scope edges. + the same order (container nodes carrying their effective size, leaves their own size, labels copied), + only the edges whose endpoints are both direct members of this scope. Every `CoreOptions` property + this scope needs is already present in `effective`, so the view carries no options of its own — the + leaf algorithm resolves them from `effective`, not from the view graph. The caller's input graph is + never mutated. +4. **Placement.** The resolved leaf algorithm places the sized view against `effective`, emitting one box + per node in input order followed by routed lines for the in-scope edges. 5. **Composition.** Each container's placed box receives its recursively laid-out children, translated from their local origin to the box's padded (and title-offset) interior via a recursive `Translate` that shifts nested boxes and line waypoints (local-to-absolute translation, following the @@ -56,12 +61,26 @@ algoId, options)` performs: 6. **Cross-container (LCA) routing.** Edges whose endpoints resolve to different direct-member containers of this scope — mapped from any descendant endpoint up to its owning top-level box — are routed at this level with `ConnectorRouter.Route`, steering around the sibling boxes; the - `EdgeRouting` style is read from the options. Edges already routed by the leaf algorithm (both - endpoints direct) or belonging to a lower scope (both endpoints under one container) are skipped. + `EdgeRouting` style is read from this scope's own cascaded `effective` snapshot, so an override set + on the owning scope's graph is honored rather than falling back to the root options. Edges already + routed by the leaf algorithm (both endpoints direct) or belonging to a lower scope (both endpoints + under one container) are skipped. 7. **Assembly.** The engine returns a `LayoutTree` with the leaf algorithm's canvas size for this level and the composed boxes followed by the leaf-routed lines and the cross-container lines. -## HierarchicalLayoutAlgorithm Design Constraints +Every `CoreOptions` property (`Algorithm`, `Direction`, `EdgeRouting`, `HierarchyHandling`, +`NodeSpacing`, `LayerSpacing`) cascades through this same generalized mechanism, built once on +`PropertyHolder.OverlayOnto` rather than threaded per property: a scope with no override of its own +inherits the nearest ancestor's resolved value, and any scope's own override wins for itself and every +unset descendant. Two established conventions decide where a container's own override lives — +`Algorithm` on the container node itself, every other property on the node's `Children` graph — and +`LayoutContainerChildren` overlays both, in that order, onto the parent's snapshot so either (or both) +take effect at their own layer. It is each leaf algorithm's own responsibility to resolve a property +from the `effective` options it is invoked with (optionally preferring its own graph's override first, +as `LayeredLayoutAlgorithm` and `ContainmentLayoutAlgorithm` do); a leaf algorithm that reads only its +input graph and ignores the supplied options would silently break cascading for that algorithm. + +### HierarchicalLayoutAlgorithm Design Constraints - The flat-graph equivalence guarantee is load-bearing: the fast path must delegate directly to the leaf algorithm and must not clone the graph or transform the tree, so selecting the engine never @@ -72,14 +91,45 @@ algoId, options)` performs: container is laid out in isolation and sized to fit its children. The `CoreOptions.HierarchyHandling` option records this selection; only the separate-children mode is honored today. -## HierarchicalLayoutAlgorithm Error Handling +### HierarchicalLayoutAlgorithm Error Handling Null `graph`, `options`, or (injecting constructor) `registry` throw `ArgumentNullException`. A scope that selects an algorithm identifier absent from the registry surfaces the registry's `KeyNotFoundException`. Edges whose endpoints are not under the current scope are skipped rather than treated as errors. -## HierarchicalLayoutAlgorithm Interactions +### HierarchicalLayoutAlgorithm Dependencies + +`HierarchicalLayoutAlgorithm` depends on the following items: + +- **Rendering.Abstractions** (`ILayoutAlgorithm`, `LayoutAlgorithmRegistry`) — implements the layout + contract and resolves the per-scope leaf algorithm from the injected registry. +- **Rendering model** (`DemaConsulting.Rendering`) — the `LayoutGraph`, `LayoutGraphNode`, + `LayoutTree`, `LayoutBox`, `LayoutLine`, and `Point2D` types on the layout contract, plus + `CoreOptions.Algorithm`, `CoreOptions.Direction`, `CoreOptions.EdgeRouting`, and + `CoreOptions.HierarchyHandling` for configuration, and `PropertyHolder.OverlayOnto` for building each + scope's cascaded effective options snapshot. +- **Layout units** — `LayeredLayoutAlgorithm` and `ContainmentLayoutAlgorithm` as bundled leaf + algorithms registered in the default registry, and `ConnectorRouter` for LCA cross-container edge + routing. See *ConnectorRouter Unit Design*. + +No OTS runtime component or shared package is consumed. + +### HierarchicalLayoutAlgorithm Callers + +`HierarchicalLayoutAlgorithm` is invoked through the `ILayoutAlgorithm` contract, so callers reach +it by algorithm identifier rather than by direct type reference: + +- **DefaultLayout** (`LayoutEngine.Layout`) — resolves this algorithm from the bundled default + registry under the `"hierarchical"` identifier and defaults to it when no algorithm is declared on + the graph or options. See *DefaultLayout Unit Design*. +- **External application code** — any caller that registers `HierarchicalLayoutAlgorithm` in its own + `LayoutAlgorithmRegistry` (or uses `LayoutAlgorithms.CreateDefaultRegistry()`) and selects it via + `CoreOptions.Algorithm = "hierarchical"` on the graph or standalone `LayoutOptions`. +- **Renderer host code** — indirectly via `LayoutEngine.Layout` when driving nested/compound + diagrams from graph to rendered output. + +### HierarchicalLayoutAlgorithm Interactions `HierarchicalLayoutAlgorithm` depends on the `ILayoutAlgorithm`, `LayoutAlgorithmRegistry`, `LayoutGraph`, `LayoutGraphNode`, `LayoutTree`, `CoreOptions`, and related model types, and composes @@ -88,7 +138,7 @@ the public `ConnectorRouter` unit for cross-container routing and the bundled le renderers and callers through the layout registry under the `"hierarchical"` identifier, selected via `CoreOptions.Algorithm`. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | @@ -98,6 +148,8 @@ renderers and callers through the layout registry under the `"hierarchical"` ide | Rendering-Layout-HierarchicalLayout-PerNodeAlgorithm | HierarchicalLayoutAlgorithm behavior described above | | Rendering-Layout-HierarchicalLayout-HierarchyHandling | HierarchicalLayoutAlgorithm behavior described above | | Rendering-Layout-HierarchicalLayout-CrossContainerEdge | HierarchicalLayoutAlgorithm behavior described above | +| Rendering-Layout-HierarchicalLayout-CascadesOptions | HierarchicalLayoutAlgorithm behavior described above | +| Rendering-Layout-HierarchicalLayout-HonorsScopeEdgeRouting | HierarchicalLayoutAlgorithm behavior described above | | Rendering-Layout-HierarchicalLayout-ValidatesGraph | HierarchicalLayoutAlgorithm behavior described above | | Rendering-Layout-HierarchicalLayout-ValidatesOptions | HierarchicalLayoutAlgorithm behavior described above | | Rendering-Layout-HierarchicalLayout-ValidatesRegistry | HierarchicalLayoutAlgorithm behavior described above | diff --git a/docs/design/rendering-layout/layered-layout-algorithm.md b/docs/design/rendering-layout/layered-layout-algorithm.md index 08caa92..87d1583 100644 --- a/docs/design/rendering-layout/layered-layout-algorithm.md +++ b/docs/design/rendering-layout/layered-layout-algorithm.md @@ -1,15 +1,15 @@ -# LayeredLayoutAlgorithm Unit Design +## LayeredLayoutAlgorithm Unit Design Part of the Rendering Layout system. -## LayeredLayoutAlgorithm Purpose +### LayeredLayoutAlgorithm Purpose `LayeredLayoutAlgorithm` is the public `ILayoutAlgorithm` implementation and the system's product boundary. It arranges an input `LayoutGraph` into Sugiyama layers, routes edges orthogonally, and produces a placed `LayoutTree` of boxes and connectors. It wraps the reusable layered pipeline via `InterconnectionLayoutEngine`. -## LayeredLayoutAlgorithm Data Model +### LayeredLayoutAlgorithm Data Model The class is stateless and sealed. It exposes the `AlgorithmId` constant (`"layered"`) and returns it from the `Id` property, the stable identifier under which the algorithm is selected and registered. @@ -17,7 +17,7 @@ Its single behavior is `Apply(LayoutGraph graph, LayoutOptions options)`, which `LayoutTree` carrying the total width and height and a flat list of `LayoutNode` items (`LayoutBox` per node followed by `LayoutLine` per edge). -## LayeredLayoutAlgorithm Methods +### LayeredLayoutAlgorithm Methods `Apply(graph, options)` rejects null `graph` or `options` with `ArgumentNullException`, then: @@ -44,13 +44,13 @@ per node followed by `LayoutLine` per edge). An empty graph yields an empty `LayoutTree` because `InterconnectionLayoutEngine.Place` returns a minimal-size empty result, which produces an empty canvas. -## LayeredLayoutAlgorithm Interactions +### LayeredLayoutAlgorithm Interactions `LayeredLayoutAlgorithm` depends on the `ILayoutAlgorithm`, `LayoutGraph`, `LayoutTree`, and related model types from `DemaConsulting.Rendering.Abstractions` and on `InterconnectionLayoutEngine` from the Engine subsystem. It is the entry point resolved by renderers through the layout registry. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-layout/rendering-layout.md b/docs/design/rendering-layout/rendering-layout.md deleted file mode 100644 index f8b0967..0000000 --- a/docs/design/rendering-layout/rendering-layout.md +++ /dev/null @@ -1,76 +0,0 @@ -# Rendering.Layout Design - -## Overview - -`DemaConsulting.Rendering.Layout` is the placement system for the Rendering stack. A caller supplies a -`LayoutGraph` plus `LayoutOptions`; the system returns a placed `LayoutTree` of boxes and orthogonally -routed connectors that downstream renderers can draw without layout knowledge. - -The system exposes bundled algorithms for layered, containment, and hierarchical layout. Reusable -geometric engines provide orthogonal routing, containment packing, and layered interconnection placement, -while the default facade resolves the requested algorithm from the bundled registry. - -## Software Structure - -```text -DemaConsulting.Rendering.Layout (System) -├── Engine (Subsystem) -│ ├── OrthogonalEdgeRouter (Unit) -│ ├── ContainmentPacker (Unit) -│ ├── InterconnectionLayoutEngine (Unit) -│ └── LayeredPipeline (Unit) -├── EdgeRoutingOption (Unit) -├── ConnectorRouter (Unit) -├── ContainmentLayout (Unit) -├── ContainmentLayoutAlgorithm (Unit) -├── HierarchicalLayoutAlgorithm (Unit) -├── DefaultLayout (Unit) -└── LayeredLayoutAlgorithm (Unit) -``` - -- **Engine** - reusable model-agnostic geometric engines. Detailed in - Engine Subsystem Design, which links to its unit designs. -- **EdgeRoutingOption** - routing-style configuration keys. Detailed in - EdgeRoutingOption Unit Design. -- **ConnectorRouter** - routes connectors among already placed boxes. Detailed in - ConnectorRouter Unit Design. -- **ContainmentLayout** - packs already sized model boxes into a container region. Detailed in - ContainmentLayout Unit Design. -- **ContainmentLayoutAlgorithm** - public containment algorithm. Detailed in - ContainmentLayoutAlgorithm Unit Design. -- **HierarchicalLayoutAlgorithm** - recursive compound-graph algorithm. Detailed in - HierarchicalLayoutAlgorithm Unit Design. -- **DefaultLayout** - bundled registry factory and layout facade. Detailed in - DefaultLayout Unit Design. -- **LayeredLayoutAlgorithm** - public layered algorithm. Detailed in - LayeredLayoutAlgorithm Unit Design. - -## System Interactions - -The system consumes `LayoutGraph`, `LayoutOptions`, `CoreOptions`, and `LayoutTree` from the Rendering -model and implements `ILayoutAlgorithm` from Rendering.Abstractions. Callers may use the bundled registry -and `LayoutEngine` facade, or resolve a specific bundled algorithm directly. Renderers consume only the -placed tree produced by this system; they do not call the geometric engines directly. - -The Engine subsystem operates on model-agnostic geometry and is composed by the public algorithms and -routing helpers. The hierarchical algorithm composes leaf algorithms per container and routes -cross-container edges at the owning scope. - -## Requirements Traceability - -| Requirement ID | Satisfied by | -| --- | --- | -| `Rendering-Layout-Algorithm` | LayeredLayoutAlgorithm | -| `Rendering-Layout-GeometricEngines` | Engine Subsystem | -| `Rendering-Layout-StagedPipeline` | LayeredPipeline | -| `Rendering-Layout-ConnectorRouting` | EdgeRoutingOption, ConnectorRouter | -| `Rendering-Layout-ContainmentPlacement` | ContainmentLayout | -| `Rendering-Layout-ContainmentAlgorithm` | ContainmentLayoutAlgorithm | -| `Rendering-Layout-HierarchicalLayout` | HierarchicalLayoutAlgorithm | -| `Rendering-Layout-DefaultLayout` | DefaultLayout | - -## Scope Exclusions - -Per-unit data models, algorithms, error handling, and design constraints are intentionally excluded from -this system document and live in the unit and subsystem documents linked above. Test projects and test -infrastructure are verification artifacts, not product design scope. diff --git a/docs/design/rendering-skia.md b/docs/design/rendering-skia.md new file mode 100644 index 0000000..2add994 --- /dev/null +++ b/docs/design/rendering-skia.md @@ -0,0 +1,96 @@ +# Rendering.Skia Design + +## Architecture + +The `DemaConsulting.Rendering.Skia` system renders a placed `LayoutTree` to raster images using +SkiaSharp. A shared `SkiaRasterRenderer` base performs all drawing; thin concrete renderers select the +encoded output format. The system draws a diagram once onto a SkiaSharp bitmap and then encodes it in a +concrete image format. + +The system is composed of one shared base unit and three concrete format units: + +```text +Rendering.Skia (System) +├── SkiaRasterRenderer (Unit) — abstract base: rasterizes a LayoutTree to a bitmap and encodes it +├── PngRenderer (Unit) — lossless PNG output +├── JpegRenderer (Unit) — lossy JPEG output +└── WebpRenderer (Unit) — WEBP output +``` + +- **SkiaRasterRenderer** — the shared SkiaSharp rasterizer that allocates the bitmap, initializes it + with the render theme's background color, draws layout-tree nodes (boxes, labels, connectors with end + markers, ports, badges, bands, lifelines, activations, and grids), and encodes the bitmap using the + derived renderer's format. Detailed in SkiaRasterRenderer Unit Design. +- **PngRenderer** — the concrete renderer that emits lossless PNG output. Detailed in PngRenderer Unit + Design. +- **JpegRenderer** — the concrete renderer that emits lossy JPEG output. Detailed in JpegRenderer Unit + Design. +- **WebpRenderer** — the concrete renderer that emits WEBP output. Detailed in WebpRenderer Unit + Design. + +All drawing logic lives in the abstract `SkiaRasterRenderer`; each concrete renderer supplies only the +SkiaSharp encoded-image format, encoding quality, media type, and file extensions. This keeps the +multi-format surface a few lines per format while guaranteeing that PNG, JPEG, and WEBP output share +the same raster drawing path apart from the final encode step. The three concrete renderers do not +interact with each other; each is a leaf that inherits its drawing behavior from `SkiaRasterRenderer`. + +## External Interfaces + +- **`PngRenderer` / `JpegRenderer` / `WebpRenderer` (`: IRenderer`)** — inbound; each realizes the + `IRenderer` contract, accepting a `LayoutTree`, `RenderOptions`, and output `Stream`, and advertising + its media type (`image/png`, `image/jpeg`, `image/webp`) and file extension for registry resolution. +- **Output stream** — outbound; encoded raster image bytes written to the caller-owned `Stream`. + +`SkiaRasterRenderer` is an internal abstract base, not a directly instantiated public entry point. + +## Dependencies + +The system references the *Rendering Model* package (`DemaConsulting.Rendering`) for `LayoutTree` and +node records, and the *Rendering Abstractions* package (`DemaConsulting.Rendering.Abstractions`) for the +`IRenderer` contract, `RenderOptions`, `Theme`, `NotationMetrics`, `BoxMetrics`, and +`ConnectorLabelPlacer`. + +Unlike the other Rendering systems, this system carries a genuine third-party runtime dependency: + +- **SkiaSharp** — the 2D graphics library (MIT license) that provides the bitmap, canvas, and image + encoders used for all raster drawing and encoding. The matching platform-specific SkiaSharp native + asset packages supply the native Skia binaries for Windows, Linux, and macOS. SkiaSharp is + documented as an OTS software item — see "SkiaSharp Integration Design" under `docs/design/ots/` + for its integration pattern. + +The system also embeds a Noto Sans font (SIL Open Font License 1.1) as a resource so text is drawn from +a bundled font rather than an installed system font. The build-time-only NuGet references (SBOM, +SourceLink, API documentation, and `Polyfill`) are private assets and not part of the runtime surface. + +## Risk Control Measures + +N/A - general-purpose rendering libraries carry no safety-related risk controls requiring +architectural segregation (IEC 62304 §5.3.3). + +## Data Flow + +```text +LayoutTree + RenderOptions (Theme) + │ + ▼ concrete renderer (Png / Jpeg / Webp) + SkiaRasterRenderer: allocate bitmap → fill background → draw nodes → encode + │ + ▼ + encoded PNG / JPEG / WEBP bytes ──► caller Stream +``` + +A caller passes a placed `LayoutTree` and `RenderOptions` to a concrete renderer. The shared +`SkiaRasterRenderer` base draws the diagram onto a SkiaSharp bitmap using the embedded font and the +shared geometry helpers, then the concrete renderer encodes the bitmap in its format and writes the +bytes to the caller-owned stream. + +## Design Constraints + +- **Target frameworks**: `net8.0`, `net9.0`, and `net10.0`. +- **SkiaSharp runtime requirement**: raster output requires the SkiaSharp library and its + platform-specific native assets, so this system cannot run in an environment where those native + binaries are unavailable. +- **Byte-reproducible output**: an embedded Noto Sans font is used for all text so raster output is + byte-reproducible across platforms regardless of installed fonts. +- **Shared drawing path**: all formats share the single `SkiaRasterRenderer` drawing path and differ + only in the final encode step, so PNG, JPEG, and WEBP output stay visually consistent. diff --git a/docs/design/rendering-skia/jpeg-renderer.md b/docs/design/rendering-skia/jpeg-renderer.md index 707d4f6..d412a32 100644 --- a/docs/design/rendering-skia/jpeg-renderer.md +++ b/docs/design/rendering-skia/jpeg-renderer.md @@ -1,14 +1,14 @@ -# JpegRenderer Unit Design +## JpegRenderer Unit Design Part of the Rendering.Skia system. -## JpegRenderer Overview +### JpegRenderer Overview `JpegRenderer` is the concrete raster renderer for lossy JPEG output. It derives from `SkiaRasterRenderer` and supplies only the JPEG format-selection metadata and lossy encoding quality; all drawing behaviour comes from the shared rasterizer. -## JpegRenderer Data Model +### JpegRenderer Data Model | Member | Value | | --- | --- | @@ -18,13 +18,49 @@ all drawing behaviour comes from the shared rasterizer. | `DefaultExtension` | `.jpg` | | `FileExtensions` | `.jpg`, `.jpeg` | -## JpegRenderer Interactions +### JpegRenderer Interactions A `RendererRegistry` can resolve `JpegRenderer` by the `image/jpeg` media type or by either advertised file extension. Because JPEG has no transparency channel, it relies on the inherited rasterizer's theme background initialization before encoding. -## Requirements Traceability +### JpegRenderer Key Methods + +`JpegRenderer` declares no methods of its own. It inherits `Render(LayoutTree, RenderOptions, Stream)` +from `SkiaRasterRenderer` (see _SkiaRasterRenderer Unit Design_ for the algorithm, preconditions, and +post-conditions) and overrides only the following members that steer the inherited encode step: + +- `EncodedFormat` returns `SKEncodedImageFormat.Jpeg` so the base class encodes the bitmap as JPEG. +- `EncodingQuality` returns `90`, the JPEG quality setting passed to SkiaSharp's encoder. +- `MediaType`, `DefaultExtension`, and `FileExtensions` return the advertised JPEG identifiers used + by `RendererRegistry` for lookup. + +### JpegRenderer Error Handling + +`JpegRenderer` contains no error-detection or error-handling logic of its own. All argument +validation (`ArgumentNullException` for null `LayoutTree`, `RenderOptions`, or output `Stream`), +minimum-size clamping, and disposal of SkiaSharp resources happen in the inherited +`SkiaRasterRenderer.Render` method — see _SkiaRasterRenderer Unit Design_ under "Error Handling". +Any exceptions raised by SkiaSharp's JPEG encoder (for example if the underlying native asset +package is missing) propagate unchanged to the caller. + +### JpegRenderer Dependencies + +- **`SkiaRasterRenderer` (base unit)** — provides all rasterization, drawing, and encoding logic. +- **SkiaSharp (OTS)** — used only through the `SKEncodedImageFormat.Jpeg` enum value returned by + `EncodedFormat`; all direct SkiaSharp API calls are made by the base class. +- **`DemaConsulting.Rendering.Abstractions`** — provides the `IRenderer` contract that + `JpegRenderer` (through its base class) implements. + +### JpegRenderer Callers + +- **`RendererRegistry`** — resolves this renderer by the `image/jpeg` media type or by the `.jpg` + or `.jpeg` extension. +- **Applications and tools that reference `DemaConsulting.Rendering.Skia`** — either instantiate + `JpegRenderer` directly (`new JpegRenderer().Render(...)`) or resolve it through the registry and + invoke it through the `IRenderer` contract. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-skia/png-renderer.md b/docs/design/rendering-skia/png-renderer.md index feb6c8f..1c0cf0f 100644 --- a/docs/design/rendering-skia/png-renderer.md +++ b/docs/design/rendering-skia/png-renderer.md @@ -1,14 +1,14 @@ -# PngRenderer Unit Design +## PngRenderer Unit Design Part of the Rendering.Skia system. -## PngRenderer Overview +### PngRenderer Overview `PngRenderer` is the concrete raster renderer for lossless PNG output. It derives from `SkiaRasterRenderer` and supplies only the PNG format-selection metadata; all drawing behaviour comes from the shared rasterizer. -## PngRenderer Data Model +### PngRenderer Data Model | Member | Value | | --- | --- | @@ -18,13 +18,51 @@ from the shared rasterizer. | `DefaultExtension` | `.png` | | `FileExtensions` | `.png` | -## PngRenderer Interactions +### PngRenderer Interactions A `RendererRegistry` can resolve `PngRenderer` by the `image/png` media type or by the `.png` file extension. After resolution, callers invoke the inherited `Render` method and receive a PNG byte stream written to their output stream. -## Requirements Traceability +### PngRenderer Key Methods + +`PngRenderer` declares no methods of its own. It inherits `Render(LayoutTree, RenderOptions, Stream)` +from `SkiaRasterRenderer` (see _SkiaRasterRenderer Unit Design_ for the algorithm, preconditions, and +post-conditions) and overrides only the following members that steer the inherited encode step: + +- `EncodedFormat` returns `SKEncodedImageFormat.Png` so the base class encodes the bitmap as PNG. +- `EncodingQuality` is not overridden; the inherited default of `100` is used (PNG is lossless, so + the quality argument does not affect the output). +- `MediaType`, `DefaultExtension`, and `FileExtensions` return the advertised PNG identifiers used + by `RendererRegistry` for lookup. + +### PngRenderer Error Handling + +`PngRenderer` contains no error-detection or error-handling logic of its own. All argument +validation (`ArgumentNullException` for null `LayoutTree`, `RenderOptions`, or output `Stream`), +minimum-size clamping, and disposal of SkiaSharp resources happen in the inherited +`SkiaRasterRenderer.Render` method — see _SkiaRasterRenderer Unit Design_ under "Error Handling". +Any exceptions raised by SkiaSharp's PNG encoder (for example if the underlying native asset +package is missing) propagate unchanged to the caller. + +### PngRenderer Dependencies + +- **`SkiaRasterRenderer` (base unit)** — provides all rasterization, drawing, and encoding logic. +- **SkiaSharp (OTS)** — used only through the `SKEncodedImageFormat.Png` enum value returned by + `EncodedFormat`; all direct SkiaSharp API calls are made by the base class. +- **`DemaConsulting.Rendering.Abstractions`** — provides the `IRenderer` contract that + `PngRenderer` (through its base class) implements. + +### PngRenderer Callers + +- **`RendererRegistry`** — resolves this renderer by the `image/png` media type or by the `.png` + extension. +- **Applications and tools that reference `DemaConsulting.Rendering.Skia`** — either instantiate + `PngRenderer` directly (`new PngRenderer().Render(...)`) or resolve it through the registry and + invoke it through the `IRenderer` contract. The XML documentation example on `PngRenderer` shows + the direct-instantiation usage. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-skia/rendering-skia.md b/docs/design/rendering-skia/rendering-skia.md deleted file mode 100644 index d32fed0..0000000 --- a/docs/design/rendering-skia/rendering-skia.md +++ /dev/null @@ -1,52 +0,0 @@ -# Rendering.Skia Design - -## Overview - -The `DemaConsulting.Rendering.Skia` system renders a placed `LayoutTree` to raster images using -[SkiaSharp](https://github.com/mono/SkiaSharp). A shared `SkiaRasterRenderer` base performs all drawing; -thin concrete renderers select the encoded output format. - -The system draws a diagram once onto a SkiaSharp bitmap and then encodes it in a concrete image format. -All drawing logic - boxes, labels, connectors with end markers, ports, badges, bands, lifelines, -activations, and grids - lives in the abstract `SkiaRasterRenderer`. Each concrete renderer -(`PngRenderer`, `JpegRenderer`, `WebpRenderer`) supplies only the SkiaSharp encoded-image format, -encoding quality, media type, and file extensions. This keeps the multi-format surface a few lines per -format while guaranteeing that PNG, JPEG, and WEBP output share the same raster drawing path apart from -the final encode step. - -An embedded Noto Sans font (SIL OFL 1.1) is used for all text so that raster output is byte-reproducible -across platforms regardless of installed fonts. SkiaSharp is MIT-licensed. - -## Software Structure - -```text -Rendering.Skia (System) -├── SkiaRasterRenderer (Unit) - abstract base: rasterizes a LayoutTree to a bitmap and encodes it -├── PngRenderer (Unit) - lossless PNG output -├── JpegRenderer (Unit) - lossy JPEG output -└── WebpRenderer (Unit) - WEBP output -``` - -- **SkiaRasterRenderer** - the shared SkiaSharp rasterizer that allocates the bitmap, initializes it - with the render theme's background color, draws layout-tree nodes, and encodes the bitmap using the - derived renderer's format. Detailed in SkiaRasterRenderer Unit Design. -- **PngRenderer** - the concrete renderer that emits lossless PNG output. Detailed in - PngRenderer Unit Design. -- **JpegRenderer** - the concrete renderer that emits lossy JPEG output. Detailed in - JpegRenderer Unit Design. -- **WebpRenderer** - the concrete renderer that emits WEBP output. Detailed in - WebpRenderer Unit Design. - -## System Interactions - -The system consumes the `LayoutTree` and node records from the Rendering model, and the `IRenderer`, -`RenderOptions`, `Theme`, `NotationMetrics`, `BoxMetrics`, and `ConnectorLabelPlacer` helpers from the -Rendering.Abstractions system. The three concrete renderers have no interactions with each other; each -is a leaf that inherits all drawing behaviour from `SkiaRasterRenderer` and differs only in the encoded -output format it selects. - -## Requirements Traceability - -| Requirement ID | Satisfied by | -| --- | --- | -| Rendering-Skia-RenderRasterImage | `SkiaRasterRenderer` rasterization plus the three format encoders | diff --git a/docs/design/rendering-skia/skia-raster-renderer.md b/docs/design/rendering-skia/skia-raster-renderer.md index 4edb7ad..c7b11a1 100644 --- a/docs/design/rendering-skia/skia-raster-renderer.md +++ b/docs/design/rendering-skia/skia-raster-renderer.md @@ -1,15 +1,23 @@ -# SkiaRasterRenderer Unit Design +## SkiaRasterRenderer Unit Design Part of the Rendering.Skia system. -## SkiaRasterRenderer Overview +### SkiaRasterRenderer Purpose + +`SkiaRasterRenderer` has a single responsibility: rasterize a placed `LayoutTree` onto a SkiaSharp +bitmap and encode that bitmap to a caller-supplied `Stream` in whatever image format the derived +renderer selects. It centralizes every drawing decision (background fill, node drawing, connector +end-markers, label backplates, typography) so that PNG, JPEG, and WEBP output share identical +pixel-level behaviour and differ only in the final encode step. + +### SkiaRasterRenderer Overview `SkiaRasterRenderer` is the abstract `IRenderer` implementation that provides the common SkiaSharp rasterization path for every raster format. Concrete renderers supply the encoded image format, quality, media type, and file extensions; the base class owns argument validation, bitmap allocation, canvas initialization, node drawing, connector-label finalization, and stream encoding. -## SkiaRasterRenderer Data Model +### SkiaRasterRenderer Data Model | Member | Type | Description | | --- | --- | --- | @@ -19,7 +27,7 @@ initialization, node drawing, connector-label finalization, and stream encoding. | `DefaultExtension` | `abstract string` | The primary output file extension. | | `FileExtensions` | `abstract IReadOnlyList` | Every file extension the concrete renderer produces. | -## SkiaRasterRenderer Methods +### SkiaRasterRenderer Methods - **`Render(LayoutTree, RenderOptions, Stream)`** - validates its arguments, allocates an `SKBitmap` sized from `LayoutTree.Width`/`LayoutTree.Height` scaled by `RenderOptions.Scale` (minimum one by one @@ -32,7 +40,7 @@ initialization, node drawing, connector-label finalization, and stream encoding. the SVG renderer. Hollow marker interiors and midpoint-label backplates use `Theme.BackgroundColor` so they occlude connector strokes with the same background used for the bitmap. -## SkiaRasterRenderer Design Constraints +### SkiaRasterRenderer Design Constraints - The rasterizer shall enforce a minimum bitmap size of one by one pixels before allocating the `SKBitmap`. @@ -44,14 +52,57 @@ initialization, node drawing, connector-label finalization, and stream encoding. - The output stream remains owned by the caller; the renderer writes encoded bytes but does not close or flush the stream. -## SkiaRasterRenderer Interactions +### SkiaRasterRenderer Interactions `SkiaRasterRenderer` consumes model nodes from the Rendering system and `RenderOptions`, `Theme`, `NotationMetrics`, `BoxMetrics`, and `ConnectorLabelPlacer` from the Rendering.Abstractions system. It is inherited by `PngRenderer`, `JpegRenderer`, and `WebpRenderer`, which provide only format-selection members. -## Requirements Traceability +### SkiaRasterRenderer Error Handling + +- **Null arguments** — `Render(layout, options, output)` calls `ArgumentNullException.ThrowIfNull` + on each of its three arguments before doing any work, so a null `LayoutTree`, `RenderOptions`, or + output `Stream` results in a fast `ArgumentNullException` rather than a later `NullReferenceException` + or a partially written stream. +- **Degenerate layout sizes** — non-positive layout widths or heights are clamped to a minimum of + one by one pixel before `SKBitmap` allocation so SkiaSharp does not raise an allocation error and + callers always receive a valid encoded image (including for an empty tree). +- **SkiaSharp exceptions** — errors surfaced by the underlying SkiaSharp APIs (for example bitmap + allocation failure, encoder errors) are not caught. They propagate unchanged to the caller so + they are visible in test output and diagnostics; the renderer records no logs of its own. +- **Output stream errors** — exceptions from `SKData.SaveTo(output)` (for example a disposed or + read-only stream) propagate to the caller. The renderer never closes, flushes, or otherwise + mutates the caller-owned `Stream`, so failures leave the stream in whatever state SkiaSharp's + partial write produced; disposal remains the caller's responsibility. +- **Resource cleanup on failure** — every SkiaSharp object (`SKBitmap`, `SKCanvas`, `SKImage`, + `SKData`, `SKPaint`, `SKPath`, `SKPathEffect`) is created with a `using` declaration so its + unmanaged handle is released even when an exception is thrown mid-render. + +### SkiaRasterRenderer Dependencies + +- **`DemaConsulting.Rendering`** — consumes `LayoutTree`, `LayoutNode`, `LayoutBox`, `LayoutLine`, + `LayoutLabel`, `LayoutPort`, and the other layout-node records. +- **`DemaConsulting.Rendering.Abstractions`** — consumes the `IRenderer` contract it implements as + well as `RenderOptions`, `Theme`, `NotationMetrics`, `BoxMetrics`, and `ConnectorLabelPlacer` + for drawing geometry and typography. +- **SkiaSharp (OTS)** — uses `SKBitmap`, `SKCanvas`, `SKImage`, `SKData`, `SKPaint`, `SKPath`, + `SKPathEffect`, `SKTypeface`, `SKColor`, and `SKEncodedImageFormat` for allocation, drawing, and + encoding; see "SkiaSharp Integration Design" under `docs/design/ots/` for lifecycle and + disposal details. +- **Embedded Noto Sans typefaces** — the regular, bold, italic, and bold-italic Noto Sans font + resources embedded in the assembly are loaded once as `SKTypeface` instances at startup. + +### SkiaRasterRenderer Callers + +- **`PngRenderer`, `JpegRenderer`, `WebpRenderer`** — the three concrete raster renderers each + derive from `SkiaRasterRenderer` and rely on its `Render` implementation; they contribute only + format metadata overrides. +- **`RendererRegistry` / consumers of `IRenderer`** — external callers do not use + `SkiaRasterRenderer` directly (it is `abstract`); they resolve one of the concrete renderers by + media type or file extension and invoke `Render` through the `IRenderer` contract. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-skia/webp-renderer.md b/docs/design/rendering-skia/webp-renderer.md index c32ab17..dbb810d 100644 --- a/docs/design/rendering-skia/webp-renderer.md +++ b/docs/design/rendering-skia/webp-renderer.md @@ -1,14 +1,14 @@ -# WebpRenderer Unit Design +## WebpRenderer Unit Design Part of the Rendering.Skia system. -## WebpRenderer Overview +### WebpRenderer Overview `WebpRenderer` is the concrete raster renderer for WEBP output. It derives from `SkiaRasterRenderer` and supplies only the WEBP format-selection metadata and encoding quality; all drawing behaviour comes from the shared rasterizer. -## WebpRenderer Data Model +### WebpRenderer Data Model | Member | Value | | --- | --- | @@ -18,13 +18,49 @@ from the shared rasterizer. | `DefaultExtension` | `.webp` | | `FileExtensions` | `.webp` | -## WebpRenderer Interactions +### WebpRenderer Interactions A `RendererRegistry` can resolve `WebpRenderer` by the `image/webp` media type or by the `.webp` file extension. After resolution, callers invoke the inherited `Render` method and receive a WEBP container written to their output stream. -## Requirements Traceability +### WebpRenderer Key Methods + +`WebpRenderer` declares no methods of its own. It inherits `Render(LayoutTree, RenderOptions, Stream)` +from `SkiaRasterRenderer` (see _SkiaRasterRenderer Unit Design_ for the algorithm, preconditions, and +post-conditions) and overrides only the following members that steer the inherited encode step: + +- `EncodedFormat` returns `SKEncodedImageFormat.Webp` so the base class encodes the bitmap as WEBP. +- `EncodingQuality` returns `90`, the WEBP quality setting passed to SkiaSharp's encoder. +- `MediaType`, `DefaultExtension`, and `FileExtensions` return the advertised WEBP identifiers used + by `RendererRegistry` for lookup. + +### WebpRenderer Error Handling + +`WebpRenderer` contains no error-detection or error-handling logic of its own. All argument +validation (`ArgumentNullException` for null `LayoutTree`, `RenderOptions`, or output `Stream`), +minimum-size clamping, and disposal of SkiaSharp resources happen in the inherited +`SkiaRasterRenderer.Render` method — see _SkiaRasterRenderer Unit Design_ under "Error Handling". +Any exceptions raised by SkiaSharp's WEBP encoder (for example if the underlying native asset +package is missing) propagate unchanged to the caller. + +### WebpRenderer Dependencies + +- **`SkiaRasterRenderer` (base unit)** — provides all rasterization, drawing, and encoding logic. +- **SkiaSharp (OTS)** — used only through the `SKEncodedImageFormat.Webp` enum value returned by + `EncodedFormat`; all direct SkiaSharp API calls are made by the base class. +- **`DemaConsulting.Rendering.Abstractions`** — provides the `IRenderer` contract that + `WebpRenderer` (through its base class) implements. + +### WebpRenderer Callers + +- **`RendererRegistry`** — resolves this renderer by the `image/webp` media type or by the `.webp` + extension. +- **Applications and tools that reference `DemaConsulting.Rendering.Skia`** — either instantiate + `WebpRenderer` directly (`new WebpRenderer().Render(...)`) or resolve it through the registry and + invoke it through the `IRenderer` contract. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering-svg.md b/docs/design/rendering-svg.md new file mode 100644 index 0000000..9c4c8c7 --- /dev/null +++ b/docs/design/rendering-svg.md @@ -0,0 +1,63 @@ +# Rendering.Svg Design + +## Architecture + +`DemaConsulting.Rendering.Svg` is the SVG renderer system. It translates a placed `LayoutTree` from the +Rendering model into a self-contained SVG 1.1 document written to a caller-supplied stream. The system +has a single product unit: + +```text +DemaConsulting.Rendering.Svg (System) +└── SvgRenderer (Unit) +``` + +- **SvgRenderer** — writes SVG markup for placed layout nodes, connector paths, labels, and end-marker + definitions. Detailed in SvgRenderer Unit Design. + +`SvgRenderer` realizes the `IRenderer` contract from Rendering.Abstractions. It is responsible for +vector output only: layout placement, the `LayoutTree` data model, renderer contracts, themes, and +shared notation geometry are defined by upstream systems and consumed here through their public APIs. +The renderer does not choose a layout algorithm and does not mutate the model. + +## External Interfaces + +- **`SvgRenderer : IRenderer`** — inbound; callers provide a `LayoutTree`, `RenderOptions`, and output + `Stream` through the shared `IRenderer` contract. The renderer advertises the `image/svg+xml` media + type and the `.svg` file extension for registry resolution. +- **Output stream** — outbound; UTF-8 SVG 1.1 document bytes written to the caller-owned `Stream`. + +All interaction is through the in-process `IRenderer` API; there is no other external surface. + +## Dependencies + +The system references the *Rendering Model* package (`DemaConsulting.Rendering`) for `LayoutTree` and +node records, and the *Rendering Abstractions* package (`DemaConsulting.Rendering.Abstractions`) for the +`IRenderer` contract, `RenderOptions`, `Theme`, and the shared notation, box, and label geometry +helpers. It has **zero external runtime dependencies** beyond the .NET base class library; all NuGet +references are build-time-only private assets (SBOM, SourceLink, API documentation, and `Polyfill`). No +OTS runtime component or Shared Package is consumed — SVG markup is emitted directly as text. + +## Risk Control Measures + +N/A - general-purpose rendering libraries carry no safety-related risk controls requiring +architectural segregation (IEC 62304 §5.3.3). + +## Data Flow + +```text +LayoutTree + RenderOptions (Theme) ──► SvgRenderer ──► UTF-8 SVG bytes ──► caller Stream +``` + +Callers first run a layout algorithm (typically from Rendering.Layout) to place a graph, then pass the +resulting `LayoutTree` to this renderer. `SvgRenderer` reads model nodes from Rendering, applies the +supplied `Theme` and shared geometry helpers from Rendering.Abstractions, and writes SVG bytes to the +caller-owned stream. A typical pipeline runs layout first, then this renderer. + +## Design Constraints + +- **Target frameworks**: `net8.0`, `net9.0`, and `net10.0`. +- **Zero external dependencies**: the SVG renderer must remain dependency-free (base class library + only) so it can be consumed in the most constrained environments. +- **Self-contained output**: the emitted document is a self-contained SVG 1.1 file with no external + references, so it renders standalone. +- **Determinism**: identical inputs produce identical SVG text, enabling byte-level verification. diff --git a/docs/design/rendering-svg/rendering-svg.md b/docs/design/rendering-svg/rendering-svg.md deleted file mode 100644 index 56fa1a8..0000000 --- a/docs/design/rendering-svg/rendering-svg.md +++ /dev/null @@ -1,43 +0,0 @@ -# Rendering.Svg Design - -## Overview - -`DemaConsulting.Rendering.Svg` is the SVG renderer system. It translates a placed -`LayoutTree` from the Rendering model into a self-contained SVG 1.1 document written to a -caller-supplied stream. The system has one product unit, `SvgRenderer`, and no runtime dependencies -beyond the .NET base class library and the in-house Rendering model and abstractions packages. - -The renderer system is responsible for vector output only. Layout placement, the `LayoutTree` data -model, renderer contracts, themes, and shared notation geometry are defined by upstream systems and are -consumed here through their public APIs. - -## Software Structure - -```text -DemaConsulting.Rendering.Svg (System) -└── SvgRenderer (Unit) -``` - -- **SvgRenderer** - writes SVG markup for placed layout nodes, connector paths, labels, and end-marker - definitions. Detailed in SvgRenderer Unit Design. - -## System Interactions - -Callers provide a `LayoutTree`, `RenderOptions`, and output `Stream` through the shared `IRenderer` -contract from Rendering.Abstractions. `SvgRenderer` reads model nodes from Rendering, uses the supplied -`Theme` and shared notation/box/label geometry helpers from Rendering.Abstractions, and writes UTF-8 SVG -bytes to the caller-owned stream. - -The SVG system does not choose a layout algorithm and does not mutate the model. A typical pipeline runs -Rendering.Layout first to place a graph, then passes the resulting tree to this renderer. - -## Requirements Traceability - -| Requirement ID | Satisfied by | -| --- | --- | -| `Rendering-Svg-WriteSvgDocument` | SvgRenderer Unit Design | - -## Scope Exclusions - -Detailed SVG element generation, font styling, text fitting, XML escaping, and marker geometry are unit -scope and live in SvgRenderer Unit Design. diff --git a/docs/design/rendering-svg/svg-renderer.md b/docs/design/rendering-svg/svg-renderer.md index f93e473..29723c1 100644 --- a/docs/design/rendering-svg/svg-renderer.md +++ b/docs/design/rendering-svg/svg-renderer.md @@ -1,15 +1,23 @@ -# SvgRenderer Unit Design +## SvgRenderer Unit Design Part of the Rendering.Svg system. -## SvgRenderer Overview +### Purpose + +`SvgRenderer` has a single responsibility: translate a placed `LayoutTree` into a self-contained +SVG 1.1 document written as UTF-8 bytes to a caller-supplied `Stream`. It realizes the +`IRenderer` contract from `DemaConsulting.Rendering.Abstractions` for the vector output path. +It does not perform layout, mutate the model, choose a theme, own the output stream, or persist +the emitted bytes; those responsibilities remain with the caller and with upstream systems. + +### SvgRenderer Overview `SvgRenderer` implements the `IRenderer` interface to produce SVG 1.1 diagram output from a `LayoutTree` intermediate representation. Each call to `Render` builds a complete SVG document in a `StringBuilder` and writes it to the supplied stream in UTF-8 encoding. The renderer is pure and stateless; no fields are mutated between calls, so a single instance may be reused concurrently. -## SvgRenderer Data Model +### SvgRenderer Data Model `SvgRenderer` has no instance state. All inputs are supplied through `Render` parameters. @@ -17,14 +25,14 @@ stateless; no fields are mutated between calls, so a single instance may be reus - `RenderOptions` — read-only input; `Theme` for visual parameters and `Scale` for sizing. - `Stream output` — write-only output; receives UTF-8 SVG bytes; caller owns lifetime. -## Font Family +### Font Family All text elements use `font-family="Noto Sans, sans-serif"`. The `Noto Sans` family is specified first so that browsers and renderers with Noto Sans installed use it; `sans-serif` is the CSS generic fallback. Naming Noto Sans first keeps the SVG output visually aligned with the PNG renderer, which embeds the same font for pixel-identical rasterization. -## Font Weight and Style Per Node Type +### Font Weight and Style Per Node Type Each node type uses a fixed font weight and style as SVG attributes: @@ -42,7 +50,7 @@ Each node type uses a fixed font weight and style as SVG attributes: | `LayoutGrid` header cells | `bold` | (default) | | `LayoutGrid` body cells | (default) | (default) | -## LayoutLabel Font Styling Fields +### LayoutLabel Font Styling Fields `LayoutLabel` carries three explicit font styling fields: @@ -53,7 +61,7 @@ Each node type uses a fixed font weight and style as SVG attributes: - `FontSize` (double) — font size in logical pixels, used as `font-size` instead of the theme body size. -## Text Length Shrink-to-Fit +### Text Length Shrink-to-Fit `LayoutBox` labels and `LayoutLabel` nodes are constrained to their available width only when the text would otherwise overflow it. `FitTextLength` estimates each label's natural width (character count @@ -67,7 +75,7 @@ When the text already fits, or the available width is non-positive, no `textLeng attribute is emitted, so short labels render at their natural width and are never stretched to fill the box. -## Key Methods +### Key Methods **`Render(LayoutTree layout, RenderOptions options, Stream output)`** @@ -159,24 +167,24 @@ Each typed method writes the SVG elements appropriate to its node kind: an 8x8 ` `` border for activations; and per-cell `` plus `` elements for grids. Fills come from `theme.DepthFillColors`, and header cells add `font-weight="bold"`. -## Error Handling +### Error Handling `Render` throws `ArgumentNullException` when `layout`, `options`, or `output` is null. No other exceptions are expected under normal operation. XML special characters in labels are escaped via `EscapeXml` to prevent malformed SVG output. -## Dependencies +### Dependencies - `DemaConsulting.Rendering` — provides `LayoutTree` and all nine `LayoutNode` subtypes. - `DemaConsulting.Rendering.Abstractions` — provides `IRenderer`, `RenderOptions`, `Theme`, `Themes`, `NotationMetrics`, `BoxMetrics`, `MarkerVertex`, and `ConnectorLabelPlacer`. -## Callers +### Callers Any consumer of the rendering library that selects vector output constructs an `SvgRenderer` and calls `IRenderer.Render` with a placed `LayoutTree` and `RenderOptions`. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | @@ -198,6 +206,10 @@ Any consumer of the rendering library that selects vector output constructs an ` | Rendering-Svg-SvgRenderer-RenderLineMidpointLabel | `ConnectorLabelPlacer` plus `RenderLineLabel` | | Rendering-Svg-SvgRenderer-RenderNodeKinds | `RenderPort` writes the port rectangle | | Rendering-Svg-SvgRenderer-RenderBadge | `RenderBadge` writes filled-circle badges | +| Rendering-Svg-SvgRenderer-BadgeBullseye | `RenderBadge` writes bullseye badges as concentric circles | +| Rendering-Svg-SvgRenderer-BadgeDiamond | `RenderBadge` writes diamond badges as polygons | +| Rendering-Svg-SvgRenderer-BadgeHorizontalBar | `RenderBadge` writes horizontal-bar badges as lines | +| Rendering-Svg-SvgRenderer-BadgeVerticalBar | `RenderBadge` writes vertical-bar badges as lines | | Rendering-Svg-SvgRenderer-RenderBand | `RenderBand` writes the band rectangle | | Rendering-Svg-SvgRenderer-RenderLifeline | `RenderLifeline` writes the header and stem | | Rendering-Svg-SvgRenderer-RenderActivation | `RenderActivation` writes the activation rectangle | @@ -210,3 +222,7 @@ Any consumer of the rendering library that selects vector output constructs an ` | Rendering-Svg-SvgRenderer-DiamondEndMarkers | `WriteEndMarkerDefs` reads diamond `NotationMetrics` | | Rendering-Svg-SvgRenderer-DiamondEndMarkerReference | `RenderLine` writes the hollow-diamond marker reference | | Rendering-Svg-SvgRenderer-CrossbarEndMarkers | `RenderLine` writes the crossbar marker reference | +| Rendering-Svg-SvgRenderer-EndMarkerFilledArrow | `RenderLine` writes the filled-arrow marker reference | +| Rendering-Svg-SvgRenderer-EndMarkerFilledDiamond | `RenderLine` writes the filled-diamond marker reference | +| Rendering-Svg-SvgRenderer-EndMarkerCircle | `RenderLine` writes the circle marker reference | +| Rendering-Svg-SvgRenderer-EndMarkerBar | `RenderLine` writes the bar marker reference | diff --git a/docs/design/rendering.md b/docs/design/rendering.md new file mode 100644 index 0000000..b8eebc2 --- /dev/null +++ b/docs/design/rendering.md @@ -0,0 +1,88 @@ +# Rendering Model Design + +## Architecture + +`DemaConsulting.Rendering` is the SysML-agnostic rendering model. It defines the data types that flow +through the rendering pipeline but contains no layout algorithm and no renderer. It provides three +things: the placed `LayoutTree` intermediate representation that a renderer draws, the open +(ELK-inspired) property-based option system that carries configuration, and the unplaced `LayoutGraph` +input that a layout algorithm consumes. + +The system is composed of three units: + +```text +DemaConsulting.Rendering (System) +├── LayoutTree (Unit) +├── Options (Unit) +└── LayoutGraph (Unit) +``` + +- **Layout Tree** — the placed intermediate representation: `LayoutTree` and the `LayoutNode` + discriminated-union hierarchy of concrete node records, plus the shared `Point2D` / `Rect` geometry + value types. Detailed in Layout Tree Unit Design. +- **Options** — the open configuration system: `LayoutProperty`, `IPropertyHolder`, + `PropertyHolder`, `LayoutOptions`, `CoreOptions`, `LayoutFlowDirection`, and `HierarchyHandling`. + Detailed in Options Unit Design. +- **Layout Graph** — the unplaced input model: `LayoutGraph`, `LayoutGraphNode`, `LayoutGraphEdge`. + Detailed in Layout Graph Unit Design. + +The units collaborate through the Options unit's `PropertyHolder`, which is the base type for the +Layout Graph elements, so configuration flows from the input model into the algorithms uniformly. The +model performs no layout and no rendering; it defines only the shared vocabulary at both ends of the +pipeline. A layout algorithm (defined in the *Rendering Abstractions* system) consumes a `LayoutGraph` +plus a `LayoutOptions` and produces a placed `LayoutTree`; a renderer then draws that tree. + +## External Interfaces + +The model exposes its contract as public .NET types rather than service interfaces: + +- **`LayoutGraph` (with `LayoutGraphNode`, `LayoutGraphEdge`)** — outbound to layout algorithms; the + unplaced input a caller builds and an `ILayoutAlgorithm` consumes. In-memory object model. +- **`LayoutOptions` / `IPropertyHolder` / `LayoutProperty` / `CoreOptions`** — outbound to both + algorithms and renderers; typed property keys carried on any property holder. Unknown properties + are ignored, so callers may set options that a given algorithm or renderer does not honor. +- **`LayoutTree` (with the `LayoutNode` hierarchy, `Point2D`, `Rect`)** — outbound to renderers; the + placed representation an `IRenderer` draws. Immutable records with absolute coordinates. + +All interfaces are in-process .NET APIs; there are no network, file, or wire-format contracts. + +## Dependencies + +The model has no project references and no runtime NuGet package dependencies beyond the .NET base +class library. Build-time-only packages (SBOM generation, SourceLink, API documentation, and +`Polyfill` for newer BCL surface on older target frameworks) are private build assets and are not part +of the delivered runtime surface. No OTS runtime component or Shared Package is consumed. + +## Risk Control Measures + +N/A - general-purpose rendering libraries carry no safety-related risk controls requiring +architectural segregation (IEC 62304 §5.3.3). + +## Data Flow + +The model sits at both ends of the rendering pipeline: + +```text +LayoutGraph + LayoutOptions (Rendering: unplaced input + open configuration) + │ + ▼ ILayoutAlgorithm (Rendering.Abstractions: layout SPI) + LayoutTree (Rendering: placed boxes and routed connectors) + │ + ▼ IRenderer (Rendering.Abstractions: render SPI) + SVG / PNG / JPEG / WEBP output +``` + +Input flows in as a `LayoutGraph` and a `LayoutOptions`; a layout algorithm reads the graph and its +properties and emits a placed `LayoutTree`; a renderer reads that tree and writes a concrete output +format. The model itself only defines and carries these values; it neither transforms nor emits them. + +## Design Constraints + +- **Target frameworks**: `net8.0`, `net9.0`, and `net10.0`. +- **Determinism and immutability**: the placed types are immutable records or small mutable holders; + stored values are returned unchanged so layout and rendering are reproducible. +- **Absolute coordinates**: `LayoutTree` node geometry uses absolute coordinates, not color- or + depth-relative encodings, so any renderer can draw the tree without re-deriving placement. +- **Zero runtime dependencies**: the model depends only on the .NET base class library, keeping it a + neutral shared vocabulary that every upstream and downstream package can reference without pulling + in additional runtime components. diff --git a/docs/design/rendering/layout-graph.md b/docs/design/rendering/layout-graph.md index 0e09136..867f1cd 100644 --- a/docs/design/rendering/layout-graph.md +++ b/docs/design/rendering/layout-graph.md @@ -1,8 +1,17 @@ -# Layout Graph Unit Design +## Layout Graph Unit Design Part of the Rendering Model system. -## Layout Graph Overview +### Layout Graph Purpose + +The Layout Graph unit's single responsibility is to define the unplaced input model that a layout +algorithm consumes: a `LayoutGraph` container of `LayoutGraphNode` boxes and `LayoutGraphEdge` +connections, each derived from `PropertyHolder` so that graph-wide, node-level, and edge-level +options may be attached anywhere on the input. It supports hierarchical (compound) nesting through a +node's lazily-allocated `Children` graph. The unit performs no layout and no rendering; it is only +the input vocabulary passed to `ILayoutAlgorithm.Apply`. + +### Layout Graph Overview The Layout Graph unit is the unplaced input to a layout algorithm: a set of sized `LayoutGraphNode` boxes and directed `LayoutGraphEdge` connections. `LayoutGraph` itself derives from `PropertyHolder`, @@ -23,7 +32,7 @@ existed. A layout algorithm that reads only the top-level `Nodes` and `Edges` `LayeredLayoutAlgorithm` — is unaffected by the presence of the capability; consuming the nesting is the responsibility of a hierarchical layout engine (a later delivery). -## Layout Graph Data Model +### Layout Graph Data Model - `LayoutGraph` (sealed class extending `PropertyHolder`) — `Nodes`, `Edges`, `AddNode`, `AddEdge`; each instance is a container scope (the root graph, or a node's `Children`). @@ -33,7 +42,7 @@ the responsibility of a hierarchical layout engine (a later delivery). - `LayoutGraphEdge` (sealed class extending `PropertyHolder`) — `Id`, `Source`, `Target`, `TargetEnd`, `LineStyle`, `Label`. -## Layout Graph Key Methods +### Layout Graph Key Methods `LayoutGraphNode AddNode(string id, double width, double height)` — creates a node with the requested identity and size, appends it to `Nodes` in insertion order, and returns it for further @@ -54,7 +63,20 @@ guarantees. child without forcing the lazy allocation, so consumers can distinguish a container from a leaf and skip empty containers. -## Layout Graph Design Constraints +### Layout Graph Error Handling + +Invalid inputs are rejected at their entry point rather than deferred to layout time. The +`LayoutGraphNode` and `LayoutGraphEdge` constructors call `ArgumentException.ThrowIfNullOrEmpty(id)` +so that a null or empty identifier throws `ArgumentException` (or `ArgumentNullException` for null), +and `LayoutGraphEdge` additionally calls `ArgumentNullException.ThrowIfNull` on its `source` and +`target`. `LayoutGraph.AddNode` and `LayoutGraph.AddEdge` throw `ArgumentException` when the supplied +identifier is already used within that container scope (the same identifier may be reused in a +different `Children` scope). All exceptions propagate to the caller unchanged; the unit performs no +recovery and no logging. Cross-container endpoint references are not validated — the model +deliberately permits them so that a hierarchical layout engine can resolve cross-container edges in +the appropriate ancestor container. + +### Layout Graph Design Constraints - `AddNode` and `AddEdge` shall preserve caller insertion order in `Nodes` and `Edges`, so that a layout algorithm processes elements deterministically. @@ -75,7 +97,26 @@ skip empty containers. cross-scope references. A hierarchical layout engine resolves the routing in the owning container's coordinate space. -## Layout Graph Interactions +### Layout Graph Dependencies + +The unit depends on the Options unit within the Rendering model (`LayoutGraph`, `LayoutGraphNode`, +and `LayoutGraphEdge` all derive from `PropertyHolder`, and each element carries `LayoutProperty` +overrides). It also consumes the shared notation enumerations `EndMarkerStyle` and `LineStyle` +declared by the Layout Tree unit, on `LayoutGraphEdge`. Outside the Rendering model, the unit has no +project references, no OTS runtime component, and no Shared Package dependency; it uses only the +.NET base class library. + +### Layout Graph Callers + +Application code constructs a `LayoutGraph` and populates it through `AddNode`, `AddEdge`, and (for +compound diagrams) the recursive `LayoutGraphNode.Children` graph. The populated graph is passed to +`ILayoutAlgorithm.Apply` in `DemaConsulting.Rendering.Abstractions`, which is implemented by the +`LayeredLayoutAlgorithm`, `ContainmentLayoutAlgorithm`, and `HierarchicalLayoutAlgorithm` in +`DemaConsulting.Rendering.Layout`. The bundled `LayeredLayoutAlgorithm` consumes only the top-level +`Nodes` and `Edges`; the recursive `Children` structure is consumed by the hierarchical layout +engine. + +### Layout Graph Interactions A `LayoutGraph` plus a `LayoutOptions` is the input to `ILayoutAlgorithm.Apply` (see the *Rendering Abstractions* design), which produces a placed `LayoutTree`. Each `LayoutGraphEdge` @@ -83,7 +124,7 @@ carries an `EndMarkerStyle` and `LineStyle` from the Layout Tree unit's enumerat `LayeredLayoutAlgorithm` (see the *Rendering Layout* design) consumes only the top-level `Nodes` and `Edges`; the recursive `Children` structure is intended for a future hierarchical layout engine. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering/layout-tree.md b/docs/design/rendering/layout-tree.md index 40d5e65..6703ba6 100644 --- a/docs/design/rendering/layout-tree.md +++ b/docs/design/rendering/layout-tree.md @@ -1,8 +1,18 @@ -# Layout Tree Unit Design +## Layout Tree Unit Design Part of the Rendering Model system. -## Layout Tree Overview +### Layout Tree Purpose + +The Layout Tree unit's single responsibility is to define the placed, renderer-agnostic intermediate +representation that a layout algorithm produces and a renderer draws: the `LayoutTree` container, the +`LayoutNode` discriminated-union hierarchy of concrete node records (`LayoutBox`, `LayoutPort`, +`LayoutLine`, `LayoutLabel`, `LayoutBadge`, `LayoutBand`, `LayoutLifeline`, `LayoutActivation`, +`LayoutGrid`, and their sub-records), the shared geometry value types (`Point2D`, `Rect`), and the +notation enumerations used across the tree. The unit performs no layout and no rendering; it is a +pure, immutable data vocabulary. + +### Layout Tree Overview The Layout Tree unit is the placed, renderer-agnostic representation of one view. A `LayoutTree` carries the canvas dimensions and a flat list of top-level `LayoutNode` instances. `LayoutNode` is an @@ -10,7 +20,7 @@ abstract record acting as the root of a discriminated union; renderers switch on and skip unknown subtypes for forward compatibility. All coordinates are absolute, with the origin at the top-left, so a renderer can draw each element directly without resolving nested offsets. -## Layout Tree Data Model +### Layout Tree Data Model - `LayoutTree` (sealed record) — `Width`, `Height`, the top-level `Nodes` list, and layout-quality `Warnings`. @@ -41,7 +51,21 @@ The unit also defines the notation enumerations `BoxShape`, `PortSide`, `EndMark they exist so every node record and every layout algorithm expresses coordinates and bounds with one consistent, allocation-light vocabulary. -## Layout Tree Design Constraints +### Layout Tree Key Methods + +N/A - the Layout Tree types are immutable data records with no behavioral methods. Each record type +exposes only its declared fields through record-generated property getters and value-based equality; +there are no operations to document beyond construction (all fields are supplied through the +positional or init-only constructor and returned unchanged). + +### Layout Tree Error Handling + +N/A - the Layout Tree types are passive immutable records that do no I/O, allocate no unmanaged +resources, and expose no operational methods. There are no runtime error paths to detect or propagate; +argument validation (for example, non-null identifiers on graph elements) is the responsibility of the +Layout Graph unit, not this unit. + +### Layout Tree Design Constraints - All node coordinates shall be absolute in logical pixels with the origin at the top-left; the model shall not apply any coordinate transform. @@ -52,13 +76,31 @@ consistent, allocation-light vocabulary. - All node records shall be immutable, so a placed tree can be shared and rendered repeatedly without defensive copying. -## Layout Tree Interactions +### Layout Tree Interactions The Layout Tree is produced by a layout algorithm (see the *Rendering Abstractions* design, `ILayoutAlgorithm`) and consumed by a renderer (`IRenderer`). Within the tree, `LayoutBox` and `LayoutBand` hold nested `Children`, so renderers walk the node hierarchy recursively. -## Requirements Traceability +### Layout Tree Dependencies + +The unit depends only on the .NET base class library. It has no project references, no OTS runtime +component, and no Shared Package dependency; within the Rendering model, it is a peer of the Options +and Layout Graph units and depends on neither at the code level (though `LayoutGraphEdge` in the +Layout Graph unit consumes the `EndMarkerStyle` and `LineStyle` enumerations declared here as part of +the shared notation vocabulary). + +### Layout Tree Callers + +The unit is written by layout algorithms in `DemaConsulting.Rendering.Layout` — the `layered`, +`containment`, and `hierarchical` algorithms and the orthogonal edge router — and read by renderers +in `DemaConsulting.Rendering.Svg` and `DemaConsulting.Rendering.Skia` (`SvgRenderer`, `PngRenderer`, +`JpegRenderer`, `WebpRenderer`). The `ILayoutAlgorithm` and `IRenderer` contracts in +`DemaConsulting.Rendering.Abstractions` reference `LayoutTree` and its geometry types as their +produced and consumed values. Application code typically does not construct a `LayoutTree` directly; +it obtains one from a layout algorithm and passes it to a renderer. + +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | diff --git a/docs/design/rendering/options.md b/docs/design/rendering/options.md index e167a28..1ac62c2 100644 --- a/docs/design/rendering/options.md +++ b/docs/design/rendering/options.md @@ -1,8 +1,17 @@ -# Options Unit Design +## Options Unit Design Part of the Rendering Model system. -## Options Overview +### Options Purpose + +The Options unit's single responsibility is to define the open, ELK-inspired property system that +carries configuration end-to-end through the rendering pipeline: it declares typed property keys +(`LayoutProperty`), the contract for any object that stores them (`IPropertyHolder`), a default +dictionary-backed store (`PropertyHolder`), a free-standing options bag (`LayoutOptions`), and the +well-known key catalogue (`CoreOptions`). It does not perform layout or rendering; it only defines and +holds configuration values. + +### Options Overview The Options unit is the open, ELK-inspired configuration system. Configuration values are keyed by `LayoutProperty` constants rather than by fixed fields, so new options are introduced by declaring @@ -10,7 +19,7 @@ new keys without changing any method signature. Any object that needs to carry c implements `IPropertyHolder`; the concrete `PropertyHolder` provides a dictionary-backed store keyed by each property's identifier. -## Options Data Model +### Options Data Model - `LayoutProperty` (sealed class) — `Id` and `DefaultValue`. - `IPropertyHolder` (interface) — `Get`, `TryGet`, `Set`, `Contains`. @@ -24,7 +33,7 @@ by each property's identifier. children. Under `SeparateChildren` each container is laid out in its own coordinate space and sized to fit its children. An `IncludeChildren` (cross-boundary) mode is a planned future additive value. -## Options Key Methods +### Options Key Methods `TValue Get(LayoutProperty property)` — returns the stored value for a property, or the property's `DefaultValue` when it has not been set. Throws `ArgumentNullException` when the @@ -42,20 +51,65 @@ explicitly set on this holder. `LayoutOptions.ForAlgorithm(string algorithmId)` — creates a `LayoutOptions` pre-set with `CoreOptions.Algorithm`. -## Options Design Constraints +`LayoutOptions OverlayOnto(PropertyHolder parent)` — the generic option-cascading primitive: builds a +new `LayoutOptions` snapshot by copying `parent`'s explicitly-set values first, then this holder's own +explicitly-set values on top, so this holder's own values win. Because the merge copies boxed values +by key rather than going through each property's typed accessors, it works for any current or future +`LayoutProperty` — including ones neither side knows about — with no per-property code. This is the +mechanism callers such as `HierarchicalLayoutAlgorithm` use to build each scope's cascaded effective +options: a nested scope's own overrides are overlaid onto its parent scope's already-resolved +snapshot, so a scope with no overrides of its own inherits the nearest ancestor's resolved value, and +a scope's own explicit override wins for itself and every unset descendant. Throws +`ArgumentNullException` when `parent` is null. + +### Options Error Handling + +`Get`, `TryGet`, `Set`, and `Contains` all reject a null `LayoutProperty` by throwing +`ArgumentNullException`, so a caller cannot silently corrupt the store with a value that has no key. The +`LayoutProperty` constructor rejects a null or empty `id` with `ArgumentException`, and +`LayoutOptions.ForAlgorithm(string)` rejects a null or empty `algorithmId` on the same terms. Reading +a property that has never been set is not an error: the store returns the property's declared +`DefaultValue` (through `Get`) or the default plus a `false` result (through `TryGet`). Unknown or +not-yet-honored keys carried on a holder are not detected here — they are ignored by whichever +algorithm or renderer chooses not to read them, which is a deliberate design property, not an error +path. + +### Options Dependencies + +The unit depends only on the .NET base class library (`System.Collections.Generic.Dictionary`). It has no project references, no OTS runtime component, and no Shared Package dependency; +the `Polyfill` build-time package is a private asset that only backfills newer BCL surface on older +target frameworks. Within the Rendering model, Options is a peer of the Layout Tree and Layout Graph +units and depends on neither. + +### Options Callers + +Within the Rendering model, the Layout Graph unit's `LayoutGraph`, `LayoutGraphNode`, and +`LayoutGraphEdge` all derive from `PropertyHolder`, so every graph element carries options through +this unit. Outside the model, layout algorithms in `DemaConsulting.Rendering.Layout` and renderers in +`DemaConsulting.Rendering.Svg` and `DemaConsulting.Rendering.Skia` read `LayoutOptions` and +`CoreOptions` keys to configure their behavior; the shared abstractions in +`DemaConsulting.Rendering.Abstractions` also consume `LayoutOptions` as the configuration parameter of +`ILayoutAlgorithm.Apply`. Application code builds a `LayoutOptions` (often via `ForAlgorithm`) and +passes it to a layout facade. + +### Options Design Constraints - `PropertyHolder` shall store values keyed by `LayoutProperty.Id`, so an option that no algorithm yet honors is carried harmlessly and simply ignored by algorithms that do not read it. - `CoreOptions` keys marked advisory shall default harmlessly until an algorithm implements them, so that adding a key is a purely additive change. +- `OverlayOnto` shall merge by raw key rather than by enumerating known `LayoutProperty` constants, + so the cascading primitive remains correct for options this unit has never heard of (including + future, not-yet-declared properties). -## Options Interactions +### Options Interactions `LayoutGraph`, `LayoutGraphNode`, and `LayoutGraphEdge` (in the Layout Graph unit) all derive from `PropertyHolder`, so configuration can be attached to the whole graph or to a single element. `LayoutOptions` is passed to a layout algorithm alongside a `LayoutGraph`. -## Requirements Traceability +### Requirements Traceability | Requirement ID | Satisfied by | | --- | --- | @@ -63,3 +117,4 @@ explicitly set on this holder. | Rendering-Model-Options-StoreAndRetrieve | `PropertyHolder.Set` / `Get` | | Rendering-Model-Options-Contains | `PropertyHolder.Contains` | | Rendering-Model-Options-TryGet | `PropertyHolder.TryGet` | +| Rendering-Model-Options-Cascade | `PropertyHolder.OverlayOnto` | diff --git a/docs/design/rendering/rendering.md b/docs/design/rendering/rendering.md deleted file mode 100644 index a126a89..0000000 --- a/docs/design/rendering/rendering.md +++ /dev/null @@ -1,69 +0,0 @@ -# Rendering Model Design - -## Overview - -`DemaConsulting.Rendering` is the SysML-agnostic rendering model. It defines the data types that -flow through the rendering pipeline but contains no layout algorithm and no renderer. It provides -three things: the placed `LayoutTree` intermediate representation that a renderer draws, the open -(ELK-inspired) property-based option system that carries configuration, and the unplaced -`LayoutGraph` input that a layout algorithm consumes. The package has no runtime dependencies beyond -the .NET base class library, and its types are immutable records or small mutable holders. - -A layout algorithm consumes a `LayoutGraph` plus a `LayoutOptions` and produces a placed -`LayoutTree`; a renderer then draws that tree. This document describes the model that sits at both -ends of that flow. The algorithm and renderer contracts themselves live in the -*Rendering Abstractions* system. - -## Software Structure - -```text -DemaConsulting.Rendering (System) -├── LayoutTree (Unit) -├── Options (Unit) -└── LayoutGraph (Unit) -``` - -- **Layout Tree** — the placed intermediate representation: `LayoutTree` and the `LayoutNode` - discriminated-union hierarchy of concrete node records, plus the shared `Point2D` / `Rect` - geometry value types. Detailed in Layout Tree Unit Design. -- **Options** — the open configuration system: `LayoutProperty`, `IPropertyHolder`, - `PropertyHolder`, `LayoutOptions`, `CoreOptions`, `LayoutFlowDirection`, and `HierarchyHandling`. - Detailed in Options Unit Design. -- **Layout Graph** — the unplaced input model: `LayoutGraph`, `LayoutGraphNode`, `LayoutGraphEdge`. - Detailed in Layout Graph Unit Design. - -## System Interactions - -A `LayoutGraph` plus a `LayoutOptions` is the input consumed by an `ILayoutAlgorithm` (defined in the -*Rendering Abstractions* system); the algorithm produces a placed `LayoutTree`, which an `IRenderer` -then draws to a concrete output format. The Options unit's `PropertyHolder` is the base type for the -Layout Graph elements, so configuration flows from the input model into the algorithms uniformly. The -model itself performs no layout and no rendering; it defines only the shared vocabulary at both ends -of the pipeline. - -## Requirements Traceability - -| Requirement ID | Satisfied by | -| --- | --- | -| Rendering-Model-LayoutTree | The `LayoutTree` and `LayoutNode` hierarchy (see Layout Tree Unit Design) | -| Rendering-Model-Configuration | The `IPropertyHolder` / `LayoutProperty` option system (see Options Unit Design) | -| Rendering-Model-InputGraph | The `LayoutGraph` input model (see Layout Graph Unit Design) | - -## Model Scope Exclusions - -The following public members are optional presentation or diagnostic metadata that the model carries -through unchanged. The -model's only obligation is to store and return them unchanged; they carry no algorithmic behavior of -their own, and the rendered behavior they influence is specified and verified in the renderer systems -(`DemaConsulting.Rendering.Svg` / `DemaConsulting.Rendering.Skia`), not in the model. They are -therefore intentionally excluded from the Rendering-Model requirement set and are not given -individual functional requirements or verification scenarios: - -| Member | Kind | Rationale | -| --- | --- | --- | -| `LayoutBox.Keyword` | presentation | Optional SysML keyword drawn above the box label; renderer pass-through. | -| `LayoutTree.Warnings` | diagnostic | Non-fatal layout-quality diagnostics; advisory only. | -| `LayoutGraphNode.Label` | input-graph presentation | Optional display text carried to the renderer. | -| `LayoutGraphEdge.Label` | input-graph presentation | Optional midpoint display text carried to the renderer. | -| `LayoutGraphEdge.TargetEnd` | input-graph presentation | Optional end-marker style hint carried to the renderer. | -| `LayoutGraphEdge.LineStyle` | input-graph presentation | Optional stroke-style hint carried to the renderer. | diff --git a/docs/gallery/gallery.md b/docs/gallery/gallery.md index febcc52..33d224f 100644 --- a/docs/gallery/gallery.md +++ b/docs/gallery/gallery.md @@ -24,9 +24,10 @@ A container node holding a nested child graph, with a cross-container edge. ## Flow direction -The same directed graph laid out in two flow directions, selected with the direction option. A rightward flow arranges -the layers left-to-right for block and pipeline diagrams; a downward flow arranges them top-to-bottom for action flows -and state machines, swapping each node's width and height so layer spacing follows node height. +The same directed graph laid out in two flow directions, selected with the direction option, plus a nested container +overriding its own direction independently of its parent. A rightward flow arranges the layers left-to-right for block +and pipeline diagrams; a downward flow arranges them top-to-bottom for action flows and state machines, swapping each +node's width and height so layer spacing follows node height. ![Directed flow laid out left to right](direction-right.svg) @@ -36,6 +37,11 @@ The default rightward direction: layers progress left-to-right. The downward direction: the same graph's layers progress top-to-bottom. +![A nested container flowing downward inside an outer rightward flow](mixed-direction-nested.svg) + +A container's own direction override is honored independently of its parent: the outer flow runs left-to-right while the +nested container runs top-to-bottom. + ## Edge routing Orthogonal connectors step around the boxes between their endpoints instead of cutting through them. diff --git a/docs/gallery/mixed-direction-nested.svg b/docs/gallery/mixed-direction-nested.svg new file mode 100644 index 0000000..55ee1c1 --- /dev/null +++ b/docs/gallery/mixed-direction-nested.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Intake + + Pipeline + + Validate + + Transform + + Publish + + + + Archive + + + diff --git a/docs/reqstream/ots/skiasharp.yaml b/docs/reqstream/ots/skiasharp.yaml new file mode 100644 index 0000000..aad9cca --- /dev/null +++ b/docs/reqstream/ots/skiasharp.yaml @@ -0,0 +1,65 @@ +--- +# SkiaSharp OTS Software Requirements +# +# Requirements for the SkiaSharp raster-graphics library functionality. +# +# Unlike the DemaConsulting tool OTS items (BuildMark, ReviewMark, ...) which are verified by their +# own self-validation TRX, and like xUnit, SkiaSharp is a runtime library rather than build/compliance +# tooling: its OTS requirements are satisfied by this repository's own renderer tests, which xUnit +# discovers, executes, and records into the TRX result files that ReqStream traces. The linked tests +# below are a stable, representative sample spanning bitmap drawing, text rendering, and image +# encoding. + +sections: + - title: OTS Software Requirements + sections: + - title: SkiaSharp Requirements + requirements: + - id: Rendering-OTS-SkiaSharp-Rasterize + title: SkiaSharp shall draw shapes and lines onto a bitmap canvas. + justification: | + SkiaSharp's SKBitmap and SKCanvas provide the in-memory raster surface, and SKPaint + provides filled/stroked shape drawing, that SkiaRasterRenderer uses to draw layout + boxes and connector lines. Passing tests that sample drawn pixels confirm these + primitives operate correctly. + tags: [ots] + tests: + - Render_SingleBox_ProducesPngSignature + - PngRenderer_Render_SingleLine_PixelOnLineIsStrokeColor + - id: Rendering-OTS-SkiaSharp-Text + title: SkiaSharp shall render text using loaded typefaces. + justification: | + SkiaSharp's SKTypeface loads the embedded Noto Sans font resources, and SKPaint's + text APIs draw node titles and labels with them. A passing test that renders a label + containing special characters confirms typeface loading and text drawing operate + correctly. + tags: [ots] + tests: + - PngRenderer_Render_LabelWithXmlSpecialCharacters_ProducesValidPng + - id: Rendering-OTS-SkiaSharp-EncodePng + title: SkiaSharp shall encode a drawn bitmap into the PNG image format. + justification: | + SkiaSharp's PNG encoder converts the drawn SKBitmap into a PNG container for + PngRenderer. A passing test that checks the PNG signature bytes confirms the + encoder produces valid output. + tags: [ots] + tests: + - Render_SingleBox_ProducesPngSignature + - id: Rendering-OTS-SkiaSharp-EncodeJpeg + title: SkiaSharp shall encode a drawn bitmap into the JPEG image format. + justification: | + SkiaSharp's JPEG encoder converts the drawn SKBitmap into a JPEG container for + JpegRenderer. A passing test that checks the JPEG signature bytes confirms the + encoder produces valid output. + tags: [ots] + tests: + - JpegRenderer_Render_ProducesJpegSignature + - id: Rendering-OTS-SkiaSharp-EncodeWebp + title: SkiaSharp shall encode a drawn bitmap into the WEBP image format. + justification: | + SkiaSharp's WEBP encoder converts the drawn SKBitmap into a WEBP container for + WebpRenderer. A passing test that checks the WEBP container header confirms the + encoder produces valid output. + tags: [ots] + tests: + - WebpRenderer_Render_ProducesWebpContainerHeader diff --git a/docs/reqstream/rendering-abstractions.yaml b/docs/reqstream/rendering-abstractions.yaml new file mode 100644 index 0000000..4f9efc9 --- /dev/null +++ b/docs/reqstream/rendering-abstractions.yaml @@ -0,0 +1,126 @@ +--- +# DemaConsulting.Rendering.Abstractions System Requirements +# +# PURPOSE: +# - Define the SYSTEM-level requirements for the DemaConsulting.Rendering.Abstractions system: the +# service provider interface (SPI) that sits between the rendering model and the concrete layout +# and renderer implementations. +# - Each system requirement decomposes downward (children) into unit requirements in the per-unit +# sibling files: +# - rendering-contracts.yaml (ILayoutAlgorithm, IRenderer, RenderOptions, RenderOutput) +# - registries.yaml (LayoutAlgorithmRegistry, RendererRegistry) +# - theme.yaml (Theme and built-in Themes) +# - notation-metrics.yaml (shared notation geometry) +# - box-metrics.yaml (box title-area and folder-tab geometry) +# - connector-label-placer.yaml (connector midpoint-label placement) + +sections: + - title: Rendering Abstractions Requirements + requirements: + - id: Rendering-Abstractions-Extensibility-Contracts + title: >- + The abstractions system shall define pluggable layout-algorithm and renderer contracts. + justification: | + An ELK-inspired provider model lets additional layout families and output formats be + introduced purely additively - a new implementation is written against the stable + contracts, and no existing contract changes are required. + children: + - Rendering-Abstractions-Contracts-AlgorithmIdentifier + - Rendering-Abstractions-Contracts-AlgorithmApply + - Rendering-Abstractions-Contracts-RendererIdentity + - Rendering-Abstractions-Contracts-RendererWrite + tests: + - LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm + - RendererRegistry_RegisterThenResolve_ReturnsRenderer + + - id: Rendering-Abstractions-Extensibility-Registries + title: >- + The abstractions system shall provide registries that select an implementation by + identifier or media type. + justification: | + Consumers resolve the implementation they need at run time, so the abstractions layer must + provide lookup services that map algorithm ids and renderer metadata to registered + implementations without changing the contracts. + children: + - Rendering-Abstractions-Registries-ResolveAlgorithm + - Rendering-Abstractions-Registries-ResolveRenderer + - Rendering-Abstractions-Registries-ResolveRendererByExtension + - Rendering-Abstractions-Registries-MissingThrows + tests: + - LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm + - RendererRegistry_RegisterThenResolve_ReturnsRenderer + + - id: Rendering-Abstractions-Theming-Model + title: >- + The abstractions system shall define a visual theme model with built-in themes. + justification: | + Centralizing colors, fonts, and line metrics in a theme lets a single layout be rendered + in different visual styles while keeping the built-in themes consistent and reusable. + children: + - Rendering-Abstractions-Theme-BuiltInGeometry + tests: + - Themes_HaveExpectedConnectorGeometry + + - id: Rendering-Abstractions-Theming-ApproachGeometry + title: >- + The abstractions system shall derive connector approach geometry from theme values. + justification: | + Deriving the connector approach zone from theme values keeps the space reserved by layout + consistent with the space drawn by renderers. + children: + - Rendering-Abstractions-Theme-ApproachZone + tests: + - ConnectorApproachZone_SumsStubBendAndClearance + + - id: Rendering-Abstractions-SharedGeometry-Notation + title: >- + The abstractions system shall provide single-source notation geometry so every renderer + draws identical connector decorations and badges. + justification: | + When every renderer reads one source of notation geometry, marker and badge dimensions + match exactly and notation literals never have to be duplicated across the rendering path. + children: + - Rendering-Abstractions-NotationMetrics-TriangleDimensions + - Rendering-Abstractions-NotationMetrics-TriangleApexOvershoot + - Rendering-Abstractions-NotationMetrics-DiamondDimensions + - Rendering-Abstractions-NotationMetrics-DiamondFarPointOnEndpoint + - Rendering-Abstractions-NotationMetrics-CircleBarGeometry + - Rendering-Abstractions-NotationMetrics-Crossbar + - Rendering-Abstractions-NotationMetrics-AlongLineLength + - Rendering-Abstractions-NotationMetrics-PortSquare + - Rendering-Abstractions-NotationMetrics-FolderTab + - Rendering-Abstractions-NotationMetrics-NoteFold + - Rendering-Abstractions-NotationMetrics-RoundedRectCorner + - Rendering-Abstractions-NotationMetrics-RejectNullTheme + - Rendering-Abstractions-NotationMetrics-Badge + - Rendering-Abstractions-NotationMetrics-LabelBackground + tests: + - TriangleFamily_HasCanonicalValues + + - id: Rendering-Abstractions-SharedGeometry-Box + title: >- + The abstractions system shall provide single-source box geometry so renderers reserve + consistent title-area and folder-tab space. + justification: | + Shared box geometry keeps title bars and folder tabs consistent across renderers and + prevents duplicated box-sizing literals. + children: + - Rendering-Abstractions-BoxMetrics-FolderTabHeight + - Rendering-Abstractions-BoxMetrics-TitleAreaHeight + - Rendering-Abstractions-BoxMetrics-RejectNullTheme + tests: + - BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding + + - id: Rendering-Abstractions-SharedGeometry-ConnectorLabel + title: >- + The abstractions system shall provide single-source connector-label geometry so renderers + place labels consistently on routed connectors. + justification: | + Shared connector-label geometry keeps label placement consistent across renderers and + prevents each renderer from re-implementing midpoint and overlap rules. + children: + - Rendering-Abstractions-ConnectorLabelPlacer-OmitUnlabelled + - Rendering-Abstractions-ConnectorLabelPlacer-LongestSegment + - Rendering-Abstractions-ConnectorLabelPlacer-AvoidOverlap + tests: + - Place_SingleLine_UsesLongestSegmentMidpoint diff --git a/docs/reqstream/rendering-abstractions/box-metrics.yaml b/docs/reqstream/rendering-abstractions/box-metrics.yaml index a311d07..14af0b1 100644 --- a/docs/reqstream/rendering-abstractions/box-metrics.yaml +++ b/docs/reqstream/rendering-abstractions/box-metrics.yaml @@ -7,7 +7,7 @@ # rendering-abstractions.yaml. sections: - - title: Box Metrics Unit Requirements + - title: Box Metrics Requirements requirements: - id: Rendering-Abstractions-BoxMetrics-FolderTabHeight title: >- @@ -30,3 +30,15 @@ sections: - BoxMetrics_TitleAreaHeight_NoLabelNoKeyword_IsZero - BoxMetrics_TitleAreaHeight_LabelOnly_ReservesTitleLine - BoxMetrics_TitleAreaHeight_LabelAndKeyword_ReservesBothLines + + - id: Rendering-Abstractions-BoxMetrics-RejectNullTheme + title: >- + The box metrics shall reject a null theme argument with an ArgumentNullException rather + than allow an unhandled NullReferenceException. + justification: | + Failing fast with a documented exception type gives callers a clear, actionable contract + instead of an undocumented NullReferenceException, matching the null-argument contract + used by the sibling NotationMetrics unit. + tests: + - BoxMetrics_FolderTabHeight_NullTheme_ThrowsArgumentNullException + - BoxMetrics_TitleAreaHeight_NullTheme_ThrowsArgumentNullException diff --git a/docs/reqstream/rendering-abstractions/connector-label-placer.yaml b/docs/reqstream/rendering-abstractions/connector-label-placer.yaml index cd616ca..9475a76 100644 --- a/docs/reqstream/rendering-abstractions/connector-label-placer.yaml +++ b/docs/reqstream/rendering-abstractions/connector-label-placer.yaml @@ -8,7 +8,7 @@ # rendering-abstractions.yaml. sections: - - title: Connector Label Placer Unit Requirements + - title: Connector Label Placer Requirements requirements: - id: Rendering-Abstractions-ConnectorLabelPlacer-OmitUnlabelled title: >- diff --git a/docs/reqstream/rendering-abstractions/notation-metrics.yaml b/docs/reqstream/rendering-abstractions/notation-metrics.yaml index 4d84377..7603173 100644 --- a/docs/reqstream/rendering-abstractions/notation-metrics.yaml +++ b/docs/reqstream/rendering-abstractions/notation-metrics.yaml @@ -7,30 +7,48 @@ # rendering-abstractions.yaml. sections: - - title: Notation Metrics Unit Requirements + - title: Notation Metrics Requirements requirements: - - id: Rendering-Abstractions-NotationMetrics-TriangleGeometry + - id: Rendering-Abstractions-NotationMetrics-TriangleDimensions title: >- The notation metrics shall define the triangular end-marker geometry with the canonical - marker dimensions and an apex vertex that overshoots the line endpoint. + marker dimensions. justification: | The open chevron, hollow triangle, and filled arrow all share one triangle geometry; defining it once ensures every renderer draws these markers identically. tests: - TriangleFamily_HasCanonicalValues - TriangleVertices_ReproduceSvgBoxPoints + + - id: Rendering-Abstractions-NotationMetrics-TriangleApexOvershoot + title: >- + The notation metrics shall define a triangular end-marker apex vertex that overshoots + the line endpoint. + justification: | + The apex must extend past the line endpoint so the drawn stroke does not leave a visible + gap under the marker; defining the overshoot once keeps every renderer's markers aligned. + tests: - TriangleVertices_Apex_OvershootsEndpoint - - id: Rendering-Abstractions-NotationMetrics-DiamondGeometry + - id: Rendering-Abstractions-NotationMetrics-DiamondDimensions title: >- The notation metrics shall define the diamond end-marker geometry with the canonical - dimensions and a far vertex that lands on the line endpoint. + dimensions. justification: | - The hollow and filled diamonds share one geometry whose far point must sit on the endpoint; - defining it once keeps the two renderers' diamonds identical and correctly anchored. + The hollow and filled diamonds share one geometry; defining it once keeps the two + renderers' diamonds identical. tests: - Diamond_HasCanonicalValues - DiamondVertices_ReproduceSvgBoxPoints + + - id: Rendering-Abstractions-NotationMetrics-DiamondFarPointOnEndpoint + title: >- + The notation metrics shall define a diamond end-marker far vertex that lands on the + line endpoint. + justification: | + The far point must sit exactly on the endpoint so the diamond anchors precisely on the + line; defining this once keeps every renderer's diamonds correctly anchored. + tests: - DiamondVertices_FarPoint_LandsOnEndpoint - id: Rendering-Abstractions-NotationMetrics-CircleBarGeometry @@ -61,17 +79,74 @@ sections: tests: - AlongLineLength_MatchesMarkerBox - - id: Rendering-Abstractions-NotationMetrics-BoxDecorations + - id: Rendering-Abstractions-NotationMetrics-PortSquare title: >- - The notation metrics shall define the port square, folder-tab, note-fold, rounded-rectangle - corner, badge, and label-background proportions used to draw box decorations. + The notation metrics shall define the port-square decoration proportion used to draw + box decorations. justification: | - These intrinsic, theme-independent proportions describe SysML box notation; defining each - once ensures the SVG and PNG renderers draw the same shapes at the same sizes. + This intrinsic, theme-independent proportion describes SysML port notation; defining it + once ensures the SVG and PNG renderers draw the same shape at the same size. tests: - Port_SizeIsTwiceHalfSize + + - id: Rendering-Abstractions-NotationMetrics-FolderTab + title: >- + The notation metrics shall define the folder-tab decoration proportion used to draw + box decorations. + justification: | + This intrinsic, theme-independent proportion describes SysML folder-tab notation; defining + it once ensures the SVG and PNG renderers draw the same shape at the same size. + tests: - FolderTab_HasDocumentedConstants + + - id: Rendering-Abstractions-NotationMetrics-NoteFold + title: >- + The notation metrics shall define the note-fold decoration proportion used to draw box + decorations. + justification: | + This intrinsic, theme-independent proportion describes SysML note-fold notation; defining + it once ensures the SVG and PNG renderers draw the same shape at the same size. + tests: - NoteFold_HasDocumentedConstants + + - id: Rendering-Abstractions-NotationMetrics-RoundedRectCorner + title: >- + The notation metrics shall define the rounded-rectangle corner proportion used to draw + box decorations. + justification: | + This intrinsic, theme-independent proportion describes SysML rounded-rectangle notation; + defining it once ensures the SVG and PNG renderers draw the same shape at the same size. + tests: - RoundedRectRadius_IsThemeRadiusTimesFactor + + - id: Rendering-Abstractions-NotationMetrics-RejectNullTheme + title: >- + NotationMetrics.RoundedRectRadius shall reject a null theme argument with an argument-null + error. + justification: | + RoundedRectRadius derives its result from the theme's corner radius; failing fast on a null + theme prevents a downstream null-reference failure and surfaces the caller's error at the + public boundary, matching the convention used by the other geometry-helper classes. + tests: + - RoundedRectRadius_NullTheme_ThrowsArgumentNullException + + - id: Rendering-Abstractions-NotationMetrics-Badge + title: >- + The notation metrics shall define the badge decoration proportions used to draw box + decorations. + justification: | + These intrinsic, theme-independent proportions describe SysML badge notation; defining + them once ensures the SVG and PNG renderers draw the same shapes at the same sizes. + tests: - BadgeFractions_HaveDocumentedValues + + - id: Rendering-Abstractions-NotationMetrics-LabelBackground + title: >- + The notation metrics shall define the label-background proportion used to draw box + decorations. + justification: | + This intrinsic, theme-independent proportion describes the SysML label-background + notation; defining it once ensures the SVG and PNG renderers draw the same shape at the + same size. + tests: - LabelBackground_ExtentMatchesInset diff --git a/docs/reqstream/rendering-abstractions/registries.yaml b/docs/reqstream/rendering-abstractions/registries.yaml index 299b3cf..f4e1a55 100644 --- a/docs/reqstream/rendering-abstractions/registries.yaml +++ b/docs/reqstream/rendering-abstractions/registries.yaml @@ -7,7 +7,7 @@ # rendering-abstractions.yaml. sections: - - title: Registries Unit Requirements + - title: Registries Requirements requirements: - id: Rendering-Abstractions-Registries-ResolveAlgorithm title: >- diff --git a/docs/reqstream/rendering-abstractions/rendering-abstractions.yaml b/docs/reqstream/rendering-abstractions/rendering-abstractions.yaml deleted file mode 100644 index 396e7fd..0000000 --- a/docs/reqstream/rendering-abstractions/rendering-abstractions.yaml +++ /dev/null @@ -1,77 +0,0 @@ ---- -# DemaConsulting.Rendering.Abstractions System Requirements -# -# PURPOSE: -# - Define the SYSTEM-level requirements for the DemaConsulting.Rendering.Abstractions system: the -# service provider interface (SPI) that sits between the rendering model and the concrete layout -# and renderer implementations. -# - Each system requirement decomposes downward (children) into unit requirements in the per-unit -# sibling files: -# - rendering-contracts.yaml (ILayoutAlgorithm, IRenderer, RenderOptions, RenderOutput) -# - registries.yaml (LayoutAlgorithmRegistry, RendererRegistry) -# - theme.yaml (Theme and built-in Themes) -# - notation-metrics.yaml (shared notation geometry) -# - box-metrics.yaml (box title-area and folder-tab geometry) -# - connector-label-placer.yaml (connector midpoint-label placement) - -sections: - - title: Rendering Abstractions System Requirements - requirements: - - id: Rendering-Abstractions-Extensibility - title: >- - The abstractions system shall define the pluggable layout-algorithm and renderer contracts - and shall provide registries that select an implementation by identifier or media type. - justification: | - An ELK-inspired provider model lets additional layout families and output formats be - introduced purely additively - a new implementation is written and registered, and no - existing contract changes - so consumers resolve the implementation they need at run time. - children: - - Rendering-Abstractions-Contracts-Algorithm - - Rendering-Abstractions-Contracts-Renderer - - Rendering-Abstractions-Registries-ResolveAlgorithm - - Rendering-Abstractions-Registries-ResolveRenderer - - Rendering-Abstractions-Registries-ResolveRendererByExtension - - Rendering-Abstractions-Registries-MissingThrows - tests: - - LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm - - RendererRegistry_RegisterThenResolve_ReturnsRenderer - - - id: Rendering-Abstractions-Theming - title: >- - The abstractions system shall define a visual theme model with built-in themes and derived - connector approach geometry. - justification: | - Centralizing colors, fonts, and line metrics in a theme lets a single layout be rendered - in different visual styles, and deriving the connector approach zone from theme values - keeps the space reserved by layout consistent with the space drawn by renderers. - children: - - Rendering-Abstractions-Theme-ApproachZone - - Rendering-Abstractions-Theme-BuiltInGeometry - tests: - - ConnectorApproachZone_SumsStubBendAndClearance - - Themes_HaveExpectedConnectorGeometry - - - id: Rendering-Abstractions-SharedGeometry - title: >- - The abstractions system shall provide single-source notation, box, and connector-label - geometry so that every renderer draws identical decorations and reserves consistent space. - justification: | - When the SVG and PNG renderers and the layout strategies all read one source of geometry, - their markers, box title areas, and label placements match exactly and a geometry literal - never has to be duplicated across the rendering path. - children: - - Rendering-Abstractions-NotationMetrics-TriangleGeometry - - Rendering-Abstractions-NotationMetrics-DiamondGeometry - - Rendering-Abstractions-NotationMetrics-CircleBarGeometry - - Rendering-Abstractions-NotationMetrics-Crossbar - - Rendering-Abstractions-NotationMetrics-AlongLineLength - - Rendering-Abstractions-NotationMetrics-BoxDecorations - - Rendering-Abstractions-BoxMetrics-FolderTabHeight - - Rendering-Abstractions-BoxMetrics-TitleAreaHeight - - Rendering-Abstractions-ConnectorLabelPlacer-OmitUnlabelled - - Rendering-Abstractions-ConnectorLabelPlacer-LongestSegment - - Rendering-Abstractions-ConnectorLabelPlacer-AvoidOverlap - tests: - - TriangleFamily_HasCanonicalValues - - BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding - - Place_SingleLine_UsesLongestSegmentMidpoint diff --git a/docs/reqstream/rendering-abstractions/rendering-contracts.yaml b/docs/reqstream/rendering-abstractions/rendering-contracts.yaml index ad42086..d28c440 100644 --- a/docs/reqstream/rendering-abstractions/rendering-contracts.yaml +++ b/docs/reqstream/rendering-abstractions/rendering-contracts.yaml @@ -8,27 +8,40 @@ # rendering-abstractions.yaml. sections: - - title: Rendering Contracts Unit Requirements + - title: Rendering Contracts Requirements requirements: - - id: Rendering-Abstractions-Contracts-Algorithm + - id: Rendering-Abstractions-Contracts-AlgorithmIdentifier title: >- - A layout algorithm shall expose a stable identifier used to select it and shall produce a - placed layout tree from an unplaced input graph and its options. + A layout algorithm shall expose a stable identifier used to select it. justification: | - A stable identifier lets a caller request a specific algorithm by name, and a uniform - apply operation lets any diagram family be laid out through the same contract. + A stable identifier lets a caller request a specific algorithm by name. tests: - LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm - - id: Rendering-Abstractions-Contracts-Renderer + - id: Rendering-Abstractions-Contracts-AlgorithmApply + title: >- + A layout algorithm shall produce a placed layout tree from an unplaced input graph and + its options. + justification: | + A uniform apply operation lets any diagram family be laid out through the same contract. + tests: + - LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm + + - id: Rendering-Abstractions-Contracts-RendererIdentity title: >- A renderer shall expose its media type, its default file extension, and the full set of - file extensions it produces, and shall write the rendered output for a layout tree to a - supplied stream. + file extensions it produces. justification: | Exposing the media type and the file extensions lets callers name and serve the output - correctly and resolve a renderer from an output filename, and writing to a caller-supplied - stream keeps the renderer free of filesystem concerns. + correctly and resolve a renderer from an output filename. tests: - RendererRegistry_RegisterThenResolve_ReturnsRenderer - PngRenderer_FileExtensions_ContainsDefault + + - id: Rendering-Abstractions-Contracts-RendererWrite + title: >- + A renderer shall write the rendered output for a layout tree to a supplied stream. + justification: | + Writing to a caller-supplied stream keeps the renderer free of filesystem concerns. + tests: + - RendererRegistry_RegisterThenResolve_ReturnsRenderer diff --git a/docs/reqstream/rendering-abstractions/theme.yaml b/docs/reqstream/rendering-abstractions/theme.yaml index 3e3cf1a..7f52918 100644 --- a/docs/reqstream/rendering-abstractions/theme.yaml +++ b/docs/reqstream/rendering-abstractions/theme.yaml @@ -7,7 +7,7 @@ # rendering-abstractions.yaml. sections: - - title: Theme Unit Requirements + - title: Theme Requirements requirements: - id: Rendering-Abstractions-Theme-ApproachZone title: >- diff --git a/docs/reqstream/rendering-layout/rendering-layout.yaml b/docs/reqstream/rendering-layout.yaml similarity index 97% rename from docs/reqstream/rendering-layout/rendering-layout.yaml rename to docs/reqstream/rendering-layout.yaml index ddd9f3d..dc5b17a 100644 --- a/docs/reqstream/rendering-layout/rendering-layout.yaml +++ b/docs/reqstream/rendering-layout.yaml @@ -16,7 +16,7 @@ # engine/*.yaml and {unit-name}.yaml. sections: - - title: Rendering.Layout System Requirements + - title: Rendering.Layout Requirements requirements: - id: Rendering-Layout-Algorithm title: >- @@ -75,6 +75,7 @@ sections: - Rendering-Layout-LayeredPipeline-BackEdgeApproach - Rendering-Layout-LayeredPipeline-LongEdgeJoining - Rendering-Layout-LayeredPipeline-ComponentPacking + - Rendering-Layout-LayeredPipeline-PackedComponentsBackEdgeApproach - Rendering-Layout-LayeredPipeline-SharedState - Rendering-Layout-LayeredPipeline-InputValidation tests: @@ -146,6 +147,7 @@ sections: - Rendering-Layout-ContainmentAlgorithm-EmptyGraph - Rendering-Layout-ContainmentAlgorithm-SkipsOutOfGraphEdges - Rendering-Layout-ContainmentAlgorithm-Validation + - Rendering-Layout-ContainmentAlgorithm-HonorsScopeEdgeRouting tests: - Apply_Graph_PacksNodesNonOverlappingInInputOrderWithinCanvas - Id_IsContainment @@ -167,6 +169,8 @@ sections: - Rendering-Layout-HierarchicalLayout-PerNodeAlgorithm - Rendering-Layout-HierarchicalLayout-HierarchyHandling - Rendering-Layout-HierarchicalLayout-CrossContainerEdge + - Rendering-Layout-HierarchicalLayout-CascadesOptions + - Rendering-Layout-HierarchicalLayout-HonorsScopeEdgeRouting - Rendering-Layout-HierarchicalLayout-ValidatesGraph - Rendering-Layout-HierarchicalLayout-ValidatesOptions - Rendering-Layout-HierarchicalLayout-ValidatesRegistry diff --git a/docs/reqstream/rendering-layout/connector-router.yaml b/docs/reqstream/rendering-layout/connector-router.yaml index 5b16a80..42073da 100644 --- a/docs/reqstream/rendering-layout/connector-router.yaml +++ b/docs/reqstream/rendering-layout/connector-router.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: ConnectorRouter Unit Requirements + - title: ConnectorRouter Requirements requirements: - id: Rendering-Layout-ConnectorRouter-AnchorsFaceEachOther title: >- diff --git a/docs/reqstream/rendering-layout/containment-layout-algorithm.yaml b/docs/reqstream/rendering-layout/containment-layout-algorithm.yaml index 06c31bc..08ba142 100644 --- a/docs/reqstream/rendering-layout/containment-layout-algorithm.yaml +++ b/docs/reqstream/rendering-layout/containment-layout-algorithm.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: ContainmentLayoutAlgorithm Unit Requirements + - title: ContainmentLayoutAlgorithm Requirements requirements: - id: Rendering-Layout-ContainmentAlgorithm-Identity title: >- @@ -78,3 +78,15 @@ sections: tests: - Apply_NullGraph_Throws - Apply_NullOptions_Throws + + - id: Rendering-Layout-ContainmentAlgorithm-HonorsScopeEdgeRouting + title: >- + ContainmentLayoutAlgorithm shall honor an explicit CoreOptions.EdgeRouting override set on the + input graph itself, in preference to the value on the supplied options. + justification: | + Same class of previously-silent defect as the hierarchical engine's cross-container case: this + algorithm is directly callable, so an override carried on its own graph (rather than the + options it happens to be invoked with) must be honored independently, mirroring + LayeredLayoutAlgorithm's graph-then-options-then-default resolution of CoreOptions.Direction. + tests: + - Apply_EdgeRoutingOverrideOnGraphScope_IsHonored diff --git a/docs/reqstream/rendering-layout/containment-layout.yaml b/docs/reqstream/rendering-layout/containment-layout.yaml index f936648..e035fae 100644 --- a/docs/reqstream/rendering-layout/containment-layout.yaml +++ b/docs/reqstream/rendering-layout/containment-layout.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: ContainmentLayout Unit Requirements + - title: ContainmentLayout Requirements requirements: - id: Rendering-Layout-ContainmentLayout-Order title: >- diff --git a/docs/reqstream/rendering-layout/default-layout.yaml b/docs/reqstream/rendering-layout/default-layout.yaml index 4225d8d..b5fa43e 100644 --- a/docs/reqstream/rendering-layout/default-layout.yaml +++ b/docs/reqstream/rendering-layout/default-layout.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: DefaultLayout Unit Requirements + - title: DefaultLayout Requirements requirements: - id: Rendering-Layout-DefaultRegistry-BundledAlgorithms title: >- diff --git a/docs/reqstream/rendering-layout/edge-routing-option.yaml b/docs/reqstream/rendering-layout/edge-routing-option.yaml index b0a9c41..ed3dbe4 100644 --- a/docs/reqstream/rendering-layout/edge-routing-option.yaml +++ b/docs/reqstream/rendering-layout/edge-routing-option.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: EdgeRouting Option Unit Requirements + - title: EdgeRouting Option Requirements requirements: - id: Rendering-Layout-EdgeRouting-Selection title: >- @@ -18,10 +18,10 @@ sections: can choose a style. An unset scope must fall back to the orthogonal default so existing callers route orthogonally without opting in. tests: - - CoreOptions_EdgeRouting_DefaultsToOrthogonal - - CoreOptions_EdgeRouting_HasStableId - - CoreOptions_EdgeRouting_SelectablePerScope - - CoreOptions_EdgeRouting_UnsetReturnsDefault + - CoreOptions_EdgeRouting_DefaultValue_IsOrthogonal + - CoreOptions_EdgeRouting_Id_IsStableDottedIdentifier + - CoreOptions_EdgeRouting_SetThenGet_RoundTripsValue + - CoreOptions_EdgeRouting_UnsetHolder_ReturnsOrthogonalDefault - id: Rendering-Layout-EdgeRouting-Defaults title: >- @@ -32,4 +32,4 @@ sections: orthogonal routing with a fixed clearance provides that, while allowing the clearance to be overridden so a downstream adapter can reproduce a specific spacing. tests: - - ConnectorRouteOptions_Defaults_AreOrthogonalWithTwelvePixelClearance + - ConnectorRouteOptions_Constructor_Defaults_AreOrthogonalWithTwelvePixelClearance diff --git a/docs/reqstream/rendering-layout/engine/containment-packer.yaml b/docs/reqstream/rendering-layout/engine/containment-packer.yaml index ed62349..4d32b00 100644 --- a/docs/reqstream/rendering-layout/engine/containment-packer.yaml +++ b/docs/reqstream/rendering-layout/engine/containment-packer.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: ContainmentPacker Unit Requirements + - title: ContainmentPacker Requirements requirements: - id: Rendering-Layout-ContainmentPacker-SingleRow title: >- diff --git a/docs/reqstream/rendering-layout/engine/engine.yaml b/docs/reqstream/rendering-layout/engine/engine.yaml index 49b4651..fd34535 100644 --- a/docs/reqstream/rendering-layout/engine/engine.yaml +++ b/docs/reqstream/rendering-layout/engine/engine.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: Engine Subsystem Requirements + - title: Engine Requirements requirements: - id: Rendering-Layout-OrthogonalRouting title: >- @@ -58,8 +58,12 @@ sections: - Rendering-Layout-InterconnectionEngine-Layering - Rendering-Layout-InterconnectionEngine-NonOverlapping - Rendering-Layout-InterconnectionEngine-DummyNodes - - Rendering-Layout-InterconnectionEngine-Waypoints - - Rendering-Layout-InterconnectionEngine-Direction + - Rendering-Layout-InterconnectionEngine-Waypoints-AcyclicMapping + - Rendering-Layout-InterconnectionEngine-Waypoints-StraightSpanOne + - Rendering-Layout-InterconnectionEngine-Waypoints-LongEdgeRouting + - Rendering-Layout-InterconnectionEngine-Direction-RequestedFlow + - Rendering-Layout-InterconnectionEngine-Direction-TransposedTotals + - Rendering-Layout-InterconnectionEngine-Direction-DefaultsToRight - Rendering-Layout-InterconnectionEngine-Deterministic tests: - Place_LinearChain_MonotonicLayerAssignment diff --git a/docs/reqstream/rendering-layout/engine/interconnection-layout-engine.yaml b/docs/reqstream/rendering-layout/engine/interconnection-layout-engine.yaml index aa6f13e..87bd26f 100644 --- a/docs/reqstream/rendering-layout/engine/interconnection-layout-engine.yaml +++ b/docs/reqstream/rendering-layout/engine/interconnection-layout-engine.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: InterconnectionLayoutEngine Unit Requirements + - title: InterconnectionLayoutEngine Requirements requirements: - id: Rendering-Layout-InterconnectionEngine-Layering title: >- @@ -40,30 +40,67 @@ sections: - Place_LongEdge_RectCountEqualsInputNodeCount - Place_LongEdge_RoutesViaDummyNodesWithinBounds - - id: Rendering-Layout-InterconnectionEngine-Waypoints + - id: Rendering-Layout-InterconnectionEngine-Waypoints-AcyclicMapping title: >- - InterconnectionLayoutEngine shall return one waypoint list per input edge; a span-one edge - produces a straight two-waypoint path and a long edge routes via dummy nodes within the - layout bounds. + InterconnectionLayoutEngine shall return one waypoint list per acyclic edge after + self-loop removal, duplicate-pair de-duplication, and back-edge reversal, with the + waypoint list index-aligned to the reported acyclic edge set. justification: | Pre-computed waypoints allow the public algorithm and the interconnection view to emit - connector lines without any additional routing logic. + connector lines without additional routing logic. The layered-layout pipeline requires an + acyclic graph to assign layers, so cycle-breaking necessarily runs before routing; the + waypoint contract reflects this and lets a consumer recover each retained edge's route. + tests: + - Place_CyclicGraph_ReversesBackEdgeAndProducesWaypoint + + - id: Rendering-Layout-InterconnectionEngine-Waypoints-StraightSpanOne + title: >- + InterconnectionLayoutEngine shall route a span-one edge as a straight two-waypoint path. + justification: | + A direct connection between adjacent layers does not require dummy nodes or bend points, so + the routed path should remain a straight source-port to target-port segment. tests: - Place_SingleEdge_ProducesStraightTwoWaypointPath + + - id: Rendering-Layout-InterconnectionEngine-Waypoints-LongEdgeRouting + title: >- + InterconnectionLayoutEngine shall route a long edge via dummy nodes within the layout + bounds. + justification: | + Routing long edges through intermediate dummy nodes keeps connectors in the correct + inter-layer corridors and prevents them from clipping intervening boxes. + tests: - Place_LongEdge_RoutesViaDummyNodesWithinBounds - - id: Rendering-Layout-InterconnectionEngine-Direction + - id: Rendering-Layout-InterconnectionEngine-Direction-RequestedFlow title: >- - InterconnectionLayoutEngine shall place the nodes along the requested flow direction, - transposing the layout and its reported total dimensions for a top-to-bottom or bottom-to-top - flow, and shall default to the left-to-right flow. + InterconnectionLayoutEngine shall place nodes along the requested flow direction. justification: | - Directed views request a top-to-bottom placement; the engine must honor the requested - direction and report total dimensions that match the transposed layout, while the default - leaves the established left-to-right geometry unchanged. + Directed views request a specific reading order, so the engine must orient the node stack + to match the selected layout direction. tests: - Place_DownDirection_TransposesTotalsRelativeToRight + - id: Rendering-Layout-InterconnectionEngine-Direction-TransposedTotals + title: >- + InterconnectionLayoutEngine shall transpose the reported total dimensions for top-to-bottom + and bottom-to-top flow directions. + justification: | + Consumers need total dimensions that match the rendered screen-space orientation when the + layered layout is rotated away from the default rightward flow. + tests: + - Place_DownDirection_TransposesTotalsRelativeToRight + + - id: Rendering-Layout-InterconnectionEngine-Direction-DefaultsToRight + title: >- + InterconnectionLayoutEngine shall default to the left-to-right flow when no direction is + requested. + justification: | + Preserving the established rightward default keeps existing callers behavior-preserving when + they omit the optional direction argument. + tests: + - Place_DefaultDirection_MatchesRightFlow + - id: Rendering-Layout-InterconnectionEngine-Deterministic title: >- Given identical input, InterconnectionLayoutEngine shall produce identical geometry and @@ -72,5 +109,4 @@ sections: Reproducible layout is required so regenerating a view from unchanged input does not shift boxes or change connector routes. tests: - - Place_LinearChain_MonotonicLayerAssignment - - Place_WorkstationTopology_CorrectLayersAndNoOverlap + - Place_RepeatedInvocation_ProducesIdenticalGeometry diff --git a/docs/reqstream/rendering-layout/engine/layered-pipeline.yaml b/docs/reqstream/rendering-layout/engine/layered-pipeline.yaml index 23eb29a..66fb0db 100644 --- a/docs/reqstream/rendering-layout/engine/layered-pipeline.yaml +++ b/docs/reqstream/rendering-layout/engine/layered-pipeline.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: Layered Pipeline Unit Requirements + - title: Layered Pipeline Requirements requirements: - id: Rendering-Layout-LayeredPipeline-StagedPipeline title: >- @@ -195,6 +195,17 @@ sections: - ComponentPacker_Apply_SingleComponent_ParallelAndSelfEdges_ProducesAlignedWaypoints - ComponentPacker_Apply_MultiComponent_ParallelAndSelfEdges_MergesAlignedWaypoints + - id: Rendering-Layout-LayeredPipeline-PackedComponentsBackEdgeApproach + title: >- + The component-packing stage shall propagate the parent graph's configured back-edge entry + approach to every packed child component's graph. + justification: | + Each packed component is laid out through a freshly constructed child LayeredGraph; without + copying the parent's BackEdgeEntryApproach, a caller-customized reversed-edge clearance would + silently revert to the class default for any multi-component (disconnected) graph. + tests: + - ComponentPacker_Apply_MultiComponent_PropagatesBackEdgeEntryApproach + - id: Rendering-Layout-LayeredPipeline-SharedState title: >- The layered graph shall carry the mutable shared state threaded through every stage and diff --git a/docs/reqstream/rendering-layout/engine/orthogonal-edge-router.yaml b/docs/reqstream/rendering-layout/engine/orthogonal-edge-router.yaml index 26ea6f4..31e70e5 100644 --- a/docs/reqstream/rendering-layout/engine/orthogonal-edge-router.yaml +++ b/docs/reqstream/rendering-layout/engine/orthogonal-edge-router.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: OrthogonalEdgeRouter Unit Requirements + - title: OrthogonalEdgeRouter Requirements requirements: - id: Rendering-Layout-OrthogonalEdgeRouter-Orthogonal title: >- diff --git a/docs/reqstream/rendering-layout/hierarchical-layout-algorithm.yaml b/docs/reqstream/rendering-layout/hierarchical-layout-algorithm.yaml index 488e063..d9e20ea 100644 --- a/docs/reqstream/rendering-layout/hierarchical-layout-algorithm.yaml +++ b/docs/reqstream/rendering-layout/hierarchical-layout-algorithm.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: HierarchicalLayoutAlgorithm Unit Requirements + - title: HierarchicalLayoutAlgorithm Requirements requirements: - id: Rendering-Layout-HierarchicalLayout-Identity title: >- @@ -74,6 +74,37 @@ sections: tests: - Apply_CrossContainerEdge_RoutesAroundInterveningContainer + - id: Rendering-Layout-HierarchicalLayout-CascadesOptions + title: >- + HierarchicalLayoutAlgorithm shall resolve every CoreOptions property (Algorithm, Direction, + EdgeRouting, HierarchyHandling, NodeSpacing, LayerSpacing) for a scope by walking to the + nearest ancestor scope with an explicit override, falling back to the property's own default + only when no scope in the chain declares one. + justification: | + A leaf algorithm reads a CoreOptions property from the options it is given, in preference to + the shared root options. Without a generalized per-scope cascade, a caller-supplied override on + a nested container would be silently dropped and the container would fall back to the parent + options' value (or the property default) instead — as previously happened for every property + except Algorithm. Generalizing the mechanism (via PropertyHolder.OverlayOnto) fixes this for + all six properties at once rather than one at a time. + tests: + - Apply_ContainerWithDirectionOverride_HonorsNestedDirection + - Apply_ThreeLevelDirectionCascade_InheritsThroughUnsetMiddleLevel + - Apply_ThreeLevelDirectionCascade_MidLevelOverrideTakesPrecedence + - Apply_ThreeLevelEdgeRoutingCascade_ReachesEveryLeafAlgorithmCall + + - id: Rendering-Layout-HierarchicalLayout-HonorsScopeEdgeRouting + title: >- + HierarchicalLayoutAlgorithm shall route a cross-container edge using the owning scope's + cascaded EdgeRouting value, honoring an explicit override set on that scope's own graph. + justification: | + Previously RouteCrossContainerEdges read CoreOptions.EdgeRouting directly from the root + options, ignoring any override set on the owning scope's own graph — a genuine, + previously-undocumented defect with zero observable effect until fixed, since the cascaded + effective snapshot now reaches this method instead. + tests: + - Apply_CrossContainerEdge_HonorsScopeEdgeRoutingOverride + - id: Rendering-Layout-HierarchicalLayout-ValidatesGraph title: >- HierarchicalLayoutAlgorithm shall reject a null graph argument with an argument-null error. diff --git a/docs/reqstream/rendering-layout/layered-layout-algorithm.yaml b/docs/reqstream/rendering-layout/layered-layout-algorithm.yaml index 5d5d360..fe77950 100644 --- a/docs/reqstream/rendering-layout/layered-layout-algorithm.yaml +++ b/docs/reqstream/rendering-layout/layered-layout-algorithm.yaml @@ -6,7 +6,7 @@ # - This file is part of the split Rendering.Layout requirements set. sections: - - title: LayeredLayoutAlgorithm Unit Requirements + - title: LayeredLayoutAlgorithm Requirements requirements: - id: Rendering-Layout-LayeredAlgorithm-Identity title: >- diff --git a/docs/reqstream/rendering-skia/rendering-skia.yaml b/docs/reqstream/rendering-skia.yaml similarity index 100% rename from docs/reqstream/rendering-skia/rendering-skia.yaml rename to docs/reqstream/rendering-skia.yaml diff --git a/docs/reqstream/rendering-skia/jpeg-renderer.yaml b/docs/reqstream/rendering-skia/jpeg-renderer.yaml index 4b2b088..0aa2ddc 100644 --- a/docs/reqstream/rendering-skia/jpeg-renderer.yaml +++ b/docs/reqstream/rendering-skia/jpeg-renderer.yaml @@ -8,7 +8,7 @@ # declared in rendering-skia.yaml. sections: - - title: JpegRenderer Unit Requirements + - title: JpegRenderer Requirements requirements: - id: Rendering-Skia-JpegRenderer-EmitsJpeg title: >- diff --git a/docs/reqstream/rendering-skia/png-renderer.yaml b/docs/reqstream/rendering-skia/png-renderer.yaml index 6b3b9b2..1838d4b 100644 --- a/docs/reqstream/rendering-skia/png-renderer.yaml +++ b/docs/reqstream/rendering-skia/png-renderer.yaml @@ -8,7 +8,7 @@ # declared in rendering-skia.yaml. sections: - - title: PngRenderer Unit Requirements + - title: PngRenderer Requirements requirements: - id: Rendering-Skia-PngRenderer-EmitsPng title: >- diff --git a/docs/reqstream/rendering-skia/skia-raster-renderer.yaml b/docs/reqstream/rendering-skia/skia-raster-renderer.yaml index e3399a9..e3520e9 100644 --- a/docs/reqstream/rendering-skia/skia-raster-renderer.yaml +++ b/docs/reqstream/rendering-skia/skia-raster-renderer.yaml @@ -8,7 +8,7 @@ # declared in rendering-skia.yaml. sections: - - title: SkiaRasterRenderer Unit Requirements + - title: SkiaRasterRenderer Requirements requirements: - id: Rendering-Skia-SkiaRasterRenderer-DrawsLayoutTree title: >- diff --git a/docs/reqstream/rendering-skia/webp-renderer.yaml b/docs/reqstream/rendering-skia/webp-renderer.yaml index 65772f6..abc49c4 100644 --- a/docs/reqstream/rendering-skia/webp-renderer.yaml +++ b/docs/reqstream/rendering-skia/webp-renderer.yaml @@ -8,7 +8,7 @@ # declared in rendering-skia.yaml. sections: - - title: WebpRenderer Unit Requirements + - title: WebpRenderer Requirements requirements: - id: Rendering-Skia-WebpRenderer-EmitsWebp title: >- diff --git a/docs/reqstream/rendering-svg/rendering-svg.yaml b/docs/reqstream/rendering-svg.yaml similarity index 100% rename from docs/reqstream/rendering-svg/rendering-svg.yaml rename to docs/reqstream/rendering-svg.yaml diff --git a/docs/reqstream/rendering-svg/svg-renderer.yaml b/docs/reqstream/rendering-svg/svg-renderer.yaml index 6378793..e07b67d 100644 --- a/docs/reqstream/rendering-svg/svg-renderer.yaml +++ b/docs/reqstream/rendering-svg/svg-renderer.yaml @@ -8,7 +8,7 @@ # in rendering-svg.yaml. sections: - - title: SvgRenderer Unit Requirements + - title: SvgRenderer Requirements requirements: - id: Rendering-Svg-SvgRenderer-ImplementsIRenderer title: >- @@ -157,6 +157,39 @@ sections: tests: - SvgRenderer_Render_SingleBadge_FilledCircle_ProducesCircle + - id: Rendering-Svg-SvgRenderer-BadgeBullseye + title: >- + SvgRenderer shall render bullseye badge nodes as concentric circle elements. + justification: | + Bullseye badges are notation icons whose outer ring and inner disc must remain visible in + the SVG output. + tests: + - SvgRenderer_Render_SingleBadge_Bullseye_ProducesConcentricCircles + + - id: Rendering-Svg-SvgRenderer-BadgeDiamond + title: >- + SvgRenderer shall render diamond badge nodes as polygon elements. + justification: | + Diamond badges are notation icons that must appear as diamond-shaped SVG polygons. + tests: + - SvgRenderer_Render_SingleBadge_Diamond_ProducesPolygon + + - id: Rendering-Svg-SvgRenderer-BadgeHorizontalBar + title: >- + SvgRenderer shall render horizontal-bar badge nodes as line elements. + justification: | + Horizontal-bar badges are notation icons that must appear as short horizontal SVG lines. + tests: + - SvgRenderer_Render_SingleBadge_HorizontalBar_ProducesLine + + - id: Rendering-Svg-SvgRenderer-BadgeVerticalBar + title: >- + SvgRenderer shall render vertical-bar badge nodes as line elements. + justification: | + Vertical-bar badges are notation icons that must appear as short vertical SVG lines. + tests: + - SvgRenderer_Render_SingleBadge_VerticalBar_ProducesLine + - id: Rendering-Svg-SvgRenderer-RenderBand title: >- SvgRenderer shall render band nodes as rect elements. @@ -259,3 +292,41 @@ sections: The crossbar marker id identifies the connector-end decoration used for crossbar line ends. tests: - SvgRenderer_Render_SingleLine_WithOpenCrossbarArrowhead_ProducesOpenCrossbarMarker + + - id: Rendering-Svg-SvgRenderer-EndMarkerFilledArrow + title: >- + SvgRenderer shall reference the filled-arrow connector end marker for filled-arrow line + ends. + justification: | + Filled-arrow markers express a distinct directed connector style and must remain attached + to the rendered line. + tests: + - SvgRenderer_Render_SingleLine_WithFilledArrow_ProducesFilledArrowMarker + + - id: Rendering-Svg-SvgRenderer-EndMarkerFilledDiamond + title: >- + SvgRenderer shall reference the filled-diamond connector end marker for filled-diamond + line ends. + justification: | + Filled-diamond markers express a distinct connector-end vocabulary and must remain attached + to the rendered line. + tests: + - SvgRenderer_Render_SingleLine_WithFilledDiamond_ProducesFilledDiamondMarker + + - id: Rendering-Svg-SvgRenderer-EndMarkerCircle + title: >- + SvgRenderer shall reference the circle connector end marker for circle line ends. + justification: | + Circle markers express a distinct connector-end vocabulary and must remain attached to the + rendered line. + tests: + - SvgRenderer_Render_SingleLine_WithCircleEnd_ProducesCircleMarker + + - id: Rendering-Svg-SvgRenderer-EndMarkerBar + title: >- + SvgRenderer shall reference the bar connector end marker for bar line ends. + justification: | + Bar markers express a distinct connector-end vocabulary and must remain attached to the + rendered line. + tests: + - SvgRenderer_Render_SingleLine_WithBarEnd_ProducesBarMarker diff --git a/docs/reqstream/rendering/rendering.yaml b/docs/reqstream/rendering.yaml similarity index 98% rename from docs/reqstream/rendering/rendering.yaml rename to docs/reqstream/rendering.yaml index d255b5a..c509f28 100644 --- a/docs/reqstream/rendering/rendering.yaml +++ b/docs/reqstream/rendering.yaml @@ -25,7 +25,7 @@ # - LayoutGraphEdge.LineStyle - optional input-edge stroke-style hint sections: - - title: Rendering Model System Requirements + - title: Rendering Model Requirements requirements: - id: Rendering-Model-LayoutTree title: >- @@ -66,6 +66,7 @@ sections: - Rendering-Model-Options-StoreAndRetrieve - Rendering-Model-Options-Contains - Rendering-Model-Options-TryGet + - Rendering-Model-Options-Cascade tests: - Get_UnsetProperty_ReturnsDefault - Get_AfterSet_ReturnsStoredValue diff --git a/docs/reqstream/rendering/layout-graph.yaml b/docs/reqstream/rendering/layout-graph.yaml index 93ddffa..7381961 100644 --- a/docs/reqstream/rendering/layout-graph.yaml +++ b/docs/reqstream/rendering/layout-graph.yaml @@ -8,7 +8,7 @@ # in rendering.yaml. sections: - - title: Layout Graph Unit Requirements + - title: Layout Graph Requirements requirements: - id: Rendering-Model-LayoutGraph-AddNode title: >- diff --git a/docs/reqstream/rendering/layout-tree.yaml b/docs/reqstream/rendering/layout-tree.yaml index 684482e..1cb0118 100644 --- a/docs/reqstream/rendering/layout-tree.yaml +++ b/docs/reqstream/rendering/layout-tree.yaml @@ -9,7 +9,7 @@ # in rendering.yaml. sections: - - title: Layout Tree Unit Requirements + - title: Layout Tree Requirements requirements: - id: Rendering-Model-LayoutTree-Canvas title: >- diff --git a/docs/reqstream/rendering/options.yaml b/docs/reqstream/rendering/options.yaml index 66b426d..9d498ff 100644 --- a/docs/reqstream/rendering/options.yaml +++ b/docs/reqstream/rendering/options.yaml @@ -9,7 +9,7 @@ # declared in rendering.yaml. sections: - - title: Options Unit Requirements + - title: Options Requirements requirements: - id: Rendering-Model-Options-Default title: >- @@ -49,3 +49,22 @@ sections: still receiving a usable default in a single call. tests: - TryGet_UnsetProperty_ReturnsFalseAndDefault + + - id: Rendering-Model-Options-Cascade + title: >- + Overlaying one property holder onto another shall yield a snapshot where the overlaying + holder's explicitly-set values take precedence and the base holder's other explicitly-set + values pass through unchanged, for any property including ones not known in advance. + justification: | + This is the generic primitive the whole per-scope option cascading model depends on: a caller + resolving a nested scope's effective options overlays each level's own overrides onto its + parent's already-resolved snapshot, nearest-ancestor-wins, without needing to know which + properties exist. Testing it once at the model level lets every current and future + scope-cascading consumer (for example HierarchicalLayoutAlgorithm) rely on it without a + bespoke test per property. + tests: + - OverlayOnto_EmptyHolderOntoPopulatedParent_ReturnsParentValuesUnchanged + - OverlayOnto_HolderOverridesProperty_HolderValueWins + - OverlayOnto_ValueOnlyOnParent_PassesThrough + - OverlayOnto_CustomPropertyNotInCoreOptions_IsMerged + - OverlayOnto_NullParent_ThrowsArgumentNullException diff --git a/docs/reqstream/rendering/platform-requirements.yaml b/docs/reqstream/rendering/platform-requirements.yaml index 3281f86..cfddb63 100644 --- a/docs/reqstream/rendering/platform-requirements.yaml +++ b/docs/reqstream/rendering/platform-requirements.yaml @@ -11,7 +11,7 @@ # library builds and executes end to end on that platform/runtime, not merely that a single type loads. sections: - - title: Platform Support + - title: Platform Support Requirements requirements: - id: Rendering-Platform-Windows title: The rendering library shall build and run on Windows platforms. diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 90a0313..7ae2f11 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -195,6 +195,37 @@ options.Set(CoreOptions.EdgeRouting, EdgeRouting.Orthogonal); downward flow swaps each node's width and height before layering so layer spacing follows node height. As with the algorithm, a declaration on the graph takes precedence over one on the options. +## Option cascading + +Every well-known option cascades: it can be set at the free-standing `LayoutOptions`, at a +`LayoutGraph` (a node's or the whole graph's own scope), or at a container node's `Children` graph, and +each scope's own explicit value wins over its parent's, falling back to the option's declared default +only when no scope in the chain sets it. This is nearest-ancestor-wins resolution, not first-set-wins: +a deeper, more specific override always takes precedence over one set higher in the tree. + +```csharp +var options = new LayoutOptions(); +options.Set(CoreOptions.Direction, LayoutFlowDirection.Right); // root default: flow rightward + +var graph = new LayoutGraph(); +var pipeline = graph.AddNode("pipeline", 10, 10); + +// A container's own children graph may override an option for everything nested inside it... +pipeline.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); +var validate = pipeline.Children.AddNode("validate", 80, 40); +var nested = pipeline.Children.AddNode("nested", 10, 10); // sets nothing: inherits Down from pipeline + +// ...and a deeper scope's own override still wins over an inherited ancestor value. +nested.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Right); +``` + +Here `validate`'s siblings inherit `pipeline.Children`'s `Down` override (the nearest ancestor that set +one), while `nested`'s own children flow `Right` again, because `nested.Children`'s own explicit +override is nearer than `pipeline.Children`'s. `HierarchicalLayoutAlgorithm` builds this per-scope +resolution automatically for every nested container using `PropertyHolder.OverlayOnto`; algorithms +invoked directly on a single flat graph resolve the same way against whatever `LayoutOptions` they are +given. + ## Routing connectors among placed boxes When you have already positioned some boxes yourself — for example a free-form or containment layout diff --git a/docs/verification/definition.yaml b/docs/verification/definition.yaml index 1568435..c040157 100644 --- a/docs/verification/definition.yaml +++ b/docs/verification/definition.yaml @@ -5,11 +5,12 @@ resource-path: input-files: - docs/verification/title.txt - docs/verification/introduction.md - - docs/verification/rendering/rendering.md - - docs/verification/rendering-abstractions/rendering-abstractions.md - - docs/verification/rendering-layout/rendering-layout.md - - docs/verification/rendering-svg/rendering-svg.md - - docs/verification/rendering-skia/rendering-skia.md + - docs/verification/rendering.md + - docs/verification/rendering-abstractions.md + - docs/verification/rendering-layout.md + - docs/verification/rendering-svg.md + - docs/verification/rendering-skia.md + - docs/verification/ots.md - docs/verification/ots/buildmark.md - docs/verification/ots/fileassert.md - docs/verification/ots/pandoc.md @@ -20,6 +21,7 @@ input-files: - docs/verification/ots/versionmark.md - docs/verification/ots/weasyprint.md - docs/verification/ots/xunit.md + - docs/verification/ots/skiasharp.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md index 0fb6195..58fd464 100644 --- a/docs/verification/introduction.md +++ b/docs/verification/introduction.md @@ -37,6 +37,7 @@ The following OTS items are also covered: - **ReqStream** — requirements traceability tool - **ReviewMark** — file review enforcement tool - **SarifMark** — SARIF report conversion tool +- **SkiaSharp** — raster graphics library (bitmap drawing and PNG/JPEG/WEBP encoding) - **SonarMark** — SonarCloud quality report tool - **VersionMark** — tool-version documentation tool - **WeasyPrint** — HTML-to-PDF conversion tool @@ -47,6 +48,7 @@ documentation. The following topics are explicitly excluded: - Build pipeline and CI/CD process testing - Infrastructure and hosting environment testing +- Test projects and test infrastructure ## Verification Approach diff --git a/docs/verification/ots.md b/docs/verification/ots.md new file mode 100644 index 0000000..f768915 --- /dev/null +++ b/docs/verification/ots.md @@ -0,0 +1,30 @@ +# OTS Verification Evidence + +This document describes the overall verification strategy for the Off-The-Shelf (OTS) software +items that the Rendering repository depends on (see "OTS Integration Design" in +`docs/design/ots.md` for the corresponding design). Each item's detailed verification evidence is +documented under `docs/verification/ots/{ots-name}.md`. + +## Verification Strategy + +Because most of these OTS items are compliance/build tooling rather than code this repository +authors, they are verified through a combination of self-validation and observable effect rather +than through bespoke test suites: + +- **Self-validation** — most DemaConsulting tools (BuildMark, ReqStream, ReviewMark, SarifMark, + SonarMark, VersionMark) expose a `--validate` mode that runs a built-in self-validation suite and + writes a TRX result (for example, `dotnet reqstream --validate --results + artifacts/reqstream-self-validation.trx`). ReqStream then traces those TRX results against the OTS + requirements in `requirements.yaml`. +- **Generated-output assertion** — tools without a self-validation suite in this pipeline (Pandoc, + WeasyPrint) are verified indirectly: FileAssert asserts that their generated HTML and PDF outputs + exist and contain expected content. +- **Repository test evidence** — xUnit and SkiaSharp are verified by the repository's own passing + tests: xUnit as the framework that discovers, executes, and records them, and SkiaSharp as the + raster library those renderer tests exercise directly (bitmap drawing, text rendering, and image + encoding). +- **Fixed-behavior assertion** — FileAssert itself is verified by tests that assert its own + documented pass/fail behavior on known-good and known-bad inputs. + +Each OTS item's per-item verification document names the concrete evidence (self-validation TRX, +generated artifacts, or repository test methods) and the requirements it satisfies. diff --git a/docs/verification/ots/buildmark.md b/docs/verification/ots/buildmark.md index ec49ced..de5d5b1 100644 --- a/docs/verification/ots/buildmark.md +++ b/docs/verification/ots/buildmark.md @@ -1,15 +1,15 @@ -# BuildMark Verification +## BuildMark Verification This document provides the verification evidence for the `BuildMark` OTS software item. -## Required Functionality +### Required Functionality DemaConsulting.BuildMark queries the GitHub API to capture workflow run details and renders them as a markdown build-notes document included in the release artifacts. It runs as part of the same CI pipeline that produces the TRX test results, so a successful pipeline run is evidence that BuildMark executed without error. -## Verification Approach +### Verification Approach BuildMark is verified by two complementary layers of evidence. First, the CI pipeline runs `buildmark --validate --results artifacts/buildmark-self-validation.trx`, which exercises @@ -22,9 +22,9 @@ artifact, and FileAssert asserts the PDF exists, has content, and contains expec (`WeasyPrint_BuildNotesPdf`). A CI build failure at any step in this chain is evidence that BuildMark did not produce the required output. -## Test Scenarios +### Test Scenarios -### BuildMark_MarkdownReportGeneration +#### BuildMark_MarkdownReportGeneration **Scenario**: A CI pipeline run triggers BuildMark with live GitHub Actions metadata. @@ -33,7 +33,7 @@ in the release artifacts. **Requirement coverage**: `Rendering-OTS-BuildMark`. -### BuildMark_GitIntegration +#### BuildMark_GitIntegration **Scenario**: BuildMark self-validation reads version tags and commits from a mock Git history. @@ -41,7 +41,7 @@ in the release artifacts. **Requirement coverage**: `Rendering-OTS-BuildMark`. -### BuildMark_IssueTracking +#### BuildMark_IssueTracking **Scenario**: BuildMark self-validation processes mock GitHub issues and pull requests. @@ -49,7 +49,7 @@ in the release artifacts. **Requirement coverage**: `Rendering-OTS-BuildMark`. -### BuildMark_KnownIssuesReporting +#### BuildMark_KnownIssuesReporting **Scenario**: BuildMark self-validation includes open bugs in the generated report when requested. @@ -57,7 +57,7 @@ in the release artifacts. **Requirement coverage**: `Rendering-OTS-BuildMark`. -### BuildMark_RulesRouting +#### BuildMark_RulesRouting **Scenario**: BuildMark self-validation assigns mock items to report sections based on label and type rules. @@ -66,7 +66,7 @@ type rules. **Requirement coverage**: `Rendering-OTS-BuildMark`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-BuildMark`**: BuildMark_MarkdownReportGeneration, BuildMark_GitIntegration, BuildMark_IssueTracking, BuildMark_KnownIssuesReporting, BuildMark_RulesRouting diff --git a/docs/verification/ots/fileassert.md b/docs/verification/ots/fileassert.md index 13de839..42f726e 100644 --- a/docs/verification/ots/fileassert.md +++ b/docs/verification/ots/fileassert.md @@ -1,16 +1,16 @@ -# FileAssert Verification +## FileAssert Verification This document provides the verification evidence for the FileAssert OTS software item. Requirements for this OTS item are defined in the FileAssert OTS Software Requirements document. -## Required Functionality +### Required Functionality DemaConsulting.FileAssert validates HTML and PDF documents produced during the build, asserting that each document exists and contains expected content. It provides OTS evidence for Pandoc and WeasyPrint and independently confirms file assertion is functioning. Self-validation proves the tool itself is operational before ReqStream consumes the results. -## Verification Approach +### Verification Approach FileAssert is verified by two complementary layers of evidence. First, the CI pipeline runs `fileassert --validate --results artifacts/fileassert-self-validation.trx` after all documents @@ -24,9 +24,9 @@ incorrect results, causing `reqstream --enforce` to report missing test coverage build. A passing CI build therefore constitutes transitive evidence that FileAssert correctly asserted document content at each stage of the pipeline. -## Test Scenarios +### Test Scenarios -### FileAssert_Results +#### FileAssert_Results **Scenario**: FileAssert self-validation exercises the `--results` option, generating TRX test results containing both passing and failing outcomes. @@ -35,7 +35,7 @@ results containing both passing and failing outcomes. **Requirement coverage**: `Rendering-OTS-FileAssert`. -### FileAssert_Exists +#### FileAssert_Exists **Scenario**: FileAssert self-validation exercises a test configuration using a glob pattern to assert file existence. @@ -44,7 +44,7 @@ assert file existence. **Requirement coverage**: `Rendering-OTS-FileAssert`. -### FileAssert_Contains +#### FileAssert_Contains **Scenario**: FileAssert self-validation exercises a test configuration using a `contains` assertion to verify file content. @@ -53,6 +53,6 @@ to verify file content. **Requirement coverage**: `Rendering-OTS-FileAssert`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-FileAssert`**: FileAssert_Results, FileAssert_Exists, FileAssert_Contains diff --git a/docs/verification/ots/pandoc.md b/docs/verification/ots/pandoc.md index f39a35d..70d2b20 100644 --- a/docs/verification/ots/pandoc.md +++ b/docs/verification/ots/pandoc.md @@ -1,23 +1,23 @@ -# Pandoc Verification +## Pandoc Verification This document provides the verification evidence for the `Pandoc` OTS software item. -## Required Functionality +### Required Functionality DemaConsulting.PandocTool converts Markdown source documents to HTML as part of the documentation build pipeline. FileAssert validates that each generated HTML file exists, contains a valid HTML title element, and includes expected document content. Passing FileAssert assertions for each document type proves Pandoc executed correctly and produced meaningful output. -## Verification Approach +### Verification Approach Pandoc is verified by self-validation evidence from the CI pipeline. Each scenario is a FileAssert assertion that runs after Pandoc converts a specific Markdown document to HTML. A passing pipeline run for all scenarios constitutes evidence that the requirement is satisfied. -## Test Scenarios +### Test Scenarios -### Pandoc_BuildNotesHtml +#### Pandoc_BuildNotesHtml **Scenario**: FileAssert asserts the build-notes HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -26,7 +26,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_CodeQualityHtml +#### Pandoc_CodeQualityHtml **Scenario**: FileAssert asserts the code-quality HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -35,7 +35,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_ReviewPlanHtml +#### Pandoc_ReviewPlanHtml **Scenario**: FileAssert asserts the review plan HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -44,7 +44,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_ReviewReportHtml +#### Pandoc_ReviewReportHtml **Scenario**: FileAssert asserts the review report HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -53,7 +53,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_DesignHtml +#### Pandoc_DesignHtml **Scenario**: FileAssert asserts the design document HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -62,7 +62,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_VerificationHtml +#### Pandoc_VerificationHtml **Scenario**: FileAssert asserts the verification HTML file exists, contains a valid HTML title element, and includes expected verification document content. @@ -71,7 +71,7 @@ element, and includes expected verification document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -### Pandoc_UserGuideHtml +#### Pandoc_UserGuideHtml **Scenario**: FileAssert asserts the user guide HTML file exists, contains a valid HTML title element, and includes expected document content. @@ -80,7 +80,7 @@ element, and includes expected document content. **Requirement coverage**: `Rendering-OTS-Pandoc`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-Pandoc`**: Pandoc_BuildNotesHtml, Pandoc_CodeQualityHtml, Pandoc_ReviewPlanHtml, Pandoc_ReviewReportHtml, Pandoc_DesignHtml, Pandoc_VerificationHtml, Pandoc_UserGuideHtml diff --git a/docs/verification/ots/reqstream.md b/docs/verification/ots/reqstream.md index d5f9510..662ee4f 100644 --- a/docs/verification/ots/reqstream.md +++ b/docs/verification/ots/reqstream.md @@ -1,8 +1,8 @@ -# ReqStream Verification +## ReqStream Verification This document provides the verification evidence for the `ReqStream` OTS software item. -## Required Functionality +### Required Functionality DemaConsulting.ReqStream processes requirements.yaml and the TRX test-result files to produce a requirements report, justifications document, and traceability matrix. When run with `--enforce`, it @@ -10,7 +10,7 @@ exits with a non-zero code if any requirement lacks test evidence, making unprov build-breaking condition. A successful pipeline run with `--enforce` proves all requirements are covered and that ReqStream is functioning. -## Verification Approach +### Verification Approach ReqStream is verified by two complementary layers of evidence. First, the CI pipeline runs `reqstream --validate --results artifacts/reqstream-self-validation.trx`, which exercises @@ -25,9 +25,9 @@ non-zero if any requirement lacks test evidence, which would also fail the build CI build proves ReqStream correctly processed the project's real requirements and found complete test coverage. -## Test Scenarios +### Test Scenarios -### ReqStream_RequirementsProcessing +#### ReqStream_RequirementsProcessing **Scenario**: ReqStream self-validation loads and processes a set of requirements YAML files. @@ -35,7 +35,7 @@ complete test coverage. **Requirement coverage**: `Rendering-OTS-ReqStream`. -### ReqStream_TraceMatrix +#### ReqStream_TraceMatrix **Scenario**: ReqStream self-validation generates a trace matrix from requirements and TRX test results. @@ -44,7 +44,7 @@ results. **Requirement coverage**: `Rendering-OTS-ReqStream`. -### ReqStream_ReportExport +#### ReqStream_ReportExport **Scenario**: ReqStream self-validation exports a requirements report to a markdown file. @@ -52,7 +52,7 @@ results. **Requirement coverage**: `Rendering-OTS-ReqStream`. -### ReqStream_TagsFiltering +#### ReqStream_TagsFiltering **Scenario**: ReqStream self-validation filters requirements by tags. @@ -60,7 +60,7 @@ results. **Requirement coverage**: `Rendering-OTS-ReqStream`. -### ReqStream_EnforcementMode +#### ReqStream_EnforcementMode **Scenario**: ReqStream self-validation exercises enforcement mode where all requirements have test coverage. @@ -69,7 +69,7 @@ coverage. **Requirement coverage**: `Rendering-OTS-ReqStream`. -### ReqStream_Lint +#### ReqStream_Lint **Scenario**: ReqStream self-validation exercises lint mode against a requirements file with deliberate issues. @@ -78,7 +78,7 @@ deliberate issues. **Requirement coverage**: `Rendering-OTS-ReqStream-Lint`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-ReqStream`**: ReqStream_RequirementsProcessing, ReqStream_TraceMatrix, ReqStream_ReportExport, ReqStream_TagsFiltering, ReqStream_EnforcementMode diff --git a/docs/verification/ots/reviewmark.md b/docs/verification/ots/reviewmark.md index dc0855e..7813f52 100644 --- a/docs/verification/ots/reviewmark.md +++ b/docs/verification/ots/reviewmark.md @@ -1,16 +1,16 @@ -# ReviewMark Verification +## ReviewMark Verification This document provides the verification evidence for the ReviewMark OTS software item. Requirements for this OTS item are defined in the ReviewMark OTS Software Requirements document. -## Required Functionality +### Required Functionality DemaConsulting.ReviewMark reads the `.reviewmark.yaml` configuration and the review evidence store to produce a review plan and review report documenting file review coverage and currency. It runs in the same CI pipeline that produces the TRX test results, so a successful pipeline run is evidence that ReviewMark executed without error. -## Verification Approach +### Verification Approach ReviewMark is verified by two complementary layers of evidence. First, the CI pipeline runs `reviewmark --validate --results artifacts/reviewmark-self-validation.trx`, which exercises @@ -24,9 +24,9 @@ WeasyPrint renders both to PDF and FileAssert asserts their content (`WeasyPrint_ReviewPlanPdf`, `WeasyPrint_ReviewReportPdf`). A CI build failure at any step is evidence that ReviewMark did not produce the required review documents. -## Test Scenarios +### Test Scenarios -### ReviewMark_ReviewPlanGeneration +#### ReviewMark_ReviewPlanGeneration **Scenario**: ReviewMark self-validation uses `--definition` and `--plan` to generate a review plan from a test configuration. @@ -35,7 +35,7 @@ from a test configuration. **Requirement coverage**: `Rendering-OTS-ReviewMark`. -### ReviewMark_ReviewReportGeneration +#### ReviewMark_ReviewReportGeneration **Scenario**: ReviewMark self-validation uses `--definition` and `--report` to generate a review report from a test configuration and evidence store. @@ -44,7 +44,7 @@ report from a test configuration and evidence store. **Requirement coverage**: `Rendering-OTS-ReviewMark`. -### ReviewMark_IndexScan +#### ReviewMark_IndexScan **Scenario**: ReviewMark self-validation uses `--index` to scan PDF evidence files and write an `index.json` catalogue. @@ -53,7 +53,7 @@ report from a test configuration and evidence store. **Requirement coverage**: `Rendering-OTS-ReviewMark-IndexScan`. -### ReviewMark_WorkingDirectoryOverride +#### ReviewMark_WorkingDirectoryOverride **Scenario**: ReviewMark self-validation uses `--dir` to override the working directory for file operations. @@ -62,7 +62,7 @@ operations. **Requirement coverage**: `Rendering-OTS-ReviewMark-DirectoryOverride`. -### ReviewMark_Enforce +#### ReviewMark_Enforce **Scenario**: ReviewMark self-validation uses `--enforce` against a configuration with review issues. @@ -71,7 +71,7 @@ issues. **Requirement coverage**: `Rendering-OTS-ReviewMark-Enforce`. -### ReviewMark_Elaborate +#### ReviewMark_Elaborate **Scenario**: ReviewMark self-validation uses `--elaborate` to print a Markdown elaboration of a named review set. @@ -80,7 +80,7 @@ named review set. **Requirement coverage**: `Rendering-OTS-ReviewMark-Elaborate`. -### ReviewMark_Lint +#### ReviewMark_Lint **Scenario**: ReviewMark self-validation uses `--lint` to validate a definition file and report issues. @@ -90,7 +90,7 @@ semantic error in the test definition that contains known errors. **Requirement coverage**: `Rendering-OTS-ReviewMark-Lint`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-ReviewMark`**: ReviewMark_ReviewPlanGeneration, ReviewMark_ReviewReportGeneration - **`Rendering-OTS-ReviewMark-IndexScan`**: ReviewMark_IndexScan diff --git a/docs/verification/ots/sarifmark.md b/docs/verification/ots/sarifmark.md index 9a350c1..bd9ad46 100644 --- a/docs/verification/ots/sarifmark.md +++ b/docs/verification/ots/sarifmark.md @@ -1,16 +1,16 @@ -# SarifMark Verification +## SarifMark Verification This document provides the verification evidence for the SarifMark OTS software item. Requirements for this OTS item are defined in the SarifMark OTS Software Requirements document. -## Required Functionality +### Required Functionality DemaConsulting.SarifMark reads the SARIF output produced by CodeQL code scanning and renders it as a human-readable markdown document included in the release artifacts. It runs in the same CI pipeline that produces the TRX test results, so a successful pipeline run is evidence that SarifMark executed without error. -## Verification Approach +### Verification Approach SarifMark is verified by two complementary layers of evidence. First, the CI pipeline runs `sarifmark --validate --results artifacts/sarifmark-self-validation.trx`, which exercises @@ -24,9 +24,9 @@ renders the result to PDF and FileAssert asserts the PDF contains expected conte (`WeasyPrint_CodeQualityPdf`). A CI build failure at any step is evidence that SarifMark did not produce the required output. -## Test Scenarios +### Test Scenarios -### SarifMark_SarifReading +#### SarifMark_SarifReading **Scenario**: SarifMark is invoked with a CodeQL SARIF results file as input. @@ -34,7 +34,7 @@ not produce the required output. **Requirement coverage**: `Rendering-OTS-SarifMark`. -### SarifMark_MarkdownReportGeneration +#### SarifMark_MarkdownReportGeneration **Scenario**: SarifMark renders the SARIF input as a markdown report included in the release artifacts. @@ -43,7 +43,7 @@ artifacts. **Requirement coverage**: `Rendering-OTS-SarifMark`. -### SarifMark_Enforcement +#### SarifMark_Enforcement **Scenario**: SarifMark self-validation processes a SARIF file containing known issues in enforcement mode. @@ -52,7 +52,7 @@ enforcement mode. **Requirement coverage**: `Rendering-OTS-SarifMark-Enforcement`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-SarifMark`**: SarifMark_SarifReading, SarifMark_MarkdownReportGeneration - **`Rendering-OTS-SarifMark-Enforcement`**: SarifMark_Enforcement diff --git a/docs/verification/ots/skiasharp.md b/docs/verification/ots/skiasharp.md new file mode 100644 index 0000000..d8c897d --- /dev/null +++ b/docs/verification/ots/skiasharp.md @@ -0,0 +1,79 @@ +## SkiaSharp Verification + +This document provides the verification evidence for the SkiaSharp OTS software item. Requirements +for this OTS item are defined in the SkiaSharp OTS Software Requirements document. + +### Required Functionality + +SkiaSharp provides the bitmap canvas, drawing primitives, typeface/text rendering, and image +encoders that `DemaConsulting.Rendering.Skia` uses to rasterize a placed `LayoutTree` into PNG, +JPEG, and WEBP output. Its correct operation is confirmed by the repository's own renderer tests +passing. + +### Verification Approach + +SkiaSharp is a runtime library rather than build/compliance tooling, so — like xUnit — it has no +separate self-validation suite. It is verified indirectly through this repository's own renderer +tests: each scenario below names a real test that exercises a SkiaSharp feature (bitmap drawing, +text rendering, or image encoding) and asserts on the resulting output. A passing test run +constitutes evidence that SkiaSharp performs the required functionality correctly. + +### Test Scenarios + +#### Render_SingleBox_ProducesPngSignature + +**Scenario**: `PngRenderer` uses SkiaSharp's `SKBitmap`/`SKCanvas` to draw a single box and encodes +the result as PNG. + +**Expected**: The output begins with the PNG signature bytes, confirming SkiaSharp's PNG encoder +ran successfully. + +**Requirement coverage**: `Rendering-OTS-SkiaSharp-Rasterize`, `Rendering-OTS-SkiaSharp-EncodePng`. + +#### JpegRenderer_Render_ProducesJpegSignature + +**Scenario**: `JpegRenderer` draws the same layout onto a SkiaSharp bitmap canvas and encodes it as +JPEG. + +**Expected**: The output begins with the JPEG signature bytes, confirming SkiaSharp's JPEG encoder +ran successfully. + +**Requirement coverage**: `Rendering-OTS-SkiaSharp-EncodeJpeg`. + +#### WebpRenderer_Render_ProducesWebpContainerHeader + +**Scenario**: `WebpRenderer` draws the same layout onto a SkiaSharp bitmap canvas and encodes it as +WEBP. + +**Expected**: The output begins with the WEBP container header (`RIFF`/`WEBP`), confirming +SkiaSharp's WEBP encoder ran successfully. + +**Requirement coverage**: `Rendering-OTS-SkiaSharp-EncodeWebp`. + +#### PngRenderer_Render_SingleLine_PixelOnLineIsStrokeColor + +**Scenario**: `PngRenderer` draws a single connector line with SkiaSharp's `SKPaint` stroke drawing. + +**Expected**: The pixel sampled on the drawn line matches the configured stroke color, confirming +SkiaSharp's shape-drawing primitives operate correctly. + +**Requirement coverage**: `Rendering-OTS-SkiaSharp-Rasterize`. + +#### PngRenderer_Render_LabelWithXmlSpecialCharacters_ProducesValidPng + +**Scenario**: `PngRenderer` renders a node label containing special characters using SkiaSharp's +embedded Noto Sans `SKTypeface` and `SKPaint` text drawing. + +**Expected**: A valid, non-empty PNG is produced, confirming SkiaSharp's typeface loading and text +rendering operate correctly regardless of label content. + +**Requirement coverage**: `Rendering-OTS-SkiaSharp-Text`. + +### Requirements Coverage + +- **`Rendering-OTS-SkiaSharp-Rasterize`**: Render_SingleBox_ProducesPngSignature, + PngRenderer_Render_SingleLine_PixelOnLineIsStrokeColor +- **`Rendering-OTS-SkiaSharp-Text`**: PngRenderer_Render_LabelWithXmlSpecialCharacters_ProducesValidPng +- **`Rendering-OTS-SkiaSharp-EncodePng`**: Render_SingleBox_ProducesPngSignature +- **`Rendering-OTS-SkiaSharp-EncodeJpeg`**: JpegRenderer_Render_ProducesJpegSignature +- **`Rendering-OTS-SkiaSharp-EncodeWebp`**: WebpRenderer_Render_ProducesWebpContainerHeader diff --git a/docs/verification/ots/sonarmark.md b/docs/verification/ots/sonarmark.md index e57c0a0..533ac75 100644 --- a/docs/verification/ots/sonarmark.md +++ b/docs/verification/ots/sonarmark.md @@ -1,16 +1,16 @@ -# SonarMark Verification +## SonarMark Verification This document provides the verification evidence for the SonarMark OTS software item. Requirements for this OTS item are defined in the SonarMark OTS Software Requirements document. -## Required Functionality +### Required Functionality DemaConsulting.SonarMark retrieves quality-gate and metrics data from SonarCloud and renders it as a markdown document included in the release artifacts. It runs in the same CI pipeline that produces the TRX test results, so a successful pipeline run is evidence that SonarMark executed without error. -## Verification Approach +### Verification Approach SonarMark is verified by two complementary layers of evidence. First, the CI pipeline runs `sonarmark --validate --results artifacts/sonarmark-self-validation.trx`, which exercises @@ -24,9 +24,9 @@ renders the result to PDF and FileAssert asserts the PDF contains expected conte (`WeasyPrint_CodeQualityPdf`). A CI build failure at any step is evidence that SonarMark did not retrieve and render quality data correctly. -## Test Scenarios +### Test Scenarios -### SonarMark_QualityGateRetrieval +#### SonarMark_QualityGateRetrieval **Scenario**: SonarMark queries the SonarCloud API for quality-gate status. @@ -34,7 +34,7 @@ not retrieve and render quality data correctly. **Requirement coverage**: `Rendering-OTS-SonarMark`. -### SonarMark_IssuesRetrieval +#### SonarMark_IssuesRetrieval **Scenario**: SonarMark queries the SonarCloud API for issues. @@ -42,7 +42,7 @@ not retrieve and render quality data correctly. **Requirement coverage**: `Rendering-OTS-SonarMark`. -### SonarMark_HotSpotsRetrieval +#### SonarMark_HotSpotsRetrieval **Scenario**: SonarMark queries the SonarCloud API for hot spots. @@ -50,7 +50,7 @@ not retrieve and render quality data correctly. **Requirement coverage**: `Rendering-OTS-SonarMark`. -### SonarMark_MarkdownReportGeneration +#### SonarMark_MarkdownReportGeneration **Scenario**: SonarMark renders quality-gate, issues, and hot-spots data as a markdown report. @@ -58,7 +58,7 @@ not retrieve and render quality data correctly. **Requirement coverage**: `Rendering-OTS-SonarMark`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-SonarMark`**: SonarMark_QualityGateRetrieval, SonarMark_IssuesRetrieval, SonarMark_HotSpotsRetrieval, SonarMark_MarkdownReportGeneration diff --git a/docs/verification/ots/versionmark.md b/docs/verification/ots/versionmark.md index 0c758b4..0564e17 100644 --- a/docs/verification/ots/versionmark.md +++ b/docs/verification/ots/versionmark.md @@ -1,15 +1,15 @@ -# VersionMark Verification +## VersionMark Verification This document provides the verification evidence for the `VersionMark` OTS software item. -## Required Functionality +### Required Functionality DemaConsulting.VersionMark reads version metadata for each dotnet tool used in the pipeline and writes a versions markdown document included in the release artifacts. It runs in the same CI pipeline that produces the TRX test results, so a successful pipeline run is evidence that VersionMark executed without error. -## Verification Approach +### Verification Approach VersionMark is verified by two complementary layers of evidence. First, the CI pipeline runs `versionmark --validate --results *.trx` in each build job, exercising VersionMark's built-in @@ -22,9 +22,9 @@ compiled by Pandoc. If VersionMark failed to produce the versions document, the compilation would be incomplete and the subsequent Pandoc step would fail. A CI build failure at any step is evidence that VersionMark did not execute correctly. -## Test Scenarios +### Test Scenarios -### VersionMark_CapturesVersions +#### VersionMark_CapturesVersions **Scenario**: VersionMark reads version metadata for each dotnet tool defined in the pipeline. @@ -32,7 +32,7 @@ at any step is evidence that VersionMark did not execute correctly. **Requirement coverage**: `Rendering-OTS-VersionMark`. -### VersionMark_GeneratesMarkdownReport +#### VersionMark_GeneratesMarkdownReport **Scenario**: VersionMark writes a versions markdown document to the release artifacts. @@ -40,7 +40,7 @@ at any step is evidence that VersionMark did not execute correctly. **Requirement coverage**: `Rendering-OTS-VersionMark`. -### VersionMark_LintPassesForValidConfig +#### VersionMark_LintPassesForValidConfig **Scenario**: VersionMark self-validation exercises lint mode against a valid `.versionmark.yaml` config. @@ -49,7 +49,7 @@ config. **Requirement coverage**: `Rendering-OTS-VersionMark-Lint`. -### VersionMark_LintReportsErrorsForInvalidConfig +#### VersionMark_LintReportsErrorsForInvalidConfig **Scenario**: VersionMark self-validation exercises lint mode against a malformed config with deliberate errors. @@ -58,7 +58,7 @@ deliberate errors. **Requirement coverage**: `Rendering-OTS-VersionMark-Lint`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-VersionMark`**: VersionMark_CapturesVersions, VersionMark_GeneratesMarkdownReport - **`Rendering-OTS-VersionMark-Lint`**: VersionMark_LintPassesForValidConfig, diff --git a/docs/verification/ots/weasyprint.md b/docs/verification/ots/weasyprint.md index 4d496ca..6bed351 100644 --- a/docs/verification/ots/weasyprint.md +++ b/docs/verification/ots/weasyprint.md @@ -1,24 +1,24 @@ -# WeasyPrint Verification +## WeasyPrint Verification This document provides the verification evidence for the WeasyPrint OTS software item. Requirements for this OTS item are defined in the WeasyPrint OTS Software Requirements document. -## Required Functionality +### Required Functionality DemaConsulting.WeasyPrintTool converts HTML documents to PDF as part of the documentation build pipeline. FileAssert validates that each generated PDF file exists, contains at least one page, and includes expected document content in the rendered text. Passing FileAssert assertions for each document type proves WeasyPrint executed correctly and produced meaningful output. -## Verification Approach +### Verification Approach WeasyPrint is verified by self-validation evidence from the CI pipeline. Each scenario is a FileAssert assertion that runs after WeasyPrint converts a specific HTML document to PDF. A passing pipeline run for all scenarios constitutes evidence that the requirement is satisfied. -## Test Scenarios +### Test Scenarios -### WeasyPrint_BuildNotesPdf +#### WeasyPrint_BuildNotesPdf **Scenario**: FileAssert asserts the build-notes PDF file exists, contains at least one page, and includes expected document content. @@ -27,7 +27,7 @@ includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_CodeQualityPdf +#### WeasyPrint_CodeQualityPdf **Scenario**: FileAssert asserts the code-quality PDF file exists, contains at least one page, and includes expected document content. @@ -36,7 +36,7 @@ includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_ReviewPlanPdf +#### WeasyPrint_ReviewPlanPdf **Scenario**: FileAssert asserts the review plan PDF file exists, contains at least one page, and includes expected document content. @@ -45,7 +45,7 @@ includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_ReviewReportPdf +#### WeasyPrint_ReviewReportPdf **Scenario**: FileAssert asserts the review report PDF file exists, contains at least one page, and includes expected document content. @@ -54,7 +54,7 @@ includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_DesignPdf +#### WeasyPrint_DesignPdf **Scenario**: FileAssert asserts the design document PDF file exists, contains at least one page, and includes expected document content. @@ -63,7 +63,7 @@ and includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_VerificationPdf +#### WeasyPrint_VerificationPdf **Scenario**: FileAssert asserts the verification PDF file exists, contains at least one page, and includes expected verification document content. @@ -72,7 +72,7 @@ includes expected verification document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -### WeasyPrint_UserGuidePdf +#### WeasyPrint_UserGuidePdf **Scenario**: FileAssert asserts the user guide PDF file exists, contains at least one page, and includes expected document content. @@ -81,7 +81,7 @@ includes expected document content. **Requirement coverage**: `Rendering-OTS-WeasyPrint`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-WeasyPrint`**: WeasyPrint_BuildNotesPdf, WeasyPrint_CodeQualityPdf, WeasyPrint_ReviewPlanPdf, WeasyPrint_ReviewReportPdf, WeasyPrint_DesignPdf, diff --git a/docs/verification/ots/xunit.md b/docs/verification/ots/xunit.md index 459c04b..49525de 100644 --- a/docs/verification/ots/xunit.md +++ b/docs/verification/ots/xunit.md @@ -1,16 +1,16 @@ -# xUnit Verification +## xUnit Verification This document provides the verification evidence for the xUnit OTS software item. Requirements for this OTS item are defined in the xUnit OTS Software Requirements document. -## Required Functionality +### Required Functionality xUnit v3 (xunit.v3 and xunit.runner.visualstudio) is the unit-testing framework used by the project. It discovers and runs all test methods and writes TRX result files that feed into coverage reporting and requirements traceability. Passing tests confirm the framework is functioning correctly. -## Verification Approach +### Verification Approach Unlike the DemaConsulting tool OTS items, which are verified by their own self-validation evidence, xUnit is the test framework itself. It is therefore verified by this repository's own test methods: @@ -18,9 +18,9 @@ each scenario names a real test — drawn from across the model, layout, and ren xUnit must discover, execute, and record in a TRX result file. A passing pipeline run for all scenarios constitutes evidence that both requirements are satisfied. -## Test Scenarios +### Test Scenarios -### Get_AfterSet_ReturnsStoredValue +#### Get_AfterSet_ReturnsStoredValue **Scenario**: xUnit discovers and runs this property-system test, which stores a value and reads it back. @@ -29,7 +29,7 @@ back. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### Contains_ReflectsExplicitSet +#### Contains_ReflectsExplicitSet **Scenario**: xUnit discovers and runs this property-system test, which checks that `Contains` reflects an explicit set. @@ -38,7 +38,7 @@ reflects an explicit set. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### AddNode_AppendsNodeAndReturnsIt +#### AddNode_AppendsNodeAndReturnsIt **Scenario**: xUnit discovers and runs this layout-graph test, which appends a node and returns it. @@ -46,7 +46,7 @@ reflects an explicit set. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### LayoutTree_Construction_StoresWidthHeightNodes +#### LayoutTree_Construction_StoresWidthHeightNodes **Scenario**: xUnit discovers and runs this model test, which constructs a layout tree and asserts its stored fields. @@ -55,7 +55,7 @@ its stored fields. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### Apply_ChainGraph_PlacesLayeredBoxesAndRoutesEdges +#### Apply_ChainGraph_PlacesLayeredBoxesAndRoutesEdges **Scenario**: xUnit discovers and runs this layout test, which lays out a chain graph with the layered algorithm. @@ -64,7 +64,7 @@ layered algorithm. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### Id_IsLayered +#### Id_IsLayered **Scenario**: xUnit discovers and runs this layout test, which asserts the layered algorithm's stable identifier. @@ -73,7 +73,7 @@ identifier. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### SvgRenderer_Render_SingleBox_ProducesSvgDocument +#### SvgRenderer_Render_SingleBox_ProducesSvgDocument **Scenario**: xUnit discovers and runs this renderer test, which renders a single box to an SVG document. @@ -82,7 +82,7 @@ document. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -### PngRenderer_Render_SingleBox_ProducesNonEmptyOutput +#### PngRenderer_Render_SingleBox_ProducesNonEmptyOutput **Scenario**: xUnit discovers and runs this renderer test, which renders a single box to a non-empty PNG. @@ -91,7 +91,7 @@ PNG. **Requirement coverage**: `Rendering-OTS-xUnit-Execute`, `Rendering-OTS-xUnit-Report`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-OTS-xUnit-Execute`**: Get_AfterSet_ReturnsStoredValue, Contains_ReflectsExplicitSet, AddNode_AppendsNodeAndReturnsIt, LayoutTree_Construction_StoresWidthHeightNodes, diff --git a/docs/verification/rendering-abstractions/rendering-abstractions.md b/docs/verification/rendering-abstractions.md similarity index 70% rename from docs/verification/rendering-abstractions/rendering-abstractions.md rename to docs/verification/rendering-abstractions.md index ea44c4e..9a0122f 100644 --- a/docs/verification/rendering-abstractions/rendering-abstractions.md +++ b/docs/verification/rendering-abstractions.md @@ -13,7 +13,7 @@ per-requirement scenarios live in the unit documents: - Box Metrics Unit Verification - Connector Label Placer Unit Verification -## Verification Strategy +## Verification Approach The abstractions system is verified through in-process unit tests. The contract interfaces (`ILayoutAlgorithm`, `IRenderer`) are exercised through minimal fake implementations that the registry @@ -38,22 +38,30 @@ A verification run passes when every scenario in this system document and in the passes without error or unexpected exception. Any wrong returned value, wrong geometry, or unexpected exception constitutes a failure. -## System Requirements Coverage +## Test Scenarios The system requirements are satisfied through the unit scenarios documented in the per-unit verification files; the representative system-level scenarios are: -- **`Rendering-Abstractions-Extensibility`**: +- **`Rendering-Abstractions-Extensibility-Contracts`**: + LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm, + RendererRegistry_RegisterThenResolve_ReturnsRenderer (see + Rendering Contracts Unit Verification and + Registries Unit Verification) +- **`Rendering-Abstractions-Extensibility-Registries`**: LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm, RendererRegistry_RegisterThenResolve_ReturnsRenderer, RendererRegistry_ResolveByExtension_MatchesAdvertisedExtensions (see Rendering Contracts Unit Verification and Registries Unit Verification) -- **`Rendering-Abstractions-Theming`**: ConnectorApproachZone_SumsStubBendAndClearance, - Themes_HaveExpectedConnectorGeometry (see Theme Unit Verification) -- **`Rendering-Abstractions-SharedGeometry`**: TriangleFamily_HasCanonicalValues, - BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding, - Place_SingleLine_UsesLongestSegmentMidpoint (see - Notation Metrics Unit Verification, - Box Metrics Unit Verification, and - Connector Label Placer Unit Verification) +- **`Rendering-Abstractions-Theming-Model`**: Themes_HaveExpectedConnectorGeometry (see + Theme Unit Verification) +- **`Rendering-Abstractions-Theming-ApproachGeometry`**: + ConnectorApproachZone_SumsStubBendAndClearance (see Theme Unit Verification) +- **`Rendering-Abstractions-SharedGeometry-Notation`**: TriangleFamily_HasCanonicalValues (see + Notation Metrics Unit Verification) +- **`Rendering-Abstractions-SharedGeometry-Box`**: + BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding (see + Box Metrics Unit Verification) +- **`Rendering-Abstractions-SharedGeometry-ConnectorLabel`**: + Place_SingleLine_UsesLongestSegmentMidpoint (see Connector Label Placer Unit Verification) diff --git a/docs/verification/rendering-abstractions/box-metrics.md b/docs/verification/rendering-abstractions/box-metrics.md index ed6151d..6cfdfb7 100644 --- a/docs/verification/rendering-abstractions/box-metrics.md +++ b/docs/verification/rendering-abstractions/box-metrics.md @@ -1,4 +1,4 @@ -# Box Metrics Unit Verification +## Box Metrics Unit Verification Part of the Rendering Abstractions Verification. @@ -9,9 +9,31 @@ verification strategy, test environment, and acceptance criteria are described i system verification document; the test project is `DemaConsulting.Rendering.Abstractions.Tests` (`BoxMetricsTests.cs`). -## Box Metrics Unit Scenarios +### Verification Approach -### Folder-tab height derives from theme +The box-metrics unit is verified with in-process xUnit unit tests that call +`BoxMetrics.FolderTabHeight` and `BoxMetrics.TitleAreaHeight` directly, passing one of the built-in +`Themes` as the theme input. No mocking is used because the helpers are pure static functions of a +`Theme` and a pair of booleans. The tests cover the folder-tab formula and every combination of the +`hasLabel` and `hasKeyword` inputs relevant to the title-area formula, including the empty-title +zero case and the label-plus-keyword additive case. + +### Test Environment + +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test project**: `DemaConsulting.Rendering.Abstractions.Tests` (`BoxMetricsTests.cs`). +- **External dependencies**: none. + +### Acceptance Criteria + +The unit is considered verified when every scenario listed below passes. Each test must return the +documented height in logical pixels; any wrong arithmetic result, unexpected exception, or missing +coverage of the reserved-space formulas constitutes a failure. + +### Test Scenarios + +#### Folder-tab height derives from theme Test `BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding` calls `FolderTabHeight` with the Light theme (body font 12, padding 6) and asserts the height is 24, equal to the body font size plus @@ -19,7 +41,7 @@ two label paddings. **Covers**: `Rendering-Abstractions-BoxMetrics-FolderTabHeight`. -### Title-area height reflects present lines +#### Title-area height reflects present lines Tests `BoxMetrics_TitleAreaHeight_NoLabelNoKeyword_IsZero`, `BoxMetrics_TitleAreaHeight_LabelOnly_ReservesTitleLine`, and @@ -29,8 +51,18 @@ and that a keyword-and-name box reserves padding plus both lines. **Covers**: `Rendering-Abstractions-BoxMetrics-TitleAreaHeight`. -## Requirements Coverage +#### Null theme is rejected + +Tests `BoxMetrics_FolderTabHeight_NullTheme_ThrowsArgumentNullException` and +`BoxMetrics_TitleAreaHeight_NullTheme_ThrowsArgumentNullException` call each helper with a `null` +theme and assert that an `ArgumentNullException` is thrown. + +**Covers**: `Rendering-Abstractions-BoxMetrics-RejectNullTheme`. + +### Requirements Coverage - **`Rendering-Abstractions-BoxMetrics-FolderTabHeight`**: BoxMetrics_FolderTabHeight_DerivesFromThemeBodyFontAndPadding - **`Rendering-Abstractions-BoxMetrics-TitleAreaHeight`**: BoxMetrics_TitleAreaHeight_NoLabelNoKeyword_IsZero, BoxMetrics_TitleAreaHeight_LabelOnly_ReservesTitleLine, BoxMetrics_TitleAreaHeight_LabelAndKeyword_ReservesBothLines +- **`Rendering-Abstractions-BoxMetrics-RejectNullTheme`**: BoxMetrics_FolderTabHeight_NullTheme_ThrowsArgumentNullException, + BoxMetrics_TitleAreaHeight_NullTheme_ThrowsArgumentNullException diff --git a/docs/verification/rendering-abstractions/connector-label-placer.md b/docs/verification/rendering-abstractions/connector-label-placer.md index 5734785..b1fc3fe 100644 --- a/docs/verification/rendering-abstractions/connector-label-placer.md +++ b/docs/verification/rendering-abstractions/connector-label-placer.md @@ -1,4 +1,4 @@ -# Connector Label Placer Unit Verification +## Connector Label Placer Unit Verification Part of the Rendering Abstractions Verification. @@ -9,16 +9,37 @@ The verification strategy, test environment, and acceptance criteria are describ system verification document; the test project is `DemaConsulting.Rendering.Abstractions.Tests` (`ConnectorLabelPlacerTests.cs`). -## Connector Label Placer Unit Scenarios +### Verification Approach -### Unlabelled line is omitted +The connector-label-placer unit is verified with in-process xUnit unit tests that call +`ConnectorLabelPlacer.Place` directly with hand-constructed `LayoutLine` inputs. No mocking or +stubbing is required because the class is a pure, static function of its inputs. The tests +construct `LayoutLine` instances with explicit `MidpointLabel` values and `Waypoints` sequences to +cover the omit-unlabelled, longest-segment-midpoint, and collision-avoidance requirements. + +### Test Environment + +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test project**: `DemaConsulting.Rendering.Abstractions.Tests` (`ConnectorLabelPlacerTests.cs`). +- **External dependencies**: none; the tests use only in-memory geometry inputs. + +### Acceptance Criteria + +The unit is considered verified when every scenario listed below passes. Each test must produce the +documented label position (or omit the line from the result); any wrong coordinate, wrong number of +entries in the returned dictionary, or unexpected exception constitutes a failure. + +### Test Scenarios + +#### Unlabelled line is omitted Test `Place_LineWithoutLabel_IsOmitted` places labels for a single line whose `MidpointLabel` is null and asserts the result is empty. **Covers**: `Rendering-Abstractions-ConnectorLabelPlacer-OmitUnlabelled`. -### Label uses the longest segment midpoint +#### Label uses the longest segment midpoint Test `Place_SingleLine_UsesLongestSegmentMidpoint` places a label for a line with a short vertical stub followed by a long horizontal run and asserts the label lands at the midpoint of the long run @@ -26,7 +47,7 @@ stub followed by a long horizontal run and asserts the label lands at the midpoi **Covers**: `Rendering-Abstractions-ConnectorLabelPlacer-LongestSegment`. -### Colliding labels are separated +#### Colliding labels are separated Test `Place_CollidingLabels_AreSeparated` places labels for two lines whose longest-segment midpoints coincide and asserts the first keeps the preferred midpoint (100, 0) while the second is nudged to a @@ -34,7 +55,7 @@ different Y so the two do not overlap. **Covers**: `Rendering-Abstractions-ConnectorLabelPlacer-AvoidOverlap`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Abstractions-ConnectorLabelPlacer-OmitUnlabelled`**: Place_LineWithoutLabel_IsOmitted - **`Rendering-Abstractions-ConnectorLabelPlacer-LongestSegment`**: Place_SingleLine_UsesLongestSegmentMidpoint diff --git a/docs/verification/rendering-abstractions/notation-metrics.md b/docs/verification/rendering-abstractions/notation-metrics.md index 5257dfe..1849c4b 100644 --- a/docs/verification/rendering-abstractions/notation-metrics.md +++ b/docs/verification/rendering-abstractions/notation-metrics.md @@ -1,4 +1,4 @@ -# Notation Metrics Unit Verification +## Notation Metrics Unit Verification Part of the Rendering Abstractions Verification. @@ -9,41 +9,75 @@ verification strategy, test environment, and acceptance criteria are described i system verification document; the test project is `DemaConsulting.Rendering.Abstractions.Tests` (`NotationMetricsTests.cs`). -## Notation Metrics Unit Scenarios +### Verification Approach -### Triangle marker geometry is canonical +The notation-metrics unit is verified with in-process xUnit unit tests that read the public +constants and call the static helpers on `NotationMetrics` and the `MarkerVertex` record struct +directly. No mocking, dependency injection, or filesystem access is used. Where a helper takes a +`Theme` argument (`RoundedRectRadius(Theme)`), the tests pass one of the built-in `Themes` so the +inputs are deterministic. The tests both assert the canonical constant values (for example the +triangle 10x7 box) and reproduce the historical SVG marker points to prove the shared vertices +match the values shipped previously. -Tests `TriangleFamily_HasCanonicalValues`, `TriangleVertices_ReproduceSvgBoxPoints`, and -`TriangleVertices_Apex_OvershootsEndpoint` assert the triangle-family constants (10x7, refX 9), map -the shared vertices back to the SVG box points "0 0, 10 3.5, 0 7", and confirm the apex overshoots the -endpoint by the documented amount. +### Test Environment -**Covers**: `Rendering-Abstractions-NotationMetrics-TriangleGeometry`. +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test project**: `DemaConsulting.Rendering.Abstractions.Tests` (`NotationMetricsTests.cs`). +- **External dependencies**: none. -### Diamond marker geometry is canonical +### Acceptance Criteria -Tests `Diamond_HasCanonicalValues`, `DiamondVertices_ReproduceSvgBoxPoints`, and -`DiamondVertices_FarPoint_LandsOnEndpoint` assert the diamond constants (14x8, refX 13), map the -shared vertices back to the SVG box points "1 4, 7 0, 13 4, 7 8", and confirm one vertex sits exactly -on the line endpoint. +The unit is considered verified when every scenario listed below passes. Each test must return the +documented constant, derived value, or vertex list; any drift in a canonical value, in a derived +proportion, or in the vertex geometry constitutes a failure. -**Covers**: `Rendering-Abstractions-NotationMetrics-DiamondGeometry`. +### Test Scenarios -### Circle and bar geometry is canonical +#### Triangle marker geometry is canonical + +Tests `TriangleFamily_HasCanonicalValues` and `TriangleVertices_ReproduceSvgBoxPoints` assert the +triangle-family constants (10x7, refX 9) and map the shared vertices back to the SVG box points +"0 0, 10 3.5, 0 7". + +**Covers**: `Rendering-Abstractions-NotationMetrics-TriangleDimensions`. + +#### Triangle apex overshoots the endpoint + +Test `TriangleVertices_Apex_OvershootsEndpoint` confirms the apex overshoots the endpoint by the +documented amount. + +**Covers**: `Rendering-Abstractions-NotationMetrics-TriangleApexOvershoot`. + +#### Diamond marker geometry is canonical + +Tests `Diamond_HasCanonicalValues` and `DiamondVertices_ReproduceSvgBoxPoints` assert the diamond +constants (14x8, refX 13) and map the shared vertices back to the SVG box points "1 4, 7 0, 13 4, 7 8". + +**Covers**: `Rendering-Abstractions-NotationMetrics-DiamondDimensions`. + +#### Diamond far point lands on the endpoint + +Test `DiamondVertices_FarPoint_LandsOnEndpoint` confirms one vertex sits exactly on the line +endpoint. + +**Covers**: `Rendering-Abstractions-NotationMetrics-DiamondFarPointOnEndpoint`. + +#### Circle and bar geometry is canonical Test `CircleAndBar_HaveCanonicalValues` asserts the circle (radius 4, box 10, centre 5, refX 9) and bar (4x12, half-along 2, half 6) constants match the historical markers. **Covers**: `Rendering-Abstractions-NotationMetrics-CircleBarGeometry`. -### Crossbar is a derived fraction +#### Crossbar is a derived fraction Test `Crossbar_IsDerivedFraction` asserts the crossbar fraction is 0.7 and the derived position is 7.0 (0.7 x the marker length). **Covers**: `Rendering-Abstractions-NotationMetrics-Crossbar`. -### Along-line length matches the marker box +#### Along-line length matches the marker box Test `AlongLineLength_MatchesMarkerBox` reads `AlongLineLength` for each `EndMarkerStyle` and asserts each reports its documented length (0 for None, 10 for the triangle family and circle, 14 for @@ -51,25 +85,64 @@ diamonds, 4 for bar). **Covers**: `Rendering-Abstractions-NotationMetrics-AlongLineLength`. -### Box-decoration proportions are documented +#### Port square proportion is documented + +Test `Port_SizeIsTwiceHalfSize` asserts the port square constant and its derivation. + +**Covers**: `Rendering-Abstractions-NotationMetrics-PortSquare`. + +#### Folder-tab proportion is documented + +Test `FolderTab_HasDocumentedConstants` asserts the folder-tab constants. + +**Covers**: `Rendering-Abstractions-NotationMetrics-FolderTab`. + +#### Note-fold proportion is documented + +Test `NoteFold_HasDocumentedConstants` asserts the note-fold constants. + +**Covers**: `Rendering-Abstractions-NotationMetrics-NoteFold`. + +#### Rounded-rectangle corner proportion is documented + +Test `RoundedRectRadius_IsThemeRadiusTimesFactor` asserts the rounded-rectangle corner derivation. + +**Covers**: `Rendering-Abstractions-NotationMetrics-RoundedRectCorner`. + +#### Null theme is rejected + +Test `RoundedRectRadius_NullTheme_ThrowsArgumentNullException` asserts a null `theme` argument is +rejected with an `ArgumentNullException`. + +**Covers**: `Rendering-Abstractions-NotationMetrics-RejectNullTheme`. + +#### Badge proportions are documented + +Test `BadgeFractions_HaveDocumentedValues` asserts the badge fraction constants. + +**Covers**: `Rendering-Abstractions-NotationMetrics-Badge`. + +#### Label-background proportion is documented -Tests `Port_SizeIsTwiceHalfSize`, `FolderTab_HasDocumentedConstants`, -`NoteFold_HasDocumentedConstants`, `RoundedRectRadius_IsThemeRadiusTimesFactor`, -`BadgeFractions_HaveDocumentedValues`, and `LabelBackground_ExtentMatchesInset` assert the port square, -folder-tab, note-fold, rounded-rectangle corner, badge, and label-background constants and derivations. +Test `LabelBackground_ExtentMatchesInset` asserts the label-background extent derivation. -**Covers**: `Rendering-Abstractions-NotationMetrics-BoxDecorations`. +**Covers**: `Rendering-Abstractions-NotationMetrics-LabelBackground`. -## Requirements Coverage +### Requirements Coverage -- **`Rendering-Abstractions-NotationMetrics-TriangleGeometry`**: TriangleFamily_HasCanonicalValues, - TriangleVertices_ReproduceSvgBoxPoints, TriangleVertices_Apex_OvershootsEndpoint -- **`Rendering-Abstractions-NotationMetrics-DiamondGeometry`**: Diamond_HasCanonicalValues, - DiamondVertices_ReproduceSvgBoxPoints, DiamondVertices_FarPoint_LandsOnEndpoint +- **`Rendering-Abstractions-NotationMetrics-TriangleDimensions`**: TriangleFamily_HasCanonicalValues, + TriangleVertices_ReproduceSvgBoxPoints +- **`Rendering-Abstractions-NotationMetrics-TriangleApexOvershoot`**: TriangleVertices_Apex_OvershootsEndpoint +- **`Rendering-Abstractions-NotationMetrics-DiamondDimensions`**: Diamond_HasCanonicalValues, + DiamondVertices_ReproduceSvgBoxPoints +- **`Rendering-Abstractions-NotationMetrics-DiamondFarPointOnEndpoint`**: DiamondVertices_FarPoint_LandsOnEndpoint - **`Rendering-Abstractions-NotationMetrics-CircleBarGeometry`**: CircleAndBar_HaveCanonicalValues - **`Rendering-Abstractions-NotationMetrics-Crossbar`**: Crossbar_IsDerivedFraction - **`Rendering-Abstractions-NotationMetrics-AlongLineLength`**: AlongLineLength_MatchesMarkerBox -- **`Rendering-Abstractions-NotationMetrics-BoxDecorations`**: Port_SizeIsTwiceHalfSize, - FolderTab_HasDocumentedConstants, NoteFold_HasDocumentedConstants, - RoundedRectRadius_IsThemeRadiusTimesFactor, BadgeFractions_HaveDocumentedValues, - LabelBackground_ExtentMatchesInset +- **`Rendering-Abstractions-NotationMetrics-PortSquare`**: Port_SizeIsTwiceHalfSize +- **`Rendering-Abstractions-NotationMetrics-FolderTab`**: FolderTab_HasDocumentedConstants +- **`Rendering-Abstractions-NotationMetrics-NoteFold`**: NoteFold_HasDocumentedConstants +- **`Rendering-Abstractions-NotationMetrics-RoundedRectCorner`**: RoundedRectRadius_IsThemeRadiusTimesFactor +- **`Rendering-Abstractions-NotationMetrics-RejectNullTheme`**: RoundedRectRadius_NullTheme_ThrowsArgumentNullException +- **`Rendering-Abstractions-NotationMetrics-Badge`**: BadgeFractions_HaveDocumentedValues +- **`Rendering-Abstractions-NotationMetrics-LabelBackground`**: LabelBackground_ExtentMatchesInset diff --git a/docs/verification/rendering-abstractions/registries.md b/docs/verification/rendering-abstractions/registries.md index 087528f..65db7e4 100644 --- a/docs/verification/rendering-abstractions/registries.md +++ b/docs/verification/rendering-abstractions/registries.md @@ -1,4 +1,4 @@ -# Registries Unit Verification +## Registries Unit Verification Part of the Rendering Abstractions Verification. @@ -9,23 +9,50 @@ verification strategy, test environment, and acceptance criteria are described i system verification document; the test project is `DemaConsulting.Rendering.Abstractions.Tests` (`RegistryTests.cs`). -## Registries Unit Scenarios +### Verification Approach -### Algorithm registers and resolves by id +The registries unit is verified with in-process xUnit unit tests that exercise +`LayoutAlgorithmRegistry` and `RendererRegistry` directly, without mocks or stubs of the +`Dictionary`-based backing storage. Two minimal test-local fakes provide the interface +implementations under test: `FakeAlgorithm` implements `ILayoutAlgorithm` with a fixed `Id` and an +`Apply` that returns a `LayoutTree` with fixed, distinguishable `PlacedWidth`/`PlacedHeight` values; +`FakeRenderer` implements `IRenderer` with a fixed `MediaType`, `DefaultExtension`, and +`FileExtensions` and a `Render` that writes a fixed, distinguishable `RenderedText` string to the +output stream. Both fakes produce deterministic, checkable output specifically so the registry tests +can assert that the resolved instance's `Apply`/`Render` was actually invoked, not merely that the +registry resolved the correct type. All registry instances are created inside each test so no state +leaks between tests, and the `KeyNotFoundException` behaviour is verified with `Assert.Throws`. + +### Test Environment + +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test project**: `DemaConsulting.Rendering.Abstractions.Tests` (`RegistryTests.cs`). +- **External dependencies**: none; no network, filesystem, or external service is required. + +### Acceptance Criteria + +The unit is considered verified when every scenario listed below passes. Each named test must +return the documented value or throw the documented exception; any wrong lookup result, +unexpected exception, or missing coverage of a listed requirement constitutes a failure. + +### Test Scenarios + +#### Algorithm registers and resolves by id Test `LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm` registers an algorithm, then asserts that `Contains("fake")` is true and `Resolve("fake")` returns the registered algorithm. **Covers**: `Rendering-Abstractions-Registries-ResolveAlgorithm`. -### Renderer registers and resolves by media type +#### Renderer registers and resolves by media type Test `RendererRegistry_RegisterThenResolve_ReturnsRenderer` registers a renderer, then asserts that `Contains("text/plain")` is true and `Resolve("text/plain")` returns the registered renderer. **Covers**: `Rendering-Abstractions-Registries-ResolveRenderer`. -### Renderer resolves by file extension +#### Renderer resolves by file extension Test `RendererRegistry_ResolveByExtension_MatchesAdvertisedExtensions` registers a renderer that advertises `.txt` and `.text`, asserts `ContainsExtension(".txt")`, resolves `.txt`, and resolves @@ -34,14 +61,14 @@ extension lookup ignores case and tolerates an omitted leading dot. **Covers**: `Rendering-Abstractions-Registries-ResolveRendererByExtension`. -### Resolving a missing id throws +#### Resolving a missing id throws Test `LayoutAlgorithmRegistry_ResolveMissing_Throws` resolves an identifier that was never registered and asserts that `Resolve` throws `KeyNotFoundException`. **Covers**: `Rendering-Abstractions-Registries-MissingThrows`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Abstractions-Registries-ResolveAlgorithm`**: LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm - **`Rendering-Abstractions-Registries-ResolveRenderer`**: RendererRegistry_RegisterThenResolve_ReturnsRenderer diff --git a/docs/verification/rendering-abstractions/rendering-contracts.md b/docs/verification/rendering-abstractions/rendering-contracts.md index 9de4a8d..68220c0 100644 --- a/docs/verification/rendering-abstractions/rendering-contracts.md +++ b/docs/verification/rendering-abstractions/rendering-contracts.md @@ -1,4 +1,4 @@ -# Rendering Contracts Unit Verification +## Rendering Contracts Unit Verification Part of the Rendering Abstractions Verification. @@ -10,22 +10,56 @@ system verification document; the primary test project is `DemaConsulting.Rendering.Abstractions.Tests` (`RegistryTests.cs`). Concrete renderer extension coverage also appears in `DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`). -## Rendering Contracts Unit Scenarios +### Verification Approach + +The rendering-contracts unit is verified indirectly through the registry tests: because +`ILayoutAlgorithm` and `IRenderer` are pure abstractions with no behaviour, their identity members +(`Id`, `MediaType`, `DefaultExtension`, `FileExtensions`) are exercised by round-tripping them +through the two registries. The tests use test-local `FakeAlgorithm` and `FakeRenderer` +implementations for that round-trip, and additionally exercise the concrete `PngRenderer` to prove +that a real renderer honours the extension-advertising portion of the contract. `RenderOptions` +and `RenderOutput` are `sealed record` types whose behaviour is limited to their compiler-generated +members and needs no dedicated test scenarios beyond compilation. + +### Test Environment + +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test projects**: `DemaConsulting.Rendering.Abstractions.Tests` (`RegistryTests.cs`) and + `DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`) for concrete-renderer + contract coverage. +- **External dependencies**: none. + +### Acceptance Criteria + +The unit is considered verified when every scenario listed below passes. Each test must return the +documented identity member value; any missing member on a resolved instance or any mismatch between +`DefaultExtension` and `FileExtensions` constitutes a failure. + +### Test Scenarios The contract interfaces carry no behavior of their own; their identity members are verified through the fake implementations registered in the registry tests. `FakeAlgorithm` implements `ILayoutAlgorithm` (returning `Id` "fake") and `FakeRenderer` implements `IRenderer` (returning `MediaType` "text/plain", `DefaultExtension` ".txt", and `FileExtensions` ".txt" and ".text"). -### Algorithm contract identity is exercised +#### Algorithm identifier is exercised Test `LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm` registers a `FakeAlgorithm`, resolves it, and reads the resolved algorithm's `Id`, asserting it is "fake" and thereby exercising `ILayoutAlgorithm.Id`. -**Covers**: `Rendering-Abstractions-Contracts-Algorithm`. +**Covers**: `Rendering-Abstractions-Contracts-AlgorithmIdentifier`. -### Renderer contract identity is exercised +#### Algorithm apply contract is exercised + +Test `LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm` registers and resolves a +`FakeAlgorithm` implementing `ILayoutAlgorithm.Apply`, exercising the contract's apply operation +through the registry round-trip. + +**Covers**: `Rendering-Abstractions-Contracts-AlgorithmApply`. + +#### Renderer identity is exercised Test `RendererRegistry_RegisterThenResolve_ReturnsRenderer` registers a `FakeRenderer`, resolves it, and reads the resolved renderer's `MediaType`, asserting it is "text/plain" and thereby exercising @@ -35,10 +69,20 @@ Test `PngRenderer_FileExtensions_ContainsDefault` constructs the concrete PNG re that its advertised `FileExtensions` contains its `DefaultExtension`, proving concrete renderers honor the extension-advertising part of the `IRenderer` contract. -**Covers**: `Rendering-Abstractions-Contracts-Renderer`. +**Covers**: `Rendering-Abstractions-Contracts-RendererIdentity`. + +#### Renderer write contract is exercised + +Test `RendererRegistry_RegisterThenResolve_ReturnsRenderer` registers and resolves a `FakeRenderer` +implementing `IRenderer.Render`, exercising the contract's stream-write operation through the +registry round-trip. + +**Covers**: `Rendering-Abstractions-Contracts-RendererWrite`. -## Requirements Coverage +### Requirements Coverage -- **`Rendering-Abstractions-Contracts-Algorithm`**: LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm -- **`Rendering-Abstractions-Contracts-Renderer`**: RendererRegistry_RegisterThenResolve_ReturnsRenderer, +- **`Rendering-Abstractions-Contracts-AlgorithmIdentifier`**: LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm +- **`Rendering-Abstractions-Contracts-AlgorithmApply`**: LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm +- **`Rendering-Abstractions-Contracts-RendererIdentity`**: RendererRegistry_RegisterThenResolve_ReturnsRenderer, PngRenderer_FileExtensions_ContainsDefault +- **`Rendering-Abstractions-Contracts-RendererWrite`**: RendererRegistry_RegisterThenResolve_ReturnsRenderer diff --git a/docs/verification/rendering-abstractions/theme.md b/docs/verification/rendering-abstractions/theme.md index ca86e5f..2ff55d2 100644 --- a/docs/verification/rendering-abstractions/theme.md +++ b/docs/verification/rendering-abstractions/theme.md @@ -1,4 +1,4 @@ -# Theme Unit Verification +## Theme Unit Verification Part of the Rendering Abstractions Verification. @@ -9,16 +9,37 @@ strategy, test environment, and acceptance criteria are described in the system verification document; the test project is `DemaConsulting.Rendering.Abstractions.Tests` (`ThemeTests.cs`). -## Theme Unit Scenarios +### Verification Approach -### Approach zone sums stub, bend, and clearance +The theme unit is verified with in-process xUnit unit tests that read properties of the built-in +`Themes.Light`, `Themes.Dark`, and `Themes.Print` instances and call `Theme.ConnectorApproachZone` +directly. No mocking is used because `Theme` is an immutable `sealed record`. The tests operate on +the shipped built-in themes as the representative inputs; caller-constructed `Theme` instances +share the same value-type semantics and require no separate scenarios. + +### Test Environment + +- **Framework**: xUnit v3 running under the .NET SDK on `net8.0`, `net9.0`, and `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and by the CI pipeline. +- **Test project**: `DemaConsulting.Rendering.Abstractions.Tests` (`ThemeTests.cs`). +- **External dependencies**: none. + +### Acceptance Criteria + +The unit is considered verified when every scenario listed below passes. Each test must produce the +documented arithmetic result (for `ConnectorApproachZone`) or the documented built-in geometry +values; any drift in a built-in theme value or in the approach-zone formula constitutes a failure. + +### Test Scenarios + +#### Approach zone sums stub, bend, and clearance Test `ConnectorApproachZone_SumsStubBendAndClearance` calls `ConnectorApproachZone(10.0)` on the Light theme (stub 8, bend radius 4) and asserts the result is 22.0. **Covers**: `Rendering-Abstractions-Theme-ApproachZone`. -### Built-in themes carry expected geometry +#### Built-in themes carry expected geometry Test `Themes_HaveExpectedConnectorGeometry` reads the connector stub and bend radius of the Light, Dark, and Print themes and asserts Light and Dark carry stub 8 and bend radius 4 while Print carries @@ -26,7 +47,7 @@ stub 6 and bend radius 0. **Covers**: `Rendering-Abstractions-Theme-BuiltInGeometry`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Abstractions-Theme-ApproachZone`**: ConnectorApproachZone_SumsStubBendAndClearance - **`Rendering-Abstractions-Theme-BuiltInGeometry`**: Themes_HaveExpectedConnectorGeometry diff --git a/docs/verification/rendering-layout/rendering-layout.md b/docs/verification/rendering-layout.md similarity index 98% rename from docs/verification/rendering-layout/rendering-layout.md rename to docs/verification/rendering-layout.md index ec34788..1db36dc 100644 --- a/docs/verification/rendering-layout/rendering-layout.md +++ b/docs/verification/rendering-layout.md @@ -13,7 +13,7 @@ linked documents below; this file maps only the Rendering.Layout system requirem - DefaultLayout Unit Verification - LayeredLayoutAlgorithm Unit Verification -## Verification Strategy +## Verification Approach Rendering.Layout is verified through deterministic in-process xUnit tests over synthetic layout graphs and geometry inputs. System coverage is established by representative scenarios for each public system @@ -38,7 +38,7 @@ linked subsystem and unit verification documents passes without unexpected excep waypoint, layer assignment, algorithm identity, registry resolution, containment region, or unsupported input behavior constitutes a failure. -## System Requirements Coverage +## Test Scenarios The system requirements are satisfied through subsystem and unit scenarios documented in the linked verification files; representative system-level coverage is: diff --git a/docs/verification/rendering-layout/connector-router.md b/docs/verification/rendering-layout/connector-router.md index 45a3eff..a9f1862 100644 --- a/docs/verification/rendering-layout/connector-router.md +++ b/docs/verification/rendering-layout/connector-router.md @@ -1,10 +1,35 @@ -# ConnectorRouter Unit Verification +## ConnectorRouter Unit Verification Part of the Rendering Layout Verification. This document maps the connector-router unit requirements to named test scenarios. -## ConnectorRouter Unit Scenarios +### Verification Approach + +`ConnectorRouter` is a stateless static class, so verification is by direct xUnit unit tests that +call `ConnectorRouter.Route` on synthetic `LayoutBox` inputs. No mocks or fakes are used: the tests +exercise the real anchor-selection, obstacle-set construction, and dispatch code paths, letting the +integration with the internal `OrthogonalEdgeRouter` engine run end-to-end so anchor geometry, +obstacle avoidance, and produced `LayoutLine` styling are all observed on real outputs. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/ConnectorRouterTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutBox` list and `Connection` records. +- **Isolation**: each test builds its own inputs; the class under test holds no static state, so + tests are order-independent. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-ConnectorRouter-*` requirement. Any wrong anchor +face, waypoint that enters a non-endpoint obstacle interior, incorrect line styling passthrough, +out-of-order batch result, or non-argument-null exception for invalid input constitutes a failure. + +### Test Scenarios - **Anchors face each other** (`Rendering-Layout-ConnectorRouter-AnchorsFaceEachOther`): `Route_TargetToTheRight_AnchorsFaceEachOther` and `Route_TargetBelow_AnchorsFaceEachOther` confirm @@ -25,7 +50,7 @@ This document maps the connector-router unit requirements to named test scenario `Route_NullConnections_Throws`, `Route_NullOptions_Throws`, and `Route_NullConnection_Throws` confirm null arguments are rejected with an argument-null error. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-ConnectorRouter-AnchorsFaceEachOther`**: Route_TargetToTheRight_AnchorsFaceEachOther, Route_TargetBelow_AnchorsFaceEachOther diff --git a/docs/verification/rendering-layout/containment-layout-algorithm.md b/docs/verification/rendering-layout/containment-layout-algorithm.md index d2be112..928c447 100644 --- a/docs/verification/rendering-layout/containment-layout-algorithm.md +++ b/docs/verification/rendering-layout/containment-layout-algorithm.md @@ -1,10 +1,35 @@ -# ContainmentLayoutAlgorithm Unit Verification +## ContainmentLayoutAlgorithm Unit Verification Part of the Rendering Layout Verification. This document maps the containment-layout-algorithm unit requirements to named test scenarios. -## ContainmentLayoutAlgorithm Scenarios +### Verification Approach + +`ContainmentLayoutAlgorithm` is verified by direct xUnit unit tests that call `Apply(graph, +options)` on synthetic `LayoutGraph` inputs. The tests use the real `ContainmentPacker` and +`ConnectorRouter` collaborators (no mocks) so identity, packing, routing (including obstacle +avoidance), and validation are all observed on production code paths. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutGraph` and `LayoutOptions` instances. +- **Isolation**: each test builds its own inputs; the algorithm is stateless between calls. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-ContainmentAlgorithm-*` requirement. Any drift in +the stable identifier (`"containment"`), in the one-box-per-node placement contract, in +edge-per-input-edge routing with styling passthrough, in obstacle avoidance for intervening boxes, +in empty-graph handling, in skipping of out-of-graph edges, or in the argument-null validation +behavior constitutes a failure. + +### Test Scenarios - **Identity** (`Rendering-Layout-ContainmentAlgorithm-Identity`): `Id_IsContainment` asserts the algorithm reports the stable `"containment"` identifier. @@ -25,8 +50,13 @@ This document maps the containment-layout-algorithm unit requirements to named t node is skipped rather than routed. - **Validation** (`Rendering-Layout-ContainmentAlgorithm-Validation`): `Apply_NullGraph_Throws` and `Apply_NullOptions_Throws` confirm null arguments are rejected with an argument-null error. +- **Honors scope edge routing** (`Rendering-Layout-ContainmentAlgorithm-HonorsScopeEdgeRouting`): + `Apply_EdgeRoutingOverrideOnGraphScope_IsHonored` confirms an explicit `CoreOptions.EdgeRouting` + override carried on the graph itself is honored (routing still succeeds) even when the supplied + options declares no routing style, mirroring `LayeredLayoutAlgorithm`'s graph-then-options resolution + of `CoreOptions.Direction`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-ContainmentAlgorithm-Identity`**: Id_IsContainment @@ -42,3 +72,5 @@ This document maps the containment-layout-algorithm unit requirements to named t Apply_EdgeReferencingOutOfGraphNode_IsSkipped - **`Rendering-Layout-ContainmentAlgorithm-Validation`**: Apply_NullGraph_Throws, Apply_NullOptions_Throws +- **`Rendering-Layout-ContainmentAlgorithm-HonorsScopeEdgeRouting`**: + Apply_EdgeRoutingOverrideOnGraphScope_IsHonored diff --git a/docs/verification/rendering-layout/containment-layout.md b/docs/verification/rendering-layout/containment-layout.md index 1b53466..f1c3047 100644 --- a/docs/verification/rendering-layout/containment-layout.md +++ b/docs/verification/rendering-layout/containment-layout.md @@ -1,10 +1,35 @@ -# ContainmentLayout Unit Verification +## ContainmentLayout Unit Verification Part of the Rendering Layout Verification. This document maps the containment-layout unit requirements to named test scenarios. -## ContainmentLayout Scenarios +### Verification Approach + +`ContainmentLayout` (the public containment packing entry point) is verified by direct xUnit unit +tests that call `Pack(children, options)` on synthetic child lists. No mocks are used; the tests +exercise the real packing algorithm and its underlying `ContainmentPacker` engine end-to-end so +ordering, wrapping, region sizing, and field preservation are all observed on production output. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory child list and `ContainmentOptions` instance. +- **Isolation**: each test builds its own inputs; the unit is stateless between calls. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-ContainmentLayout-*` requirement. Any overlap +between packed children, child positioned outside the reported region, wrong wrapping behavior for +overflowing or oversized children, lost non-position field (label, depth, shape, compartments, +nested children, keyword), drift in default gaps or padding, or non-argument-null exception for +invalid input constitutes a failure. + +### Test Scenarios - **Order preserved** (`Rendering-Layout-ContainmentLayout-Order`): `Pack_ItemsFitInRow_PreservesOrderLeftToRight` confirms the packed children keep their input order, @@ -32,7 +57,7 @@ This document maps the containment-layout unit requirements to named test scenar `Pack_NullOptions_Throws`, and `Pack_NullChildElement_Throws` confirm null arguments are rejected with an argument-null error. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-ContainmentLayout-Order`**: Pack_ItemsFitInRow_PreservesOrderLeftToRight diff --git a/docs/verification/rendering-layout/default-layout.md b/docs/verification/rendering-layout/default-layout.md index bb7d56d..094be99 100644 --- a/docs/verification/rendering-layout/default-layout.md +++ b/docs/verification/rendering-layout/default-layout.md @@ -1,10 +1,39 @@ -# DefaultLayout Unit Verification +## DefaultLayout Unit Verification Part of the Rendering Layout Verification. This document maps the default-layout unit requirements to named test scenarios. -## DefaultLayout Scenarios +### Verification Approach + +`LayoutAlgorithms` (registry factory) and `LayoutEngine` (facade) are stateless static units, so +verification is by direct xUnit unit tests. The tests use the real bundled algorithms +(`LayeredLayoutAlgorithm`, `ContainmentLayoutAlgorithm`, `HierarchicalLayoutAlgorithm`) rather than +mocks so that resolution, flat-graph equivalence, and nested composition are all observed on the +production code paths. A subset of tests deep-compares the facade's `LayoutTree` output with the +leaf algorithm applied directly to guarantee byte-identical behavior for existing callers. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Projects**: `test/DemaConsulting.Rendering.Layout.Tests/LayoutAlgorithmsTests.cs` and + `test/DemaConsulting.Rendering.Layout.Tests/LayoutEngineTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutGraph` and `LayoutOptions` instances. +- **Isolation**: each test creates fresh inputs and (where applicable) its own registry via + `LayoutAlgorithms.CreateDefaultRegistry()`, which returns an independent instance per call. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-DefaultRegistry-*` and +`Rendering-Layout-LayoutEngine-*` requirement. Any drift in the bundled algorithm set, in the +default algorithm identifier (`"hierarchical"`), in the resolution precedence (graph over options +over default), in the flat-graph equivalence guarantee, in nested composition, or in the argument- +null validation behavior constitutes a failure. + +### Test Scenarios - **Bundled algorithms** (`Rendering-Layout-DefaultRegistry-BundledAlgorithms`): `CreateDefaultRegistry_ResolvesLayeredAlgorithm`, `CreateDefaultRegistry_ResolvesContainmentAlgorithm`, @@ -36,7 +65,7 @@ This document maps the default-layout unit requirements to named test scenarios. `Layout_NullOptions_Throws`, and `Layout_NullRegistry_Throws` confirm null arguments are rejected with an argument-null error. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-DefaultRegistry-BundledAlgorithms`**: CreateDefaultRegistry_ResolvesLayeredAlgorithm, CreateDefaultRegistry_ResolvesContainmentAlgorithm, diff --git a/docs/verification/rendering-layout/edge-routing-option.md b/docs/verification/rendering-layout/edge-routing-option.md index fe7518a..9d529cc 100644 --- a/docs/verification/rendering-layout/edge-routing-option.md +++ b/docs/verification/rendering-layout/edge-routing-option.md @@ -1,25 +1,52 @@ -# EdgeRouting Option Unit Verification +## EdgeRouting Option Unit Verification Part of the Rendering Layout Verification. This document maps the edge-routing-option behavior requirements to named test scenarios. -## EdgeRouting Option Unit Scenarios +### Verification Approach + +The unit is a configuration realization with no methods of its own, so verification is by direct +xUnit unit tests that (1) read the declared property key metadata, (2) exercise the open property +system round-trip on `IPropertyHolder` scopes, and (3) read the defaults of the Layout-side +`ConnectorRouteOptions` record. No mocks or fakes are used — the tests operate on real +`LayoutGraph`, `LayoutOptions`, and `ConnectorRouteOptions` instances so the observed behavior is +the same as production callers see. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory property holders and options records. +- **Isolation**: each test builds its own inputs; nothing in the option or route-options types + carries static state between tests. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-EdgeRouting-*` requirement. Any change to the +default value, property-key identifier, per-scope selection round-trip, or `ConnectorRouteOptions` +defaults constitutes a failure. + +### Test Scenarios - **Per-scope selection** (`Rendering-Layout-EdgeRouting-Selection`): - `CoreOptions_EdgeRouting_DefaultsToOrthogonal` and `CoreOptions_EdgeRouting_HasStableId` confirm the + `CoreOptions_EdgeRouting_DefaultValue_IsOrthogonal` and + `CoreOptions_EdgeRouting_Id_IsStableDottedIdentifier` confirm the `rendering.edgerouting` key defaults to `Orthogonal` and carries the ELK-flavored id; - `CoreOptions_EdgeRouting_SelectablePerScope` sets and reads the style back through the property - system, and `CoreOptions_EdgeRouting_UnsetReturnsDefault` confirms an unset scope falls back to the - orthogonal default. + `CoreOptions_EdgeRouting_SetThenGet_RoundTripsValue` sets and reads the style back through the property + system, and `CoreOptions_EdgeRouting_UnsetHolder_ReturnsOrthogonalDefault` confirms an unset scope falls + back to the orthogonal default. - **Route-option defaults** (`Rendering-Layout-EdgeRouting-Defaults`): - `ConnectorRouteOptions_Defaults_AreOrthogonalWithTwelvePixelClearance` confirms the default style is + `ConnectorRouteOptions_Constructor_Defaults_AreOrthogonalWithTwelvePixelClearance` confirms the default style is orthogonal, the default clearance is twelve logical pixels, and the clearance is caller-overridable. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-EdgeRouting-Selection`**: - CoreOptions_EdgeRouting_DefaultsToOrthogonal, CoreOptions_EdgeRouting_HasStableId, - CoreOptions_EdgeRouting_SelectablePerScope, CoreOptions_EdgeRouting_UnsetReturnsDefault + CoreOptions_EdgeRouting_DefaultValue_IsOrthogonal, CoreOptions_EdgeRouting_Id_IsStableDottedIdentifier, + CoreOptions_EdgeRouting_SetThenGet_RoundTripsValue, CoreOptions_EdgeRouting_UnsetHolder_ReturnsOrthogonalDefault - **`Rendering-Layout-EdgeRouting-Defaults`**: - ConnectorRouteOptions_Defaults_AreOrthogonalWithTwelvePixelClearance + ConnectorRouteOptions_Constructor_Defaults_AreOrthogonalWithTwelvePixelClearance diff --git a/docs/verification/rendering-layout/engine/containment-packer.md b/docs/verification/rendering-layout/engine/containment-packer.md index b5acea1..32ea78c 100644 --- a/docs/verification/rendering-layout/engine/containment-packer.md +++ b/docs/verification/rendering-layout/engine/containment-packer.md @@ -1,10 +1,34 @@ -# ContainmentPacker Unit Verification +### ContainmentPacker Unit Verification Part of the Rendering Layout Verification. This document maps the ContainmentPacker unit requirements to named test scenarios. -## ContainmentPacker Unit Scenarios +#### Verification Approach + +`ContainmentPacker` is a stateless static engine, so verification is by direct xUnit unit tests +that call `Pack` on synthetic size lists. No mocks are used; the tests observe the real shelf- +packing algorithm end-to-end so single-row placement, wrapping, non-overlap, oversized-item +handling, and the reported region are all measured on production output. + +#### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/Engine/ContainmentPackerTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory item size list. +- **Isolation**: each test builds its own inputs; the engine holds no state between calls. + +#### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-ContainmentPacker-*` requirement. Any overlap +between packed rectangles, rectangle placed outside the reported region, wrong wrapping for +overflowing items, wrong solitary placement for oversized items, incorrect empty-input region, or +wrong single-item origin constitutes a failure. + +#### Test Scenarios - **Single row** (`Rendering-Layout-ContainmentPacker-SingleRow`): `Pack_ItemsFitInRow_ShareSameRow` asserts items that fit the width budget share one row, left to right. @@ -24,7 +48,7 @@ This document maps the ContainmentPacker unit requirements to named test scenari `Pack_SingleItem_PositionsAtPaddingOrigin` confirms a lone item lands at the padding origin with the region sized to wrap it. -## Requirements Coverage +#### Requirements Coverage - **`Rendering-Layout-ContainmentPacker-SingleRow`**: Pack_ItemsFitInRow_ShareSameRow diff --git a/docs/verification/rendering-layout/engine/engine.md b/docs/verification/rendering-layout/engine/engine.md index c8b26c2..111d30e 100644 --- a/docs/verification/rendering-layout/engine/engine.md +++ b/docs/verification/rendering-layout/engine/engine.md @@ -1,4 +1,4 @@ -# Engine Subsystem Verification +### Engine Subsystem Verification Part of the Rendering Layout Verification. @@ -10,7 +10,35 @@ Unit scenarios live in the Engine unit verification documents: - InterconnectionLayoutEngine Unit Verification - Layered Pipeline Unit Verification -## Engine Subsystem Coverage +#### Verification Approach + +The Engine subsystem is verified through its unit-level xUnit tests: each engine +(`OrthogonalEdgeRouter`, `ContainmentPacker`, `InterconnectionLayoutEngine`, `LayeredPipeline`) is +exercised directly on real inputs, and the assembled layered pipeline is byte-compared to a legacy +oracle. Because the engines have no shared runtime state, the subsystem does not require additional +integration mocks at its boundary — the unit tests together constitute the subsystem verification. +See each linked Unit Verification for engine-specific approach and mocking notes. + +#### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Projects**: `test/DemaConsulting.Rendering.Layout.Tests/Engine/` (per-engine tests and their + `Layered/` subfolder for pipeline stages). +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory geometric inputs. Deterministic seeds are used where the underlying engines run over + pseudo-random graphs. +- **Isolation**: each engine is stateless; tests are order-independent. + +#### Acceptance Criteria + +A verification run passes when every subsystem-requirement scenario below is covered by passing +unit tests in the linked documents, and no engine regression is observed against the layered +pipeline's legacy-oracle byte-identity suite. Any regression in orthogonal routing, +containment packing, interconnection placement, or the assembled staged pipeline constitutes a +failure. + +#### Test Scenarios - **`Rendering-Layout-OrthogonalRouting`**: Route_NoObstacles_ProducesOrthogonalPath, diff --git a/docs/verification/rendering-layout/engine/interconnection-layout-engine.md b/docs/verification/rendering-layout/engine/interconnection-layout-engine.md index 30b5316..daabac0 100644 --- a/docs/verification/rendering-layout/engine/interconnection-layout-engine.md +++ b/docs/verification/rendering-layout/engine/interconnection-layout-engine.md @@ -1,10 +1,36 @@ -# InterconnectionLayoutEngine Unit Verification +### InterconnectionLayoutEngine Unit Verification Part of the Rendering Layout Verification. This document maps the InterconnectionLayoutEngine unit requirements to named test scenarios. -## InterconnectionLayoutEngine Unit Scenarios +#### Verification Approach + +`InterconnectionLayoutEngine` is verified by direct xUnit unit tests that call +`Place(nodes, edges, direction)` on synthetic `IReadOnlyList` / +`IReadOnlyList` inputs. The tests run the real underlying `LayeredPipeline` end-to-end +(no stage is mocked) so layering, non-overlap, dummy-node handling, waypoint emission, direction +handling, validation, and determinism are all measured on production output. + +#### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutGraph` and options. +- **Isolation**: each test builds its own inputs; the engine and pipeline are stateless between + calls. + +#### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-InterconnectionEngine-*` requirement. Any drift +in monotonic layer indices along a chain, overlapping placed rectangles, dummy nodes leaking into +the returned rectangle list, missing or non-orthogonal edge waypoints, direction handling +transposing incorrectly, or non-deterministic geometry for identical input constitutes a failure. + +#### Test Scenarios - **Layering** (`Rendering-Layout-InterconnectionEngine-Layering`): `Place_LinearChain_MonotonicLayerAssignment` asserts monotonic layer indices along a chain, and @@ -16,19 +42,34 @@ This document maps the InterconnectionLayoutEngine unit requirements to named te `Place_LongEdge_RectCountEqualsInputNodeCount` confirms dummy nodes are excluded from the returned rectangles, and `Place_LongEdge_RoutesViaDummyNodesWithinBounds` confirms long edges route through them within bounds. -- **Waypoints** (`Rendering-Layout-InterconnectionEngine-Waypoints`): +- **Waypoints / acyclic mapping** (`Rendering-Layout-InterconnectionEngine-Waypoints-AcyclicMapping`): + `Place_CyclicGraph_ReversesBackEdgeAndProducesWaypoint` confirms cycle breaking reverses a back + edge, reports an acyclic edge set, and returns one waypoint list index-aligned with that retained + edge set. +- **Waypoints / straight span-one** (`Rendering-Layout-InterconnectionEngine-Waypoints-StraightSpanOne`): `Place_SingleEdge_ProducesStraightTwoWaypointPath` confirms a span-one edge produces a straight - two-waypoint path; `Place_LongEdge_RoutesViaDummyNodesWithinBounds` confirms a long edge's route. -- **Direction** (`Rendering-Layout-InterconnectionEngine-Direction`): - `Place_DownDirection_TransposesTotalsRelativeToRight` confirms a downward flow transposes the layout - relative to the default rightward flow — the same chain becomes taller than it is wide with its boxes - stacked in increasing Y — exercising the direction-aware total-dimension computation. -- **Deterministic** (`Rendering-Layout-InterconnectionEngine-Deterministic`): the layering scenarios - `Place_LinearChain_MonotonicLayerAssignment` and - `Place_WorkstationTopology_CorrectLayersAndNoOverlap` assert fixed, reproducible geometry for fixed - input. - -## Requirements Coverage + two-waypoint path. +- **Waypoints / long-edge routing** (`Rendering-Layout-InterconnectionEngine-Waypoints-LongEdgeRouting`): + `Place_LongEdge_RoutesViaDummyNodesWithinBounds` confirms a long edge routes through dummy nodes + and stays within the layout bounds. +- **Direction / requested flow** (`Rendering-Layout-InterconnectionEngine-Direction-RequestedFlow`): + `Place_DownDirection_TransposesTotalsRelativeToRight` confirms a downward flow stacks the chain in + increasing Y rather than the default rightward orientation. +- **Direction / transposed totals** (`Rendering-Layout-InterconnectionEngine-Direction-TransposedTotals`): + `Place_DownDirection_TransposesTotalsRelativeToRight` confirms the downward flow reports + transposed total dimensions relative to the default rightward flow. +- **Direction / defaults to right** (`Rendering-Layout-InterconnectionEngine-Direction-DefaultsToRight`): + `Place_DefaultDirection_MatchesRightFlow` confirms omitting the optional direction argument + produces the same geometry and totals as explicitly requesting `LayoutDirection.Right`. +- **Deterministic** (`Rendering-Layout-InterconnectionEngine-Deterministic`): + `Place_RepeatedInvocation_ProducesIdenticalGeometry` confirms identical input produces identical + rects, totals, retained acyclic edges, layer indices, and connector waypoints on repeated + invocation. +- **Validation** (supporting the documented error contract): + `Place_NullNodes_ThrowsArgumentNullException` and `Place_NullEdges_ThrowsArgumentNullException` + confirm null inputs are rejected before any pipeline stage executes. + +#### Requirements Coverage - **`Rendering-Layout-InterconnectionEngine-Layering`**: Place_LinearChain_MonotonicLayerAssignment, Place_WorkstationTopology_CorrectLayersAndNoOverlap @@ -36,9 +77,17 @@ This document maps the InterconnectionLayoutEngine unit requirements to named te Place_WorkstationTopology_CorrectLayersAndNoOverlap - **`Rendering-Layout-InterconnectionEngine-DummyNodes`**: Place_LongEdge_RectCountEqualsInputNodeCount, Place_LongEdge_RoutesViaDummyNodesWithinBounds -- **`Rendering-Layout-InterconnectionEngine-Waypoints`**: - Place_SingleEdge_ProducesStraightTwoWaypointPath, Place_LongEdge_RoutesViaDummyNodesWithinBounds -- **`Rendering-Layout-InterconnectionEngine-Direction`**: +- **`Rendering-Layout-InterconnectionEngine-Waypoints-AcyclicMapping`**: + Place_CyclicGraph_ReversesBackEdgeAndProducesWaypoint +- **`Rendering-Layout-InterconnectionEngine-Waypoints-StraightSpanOne`**: + Place_SingleEdge_ProducesStraightTwoWaypointPath +- **`Rendering-Layout-InterconnectionEngine-Waypoints-LongEdgeRouting`**: + Place_LongEdge_RoutesViaDummyNodesWithinBounds +- **`Rendering-Layout-InterconnectionEngine-Direction-RequestedFlow`**: Place_DownDirection_TransposesTotalsRelativeToRight +- **`Rendering-Layout-InterconnectionEngine-Direction-TransposedTotals`**: + Place_DownDirection_TransposesTotalsRelativeToRight +- **`Rendering-Layout-InterconnectionEngine-Direction-DefaultsToRight`**: + Place_DefaultDirection_MatchesRightFlow - **`Rendering-Layout-InterconnectionEngine-Deterministic`**: - Place_LinearChain_MonotonicLayerAssignment, Place_WorkstationTopology_CorrectLayersAndNoOverlap + Place_RepeatedInvocation_ProducesIdenticalGeometry diff --git a/docs/verification/rendering-layout/engine/layered-pipeline.md b/docs/verification/rendering-layout/engine/layered-pipeline.md index c564a96..44211bf 100644 --- a/docs/verification/rendering-layout/engine/layered-pipeline.md +++ b/docs/verification/rendering-layout/engine/layered-pipeline.md @@ -1,4 +1,4 @@ -# Layered Pipeline Unit Verification +### Layered Pipeline Unit Verification Part of the Rendering Layout Verification. @@ -8,6 +8,40 @@ The pipeline stages are exercised individually, and the assembled pipeline is ch equivalence with the legacy oracle. Dependencies are real (each stage operates on a `LayeredGraph`); nothing is mocked. +#### Verification Approach + +The `LayeredPipeline` unit — the ELK-style staged Sugiyama pipeline over a `LayeredGraph` — is +verified by direct xUnit unit tests at three levels: (1) each stage is exercised in isolation on a +`LayeredGraph` (cycle breaker, layer assigner, long-edge splitter, crossing minimizer, +Brandes-Köpf placer, port distributor, orthogonal router, long-edge joiner, component packer, +axis transform); (2) the assembled `LayeredLayoutPipeline` is byte-compared to a legacy oracle on +random and named topologies; and (3) input-validation tests confirm null/unsupported inputs are +rejected. No stage is mocked — real `LayeredGraph` instances flow through every check. + +#### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Projects**: `test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/` (per-stage tests) and + the pipeline-level tests in the same test project. +- **Dependencies**: no external services, files, or network access. Random-graph and named-topology + tests use deterministic seeds so byte-identity checks are reproducible. The legacy oracle is a + reference implementation compiled into the test project. +- **Isolation**: each test builds its own `LayeredGraph`; the pipeline and stages are stateless + between calls. + +#### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-LayeredPipeline-*` requirement. Any drift in +per-stage geometry, in byte-identity with the legacy oracle on the random and named topologies, +in supported hierarchy handling (recursive input must throw), in flow directions, in orthogonal +waypoint shape, in back-edge approach behavior, in component packing determinism, in shared +`LayeredGraph` state validation, or in `LayeredLayoutPipeline` input validation constitutes a +failure. + +#### Test Scenarios + - **Staged pipeline** (`Rendering-Layout-LayeredPipeline-StagedPipeline`): `LayeredLayoutPipeline_RunDefaultStages_ChainGraph_PopulatesWaypointsWithoutThrowing` runs the full default sequence and confirms waypoints are populated; `Pipeline_MatchesLegacyOracle_OnRandomGraphs` @@ -73,6 +107,12 @@ nothing is mocked. `ComponentPacker_Apply_Waypoints_TranslatedWithComponent`, `ComponentPacker_Apply_EmptyGraph_IsNoOp`, `ComponentPacker_Apply_SingleComponent_ParallelAndSelfEdges_ProducesAlignedWaypoints`, and `ComponentPacker_Apply_MultiComponent_ParallelAndSelfEdges_MergesAlignedWaypoints`. +- **Component packing propagates back-edge approach** + (`Rendering-Layout-LayeredPipeline-PackedComponentsBackEdgeApproach`): + `ComponentPacker_Apply_MultiComponent_PropagatesBackEdgeEntryApproach` builds two disconnected + triangle components, each with a short cycle producing a long back edge, and confirms the routed + back-edge corridor reflects the parent graph's configured `BackEdgeEntryApproach` in every packed + component instead of always reverting to the class default. - **Shared state** (`Rendering-Layout-LayeredPipeline-SharedState`): `LayeredGraph_Constructor_ValidInput_StoresNodesEdgesDirectionAndCount`, `LayeredGraph_Constructor_NullNodes_ThrowsArgumentNullException`, and @@ -81,7 +121,7 @@ nothing is mocked. `LayeredLayoutPipeline_AddStage_NullStage_ThrowsArgumentNullException` and `LayeredLayoutPipeline_Run_NullGraph_ThrowsArgumentNullException`. -## Requirements Coverage +#### Requirements Coverage - **`Rendering-Layout-LayeredPipeline-StagedPipeline`**: LayeredLayoutPipeline_RunDefaultStages_ChainGraph_PopulatesWaypointsWithoutThrowing, @@ -133,6 +173,8 @@ nothing is mocked. ComponentPacker_Apply_Waypoints_TranslatedWithComponent, ComponentPacker_Apply_EmptyGraph_IsNoOp, ComponentPacker_Apply_SingleComponent_ParallelAndSelfEdges_ProducesAlignedWaypoints, ComponentPacker_Apply_MultiComponent_ParallelAndSelfEdges_MergesAlignedWaypoints +- **`Rendering-Layout-LayeredPipeline-PackedComponentsBackEdgeApproach`**: + ComponentPacker_Apply_MultiComponent_PropagatesBackEdgeEntryApproach - **`Rendering-Layout-LayeredPipeline-SharedState`**: LayeredGraph_Constructor_ValidInput_StoresNodesEdgesDirectionAndCount, LayeredGraph_Constructor_NullNodes_ThrowsArgumentNullException, diff --git a/docs/verification/rendering-layout/engine/orthogonal-edge-router.md b/docs/verification/rendering-layout/engine/orthogonal-edge-router.md index 01d3890..8c8c726 100644 --- a/docs/verification/rendering-layout/engine/orthogonal-edge-router.md +++ b/docs/verification/rendering-layout/engine/orthogonal-edge-router.md @@ -1,10 +1,35 @@ -# OrthogonalEdgeRouter Unit Verification +### OrthogonalEdgeRouter Unit Verification Part of the Rendering Layout Verification. This document maps the OrthogonalEdgeRouter unit requirements to named test scenarios. -## OrthogonalEdgeRouter Unit Scenarios +#### Verification Approach + +`OrthogonalEdgeRouter` is a stateless static engine, so verification is by direct xUnit unit tests +that call `Route` and `RouteWithStatus` on synthetic anchor / obstacle inputs. No mocks are used; +the tests observe the real grid construction, A\*-style search, clearance-retry ladder, and cost- +band biasing so orthogonality, obstacle avoidance, clearance, perpendicular ends, and crossing +status are all measured on production output. + +#### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/Engine/OrthogonalEdgeRouterTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `Point2D` anchors and `Rect` obstacle lists. +- **Isolation**: each test builds its own inputs; the engine holds no state between calls. + +#### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-OrthogonalEdgeRouter-*` requirement. Any drift in +the orthogonality of returned waypoints, entry of a segment into an obstacle interior when a clean +route exists, failure to keep the requested clearance, non-perpendicular exit or entry at a +supplied side, incorrect `Crossed` flag, or lost cost-band bias constitutes a failure. + +#### Test Scenarios - **Orthogonal path** (`Rendering-Layout-OrthogonalEdgeRouter-Orthogonal`): `Route_NoObstacles_ProducesOrthogonalPath` asserts consecutive waypoints share an X or Y @@ -28,7 +53,7 @@ This document maps the OrthogonalEdgeRouter unit requirements to named test scen `RouteWithStatus_HighwayBand_PrefersBandedDetour` confirms the router prefers a discounted band over an equal-length alternative. -## Requirements Coverage +#### Requirements Coverage - **`Rendering-Layout-OrthogonalEdgeRouter-Orthogonal`**: Route_NoObstacles_ProducesOrthogonalPath, Route_AlignedEndpoints_ProducesStraightLine diff --git a/docs/verification/rendering-layout/hierarchical-layout-algorithm.md b/docs/verification/rendering-layout/hierarchical-layout-algorithm.md index 1f713a6..125903a 100644 --- a/docs/verification/rendering-layout/hierarchical-layout-algorithm.md +++ b/docs/verification/rendering-layout/hierarchical-layout-algorithm.md @@ -1,10 +1,40 @@ -# HierarchicalLayoutAlgorithm Unit Verification +## HierarchicalLayoutAlgorithm Unit Verification Part of the Rendering Layout Verification. This document maps the hierarchical-layout-algorithm unit requirements to named test scenarios. -## HierarchicalLayoutAlgorithm Scenarios +### Verification Approach + +`HierarchicalLayoutAlgorithm` is verified by direct xUnit unit tests that call `Apply(graph, +options)` on synthetic flat and nested `LayoutGraph` inputs. The tests use the real bundled leaf +algorithms (`LayeredLayoutAlgorithm`, `ContainmentLayoutAlgorithm`) rather than mocks, so +per-scope resolution, container sizing, cross-container routing through `ConnectorRouter`, and the +flat-graph equivalence fast path are all observed on production code paths. A subset of tests +deep-compares the algorithm's `LayoutTree` output against the leaf algorithm applied directly on +hundreds of pseudo-random flat graphs to prove byte-identical behavior. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutGraph` and `LayoutOptions` instances. Deterministic pseudo-random graphs use + fixed seeds so the equivalence suite is reproducible. +- **Isolation**: each test builds its own inputs; the algorithm and its default registry are + stateless between calls. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-HierarchicalLayout-*` requirement. Any drift in +the stable identifier (`"hierarchical"`), in the flat-graph byte-equivalence guarantee, in +container sizing (padding, title band), in per-scope algorithm resolution, in cross-container +LCA edge routing, or in the argument-null validation behavior constitutes a failure. Mutation of +input node sizes also constitutes a failure. + +### Test Scenarios - **Identity** (`Rendering-Layout-HierarchicalLayout-Identity`): `Id_IsHierarchical` asserts the engine reports the stable `"hierarchical"` identifier. @@ -32,13 +62,31 @@ This document maps the hierarchical-layout-algorithm unit requirements to named `Apply_CrossContainerEdge_RoutesAroundInterveningContainer` confirms an edge between children of different sibling containers is routed at the owning scope and no routed segment passes through the intervening container's interior. +- **Propagates direction** (`Rendering-Layout-HierarchicalLayout-CascadesOptions`): + `Apply_ContainerWithDirectionOverride_HonorsNestedDirection` sets `CoreOptions.Direction` on a + container's own children graph (while the top-level options select the default direction) and + confirms the nested chain is laid out with the container's own override — stacking vertically. + `Apply_ThreeLevelDirectionCascade_InheritsThroughUnsetMiddleLevel` confirms a direction override set + two levels up cascades through an intermediate container that sets nothing of its own, reaching a + third-level leaf chain. `Apply_ThreeLevelDirectionCascade_MidLevelOverrideTakesPrecedence` confirms a + deeper, explicit override wins over an inherited ancestor value rather than the ancestor's value + winning because it was set first or higher in the tree. + `Apply_ThreeLevelEdgeRoutingCascade_ReachesEveryLeafAlgorithmCall` uses a recording leaf-algorithm + test double to confirm the cascaded effective options snapshot — including `CoreOptions.EdgeRouting` + and an arbitrary custom marker property proving generality — actually reaches every leaf-algorithm + invocation across three levels of nesting, with the deepest scope's own override winning over an + inherited ancestor value. +- **Honors scope edge routing** (`Rendering-Layout-HierarchicalLayout-HonorsScopeEdgeRouting`): + `Apply_CrossContainerEdge_HonorsScopeEdgeRoutingOverride` confirms a cross-container edge is routed + using the owning scope's own cascaded `CoreOptions.EdgeRouting` override rather than the root + options. - **Validation** (`Rendering-Layout-HierarchicalLayout-ValidatesGraph`, `Rendering-Layout-HierarchicalLayout-ValidatesOptions`, `Rendering-Layout-HierarchicalLayout-ValidatesRegistry`): `Apply_NullGraph_Throws`, `Apply_NullOptions_Throws`, and `Constructor_NullRegistry_Throws` confirm a null graph, null options, and null registry are each rejected with an argument-null error. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-HierarchicalLayout-Identity`**: Id_IsHierarchical @@ -52,6 +100,13 @@ This document maps the hierarchical-layout-algorithm unit requirements to named Apply_TwoLevelNesting_SizesContainerAndNestsChildrenAbsolutely, Apply_CompoundGraph_DoesNotMutateInputNodeSizes - **`Rendering-Layout-HierarchicalLayout-CrossContainerEdge`**: Apply_CrossContainerEdge_RoutesAroundInterveningContainer +- **`Rendering-Layout-HierarchicalLayout-CascadesOptions`**: + Apply_ContainerWithDirectionOverride_HonorsNestedDirection, + Apply_ThreeLevelDirectionCascade_InheritsThroughUnsetMiddleLevel, + Apply_ThreeLevelDirectionCascade_MidLevelOverrideTakesPrecedence, + Apply_ThreeLevelEdgeRoutingCascade_ReachesEveryLeafAlgorithmCall +- **`Rendering-Layout-HierarchicalLayout-HonorsScopeEdgeRouting`**: + Apply_CrossContainerEdge_HonorsScopeEdgeRoutingOverride - **`Rendering-Layout-HierarchicalLayout-ValidatesGraph`**: Apply_NullGraph_Throws - **`Rendering-Layout-HierarchicalLayout-ValidatesOptions`**: diff --git a/docs/verification/rendering-layout/layered-layout-algorithm.md b/docs/verification/rendering-layout/layered-layout-algorithm.md index c70ed44..e2be349 100644 --- a/docs/verification/rendering-layout/layered-layout-algorithm.md +++ b/docs/verification/rendering-layout/layered-layout-algorithm.md @@ -1,9 +1,35 @@ -# LayeredLayoutAlgorithm Unit Verification +## LayeredLayoutAlgorithm Unit Verification Part of the Rendering Layout Verification. This document maps the layered-layout-algorithm unit requirements to named test scenarios. +### Verification Approach + +`LayeredLayoutAlgorithm` is verified by direct xUnit unit tests that call `Apply(graph, options)` on +synthetic `LayoutGraph` inputs. The tests exercise the real ELK-style layered pipeline end-to-end +(no stage is mocked) so identity, placement, routing, and direction handling are all observed on +production code paths. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Layout.Tests/LayeredLayoutAlgorithmTests.cs`. +- **Dependencies**: no external services, files, or network access; every test constructs its own + in-memory `LayoutGraph` and `LayoutOptions` instances. +- **Isolation**: each test builds its own inputs; the algorithm is stateless between calls. + +### Acceptance Criteria + +A verification run passes when every named scenario below asserts without unexpected exception, and +the referenced tests cover each `Rendering-Layout-LayeredAlgorithm-*` requirement. Any drift in the +stable identifier (`"layered"`), in the one-box-per-node / one-line-per-edge placement contract, in +empty-graph handling, in flow-direction honoring, or in the argument-null validation behavior +constitutes a failure. + +### Test Scenarios + - **Identity** (`Rendering-Layout-LayeredAlgorithm-Identity`): `Id_IsLayered` asserts the algorithm reports the stable `"layered"` identifier. - **Places and routes** (`Rendering-Layout-LayeredAlgorithm-PlacesAndRoutes`): @@ -22,7 +48,7 @@ This document maps the layered-layout-algorithm unit requirements to named test a null graph argument is rejected with an argument-null error, and `Apply_NullOptions_Throws` confirms a null options argument is likewise rejected with an argument-null error. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Layout-LayeredAlgorithm-Identity`**: Id_IsLayered diff --git a/docs/verification/rendering-skia/rendering-skia.md b/docs/verification/rendering-skia.md similarity index 97% rename from docs/verification/rendering-skia/rendering-skia.md rename to docs/verification/rendering-skia.md index c9d9cd2..0fd833d 100644 --- a/docs/verification/rendering-skia/rendering-skia.md +++ b/docs/verification/rendering-skia.md @@ -11,7 +11,7 @@ unit documents: - JpegRenderer Unit Verification - WebpRenderer Unit Verification -## Verification Strategy +## Verification Approach The Skia renderers are verified by unit tests that render small layout trees and assert on the produced bytes. Format is checked by the encoded file signature (PNG signature, JPEG Start-Of-Image marker, WEBP @@ -35,7 +35,7 @@ A verification run passes when every scenario in this system document and in the passes without error or unexpected exception. Any wrong encoded signature, media type, file extension, pixel colour, marker geometry, or unexpected exception constitutes a failure. -## System Requirements Coverage +## Test Scenarios The system requirement is satisfied through the unit scenarios documented in the per-unit verification files; the representative system-level scenarios are: diff --git a/docs/verification/rendering-skia/jpeg-renderer.md b/docs/verification/rendering-skia/jpeg-renderer.md index a2d3d0e..e2f3381 100644 --- a/docs/verification/rendering-skia/jpeg-renderer.md +++ b/docs/verification/rendering-skia/jpeg-renderer.md @@ -1,17 +1,43 @@ -# JpegRenderer Unit Verification +## JpegRenderer Unit Verification Part of the Rendering.Skia Verification. This document describes the verification design for the `JpegRenderer` unit of the `DemaConsulting.Rendering.Skia` system. It maps every JpegRenderer unit requirement to at least one -named test scenario so a reviewer can confirm coverage without reading the test code. The verification -strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is -`DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`). +named test scenario so a reviewer can confirm coverage without reading the test code. -## JpegRenderer Unit Scenarios +### JpegRenderer Verification Approach -### JPEG output has the expected signature and metadata +The `JpegRenderer` unit is verified with xUnit v3 unit tests in +`DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`) that render a small placed +`LayoutTree` into a `MemoryStream` and inspect the resulting bytes. The renderer is exercised as a +real `IRenderer` — no dependencies are mocked or stubbed — because it is a pure, deterministic +transformation from layout to encoded image bytes. Injected dependencies (the theme carried by +`RenderOptions`) are supplied by the test as concrete instances. Verification checks two things: +that the produced byte stream is a real JPEG (Start-Of-Image marker) and that the renderer +advertises the expected media type and file extensions for registry resolution. + +### JpegRenderer Test Environment + +- **Framework**: xUnit v3. +- **Target frameworks**: `net8.0`, `net9.0`, `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and the CI pipeline (see + _Rendering.Skia Verification_ for the system-level environment). +- **External services / files**: none. Rendering is deterministic and writes to a `MemoryStream`. +- **Native assets**: the SkiaSharp platform-specific native asset packages provided by + `DemaConsulting.Rendering.Skia`'s NuGet references must be available at test time; no other + setup beyond `dotnet restore` is required. + +### JpegRenderer Acceptance Criteria + +A verification run passes when the JPEG scenarios below execute without an unexpected exception, +the encoded output begins with the JPEG Start-Of-Image marker, and the renderer reports the +`image/jpeg` media type plus the `.jpg` default and `.jpeg` alternate file extensions. Any wrong +byte signature, wrong metadata value, or unexpected exception constitutes a failure. + +### JpegRenderer Unit Scenarios + +#### JPEG output has the expected signature and metadata Test `JpegRenderer_Render_ProducesJpegSignature` renders a sample layout and asserts that output begins with the JPEG Start-Of-Image marker and that the renderer reports the `image/jpeg` media type, the @@ -19,6 +45,6 @@ with the JPEG Start-Of-Image marker and that the renderer reports the `image/jpe **Covers**: `Rendering-Skia-JpegRenderer-EmitsJpeg`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Skia-JpegRenderer-EmitsJpeg`**: JpegRenderer_Render_ProducesJpegSignature diff --git a/docs/verification/rendering-skia/png-renderer.md b/docs/verification/rendering-skia/png-renderer.md index 98d63e8..9f296b0 100644 --- a/docs/verification/rendering-skia/png-renderer.md +++ b/docs/verification/rendering-skia/png-renderer.md @@ -1,18 +1,45 @@ -# PngRenderer Unit Verification +## PngRenderer Unit Verification Part of the Rendering.Skia Verification. This document describes the verification design for the `PngRenderer` unit of the `DemaConsulting.Rendering.Skia` system. It maps every PngRenderer unit requirement to at least one -named test scenario so a reviewer can confirm coverage without reading the test code. The verification -strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is -`DemaConsulting.Rendering.Skia.Tests` (`PngRendererTests.cs`, `PngRendererPortedTests.cs`, -`SkiaFormatRendererTests.cs`). +named test scenario so a reviewer can confirm coverage without reading the test code. -## PngRenderer Unit Scenarios +### PngRenderer Verification Approach -### PNG output has the expected signature and metadata +The `PngRenderer` unit is verified with xUnit v3 unit tests in +`DemaConsulting.Rendering.Skia.Tests` (`PngRendererTests.cs`, `PngRendererPortedTests.cs`, and +`SkiaFormatRendererTests.cs`) that render placed `LayoutTree` instances into `MemoryStream`s and +inspect the resulting bytes. The renderer is used as a real `IRenderer` — no dependencies are +mocked or stubbed — because it is a pure, deterministic transformation from layout to encoded +image bytes. Verification checks that the output begins with the PNG file signature and that the +renderer's advertised `MediaType`, `DefaultExtension`, and `FileExtensions` match the PNG contract. +Because `PngRenderer` inherits the whole drawing path from `SkiaRasterRenderer`, the PNG pixel +tests in the `PngRendererPortedTests` suite implicitly cross-verify that the base rasterizer works +correctly with the PNG encoder. + +### PngRenderer Test Environment + +- **Framework**: xUnit v3. +- **Target frameworks**: `net8.0`, `net9.0`, `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and the CI pipeline (see + _Rendering.Skia Verification_ for the system-level environment). +- **External services / files**: none. Rendering is deterministic and writes to a `MemoryStream`. +- **Native assets**: the SkiaSharp platform-specific native asset packages provided by + `DemaConsulting.Rendering.Skia`'s NuGet references must be available at test time; no other + setup beyond `dotnet restore` is required. + +### PngRenderer Acceptance Criteria + +A verification run passes when the PNG scenarios below execute without an unexpected exception, +the encoded output begins with the PNG signature, and the renderer reports the `image/png` media +type with `.png` in its advertised file extensions. Any wrong byte signature, wrong metadata +value, or unexpected exception constitutes a failure. + +### PngRenderer Unit Scenarios + +#### PNG output has the expected signature and metadata Tests `Render_SingleBox_ProducesPngSignature`, `PngRenderer_Render_EmptyTree_WritesPngSignature`, and `PngRenderer_FileExtensions_ContainsDefault` render sample layouts and assert that output begins with @@ -21,7 +48,7 @@ is included in its advertised extensions. **Covers**: `Rendering-Skia-PngRenderer-EmitsPng`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Skia-PngRenderer-EmitsPng`**: Render_SingleBox_ProducesPngSignature, PngRenderer_Render_EmptyTree_WritesPngSignature, PngRenderer_FileExtensions_ContainsDefault diff --git a/docs/verification/rendering-skia/skia-raster-renderer.md b/docs/verification/rendering-skia/skia-raster-renderer.md index 82162c0..c768f97 100644 --- a/docs/verification/rendering-skia/skia-raster-renderer.md +++ b/docs/verification/rendering-skia/skia-raster-renderer.md @@ -1,17 +1,52 @@ -# SkiaRasterRenderer Unit Verification +## SkiaRasterRenderer Unit Verification Part of the Rendering.Skia Verification. This document describes the verification design for the `SkiaRasterRenderer` unit of the `DemaConsulting.Rendering.Skia` system. It maps every SkiaRasterRenderer unit requirement to at least -one named test scenario so a reviewer can confirm coverage without reading the test code. The -verification strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is -`DemaConsulting.Rendering.Skia.Tests` (`PngRendererPortedTests.cs`, `PngEndMarkerTests.cs`). +one named test scenario so a reviewer can confirm coverage without reading the test code. -## SkiaRasterRenderer Unit Scenarios +### SkiaRasterRenderer Verification Approach -### Draws all layout-tree node kinds +Because `SkiaRasterRenderer` is `abstract`, it is exercised indirectly through its concrete +`PngRenderer` subclass in `DemaConsulting.Rendering.Skia.Tests` (`PngRendererPortedTests.cs` and +`PngEndMarkerTests.cs`). Each test renders a small placed `LayoutTree` into a `MemoryStream` and +either decodes the resulting PNG to inspect specific pixel colours or asserts on geometric +properties of the encoded output. Using PNG as the transport is deliberate: PNG is lossless so +individual pixels can be compared to theme colours without lossy-encoding error. No dependencies +are mocked; `RenderOptions`, `Theme`, `NotationMetrics`, `BoxMetrics`, and `ConnectorLabelPlacer` +are supplied as real instances (typically from `Themes.Light` / `Themes.Dark`). + +Coverage is organized around four concerns: + +1. Drawing of every supported `LayoutTree` node kind (boxes, lines, ports, badges, lifelines, + activations, bands, labels, deeply nested boxes). +2. Theme-driven fill selection from `Theme.DepthFillColors` and `Theme.BackgroundColor`. +3. Connector end-marker geometry derived from `NotationMetrics`. +4. Robust handling of the degenerate empty-tree case (minimum one-by-one bitmap). + +### SkiaRasterRenderer Test Environment + +- **Framework**: xUnit v3. +- **Target frameworks**: `net8.0`, `net9.0`, `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and the CI pipeline (see + _Rendering.Skia Verification_ for the system-level environment). +- **External services / files**: none. Rendering is deterministic and writes to a `MemoryStream`; + pixel inspection uses `SKBitmap.Decode` on the in-memory PNG bytes. +- **Native assets**: the SkiaSharp platform-specific native asset packages provided by + `DemaConsulting.Rendering.Skia`'s NuGet references must be available at test time; no other + setup beyond `dotnet restore` is required. + +### SkiaRasterRenderer Acceptance Criteria + +A verification run passes when every scenario below executes without an unexpected exception, +each inspected pixel or geometric measurement equals its expected theme colour or notation-metric +value, and the encoded byte stream is a valid PNG. Any wrong pixel colour, wrong marker geometry, +stack overflow, or unexpected exception constitutes a failure. + +### SkiaRasterRenderer Unit Scenarios + +#### Draws all layout-tree node kinds Tests `PngRenderer_Render_SingleBox_ProducesNonEmptyOutput`, `PngRenderer_Render_BackgroundIsThemeBackground`, `PngRenderer_Render_SingleLine_PixelOnLineIsStrokeColor`, @@ -32,7 +67,7 @@ whose background is not white, proves the fill is genuinely theme-driven rather **Covers**: `Rendering-Skia-SkiaRasterRenderer-DrawsLayoutTree`. -### Theme colours drive fills +#### Theme colours drive fills Tests `PngRenderer_Render_SingleBox_FillColorMatchesTheme`, `PngRenderer_Render_SingleBox_DepthOneUsesSecondColor`, and @@ -41,7 +76,7 @@ pixels equal the theme depth-palette colour selected by nesting depth. **Covers**: `Rendering-Skia-SkiaRasterRenderer-ThemeColours`. -### End markers match notation metrics +#### End markers match notation metrics Tests `FilledArrow_AlongLength_MatchesNotationMetrics`, `FilledArrow_BaseWidth_MatchesNotationMetrics`, `OpenChevron_HasFewerInkPixelsThanClosedTriangle`, and @@ -51,14 +86,14 @@ distinguishable output. **Covers**: `Rendering-Skia-SkiaRasterRenderer-EndMarkers`. -### Empty tree renders as a valid image +#### Empty tree renders as a valid image Test `PngRenderer_Render_EmptyTree_WritesPngSignature` renders an empty layout tree and asserts that a valid image with the PNG signature is produced, proving the minimum one by one pixel bitmap path. **Covers**: `Rendering-Skia-SkiaRasterRenderer-EmptyTree`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Skia-SkiaRasterRenderer-DrawsLayoutTree`**: PngRenderer_Render_SingleBox_ProducesNonEmptyOutput, PngRenderer_Render_BackgroundIsThemeBackground, diff --git a/docs/verification/rendering-skia/webp-renderer.md b/docs/verification/rendering-skia/webp-renderer.md index d7f2909..fa39801 100644 --- a/docs/verification/rendering-skia/webp-renderer.md +++ b/docs/verification/rendering-skia/webp-renderer.md @@ -1,17 +1,42 @@ -# WebpRenderer Unit Verification +## WebpRenderer Unit Verification Part of the Rendering.Skia Verification. This document describes the verification design for the `WebpRenderer` unit of the `DemaConsulting.Rendering.Skia` system. It maps every WebpRenderer unit requirement to at least one -named test scenario so a reviewer can confirm coverage without reading the test code. The verification -strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is -`DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`). +named test scenario so a reviewer can confirm coverage without reading the test code. -## WebpRenderer Unit Scenarios +### WebpRenderer Verification Approach -### WEBP output has the expected container header and metadata +The `WebpRenderer` unit is verified with xUnit v3 unit tests in +`DemaConsulting.Rendering.Skia.Tests` (`SkiaFormatRendererTests.cs`) that render a placed +`LayoutTree` into a `MemoryStream` and inspect the resulting bytes. The renderer is used as a real +`IRenderer` — no dependencies are mocked or stubbed — because it is a pure, deterministic +transformation from layout to encoded image bytes. Verification checks that the output is a valid +RIFF/WEBP container and that the renderer's advertised `MediaType` and `FileExtensions` match the +WEBP contract. + +### WebpRenderer Test Environment + +- **Framework**: xUnit v3. +- **Target frameworks**: `net8.0`, `net9.0`, `net10.0`. +- **Execution**: `dotnet test` invoked by `build.ps1` and the CI pipeline (see + _Rendering.Skia Verification_ for the system-level environment). +- **External services / files**: none. Rendering is deterministic and writes to a `MemoryStream`. +- **Native assets**: the SkiaSharp platform-specific native asset packages provided by + `DemaConsulting.Rendering.Skia`'s NuGet references must be available at test time; no other + setup beyond `dotnet restore` is required. + +### WebpRenderer Acceptance Criteria + +A verification run passes when the WEBP scenarios below execute without an unexpected exception, +the encoded output is a valid RIFF/WEBP container header, and the renderer reports the +`image/webp` media type and `.webp` file extension. Any wrong container header, wrong metadata +value, or unexpected exception constitutes a failure. + +### WebpRenderer Unit Scenarios + +#### WEBP output has the expected container header and metadata Test `WebpRenderer_Render_ProducesWebpContainerHeader` renders a sample layout and asserts that output is a RIFF/WEBP container and that the renderer reports the `image/webp` media type and `.webp` file @@ -19,6 +44,6 @@ extension. **Covers**: `Rendering-Skia-WebpRenderer-EmitsWebp`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Skia-WebpRenderer-EmitsWebp`**: WebpRenderer_Render_ProducesWebpContainerHeader diff --git a/docs/verification/rendering-svg/rendering-svg.md b/docs/verification/rendering-svg.md similarity index 97% rename from docs/verification/rendering-svg/rendering-svg.md rename to docs/verification/rendering-svg.md index eaf5cd0..9daba07 100644 --- a/docs/verification/rendering-svg/rendering-svg.md +++ b/docs/verification/rendering-svg.md @@ -4,7 +4,7 @@ This document describes the system-level verification design for the `DemaConsul system and links to the per-unit verification document for its single unit. Detailed per-requirement scenarios live in SvgRenderer Unit Verification. -## Verification Strategy +## Verification Approach The SVG system is verified through in-process xUnit tests that render placed `LayoutTree` inputs and assert on the emitted SVG markup. The system-level smoke scenario exercises the public `IRenderer.Render` @@ -30,7 +30,7 @@ SvgRenderer Unit Verification pass without unexpected exception. Any missing SVG wrong renderer metadata, wrong emitted element or attribute, malformed XML escaping, or marker geometry that does not match `NotationMetrics` constitutes a failure. -## System Requirements Coverage +## Test Scenarios The system requirement is satisfied through the SvgRenderer unit scenarios; the representative system-level scenario is: diff --git a/docs/verification/rendering-svg/svg-renderer.md b/docs/verification/rendering-svg/svg-renderer.md index 106e367..12d631d 100644 --- a/docs/verification/rendering-svg/svg-renderer.md +++ b/docs/verification/rendering-svg/svg-renderer.md @@ -1,4 +1,4 @@ -# SvgRenderer Unit Verification +## SvgRenderer Unit Verification Part of the Rendering.Svg Verification. @@ -9,9 +9,49 @@ strategy, test environment, and acceptance criteria are described in the system verification document; the test project is `DemaConsulting.Rendering.Svg.Tests`. -## SvgRenderer Unit Scenarios +### Verification Approach -### Renderer contract and metadata +Unit verification uses xUnit v3 tests in `DemaConsulting.Rendering.Svg.Tests` that construct +concrete `LayoutTree`, `RenderOptions`, and `MemoryStream` instances and invoke +`SvgRenderer.Render` directly. Because `SvgRenderer` is pure and stateless, no mocking or +stubbing is used: the real `Themes.Light` theme, the real `NotationMetrics` and +`ConnectorLabelPlacer` helpers from `DemaConsulting.Rendering.Abstractions`, and the real +`LayoutTree` and `LayoutNode` records from `DemaConsulting.Rendering` are exercised end-to-end. +Each test decodes the emitted UTF-8 bytes and asserts on the SVG text, element presence, +attribute values, well-formed XML, and geometric parity with `NotationMetrics`. + +### Test Environment + +- **Framework**: xUnit v3 on the .NET SDK, run against the `net8.0`, `net9.0`, and `net10.0` + target frameworks. +- **Execution**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Dependencies**: none — no external services, databases, files, or network access are used; + tests write to in-memory `MemoryStream` instances only. +- **Isolation**: each test constructs its own `SvgRenderer`, layout tree, render options, and + output stream, so tests are independent and order-agnostic. + +### Acceptance Criteria + +A unit verification run passes when every named scenario listed below completes without +unexpected exception and every assertion holds. A failure is any missing SVG root element, +wrong renderer metadata (`MediaType`, `DefaultExtension`), wrong emitted SVG element or +attribute, malformed XML escaping, or marker geometry that does not match `NotationMetrics`. +Requirements coverage is enforced by the mapping in the _Requirements Coverage_ section below: +every `Rendering-Svg-SvgRenderer-*` requirement must map to at least one named scenario, and +every mapped scenario must exist in `DemaConsulting.Rendering.Svg.Tests` and pass. + +### Test Scenarios + +The named scenarios that satisfy each unit requirement are enumerated below under +_SvgRenderer Unit Scenarios_, grouped by rendering concern (renderer contract and metadata, +SVG document root and empty tree, box rectangle and compartments, label text and styling and +escaping, connector path and corners and dash pattern and label, additional node kinds, and +connector end markers). Every `Rendering-Svg-SvgRenderer-*` requirement is traced to at least +one named scenario in the _Requirements Coverage_ table at the end of this document. + +### SvgRenderer Unit Scenarios + +#### Renderer contract and metadata Test `SvgRenderer_Render_SingleBox_ProducesSvgDocument` renders a single box, asserts that SVG markup is produced, and checks `MediaType` is `image/svg+xml` and `DefaultExtension` is `.svg`. @@ -19,7 +59,7 @@ is produced, and checks `MediaType` is `image/svg+xml` and `DefaultExtension` is **Covers**: `Rendering-Svg-SvgRenderer-ImplementsIRenderer`, `Rendering-Svg-SvgRenderer-MediaType`, `Rendering-Svg-SvgRenderer-DefaultExtension`. -### SVG document root and empty tree +#### SVG document root and empty tree Test `SvgRenderer_Render_EmptyTree_ProducesSvgDocument` renders an empty `LayoutTree`, asserts the output stream is non-empty, and checks that the decoded text contains ``. @@ -27,7 +67,7 @@ output stream is non-empty, and checks that the decoded text contains `` and no ``, and that an open-chevron @@ -103,6 +151,12 @@ dimensions and points match `NotationMetrics` and that the rendered SVG contains Test `SvgRenderer_Render_SingleLine_WithOpenCrossbarArrowhead_ProducesOpenCrossbarMarker` asserts that the rendered SVG contains the `line-end-hollow-triangle-crossbar` id. +Tests `SvgRenderer_Render_SingleLine_WithFilledArrow_ProducesFilledArrowMarker`, +`SvgRenderer_Render_SingleLine_WithFilledDiamond_ProducesFilledDiamondMarker`, +`SvgRenderer_Render_SingleLine_WithCircleEnd_ProducesCircleMarker`, and +`SvgRenderer_Render_SingleLine_WithBarEnd_ProducesBarMarker` assert that the connector path carries +the marker reference for the filled-arrow, filled-diamond, circle, and bar end-marker variants. + The diamond and crossbar assertions now check that the connector path element carries the marker reference itself — `marker-start="url(#line-end-hollow-diamond)"` for the source end and `marker-end="url(#line-end-hollow-triangle-crossbar)"` for the target end — rather than only that the @@ -116,9 +170,13 @@ marker id appears somewhere in the document. This prevents a false pass if the m `Rendering-Svg-SvgRenderer-TriangleEndMarkerMetrics`, `Rendering-Svg-SvgRenderer-DiamondEndMarkers`, `Rendering-Svg-SvgRenderer-DiamondEndMarkerReference`, -`Rendering-Svg-SvgRenderer-CrossbarEndMarkers`. +`Rendering-Svg-SvgRenderer-CrossbarEndMarkers`, +`Rendering-Svg-SvgRenderer-EndMarkerFilledArrow`, +`Rendering-Svg-SvgRenderer-EndMarkerFilledDiamond`, +`Rendering-Svg-SvgRenderer-EndMarkerCircle`, +`Rendering-Svg-SvgRenderer-EndMarkerBar`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Svg-SvgRenderer-ImplementsIRenderer`**: `SvgRenderer_Render_SingleBox_ProducesSvgDocument` @@ -152,6 +210,14 @@ marker id appears somewhere in the document. This prevents a false pass if the m - **`Rendering-Svg-SvgRenderer-RenderNodeKinds`**: `SvgRenderer_Render_SinglePort_ProducesRect` - **`Rendering-Svg-SvgRenderer-RenderBadge`**: `SvgRenderer_Render_SingleBadge_FilledCircle_ProducesCircle` +- **`Rendering-Svg-SvgRenderer-BadgeBullseye`**: + `SvgRenderer_Render_SingleBadge_Bullseye_ProducesConcentricCircles` +- **`Rendering-Svg-SvgRenderer-BadgeDiamond`**: + `SvgRenderer_Render_SingleBadge_Diamond_ProducesPolygon` +- **`Rendering-Svg-SvgRenderer-BadgeHorizontalBar`**: + `SvgRenderer_Render_SingleBadge_HorizontalBar_ProducesLine` +- **`Rendering-Svg-SvgRenderer-BadgeVerticalBar`**: + `SvgRenderer_Render_SingleBadge_VerticalBar_ProducesLine` - **`Rendering-Svg-SvgRenderer-RenderBand`**: `SvgRenderer_Render_SingleBand_ProducesRect` - **`Rendering-Svg-SvgRenderer-RenderLifeline`**: `SvgRenderer_Render_SingleLifeline_ProducesRectAndLine` @@ -173,3 +239,11 @@ marker id appears somewhere in the document. This prevents a false pass if the m `SvgRenderer_Render_SingleLine_WithDiamondArrowhead_ProducesDiamondMarker` - **`Rendering-Svg-SvgRenderer-CrossbarEndMarkers`**: `SvgRenderer_Render_SingleLine_WithOpenCrossbarArrowhead_ProducesOpenCrossbarMarker` +- **`Rendering-Svg-SvgRenderer-EndMarkerFilledArrow`**: + `SvgRenderer_Render_SingleLine_WithFilledArrow_ProducesFilledArrowMarker` +- **`Rendering-Svg-SvgRenderer-EndMarkerFilledDiamond`**: + `SvgRenderer_Render_SingleLine_WithFilledDiamond_ProducesFilledDiamondMarker` +- **`Rendering-Svg-SvgRenderer-EndMarkerCircle`**: + `SvgRenderer_Render_SingleLine_WithCircleEnd_ProducesCircleMarker` +- **`Rendering-Svg-SvgRenderer-EndMarkerBar`**: + `SvgRenderer_Render_SingleLine_WithBarEnd_ProducesBarMarker` diff --git a/docs/verification/rendering/rendering.md b/docs/verification/rendering.md similarity index 97% rename from docs/verification/rendering/rendering.md rename to docs/verification/rendering.md index 478dfdd..1ececec 100644 --- a/docs/verification/rendering/rendering.md +++ b/docs/verification/rendering.md @@ -10,7 +10,7 @@ scenarios live in the unit documents: - Options Unit Verification - Layout Graph Unit Verification -## Verification Strategy +## Verification Approach The rendering model is a pure data-and-configuration library with no I/O, so it is verified entirely through in-process unit tests that construct the model types and assert on their observable state. @@ -34,7 +34,7 @@ A verification run passes when every scenario in this system document and in the passes without error or unexpected exception. Any wrong stored value, wrong type, or unexpected exception constitutes a failure. -## System Requirements Coverage +## Test Scenarios The system requirements are satisfied through the unit scenarios documented in the per-unit verification files; the representative system-level scenarios are: diff --git a/docs/verification/rendering/layout-graph.md b/docs/verification/rendering/layout-graph.md index 6dd1414..46fa848 100644 --- a/docs/verification/rendering/layout-graph.md +++ b/docs/verification/rendering/layout-graph.md @@ -1,38 +1,60 @@ -# Layout Graph Unit Verification +## Layout Graph Unit Verification Part of the Rendering Model Verification. This document describes the verification design for the layout-graph unit of the `DemaConsulting.Rendering` system. It maps every layout-graph unit requirement to at least one named -test scenario so a reviewer can confirm coverage without reading the test code. The verification -strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is `DemaConsulting.Rendering.Tests` -(`LayoutGraphTests.cs`). +test scenario so a reviewer can confirm coverage without reading the test code. The test project is +`DemaConsulting.Rendering.Tests` (`LayoutGraphTests.cs`). -## Layout Graph Unit Scenarios +### Verification Approach -### AddNode appends and returns the node +`LayoutGraph` is verified by direct in-process xUnit unit tests against the real `LayoutGraph`, +`LayoutGraphNode`, and `LayoutGraphEdge` types. The unit is a pure in-memory model, so no mocking or +test doubles are required: each scenario constructs a graph, performs the node/edge operation under +test, and asserts on the returned objects, graph contents, per-container scoping behavior, or thrown +exceptions. + +### Test Environment + +- **Framework**: xUnit v3 running on the .NET SDK. +- **Runner**: `dotnet test` invoked by `build.ps1` and the CI pipeline. +- **Project**: `test/DemaConsulting.Rendering.Tests/LayoutGraphTests.cs`. +- **Target frameworks**: .NET 8, .NET 9, and .NET 10. +- **Dependencies**: no external services, files, or network access; every test uses only in-memory + graph objects. + +### Acceptance Criteria + +A verification run passes when every named scenario below executes without unexpected exception and +the observed graph structure matches the requirement under test. Any wrong node or edge count, wrong +per-container scoping behavior, wrong stored property value, or unexpected exception constitutes a +failure. + +### Test Scenarios + +#### AddNode appends and returns the node Test `AddNode_AppendsNodeAndReturnsIt` calls `AddNode` on a fresh graph and asserts that the graph contains one node and that the returned node carries the requested id, width, and height. **Covers**: `Rendering-Model-LayoutGraph-AddNode`. -### AddEdge appends an edge with endpoints +#### AddEdge appends an edge with endpoints Test `AddEdge_AppendsEdgeWithEndpoints` adds two nodes and an edge referencing them, then asserts that the graph contains one edge whose `Source` and `Target` are the same node instances supplied. **Covers**: `Rendering-Model-LayoutGraph-AddEdge`. -### Node carries per-element properties +#### Node carries per-element properties Test `Node_CarriesPerElementProperties` sets the `CoreOptions.Direction` property on a node and reads it back, asserting that the read returns the value set on that node. **Covers**: `Rendering-Model-LayoutGraph-PerElementProperties`. -### Container node holds nested children and a leaf reports none +#### Container node holds nested children and a leaf reports none Tests `LayoutGraphNode_Children_ContainerNode_HoldsChildNodesAndEdges` and `LayoutGraphNode_HasChildren_LeafNode_ReturnsFalse` populate a node's `Children` subgraph with two @@ -43,7 +65,7 @@ a leaf allocates no child subgraph. **Covers**: `Rendering-Model-LayoutGraph-ContainerNodes`. -### Identifiers are scoped per container +#### Identifiers are scoped per container Tests `LayoutGraph_AddNode_ChildScope_AllowsIdReuseAcrossScopes` and `LayoutGraph_AddNode_ChildScope_DuplicateId_ThrowsArgumentException` add a node named `x` inside two @@ -55,7 +77,7 @@ confirming edge identifiers are scoped per container just as node identifiers ar **Covers**: `Rendering-Model-LayoutGraph-ScopedIdentifiers`. -### Cross-container edge references a descendant node +#### Cross-container edge references a descendant node Test `LayoutGraphEdge_CrossContainer_ReferencingDescendant_ConstructibleAtRoot` adds, at the root graph, an edge between a root-level leaf node and a node nested inside a container, then asserts the @@ -67,7 +89,7 @@ edge is expressible at the lowest common ancestor, including the sibling-contain **Covers**: `Rendering-Model-LayoutGraph-CrossContainerEdge`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Model-LayoutGraph-AddNode`**: AddNode_AppendsNodeAndReturnsIt - **`Rendering-Model-LayoutGraph-AddEdge`**: AddEdge_AppendsEdgeWithEndpoints diff --git a/docs/verification/rendering/layout-tree.md b/docs/verification/rendering/layout-tree.md index 41b9f0a..5fa4d09 100644 --- a/docs/verification/rendering/layout-tree.md +++ b/docs/verification/rendering/layout-tree.md @@ -1,17 +1,43 @@ -# Layout Tree Unit Verification +## Layout Tree Unit Verification Part of the Rendering Model Verification. This document describes the verification design for the layout-tree unit of the `DemaConsulting.Rendering` system. It maps every layout-tree unit requirement to at least one named -test scenario so a reviewer can confirm coverage without reading the test code. The verification -strategy, test environment, and acceptance criteria are described in the -system verification document; the test project is `DemaConsulting.Rendering.Tests` -(`LayoutTests.cs`). +test scenario so a reviewer can confirm coverage without reading the test code. -## Layout Tree Unit Scenarios +### Layout Tree Unit Verification Approach -### Layout tree carries canvas and nodes +The Layout Tree unit is verified by direct in-process xUnit tests that construct each concrete +`LayoutNode` record and the `LayoutTree`, `Point2D`, and `Rect` value types with explicit inputs and +assert on the observable state. No mocking or stubbing is required: the types are immutable data +records with no dependencies and no I/O, so the tests exercise the real records exactly as consumers +do. Each test focuses on a single record type or geometry type and verifies that supplied +constructor arguments are stored and returned unchanged, that heterogeneous children are retained in +insertion order, and that the depth-not-color and absolute-coordinate invariants hold. + +### Layout Tree Unit Test Environment + +- **Framework**: xUnit v3, run through the standard `dotnet test` runner. +- **Test project**: `DemaConsulting.Rendering.Tests`, source file `LayoutTests.cs`. +- **Runtime**: any target framework built by the solution (`net8.0`, `net9.0`, or `net10.0`). +- **Dependencies**: none beyond the standard test runner; no external services, network, filesystem, + or configuration is required. +- **Isolation**: each test constructs its own record instances, so there is no shared mutable state + between tests and no ordering dependency. + +### Layout Tree Unit Acceptance Criteria + +Every named scenario listed below passes without error or unexpected exception (IEC 62304 §5.5.2). A +failure is any stored value that does not match the constructor input, any nested child that is +missing or reordered, any non-integer `Depth`, any coordinate that has been transformed away from the +supplied absolute value, or any unexpected exception. The verification run is considered complete +when every requirement listed in the Requirements Coverage section is mapped to at least one passing +test. + +### Layout Tree Unit Scenarios + +#### Layout tree carries canvas and nodes Test `LayoutTree_Construction_StoresWidthHeightNodes` constructs a `LayoutTree` with explicit width, height, and a single top-level node, then asserts that `Width`, `Height`, and `Nodes` return the @@ -19,7 +45,7 @@ supplied values and that the single node is the same instance supplied. **Covers**: `Rendering-Model-LayoutTree-Canvas`. -### Node coordinates are absolute +#### Node coordinates are absolute Tests `LayoutBox_Coordinates_AreAbsolute`, `LayoutPort_Coordinates_AreAbsolute`, and `LayoutLine_Waypoints_AreAbsolute` construct a box, a port, and a line at explicit positions and @@ -27,7 +53,7 @@ assert that the stored coordinates equal the supplied values with no offset or t **Covers**: `Rendering-Model-LayoutTree-AbsoluteCoordinates`. -### Box carries all fields and children +#### Box carries all fields and children Tests `LayoutBox_Construction_StoresAllFields` and `LayoutBox_Children_ContainsNestedNodes` construct a box with all nine parameters non-default and a box with a port and a nested box as children, then @@ -36,21 +62,21 @@ insertion order. **Covers**: `Rendering-Model-LayoutTree-Box`. -### Box depth is an integer +#### Box depth is an integer Test `LayoutBox_Depth_IsInteger` constructs a box with `Depth` set to 3 and asserts that `Depth` is stored as an `int` with value 3, confirming the depth-not-color invariant. **Covers**: `Rendering-Model-LayoutTree-DepthNotColor`. -### Port carries all fields +#### Port carries all fields Test `LayoutPort_Construction_StoresAllFields` constructs a port with centre, side, and label set and asserts that `CentreX`, `CentreY`, `Side`, and `Label` equal the supplied values. **Covers**: `Rendering-Model-LayoutTree-Port`. -### Line carries all fields +#### Line carries all fields Test `LayoutLine_Construction_StoresAllFields` constructs a line with two waypoints, both end-marker styles, a line style, and a midpoint label, and asserts that `Waypoints`, `SourceEnd`, `TargetEnd`, @@ -58,7 +84,7 @@ styles, a line style, and a midpoint label, and asserts that `Waypoints`, `Sourc **Covers**: `Rendering-Model-LayoutTree-Line`. -### Label carries all fields +#### Label carries all fields Test `LayoutLabel_Construction_StoresAllFields` constructs a label with all eight parameters non-default and asserts that `X`, `Y`, `MaxWidth`, `Text`, `Align`, `Weight`, `Style`, and `FontSize` @@ -66,14 +92,14 @@ equal the supplied values. **Covers**: `Rendering-Model-LayoutTree-Label`. -### Badge carries all fields +#### Badge carries all fields Test `LayoutBadge_Construction_StoresAllFields` constructs a badge with centre, size, shape, and label and asserts that `CentreX`, `CentreY`, `Size`, `Shape`, and `Label` equal the supplied values. **Covers**: `Rendering-Model-LayoutTree-Badge`. -### Band carries all fields +#### Band carries all fields Test `LayoutBand_Construction_StoresAllFields` constructs a band with bounds, orientation, label, and one child and asserts that `X`, `Y`, `Width`, `Height`, `Orientation`, `Label`, and `Children` equal @@ -81,7 +107,7 @@ the supplied values. **Covers**: `Rendering-Model-LayoutTree-Band`. -### Lifeline carries all fields +#### Lifeline carries all fields Test `LayoutLifeline_Construction_StoresAllFields` constructs a lifeline with centre, extent, label, and header dimensions and asserts that `CentreX`, `TopY`, `BottomY`, `Label`, `HeaderWidth`, and @@ -89,14 +115,14 @@ and header dimensions and asserts that `CentreX`, `TopY`, `BottomY`, `Label`, `H **Covers**: `Rendering-Model-LayoutTree-Lifeline`. -### Activation carries all fields +#### Activation carries all fields Test `LayoutActivation_Construction_StoresAllFields` constructs an activation with centre and vertical extent and asserts that `CentreX`, `TopY`, and `BottomY` equal the supplied values. **Covers**: `Rendering-Model-LayoutTree-Activation`. -### Grid carries rows and cells +#### Grid carries rows and cells Test `LayoutGrid_Construction_StoresAllFields` constructs a grid with one header row containing one cell and asserts the grid position, the row's `IsHeader` flag, and the cell's `Width`, `Height`, @@ -104,7 +130,7 @@ cell and asserts the grid position, the row's `IsHeader` flag, and the cell's `W **Covers**: `Rendering-Model-LayoutTree-Grid`. -### Geometry value types carry their fields +#### Geometry value types carry their fields Tests `Point2D_Construction_StoresXY` and `Rect_Construction_StoresAllFields` construct a point at a known location and a rectangle with all four fields non-default, then assert each value type stores @@ -112,7 +138,7 @@ and returns its supplied coordinates and bounds unchanged. **Covers**: `Rendering-Model-LayoutTree-Geometry`. -## Requirements Coverage +### Requirements Coverage - **`Rendering-Model-LayoutTree-Canvas`**: LayoutTree_Construction_StoresWidthHeightNodes - **`Rendering-Model-LayoutTree-AbsoluteCoordinates`**: LayoutBox_Coordinates_AreAbsolute, diff --git a/docs/verification/rendering/options.md b/docs/verification/rendering/options.md index eb19fa5..394a949 100644 --- a/docs/verification/rendering/options.md +++ b/docs/verification/rendering/options.md @@ -1,47 +1,86 @@ -# Options Unit Verification +## Options Unit Verification Part of the Rendering Model Verification. This document describes the verification design for the options unit of the `DemaConsulting.Rendering` system. It maps every options unit requirement to at least one named test -scenario so a reviewer can confirm coverage without reading the test code. The verification strategy, -test environment, and acceptance criteria are described in the -system verification document; the test project is `DemaConsulting.Rendering.Tests` -(`PropertyHolderTests.cs`). +scenario so a reviewer can confirm coverage without reading the test code. -## Options Unit Scenarios +### Options Unit Verification Approach -### Unset property returns default +The Options unit is verified by direct in-process xUnit tests that construct a fresh +`PropertyHolder`, perform the operation under test, and assert on the observable result. No mocking +or stubbing is required: `LayoutProperty` is a small immutable value type constructed directly in +each test, and `PropertyHolder` is the concrete store consumed as-is. There are no injected +dependencies to substitute because the unit has none beyond the .NET base class library. Each test +exercises one API on `IPropertyHolder` (`Get`, `TryGet`, `Set`, or `Contains`) and asserts that +either the stored value or the property's declared default is returned as specified by the design. + +### Options Unit Test Environment + +- **Framework**: xUnit v3, run through the standard `dotnet test` runner. +- **Test project**: `DemaConsulting.Rendering.Tests`, source file `PropertyHolderTests.cs`. +- **Runtime**: any target framework built by the solution (`net8.0`, `net9.0`, or `net10.0`). +- **Dependencies**: none beyond the standard test runner; no external services, network, filesystem, + or configuration is required. +- **Isolation**: each test constructs its own `PropertyHolder` and `LayoutProperty` instances, so + there is no shared mutable state between tests and no ordering dependency. + +### Options Unit Acceptance Criteria + +Every named scenario listed below passes without error or unexpected exception (IEC 62304 §5.5.2). A +failure is any wrong stored value, wrong `TryGet`/`Contains` result, missing default, or unexpected +exception. The verification run is considered complete when every requirement listed in the +Requirements Coverage section is mapped to at least one passing test. + +### Options Unit Scenarios + +#### Unset property returns default Test `Get_UnsetProperty_ReturnsDefault` reads a property from a fresh `PropertyHolder` without setting it and asserts that the read returns the property's declared default value. **Covers**: `Rendering-Model-Options-Default`. -### Set value is retrieved +#### Set value is retrieved Test `Get_AfterSet_ReturnsStoredValue` sets a property to a value, then reads the same property and asserts that the read returns the stored value rather than the default. **Covers**: `Rendering-Model-Options-StoreAndRetrieve`. -### Contains reflects explicit set +#### Contains reflects explicit set Test `Contains_ReflectsExplicitSet` queries `Contains` before and after setting a property and asserts that it returns false before the set and true afterwards. **Covers**: `Rendering-Model-Options-Contains`. -### TryGet reports unset and yields default +#### TryGet reports unset and yields default Test `TryGet_UnsetProperty_ReturnsFalseAndDefault` calls `TryGet` for a property that has not been set and asserts that it returns false and yields the declared default through its out parameter. **Covers**: `Rendering-Model-Options-TryGet`. -## Requirements Coverage +#### Overlaying cascades explicit values with correct precedence + +Tests `OverlayOnto_EmptyHolderOntoPopulatedParent_ReturnsParentValuesUnchanged`, +`OverlayOnto_HolderOverridesProperty_HolderValueWins`, `OverlayOnto_ValueOnlyOnParent_PassesThrough`, +`OverlayOnto_CustomPropertyNotInCoreOptions_IsMerged`, and +`OverlayOnto_NullParent_ThrowsArgumentNullException` construct a parent and an overlaying holder with +various combinations of explicitly-set values (including a property outside `CoreOptions`, proving the +merge is generic) and assert that the overlaying holder's own values win, the parent's other values +pass through unchanged, and a null parent is rejected. + +**Covers**: `Rendering-Model-Options-Cascade`. + +### Requirements Coverage - **`Rendering-Model-Options-Default`**: Get_UnsetProperty_ReturnsDefault - **`Rendering-Model-Options-StoreAndRetrieve`**: Get_AfterSet_ReturnsStoredValue - **`Rendering-Model-Options-Contains`**: Contains_ReflectsExplicitSet - **`Rendering-Model-Options-TryGet`**: TryGet_UnsetProperty_ReturnsFalseAndDefault +- **`Rendering-Model-Options-Cascade`**: OverlayOnto_EmptyHolderOntoPopulatedParent_ReturnsParentValuesUnchanged, + OverlayOnto_HolderOverridesProperty_HolderValueWins, OverlayOnto_ValueOnlyOnParent_PassesThrough, + OverlayOnto_CustomPropertyNotInCoreOptions_IsMerged, OverlayOnto_NullParent_ThrowsArgumentNullException diff --git a/requirements.yaml b/requirements.yaml index bdb54a2..3cd5321 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -4,19 +4,19 @@ # requirements plus the unit-level requirements (as separate sections) that decompose them. includes: # In-house software systems - - docs/reqstream/rendering/rendering.yaml + - docs/reqstream/rendering.yaml - docs/reqstream/rendering/layout-tree.yaml - docs/reqstream/rendering/options.yaml - docs/reqstream/rendering/layout-graph.yaml - docs/reqstream/rendering/platform-requirements.yaml - - docs/reqstream/rendering-abstractions/rendering-abstractions.yaml + - docs/reqstream/rendering-abstractions.yaml - docs/reqstream/rendering-abstractions/rendering-contracts.yaml - docs/reqstream/rendering-abstractions/registries.yaml - docs/reqstream/rendering-abstractions/theme.yaml - docs/reqstream/rendering-abstractions/notation-metrics.yaml - docs/reqstream/rendering-abstractions/box-metrics.yaml - docs/reqstream/rendering-abstractions/connector-label-placer.yaml - - docs/reqstream/rendering-layout/rendering-layout.yaml + - docs/reqstream/rendering-layout.yaml - docs/reqstream/rendering-layout/engine/engine.yaml - docs/reqstream/rendering-layout/engine/orthogonal-edge-router.yaml - docs/reqstream/rendering-layout/engine/containment-packer.yaml @@ -29,9 +29,9 @@ includes: - docs/reqstream/rendering-layout/containment-layout-algorithm.yaml - docs/reqstream/rendering-layout/hierarchical-layout-algorithm.yaml - docs/reqstream/rendering-layout/default-layout.yaml - - docs/reqstream/rendering-svg/rendering-svg.yaml + - docs/reqstream/rendering-svg.yaml - docs/reqstream/rendering-svg/svg-renderer.yaml - - docs/reqstream/rendering-skia/rendering-skia.yaml + - docs/reqstream/rendering-skia.yaml - docs/reqstream/rendering-skia/skia-raster-renderer.yaml - docs/reqstream/rendering-skia/png-renderer.yaml - docs/reqstream/rendering-skia/jpeg-renderer.yaml @@ -47,3 +47,4 @@ includes: - docs/reqstream/ots/pandoc.yaml - docs/reqstream/ots/weasyprint.yaml - docs/reqstream/ots/fileassert.yaml + - docs/reqstream/ots/skiasharp.yaml diff --git a/src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs b/src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs index 62cfb7e..3e0d1f5 100644 --- a/src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs +++ b/src/DemaConsulting.Rendering.Abstractions/BoxMetrics.cs @@ -19,8 +19,12 @@ public static class BoxMetrics /// /// Theme providing font and padding metrics. /// The tab height in logical pixels. - public static double FolderTabHeight(Theme theme) => - theme.FontSizeBody + 2.0 * theme.LabelPadding; + /// is . + public static double FolderTabHeight(Theme theme) + { + ArgumentNullException.ThrowIfNull(theme); + return theme.FontSizeBody + 2.0 * theme.LabelPadding; + } /// /// Computes the height of the title area of a box: the vertical space reserved at the top @@ -30,8 +34,11 @@ public static double FolderTabHeight(Theme theme) => /// Whether the box has a name label. /// Whether the box has a keyword line above the name. /// The title-area height in logical pixels. + /// is . public static double TitleAreaHeight(Theme theme, bool hasLabel, bool hasKeyword) { + ArgumentNullException.ThrowIfNull(theme); + if (!hasLabel && !hasKeyword) { return 0.0; diff --git a/src/DemaConsulting.Rendering.Abstractions/Theme.cs b/src/DemaConsulting.Rendering.Abstractions/Theme.cs index 7925699..cb2d493 100644 --- a/src/DemaConsulting.Rendering.Abstractions/Theme.cs +++ b/src/DemaConsulting.Rendering.Abstractions/Theme.cs @@ -12,10 +12,10 @@ namespace DemaConsulting.Rendering.Abstractions; /// to ensure consistent output across all platforms. /// /// -/// Hex colour strings indexed by nesting depth. Wraps if depth exceeds count. +/// Hex color strings indexed by nesting depth. Wraps if depth exceeds count. /// Example: ["#FFFFFF", "#EEF4FF", "#D6E8FF"]. /// -/// Hex colour used for all borders and lines. +/// Hex color used for all borders and lines. /// Width of borders and lines in logical pixels. /// Corner radius for orthogonal-line elbows. 0 = sharp; >0 = rounded. /// Font size for title / heading text. diff --git a/src/DemaConsulting.Rendering.Layout/ContainmentLayoutAlgorithm.cs b/src/DemaConsulting.Rendering.Layout/ContainmentLayoutAlgorithm.cs index 3374d01..0f7f496 100644 --- a/src/DemaConsulting.Rendering.Layout/ContainmentLayoutAlgorithm.cs +++ b/src/DemaConsulting.Rendering.Layout/ContainmentLayoutAlgorithm.cs @@ -148,7 +148,7 @@ public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) } // Route the connections around the packed boxes using the per-scope routing style. - var routeOptions = new ConnectorRouteOptions(options.Get(CoreOptions.EdgeRouting)); + var routeOptions = new ConnectorRouteOptions(ResolveEdgeRouting(graph, options)); var routedLines = ConnectorRouter.Route(packedBoxes, connections, routeOptions); var nodes = new List(packedBoxes.Count + routedLines.Count); @@ -158,6 +158,29 @@ public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) return new LayoutTree(result.Width, result.Height, nodes); } + /// + /// Resolves the edge-routing style for this layout: an explicit + /// on the graph takes precedence, then one on the options, falling back to the property default when + /// neither declares one. Mirrors 's graph-then-options-then-default + /// resolution for , so a graph-level override is honored whether + /// this algorithm is invoked directly or as a scope's leaf algorithm inside + /// . + /// + /// The graph whose explicit edge-routing declaration takes precedence. + /// The options consulted when the graph declares no edge-routing style. + /// The edge-routing style to route connections with. + private static EdgeRouting ResolveEdgeRouting(LayoutGraph graph, LayoutOptions options) + { + if (graph.TryGet(CoreOptions.EdgeRouting, out var fromGraph)) + { + return fromGraph; + } + + return options.TryGet(CoreOptions.EdgeRouting, out var fromOptions) + ? fromOptions + : CoreOptions.EdgeRouting.DefaultValue; + } + /// /// Derives the packer's content-width budget from the boxes: the square root of their total area /// biased toward a landscape canvas, widened to at least the widest box, and floored to a positive diff --git a/src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.cs b/src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.cs index a767350..19b0f01 100644 --- a/src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.cs +++ b/src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.cs @@ -327,8 +327,13 @@ private ComponentLayout LayoutComponent(LayeredGraph graph, List members) } } - // Lay out the component sub-graph with the wrapped inner stages. - var child = new LayeredGraph(localNodes, localEdges, graph.Direction); + // Lay out the component sub-graph with the wrapped inner stages. Propagate the parent's + // BackEdgeEntryApproach so a caller-customized reversed-edge clearance is honored for packed + // disconnected components instead of silently reverting to the LayeredGraph default. + var child = new LayeredGraph(localNodes, localEdges, graph.Direction) + { + BackEdgeEntryApproach = graph.BackEdgeEntryApproach, + }; RunInner(child); // Map the child's acyclic edges (local indices) back to original node indices so the merged diff --git a/src/DemaConsulting.Rendering.Layout/HierarchicalLayoutAlgorithm.cs b/src/DemaConsulting.Rendering.Layout/HierarchicalLayoutAlgorithm.cs index bfa9b50..a3681bc 100644 --- a/src/DemaConsulting.Rendering.Layout/HierarchicalLayoutAlgorithm.cs +++ b/src/DemaConsulting.Rendering.Layout/HierarchicalLayoutAlgorithm.cs @@ -23,12 +23,20 @@ namespace DemaConsulting.Rendering.Layout; /// each container box's interior and attached as the box's . /// /// -/// The leaf algorithm is chosen per scope through : the -/// root scope inherits the algorithm from the supplied (default -/// layered), and any container node may override it by setting -/// on itself; unset containers inherit their parent scope's -/// algorithm. This lets, for example, a containment-packed root hold a layered -/// container without either level knowing about the other. +/// Every property — , +/// , , +/// , , and +/// — cascades per scope through the same generalized +/// mechanism, built on : each scope's own explicit +/// overrides are overlaid onto its parent scope's already-resolved effective options, so an +/// unset scope inherits its nearest ancestor's value and any scope may override it for itself and +/// its descendants. Two established, independently-tested conventions decide where a container's +/// own overrides live: is set on the container node +/// itself (for example group.Set(CoreOptions.Algorithm, "layered")), while every other +/// property is set on the container's graph (for example +/// group.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down)). This lets, for +/// example, a containment-packed root hold a layered container flowing in its own +/// direction without either level knowing about the other. /// /// /// Hierarchy is handled in mode: each container is @@ -51,6 +59,15 @@ namespace DemaConsulting.Rendering.Layout; /// untouched. The engine is stateless and its is safe to call concurrently on /// distinct graphs. /// +/// +/// Leaf-algorithm contract. This engine builds each scope's cascaded effective +/// snapshot once and passes it to the selected leaf algorithm; it is +/// the leaf algorithm's own responsibility to resolve a property from that snapshot as the +/// ultimate source of a cascaded value (optionally preferring its own graph's override first, as +/// and do). A leaf +/// algorithm that reads only its input graph and ignores the supplied options would silently break +/// cascading for that algorithm. +/// /// /// /// @@ -159,10 +176,10 @@ public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) ArgumentNullException.ThrowIfNull(graph); ArgumentNullException.ThrowIfNull(options); - // The root scope inherits the algorithm from the options (its own default is "layered"), unless - // the graph itself carries an explicit CoreOptions.Algorithm override. - var algoId = ResolveScopeAlgorithm(graph, options.Get(CoreOptions.Algorithm)); - return LayoutScope(graph, algoId, options); + // The root scope's own explicit overrides (set directly on the graph) win over the supplied + // options, giving the root the same "own overrides win" treatment every nested scope receives. + var effective = graph.OverlayOnto(options); + return LayoutScope(graph, effective); } /// @@ -170,12 +187,14 @@ public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) /// containers first and composing their sub-layouts into this level's coordinate space. /// /// The container scope to place (the root graph or a node's child subgraph). - /// Identifier of the leaf algorithm to place this scope with. - /// The shared options carried into every leaf-algorithm invocation. + /// + /// This scope's cascaded effective options: the parent scope's resolved snapshot overlaid by this + /// scope's own explicit overrides, per . + /// /// The placed sub-tree for this scope, in local coordinates rooted at the origin. - private LayoutTree LayoutScope(LayoutGraph graph, string algoId, LayoutOptions options) + private LayoutTree LayoutScope(LayoutGraph graph, LayoutOptions effective) { - var algo = _registry.Resolve(algoId); + var algo = _registry.Resolve(ResolveLeafAlgorithmId(effective)); // Flat fast path: when no direct node is a container, delegate straight to the leaf algorithm so // the result is byte-for-byte identical to invoking that algorithm directly. This preserves the @@ -184,26 +203,26 @@ private LayoutTree LayoutScope(LayoutGraph graph, string algoId, LayoutOptions o if (!anyContainer) { - return algo.Apply(graph, options); + return algo.Apply(graph, effective); } // Hierarchical path (SeparateChildren): recurse into each container child, size it to fit, place // this level over a sized view, compose the sub-layouts, and route cross-container edges. var subLayouts = new Dictionary(); var effectiveSize = new Dictionary(); - LayoutContainerChildren(graph, algoId, options, subLayouts, effectiveSize); + LayoutContainerChildren(graph, effective, subLayouts, effectiveSize); var (view, _) = BuildSizedView(graph, effectiveSize); // Place this level with the resolved leaf algorithm over the sized view. The leaf algorithm // emits one box per node in Nodes order, so placed boxes align with graph.Nodes by index. - var placed = algo.Apply(view, options); + var placed = algo.Apply(view, effective); var placedBoxes = placed.Nodes.OfType().ToList(); var placedLines = placed.Nodes.OfType().ToList(); var (composed, indexOf) = ComposeBoxes(graph, placedBoxes, subLayouts); - var crossLines = RouteCrossContainerEdges(graph, composed, indexOf, options); + var crossLines = RouteCrossContainerEdges(graph, composed, indexOf, effective); // Assemble: composed boxes, then the leaf algorithm's routed lines, then the cross-container // lines. The canvas dimensions are the leaf algorithm's for this (sized) level. @@ -219,14 +238,12 @@ private LayoutTree LayoutScope(LayoutGraph graph, string algoId, LayoutOptions o /// recording both the resulting sub-layout and the container's effective (child-fitting) size. /// /// The scope whose container children are laid out. - /// The algorithm inherited by children that do not override it. - /// The shared options carried into each recursive layout. + /// This scope's own already-resolved cascaded effective options. /// Receives each container's placed sub-tree in local coordinates. /// Receives each container's size enclosing its children plus padding/title. private void LayoutContainerChildren( LayoutGraph graph, - string algoId, - LayoutOptions options, + LayoutOptions parentEffective, Dictionary subLayouts, Dictionary effectiveSize) { @@ -237,9 +254,16 @@ private void LayoutContainerChildren( continue; } - // A container inherits this scope's algorithm unless it overrides CoreOptions.Algorithm. - var childAlgoId = ResolveScopeAlgorithm(node, algoId); - var sub = LayoutScope(node.Children, childAlgoId, options); + // Preserve both established, independently-tested conventions: CoreOptions.Algorithm + // overrides live on the container node itself, while every other CoreOptions property + // (Direction, EdgeRouting, and so on) lives on the node's Children graph. Overlaying in this + // order lets either layer's own overrides win over the parent's resolved snapshot, and lets + // the Children graph's overrides win over the node's own (though the two never collide, since + // Algorithm is only ever set on the node and every other property only ever on Children). + var nodeEffective = node.OverlayOnto(parentEffective); + var childEffective = node.Children.OverlayOnto(nodeEffective); + + var sub = LayoutScope(node.Children, childEffective); subLayouts[node] = sub; // Size the container to enclose its sub-layout plus a padding inset on every side and, when @@ -253,8 +277,11 @@ private void LayoutContainerChildren( /// /// Builds an internal, side-effect-free sized view of : the same nodes in - /// the same order (container nodes carrying their effective size, leaves their own size) and only - /// the edges whose endpoints are both direct members of this scope. + /// the same order (container nodes carrying their effective size, leaves their own size), only + /// the edges whose endpoints are both direct members of this scope. The scope's own cascaded + /// options are carried separately as the caller's effective snapshot rather than propagated + /// onto this view, since every leaf algorithm resolves such properties from the options it is + /// invoked with. /// /// The caller's scope, which is never mutated. /// Effective sizes for the container nodes of this scope. @@ -264,6 +291,7 @@ private static (LayoutGraph View, Dictionary V Dictionary effectiveSize) { var view = new LayoutGraph(); + var viewOf = new Dictionary(graph.Nodes.Count); foreach (var node in graph.Nodes) { @@ -345,13 +373,13 @@ private static (LayoutBox[] Composed, Dictionary IndexOf) /// The scope whose edges are examined for cross-container routing. /// The composed top-level boxes of this scope, aligned with its nodes by index. /// Map from each direct-member node to its positional index in . - /// Options supplying the per-scope style. + /// This scope's cascaded effective options, supplying . /// One routed line per cross-container edge owned by this scope. private static List RouteCrossContainerEdges( LayoutGraph graph, LayoutBox[] composed, Dictionary indexOf, - LayoutOptions options) + LayoutOptions effective) { // Map every descendant node to the direct member of this scope that contains it, so an edge that // references a deeply nested endpoint can be anchored to the top-level box that owns it. @@ -361,7 +389,7 @@ private static List RouteCrossContainerEdges( MapDescendants(direct, direct, descendantToDirect); } - var routeOptions = new ConnectorRouteOptions(options.Get(CoreOptions.EdgeRouting)); + var routeOptions = new ConnectorRouteOptions(effective.Get(CoreOptions.EdgeRouting)); var boxesForRouting = (IReadOnlyList)composed; var crossLines = new List(); foreach (var edge in graph.Edges) @@ -399,19 +427,21 @@ private static List RouteCrossContainerEdges( } /// - /// Resolves the layout algorithm for a scope: the scope's explicit - /// override when present, otherwise the algorithm from the parent scope. + /// Resolves the leaf-algorithm identifier a scope is placed with from its already-cascaded effective + /// options. /// - /// The graph or node whose algorithm override is consulted. - /// The algorithm inherited when the scope carries no override. + /// + /// This scope's cascaded effective options, produced by overlaying the scope's own explicit + /// override (if any) onto its parent's resolved snapshot. + /// /// - /// The leaf-algorithm identifier to place the scope with. When the scope resolves to this engine's own + /// The leaf-algorithm identifier to place the scope with. When it resolves to this engine's own /// (which is not a leaf), the is /// substituted so recursion terminates in a registered leaf instead of failing to resolve. /// - private static string ResolveScopeAlgorithm(PropertyHolder scope, string inherited) + private static string ResolveLeafAlgorithmId(LayoutOptions effective) { - var resolved = scope.TryGet(CoreOptions.Algorithm, out var value) ? value : inherited; + var resolved = effective.Get(CoreOptions.Algorithm); return resolved == AlgorithmId ? DefaultLeafAlgorithmId : resolved; } diff --git a/src/DemaConsulting.Rendering.Skia/JpegRenderer.cs b/src/DemaConsulting.Rendering.Skia/JpegRenderer.cs index 98591f8..8b95f7e 100644 --- a/src/DemaConsulting.Rendering.Skia/JpegRenderer.cs +++ b/src/DemaConsulting.Rendering.Skia/JpegRenderer.cs @@ -8,7 +8,8 @@ namespace DemaConsulting.Rendering.Skia; /// /// Renders a to a JPEG image using SkiaSharp. JPEG is a lossy format with -/// no transparency; the renderer draws on the opaque white background shared by all raster renderers. +/// no transparency; the renderer draws on the opaque theme background color shared by all raster +/// renderers. /// public sealed class JpegRenderer : SkiaRasterRenderer { diff --git a/src/DemaConsulting.Rendering/Options/CoreOptions.cs b/src/DemaConsulting.Rendering/Options/CoreOptions.cs index 1d02745..691c238 100644 --- a/src/DemaConsulting.Rendering/Options/CoreOptions.cs +++ b/src/DemaConsulting.Rendering/Options/CoreOptions.cs @@ -11,9 +11,20 @@ namespace DemaConsulting.Rendering; /// bundled algorithms; they default harmlessly until an algorithm implements them. /// /// +/// /// Set a well-known option by passing the key together with a value of its type to /// on any scope — a graph, a /// node, an edge, or a free-standing . +/// +/// +/// Cascading. Every property below is resolved per scope by nearest-ancestor +/// override: a scope that sets its own explicit value wins, both for itself and for every descendant +/// scope that sets nothing; a scope with no explicit value inherits the nearest ancestor scope's +/// resolved value; and only when no scope in the chain declares one does the property's own documented +/// default apply. Consumers such as the bundled hierarchical layout engine build this per-scope +/// resolution with , the generic primitive that merges a +/// parent scope's already-resolved snapshot with a scope's own overrides. +/// /// /// /// Setting well-known options at two different scopes: @@ -27,6 +38,19 @@ namespace DemaConsulting.Rendering; /// var options = LayoutOptions.ForAlgorithm(LayeredLayoutAlgorithm.AlgorithmId); /// options.Set(CoreOptions.Direction, LayoutFlowDirection.Down); /// +/// Three-level cascading of a single property (here ), showing both +/// nearest-ancestor inheritance and a deeper override taking precedence: +/// +/// var root = new LayoutGraph(); +/// var mid = root.AddNode("mid", 10, 10); +/// mid.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); // mid-tree override +/// var leaf = mid.Children.AddNode("leaf", 10, 10); +/// leaf.Children.AddNode("a", 80, 40); +/// leaf.Children.AddNode("b", 80, 40); // leaf's own Children set nothing: inherits Down from mid +/// +/// var deeper = mid.Children.AddNode("deeper", 10, 10); +/// deeper.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Right); // deeper override wins locally +/// /// public static class CoreOptions { @@ -36,7 +60,10 @@ public static class CoreOptions /// LayeredLayoutAlgorithm.AlgorithmId ("layered"), /// ContainmentLayoutAlgorithm.AlgorithmId ("containment"), or /// HierarchicalLayoutAlgorithm.AlgorithmId ("hierarchical") — over a hardcoded string - /// so callers never have to spell the identifier by hand. + /// so callers never have to spell the identifier by hand. Cascades per scope: a container node may + /// override it for itself and its descendants (by convention, set directly on the container node), + /// falling back to the nearest ancestor's resolved algorithm, and ultimately to this property's own + /// default when nothing in the chain declares one. /// public static readonly LayoutProperty Algorithm = new("rendering.algorithm", "layered"); @@ -45,7 +72,8 @@ public static class CoreOptions /// How a hierarchical layout engine treats a container node's nested children, mirroring ELK's /// elk.hierarchyHandling. Carried on any scope (graph, node, or a free-standing /// ) so hierarchy handling can be selected per scope just like the - /// algorithm. The value defaults to , + /// algorithm, cascading by nearest-ancestor override the same way every other property in this + /// catalog does. The value defaults to , /// which is the only mode honored by the bundled hierarchical engine today: each container is laid /// out in its own coordinate space and sized to fit its children. The vocabulary grows additively as /// new hierarchy-handling modes are implemented. @@ -59,7 +87,11 @@ public static class CoreOptions /// flow left-to-right and right-to-left, while /// and flow /// top-to-bottom and bottom-to-top (swapping each node's width and height before layering so layer - /// spacing is driven by node height). Defaults to . + /// spacing is driven by node height). Cascades per scope: a container's nested content inherits the + /// nearest ancestor scope's resolved direction unless the container's own scope declares an + /// explicit override (by convention, set on the container node's + /// graph), falling back to + /// only when no scope in the chain declares one. /// public static readonly LayoutProperty Direction = new("rendering.direction", LayoutFlowDirection.Right); @@ -68,22 +100,27 @@ public static class CoreOptions /// Routing style applied to connectors, mirroring ELK's elk.edgeRouting. Carried on any /// scope (graph, node, edge, or a free-standing ) so routing can be /// selected per scope just like the algorithm. The bundled routing orchestration reads this key - /// to choose the router; the value defaults to , the only - /// shipped style, and the vocabulary grows additively as new routers are implemented. + /// to choose the router, cascading per scope by nearest-ancestor override the same way every other + /// property in this catalog does; the value defaults to , the + /// only shipped style, and the vocabulary grows additively as new routers are implemented. /// public static readonly LayoutProperty EdgeRouting = new("rendering.edgerouting", Rendering.EdgeRouting.Orthogonal); /// /// Advisory: desired spacing, in logical pixels, between adjacent nodes within a layer. Accepted - /// but not yet honored by the bundled layered algorithm, which uses fixed engine metrics. + /// but not yet honored by the bundled layered algorithm, which uses fixed engine metrics. Would + /// cascade per scope by nearest-ancestor override, consistent with every other property in this + /// catalog, once an algorithm honors it. /// public static readonly LayoutProperty NodeSpacing = new("rendering.spacing.node", 20.0); /// /// Advisory: desired spacing, in logical pixels, between adjacent layers. Accepted but not yet - /// honored by the bundled layered algorithm, which uses fixed engine metrics. + /// honored by the bundled layered algorithm, which uses fixed engine metrics. Would cascade per + /// scope by nearest-ancestor override, consistent with every other property in this catalog, once + /// an algorithm honors it. /// public static readonly LayoutProperty LayerSpacing = new("rendering.spacing.layer", 40.0); diff --git a/src/DemaConsulting.Rendering/Options/PropertyHolder.cs b/src/DemaConsulting.Rendering/Options/PropertyHolder.cs index dec81f0..c475de5 100644 --- a/src/DemaConsulting.Rendering/Options/PropertyHolder.cs +++ b/src/DemaConsulting.Rendering/Options/PropertyHolder.cs @@ -60,4 +60,40 @@ public bool Contains(LayoutProperty property) ArgumentNullException.ThrowIfNull(property); return _values.ContainsKey(property.Id); } + + /// + /// Builds a new snapshot that merges 's + /// explicitly-set values with this holder's own, with this holder's own values taking precedence. + /// This is the generic cascading primitive behind option inheritance: a caller resolving a nested + /// scope's effective options overlays each level's own overrides onto its parent's already-resolved + /// snapshot, nearest-ancestor-wins, without needing to know which properties exist. Because the + /// merge copies raw, boxed values by key rather than reading through + /// accessors, it works for any current or future property — including ones this holder or its + /// caller has never heard of — with no per-property code. + /// + /// + /// The base holder whose explicitly-set values are used wherever this holder has not set its own. + /// + /// + /// A new containing 's values overlaid by this + /// holder's own values. + /// + /// Thrown when is . + public LayoutOptions OverlayOnto(PropertyHolder parent) + { + ArgumentNullException.ThrowIfNull(parent); + + var effective = new LayoutOptions(); + foreach (var kvp in parent._values) + { + effective._values[kvp.Key] = kvp.Value; + } + + foreach (var kvp in _values) + { + effective._values[kvp.Key] = kvp.Value; + } + + return effective; + } } diff --git a/test/DemaConsulting.Rendering.Abstractions.Tests/BoxMetricsTests.cs b/test/DemaConsulting.Rendering.Abstractions.Tests/BoxMetricsTests.cs index 05efa24..9715d84 100644 --- a/test/DemaConsulting.Rendering.Abstractions.Tests/BoxMetricsTests.cs +++ b/test/DemaConsulting.Rendering.Abstractions.Tests/BoxMetricsTests.cs @@ -72,4 +72,32 @@ public void BoxMetrics_TitleAreaHeight_LabelAndKeyword_ReservesBothLines() theme.LabelPadding + theme.FontSizeBody + theme.LabelPadding + theme.FontSizeTitle + theme.LabelPadding, height); } + + /// A null theme is rejected with an . + [Fact] + public void BoxMetrics_FolderTabHeight_NullTheme_ThrowsArgumentNullException() + { + // Arrange + Theme? theme = null; + + // Act + void Act() => BoxMetrics.FolderTabHeight(theme!); + + // Assert + Assert.Throws(Act); + } + + /// A null theme is rejected with an . + [Fact] + public void BoxMetrics_TitleAreaHeight_NullTheme_ThrowsArgumentNullException() + { + // Arrange + Theme? theme = null; + + // Act + void Act() => BoxMetrics.TitleAreaHeight(theme!, hasLabel: true, hasKeyword: true); + + // Assert + Assert.Throws(Act); + } } diff --git a/test/DemaConsulting.Rendering.Abstractions.Tests/NotationMetricsTests.cs b/test/DemaConsulting.Rendering.Abstractions.Tests/NotationMetricsTests.cs index 7d36e3a..b7092b7 100644 --- a/test/DemaConsulting.Rendering.Abstractions.Tests/NotationMetricsTests.cs +++ b/test/DemaConsulting.Rendering.Abstractions.Tests/NotationMetricsTests.cs @@ -153,6 +153,14 @@ public void RoundedRectRadius_IsThemeRadiusTimesFactor() Assert.Equal(theme.LineCornerRadius * 2.0, NotationMetrics.RoundedRectRadius(theme)); } + /// A null theme is rejected with an argument-null error, as documented. + [Fact] + public void RoundedRectRadius_NullTheme_ThrowsArgumentNullException() + { + // Act & Assert: a null theme is rejected with an argument-null error. + Assert.Throws(() => NotationMetrics.RoundedRectRadius(null!)); + } + /// The label-background extent is symmetric about the documented inset. [Fact] public void LabelBackground_ExtentMatchesInset() diff --git a/test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs b/test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs index ac19481..0a79184 100644 --- a/test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs +++ b/test/DemaConsulting.Rendering.Abstractions.Tests/RegistryTests.cs @@ -13,20 +13,27 @@ namespace DemaConsulting.Rendering.Abstractions.Tests; public class RegistryTests { /// - /// Proves that a registered algorithm can be found by identifier. + /// Proves that a registered algorithm can be found by identifier and that resolving it returns + /// an instance whose can be invoked through the contract to + /// produce a placed layout tree. /// [Fact] public void LayoutAlgorithmRegistry_RegisterThenResolve_ReturnsAlgorithm() { // Arrange var registry = new LayoutAlgorithmRegistry(); + registry.Register(new FakeAlgorithm()); // Act - registry.Register(new FakeAlgorithm()); + var algorithm = registry.Resolve("fake"); + var tree = algorithm.Apply(new LayoutGraph(), new LayoutOptions()); - // Assert + // Assert: the algorithm is resolved by identifier and its Apply contract method can be invoked, + // producing the placed layout tree it was implemented to return. Assert.True(registry.Contains("fake")); - Assert.Equal("fake", registry.Resolve("fake").Id); + Assert.Equal("fake", algorithm.Id); + Assert.Equal(FakeAlgorithm.PlacedWidth, tree.Width); + Assert.Equal(FakeAlgorithm.PlacedHeight, tree.Height); } /// @@ -43,20 +50,27 @@ public void LayoutAlgorithmRegistry_ResolveMissing_Throws() } /// - /// Proves that a registered renderer can be found by media type. + /// Proves that a registered renderer can be found by media type and that resolving it returns an + /// instance whose can be invoked through the contract to write + /// rendered output for a layout tree to a supplied stream. /// [Fact] public void RendererRegistry_RegisterThenResolve_ReturnsRenderer() { // Arrange var registry = new RendererRegistry(); + registry.Register(new FakeRenderer()); + using var output = new MemoryStream(); // Act - registry.Register(new FakeRenderer()); + var renderer = registry.Resolve("text/plain"); + renderer.Render(new LayoutTree(0, 0, []), new RenderOptions(Themes.Light), output); - // Assert + // Assert: the renderer is resolved by media type and its Render contract method can be invoked, + // writing the output it was implemented to produce to the caller's stream. Assert.True(registry.Contains("text/plain")); - Assert.Equal("text/plain", registry.Resolve("text/plain").MediaType); + Assert.Equal("text/plain", renderer.MediaType); + Assert.Equal(FakeRenderer.RenderedText, System.Text.Encoding.UTF8.GetString(output.ToArray())); } /// @@ -81,13 +95,18 @@ public void RendererRegistry_ResolveByExtension_MatchesAdvertisedExtensions() private sealed class FakeAlgorithm : ILayoutAlgorithm { + public const double PlacedWidth = 42.0; + public const double PlacedHeight = 24.0; + public string Id => "fake"; - public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) => new(0, 0, []); + public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) => new(PlacedWidth, PlacedHeight, []); } private sealed class FakeRenderer : IRenderer { + public const string RenderedText = "fake-render-output"; + public string MediaType => "text/plain"; public string DefaultExtension => ".txt"; @@ -96,7 +115,10 @@ private sealed class FakeRenderer : IRenderer public void Render(LayoutTree layout, RenderOptions options, Stream output) { - // No-op fake used only to exercise registry lookup. + // Write recognizable, deterministic output so tests can prove Render was actually invoked + // through the contract rather than only resolved by media type. + var bytes = System.Text.Encoding.UTF8.GetBytes(RenderedText); + output.Write(bytes, 0, bytes.Length); } } } diff --git a/test/DemaConsulting.Rendering.Gallery/GalleryCatalog.cs b/test/DemaConsulting.Rendering.Gallery/GalleryCatalog.cs index cc6c58c..48d66d5 100644 --- a/test/DemaConsulting.Rendering.Gallery/GalleryCatalog.cs +++ b/test/DemaConsulting.Rendering.Gallery/GalleryCatalog.cs @@ -37,6 +37,7 @@ internal static class GalleryCatalog public const string OrthogonalObstacleSvg = "orthogonal-obstacle.svg"; public const string DirectionRightSvg = "direction-right.svg"; public const string DirectionDownSvg = "direction-down.svg"; + public const string MixedDirectionNestedSvg = "mixed-direction-nested.svg"; public const string ThemeLightPng = "theme-light.png"; public const string ThemeDarkPng = "theme-dark.png"; public const string ThemePrintPng = "theme-print.png"; @@ -68,9 +69,10 @@ internal static class GalleryCatalog new GallerySection( "Flow direction", "The same directed graph laid out in two flow directions, selected with the direction " - + "option. A rightward flow arranges the layers left-to-right for block and pipeline " - + "diagrams; a downward flow arranges them top-to-bottom for action flows and state " - + "machines, swapping each node's width and height so layer spacing follows node height.", + + "option, plus a nested container overriding its own direction independently of its parent. " + + "A rightward flow arranges the layers left-to-right for block and pipeline diagrams; a " + + "downward flow arranges them top-to-bottom for action flows and state machines, swapping " + + "each node's width and height so layer spacing follows node height.", [ new GalleryImage( DirectionRightSvg, @@ -80,6 +82,11 @@ internal static class GalleryCatalog DirectionDownSvg, "The same directed flow laid out top to bottom", "The downward direction: the same graph's layers progress top-to-bottom."), + new GalleryImage( + MixedDirectionNestedSvg, + "A nested container flowing downward inside an outer rightward flow", + "A container's own direction override is honored independently of its parent: the " + + "outer flow runs left-to-right while the nested container runs top-to-bottom."), ]), new GallerySection( "Edge routing", diff --git a/test/DemaConsulting.Rendering.Gallery/GalleryDiagrams.cs b/test/DemaConsulting.Rendering.Gallery/GalleryDiagrams.cs index fec7865..236dc8e 100644 --- a/test/DemaConsulting.Rendering.Gallery/GalleryDiagrams.cs +++ b/test/DemaConsulting.Rendering.Gallery/GalleryDiagrams.cs @@ -169,6 +169,42 @@ public static LayoutGraph DirectionShowcase() return graph; } + /// + /// A container node whose own child graph overrides the flow direction to Down, nested inside an + /// outer graph that uses the default rightward direction. Direct edges connect the outer intake + /// and archive leaves straight to the container itself (not to its nested descendants), so the + /// root scope has real connectivity of its own and is genuinely laid out left-to-right, while the + /// container's nested chain is laid out top-to-bottom. This demonstrates that a container's own + /// override is honored independently of its parent's flow + /// direction. + /// + /// A two-level compound graph mixing flow directions across nesting levels. + public static LayoutGraph MixedDirectionNested() + { + var graph = new LayoutGraph(); + + var intake = AddLabelled(graph, "intake", "Intake"); + + var pipeline = graph.AddNode("pipeline", 10, 10); + pipeline.Label = "Pipeline"; + pipeline.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); + var validate = AddLabelled(pipeline.Children, "validate", "Validate"); + var transform = AddLabelled(pipeline.Children, "transform", "Transform"); + var publish = AddLabelled(pipeline.Children, "publish", "Publish"); + Connect(pipeline.Children, "validate-transform", validate, transform); + Connect(pipeline.Children, "transform-publish", transform, publish); + + var archive = AddLabelled(graph, "archive", "Archive"); + + // Direct root-level edges (both endpoints are direct members of the root scope, not nested + // descendants) so the outer scope has real connectivity and is genuinely laid out left-to-right, + // rather than falling back to disconnected-singleton packing. + Connect(graph, "intake-pipeline", intake, pipeline); + Connect(graph, "pipeline-archive", pipeline, archive); + + return graph; + } + /// Adds a labelled leaf node of the standard showcase size to the given graph. private static LayoutGraphNode AddLabelled(LayoutGraph graph, string id, string label) { diff --git a/test/DemaConsulting.Rendering.Gallery/GalleryShowcaseTests.cs b/test/DemaConsulting.Rendering.Gallery/GalleryShowcaseTests.cs index 6ec4d1d..23b8237 100644 --- a/test/DemaConsulting.Rendering.Gallery/GalleryShowcaseTests.cs +++ b/test/DemaConsulting.Rendering.Gallery/GalleryShowcaseTests.cs @@ -105,6 +105,21 @@ public void Gallery_DirectionDown_RendersSvg() Themes.Dark); } + /// + /// Renders the mixed-direction nested diagram, proving a container's own + /// override is honored independently of the outer graph's + /// flow direction: the outer flow runs rightward while the nested container runs downward. + /// + [Fact] + public void Gallery_MixedDirectionNested_RendersSvg() + { + GalleryWriter.Svg( + GalleryCatalog.MixedDirectionNestedSvg, + GalleryDiagrams.MixedDirectionNested(), + new LayoutOptions(), + Themes.Dark); + } + /// /// Renders the representative diagram with the light theme to PNG, giving it a solid light /// background. diff --git a/test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs b/test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs index ba112ed..2cef841 100644 --- a/test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs +++ b/test/DemaConsulting.Rendering.Layout.Tests/ContainmentLayoutAlgorithmTests.cs @@ -202,6 +202,38 @@ public void Apply_NullOptions_Throws() () => new ContainmentLayoutAlgorithm().Apply(new LayoutGraph(), null!)); } + /// + /// Proves that an explicit override carried on the graph + /// itself is honored (routing still succeeds) even when the supplied options declares no routing + /// style at all, mirroring how resolves + /// from the graph before falling back to the options. + /// Regression guard: previously this algorithm read options.Get(CoreOptions.EdgeRouting) + /// directly, silently ignoring any override carried on the graph itself — a genuine defect for a + /// standalone-callable algorithm, and for a scope inside + /// whose own graph carries the override. declares only one member + /// () today, so this cannot be an output-geometry-differing + /// test; it proves the graph-first resolution path is exercised without throwing and still + /// produces valid routed output. + /// + [Fact] + public void Apply_EdgeRoutingOverrideOnGraphScope_IsHonored() + { + // Arrange: a graph with an explicit EdgeRouting override and no such override on the options. + var graph = new LayoutGraph(); + var a = graph.AddNode("a", 60, 240); + graph.AddNode("mid", 60, 240); + var b = graph.AddNode("b", 60, 240); + graph.AddEdge("e", a, b); + graph.Set(CoreOptions.EdgeRouting, EdgeRouting.Orthogonal); + + // Act + var tree = new ContainmentLayoutAlgorithm().Apply(graph, new LayoutOptions()); + + // Assert: the edge is still routed using the graph's own (orthogonal) style. + var line = Assert.Single(tree.Nodes.OfType()); + Assert.True(line.Waypoints.Count >= 2); + } + /// /// Determines whether two boxes overlap with a positive-area intersection. /// diff --git a/test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs b/test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs index d2a4aa3..6d215c8 100644 --- a/test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs +++ b/test/DemaConsulting.Rendering.Layout.Tests/EdgeRoutingOptionTests.cs @@ -19,7 +19,7 @@ public sealed class EdgeRoutingOptionTests /// , the only shipped routing style. /// [Fact] - public void CoreOptions_EdgeRouting_DefaultsToOrthogonal() + public void CoreOptions_EdgeRouting_DefaultValue_IsOrthogonal() { // Assert: the well-known key advertises the orthogonal default Assert.Equal(EdgeRouting.Orthogonal, CoreOptions.EdgeRouting.DefaultValue); @@ -30,7 +30,7 @@ public void CoreOptions_EdgeRouting_DefaultsToOrthogonal() /// rendering.edgerouting. /// [Fact] - public void CoreOptions_EdgeRouting_HasStableId() + public void CoreOptions_EdgeRouting_Id_IsStableDottedIdentifier() { // Assert: the stable dotted id mirrors elk.edgeRouting Assert.Equal("rendering.edgerouting", CoreOptions.EdgeRouting.Id); @@ -41,7 +41,7 @@ public void CoreOptions_EdgeRouting_HasStableId() /// the key on any and read back, exactly like the algorithm key. /// [Fact] - public void CoreOptions_EdgeRouting_SelectablePerScope() + public void CoreOptions_EdgeRouting_SetThenGet_RoundTripsValue() { // Arrange: a free-standing options holder var options = new LayoutOptions(); @@ -59,7 +59,7 @@ public void CoreOptions_EdgeRouting_SelectablePerScope() /// select a style still route orthogonally. /// [Fact] - public void CoreOptions_EdgeRouting_UnsetReturnsDefault() + public void CoreOptions_EdgeRouting_UnsetHolder_ReturnsOrthogonalDefault() { // Arrange: an options holder with no routing selection var options = new LayoutOptions(); @@ -74,7 +74,7 @@ public void CoreOptions_EdgeRouting_UnsetReturnsDefault() /// clearance, and callers can override the clearance. /// [Fact] - public void ConnectorRouteOptions_Defaults_AreOrthogonalWithTwelvePixelClearance() + public void ConnectorRouteOptions_Constructor_Defaults_AreOrthogonalWithTwelvePixelClearance() { // Act: construct with defaults, then with an explicit clearance override var defaults = new ConnectorRouteOptions(); diff --git a/test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs b/test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs index 6980596..ea3b8b4 100644 --- a/test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs +++ b/test/DemaConsulting.Rendering.Layout.Tests/Engine/InterconnectionLayoutEngineTests.cs @@ -13,6 +13,36 @@ namespace DemaConsulting.Rendering.Layout.Tests.Engine; /// public sealed class InterconnectionLayoutEngineTests { + /// + /// A null node list is rejected before the layered pipeline is assembled so callers get a + /// clear argument-contract failure rather than a deeper routing exception. + /// + [Fact] + public void Place_NullNodes_ThrowsArgumentNullException() + { + // Arrange + IReadOnlyList nodes = null!; + var edges = new List(); + + // Act / Assert + Assert.Throws(() => InterconnectionLayoutEngine.Place(nodes, edges)); + } + + /// + /// A null edge list is rejected before any layering or routing work starts so the entry-point + /// contract stays explicit and deterministic. + /// + [Fact] + public void Place_NullEdges_ThrowsArgumentNullException() + { + // Arrange + var nodes = new List { new(60, 40) }; + IReadOnlyList edges = null!; + + // Act / Assert + Assert.Throws(() => InterconnectionLayoutEngine.Place(nodes, edges)); + } + /// /// A simple directed chain A(0)→B(1)→C(2) produces three monotonically increasing /// layer indices: 0, 1, 2. Longest-path layering assigns each node to the length @@ -49,6 +79,39 @@ public void Place_SingleEdge_ProducesStraightTwoWaypointPath() Assert.Equal(2, result.ConnectorWaypoints[0].Count); } + /// + /// A simple 3-cycle is broken into an acyclic edge set by reversing exactly one back edge, and + /// the returned waypoint list stays index-aligned with that retained edge set. + /// + [Fact] + public void Place_CyclicGraph_ReversesBackEdgeAndProducesWaypoint() + { + // Arrange: a 3-cycle requires one retained edge to be reversed to become acyclic. + var nodes = new List { new(60, 40), new(60, 40), new(60, 40) }; + var edges = new List { new(0, 1), new(1, 2), new(2, 0) }; + var originalEdges = edges.ToHashSet(); + + // Act + var result = InterconnectionLayoutEngine.Place(nodes, edges); + + // Assert: the retained acyclic edge set stays index-aligned with the waypoint list. + Assert.Equal(result.AcyclicEdges.Count, result.ConnectorWaypoints.Count); + Assert.Equal(3, result.AcyclicEdges.Count); + + var reversedEntry = result.AcyclicEdges + .Select((edge, index) => new { edge, index }) + .Single(entry => + !originalEdges.Contains(entry.edge) && + originalEdges.Any(original => + original.Source == entry.edge.Target && + original.Target == entry.edge.Source)); + + Assert.True(reversedEntry.index >= 0); + Assert.True( + result.ConnectorWaypoints[reversedEntry.index].Count >= 2, + "Expected the reversed retained edge to have a routed waypoint list."); + } + /// /// The rect count always equals the number of input nodes — long edges use dummy-node /// routing through intermediate layer gaps, not additional real boxes. @@ -189,6 +252,62 @@ public void Place_DownDirection_TransposesTotalsRelativeToRight() Assert.True(down.Rects[1].Y < down.Rects[2].Y); } + /// + /// Omitting the optional direction argument preserves the established rightward default so + /// existing callers see identical geometry to an explicit . + /// + [Fact] + public void Place_DefaultDirection_MatchesRightFlow() + { + // Arrange + var nodes = new List { new(80, 40), new(80, 40), new(80, 40) }; + var edges = new List { new(0, 1), new(1, 2) }; + + // Act + var @default = InterconnectionLayoutEngine.Place(nodes, edges); + var right = InterconnectionLayoutEngine.Place(nodes, edges, LayoutDirection.Right); + + // Assert + Assert.Equal(right.TotalWidth, @default.TotalWidth); + Assert.Equal(right.TotalHeight, @default.TotalHeight); + Assert.Equal(right.NodeLayers, @default.NodeLayers); + Assert.Equal(right.Rects, @default.Rects); + Assert.Equal(right.AcyclicEdges, @default.AcyclicEdges); + Assert.Equal(right.ConnectorWaypoints.Count, @default.ConnectorWaypoints.Count); + for (var i = 0; i < right.ConnectorWaypoints.Count; i++) + { + Assert.Equal(right.ConnectorWaypoints[i], @default.ConnectorWaypoints[i]); + } + } + + /// + /// Re-running the engine with identical input produces identical rects, totals, retained + /// acyclic edges, layer indices, and connector waypoints. + /// + [Fact] + public void Place_RepeatedInvocation_ProducesIdenticalGeometry() + { + // Arrange + var nodes = new List { new(80, 40), new(80, 40), new(80, 40), new(80, 40) }; + var edges = new List { new(0, 1), new(0, 2), new(1, 3), new(2, 3), new(0, 3) }; + + // Act + var first = InterconnectionLayoutEngine.Place(nodes, edges); + var second = InterconnectionLayoutEngine.Place(nodes, edges); + + // Assert + Assert.Equal(first.TotalWidth, second.TotalWidth); + Assert.Equal(first.TotalHeight, second.TotalHeight); + Assert.Equal(first.NodeLayers, second.NodeLayers); + Assert.Equal(first.Rects, second.Rects); + Assert.Equal(first.AcyclicEdges, second.AcyclicEdges); + Assert.Equal(first.ConnectorWaypoints.Count, second.ConnectorWaypoints.Count); + for (var i = 0; i < first.ConnectorWaypoints.Count; i++) + { + Assert.Equal(first.ConnectorWaypoints[i], second.ConnectorWaypoints[i]); + } + } + private static bool Overlaps(Rect a, Rect b) => a.X < b.X + b.Width && b.X < a.X + a.Width && diff --git a/test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/ComponentPackerTests.cs b/test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/ComponentPackerTests.cs index 622e8dc..0b0628a 100644 --- a/test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/ComponentPackerTests.cs +++ b/test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/ComponentPackerTests.cs @@ -255,6 +255,68 @@ public void ComponentPacker_Apply_MultiComponent_ParallelAndSelfEdges_MergesAlig AssertEndpointsOnBoxes(graph, routed[(2, 3)], 2, 3, eps); } + /// + /// Two disconnected components, each containing a short back-edge cycle, propagate the parent + /// graph's to every packed child component. + /// Regression test: previously LayoutComponent built each child + /// without copying the parent's BackEdgeEntryApproach, so a caller-configured clearance + /// silently reverted to the class default for any multi-component (packed) graph. + /// + [Fact] + public void ComponentPacker_Apply_MultiComponent_PropagatesBackEdgeEntryApproach() + { + // Arrange: two 3-node triangle components, each with a cycle (0->1, 1->2, 2->0) so CycleBreaker + // reverses the closing edge into a long back edge (skipping a layer via a dummy node), whose + // routed corridor width is governed by BackEdgeEntryApproach. + static LayeredGraph Build(double backEdgeEntryApproach) + { + var nodes = new List + { + new(NodeWidth, NodeHeight), new(NodeWidth, NodeHeight), new(NodeWidth, NodeHeight), + new(NodeWidth, NodeHeight), new(NodeWidth, NodeHeight), new(NodeWidth, NodeHeight), + }; + var edges = new List { new(0, 1), new(1, 2), new(2, 0), new(3, 4), new(4, 5), new(5, 3) }; + return new LayeredGraph(nodes, edges, LayoutDirection.Right) { BackEdgeEntryApproach = backEdgeEntryApproach }; + } + + var small = Build(LayeredLayoutMetrics.ConnectorClearance); + var large = Build(LayeredLayoutMetrics.ConnectorClearance * 10.0); + + // Act. + ComponentPacker.WithDefaultStages().Apply(small); + ComponentPacker.WithDefaultStages().Apply(large); + + // Assert: each reversed (back) edge's first routed bend point is pushed further out under the + // larger BackEdgeEntryApproach, proving each packed child component honored the parent graph's + // configured clearance instead of always reverting to the LayeredGraph default. + Assert.True(MaxBackEdgeBendX(large) > MaxBackEdgeBendX(small)); + } + + /// Finds the furthest first-bend-point X coordinate among a graph's reversed (back) edges. + /// The laid-out graph. + /// The maximum X coordinate of the first bend point across all reversed edges. + private static double MaxBackEdgeBendX(LayeredGraph graph) + { + var best = double.NegativeInfinity; + for (var i = 0; i < graph.AcyclicReversed.Length; i++) + { + if (!graph.AcyclicReversed[i]) + { + continue; + } + + var waypoints = graph.Waypoints[i]; + if (waypoints.Count < 2) + { + continue; + } + + best = Math.Max(best, waypoints[1].X); + } + + return best; + } + /// Builds a (source, target) to polyline lookup over a graph's acyclic edge set. /// The laid-out graph. /// A dictionary keyed by each acyclic edge's endpoints. diff --git a/test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs b/test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs index 16479f8..99a1734 100644 --- a/test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs +++ b/test/DemaConsulting.Rendering.Layout.Tests/HierarchicalLayoutAlgorithmTests.cs @@ -3,6 +3,7 @@ // using DemaConsulting.Rendering; +using DemaConsulting.Rendering.Abstractions; namespace DemaConsulting.Rendering.Layout.Tests; @@ -168,6 +169,186 @@ public void Apply_LayeredRootWithContainmentContainer_Composes() Assert.Equal(3, containerBox.Children.OfType().Count()); } + /// + /// Proves that a container scope's own override is honored by + /// the leaf algorithm laying out its children, even though the engine builds a fresh sized-view + /// graph for that scope. This exercises the generalized cascading mechanism (): + /// the container's own graph override is overlaid onto its + /// parent scope's resolved options to produce the effective snapshot passed to the leaf algorithm. + /// + [Fact] + public void Apply_ContainerWithDirectionOverride_HonorsNestedDirection() + { + // Arrange: a labelled container whose children graph overrides Direction to Down, while the + // top-level options select the (default) Right direction. + var graph = new LayoutGraph(); + var group = graph.AddNode("group", 10, 10); + group.Label = "Group"; + group.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); + var a = group.Children.AddNode("a", 80, 40); + var b = group.Children.AddNode("b", 80, 40); + var c = group.Children.AddNode("c", 80, 40); + group.Children.AddEdge("a-b", a, b); + group.Children.AddEdge("b-c", b, c); + graph.AddNode("outside", 80, 40); + + // Act + var tree = new HierarchicalLayoutAlgorithm().Apply(graph, LayoutOptions.ForAlgorithm("layered")); + + // Assert: the nested chain stacks vertically (strictly increasing Y), proving the container's own + // Down override was honored rather than falling back to the top-level Right default. + var containerBox = Assert.Single(tree.Nodes.OfType(), box => box.Children.Count > 0); + var nestedBoxes = containerBox.Children.OfType().ToList(); + Assert.Equal(3, nestedBoxes.Count); + Assert.True(nestedBoxes[0].Y < nestedBoxes[1].Y); + Assert.True(nestedBoxes[1].Y < nestedBoxes[2].Y); + } + + /// + /// Proves that a override set two levels up cascades through + /// an intermediate container that sets nothing of its own, reaching a third-level leaf chain. + /// This is the multi-level cascade the single-level regression test above cannot exercise. + /// + [Fact] + public void Apply_ThreeLevelDirectionCascade_InheritsThroughUnsetMiddleLevel() + { + // Arrange: root (unset) -> level1 container (Children.Set(Direction, Down)) -> level2 container + // (unset) -> level3 leaf chain. + var graph = new LayoutGraph(); + var level1 = graph.AddNode("level1", 10, 10); + level1.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); + var level2 = level1.Children.AddNode("level2", 10, 10); + var leafA = level2.Children.AddNode("leafA", 80, 40); + var leafB = level2.Children.AddNode("leafB", 80, 40); + var leafC = level2.Children.AddNode("leafC", 80, 40); + level2.Children.AddEdge("a-b", leafA, leafB); + level2.Children.AddEdge("b-c", leafB, leafC); + + // Act + var tree = new HierarchicalLayoutAlgorithm().Apply(graph, LayoutOptions.ForAlgorithm("layered")); + + // Assert: level3 leaves stack vertically, proving Down cascaded through the unset level2 scope. + var outerBox = Assert.Single(tree.Nodes.OfType()); + var middleBox = Assert.Single(outerBox.Children.OfType()); + var leafBoxes = middleBox.Children.OfType().ToList(); + Assert.Equal(3, leafBoxes.Count); + Assert.True(leafBoxes[0].Y < leafBoxes[1].Y); + Assert.True(leafBoxes[1].Y < leafBoxes[2].Y); + } + + /// + /// Proves that a deeper, explicit override takes precedence + /// over an ancestor's own override, rather than the ancestor's value winning because it is set + /// first or higher in the tree. Nearest-ancestor-override-wins, not first-set-wins. + /// + [Fact] + public void Apply_ThreeLevelDirectionCascade_MidLevelOverrideTakesPrecedence() + { + // Arrange: same shape as the inherit-through-unset test, but level2 sets its own explicit + // Direction override (Right), which must win over level1's inherited Down for level2's children. + var graph = new LayoutGraph(); + var level1 = graph.AddNode("level1", 10, 10); + level1.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Down); + var level2 = level1.Children.AddNode("level2", 10, 10); + level2.Children.Set(CoreOptions.Direction, LayoutFlowDirection.Right); + var leafA = level2.Children.AddNode("leafA", 80, 40); + var leafB = level2.Children.AddNode("leafB", 80, 40); + var leafC = level2.Children.AddNode("leafC", 80, 40); + level2.Children.AddEdge("a-b", leafA, leafB); + level2.Children.AddEdge("b-c", leafB, leafC); + + // Act + var tree = new HierarchicalLayoutAlgorithm().Apply(graph, LayoutOptions.ForAlgorithm("layered")); + + // Assert: level3 leaves flow horizontally, not vertically, proving level2's own Right override + // took precedence over level1's inherited Down. + var outerBox = Assert.Single(tree.Nodes.OfType()); + var middleBox = Assert.Single(outerBox.Children.OfType()); + var leafBoxes = middleBox.Children.OfType().ToList(); + Assert.Equal(3, leafBoxes.Count); + Assert.True(leafBoxes[0].X < leafBoxes[1].X); + Assert.True(leafBoxes[1].X < leafBoxes[2].X); + } + + /// + /// Proves that the per-scope cascaded effective options snapshot — built by overlaying each + /// scope's own overrides via — actually reaches every + /// leaf-algorithm invocation, at three levels of nesting, using a recording test double that + /// captures the exact instance each scope was placed with. Because + /// declares only one member () + /// today, a resolved value alone cannot distinguish "inherited" from "never set"; this test pairs + /// the real cascade with an arbitrary custom marker property + /// set at the same scopes, which does have distinguishable values, to prove both that + /// is carried through every level's effective snapshot and + /// that a deeper scope's own override of another property wins over an ancestor's, at the exact + /// effective-options instance each leaf-algorithm call receives. + /// + [Fact] + public void Apply_ThreeLevelEdgeRoutingCascade_ReachesEveryLeafAlgorithmCall() + { + // Arrange: root -> level1 container (Children overrides EdgeRouting and a custom Marker) -> + // level2 container (Children overrides only the Marker, not EdgeRouting) -> level3 leaves. A + // recording algorithm captures the graph/options pair passed to every leaf-algorithm invocation. + var marker = new LayoutProperty("test.cascade.marker", null); + var recorder = new RecordingLayoutAlgorithm(new LayeredLayoutAlgorithm()); + var registry = new LayoutAlgorithmRegistry() + .Register(recorder) + .Register(new ContainmentLayoutAlgorithm()); + + var graph = new LayoutGraph(); + var level1 = graph.AddNode("level1", 10, 10); + level1.Children.Set(CoreOptions.EdgeRouting, EdgeRouting.Orthogonal); + level1.Children.Set(marker, "level1"); + var level2 = level1.Children.AddNode("level2", 10, 10); + level2.Children.Set(marker, "level2"); + var leafA = level2.Children.AddNode("leafA", 80, 40); + var leafB = level2.Children.AddNode("leafB", 80, 40); + level2.Children.AddEdge("a-b", leafA, leafB); + + // Act + _ = new HierarchicalLayoutAlgorithm(registry).Apply(graph, LayoutOptions.ForAlgorithm("layered")); + + // Assert: the leaf-scope call (level2's own children graph, the flat fast path) received the + // EdgeRouting override cascaded down from level1, and its own Marker override, not level1's. + var leafScopeCall = Assert.Single(recorder.Calls, call => ReferenceEquals(call.Graph, level2.Children)); + Assert.Equal(EdgeRouting.Orthogonal, leafScopeCall.Options.Get(CoreOptions.EdgeRouting)); + Assert.Equal("level2", leafScopeCall.Options.Get(marker)); + + // The level1-scope call (placing level2's box within level1's sized view) saw level1's own Marker + // value, proving each scope's effective snapshot is distinct rather than one shared reference. + var level1ScopeCall = Assert.Single(recorder.Calls, call => call.Options.Get(marker) == "level1"); + Assert.Equal(EdgeRouting.Orthogonal, level1ScopeCall.Options.Get(CoreOptions.EdgeRouting)); + } + + /// + /// Proves that 's cross-container-edge routing (owned by + /// ) uses the cascaded scope's + /// rather than the root options, by overriding it away from the root's own value at the scope that + /// owns the cross-container edge and confirming the routed line still appears (the recording + /// algorithm on its own only observes leaf-algorithm calls, not the cross-container routing path, + /// so this test exercises the composed public behavior directly instead). + /// + [Fact] + public void Apply_CrossContainerEdge_HonorsScopeEdgeRoutingOverride() + { + // Arrange: a scope whose own graph overrides EdgeRouting away from the root options' value. + var graph = new LayoutGraph(); + var a = graph.AddNode("A", 10, 10); + var b = graph.AddNode("B", 10, 10); + var aChild = a.Children.AddNode("a-child", 60, 40); + var bChild = b.Children.AddNode("b-child", 60, 40); + graph.Set(CoreOptions.EdgeRouting, EdgeRouting.Orthogonal); + graph.AddEdge("cross", aChild, bChild); + + // Act + var tree = new HierarchicalLayoutAlgorithm().Apply(graph, LayoutOptions.ForAlgorithm("containment")); + + // Assert: the cross-container edge is still routed (the scope's own EdgeRouting override, + // cascaded via the effective snapshot, was consulted rather than being silently ignored). + var line = Assert.Single(tree.Nodes.OfType()); + Assert.True(line.Waypoints.Count >= 2); + } + /// /// Proves that a cross-container edge — one whose endpoints live inside different sibling /// containers — is routed at the owning scope around the intervening container rather than @@ -572,4 +753,27 @@ private static bool SegmentCrossesRect(Point2D a, Point2D b, Rect r) return r.X < x && x < r.X + r.Width && Math.Max(ya, r.Y) < Math.Min(yb, r.Y + r.Height); } + + /// + /// A leaf-algorithm test double that records the exact and + /// instance every call receives, then delegates + /// to a real inner algorithm so the composed layout remains valid. It advertises the inner + /// algorithm's own so it can be registered in its place, transparently observing + /// every scope places with that algorithm identifier. + /// + private sealed class RecordingLayoutAlgorithm(ILayoutAlgorithm inner) : ILayoutAlgorithm + { + /// The graph/options pair captured from every invocation, in call order. + public List<(LayoutGraph Graph, LayoutOptions Options)> Calls { get; } = []; + + /// + public string Id => inner.Id; + + /// + public LayoutTree Apply(LayoutGraph graph, LayoutOptions options) + { + Calls.Add((graph, options)); + return inner.Apply(graph, options); + } + } } diff --git a/test/DemaConsulting.Rendering.Skia.Tests/PngEndMarkerTests.cs b/test/DemaConsulting.Rendering.Skia.Tests/PngEndMarkerTests.cs index 7135f4a..71bfad0 100644 --- a/test/DemaConsulting.Rendering.Skia.Tests/PngEndMarkerTests.cs +++ b/test/DemaConsulting.Rendering.Skia.Tests/PngEndMarkerTests.cs @@ -67,15 +67,18 @@ private static LayoutTree HorizontalLineTo(EndMarkerStyle target) [Fact] public void OpenChevron_HasFewerInkPixelsThanClosedTriangle() { + // Arrange var options = new RenderOptions(Themes.Light); using var chevron = RenderToBitmap(HorizontalLineTo(EndMarkerStyle.OpenChevron), options); using var triangle = RenderToBitmap(HorizontalLineTo(EndMarkerStyle.HollowTriangle), options); + // Act // Marker zone: the back edge of the triangle marker sits near x=141 (refX 9 back from the // x=150 endpoint). Count ink strictly behind the shaft join so the base edge is isolated. var chevronInk = CountInk(chevron, 138, 42, 144, 58); var triangleInk = CountInk(triangle, 138, 42, 144, 58); + // Assert Assert.True( chevronInk < triangleInk, $"Expected open chevron ({chevronInk}) to have fewer base-edge ink pixels than closed triangle ({triangleInk})."); @@ -90,9 +93,11 @@ public void OpenChevron_HasFewerInkPixelsThanClosedTriangle() [Fact] public void FilledArrow_AlongLength_MatchesNotationMetrics() { + // Arrange var options = new RenderOptions(Themes.Light); using var bmp = RenderToBitmap(HorizontalLineTo(EndMarkerStyle.FilledArrow), options); + // Act // For each column near the endpoint, measure the vertical ink extent. The base column has the // greatest extent (full marker width); the tip is the furthest column carrying marker ink. var baseX = -1; @@ -126,6 +131,7 @@ public void FilledArrow_AlongLength_MatchesNotationMetrics() tipX = x; // furthest column with any ink } + // Assert Assert.True(baseX >= 0, "Expected to locate the marker base column."); var alongLength = tipX - baseX; @@ -140,9 +146,11 @@ public void FilledArrow_AlongLength_MatchesNotationMetrics() [Fact] public void FilledArrow_BaseWidth_MatchesNotationMetrics() { + // Arrange var options = new RenderOptions(Themes.Light); using var bmp = RenderToBitmap(HorizontalLineTo(EndMarkerStyle.FilledArrow), options); + // Act // Base column: refX (9) back from the x=150 endpoint => x≈141. var baseX = (int)Math.Round(150 - NotationMetrics.EndMarkerRefX); var minY = int.MaxValue; @@ -156,6 +164,7 @@ public void FilledArrow_BaseWidth_MatchesNotationMetrics() } } + // Assert Assert.True(maxY >= minY, "Expected ink at the marker base column."); var measuredWidth = maxY - minY + 1; diff --git a/test/DemaConsulting.Rendering.Skia.Tests/PngRendererTests.cs b/test/DemaConsulting.Rendering.Skia.Tests/PngRendererTests.cs index b848246..9a90c71 100644 --- a/test/DemaConsulting.Rendering.Skia.Tests/PngRendererTests.cs +++ b/test/DemaConsulting.Rendering.Skia.Tests/PngRendererTests.cs @@ -18,6 +18,7 @@ public class PngRendererTests [Fact] public void Render_SingleBox_ProducesPngSignature() { + // Arrange var tree = new LayoutTree(100, 60, new LayoutNode[] { new LayoutBox(10, 10, 80, 40, "Box", 0, BoxShape.Rectangle, [], []), @@ -25,8 +26,10 @@ public void Render_SingleBox_ProducesPngSignature() var renderer = new PngRenderer(); using var stream = new MemoryStream(); + // Act renderer.Render(tree, new RenderOptions(Themes.Light), stream); + // Assert var bytes = stream.ToArray(); Assert.True(bytes.Length > 8); diff --git a/test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs b/test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs index 78a86e7..d453add 100644 --- a/test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs +++ b/test/DemaConsulting.Rendering.Skia.Tests/SkiaFormatRendererTests.cs @@ -27,11 +27,14 @@ public class SkiaFormatRendererTests [Fact] public void JpegRenderer_Render_ProducesJpegSignature() { + // Arrange var renderer = new JpegRenderer(); using var stream = new MemoryStream(); + // Act renderer.Render(SampleTree(), new RenderOptions(Themes.Light), stream); + // Assert var bytes = stream.ToArray(); Assert.True(bytes.Length > 3); @@ -51,11 +54,14 @@ public void JpegRenderer_Render_ProducesJpegSignature() [Fact] public void WebpRenderer_Render_ProducesWebpContainerHeader() { + // Arrange var renderer = new WebpRenderer(); using var stream = new MemoryStream(); + // Act renderer.Render(SampleTree(), new RenderOptions(Themes.Light), stream); + // Assert var bytes = stream.ToArray(); Assert.True(bytes.Length > 12); @@ -64,6 +70,7 @@ public void WebpRenderer_Render_ProducesWebpContainerHeader() Assert.Equal("WEBP", Encoding.ASCII.GetString(bytes, 8, 4)); Assert.Equal("image/webp", renderer.MediaType); Assert.Equal(".webp", renderer.DefaultExtension); + Assert.Contains(".webp", renderer.FileExtensions); } /// @@ -72,8 +79,10 @@ public void WebpRenderer_Render_ProducesWebpContainerHeader() [Fact] public void PngRenderer_FileExtensions_ContainsDefault() { + // Arrange var renderer = new PngRenderer(); + // Act / Assert Assert.Contains(renderer.DefaultExtension, renderer.FileExtensions); Assert.Equal(".png", renderer.DefaultExtension); } diff --git a/test/DemaConsulting.Rendering.Svg.Tests/SvgEndMarkerTests.cs b/test/DemaConsulting.Rendering.Svg.Tests/SvgEndMarkerTests.cs index 80d10b6..b0da6ee 100644 --- a/test/DemaConsulting.Rendering.Svg.Tests/SvgEndMarkerTests.cs +++ b/test/DemaConsulting.Rendering.Svg.Tests/SvgEndMarkerTests.cs @@ -112,6 +112,38 @@ public void OpenChevronLine_ReferencesOpenChevronMarker() Assert.Contains("marker-end=\"url(#line-end-open-chevron)\"", svg, StringComparison.Ordinal); } + /// A filled-arrow line references the filled-arrow marker via marker-end. + [Fact] + public void SvgRenderer_Render_SingleLine_WithFilledArrow_ProducesFilledArrowMarker() + { + var svg = RenderLine(EndMarkerStyle.None, EndMarkerStyle.FilledArrow); + Assert.Contains("marker-end=\"url(#line-end-filled-arrow)\"", svg, StringComparison.Ordinal); + } + + /// A filled-diamond line references the filled-diamond marker via marker-start. + [Fact] + public void SvgRenderer_Render_SingleLine_WithFilledDiamond_ProducesFilledDiamondMarker() + { + var svg = RenderLine(EndMarkerStyle.FilledDiamond, EndMarkerStyle.None); + Assert.Contains("marker-start=\"url(#line-end-filled-diamond)\"", svg, StringComparison.Ordinal); + } + + /// A circle-ended line references the circle marker via marker-end. + [Fact] + public void SvgRenderer_Render_SingleLine_WithCircleEnd_ProducesCircleMarker() + { + var svg = RenderLine(EndMarkerStyle.None, EndMarkerStyle.Circle); + Assert.Contains("marker-end=\"url(#line-end-circle)\"", svg, StringComparison.Ordinal); + } + + /// A bar-ended line references the bar marker via marker-end. + [Fact] + public void SvgRenderer_Render_SingleLine_WithBarEnd_ProducesBarMarker() + { + var svg = RenderLine(EndMarkerStyle.None, EndMarkerStyle.Bar); + Assert.Contains("marker-end=\"url(#line-end-bar)\"", svg, StringComparison.Ordinal); + } + private static string Num(double value) => Math.Round(value, 6).ToString(System.Globalization.CultureInfo.InvariantCulture); } diff --git a/test/DemaConsulting.Rendering.Svg.Tests/SvgRendererPortedTests.cs b/test/DemaConsulting.Rendering.Svg.Tests/SvgRendererPortedTests.cs index 6204e2c..239abe7 100644 --- a/test/DemaConsulting.Rendering.Svg.Tests/SvgRendererPortedTests.cs +++ b/test/DemaConsulting.Rendering.Svg.Tests/SvgRendererPortedTests.cs @@ -322,6 +322,100 @@ public void SvgRenderer_Render_SingleBadge_FilledCircle_ProducesCircle() Assert.Contains(" + /// Render a LayoutBadge with Bullseye shape produces SVG output containing two circle + /// elements, confirming that the bullseye ring is rendered as concentric circles. + /// + [Fact] + public void SvgRenderer_Render_SingleBadge_Bullseye_ProducesConcentricCircles() + { + // Arrange: a bullseye badge + var renderer = new SvgRenderer(); + var badge = new LayoutBadge(50, 50, 12, BadgeShape.Bullseye, "I"); + var layout = new LayoutTree(200, 100, [badge]); + var options = new RenderOptions(Themes.Light); + using var output = new MemoryStream(); + + // Act + renderer.Render(layout, options, output); + + // Assert + output.Position = 0; + var body = BodyAfterDefs(ReadAllText(output)); + Assert.True( + body.Split("= 2, + "Expected the bullseye badge to render two circle elements."); + } + + /// + /// Render a LayoutBadge with Diamond shape produces SVG output containing a polygon element, + /// confirming that the badge renders as a diamond outline. + /// + [Fact] + public void SvgRenderer_Render_SingleBadge_Diamond_ProducesPolygon() + { + // Arrange: a diamond badge + var renderer = new SvgRenderer(); + var badge = new LayoutBadge(50, 50, 12, BadgeShape.Diamond, "I"); + var layout = new LayoutTree(200, 100, [badge]); + var options = new RenderOptions(Themes.Light); + using var output = new MemoryStream(); + + // Act + renderer.Render(layout, options, output); + + // Assert + output.Position = 0; + var body = BodyAfterDefs(ReadAllText(output)); + Assert.Contains(" + /// Render a LayoutBadge with HorizontalBar shape produces SVG output containing a line + /// element, confirming that the badge renders as a horizontal bar. + /// + [Fact] + public void SvgRenderer_Render_SingleBadge_HorizontalBar_ProducesLine() + { + // Arrange: a horizontal-bar badge + var renderer = new SvgRenderer(); + var badge = new LayoutBadge(50, 50, 12, BadgeShape.HorizontalBar, "I"); + var layout = new LayoutTree(200, 100, [badge]); + var options = new RenderOptions(Themes.Light); + using var output = new MemoryStream(); + + // Act + renderer.Render(layout, options, output); + + // Assert + output.Position = 0; + var body = BodyAfterDefs(ReadAllText(output)); + Assert.Contains(" + /// Render a LayoutBadge with VerticalBar shape produces SVG output containing a line + /// element, confirming that the badge renders as a vertical bar. + /// + [Fact] + public void SvgRenderer_Render_SingleBadge_VerticalBar_ProducesLine() + { + // Arrange: a vertical-bar badge + var renderer = new SvgRenderer(); + var badge = new LayoutBadge(50, 50, 12, BadgeShape.VerticalBar, "I"); + var layout = new LayoutTree(200, 100, [badge]); + var options = new RenderOptions(Themes.Light); + using var output = new MemoryStream(); + + // Act + renderer.Render(layout, options, output); + + // Assert + output.Position = 0; + var body = BodyAfterDefs(ReadAllText(output)); + Assert.Contains(" /// Render a LayoutBand produces SVG output containing a rect element, /// confirming that swim-lane bands are rendered as rectangles. @@ -568,4 +662,17 @@ private static string ReadAllText(Stream stream) using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true); return reader.ReadToEnd(); } + + /// + /// Returns the SVG body content that follows the shared <defs> marker-definitions + /// block, so element-shape assertions (circle/polygon/line, etc.) verify the actual badge + /// rendering rather than incidentally matching the same element types used by end markers. + /// + /// The full rendered SVG document text. + /// The substring of after the closing </defs> tag. + private static string BodyAfterDefs(string svgText) + { + var index = svgText.IndexOf("", StringComparison.Ordinal); + return index < 0 ? svgText : svgText[(index + "".Length)..]; + } } diff --git a/test/DemaConsulting.Rendering.Tests/PropertyHolderTests.cs b/test/DemaConsulting.Rendering.Tests/PropertyHolderTests.cs index 1d3cd6d..58bdca6 100644 --- a/test/DemaConsulting.Rendering.Tests/PropertyHolderTests.cs +++ b/test/DemaConsulting.Rendering.Tests/PropertyHolderTests.cs @@ -173,4 +173,100 @@ public void Contains_NullProperty_ThrowsArgumentNullException() // Act / Assert: a null property argument is rejected Assert.Throws(() => holder.Contains(null!)); } + + /// + /// Proves that overlaying an empty holder onto a populated parent returns the parent's values + /// unchanged, so a scope with no overrides of its own fully inherits its parent's snapshot. + /// + [Fact] + public void OverlayOnto_EmptyHolderOntoPopulatedParent_ReturnsParentValuesUnchanged() + { + // Arrange: a populated parent and an empty overlaying holder + var parent = new PropertyHolder(); + parent.Set(Count, 42); + parent.Set(Name, "parent-name"); + var holder = new PropertyHolder(); + + // Act: overlay the empty holder onto the populated parent + var effective = holder.OverlayOnto(parent); + + // Assert: the parent's values pass through unchanged + Assert.Equal(42, effective.Get(Count)); + Assert.Equal("parent-name", effective.Get(Name)); + } + + /// + /// Proves that the overlaying holder's own explicit value wins over the parent's value for the + /// same property, the core precedence rule behind nearest-ancestor-override cascading. + /// + [Fact] + public void OverlayOnto_HolderOverridesProperty_HolderValueWins() + { + // Arrange: a parent with a value, and an overlaying holder that overrides it + var parent = new PropertyHolder(); + parent.Set(Count, 1); + var holder = new PropertyHolder(); + holder.Set(Count, 99); + + // Act: overlay the holder onto the parent + var effective = holder.OverlayOnto(parent); + + // Assert: the overlaying holder's own value takes precedence + Assert.Equal(99, effective.Get(Count)); + } + + /// + /// Proves that a value present only on the parent (and not overridden by the overlaying holder) + /// passes through into the effective snapshot. + /// + [Fact] + public void OverlayOnto_ValueOnlyOnParent_PassesThrough() + { + // Arrange: a parent with a value the overlaying holder never sets + var parent = new PropertyHolder(); + parent.Set(Name, "only-on-parent"); + var holder = new PropertyHolder(); + holder.Set(Count, 5); + + // Act: overlay the holder onto the parent + var effective = holder.OverlayOnto(parent); + + // Assert: both the parent-only and holder-only values are present in the merged snapshot + Assert.Equal("only-on-parent", effective.Get(Name)); + Assert.Equal(5, effective.Get(Count)); + } + + /// + /// Proves that OverlayOnto is fully generic: it merges an arbitrary custom property that is not + /// part of CoreOptions, without any per-property code. + /// + [Fact] + public void OverlayOnto_CustomPropertyNotInCoreOptions_IsMerged() + { + // Arrange: a custom property unknown to CoreOptions, overridden by the overlaying holder + var custom = new LayoutProperty("custom.arbitrary.property", 0.0); + var parent = new PropertyHolder(); + parent.Set(custom, 1.5); + var holder = new PropertyHolder(); + holder.Set(custom, 2.5); + + // Act: overlay the holder onto the parent + var effective = holder.OverlayOnto(parent); + + // Assert: the overlaying holder's value for the arbitrary property wins + Assert.Equal(2.5, effective.Get(custom)); + } + + /// + /// Proves that OverlayOnto rejects a null parent. + /// + [Fact] + public void OverlayOnto_NullParent_ThrowsArgumentNullException() + { + // Arrange: create an empty holder + var holder = new PropertyHolder(); + + // Act / Assert: a null parent argument is rejected + Assert.Throws(() => holder.OverlayOnto(null!)); + } }