From 4af41d61b8acd27d84618ea4cf59b5dffa850a38 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 21:25:48 +0000 Subject: [PATCH 1/2] Fail closed when visual_map mixed sources is a string or nested list A YAML string for visual_map..sources was iterated as characters in compose; a nested list item was Path-joined. Require a list of non-empty strings at config load. Co-authored-by: jmjava --- milestones/README.md | 7 ++++-- milestones/pages-config-strings.md | 4 ++-- milestones/visual-map-sources.md | 35 ++++++++++++++++++++++++++++++ src/docgen/config.py | 7 ++++++ tests/test_config.py | 22 +++++++++++++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 milestones/visual-map-sources.md diff --git a/milestones/README.md b/milestones/README.md index 38d03f3..821ffc9 100644 --- a/milestones/README.md +++ b/milestones/README.md @@ -5,10 +5,13 @@ repositories that install `docgen` and maintain their own demo bundle. The library no longer ships an in-repo dogfood; consumers are the integration test of record. -**Active:** **[pages-config-strings.md](pages-config-strings.md)** — -`pages.docs_dir` / title / extra_links must be typed at config load. +**Active:** **[visual-map-sources.md](visual-map-sources.md)** — +`visual_map` mixed `sources` must be a YAML list of strings at config +load. **Shipped:** +- **[pages-config-strings.md](pages-config-strings.md)** — + `pages.docs_dir` / title / extra_links must be typed (#111). - **[generation-segment-strings.md](generation-segment-strings.md)** — per-segment narration / scene-generation prompts must be YAML strings (#110). diff --git a/milestones/pages-config-strings.md b/milestones/pages-config-strings.md index 3cf3f6f..783fcdd 100644 --- a/milestones/pages-config-strings.md +++ b/milestones/pages-config-strings.md @@ -1,7 +1,7 @@ # Milestone: pages docs_dir / title / extra_links must be typed -**Status:** Active -**PR:** [#111](https://github.com/jmjava/documentation-generator/pull/111) +**Status:** Shipped +**PR:** #111 **Depends on:** `milestones/generation-segment-strings.md` (PR #110), `milestones/path-config-strings.md` (PR #109), `milestones/concat-segment-lists.md` (PR #84) diff --git a/milestones/visual-map-sources.md b/milestones/visual-map-sources.md new file mode 100644 index 0000000..9243846 --- /dev/null +++ b/milestones/visual-map-sources.md @@ -0,0 +1,35 @@ +# Milestone: visual_map mixed sources must be a list of strings + +**Status:** Active +**PR:** pending +**Depends on:** `milestones/visual-map-field-strings.md` (PR #108), +`milestones/pages-config-strings.md` (PR #111) + +## Problem + +`visual_map..source` is already a YAML string. Mixed rows use +**`sources:`** instead: + +1. A YAML **string** (`sources: clip.mp4`) was iterated as characters + in `Composer.compose_segments`, looking up one-letter paths. +2. A nested list item was passed to `_resolve_source` and Path-joined. + +Missing `sources` remains allowed (empty mixed row). + +## Goal + +Fail closed at `Config.from_yaml`. Present `visual_map..sources` +must be a YAML list of non-empty strings. + +## Done when + +- [x] Present `visual_map..sources` must be a YAML list of strings. +- [x] Tests for a string value and a nested-list item. +- [ ] `ruff check src/ tests/` +- [ ] `pytest tests/` +- [ ] `docgen benchmark` (no clock change) + +## Out of scope + +- `wizard.default_guidance` type gating is separate. +- Empty `sources: []` remains allowed. diff --git a/src/docgen/config.py b/src/docgen/config.py index efb3c89..8b32b60 100644 --- a/src/docgen/config.py +++ b/src/docgen/config.py @@ -338,6 +338,13 @@ def __post_init__(self) -> None: f"{src}: visual_map.{sid_s}.{fname} must be a YAML string, " f"not {type(val).__name__}" ) + if spec.get("sources") is not None: + string_list_block( + spec, + "sources", + label=f"visual_map.{sid_s}.sources", + source=src, + ) wiz = self._block("wizard") if wiz.get("exclude_patterns") is not None: string_list_block( diff --git a/tests/test_config.py b/tests/test_config.py index f1459e6..6426176 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -741,3 +741,25 @@ def test_from_yaml_list_pages_segment_title_raises(tmp_path: Path) -> None: ) with pytest.raises(ConfigError, match="pages.segments.01.title must be a YAML string"): Config.from_yaml(p) + + +def test_from_yaml_string_visual_map_sources_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text( + 'visual_map:\n "01":\n type: mixed\n sources: clip.mp4\n', + encoding="utf-8", + ) + with pytest.raises(ConfigError, match="visual_map.01.sources must be a YAML list"): + Config.from_yaml(p) + + +def test_from_yaml_list_visual_map_sources_item_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text( + 'visual_map:\n "01":\n type: mixed\n sources:\n - - clip.mp4\n', + encoding="utf-8", + ) + with pytest.raises( + ConfigError, match=r"visual_map.01.sources\[0\] must be a YAML string" + ): + Config.from_yaml(p) From c8f8bfcdab1b52eafdf3e41c0988e4b386e0b5e7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Sep 2026 21:26:23 +0000 Subject: [PATCH 2/2] Mark visual-map-sources milestone gates as run ruff, pytest (654 passed, 1 skipped), and docgen benchmark are green. Co-authored-by: jmjava --- milestones/visual-map-sources.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/milestones/visual-map-sources.md b/milestones/visual-map-sources.md index 9243846..94c1321 100644 --- a/milestones/visual-map-sources.md +++ b/milestones/visual-map-sources.md @@ -1,7 +1,7 @@ # Milestone: visual_map mixed sources must be a list of strings **Status:** Active -**PR:** pending +**PR:** [#112](https://github.com/jmjava/documentation-generator/pull/112) **Depends on:** `milestones/visual-map-field-strings.md` (PR #108), `milestones/pages-config-strings.md` (PR #111) @@ -25,9 +25,9 @@ must be a YAML list of non-empty strings. - [x] Present `visual_map..sources` must be a YAML list of strings. - [x] Tests for a string value and a nested-list item. -- [ ] `ruff check src/ tests/` -- [ ] `pytest tests/` -- [ ] `docgen benchmark` (no clock change) +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` +- [x] `docgen benchmark` (no clock change) ## Out of scope