Append-only log of known technical debt. Maintained by .claude/agents/qa-tech-debt.md. Do not delete paid-down entries — mark them resolved instead.
| Date | Item | Type (unintentional/unavoidable/deliberate) | Impact (“interest rate”) | Status | Resolution |
|---|---|---|---|---|---|
| 2026-09-08 | EnvelopeRenderer.Cli exits 64 (“render not implemented”) for any argument-valid run, since the Debenu render engine isn't wired in yet. Self-documenting and expected to disappear once “Render text-only PDFs through Debenu Quick PDF” (Sprint 1 Batch 2) lands and exit 0 becomes reachable. |
Deliberate | Low | Paid Down | RenderNotImplemented branch removed from Program.cs; exit 0 is now reachable and exit 1 covers all template/CSV/render failures (see code/CLI_CONTRACT.md). |
| 2026-09-08 | DebenuPdfRenderer always embeds TrueType fonts fully (AddTrueTypeFont(..., Embed: 1)), cached once per unique font name per document. Fine at today's scale (one sample render: ~1.3 MB for 392 pages, one font) but full embedding could add up if a template ever uses many distinct fonts/styles at very high page counts, working against the sub-2GB PDF constraint. |
Deliberate | Low | Open | Revisit if the “Time-box the first high-volume benchmark” story (not in this sprint) shows file size becoming an issue; Debenu also exposes AddSubsettedFont as a smaller-footprint alternative if needed. Sprint 3's batching mitigation made this concern concrete: each batch now re-embeds the font from scratch (see the 2026-09-21 entry below). |
| 2026-09-08 | EnvelopeRenderer.Desktop's Render button re-enables as soon as the CLI process is confirmed started (CliProcessLauncher.LaunchAsync returning), not once it finishes rendering — this story (Sprint 1 Batch 4) intentionally does not track render completion. An operator can click Render again (e.g. against the same output path) while a prior render is still in progress, since nothing yet observes the child process's lifetime or exit code. |
Unavoidable (scope boundary of this story) | Low | Resolved | Sprint 1 Batch 5 (“Show render progress and completion summary”) replaced CliProcessLauncher.LaunchAsync/Launch with RunAsync/Run, which stream the CLI's redirected stdout/stderr, wait for the process to actually exit, and return exit code + final progress event. MainForm.OnRenderClick now only re-enables the Render button in the finally block after that awaited call completes, not when the process starts — verified with a real launch against the sample CSV (button stayed disabled for the full render, both success and forced-failure runs). |
| 2026-09-14 | The render path's actual high-volume throughput badly misses the product's stated “100,000 records at 300 DPI in under 10 minutes” target: a real (not simulated) 100k-record benchmark run showed throughput degrading monotonically from ~399 rec/s to ~15 rec/s (and still falling) by record 4,827 of 100,352, isolated with reasonable confidence to Debenu Quick PDF Library 10.13's in-memory document model rather than this repo's own O(1)-per-record merge/CSV code. Projected full-run time is on the order of 45-90+ minutes — 5x-10x+ over target. Full methodology, raw data, and root-cause investigation in code/BENCHMARK.md. |
Unavoidable (external vendor library characteristic, not yet confirmed fixable) | High (directly threatens a hard product constraint — project_config.md's “render 100,000 records at 300 DPI in under 10 minutes” — and would be worse at the stated 1,000,000-record ceiling) |
Resolved (for the 100,000-record target) | Sprint 3 story “Investigate and address high-volume render throughput degradation” confirmed the Debenu-internal-document-model hypothesis (a scaled-down probe showed the per-page cost reliably resets after a save+release/reopen cycle) and implemented a batching mitigation in DebenuPdfRenderer (save+release/reopen every 300 pages, merged via Debenu's MergeFileListFast). The full 100k-record benchmark was re-run to completion (not time-boxed): 315 seconds, ~47.5% under the 10-minute budget, valid 100,352-page PDF, 397 MB. Full results in code/BENCHMARK.md's “Sprint 3 follow-up” section. This closes the 100,000-record risk but opens a new, narrower one at the 1,000,000-record ceiling -- see the next entry. |
| 2026-09-21 | The Sprint 3 batching mitigation for the above throughput issue (save+release/reopen every 300 pages, merged via MergeFileListFast) fixes the 100,000-record/10-minute target with margin, but introduces a new file-size risk at the product's stated 1,000,000-record ceiling: each batch re-embeds the template's TrueType font from scratch (~1.05 MB per extra batch, measured directly), so linear extrapolation of the 100k run's real numbers (397 MB, ~350 MB of which is batching overhead) to 1,000,000 records suggests a combined output size in the neighborhood of 4.5-5 GB -- over the product's sub-2GB final PDF constraint (project_config.md). Not yet observed directly (no 1,000,000-record run has been performed); this is a qualitative, evidence-informed extrapolation, not a confirmed failure. Full analysis in code/BENCHMARK.md's “Re-assessment of the 1,000,000-record ceiling” section. |
Unintentional (side effect of the chosen mitigation, not present in this form before it) | Medium (only threatens the stated ceiling's extreme end, not the tested and confirmed 100,000-record target; no story currently commits to rendering 1,000,000 records) | Open | Not fixed as part of this story (out of scope: the story's AC2 asks for one mitigation sized against the 100k target). Two candidate directions identified, not yet attempted: a batch size that scales with total record count, or switching to Debenu's AddTrueTypeSubsettedFont so each batch only re-embeds the glyphs actually used instead of the full font. Should be revisited before any story commits to rendering at or near the 1,000,000-record ceiling. |
| 2026-10-09 | A rotated text element bound to a CSV column (or, since Sprint 5, containing any field run within mixed content) renders at a different position on every record's page instead of a consistent one. Root cause confirmed by code inspection: DebenuPdfRenderer.AddPage measures each record's own resolved text (_pdf.GetTextWidth(draw.Text)) to compute the rotation pivot (RotatedTextAnchorCalculator.ComputeAnchor's centerLocalX = width / 2.0), so the bounding-box center — and therefore the anchor point passed to DrawRotatedText — shifts whenever a record's resolved text length differs from another's. Static (fixed-text) rotated elements are unaffected, since their width never varies by record, which is why this wasn't caught during Sprint 4's live verification (only a static “ROTATED” label was tested rotated). Compounding finding: this also breaks the “Set a rotation angle...” story's own already-shipped AC4 (canvas and rendered PDF must share the same pivot) for any dynamic/mixed content — TemplateCanvasControl.MeasureElement measures element.DisplayText, a fixed design-time placeholder, so the canvas always shows one static position that cannot match the actual per-record rendered position except by coincidence. User-reported (2026-10-09): observed as rendered records appearing to “jump around” position-to-position when rotation is applied to real per-record data. |
Unintentional (an unanticipated interaction between Sprint 4's “rotate around bounding-box center” design and any per-record-varying resolved text — never exercised together until real dynamic/mixed content was rotated) | High (breaks correct print positioning for any real workflow combining rotation with dynamic or mixed-content fields — a realistic, likely-common combination — and breaks an already-shipped, verified acceptance criterion) | Resolved | Sprint 6 Batch 1 fixed this by choosing the stable fixed-anchor pivot for any rotated text element containing at least one field run, while leaving rotated static text on the Sprint 4 bounding-box-center path. RenderEngine now marks dynamic/mixed TextDraws with UsesFixedRotationPivot; DebenuPdfRenderer uses (X, Y) directly for those rotated draws instead of measuring record-specific text width; CanvasElementEditor/TemplateCanvasControl use the same pivot rule for drawing, hit-testing, and rotate handles. Verified by 336/336 passing tests, including a real-DLL regression proving two different text widths use the same authored rotated transform anchor, plus a real CLI render of sample-envelope-template2.xml against the 392-record sample CSV and a bitmap smoke of the actual WinForms canvas drawing the rotated dynamic field. |
| 2026-10-19 | Address Control lines rendered on the designer canvas (TemplateCanvasControl.DrawAddressControl) and, since Sprint 7, on the new record-accurate preview panel (TemplatePreviewControl) visually overlap when a control uses a small font size with the default 1.25 line-spacing multiplier (observed with sample-envelope-template3.xml's 8pt lines against the real sample CSV): each line's GDI+ MeasureString height is taller than the FontSize * LineSpacingMultiplier row height the baseline math advances by, so consecutive lines’ bounding boxes visually overlap on screen even though their baselines are correctly spaced per TEMPLATE_FORMAT.md. Confirmed via live built-.exe screenshots of both surfaces during Sprint 7 batch 1 verification — present identically on the pre-existing (Sprint 6) design canvas, so this is not a Sprint 7 regression, just newly re-observed because a second surface now shares the same measurement approach. |
Unintentional (side effect of using GDI+ MeasureString's full line height as a stand-in for the real Debenu-rendered glyph height, which the code's own remarks already flag as “a design-time visual approximation... not a guarantee of pixel-for-point parity with the final PDF”) |
Low (cosmetic, canvas/preview-only — the actual PDF render path, DebenuPdfRenderer/RenderEngine, does not use this measurement at all and is unaffected; does not block reading resolved text, just makes tightly-spaced small-font address blocks visually crowded in the designer) |
Open | Not fixed as part of Sprint 7 (out of scope for both the preview-panel and snap-to-grid stories). Candidate direction: derive each line's row height from the same font's ascent/descent (or a smaller line-height fraction of MeasureString) instead of the raw measured string height, or accept a documented minimum recommended LineSpacingMultiplier for small fonts. Revisit if an operator reports this as more than cosmetic. Product-owner review confirmation (Sprint 7 Review, 2026-10-19): Impact/status agreed as logged — Low, Open, non-blocking. Independently confirmed the root cause is confined to TemplateCanvasControl/TemplatePreviewControl's GDI+ measurement path and that neither DebenuPdfRenderer nor RenderEngine (the real PDF render path) references this measurement at all, so no print-output correctness is at risk. No pushback on leaving this open; agree it should not block Sprint 8 planning. |
| 2026-09-04 | EnvelopeRenderer.Cli only read DEBENU_LICENSE_KEY from the process environment. That's fine for dotnet run --project ... -- (the CLI inherits the invoking shell's env directly), but EnvelopeRenderer.Desktop launches the CLI as a child process, which only inherits whatever environment variables were already present in whatever launched the desktop app itself (a double-clicked .exe or Start Menu shortcut typically has none) — so every desktop-launched render failed with Debenu error 999 regardless of a valid key existing on disk. Real-user-reported: the operator correctly guessed a key.txt dropped next to the exe should work (matching how the CLI's own test helper already resolved keys), but production code had no such fallback. This is a real Definition-of-Done verification gap from Sprint 1 Batches 4-5: the “real success run” verification used an in-process test harness with the env var set directly in that process, never the actual built .exe launched the way an operator would, so the gap wasn't caught before Sprint Review. |
Unintentional | High (silently broke the desktop app's core success path for any non-dotnet run launch) |
Resolved | Added DebenuLicenseKeyResolver (code/src/EnvelopeRenderer.Cli/DebenuLicenseKeyResolver.cs) to the shipped CLI: env var first, then a key.txt walked up from the executable's own directory — the same rule the test-only helper already used, now shared via delegation instead of duplicated. Documented in CLI_CONTRACT.md's “Debenu license key” section and code/README.md's desktop-app instructions. Verified by running the actual built EnvelopeRenderer.Cli.exe from the Desktop app's own output folder with no environment variable set at all, key.txt sitting next to it: exit 0, valid 1.3 MB %PDF-1.4 output, all 392 records. 5 new unit tests added (DebenuLicenseKeyResolverTests.cs); full suite 111/111 passing. |
| 2026-10-19 | Post-Sprint-7-review, the plain editing canvas (TemplateCanvasControl/CanvasElementEditor) now deliberately rotates every element — static and dynamic/mixed alike — around its own bounding-box center (RotationPivotCalculator.ComputeForCanvasEditing), instead of the Sprint 6 fixed-anchor rule (RotationPivotCalculator.Compute(isDynamic, ...)) it previously shared uniformly with the real render and the new Sprint 7 preview panel. The editing canvas only ever draws an element's literal authored {ColumnName} bracket-token text (TextElementLayout.DisplayText), never a per-record resolved value, so the record-to-record text-width drift the fixed-anchor rule exists to prevent cannot occur there — applying it anyway just made a dynamic/mixed element's rotate-handle drag swing around a corner instead of spinning in place, a jarring interactive inconsistency the human product owner asked to have fixed after using the Sprint 7 increment. The real render (RotatedTextAnchorCalculator/DebenuPdfRenderer) and the new preview panel (TemplatePreviewControl/TemplatePreviewBuilder) are intentionally untouched and still call RotationPivotCalculator.Compute with the real isDynamic value, so the original 2026-10-09 record-drift defect fix is fully preserved where it actually matters. See the dated note added to the “Keep rotated dynamic and mixed-content fields positioned consistently across records” story in backlog/epics/02_template_designer_gui_foundation.md for the full reasoning and human product-owner approval record. |
Deliberate (a considered, approved trade-off decoupling a purely cosmetic/interactive editing-canvas behavior from a record-accuracy rule that surface was never actually subject to — not a regression or an oversight) | Low (the editing canvas was never a record-accuracy surface for rotation to begin with, since it never draws resolved per-record text; the real render and record-accurate preview panel, where the original defect actually mattered, are unaffected and keep the exact same fixed-anchor behavior) | Logged (deliberate design decision, not a defect to resolve) | Not applicable — this row documents a considered, already-implemented, and approved design decision for future reference (e.g. if a future story ever gives the editing canvas its own resolved-per-record preview capability, this decoupling would need to be revisited). |
| 2026-10-26 | Sprint 8 (“Rotate the whole Address Control...") extracted its new rigid-group-rotation and drag-handle math into framework-free, unit-tested EnvelopeRenderer.Desktop.Core.Design classes (AddressControlLayout.BoxCenter, PointRotation, AddressControlRotateHandle), but the pre-existing (Sprint 6) Address Control interaction logic it builds on top of — TemplateCanvasControl.HitTestAddressControl, HitTestAddressResizeHandle, and the whole-control move/resize mouse handlers — still lives directly in the WinForms Views project rather than a CanvasElementEditor-equivalent for Address Controls, so it remains untestable by EnvelopeRenderer.Desktop.Tests (verified only via a live built-.exe reflection-driven smoke, not a unit test) unlike the standalone-element equivalents (CanvasElementEditor.HitTest/HandlePosition/RotateDragTo, which are unit tested). This is a proportional, in-scope decision for this sprint (retroactively refactoring Sprint 6's already-shipped, already-smoke-verified move/resize/hit-test code was not part of either committed story), not an oversight, but it leaves a real, growing architectural inconsistency between the two element kinds’ testability. |
Unintentional (a natural side effect of adding new, better-architected code alongside older code that predates the pattern, not a deliberate call to leave the old code as-is) | Low (no known behavioral defect — the smoke-tested code paths work correctly per this sprint's live verification; purely a testability/maintainability gap, and the two newly-added rotation classes themselves are fully unit tested) | Open | Candidate direction: extract TemplateCanvasControl's Address-Control-specific hit-test/drag/resize state and math into a new AddressControlEditor (Desktop.Core), mirroring CanvasElementEditor's existing role for standalone elements, so all four interaction kinds (select, move, resize, rotate) are unit tested consistently. Not required by any currently-committed story; revisit if Address Control interaction logic grows further (e.g. multi-select epic 6 work) or a live-verified defect is found in it. |
Powered by TurnKey Linux.