25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

23KB

Sprint Backlog

Sprint: 7 Dates: 2026-10-19 - 2026-10-23 Sprint Goal: Give operators an accurate, record-specific layout preview with record navigation — built on one shared text-resolution routine across CLI render, canvas, and preview, closing the canvas/render-drift risk class that caused the 2026-10-09 rotation defect — and round out capacity with a self-contained grid/guide snapping tool for faster manual layout work.

Committed Items

Story Size Status Tasks
Render an accurate, record-specific preview of the current template 8 points Done - [x] Extract RenderEngine.ResolveText's per-run resolution logic (or an equivalent) into a routine callable from EnvelopeRenderer.Desktop.Core, so CLI render, canvas collapse/mapping state, and the new preview panel all resolve text through the same code, not three independent implementations
- [x] Add a new, dedicated preview panel to TemplateDesignerForm (separate from the editable design canvas, which keeps showing bracket tokens)
- [x] Wire the preview panel to draw every standalone text element and every Address Control line using one selected CSV record's real resolved values via the shared resolution routine
- [x] Reproduce current shipped rendering rules exactly in the preview: address-line collapse (AddressLineCollapser), per-run mixed static/field content, and both rotation pivot rules (fixed authored anchor for dynamic/mixed elements, bounding-box center for static elements)
- [x] Auto-refresh the preview after a layout edit, a field remapping, or a different record being selected
- [x] Show a clear, specific message in the panel (not a blank/stale preview) when the selected record can't be resolved, e.g. a bound column missing from the loaded CSV
- [x] Unit tests: shared resolution routine correctness, preview panel resolution/refresh behavior, rotation-pivot and collapse parity with existing render/canvas tests
- [x] Live actual built-form smoke (per Sprint 6 retrospective action) showing the real preview panel rendering a real record's resolved text in the built .exe
Jump to a specific record number 5 points Done - [x] Add a UI control for the operator to enter a record number and navigate to it
- [x] Integrate a full-file forward-only streaming reader (mirroring the CLI's proven CsvRecordSource pattern) via skip/take to fetch the requested record, replacing the bounded 20-row CsvPreviewLoader for this feature — no new indexed/random-access reader
- [x] Wire the fetched record into the preview panel from the prior story so navigation updates what's shown
- [x] Handle invalid or out-of-range record numbers with a clear operator-facing message
- [x] Unit tests, including navigation against a real CSV larger than 20 rows to prove the full-file reader (not the sample loader) is actually used
- [x] Live actual built-form smoke of entering a record number and seeing the preview panel update in the built .exe
Snap elements to grid and guides 5 points Done - [x] Add a toggleable snap-to-grid state and grid increment to the canvas editing path
- [x] Update CanvasElementEditor's drag (and resize) math to round position/edges to the nearest grid increment continuously during the gesture, not only on release
- [x] Paint grid lines/guides in TemplateCanvasControl while snap is enabled so alignment is visible
- [x] Confirm snapped positions persist and reopen as ordinary X/Y values with no template format change
- [x] Unit tests for the snap math on drag and resize
- [x] Canvas-only bitmap smoke against the built app (sufficient per this story's own scope note — no form-layout/properties-panel/toolbar surface is touched, so the heavier full-form smoke is not required)

Notes

  • Capacity signal: completed totals across Sprints 1-6 are 20, 19, 18, 18, 15, 18 — six data points now, a stable 18-20 point range. Sprint 5's 15 was a deliberate under-commit around one large, high-structural-risk story (confirmed at that sprint's planning and not a capacity miss), and Sprint 6 landed exactly on an 18-point commitment with zero scope change. This plan commits 18 points, the same proven low end used for Sprints 3, 4, and 6.
  • Planning rationale — why the full epic 4 pair plus one epic 6 stretch item, not just the pair alone: the epic 4 pair (13 points) is clearly under the 18-20 range on its own, and capacity would go unused without a second pull. “Snap elements to grid and guides” (epic 6, 5 points) is deliberately chosen as that second pull over any other Ready epic 6 story: it has zero dependencies, is contained to the existing CanvasElementEditor/TemplateCanvasControl pair with no CLI/XML surface, and per its own story notes needs only a canvas-only smoke rather than the heavier full-form smoke — the lowest-risk way to reach 18 points without threatening “don't start what you can't finish.” This matches product-owner's own stretch recommendation from the 2026-10-16 refinement outcome.
  • Why not more from epic 6 instead: “Select multiple elements at once on the canvas” (5 pts) and “Align and distribute multiple elements” (5 pts, hard-depends on the former) only deliver real operator value together — splitting them across sprints repeats the exact anti-pattern epic 8 deliberately avoided in Sprints 5-6. Pulling both alongside the epic 4 pair would total 23 points, over the proven range. “Undo and redo layout changes” (13 pts) is explicitly flagged by dev-team as the backlog's highest-uncertainty estimate with no prior undo/redo-shaped story to size against, and its own sizing note recommends against pairing it with another large/novel story in the same sprint (the epic 4 preview story is itself non-trivial, touching a genuinely new drawing surface and a cross-cutting resolution-routine extraction) — deferring it whole to a future sprint, ideally with its own short feasibility check on document-cloning first, rather than starting it under time pressure this sprint.
  • Product-risk-first ordering (Sprint 6 retrospective carry-in #2, reaffirmed here): the epic 4 pair is pulled first and the epic 6 item second. The preview story exists specifically to close a known-shape defect class (divergent per-record text-resolution paths) that already caused one shipped-output defect (the 2026-10-09 rotation-positioning bug) — this outweighs epic 6's plain backlog-order position below epic 4.
  • Sprint 6 retrospective carry-in #1 (built-form smoke for GUI-facing stories) is written directly into each committed story's tasks above: both epic 4 stories touch TemplateDesignerForm layout and require a full built-form smoke; “Snap elements to grid and guides” touches canvas interaction only, so its own story notes call for a canvas-only smoke instead — this distinction is intentional, not a shortcut.
  • Sprint 6 retrospective carry-in #3 (size composite/multi-part element stories from real cross-layer code inspection): verified as already held for this sprint's candidates rather than assumed. Epic 4's preview story was sized “via code inspection of TemplateCanvasControl, AddressBlockPreviewCalculator, RenderEngine, TemplateDesignerForm” (2026-10-16); “Snap elements to grid and guides” was sized “via inspection of CanvasElementEditor.DragTo/BeginDrag/EndDrag and TemplateCanvasControl's paint routine” (2026-10-16) — both cite specific real code paths, not visible-UI guesses, so no re-sizing was needed at planning time.
  • 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 — none touch image assets or network paths.
  • Carried over from previous sprint: none (Sprint 6 completed both committed stories, 18/18 points).
  • Not committed: “Select multiple elements at once on the canvas” + “Align and distribute multiple elements” (10 pts combined, epic 6 — natural pull for Sprint 8, kept as a pair per the reasoning above); “Undo and redo layout changes” (13 pts, epic 6 — recommend a short document-cloning feasibility check before it's committed to any sprint, given its flagged uncertainty); “Warn on text overflow before render” (epic 4) remains Not Ready, blocked on an open product question for the human product owner about what “overflow” means with no element having an enforced bounding width today — not blocking this sprint.

Execution Order

Sequenced by product risk first, then feature dependency.

Batch Story Why it's gated here
1 Render an accurate, record-specific preview of the current template Highest product risk in this sprint — closes the divergent text-resolution-path defect class that already caused a shipped-output bug (2026-10-09 rotation defect). No dependency on any other committed story, and the story below depends on it.
2 Jump to a specific record number Depends on Batch 1's preview panel to have somewhere to show the navigated-to record.
3 Snap elements to grid and guides No dependency on Batches 1-2 or vice versa. Pulled last as the self-contained, lowest-risk stretch item that rounds capacity out to the proven 18-point low end; if the sprint runs short on time, this is the item to flag as at-risk first, not either epic 4 story.

Daily Scrum Log

Day Date Completed Planned Blocked/At risk
1 2026-10-19 Batch 1 (“Render an accurate, record-specific preview of the current template”, 8 points) done, all 6 ACs met. Extracted the shared per-run resolution routine into EnvelopeRenderer.Desktop.Core.Design.TextResolver (TryResolve/ComputeUnmappedFlags) and refactored AddressBlockPreviewCalculator and TemplateCanvasControl.ResolveAddressLinePreviewText to call it instead of each keeping its own near-duplicate — one resolution implementation inside Desktop.Core instead of two, mirrored by test-data parity against RenderEngineTests’ CLI scenarios (the CLI keeps its own independent RenderEngine.ResolveText, per this project's deliberate desktop/CLI project split documented in EnvelopeRenderer.Desktop.csproj). Also extracted the previously-duplicated rotation-pivot rule (static bounding-box-center vs. dynamic/mixed fixed-anchor) out of both CanvasElementEditor and TemplateCanvasControl into one shared RotationPivotCalculator. Built TemplatePreviewBuilder (framework-free, Desktop.Core) to produce ordered, already-resolved PreviewTextDraws reproducing address-line collapse, mixed-content concatenation, both rotation pivot rules, and Address Control line layout/spacing — the desktop-model mirror of RenderEngine.Render/BuildDraws. Added a new read-only TemplatePreviewControl (WinForms) wired into TemplateDesignerForm beside (not replacing) the existing editable design canvas, laid out via a deterministic two-column TableLayoutPanel (preview area + properties panel) rather than ambiguous multi-Dock-Right ordering, specifically to avoid repeating the Sprint 6 dock-order bug class. The preview auto-refreshes via Invalidate() on every TemplateCanvasControl.ElementsChanged (layout edits/remaps) since it re-resolves against the live shared document on every repaint; an unresolvable-column or no-selected-record case shows a specific red message instead of a blank/stale preview. Tests: full suite 383/383 passing (dotnet test code/EnvelopeRenderer.slnx: 101 CLI, 282 desktop — up from 349/349), with new coverage in TextResolverTests, RotationPivotCalculatorTests, and TemplatePreviewBuilderTests (the latter deliberately mirrors RenderEngineTests’ scenario names/data: mixed-content concatenation, collapsible-blank-line shift, rotated-dynamic fixed-anchor stability across different resolved text lengths, Address Control expansion/collapse/line-spacing, render/z-order, unknown-column failure). Live verification: built the desktop .exe (0 warnings/errors) and drove the real built TemplateDesignerForm/TemplateCanvasControl/TemplatePreviewControl via a throwaway reflection-driven harness project referencing the built assemblies (calling the same internal LoadCsvFromPath/NavigateToRecord entry points and public TemplateLayoutXmlSerializer/AddStaticTextElement APIs the real UI uses) — full-form screenshot confirmed no dock/clipping regression (properties panel, record-navigation toolbar, and preview panel all fully visible); preview screenshots against the real 392-record sample CSV and sample-envelope-template3.xml (Address Control) showed correctly resolved names/addresses with the blank Address 3 line correctly collapsed; preview screenshots against sample-envelope-template2.xml (rotated dynamic {Full Name}) showed the rotated text's anchor point staying fixed in the same screen position across two records with very different name lengths (“WILLIAM EDWARD ZIMMERMAN JR” vs. “MURIEL LYN ZIMMERMAN”) while the adjacent, still-token-showing design canvas visibly differed from the resolved preview — direct visual proof of this story's core fix; a synthetic bad-column template produced the exact expected “Cannot preview — the template references CSV column ‘NoSuchColumn’...” message instead of a blank panel. One new, pre-existing (not a regression) cosmetic issue noticed during this verification — Address Control lines visually overlapping in both the canvas and the new preview at small font sizes — logged to logs/technical_debt_log.md (Low impact, canvas/preview-only, does not affect the real PDF render path). Begin Batch 2 (“Jump to a specific record number”), which builds directly on this batch's preview panel and CSV-loaded-headers state. None.
1 2026-10-19 Batch 2 (“Jump to a specific record number”, 5 points) done, all 4 ACs met. Added EnvelopeRenderer.Desktop.Core.Csv.CsvRecordNavigator.TryReadRecord(path, recordNumber) — a forward-only streaming reader mirroring the CLI's proven CsvRecordSource pattern/CsvHelper configuration (not the bounded 20-row CsvPreviewLoader, and not a new indexed/random-access reader, per the story's own scope note); returns a clear operator-facing error for an out-of-range record, a non-positive record number, a missing file, or a malformed CSV, without ever throwing. Wired a “Record #” NumericUpDown + “Go” button + status label into the new preview area (TemplateDesignerForm.BuildPreviewArea/NavigateToRecord); loading a CSV now automatically navigates to record 1 through this same full-file path (replacing the prior hardcoded “first bounded-sample row” used only for the canvas's own separate collapse/mapping preview, which is unchanged). An out-of-range or unreadable navigation leaves the previously-shown preview untouched and surfaces the specific error in the status label, rather than blanking a valid preview. Tests: CsvRecordNavigatorTests covers first-record, case-insensitive headers, out-of-range, non-positive input, missing file, no-path, and — the key proof this uses the full-file reader, not the bounded sample — a record beyond row 20. Full suite still 383/383 (this story's coverage is folded into the same run as Batch 1, both landed together). Live verification (same harness/session as Batch 1): navigated to record 2, to record 392 (the last record — proving full-file reads far past the 20-row bound), and to an out-of-range record 999999 (status correctly read “Record 999999 is out of range — the CSV has 392 data row(s).” while the preview panel correctly kept showing record 392's resolved text rather than going blank), then back to record 1 via the same NumericUpDown/“Go” path an operator would use. Begin Batch 3 (“Snap elements to grid and guides”) — no dependency on Batches 1-2. None.
1 2026-10-19 Batch 3 (“Snap elements to grid and guides”, 5 points) done, all 4 ACs met, bringing Sprint 7 to 18/18 points Done. Added EnvelopeRenderer.Desktop.Core.Design.GridSnapper.Snap (pure rounding-to-nearest-increment helper) and CanvasElementEditor.SnapToGridEnabled/GridSizePoints (defaulting to off/10pt, so no existing drag behavior changes unless an operator opts in); DragTo now applies snapping on every call, i.e. continuously through the drag gesture rather than only at EndDrag. TemplateCanvasControl mirrors the same toggle/grid size for Address Control move and resize (handled directly in that control's mouse handlers, not through CanvasElementEditor) and paints light dotted grid lines across the page whenever snap is enabled, so the alignment grid is visible, not just felt. Snapped values are still plain X/Y/Width doubles — no template/XML format change, confirmed by inspection of TemplateLayoutXmlSerializer (untouched). Added a “Snap to grid” checkbox + grid-size (pt) NumericUpDown to the designer's canvas-settings toolbar. Tests: GridSnapperTests (rounding including banker's-rounding-at-exact-midpoint, non-positive grid size treated as no-op, fractional grid sizes) and new CanvasElementEditorTests cases (snap-disabled exact positioning, snap-enabled rounding, continuous-during-gesture snapping asserted mid-drag before release, custom grid size, and the off-by-default guarantee). Full suite 400/400 passing (dotnet test code/EnvelopeRenderer.slnx: 101 CLI, 299 desktop). Live verification: per this story's own scope note (canvas-interaction-only, no form/panel/toolbar layout touched), a canvas-only bitmap smoke was sufficient rather than the heavier full-form smoke — built the desktop .exe and drove the real TemplateCanvasControl via the same reflection-driven harness, invoking its actual (overridden) OnMouseDown/OnMouseMove/OnMouseUp handlers with real MouseEventArgs to simulate a genuine drag: with snap off, a drag landed at the raw pointer position (~121.6, ~559.6 for a ~123.4, ~561.7 target — pixel-rounding-only difference); with snap on at a 20pt grid, an intermediate move mid-gesture (before mouse-up) already landed exactly on-grid at (140, 480), and the final released position landed exactly on-grid at (220, 380) — both exact multiples of 20, confirming continuous snapping, not just an on-release correction. Screenshots confirmed the dotted grid lines are visible and the element visibly moved between grid intersections. Sprint 7 complete — move to product-owner Sprint Review. None.

Post-review fixes (2026-10-19)

Sprint 7 was already reviewed and retro'd before the human product owner, using the shipped increment, reported two same-increment issues. Both are fixed here as post-review corrections to already-Done Sprint 7 stories, not new sprint work — no phase transition, no new story/points.

  • “Jump to a specific record number” — auto-refresh instead of a “Go” button. The operator wants the preview to update the instant the record number changes, with no explicit click. Fixed: _recordNumberInput.ValueChanged now calls NavigateToRecord((int)_recordNumberInput.Value) directly in TemplateDesignerForm, guarded by the existing _suppressEvents flag so the programmatic reset to record 1 on CSV load (which already calls NavigateToRecord(1) explicitly right after) doesn't also fire a redundant navigation through the event handler. _goToRecordButton (the &Go button) has been removed entirely, per the human product owner's explicit preference. This is a UX correction to how the story's AC1 is satisfied (“The UI allows the operator to enter a record number and navigate to it”) — automatic instead of button-triggered — not a change to any AC's substance; ACs 2-4 (preview updates, clear message on invalid input, full-file reader) are unaffected and unchanged. See the dated note on the “Jump to a specific record number” story in backlog/epics/04_live_preview_and_record_navigation.md.
  • Rotation feel on the editing canvas — dynamic/mixed elements now rotate like static text. The Sprint 6 fixed-anchor rotation-pivot rule (dynamic/mixed content pivots around its authored (X, Y) anchor instead of its bounding-box center, to keep per-record resolved-text-width drift from moving the pivot) was being applied uniformly to the plain editing canvas as well as the real render and the new Sprint 7 preview panel — but the editing canvas only ever draws an element's literal {ColumnName} token text, never a per-record resolved value, so that drift-prevention rule never actually applied there; it just made a dynamic/mixed element's rotate-handle drag swing around a corner instead of spinning in place like static text, a jarring interactive inconsistency. Fixed, with the human product owner's explicit approval of this exact trade-off: added RotationPivotCalculator.ComputeForCanvasEditing (always takes the center-pivot path, regardless of IsDynamic) and switched TemplateCanvasControl.RotationPivot and CanvasElementEditor.RotationPivot (used for drawing, hit-testing, the drag handle's position, and live rotate-drag math) to call it. The real render (RotatedTextAnchorCalculator/DebenuPdfRenderer) and the new preview panel (TemplatePreviewControl/TemplatePreviewBuilder) are untouched — both still call RotationPivotCalculator.Compute directly with the real isDynamic value, so the original 2026-10-09 record-drift defect fix is fully preserved where it actually matters. Documented with a dated note on the “Keep rotated dynamic and mixed-content fields positioned consistently across records” story in backlog/epics/02_template_designer_gui_foundation.md (reopening language in an already-accepted AC, per this project's convention for that kind of change) and a new Deliberate-debt row in logs/technical_debt_log.md.
  • Tests updated: RotationPivotCalculatorTests gained ComputeForCanvasEditing_MatchesStaticBoundingBoxCenter and ComputeForCanvasEditing_DifferentMeasuredWidths_MovesWithTheBoundingBox. Three CanvasElementEditorTests cases that previously asserted the fixed-anchor pivot for dynamic elements on the canvas (HitTest_RotatedDynamicElement_UsesFixedAnchorPivot, HandlePosition_RotatedDynamicElement_OrbitsAroundFixedAnchor, RotateDragTo_DynamicElement_UsesFixedAnchorZeroDirection) were rewritten to assert the new center-pivot behavior instead (renamed to ..._UsesCenterPivot_SameAsStatic/..._OrbitsAroundCenter_SameAsStatic), reusing the same math as their static-element counterparts. RotatedTextAnchorCalculator/CLI tests and TemplatePreviewBuilderTests were not touched and still pass unchanged, confirming the render/preview paths are unaffected. Full suite: 400/400 before -> 402/402 after (Desktop 299 -> 301, CLI 101 unchanged; net +2: 2 new RotationPivotCalculatorTests cases, plus 3 CanvasElementEditorTests cases rewritten in place — same count, new assertions — to replace their prior fixed-anchor-for-dynamic versions).
  • Live smoke: built the desktop .exe and confirmed both fixes against the real app: dragging the rotate handle on a static element and on a dynamic/mixed element now both visibly spin in place around their own center identically (no more corner-swing for the dynamic element); typing a new record number into the “Record #” field updates the preview panel immediately with no click and no “Go” button present at all, including typing an out-of-range record number (status label correctly reported the specific out-of-range message while the preview kept showing the last valid record, matching the story's existing AC3 behavior).

Powered by TurnKey Linux.