diff --git a/CHANGELOG.md b/CHANGELOG.md index 358bc4a..231de31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.8 + +### Fixed +- **CSV writer**: downgrade trailing blank row mismatch from error to warning + when a tall instruction's stripped continuation absorbs extra blank rows on + read-back — fixes `WriterError` on rungs with blank rows beyond an + instruction's visual height + ## 0.1.7 ### Fixed diff --git a/src/laddercodec/csv/writer.py b/src/laddercodec/csv/writer.py index 80b9ce2..568cc2a 100644 --- a/src/laddercodec/csv/writer.py +++ b/src/laddercodec/csv/writer.py @@ -365,10 +365,26 @@ def _validate_roundtrip(rung: Rung, rows: Sequence[Sequence[str]]) -> None: rebuilt = _rebuild_rung_from_rows(rows) if rebuilt.logical_rows != rung.logical_rows: - raise WriterError( - "CSV round-trip validation failed: " - f"logical row count mismatch: expected {rung.logical_rows}, got {rebuilt.logical_rows}" - ) + # Trailing blank rows (no conditions, no AF) can be lost when + # a tall instruction's stripped continuation absorbs them on + # read-back. This is cosmetic — warn instead of failing. + extra = rung.logical_rows - rebuilt.logical_rows + if extra > 0 and all( + _is_padding_row(rung.conditions[r]) and rung.instructions[r] == "" + for r in range(rebuilt.logical_rows, rung.logical_rows) + ): + import warnings + + warnings.warn( + f"CSV round-trip lost {extra} trailing blank row(s) " + f"(logical_rows {rung.logical_rows} → {rebuilt.logical_rows})", + stacklevel=2, + ) + else: + raise WriterError( + "CSV round-trip validation failed: " + f"logical row count mismatch: expected {rung.logical_rows}, got {rebuilt.logical_rows}" + ) if rebuilt.comment != rung.comment: raise WriterError( @@ -377,7 +393,7 @@ def _validate_roundtrip(rung: Rung, rows: Sequence[Sequence[str]]) -> None: ) for row_idx, (expected_row, actual_row) in enumerate( - zip(rung.conditions, rebuilt.conditions, strict=True), + zip(rung.conditions[: rebuilt.logical_rows], rebuilt.conditions, strict=True), start=1, ): for col_idx, (expected, actual) in enumerate(zip(expected_row, actual_row, strict=True)): @@ -389,7 +405,7 @@ def _validate_roundtrip(rung: Rung, rows: Sequence[Sequence[str]]) -> None: ) for row_idx, (expected, actual) in enumerate( - zip(rung.instructions, rebuilt.instructions, strict=True), + zip(rung.instructions[: rebuilt.logical_rows], rebuilt.instructions, strict=True), start=1, ): if not _af_tokens_match(expected, actual):