Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

18KB

Sprint Backlog

Sprint: 4 Dates: 2026-09-28 - 2026-10-02 Sprint Goal: Complete the CSV Integration and Field Mapping epic by collapsing blank optional address lines, and deliver the first slice of text/field rotation (numeric angle entry, persistence, and correct rendering around the element's bounding-box center).

Committed Items

Story Size Status Tasks
Collapse blank optional address lines consistently 5 points Done - [x] Design and implement a per-line “collapsible if blank” configuration flag on address-block text elements (model + XML persistence), defaulting existing templates to today's non-collapsing behavior
- [x] Implement the shift-upward layout rule in the render engine so subsequent configured lines move up when a bound field is blank for that record
- [x] Apply the same collapsing math to the desktop canvas preview so preview and final PDF agree
- [x] Give operators a way to visually distinguish “intentionally blank per data” from “field failed to map”
- [x] Unit tests: single blank line collapses and shifts correctly, populated line preserves normal spacing, multiple consecutive blank lines, preview/render parity
- [x] Live manual verification against the built .exe and a real CLI render: one real record with a blank optional line, one with it populated
Set a rotation angle for text and dynamic field elements 8 points Done - [x] Add RotationAngle (degrees) to TextElementLayout, applying uniformly to static and dynamic elements
- [x] Add a numeric angle field to TextElementPropertiesEditor/TemplateDesignerForm's properties panel with validation
- [x] Update TemplateCanvasControl.DrawElement to rotate the drawn element about its bounding-box center and update CanvasElementEditor.HitTest for point-in-rotated-rectangle selection
- [x] Persist angle as a new TemplateLayoutXmlSerializer attribute, defaulting to 0 for templates saved before this story
- [x] Extend the CLI's render-time template format (TextDraw, TemplateXmlParser, RenderEngine) to read the new angle attribute
- [x] Implement rotate-about-bounding-box-center rendering in DebenuPdfRenderer using GetTextWidth/GetTextAscent/GetTextDescent to compute the anchor offset, then call DrawRotatedText; empirically confirm Debenu's rotation-direction sign convention against the real DLL and document it in TEMPLATE_FORMAT.md
- [x] Unit tests: the property itself, persistence round-trip (including old templates with no angle attribute), and the anchor-offset rotation math
- [x] Live manual verification against the built .exe and a real CLI render: a non-zero angle rotates the canvas and the rendered PDF the same way, around the same visual center
Rotate elements by dragging a handle on the canvas 5 points Done - [x] Add a rotate-handle hit-test region to CanvasElementEditor for a selected element, positioned relative to its rotated bounding box
- [x] Implement drag-to-rotate: compute the angle via atan2 of the pointer position relative to the bounding-box center as the operator drags
- [x] Wire the drag result into the existing bidirectional properties-panel/canvas sync so the numeric angle field and the handle update each other live
- [x] Add handle-glyph painting to TemplateCanvasControl
- [x] Unit tests: handle hit-test, drag-to-angle math, panel/canvas sync in both directions
- [x] Live manual verification against the built .exe: drag the handle, confirm canvas and properties-panel angle stay in sync, save and confirm the resulting angle persists exactly as a typed value would

Notes

  • Capacity signal: velocity range refined to 18-20 points, now off three data points (Sprint 1: 20 completed, Sprint 2: 19 completed, Sprint 3: 18 committed and completed exactly, no scope change) — a tighter, more trustworthy range than Sprint 3 planning had.
  • Committing 18 points, deliberately at the low end of the range rather than the 20-point ceiling, per AGENTS.md's guidance to reserve roughly 10% of capacity for grooming rather than packing to the exact top. This mirrors Sprint 3's own committed (and fully completed) total.
  • Deliberately not committed this sprint (capacity discipline): “Harden production configuration delivery for CLI runtime settings” (2 points) — kept as an explicit stretch item, consistent with product-owner's standing note that it isn't urgent (see backlog/epics/05_cli_rendering_engine_and_debenu_integration.md). Pull it only if the three committed stories finish with room to spare.
  • Planning rationale: “Collapse blank optional address lines consistently” completes the CSV Integration and Field Mapping epic outright (its only remaining story) and has no dependency on this sprint's other work, so it's pulled first. The two rotation stories are committed together as one dependency-ordered chain (angle property before drag handle) — the same pattern Sprint 3 used successfully for its CSV chain (headers -> token -> rebind, 10 points across 3 dependent stories in one sprint).
  • Sizing: both rotation stories were sized during backlog refinement (2026-09-22) with real code/vendor-API inspection (see backlog/epics/02_template_designer_gui_foundation.md's sizing notes) — the vendor Debenu API has no “rotate about center” primitive, so the center-pivot math in story 1 is genuine new work, not a pass-through parameter. Both pass the Definition of Ready.
  • Impediments: template asset path strategy (absolute vs relative) and UNC timeout/retry behavior remain open in logs/impediment_log.md. Neither blocks this sprint's committed items.
  • Carried over from previous sprint: none (Sprint 3 completed all 4 committed stories).
  • Sequencing risk: “Rotate elements by dragging a handle on the canvas” depends on “Set a rotation angle for text and dynamic field elements” landing first, both committed in this same sprint — swarm to finish the angle-property story before starting the drag-handle story's implementation, mirroring how Sprint 3 sequenced its CSV dependency chain.
  • Sprint 4 retrospective action items to apply during this sprint (from backlog/sprints/sprint-3-retrospective.md): continue live actual-built-artifact verification for GUI-facing stories; continue verifying real spec/code (not stale notes) before implementing; before “Collapse blank optional address lines” is considered fully done, note it has no dependency on the still-open 1,000,000-record file-size technical debt item, so that item remains untouched this sprint, as expected.

Execution Order

Sequenced by dependency; the two independent stories are pulled before the dependent drag-handle story.

Batch Story Why it's gated here
1 Collapse blank optional address lines consistently No dependency on any other Sprint 4 story (its only dependency, “Create a dynamic text token,” is already Done). Pulled first, completing epic 3 outright.
2 Set a rotation angle for text and dynamic field elements No dependency on any other Sprint 4 story. Pulled second so its angle property exists before Batch 3 needs it.
3 Rotate elements by dragging a handle on the canvas Needs the RotationAngle property, persistence, and render/pivot logic from Batch 2 done first.

Daily Scrum Log

Day Date Completed Planned Blocked/At risk
1 2026-09-28 Batch 1 (“Collapse blank optional address lines consistently”, 5 points) done, all 4 ACs met. Added a shared, independently-implemented-twice AddressLineCollapser pure-geometry class (EnvelopeRenderer.Cli.Render and EnvelopeRenderer.Desktop.Core.Design — deliberately not shared via project reference, mirroring TemplateLayoutXmlSerializer's existing precedent for keeping the desktop project free of CLI-internals references) implementing the row-height shift math: lines sharing the same X position (rounded to 2 decimals) form a top-to-bottom stack, and a collapsed line's own row height (gap to the line below it, using original undisturbed Y values) shifts every subsequent line up. Added Collapsible/CollapseIfBlank to TemplateElement/TextElementLayout and a new collapsible="true" XML attribute (TemplateXmlParser, TemplateLayoutXmlSerializer), both defaulting to false/absent so every pre-existing template is unaffected. RenderEngine.BuildDraws now resolves each record's text, decides per-line collapse via AddressLineCollapser, and omits collapsed lines’ draws entirely. For “distinguish intentionally blank from a mapping error” (the story's 4th AC and the one open design decision it left to the team): a column that isn't a real CSV header remains a hard, whole-run failure exactly as before (never silently collapses regardless of collapsible) — the design decision made was to surface that same distinction visually in the desktop canvas via a new AddressBlockPreviewCalculator (framework-free, unit tested) that flags a dynamic element bound to a column absent from the currently loaded CSV with a dashed orange/red highlight, distinct from the ordinary blue dynamic-field fill; a collapsible field that's merely blank in the loaded sample data gets no warning at all, just silently doesn't render — exactly the intended distinction. TemplateCanvasControl now runs the identical collapse-preview math (via SetCsvPreviewContext, fed the loaded CSV's headers and first sample row from TemplateDesignerForm) so preview and final PDF agree, per the story's own conversation note. 41 new unit tests across both test projects (AddressLineCollapserTests x2, AddressBlockPreviewCalculatorTests, plus TemplateXmlParser/TemplateLayoutXmlSerializer/RenderEngine collapsible-attribute coverage). Live-verified against the real CLI and the real Debenu DLL: built a template binding “Address 2/3/4” as collapsible against the real sample CSV (87700 - 999999 - Wilson Township.csv, where Address 3/4 are blank for every sampled record and Address 2 is populated) — PROGRESS complete ... completed=392, exit 0; rasterized page 1 via Debenu's own RenderPageToFile and visually confirmed exactly 3 tightly, evenly spaced address lines (Full Name / Address 1 / Address 2) with no gap where the two blank collapsed lines would have been, and GetPageText confirmed no stray blank-line artifacts. Desktop-side: added a static element, toggled the new “Collapse if blank” checkbox, confirmed a small green marker badge appears beneath it on canvas (screenshot captured), and confirmed a hand-authored template with one column bound to a real CSV header and one to a nonexistent header shows the intended visual contrast (plain vs. dashed-orange) once the real sample CSV is loaded (screenshot captured). Batch 2 (“Set a rotation angle..."), pulled next per the Execution Order table (independent of Batch 1). None.
1-2 2026-09-28 to 2026-09-29 Batch 2 (“Set a rotation angle for text and dynamic field elements”, 8 points) done, all 4 ACs met. Before writing any rendering code, empirically confirmed two real vendor-DLL behaviors rather than assuming them (per this project's standing practice, same as the Sprint 3 throughput investigation): (1) a probe render (unrotated baseline vs. DrawRotatedText at Angle=90, both rasterized via Debenu's own RenderPageToFile and pixel-inspected) showed positive Angle is counterclockwise in PDF's Y-up coordinate system — the standard math rotation convention, no sign flip needed against PDF space; (2) GetTextDescent() returns an already-negative value (e.g. -5.088 for 24pt Arial), i.e. a glyph's local vertical extent is [descent, ascent] directly. Also discovered, only once real DrawRotatedText calls were exercised: it flatly rejects a negative Angle (returns 0, no error code) despite rotation being periodic — DebenuPdfRenderer now normalizes to Debenu's expected [0, 360) range for that specific call only, while the anchor-offset math itself correctly uses the original signed angle. Added RotatedTextAnchorCalculator (pure geometry, EnvelopeRenderer.Cli.Render): solves for the anchor point DrawRotatedText needs so its rotation lands on the unrotated bounding-box center rather than the (x, y) anchor Debenu itself rotates around, using GetTextWidth/GetTextAscent/GetTextDescent. angle=0 (including when the attribute is absent) takes the exact original DrawText code path — byte-for-byte unchanged for every pre-Sprint-4 template. Added RotationAngle to TextElementLayout/TemplateElement/TextDraw, a numeric “Angle (deg):” field to the properties panel (TextElementPropertiesEditor.SetRotationAngle, free-form, no clamping since any real value is valid), a new angle XML attribute (TemplateLayoutXmlSerializer, TemplateXmlParser), and rotated-bounding-box hit-testing in CanvasElementEditor.HitTest (rotates the click point into the element's local frame by the inverse angle) so a rotated element's actual (not axis-aligned) footprint is what's clickable. TemplateCanvasControl.DrawElement rotates the drawn glyph and its selection outline via GDI+'s Graphics.RotateTransform around the bounding-box center — negating the stored angle, since GDI+'s Y-down pixel space makes a positive RotateTransform angle appear clockwise as viewed, the opposite of the PDF-space convention just confirmed; this negation is what keeps the canvas and the final PDF rotating the same visual direction. 29 new unit tests (RotatedTextAnchorCalculatorTests — including an angle-agnostic property test asserting the bounding-box center is preserved at 30/90/180/-45 degrees — plus rotated-hit-test and property/persistence coverage). Live-verified against the real Debenu DLL with a durable regression test, not just a one-off probe: DebenuPdfRendererRotationTests renders real text at five different angles (0/30/90/180/-45) through the real DebenuPdfRenderer, rasterizes each page, and asserts via actual pixel-ink-centroid analysis that the centroid stays within a tight, generous-but-meaningful tolerance of the angle-0 centroid — the concrete, observable meaning of “rotates around the bounding-box center,” which would fail by 90-190px without the anchor-offset correction (rotating around the plain X/Y anchor instead). Also live-verified end-to-end against the real CLI/sample CSV: a 45-degree rotated “ROTATED” static label rendered visibly tilted counterclockwise in the actual output PDF (rasterized and visually inspected). Desktop-side, live against the actual built .exe: added a static element, typed 60 into the Angle field, confirmed the canvas glyph and its rotate-handle marker visibly rotated counterclockwise (screenshot captured); saved the template and confirmed the on-disk XML's angle attribute matched exactly. Batch 3 (“Rotate elements by dragging a handle..."), which depends on this batch. None.
2 2026-09-29 Batch 3 (“Rotate elements by dragging a handle on the canvas”, 5 points) done, all 4 ACs met, swarmed immediately after Batch 2 landed per the Execution Order table's dependency note. Added a rotate-handle to CanvasElementEditor: HandlePosition() computes a world-space point HandleOffsetPoints (20pt, a Development Team design choice) beyond the rotated bounding box's top edge, rotating with the element (using the same forward-rotation convention HitTest's inverse undoes, so hit-testing and painting always agree); HitTestHandle grabs it within an 8pt radius. BeginRotateDrag/RotateDragTo/IsRotating add a second drag mode alongside the existing move-drag (mutually exclusive — BeginRotateDrag clears any pending move offset): RotateDragTo computes the new angle via atan2 of the pointer relative to the bounding-box center, adjusted by -90 degrees since the handle's own zero-rotation reference direction is “up” (+Y), not the atan2 convention's +X. TemplateCanvasControl paints the handle as a small filled circle connected to the element's center by a dotted line, and its OnMouseDown/OnMouseMove check the handle hit-region first (only when something is already selected) before falling through to the existing select/move logic. Bidirectional panel/canvas sync required no new plumbing at all — it reused Batch 2's own RotationAngle panel wiring and the pre-existing ElementsChanged-triggers-RefreshPropertiesPanel() pattern TemplateDesignerForm already had for X/Y, exactly as the story's sizing note anticipated. 20 new unit tests (rotated hit-test edge cases, handle position/hit-test at several angles, atan2 drag-to-angle math including cardinal-direction sanity checks, mutual-exclusivity with move-drag). Live-verified against the actual built .exe with real Win32 mouse-drag input injection (SetCursorPos/mouse_event, not an in-process harness): computed the handle's exact expected screen position externally (replicating CanvasElementEditor's own geometry plus a pixel-calibrated canvas transform derived from the real rendered page's on-screen bounds), dragged from that position to a point straight above the element's center, and confirmed via UI Automation that the properties panel's Angle field live-updated to -1.5 degrees (expected ~0, within pixel-rounding tolerance) — both the visual rotation (screenshot captured, text visibly returned to level) and the numeric field updated together, live, during the drag. Saved the template afterward and confirmed the on-disk XML's angle="-1.5464733298407793" matched the dragged value exactly (not the rounded 1-decimal display), satisfying “releasing the drag persists the resulting angle the same way a typed value would.” One incidental, honestly-reported note (not a product defect): mid-session, an SetForegroundWindow call from the verification tooling itself failed to bring the designer window forward as expected, and a handful of keystrokes intended for a CSV-column combo box briefly landed in the development environment instead of the app under test — caught immediately from the screenshot evidence, worked around by switching remaining interactions to direct UI-Automation control invocation instead of SendKeys, and does not reflect any behavior of the product under test. With this batch done, all three of Sprint 4's committed stories are complete: 288/288 tests passing (up from 223 at Sprint 3 close). Sprint's committed backlog is now fully done (18/18 points across all 3 committed stories). Next: sprint review and retrospective per process/03_sprint_execution.md's exit criteria. None.

Powered by TurnKey Linux.