Skip to content

Analysis: Multi-PDF Print and Bugs

This analysis compares the approved feature specifications (SAC::PRINT, SAC::PRINT-DX) and their use cases against the current implementation in operations (item module, kanban module, pdfRender module) and arda-frontend-app. The analysis identifies 12 gaps organized into three phases by priority.

The current printing pipeline works correctly for single-template batches but has three categories of issues: (1) data integrity bugs in the card notes mapping, (2) missing multi-template and batch-limiting capabilities, and (3) missing diagnostic API parameters. The frontend requires changes to handle composite responses (multiple PDFs per request) and to expose the unmark-as-printed action.

Specification vs. Implementation Comparison

Section titled “Specification vs. Implementation Comparison”

These are behaviors that contradict the specification and cause incorrect output for users.

Gap B-1: Card notes mapping sends wrong field to Documint

Section titled “Gap B-1: Card notes mapping sends wrong field to Documint”

Specification: SAC::PRINT::FR-0014 — When printing Kanban Cards, the Documint notes field shall contain KanbanCard.notes.

Current Implementation (KanbanCardPrinter.kt:48):

notes = if(card.notes == null || card.notes.isBlank())
card.itemDetails.cardNotesDefault ?: card.itemDetails.notes ?: ""
else card.notes

Gap: When KanbanCard.notes is null or blank, the code falls back to Item.cardNotesDefault and then to Item.notes (order history). Per SAC::PRINT::FR-0016, the Documint notes field should be an empty string when card notes are null or empty — notes from other sources should not leak into the card print output.

Impact: Cards print with order history text (e.g., “Ordered: 3/3/26, 9:22 AM”) in the notes area instead of being blank. Reported in #815 and #816.

Fix: Change the fallback to empty string:

notes = card.notes?.takeUnless { it.isBlank() } ?: ""

Gap B-2: Label/breadcrumb notes mapping — verification needed

Section titled “Gap B-2: Label/breadcrumb notes mapping — verification needed”

Specification: SAC::PRINT::FR-0015 — When printing Labels or Breadcrumbs, the Documint notes field shall contain Item.notes.

Current Implementation (ItemPrinter.kt:106-107):

notes = item.notes ?: "",
card_notes = item.cardNotesDefault ?: "",

Gap: The notes field correctly maps Item.notes. However, the card_notes field maps Item.cardNotesDefault — this is sent to Documint but the Documint template for labels/breadcrumbs may or may not use it. This is not a bug per se but should be verified against the Documint templates to confirm no unintended display.

Impact: Low — needs verification only.

Gap B-3: No mechanism to unmark a card as printed

Section titled “Gap B-3: No mechanism to unmark a card as printed”

Specification: SAC::PRINT::FR-0012 — The system shall provide a mechanism to reset a Kanban Card’s print status from PRINTED to NOT_PRINTED.

Current Implementation (PrintLifecycleImpl.kt:62-72): The _resultPrintState() function maps event types to states. There is no event type that transitions to NOT_PRINTED. The KanbanCardPrintEventType enum has: PRINT, REPRINT, LOST, DEPRECATE, RETIRE, DESTROY, NONE.

Gap: No UNMARK or equivalent event type exists. No endpoint exposes this action. The frontend has no UI for it.

Impact: Users cannot recover from failed physical prints. Reported in #595.

Fix: Add UNMARK to KanbanCardPrintEventType, map it to NOT_PRINTED in _resultPrintState(), expose via a new endpoint, and add frontend UI.


Category 2: Significant Gaps (Features Not Implemented)

Section titled “Category 2: Significant Gaps (Features Not Implemented)”

Gap F-1: Multi-template grouping not supported

Section titled “Gap F-1: Multi-template grouping not supported”

Specification: SAC::PRINT::FR-0001 through FR-0004 — When a bulk print request contains items with different template sizes, the system shall group items by template and produce one PDF per group.

Current Implementation:

  • Backend (ItemPrintingService.kt:92-93): Enforces single template — "All items in a print batch must have the same required template".
  • Backend (PrintLifecycleImpl.kt:56): Same enforcement for cards — "All cards in a print batch must have the same template required".
  • Frontend (page.tsx:1505-1549): Cards are grouped by cardSize client-side and sent as separate requests. Labels/breadcrumbs send all IDs in one request and rely on backend rejection.

Gap: The backend rejects mixed-size batches instead of grouping them. The response shape (RenderResult) supports only a single PDF URL.

Impact: Users cannot bulk-print items with different sizes in a single action (labels/breadcrumbs). Cards work around this via frontend grouping but produce separate requests. Reported in #575.

Specification: SAC::PRINT::FR-0007 through FR-0010 — The system shall enforce per-call and per-request batch size limits.

Current Implementation: No batch size limits exist. The entire batch is sent as one Documint call regardless of size.

Gap: Large batches (>40 items) overwhelm Documint and cause failures. No configuration for limits exists in application.conf.

Impact: Bulk printing of large inventories fails unpredictably. Reported in #519.

Gap F-3: Composite response shape not implemented

Section titled “Gap F-3: Composite response shape not implemented”

Specification: SAC::PRINT::FR-0003 — The bulk print response shall contain one result entry per template group.

Current Implementation: RenderResult is a single-URL response:

data class RenderResult(val url: URL, val job: UUID, val asOF: TimeCoordinates, val templateId: String)

Gap: No composite response type exists. The API returns exactly one URL or one error.

Impact: Blocks multi-template grouping (F-1). Frontend expects data.data.url as a single string (page.tsx:1520, 1661, 1771).

Specification: SAC::PRINT::FR-0002 — Template groups shall be rendered in parallel, up to a configurable maximum concurrency limit.

Current Implementation: Single sequential Documint call per request. No concurrency configuration.

Gap: No parallel rendering capability. No maxParallelRenders configuration.

Gap F-5: Frontend does not handle multiple PDF URLs

Section titled “Gap F-5: Frontend does not handle multiple PDF URLs”

Specification: SAC::PRINT::FR-0005, FR-0006 — The frontend shall open one new browser tab per successfully returned PDF URL and display error notifications for failed groups.

Current Implementation (page.tsx): All three handlers (handlePrintSelectedCards:1520, handlePrintSelectedLabels:1661, handlePrintSelectedBreadcrumbs:1771) expect a single data.data.url and call window.open() once. Error handling shows a single toast notification.

Gap: Frontend cannot process composite responses with multiple URLs and per-group errors.


Category 3: Missing API Exposure (Diagnostics)

Section titled “Category 3: Missing API Exposure (Diagnostics)”

Specification: SAC::PRINT-DX::FR-0003 through FR-0005 — When debug=true, the API response shall include the Documint payload.

Current Implementation: No debug query parameter on any print endpoint. The constructed payload is logged to CloudWatch but not returned to the caller.

Gap: Support team cannot inspect what was sent to Documint without AWS log access.

Impact: Reported in #762.

Specification: SAC::PRINT-DX::FR-0006 through FR-0009 — When dry-run=true, the system shall construct the payload but not call Documint.

Current Implementation: No dry-run query parameter. Every print request calls Documint.

Gap: Support team cannot inspect payload construction without triggering a render and consuming quota.

Impact: Reported in #792.

Gap D-3: Live-print documentation alignment and OpenAPI descriptions

Section titled “Gap D-3: Live-print documentation alignment and OpenAPI descriptions”

Note: All diagnostic parameters must include detailed descriptions in the endpoint route definitions so they appear in the OpenAPI specification.

Specification: SAC::PRINT-DX::FR-0001, FR-0002 — All print endpoints shall accept live-print with documented behavior.

Current Implementation: The live-print parameter exists on all three print endpoints. Cards default to live=true (KanbanCardEndpoint.kt:375), labels/breadcrumbs default to false (from module config documint.useLive). Frontend always sends live-print=true.

Gap: Not a code gap — parameter already works. Needs documentation alignment only to clarify the test/preview vs. production distinction.


Gap A-1: Existing print API tests are disabled and incomplete

Section titled “Gap A-1: Existing print API tests are disabled and incomplete”

Current Implementation: The api-test repository has 5 Bruno test files for print endpoints:

  • collections/item/bearer-auth/Printing/PrintLabels.bru — tagged disabled
  • collections/item/bearer-auth/Printing/PrintBreadcrumb.bru — tagged disabled
  • collections/kanban/Print-Cards/PrintOneCard.bru — tagged disabled
  • collections/kanban/Print-Cards/PrintMultpleCards.bru — tagged disabled
  • collections/partitionCheck/pdf-render/list-templates.bru — tagged disabled

Gap: All printing tests are disabled. They test the current single-URL response shape and do not cover: composite responses, mixed-size batches, batch limiting, the unmark endpoint, debug/dry-run parameters, or the notes mapping fix. After Phases 1-3, these tests must be updated and new tests added to validate the changed API contracts.


Gap A-2: api-proxy types and methods do not match updated API contracts

Section titled “Gap A-2: api-proxy types and methods do not match updated API contracts”

Current Implementation: The api-proxy package defines RenderResult (reference/shared/types.ts) as a single-URL response, and ItemProxy.printLabels(), ItemProxy.printBreadcrumbs(), KanbanProxy.printCards() all return Promise<RenderResult>. No unmarkPrinted() method exists on KanbanProxy. No diagnostic parameter support.

Gap: After Phases 1-3, the API contracts change: composite response shape, new unmark endpoint, and diagnostic query parameters. The TypeScript proxy types and methods must be updated to match.


#Specification RequirementStatusCategoryPhase
B-1SAC::PRINT::FR-0014, FR-0016 (card notes mapping)BugBreaking1
B-2SAC::PRINT::FR-0015 (label/breadcrumb notes)VerifyBreaking1
B-3SAC::PRINT::FR-0012 (unmark as printed)GapBreaking1
F-1SAC::PRINT::FR-0001 to FR-0004 (multi-template grouping)GapFeature2
F-2SAC::PRINT::FR-0007 to FR-0010 (batch limiting)GapFeature2
F-3SAC::PRINT::FR-0003 (composite response)GapFeature2
F-4SAC::PRINT::FR-0002 (parallel rendering)GapFeature2
F-5SAC::PRINT::FR-0005, FR-0006 (frontend multi-URL)GapFeature2
D-1SAC::PRINT-DX::FR-0003 to FR-0005 (debug payload)GapDiagnostics3
D-2SAC::PRINT-DX::FR-0006 to FR-0009 (dry-run)GapDiagnostics3
D-3SAC::PRINT-DX::FR-0001, FR-0002 (live-print docs)Docs onlyDiagnostics3
A-1API test coverage for all print endpointsGapTesting4
A-2api-proxy types and methods out of dateGapProxy5

Scope: Gaps B-1, B-2, B-3. Repositories: operations (kanban module, item module), arda-frontend-app. Rationale: These are customer-reported bugs with immediate impact. B-1 causes incorrect printed output. B-3 prevents recovery from failed prints. B-2 is a low-risk verification.

Phase 2: Multi-Template Printing (New Features)

Section titled “Phase 2: Multi-Template Printing (New Features)”

Scope: Gaps F-1, F-2, F-3, F-4, F-5. Repositories: operations (item module, kanban module, pdfRender module), arda-frontend-app. Rationale: This is the primary feature work. F-3 (composite response) and F-1 (grouping) are the architectural changes that enable F-2 (batch limiting), F-4 (parallel rendering), and F-5 (frontend handling). These should be implemented together.

Scope: Gaps D-1, D-2, D-3. Repositories: operations (item module, kanban module, pdfRender module). Rationale: These are additive capabilities for the support persona. No breaking changes. Can be implemented independently after Phase 2 since they benefit from the refactored printing pipeline.

Scope: Gap A-1. Repositories: api-test. Rationale: After all code changes are deployed to a test environment, the existing disabled print tests must be updated and new tests added to validate the changed API contracts. This phase runs against the deployed system to verify end-to-end behavior.

Scope: Gap A-2. Repositories: api-proxy. Rationale: The @arda-cards/api-proxy TypeScript package must reflect the updated API contracts. Small scope (type definitions + proxy method updates) but requires its own PR since it’s a separate repository with independent CI/CD.

ModulePhase 1Phase 2Phase 3Phase 4Phase 5
item (ItemPrintingService, ItemPrinter, ItemEndpoint)B-2 verifyF-1, F-3, F-4D-1, D-2
kanban (KanbanCardPrinter, PrintLifecycleImpl, KanbanCardEndpoint)B-1 fix, B-3 new endpointF-1, F-3 (response shape)D-1, D-2
pdfRender (PdfRenderService)F-1, F-2, F-3, F-4 (grouping, batching, parallelism)D-1, D-2 (passthrough)
arda-frontend-app (page.tsx, API routes)B-3 (unmark UI)F-5 (multi-URL handling)
api-test (Bruno)A-1
api-proxy (TypeScript)A-2

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