# Print Layouts

**Date:** 2026-06-01
**Status:** Draft
**Spec:** 161
**Tier:** All tiers
**Depends on:** `invoices-core`, `contracts-esignature`, `proposal-pdf-export`
**Referenced by:** `invoices-core`, `proposal-pdf-export`

---

## Overview

No spec defines `@media print` CSS for zync-app pages. Invoices (spec 15) and contracts (spec 48) generate PDFs via the HTML-to-PDF Worker for archival. Print layouts serve a different need: in-browser `Ctrl+P` / `window.print()` for quick hard-copy printing of invoices, quotes, and proposals without a server roundtrip.

This spec defines print CSS for the three document types users commonly print directly from the browser:
1. Invoice detail page (`/invoices/:id`)
2. Proposal public preview page (`/p/:token`) — already noted in spec 159
3. Customer statement page (`/customers/:id/statement`) — spec 34 (ar-aging-report)

---

## Scope

Print layout = `@media print` CSS applied to existing page HTML. No new pages or API endpoints.

---

## Invoice Print Layout (`/invoices/:id`)

### What shows in print

- Tenant logo + name, contact email, phone
- Invoice number, issue date, due date
- Customer name + billing address
- Line items table (description, qty, unit price, amount)
- Subtotal, tax line per rate, total
- Payment terms, payment instructions (from `tenant_settings.invoice_footer_text`; spec 125)
- Invoice status watermark: "PAID" (green diagonal) or "VOID" (red diagonal) if applicable

### What hides in print

- Sidebar navigation
- Top navigation bar
- Action buttons (Send, Edit, Download PDF, etc.)
- Browser-injected URL and date (via `@page { margin: 2cm; }`)

### CSS structure

```css
@media print {
  /* hide chrome */
  [data-sidebar], [data-topbar], [data-invoice-actions] {
    display: none !important;
  }

  /* page setup */
  @page {
    size: A4;
    margin: 2cm;
  }

  /* status watermark */
  [data-invoice-status="PAID"]::after {
    content: "PAID";
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(-45deg);
    font-size: 8rem;
    font-weight: 700;
    color: color-mix(in oklch, var(--success) 20%, transparent);
    pointer-events: none;
    z-index: 9999;
  }

  [data-invoice-status="VOID"]::after {
    content: "VOID";
    color: color-mix(in oklch, var(--danger) 20%, transparent);
    /* same positioning as PAID */
  }

  /* line items table: no page break inside rows */
  [data-line-items-table] tr {
    page-break-inside: avoid;
  }

  /* totals block: keep together */
  [data-invoice-totals] {
    page-break-inside: avoid;
  }
}
```

Data attributes (`data-sidebar`, `data-topbar`, `data-invoice-actions`, `data-invoice-status`, `data-line-items-table`, `data-invoice-totals`) are added to existing invoice page components.

---

## Proposal Public Preview Print Layout (`/p/:token`)

Already noted in spec 159: public page uses `window.print()` triggered by "Print / Save as PDF" button in footer.

```css
@media print {
  [data-proposal-footer-actions] {
    display: none !important;
  }

  @page {
    size: A4;
    margin: 2cm;
  }

  /* section page breaks */
  [data-proposal-section] {
    page-break-inside: avoid;
  }

  /* keep pricing table together */
  [data-proposal-pricing] {
    page-break-inside: avoid;
  }
}
```

---

## Customer Statement Print Layout (`/customers/:id/statement`)

Customer statement = aging statement for a customer defined in spec 34 (ar-aging-report); statement view is an aggregated read).

```css
@media print {
  [data-sidebar], [data-topbar], [data-statement-actions] {
    display: none !important;
  }

  @page {
    size: A4;
    margin: 2cm;
  }

  [data-statement-row] {
    page-break-inside: avoid;
  }
}
```

---

## Schema Delta

None. Print layout is pure CSS.

---

## RTL Print Support

Hebrew tenants print RTL documents. Print CSS must preserve the document's text direction — it is inherited from `<html dir="rtl">` automatically. No additional CSS is needed for basic RTL.

However, watermark text must be localised for Hebrew:

```css
/* Hebrew locale — watermark text */
html[dir="rtl"] [data-invoice-status="PAID"]::after {
  content: "שולם";
}

html[dir="rtl"] [data-invoice-status="VOID"]::after {
  content: "מבוטל";
}
```

Table column order reverses automatically under `dir="rtl"` (browser handles it). Line-items table uses logical CSS properties (`padding-inline-start` etc.) so print output is visually correct without extra `@media print + rtl` overrides.

Verify: test `Ctrl+P` on an invoice detail page with `html[dir="rtl"]` set — confirm columns appear right-to-left (Amount → Description, not Description → Amount).

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| `@media print` in component CSS | Not a separate print stylesheet | Co-located with the component it styles; changes to component layout update print layout in the same PR |
| Data attributes for print targets | Not class names | Class names carry styling semantics and can be renamed during refactors; data attributes are explicit print targets |
| A4 page size | Not letter | Primary market is IL (A4); letter is a US standard; PDF Worker also targets A4 (spec 15) — consistent |
| Watermark via `::after` | Not a hidden DOM element | Zero HTML change; pseudo-element doesn't affect layout or accessibility tree |
