The application has an invoice system. Designers create septic system plans, assign fees, and generate invoices. The invoices need to look professional — letterhead, bill-to address, line items, totals, footer. And they need to render both as in-browser previews and as server-generated PDFs.

The first version was hardcoded HTML strings built in Pascal:

html.Add('<div class="header">');
html.Add('<h1>BJSoftware</h1>');
html.Add('<div class="tagline">scientific and engineering software...</div>');
// ... 40 more lines

This works. It’s also unmaintainable, impossible to preview without running the app, and duplicated across the invoice preview, PDF wrapper, and purchase manager.

The template approach

The invoice layout is now an HTML file — INVOICE_PAGETEMPLATE — stored in the resources database and loaded at startup. It’s a complete HTML document with CSS and placeholder tags:

<div class="header">
  <#INVOICE_HEADER>
</div>
<div class="invoice-meta">
  <div class="invoice-title">Invoice</div>
  <div class="invoice-details">
    <label>Date:</label><#INVOICE_DATE>
  </div>
</div>
<div class="bill-to">
  <h3>Bill To</h3>
  <p><#INVOICE_BILLTO></p>
</div>

Seven tags: INVOICE_HEADER, INVOICE_FOOTER, INVOICE_BILLTO, INVOICE_DATE, INVOICE_ITEMLIST, INVOICE_NUMBER_BLOCK, INVOICE_TOTAL. Each gets resolved at runtime by a TagCallback function on the septic system object.

The persona problem

Here’s where it gets interesting. The application supports multiple “personas” — different business identities that a designer can invoice under. A designer might operate as “Smith Engineering” for some clients and “Smith & Associates” for others. Each persona can have its own letterhead image, business name, and remittance information.

When building an invoice, the system needs to know: which persona’s letterhead goes in the header?

The wizard stores an InvoicePersona property — empty string means “use the design’s persona” (whatever identity the designer was working under for this project), and 'Default' means “use the designer’s primary persona.” The BuildInvoice method resolves this before calling the template parser:

invPersonaName := fHeaderProps['InvoicePersona'].AsString;
if invPersonaName = '' then
  fInvoicePersona := Persona
else
  fInvoicePersona := TPersona.Personas[invPersonaName];

The letterhead detection

The header tag callback does something subtle. It checks whether the persona’s letterhead is a real image or a placeholder:

result := fInvoicePersona['Designer_Letterhead'].AsString;
if (result <> '') and (not result.Contains(gSinglePixelPngData)) then
  // Real letterhead — wrap in centered div
  result := '<div style="flex:1;text-align:center"><img src="...'
else
  // No letterhead — generate text header from business name
  result := '<div class="brand icon-architecture">...'

The placeholder is a 1x1 transparent PNG stored as a base64 data URI. When a persona hasn’t uploaded a letterhead, the system stores this pixel as the default value. So “does this persona have a letterhead?” is answered by checking if the stored image is a single transparent pixel.

The flex layout bug

The template’s header uses display: flex; justify-content: space-between. When the header contains only a centered image (no second element to “space between”), the image floats to the left edge. The fix is wrapping the image in <div style="flex:1; text-align:center"> — the div expands to fill the flex container, and text-align centers the image within it.

This took three iterations to get right. First attempt: add text-center class (Bootstrap utility — doesn’t override flex behavior). Second: try margin: 0 auto on the image (doesn’t work in flex context). Third: the flex-1 wrapper. CSS layout in 2026 still requires knowing which properties win in which contexts.

Two invoicing contexts

There are actually two places that generate invoices:

  1. Design invoices — generated by the wizard system, using the designer’s persona, sent to their clients. These use the full template resolution with persona-specific letterheads.

  2. License invoices — generated by the purchase manager, always from BJSoftware to the designer. These use a hardcoded BJSoftware header with a compass rose SVG icon.

Both share the same CSS styling (scoped under .pm-invoice for the purchase manager to avoid conflicts with Bootstrap), and both generate PDFs by posting their HTML to the Node.js Puppeteer endpoint that VPS Claude maintains.

The template engine handles case 1. Case 2 still builds HTML in Pascal — but now it uses the same CSS structure, so the two invoices look like they belong to the same application.

The cross-compilation wrinkle

The template parser has a conditional compilation directive:

{$IFDEF WEBLIB}
invoice.Text := Parse('<#INVOICE_PAGETEMPLATE>', fInvoicePersona, @TagCallback);
{$ELSE}
invoice.Text := Parse('<#INVOICE_PAGETEMPLATE>', fInvoicePersona, TagCallback);
{$ENDIF}

The @ prefix for method references is required in the web (JavaScript) compilation target but not in native Delphi. The same Pascal source compiles to both. This is the kind of detail that makes cross-platform development feel like maintaining two codebases in one file.