# Document Templates Quality Analysis Report

**Date:** June 15, 2026
**Location:** `/home/vincent/projects/agentforms/app/templates/documents/`
**Render Engine:** WeasyPrint (HTML/CSS → PDF)
**Template Engine:** Jinja2

---

## Shared Base Template / Common Styles

**Finding: NO shared base template for document PDFs.**

Each of the 10 templates is a fully self-contained HTML document with inline `<style>` blocks. There is NO inheritance, NO `{% extends %}`, and NO shared mixin between document templates.

A `_shared.css.html` file exists in `/app/templates/` but it is for the **web UI** (buttons, forms, cards, nav) — it is NOT used by any document template.

**Implication:** Every CSS variable reset, page-break rule, watermark, and status badge is duplicated across templates. Fixing a bug or improving typography means editing 10 separate files.

---

## 1. stripe.html (565 lines)

**Quality Assessment:** ★★★★☆ — Very professional, well-designed SaaS-style invoice

**Strengths:**
- Clean, modern Stripe-inspired design with proper visual hierarchy
- Good use of CSS custom properties (`--primary`, `--text-primary`, etc.)
- Proper `@page` rules with running headers/footers
- Subtle alternating row backgrounds for table readability
- `font-variant-numeric: tabular-nums` for aligned numbers
- Status badges with accessible color coding
- Watermark support
- Logo support with fallback to company initial

**Issues:**
- **Font loading:** References `'Inter'` font but never loads it via `@font-face`. If the system doesn't have Inter installed, it falls back silently to sans-serif — inconsistent rendering across machines.
- **Line 438:** `| replace('', '')` filter is a no-op bug — this was clearly intended to clean the string for `truncate(1)` but replaces empty with empty. The fallback logo initial will still work, but the filter is dead code.
- **Line 515:** `item.qty` should be `item.quantity` for consistency (uses `.qty` here but `.quantity` in description). Minor inconsistency.
- **Watermark color hardcoded:** Uses `rgba(5, 150, 105, 0.06)` (green) regardless of `--primary` color — if a user sets their primary to red, the watermark stays green.
- **Grand total border:** Uses `2px solid var(--text-primary)` which is very dark — could be softer.

**Professional upgrade path:**
- Add `@font-face` or use a system font stack that actually exists on the render server
- Make watermark color derive from `--primary` 
- Fix the `| replace('', '')` no-op
- Normalize `item.qty` vs `item.quantity` field names

---

## 2. hubspot.html (535 lines)

**Quality Assessment:** ★★★☆☆ — Good but has some aggressive styling choices

**Strengths:**
- Bold hero header with colored background — very recognizable HubSpot style
- Full-width colored header spans the page (`@page { margin: 0; }`)
- Card-based info sections with `border-radius`
- Proper status badges
- Discount support in totals

**Issues:**
- **Font loading:** Same `'Inter'` reference without `@font-face` — unreliable fallback
- **Zero page margins:** `@page { margin: 0; }` means the hero header and footer bleed to absolute page edges. Some PDF viewers/printers crop this. Most professional invoices keep at least 5-10mm margins.
- **Hero title too large:** `font-size: 32px` for "INVOICE" in the header — this dominates the page. The title should be secondary to the content.
- **Primary color hardcoded in table headers:** `thead th { background: var(--primary); }` — solid primary color table headers with white text can have poor contrast depending on the chosen primary. No contrast check.
- **Status badge colors:** HubSpot-specific colors (`#26BA37`, `#FF7A59`, `#D73953`) are hardcoded instead of using semantic CSS variables.
- **Line 415:** Same `| replace('', '')` no-op as stripe.html
- **Watermark color hardcoded** to green `rgba(38, 186, 55, 0.06)` — doesn't adapt to user's primary color

**Professional upgrade path:**
- Restore page margins (at least 5mm on all sides)
- Reduce hero title size or weight
- Add contrast-safe table header styling
- Derive watermark and status colors from `--primary`

---

## 3. notion.html (602 lines)

**Quality Assessment:** ★★★★☆ — Creative sidebar layout, very polished

**Strengths:**
- Unique sidebar + main content layout — visually distinctive
- Notion-inspired design with soft backgrounds and subtle colors
- Clean typography with proper hierarchy
- Discount support in totals
- Currency display in payment details
- `border-collapse: separate` with `border-radius` for rounded table corners
- Proper alternating row backgrounds

**Issues:**
- **Font loading:** Same `'Inter'` without `@font-face`
- **Sidebar width:** Fixed `width: 180px` sidebar — this consumes significant A4 space. On a 210mm wide page with 20mm margins (170mm content), the sidebar takes 180px (~48mm), leaving only ~122mm for content. Tight for long line item descriptions.
- **Sidebar content overflow:** If company address is long, the sidebar could push content awkwardly. No overflow handling.
- **Line 462:** Same `| replace('', '')` no-op bug
- **Watermark color hardcoded** to `rgba(35, 131, 226, 0.06)` — blue, doesn't adapt
- **Zero page margins:** `@page { margin: 0; }` — the sidebar design depends on this, but it risks content being cropped at print.
- **`@top-center` running header:** Only stripe.html uses `element(header-bar)`. Notion.html does NOT — inconsistent.

**Professional upgrade path:**
- Add `@font-face` for Inter or use a guaranteed system font
- Consider making sidebar narrower or conditional
- Add `overflow: hidden` on sidebar content
- Standardize page margins with the sidebar integrated

---

## 4. classic.html (346 lines)

**Quality Assessment:** ★★★☆☆ — Traditional, functional but dated

**Strengths:**
- Configurable page format (`{{ style.page_format }}`)
- Configurable margins via style variables
- Clean, traditional invoice layout
- Configurable font family
- Simpler design means fewer rendering issues

**Issues:**
- **No CSS custom properties for text colors:** Uses hardcoded `#1a1a1a`, `#666`, `#999` instead of semantic variables like `--text-primary`, `--text-secondary`. Harder to theme consistently.
- **No border-radius or modern styling:** Completely flat design. This isn't necessarily bad for a "classic" theme, but it feels unfinished compared to the others.
- **No `font-variant-numeric: tabular-nums`:** Numbers in the table won't align vertically — monetary amounts will look misaligned.
- **Table headers:** Solid primary background with white text — same contrast issue as hubspot.
- **Logo rendering:** Logo is placed before the header div (line 252) but only if `logo_url` exists. No fallback branding element if no logo.
- **Watermark color hardcoded** to green
- **No status badges** at all — missing a key feature present in 7 other templates
- **No `border-collapse`/`border-spacing` on table** — uses default rendering
- **No page break prevention on line items tbody rows** (wait, actually it does have `.line-items tbody tr { page-break-inside: avoid; }` — good)
- **No `@font-face` for font** — same problem, though this one does support `{{ style.font_family }}` customization

**Professional upgrade path:**
- Add `font-variant-numeric: tabular-nums` for number alignment
- Add status badge support
- Add semantic CSS variables for colors
- Improve logo handling with fallback branding

---

## 5. modern.html (352 lines)

**Quality Assessment:** ★★★★☆ — Clean card-based design with good visual hierarchy

**Strengths:**
- Card-based layout with `box-shadow` and `border-radius: 12px`
- Colored banner header similar to hubspot but more refined
- Info cards with subtle backgrounds
- Configurable page format and margins
- Rounded table with `border-collapse: separate`
- Good use of spacing and breathing room
- Watermark as inline style (avoids Jinja scoping issue)

**Issues:**
- **Font loading:** No `@font-face`, but does support `{{ style.font_family }}` — better than others
- **`box-shadow` on `.invoice`:** `box-shadow: 0 1px 3px rgba(0,0,0,0.05)` — shadows in PDF are generally fine with WeasyPrint, but adds visual noise that may not print well on all printers
- **No `font-variant-numeric: tabular-nums`:** Same number alignment issue
- **No status badges:** Missing a feature present in most other templates
- **Banner right-aligned h1:** `font-size: 32px` is quite large — similar issue to hubspot
- **No `border-bottom: none` on last row:** Actually it does have `.line-items tbody tr:last-child td { border-bottom: none; }` — good
- **Watermark uses inline style in `<div>`:** This works but is inconsistent with other templates that use CSS classes

**Professional upgrade path:**
- Add `font-variant-numeric: tabular-nums`
- Add status badge support
- Consider reducing banner title size

---

## 6. professional.html (489 lines)

**Quality Assessment:** ★★★★★ — Best overall design in the set

**Strengths:**
- Accent stripe at top — elegant, subtle branding element
- Excellent typography: `letter-spacing: -0.3px` on company name, proper weights
- Most sophisticated status badge system with 5 states (draft, sent, viewed, paid, void)
- Semantic color variables throughout
- `font-variant-numeric: tabular-nums` — numbers align properly!
- Footer with 3-column grid (notes, terms, issuer) — most comprehensive
- Alternating row backgrounds with `#fafbfc`
- Left border accent on totals card (`border-left: 3px solid var(--primary)`)
- Watermark opacity `0.08` — subtle, professional
- `vertical-align: top` on table cells — prevents alignment issues with multi-line descriptions

**Issues:**
- **Font loading:** No `@font-face` for Inter, though supports `{{ style.font_family }}`
- **Status badge in CSS but conditional in Jinja:** Lines 118-135 wrap status badge CSS in `{% if data.document_status %}` — this is unusual and risky. If a document has no status, the CSS classes aren't even emitted. Works, but unconventional.
- **No page margins configured:** Uses configurable margins which is good, but `@page` doesn't set `margin: 0` — so it relies on WeasyPrint defaults when margins aren't passed.
- **No discount support in totals:** Unlike stripe, hubspot, notion, and customer_portal, this template doesn't render discounts.
- **Date format inconsistent:** Uses `{{ data.issue_date }}` directly (line 378) instead of `{{ data.issue_date | date_format }}` — this could output raw datetime objects instead of formatted strings.

**Professional upgrade path:**
- Add discount support in totals section
- Fix `data.issue_date` to use `| date_format` filter
- Consider extracting status badge CSS from conditional block

---

## 7. minimal.html (317 lines)

**Quality Assessment:** ★★★☆☆ — Clean minimal aesthetic but lacking features

**Strengths:**
- Truly minimal design — lots of whitespace, subtle borders
- Outlined payment button (no fill) — appropriate for minimal aesthetic
- Subtle footer with `#ccc` gray
- Good typography hierarchy
- Configurable page format and margins
- Configurable font family

**Issues:**
- **Font loading:** No `@font-face`
- **No `font-variant-numeric: tabular-nums`:** Numbers won't align
- **No status badges:** Missing entirely
- **No table header styling:** Headers are just gray text with a thin border — very understated. In minimal design, this could work, but the headers nearly blend into the content.
- **Totals section:** Only subtotal and total — no border separator between rows. The grand total has a thin `border-top: 1px solid #f0f0f0` which is extremely subtle — might be hard to read.
- **No payment info section:** Unlike stripe/hubspot/notion, there's no dedicated "Payment Details" block
- **No `border-collapse`/`border-spacing` on table**
- **Payment button uses `:hover` styles:** `:hover` is meaningless in PDF — dead CSS
- **No `border-radius` on anything:** Completely flat, which is fine for "minimal" but feels unpolished

**Professional upgrade path:**
- Add `font-variant-numeric: tabular-nums`
- Add status badges
- Remove `:hover` styles (PDFs don't support hover)
- Add subtle table header styling

---

## 8. bold.html (332 lines)

**Quality Assessment:** ★★☆☆☆ — Aggressive styling with several issues

**Strengths:**
- Strong visual impact — full-width colored header
- Bold typography (`font-weight: 900`, uppercase, wide letter-spacing)
- Configurable page format and margins
- Left border accent on totals card
- Configurable font family

**Issues:**
- **Font loading:** No `@font-face`
- **No `font-variant-numeric: tabular-nums`:** Numbers won't align
- **`font-weight: 900` everywhere:** Heavy use of 900 weight (bold header, bold total, bold footer) — this looks aggressive and can cause rendering issues in PDF (some PDF fonts don't have a true 900 weight and render blurry).
- **Table headers:** Solid primary background — contrast risk
- **Footer is all uppercase:** `text-transform: uppercase` on footer text — feels shouty for a "Thank you" message
- **No status badges:** Missing entirely
- **No `border-collapse`/`border-spacing` on table**
- **No alternating row backgrounds:** All rows look identical, harder to scan
- **No discount support:** Missing feature
- **`margin: -20px -40px` on header:** Negative margins to bleed to page edges — fragile, depends on body padding being exactly `20px 40px`. If body padding changes, the header misaligns.
- **Watermark color hardcoded** to green

**Professional upgrade path:**
- Tone down font weights from 900 to 700
- Add `font-variant-numeric: tabular-nums`
- Add status badges
- Remove negative margin trick — use proper page layout
- Add alternating row backgrounds

---

## 9. receipt.html (237 lines)

**Quality Assessment:** ★★★☆☆ — Appropriate receipt format but some inconsistencies

**Strengths:**
- Letter page size — correct for US-style receipts
- Narrow `max-width: 400px` — appropriate for receipt format
- Dashed borders — receipt-like aesthetic
- Compact layout — good use of space
- Centered header — traditional receipt style
- Notes section with background highlight

**Issues:**
- **Font loading:** No `@font-face`, supports `{{ style.font_family }}`
- **No `font-variant-numeric: tabular-nums`:** Numbers won't align
- **No `border-collapse`/`border-spacing` on table**
- **No status badges:** Receipts typically don't need status, so this is acceptable
- **`@page { margin: 10mm; }`** with **no `@bottom-center` footer:** No page numbering — acceptable for single-page receipts
- **No `page-break-inside: avoid` on items:** Actually has `.totals { page-break-inside: avoid; }` which is good
- **Grand total uses `TOTAL` instead of `Total`:** Inconsistent with other templates
- **Watermark uses inline style:** Like modern.html, this works but is inconsistent
- **No currency display in payment details section:** Acceptable for receipt format
- **No `border-radius` on table:** Receipts traditionally don't have rounded corners, so this is fine
- **Body padding only `15px`:** Very tight padding inside the `@page` 10mm margins

**Professional upgrade path:**
- Add `font-variant-numeric: tabular-nums`
- Consider adding a "Payment Method" field (Cash, Card, etc.)
- Standardize grand total label casing

---

## 10. customer_portal.html (536 lines)

**Quality Assessment:** ★★★☆☆ — Very similar to hubspot, some copy-paste issues

**Strengths:**
- Clean card-based layout
- Full-width colored hero header
- Status badges with solid background colors
- Discount support in totals
- `font-variant-numeric: tabular-nums` — numbers align properly!

**Issues:**
- **Near-identical to hubspot.html:** 95%+ code duplication. The only real differences are:
  - Primary color default (`#6366F1` indigo vs `#FF7A59` HubSpot orange)
  - Status badge colors (Tailwind palette vs HubSpot palette)
  - `--primary-dark` and `--primary-light` variables (customer_portal has them, hubspot doesn't use them)
- **Font loading:** Same `'Inter'` without `@font-face`
- **Zero page margins:** `@page { margin: 0; }` — same issue as hubspot
- **Line 416:** Same `| replace('', '')` no-op bug
- **Watermark color hardcoded** to green `rgba(5, 150, 105, 0.06)` — doesn't adapt
- **Hero title too large:** Same 32px "INVOICE" issue as hubspot
- **Status badge `draft` uses `var(--bg-subtle)` background:** Very low contrast — nearly invisible

**Professional upgrade path:**
- Extract shared hero-header pattern into a common component or mixin
- Restore page margins
- Fix `| replace('', '')` no-op
- Make watermark adaptive to primary color
- Reduce hero title size

---

## Cross-Cutting Issues (Apply to ALL Templates)

### Critical:
1. **No `@font-face` declarations:** All templates reference `'Inter'` but none load it. The rendered PDF font depends entirely on what's installed on the server. If Inter isn't installed, every template silently degrades to a generic sans-serif. **This is the single biggest quality issue.**

2. **`font-variant-numeric: tabular-nums` missing in 7/10 templates:** Only stripe, hubspot, notion, customer_portal, and professional have tabular numerals. classic, modern, minimal, bold, and receipt do NOT. Without this, decimal points and digits don't align vertically in tables.

3. **Watermark colors hardcoded to green:** Every template hardcodes watermark opacity/color to a green shade, regardless of the user's chosen primary color. Should derive from `--primary`.

4. **`| replace('', '')` no-op bug:** Present in stripe, hubspot, notion, and customer_portal. This filter replaces an empty string with an empty string — it does nothing. Was intended to clean the string before `truncate(1)`.

5. **No shared base template:** 10 completely independent files. Any improvement (font loading, watermark fix, status badges) must be applied to all 10 files manually.

### Moderate:
6. **Inconsistent field names:** Some templates use `item.qty`, others `item.quantity`. Some use `data.document_number`, others `data.invoice_number`. Some use `data.document_status`, others `data.status`.

7. **`@page { margin: 0; }` in 3/10 templates:** hubspot, notion, and customer_portal use zero margins. This can cause content to be cropped by PDF viewers and printers.

8. **`:hover` styles in PDF:** minimal.html has hover styles on the payment button — these are meaningless in a static PDF.

9. **No discount support:** 5/10 templates (classic, modern, minimal, bold, receipt) don't support discount rendering.

10. **Date formatting inconsistency:** professional.html uses `{{ data.issue_date }}` directly instead of `{{ data.issue_date | date_format }}`.

### Minor:
11. **No `border-collapse: separate` with `border-radius`** in classic, minimal, bold, receipt — table corners are sharp in these templates.

12. **No `vertical-align: top` on table cells** in most templates — multi-line descriptions can misalign with quantity/price columns.

13. **Status badge implementation varies:** 5 templates have them, 5 don't. The CSS class names and colors are inconsistent across templates.

---

## Template Comparison Matrix

| Feature | stripe | hubspot | notion | classic | modern | professional | minimal | bold | receipt | customer_portal |
|---|---|---|---|---|---|---|---|---|---|---|
| Tabular nums | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ |
| Status badges | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ (5 states) | ✗ | ✗ | ✗ | ✓ |
| Discount support | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
| Configurable margins | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
| Configurable font | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| Watermark | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Logo support | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ |
| Page numbering | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
| `@font-face` | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| `| replace('', '')` bug | ✓ | ✓ | ✓ | N/A | N/A | N/A | N/A | N/A | N/A | ✓ |
| Zero page margins | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
| Payment info section | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |

---

## Recommendations for a Professional Version

### Immediate Wins (Low Effort):
1. **Add `@font-face` for Inter** (or embed a free alternative like system fonts) — fixes ALL 10 templates
2. **Add `font-variant-numeric: tabular-nums`** to the 7 templates missing it
3. **Fix watermark color** to derive from `--primary` variable
4. **Remove `| replace('', '')` no-op** — replace with proper string cleaning or remove
5. **Remove `:hover` styles** from minimal.html

### Medium Effort:
6. **Create a shared document base:** A `{% block %}`-based template or a common CSS mixin that all 10 extend, reducing duplication
7. **Standardize data field names** across all templates (document_number vs invoice_number, status vs document_status, qty vs quantity)
8. **Add discount support** to the 5 templates that lack it
9. **Add status badges** to the 5 templates that lack them
10. **Fix `@page { margin: 0; }`** templates to use safe margins

### High Effort:
11. **Audit contrast ratios** for primary color usage in table headers (white text on arbitrary primary color)
12. **Add `@page` header/footer consistency** — ensure all templates have running headers/footers or none do
13. **Add `vertical-align: top`** to all table cells for consistent multi-line rendering
14. **Standardize the totals section** — consistent styling across all 10 templates
15. **Consider CSS `@import`** for a shared document.css to reduce code duplication
