fix(ooxml): Preserve styles on untouched sheets during metadata writes (TJC-751)#220
fix(ooxml): Preserve styles on untouched sheets during metadata writes (TJC-751)#220
Conversation
…s (TJC-751) StyleIndex.fromWorkbookWithSource only created style remappings for sheets in tracker.modifiedSheets, but metadata-only changes (add-sheet, reorder) cause ALL sheets to be regenerated from the domain model. Unmodified sheets received empty remappings, causing every cell's styleId to fall back to 0 (default), silently stripping all styling. Generate remappings for all sheets unconditionally. Unmodified sheets' styles all originate from the source file, so every canonical key is found in the unified index — no new styles are added, and the remapping is identity-like. When sheets are copied verbatim (the normal surgical path), the extra remappings simply go unused. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Review: fix(ooxml): Preserve styles on untouched sheets during metadata writes (TJC-751)SummaryThe root cause diagnosis is correct and the fix is sound. When The fix—unconditionally generating remappings for all sheets—is correct and minimal. Issues1. Test: ZipFile resource leak on assertion failureIn the regression test, val outputZip = new ZipFile(output.toFile)
val sheet1Xml = readEntryString(outputZip, outputZip.getEntry("xl/worksheets/sheet1.xml"))
// ... assertions that can throw ...
outputZip.close() // never reached if assertion above fails
Files.deleteIfExists(source)
Files.deleteIfExists(output)If an assertion fails the ZipFile handle and temp files leak. Wrap with Using(new ZipFile(output.toFile)) { outputZip =>
val sheet1Xml = readEntryString(outputZip, outputZip.getEntry("xl/worksheets/sheet1.xml"))
// assertions...
}Similarly 2. Minor performance regression for partial-sheet-modify pathPreviously, when only A targeted guard would restore the optimisation without reintroducing the bug: val needsRemappingForAll = ctx.modificationTracker.modifiedMetadata || ctx.modificationTracker.reorderedSheets
val remappings = wb.sheets.zipWithIndex.map { case (sheet, sheetIdx) =>
if needsRemappingForAll || ctx.modificationTracker.modifiedSheets.contains(sheetIdx) then
// ... existing remapping logic ...
sheetIdx -> remapping.toMap
else
sheetIdx -> Map.empty[Int, Int] // copied verbatim; remapping unused
}.toMapThis is not a correctness issue—the current code is correct—but worth considering given the project's performance focus. 3. No coverage for
|
1. Performance: restore skip for unmodified sheets when only modifiedSheets is set (not modifiedMetadata/reorderedSheets). Avoids O(n × styles) of unused remapping work. 2. Test: wrap ZipFile and temp files in try/finally to prevent resource leaks on assertion failure. 3. Test: add reorder variant (reorderedSheets=true) covering the other code path that triggers full-sheet regeneration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-up Review: fix(ooxml): Preserve styles on untouched sheets during metadata writes (TJC-751)The three issues flagged in the previous round (resource leak, performance regression, missing reorder coverage) are all addressed in the current diff. The fix and tests are in good shape. A few remaining observations: Remaining observations1.
|
Summary
StyleIndex.fromWorkbookWithSourceonly created style remappings for sheets intracker.modifiedSheets, but metadata-only changes (add-sheet,reorder) force ALL sheets to be regenerated from the domain model. Unmodified sheets receivedMap.emptyremappings, causing every cell's styleId to fall back to 0 (default).modifiedMetadataonly), verifies styles survive on untouched sheets at both domain and XML levels.Test plan
add-sheet preserves styles on untouched sheets (TJC-751)./mill __.test)./mill __.checkFormat)generated_AIT_CapTable_Waterfall.xlsx— add-sheet + batch put/style, then diff HTML output of untouched sheets against original (identical)Closes TJC-751
🤖 Generated with Claude Code