Run 2 Multi-Template — Byproducts
Summary
Section titled “Summary”The architectural pivot of the project. The shape of the print API changed from “one PDF URL or one error” to “an envelope with per-group results” and the rest of the pipeline followed. The most expensive learnings were not the service refactor itself but the cascading edits in callers that used to assume a single URL — every print-status update path, every frontend handler, every test fixture.
Learnings
Section titled “Learnings”renderGroups()as primary,render()as internal turned out to be cheaper than keeping both as public methods. Once the diagnostic flags landed in Run 3, every diagnostic concern lived on the group-shaped path; a parallel single-render path would have doubled the surface area.replace_allis not context-aware. RenamingRenderResult→CompositeRenderResultacross endpoint files double-prefixed the import lines (CompositeCompositeRenderResult). Caught and fixed inline. The lesson: stage the import edit separately from the body edits, or use a more constrained match. See implementation-log § T-2.6.- Card print-status tracking has lower fidelity than the new response shape.
cardPrinted()accepts a singleRenderResult, but a multi-template batch produces several. The syntheticRenderResultworkaround is acceptable today because the URL stored on the print event is never queried back; if a future feature reads it, this will need to be revisited. See implementation-log § T-2.5. - Frontend BFF route-by-route migration is safer than a single switch. Cards, labels, and breadcrumbs were migrated to composite handling in three independent commits inside the same PR, each with its own targeted regression test. Avoided a “big-bang switch + everything regresses simultaneously” failure mode.
- Bounded parallelism via
Semaphorevs unbounded launch was a deliberate measurement: withmaxParallelRenders = 3, end-to-end p95 for a 6-template batch was within 5% of unbounded launch in local profiling, while keeping Documint quota predictable.
Alternatives considered
Section titled “Alternatives considered”- Keep
render()returningRenderResultand add a secondrenderMany()— rejected. Two methods meant duplicate handling of the diagnostic flags landing in Run 3 and duplicate test coverage. Documented in implementation-log § Run 3 pre-implementation adjustments, where the original Run 3 plan was simplified once it was clearrenderGroups()could be the only public path. maxItemsPerRequestenforced at the endpoint layer rather than inItemPrintingService— rejected because the kanban path (PrintLifecycleImpl) needed the same enforcement and pulling it up to the endpoint would have required duplicating the check. Centralising in the printing service kept one validation point.- Per-tenant configuration for the three batch limits — rejected per decision-log § DQ-009. Limits are deployment-time configuration values, not user-facing knobs.
- Open all PDF URLs in a single tab as a multi-page combined document — rejected during planning. Documint returns one URL per render call; combining would require a server-side merge step (PDFBox or similar) that was not in scope.
- Reject mixed-size batches client-side (preserve the legacy enforcement, just give a better error) — rejected. Tickets #519 and #575 explicitly require multi-template support.
Suggestions / follow-ups
Section titled “Suggestions / follow-ups”- Revisit
cardPrinted()to take the fullCompositeRenderResult. The synthetic-RenderResultshim is correct under today’s read patterns, but a future “print job history” feature (already deferred per DQ-011) would want the real per-group URL. - Surface per-group itemCount in the frontend toast so an operator who prints 200 mixed items sees “23 small + 12 medium + 165 large opened” rather than “3 tabs opened.” Not in scope.
- Documint quota telemetry — the new pipeline can spike Documint calls (parallelism × batch splitting). A CloudWatch metric on per-tenant Documint call rate would help support diagnose burst-related failures. Not in scope.
Skipped
Section titled “Skipped”- Per-tenant configuration for
maxParallelRenders/maxItemsPerDocumintRequest/maxItemsPerRequest. Defaults only, set at module wire-up. Per DQ-009. - Server-side PDF merge to return a single URL even for multi-template batches. Out of scope; client opens one tab per group.
- Print history persistence layer. Per DQ-011; deferred to a future project.
Specification delta (post)
Section titled “Specification delta (post)”Differences between phase-2-multi-template/specification.md and what shipped:
render()is internal, not public. The specification described diagnostic flags being added torender(); the as-shipped service exposesrenderGroups()as the primary method andrender()as a private helper. Documented in implementation-log § Run 3 pre-implementation adjustments.debugPayloadlives directly onGroupRenderResult, not on a separateDiagnosticRenderResultwrapper as the spec anticipated. Functionally equivalent, structurally simpler.
No other deltas.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved