Sprint: 3 Dates: 2026-09-21 - 2026-09-25 Sprint Goal: Close the high-volume render throughput risk with a confirmed root cause and a measured mitigation, and deliver the first slice of CSV field mapping (load headers, bind a dynamic field, rebind it).
| Story | Size | Status | Tasks |
|---|---|---|---|
| Investigate and address high-volume render throughput degradation | 8 points | Done | - [x] Build a scaled-down repeatable throughput probe (extend Sprint 2's benchmark scenario) that runs in minutes, not tens of minutes, so hypotheses can be iterated on quickly - [x] Test hypothesis: does periodic Save-to-file + release/reopen a fresh PDFLibrary instance reset the per-page cost curve? Record results against BENCHMARK.md's root-cause section - [x] Confirm or rule out the Debenu-internal-document-model hypothesis based on the above; document the evidence - [x] Design and implement one mitigation in production code: extend IPdfRenderer/DebenuPdfRenderer to batch pages across multiple underlying Debenu documents (save+reopen every N pages) and merge/concatenate the batches into the single final output PDF - [x] Add/extend unit tests for the batching + merge logic (fake-renderer path plus a real-Debenu integration test for actual merge correctness) - [x] Re-run the full 100k-record benchmark to completion (not time-boxed) with the mitigation in place; record elapsed time, throughput curve, and output PDF validity/size - [x] Document the result in code/BENCHMARK.md against the 10-minute target (met / not met, by how much, what remains open) and qualitatively re-assess project_config.md's 1,000,000-record ceiling - [x] Regression pass: confirm existing render correctness (392-record sample, exit codes, PROGRESS stream) is unchanged; update CLI_CONTRACT.md if the mitigation changes any observable CLI behavior (no contract changes needed — behavior unchanged, confirmed live) |
| Load CSV headers and sample rows into the desktop app | 3 points | Done | - [x] Add a CSV file-open action to the desktop app (file picker supporting local and UNC paths) using the existing CsvHelper dependency - [x] Parse the header row plus a bounded sample of data rows; surface a clear operator-facing error for malformed CSV instead of crashing - [x] Display column headers and sample rows in a new grid/list view in the designer - [x] Unit tests (valid header+rows, malformed CSV, empty file) plus a live manual check against code/sample-data/87700 - 999999 - Wilson Township.csv |
| Create a dynamic text token from a CSV column | 5 points | Done | - [x] Surface the loaded CSV header list (from the story above) in the designer as a selectable field source - [x] Extend the existing “Add Dynamic Placeholder” flow ( TemplateDesignerForm) so a new dynamic field is bound to an operator-selected real CSV column instead of a generic placeholder (drag-and-drop or an explicit bind action, whichever is faster to deliver) - [x] Persist the bound column using the render-time column="Column Name" attribute format in TemplateLayoutDocument/the XML serializer — confirmed against TEMPLATE_FORMAT.md/TemplateXmlParser and Sprint 2's already-existing serializer that this (not a {{Column Name}} token) is the real, already-renderable format; see the epic's verification note - [x] Render the bound field on canvas with a visible placeholder representation distinct from static text - [x] Unit tests covering token creation, persistence round-trip, and arbitrary (non-hardcoded) column names - [x] Live manual verification against the built .exe: load the real sample CSV, create a mapped field, save, and confirm the saved XML token format |
| Re-map an existing dynamic field | 2 points | Done | - [x] Add a rebind control (properties panel or mapping pane) listing the currently loaded CSV's columns for a selected dynamic field - [x] Implement rebind logic: update the bound column without recreating or repositioning the element; persist on save - [x] Unit tests for rebind (persistence, no repositioning, correct column reflected) plus a live check that a subsequent preview/render run reflects the new mapping |
AGENTS.md's guidance to reserve roughly 10% of capacity for grooming rather than packing to the exact ceiling, especially with the throughput story carrying more open-ended risk than a typical 8-pointer.backlog/backlog.md) — the throughput-degradation story first (highest priority: threatens a hard product constraint and compounds at the stated 1,000,000-record ceiling), then CSV Integration stories in existing backlog order starting with the no-dependency “Load CSV headers” story (deferred from Sprint 2 as a stretch item), no cherry-picking.dev-team sized it at 8 points during planning — see the sizing note now recorded in backlog/epics/05_cli_rendering_engine_and_debenu_integration.md. Its own acceptance criteria already bound it to a time-boxed shape (confirm one hypothesis, implement one mitigation, honest “not met” is an acceptable documented outcome), and code inspection found a clean, testable seam for the leading mitigation candidate — so it was sized directly rather than split into a separate spike-then-mitigation pair.logs/impediment_log.md. Neither blocks this sprint's committed items.| Day | Date | Completed | Planned | Blocked/At risk |
|---|---|---|---|---|
| 1 | 2026-09-21 | Batch 1 (“Investigate and address high-volume render throughput degradation”, 8 points) done, all 8 tasks and all 4 ACs met with real evidence, no simulation. Built a scaled-down probe (real Debenu DLL, 1,000-4,000 pages, runs in 2-3 min) that reproduced the Sprint 2 spike's degradation curve at a fraction of the wall time. Confirmed the Debenu-internal-document-model hypothesis directly: save+release/reopen a fresh PDFLibrary instance repeatably resets the per-page cost curve (validated at both 4,000-page and sustained 10,000-page/20-batch scale). Measured a genuine throughput-vs-file-size trade-off across batch sizes 100-1,000 and picked 300 pages/batch for production (evidence in code/BENCHMARK.md). Implemented the mitigation in DebenuPdfRenderer (batch across multiple Debenu documents, merge via Debenu's real MergeFileListFast), with a documented fast path that keeps small/typical renders byte-for-byte unchanged. Added 8 new unit/integration tests (201/201 passing total, up from 195). Re-ran the full 100,352-record/300 DPI benchmark to completion (not time-boxed): 315 seconds, ~47.5% under the 10-minute target, valid 100,352-page PDF (397 MB), content-correctness spot-checked at start/middle/end. Documented the result and qualitatively re-assessed the 1,000,000-record ceiling in code/BENCHMARK.md and logs/technical_debt_log.md: throughput looks fine extrapolated, but a new file-size risk (~4.5-5 GB extrapolated, over the 2 GB constraint) was found and logged as open technical debt rather than silently absorbed or overstated as solved. Regression pass complete: live-verified the real built CLI's --help, no-args, missing-template, and full 392-record render paths all match CLI_CONTRACT.md exactly; no contract changes needed since batching is entirely internal to DebenuPdfRenderer. |
Next: begin Batch 2 (“Load CSV headers and sample rows into the desktop app”) per the Execution Order table below. | None. The one open item is the newly-discovered 1,000,000-record file-size risk (logs/technical_debt_log.md, 2026-09-21 entry) — not a blocker for this sprint (no committed story renders at that scale), tracked as open technical debt for a future sprint to pick up before any story commits to rendering near that ceiling. |
| 2 | 2026-09-22 | Batches 2-4 (the full CSV Integration chain, 3+5+2 = 10 points) all done in dependency order, swarmed to completion one at a time rather than started in parallel. Batch 2 (“Load CSV headers and sample rows into the desktop app”): added CsvPreviewLoader (EnvelopeRenderer.Desktop.Core.Csv, same CsvHelper package the CLI already uses) and a “Load CSV...” action + preview grid in TemplateDesignerForm; 7 new tests (valid rows, bounded sample, empty file, malformed/unterminated-quote CSV, missing file, blank path, header-only file). Batch 3 (“Create a dynamic text token from a CSV column”): before writing any code, confirmed in TEMPLATE_FORMAT.md/TemplateXmlParser and Sprint 2's existing TemplateLayoutXmlSerializer that the real render-time binding is a column="<header>" attribute, not the {{Column Name}} token mentioned in this story's older conversation notes — built on the existing, already-renderable format instead of inventing a second one. Extended the existing “Add Dynamic Placeholder” flow with an explicit column-picker combo box (populated from Batch 2's loaded headers, disabled until a CSV is loaded) and added a light highlight fill so dynamic elements are visibly distinct on canvas, not just textually ({Column} bracket notation already existed from Sprint 2). 9 new tests cover token creation and XML round-trip with arbitrary column names (including +, &, ', embedded quotes). Batch 4 (“Re-map an existing dynamic field”): added a “CSV column:” rebind combo to the existing properties panel (TextElementPropertiesEditor.SetColumnName, touches only ColumnName, never X/Y), enabled only for a selected dynamic element with a loaded CSV. 5 new tests cover rebind-without-moving, static-element no-op, blank-input rejection, and an arbitrary column name. 223/223 tests passing across both test projects (up from 201). Live verification against the actual built .exe (real Win32/UI-Automation input injection, not an in-process harness): loaded the real sample CSV (confirmed 22 columns/20 sample rows in the grid); bound a new dynamic field to “Full Name” and confirmed the highlighted {Full Name} placeholder at (29.70, 547.20) pt; rebound it live to “G2 City St ZIP+4” and confirmed the canvas text updated to {G2 City St ZIP+4} at the exact same (29.70, 547.20) pt (no repositioning); saved and inspected the real XML on disk — column="G2 City St ZIP+4" with matching x/y; ran the real EnvelopeRenderer.Cli.exe against that saved template and the real sample CSV — exit 0, PROGRESS complete ... completed=392, valid 392-page PDF, confirming the new mapping is honored on the next render. One honestly-reported gap: every one of the 392 sample records has a blank G2 City St ZIP+4 value (confirmed via Import-Csv), so that specific rendered page is visually blank at the field — not a defect, just this sample file's data shape; to get a positive visual confirmation of the same coordinate pipeline, additionally rendered a second template bound to the populated Precinct column at the identical x/y and confirmed via a rasterized page crop that “15” (the real first-record value) renders exactly where expected. |
Sprint's committed backlog is now fully done (18/18 points across all 4 committed stories). Next: sprint review and retrospective per process/03_sprint_execution.md's exit criteria. |
None. |
Sequenced by dependency and priority; the throughput story is pulled first since it's the highest-priority known risk and has no in-sprint dependency, followed by the CSV Integration chain in backlog order.
| Batch | Story | Why it's gated here |
|---|---|---|
| 1 | Investigate and address high-volume render throughput degradation | No dependency on any other Sprint 3 story; highest priority per Sprint 2 review (threatens a hard product constraint). Pulled first. |
| 2 | Load CSV headers and sample rows into the desktop app | No dependency on Batch 1; needed before any dynamic-field-binding work since fields must bind to real loaded CSV columns. |
| 3 | Create a dynamic text token from a CSV column | Needs loaded CSV headers (Batch 2) to bind fields to, and the canvas placement work from Sprint 2 (already Done). |
| 4 | Re-map an existing dynamic field | Needs an existing dynamic field to rebind, i.e. Batch 3 done first. |
Powered by TurnKey Linux.