Files
2026-07-12 10:17:17 -04:00

3.3 KiB

v2 PDF Generation — Known Issues and Fixes

jspdf 4.x Array-Based doc.text() Causes Text Overlap

Symptom: Text in the generated PDF overlaps heavily — job titles, descriptions, prices, and status flags render on top of each other.

Root cause: When you pass a string array to doc.text(array, x, y), jspdf 4.x uses internal matrix-based line spacing that does NOT match manual yPos += array.length * lineHeight calculations. This causes yPos to desync from where jspdf actually rendered the text, cascading into overlap throughout the entire document.

Fix: NEVER pass arrays to doc.text(). Instead, iterate through each line and render individually:

// BROKEN (jspdf 4.x):
const lines = doc.splitTextToSize(message, maxWidth);
doc.text(lines, x, yPos);           // jspdf uses its own spacing
yPos += lines.length * 6 + 15;      // does NOT match actual render position

// FIXED:
const lines = doc.splitTextToSize(message, maxWidth);
lines.forEach((line) => {
  doc.text(line, x, yPos);
  yPos += 6;                        // explicit control per line
});
yPos += 15;

Service Height Calculation Must Match Actual yPos Increments

The page-break estimator (serviceHeight) must use the EXACT same multipliers as the actual yPos advances. Mismatched values cause services to overflow the page edge without triggering a page break.

Element Actual yPos advance Required in estimate
Customer decision badge yPos += 5 serviceHeight += 5
Service name + price yPos += 6 serviceHeight += 6
Priority (label + reason) yPos += 6; yPos += 10 serviceHeight += 16
Recommendation yPos += 8 serviceHeight += 8
Explanation (per line) yPos += 5 then yPos += 2 lines.length * 5 + 2
Parts flags (each) yPos += 8 (or 16 with extra line) 8 or 16
Separator yPos += 6 serviceHeight += 6

boxBottomY Stale After Summary Page Break

When the pricing summary box triggers a page break, boxBottomY must be updated to the new page's coordinates. Declare it with let (not const) and reassign in the page-break branch:

let boxBottomY = boxTopY + boxPadding + boxContentHeight + boxPadding;
if (boxBottomY + 50 > PAGE_HEIGHT - 20) {
  doc.addPage();
  // ... re-render box ...
  boxBottomY = bby; // update for new page position
}
// ...
yPos = boxBottomY + 10; // consistent regardless of page break

Customer Info Not Populating When Loading Saved Quote

Symptom: Services load from PocketBase but customer info fields remain empty.

Causes and fixes:

  1. reset() ordering: reset() wipes all state including customerInfo. Must call reset() BEFORE setCustomerInfo():

    reset();
    if (data.customerInfo) setCustomerInfo(data.customerInfo);  // after reset
    
  2. PocketBase JSON field type: If the PocketBase collection stores customerInfo as a text field (not json), the retrieved value is a JSON string. Handle both cases:

    const ci = typeof data.customerInfo === 'string'
      ? JSON.parse(data.customerInfo)
      : data.customerInfo;
    if (ci && typeof ci === 'object') setCustomerInfo(ci);
    

    Apply the same defensive parsing to services and discount fields.