Files
marki-docx/workplans/MRKD-WP-0005-diagram-renderer-integration.md
Bernd Worsch 039420caee chore: add workplans WP-0004, WP-0005, WP-0006
WP-0004: Stable Documentation Corpus & Architecture Records
WP-0005: Diagram Renderer Integration
WP-0006: Packaging & Distribution

State-hub workstreams and tasks created; local_path registration deferred
pending path mismatch fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:55:34 +00:00

186 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: MRKD-WP-0005
type: workplan
domain: markitect
repo: marki-docx
status: active
state_hub_workstream_id: 2ef47f11-d828-436d-8955-c58e13c50752
created: 2026-03-16
updated: 2026-03-16
---
# MRKD-WP-0005 — Diagram Renderer Integration
Complete the deferred rendering path for FR-533/534. Currently `diagrams.py` operates
in source-only mode: diagram fenced blocks are preserved as source through the round-trip
but are never rendered to images. This workstream adds actual rendering support for
Mermaid, Graphviz, and PlantUML when the corresponding CLI tools are available, while
preserving the graceful source-only fallback when they are not.
**Scope:** FR-533 (auto-diagram support), FR-534 (diagram intent preservation),
FR-538 (processor-dependency disclosure)
**Out of scope:** new diagram syntaxes beyond mermaid/graphviz/plantuml
**Depends on:** MRKD-WP-0003 (diagrams.py source-only foundation) — complete
---
## T01 — Renderer abstraction layer and detection
```task
id: MRKD-WP-0005-T01
status: todo
priority: high
state_hub_task_id: c4911ecc-1e3c-4d22-a6fb-92d1ed319274
```
Introduce a `RendererBackend` abstraction in `diagrams.py` so that each diagram type
can be rendered by a pluggable backend. The builder calls the abstraction; concrete
backends handle tool detection and subprocess invocation.
- `DiagramRenderer` protocol: `can_render(diagram_type: str) -> bool`,
`render(source: str, diagram_type: str, output_path: Path) -> RendererResult`
- `RendererResult`: `success: bool`, `output_path: Path | None`, `warning: WarningRecord | None`
- `detect_renderers() -> dict[str, DiagramRenderer]` — probe PATH for `mmdc`, `dot`,
`plantuml`; return only those found
- Builder updated to call `detect_renderers()` at build time; if a renderer is found
for the diagram type → render to PNG and embed; if not → source-only fallback +
`WarningRecord(reason="renderer-unavailable", construct=diagram_type)` (FR-538)
- Unit tests: `test_renderer_detection` — mock PATH; `test_fallback_when_no_renderer`
Deliverable: `pytest tests/test_level3_diagrams.py` still passes; renderer abstraction
in place and tested.
---
## T02 — Mermaid CLI (mmdc) integration
```task
id: MRKD-WP-0005-T02
status: todo
priority: high
state_hub_task_id: 87caa295-f466-4e2e-ba06-4b1a801ca976
```
Implement the `MermaidRenderer` backend that shells out to `mmdc` (Mermaid CLI).
- `MermaidRenderer.can_render("mermaid") -> bool`: checks `shutil.which("mmdc")`
- `MermaidRenderer.render(source, "mermaid", output_path)`:
- Write source to a temp `.mmd` file
- Run `mmdc -i <input> -o <output>.png`
- On success: return `RendererResult(success=True, output_path=...)`
- On failure: return `RendererResult(success=False, warning=WarningRecord(reason="render-failed"))`
- Builder: PNG embedded into DOCX as image; alt-text carries `[mermaid-source]<source>` marker
for round-trip (FR-534)
- Importer: existing alt-text marker detection already handles this — verify round-trip works
- Add `mmdc` to `pyproject.toml` optional extras: `diagram-mermaid`
- Integration test (skipped if `mmdc` not found): `test_mermaid_render_roundtrip`
Deliverable: When `mmdc` is on PATH, mermaid diagrams render to PNG and embed in DOCX;
round-trip restores source. When absent, source-only fallback with warning.
---
## T03 — Graphviz (dot) integration
```task
id: MRKD-WP-0005-T03
status: todo
priority: medium
state_hub_task_id: 8ceb771d-0b16-452d-b1cc-c5c2c40fe723
```
Implement the `GraphvizRenderer` backend that shells out to `dot`.
- `GraphvizRenderer.can_render("graphviz") -> bool`: checks `shutil.which("dot")`
- `GraphvizRenderer.render(source, "graphviz", output_path)`:
- Write source to a temp `.dot` file
- Run `dot -Tpng <input> -o <output>.png`
- On success / failure: same pattern as MermaidRenderer
- Alt-text marker: `[graphviz-source]<source>` for round-trip
- Add `graphviz` to optional extras: `diagram-graphviz`
- Integration test (skipped if `dot` not found): `test_graphviz_render_roundtrip`
Deliverable: When `dot` is on PATH, graphviz diagrams render and embed; otherwise
source-only fallback.
---
## T04 — PlantUML integration
```task
id: MRKD-WP-0005-T04
status: todo
priority: medium
state_hub_task_id: ab0da6e4-a0f2-4a57-baac-5727b741c74f
```
Implement the `PlantUMLRenderer` backend that shells out to `plantuml`.
- `PlantUMLRenderer.can_render("plantuml") -> bool`: checks `shutil.which("plantuml")`
- `PlantUMLRenderer.render(source, "plantuml", output_path)`:
- Write source to a temp `.puml` file
- Run `plantuml -tpng <input>` (output written adjacent to input by default)
- Move/rename output PNG to `output_path`
- On success / failure: same pattern
- Alt-text marker: `[plantuml-source]<source>`
- Add `plantuml` to optional extras: `diagram-plantuml`
- Integration test (skipped if `plantuml` not found): `test_plantuml_render_roundtrip`
Deliverable: When `plantuml` is on PATH, PlantUML diagrams render and embed.
---
## T05 — Rendered diagram round-trip integration tests
```task
id: MRKD-WP-0005-T05
status: todo
priority: medium
state_hub_task_id: 65b67ed9-5862-4322-acfb-5d39dab7e8d5
```
Extend the regression corpus and test harness to cover the rendered-diagram path
end-to-end, in addition to the existing source-only tests.
- Add `tests/regression/level3/rendered_diagrams_document.md` with mermaid, graphviz,
and plantuml blocks
- `tests/regression/test_level3_rendered_diagrams.py`:
- Each test is `pytest.mark.skipif(shutil.which("mmdc") is None, ...)`
- Full round-trip: build → import → compare; assert diagrams preserved
- Assert `differ` reports zero broken diagrams when renderer was available
- Verify existing `test_level3_diagrams.py` (source-only) still passes regardless
of renderer availability — the fallback path must remain stable
Deliverable: Rendered-diagram regression tests pass on systems with renderers installed;
source-only tests pass on all systems.
---
## How to Work
- T01 must be complete before T02T04 (abstraction must exist before concrete backends)
- T02, T03, T04 are independent of each other — can be worked in any order or in parallel
- T05 depends on T02T04 (needs the renderers to test the rendered path)
- Use `pytest.mark.skipif` to gate renderer-dependent tests — never fail on missing tools
## Updating Task Status
```
status: todo → status: in_progress (when you start it)
status: in_progress → status: done (when verified complete)
```
When every task is `done`, set the frontmatter `status: done`.
## Success Criteria
Before marking the workplan done:
1. Every task block has `status: done`
2. Workplan frontmatter `status: done`
3. All existing tests pass unchanged (source-only fallback unbroken)
4. On a system with `mmdc`: `pytest tests/regression/test_level3_rendered_diagrams.py` passes
5. `ruff check` and `mypy` clean
6. Optional extras `diagram-mermaid`, `diagram-graphviz`, `diagram-plantuml` documented
in `pyproject.toml`