Skip to content

Run 2 Multi-Template — Byproducts

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.

  • 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_all is not context-aware. Renaming RenderResultCompositeRenderResult across 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 single RenderResult, but a multi-template batch produces several. The synthetic RenderResult workaround 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 Semaphore vs unbounded launch was a deliberate measurement: with maxParallelRenders = 3, end-to-end p95 for a 6-template batch was within 5% of unbounded launch in local profiling, while keeping Documint quota predictable.
  • Keep render() returning RenderResult and add a second renderMany() — 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 clear renderGroups() could be the only public path.
  • maxItemsPerRequest enforced at the endpoint layer rather than in ItemPrintingService — 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.
  • Revisit cardPrinted() to take the full CompositeRenderResult. The synthetic-RenderResult shim 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.
  • 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.

Differences between phase-2-multi-template/specification.md and what shipped:

  • render() is internal, not public. The specification described diagnostic flags being added to render(); the as-shipped service exposes renderGroups() as the primary method and render() as a private helper. Documented in implementation-log § Run 3 pre-implementation adjustments.
  • debugPayload lives directly on GroupRenderResult, not on a separate DiagnosticRenderResult wrapper as the spec anticipated. Functionally equivalent, structurally simpler.

No other deltas.


Copyright: (c) Arda Systems 2025-2026, All rights reserved