- Replace dark navy (30,45,69) with accent blue #2f6fed (47,111,237) throughout:
issuer name, invoice word, charge-to name, table header bar, divider line, to-pay bar
- Named colour constants (ACCENT, BODY, MUTED, BORDER, WHITE, STRIPE) for clarity
- Filename: [ISSUER]_[YYYYMMDD]_[INVOICENUMBER].pdf with non-alphanumeric chars sanitised
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
- Add id="action-row" to button flex div for reliable message anchoring
- Insert validate message after #action-row (not via btn.closest)
- Both Save and Validate already use kb-btn--ghost; explicit in markup
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
Replace fragile viewBox string-replace with proper width/height injection
so the 24px icon renders at the right size. Use non-breaking space in
.meta when no invoice number is set (preserves row height).
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
- Always use kb-brand structure (remove app-wordmark fallback path)
- Default brand: kbenestad.invoice / Invoice Generator when org-name
and org-subheading are absent from config
- Remove .app-wordmark CSS class
- kb-footer max-width 980px to match kb-wrap
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
Remove align-self:center from line total span so it sits at the top of
the row. Use sp()+forEach in buildPDF to wrap sender and charge-to
contact lines within their column instead of overflowing.
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
- Bidirectional FX rate entry: rcur dropdown (e.g. "35 THB per USD" or
"0.028 USD per THB") with rother label updating dynamically
- FX labels from config translations; currency code appended at runtime
- ct-pick persisted to localStorage; restoreStorage rebuilds client-
specific project code options before re-applying saved pcode value
- Footer rebuilt by buildFooter() with dynamic About link
- About modal with markdown-rendered content from config.yml (marked.js)
- about section added to config.yml with EN/DE/FR/NO content
- rcur saved/restored in inv_lines_v1; gatherData includes rcur in fxNote
- relabel() calls updateFxLabels() for each active FX line on lang switch
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
Replaces the generic file-icon placeholder with the real invoice.svg squircle
(28px, 82% opacity, accent fill with white glyph). Strips the brand text to
12px muted "kBenestad" at 70% opacity. Tightens header vertical padding so
the whole bar recedes after a few uses — noticeable on first visit, invisible
by the third.
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
Replaces all custom CSS with the kBenestad token set (Schibsted Grotesk +
JetBrains Mono, Nordic-minimal palette, single #2F6FED accent). Converts
the navy-banner layout to kb-header/kb-card/kb-grid/kb-row structure from
kbenestad-forms.css. Adds auto + manual dark mode via data-theme. Switches
line-item and tax tables from <table> to CSS-grid .kb-row divs. Adds a
light/dark toggle button to the toolbar. All JS logic, IDs, localStorage
keys, and PDF output are unchanged.
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
This document serves as a UI design brief for kBenestad web apps, detailing visual guidelines, design tokens, typography, spacing, and more to ensure consistency across applications.
Products in config.yml can now include a qty field. When a predefined
line is selected the quantity input is pre-filled with that value (still
editable). Demonstrated on the REPORT product (qty: 1).
https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
Clicking Generate now immediately triggers a PDF download instead of
opening a full-screen overlay preview that was inaccessible on smaller
screens. Removes the overlay HTML, its CSS, buildPreviewHTML(), and
closeOverlay().
https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
docs/user-guide.md — end-user guide covering the full invoice workflow
docs/admin-guide.md — config.yml reference for setting up the app
docs/dev-guide.md — codebase guide: architecture, state, functions,
localStorage, i18n, FX convention, PDF generation
CLAUDE.md — project briefing for Claude Code sessions
Each charge-to entry in config.yml can now have its own project-codes
list. Selecting a recipient swaps the project code dropdown to that
recipient's codes; switching back to Select or Other restores the
global list. Falls back to the global list if a recipient has no
project-codes defined.
Added data-ls attributes to pcode select and pcode-other input so they
are persisted and restored with the rest of the form. Restoring an
"Other" selection also re-shows the free-text input field.
Charge to:
- All fields locked when "Select"; fill+lock on predefined entity;
unlock all fields only when "Other" is chosen
Invoice lines:
- QTY, UOM, Price start disabled; Description dropdown is always active
- Predefined item: fills UOM (locked) and rate (editable) from config
- "Other": unlocks all three fields
Foreign currency:
- Unit Price locked and calculated-only when FX is enabled
- Re-enables Unit Price when FX is toggled back to No
- Exchange rate label dynamically shows "Exchange rate (X USD = 1 EUR)"
using the selected foreign currency and invoice currency
- Per-item label shows "Price per item in USD" (selected FCY)
- Label updates when either currency changes
Invoice output:
- FX note shows "Price per item (foreign currency): USD …" —
the "Converted from …" prefix is removed from preview and PDF
Invoice lines persistence:
- Lines saved to localStorage on every change (desc, qty, UOM, price,
FX toggle, FCY, rate, per-item)
- Lines fully restored on page reload including FX state
- "Reset invoice lines" button added to the INVOICE LINES card header
Exchange rate semantics changed from "1 foreign = X local" to
"X foreign = 1 local" (divide instead of multiply). Updates the
calculation, preview note, both PDF fxStr strings, and config labels.
Adds accessible font-size A−/A+ buttons to the lang-bar (always
visible). Zooms only #form-root; index persisted in localStorage.
Language selector shown only when multiple languages are configured.
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- New VAT / Tax ID field on the sender form (saved to localStorage)
- Preview: sender phone/email/tax ID now rendered as .bt-meta spans matching
charge-to style (bold label, muted text, flex wrap with gap)
- PDF: sender contact line includes tax ID alongside phone and email
- .bt-meta CSS selector generalised (removed .d-bill scope) so it works for
both sender and charge-to panels without duplication
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- New layout: Sender | Invoice details (top row), Charge to | Payment (bottom row)
- Bank account fields (#bank-section) are hidden by default and only revealed
when "Other" is selected for charge-to; hidden again for predefined recipients
- gatherData() only collects bank fields when bank-section is visible
- Preview and PDF panels updated to match the new quadrant order
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
Form and invoice output now follow the same four-panel layout:
Sender | Payment details
Charge to | Invoice details (INVOICE title + meta)
Invoice lines (full width)
- Payment card is standalone (top-right), bank fields always visible
- Bank fields saved to localStorage so sender enters them once
- hide-payment-info suppresses bank details from invoice output only
- fillChargeTo() no longer touches payment/bank fields
- gatherData() always collects bank fields; hidePaymentOut passed to renderers
- buildPreviewHTML() uses .d-4hdr grid with four .d-tl/.d-tr/.d-bl/.d-br cells
- buildPDF() rewritten: row-1 (sender|payment), light divider, row-2 (charge-to|INVOICE+meta), navy rule, then lines
- Removed bank config fields from Example NGO (those belong to the sender)
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- Payment terms (default 7 days) and pay-by date are now always visible in the
form and always rendered in the invoice preview and PDF when pTerm > 0
- Bank account details moved into a separate #bank-section that is shown only
when "Other" is selected or when the predefined charge-to has bank info in config
- hide-payment-info: yes now only suppresses bank details, not terms/pay-by
- Example NGO in config.yml now includes UK bank info (NatWest IBAN + BIC) which
auto-fills and locks the bank section when that recipient is selected
- calcPayBy() is called on form init so the default 7-day pay-by date shows immediately
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- New right-hand Payment column appears only when "Other" is selected for charge-to
- config.yml: hide-payment-info key (true/yes) suppresses the panel entirely
- Fields: payment terms (days), computed pay-by date, account holder, account no.
(BBAN/IBAN), bank/BIC, bank address (2 lines), please use reference
- calcPayBy() derives the pay-by date from invoice date + terms days using fmtDate()
- relabel() keeps all payment labels in sync on language switch
- gatherData() captures payment fields only when panel is visible
- buildPreviewHTML() and buildPDF() render a payment section after totals when present
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- Add reg-no key to charge-to entries in config.yml
- Add registration-no translation key (en/de/fr/no)
- Add Registration no. input below VAT ID in the charge-to form
- fillChargeTo() populates and clears creg; field locks when predefined recipient is selected
- relabel() maps lbl-creg to registration-no translation
- gatherData() collects ctReg; buildPreviewHTML() and buildPDF() display it in the bill-to block
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
Date format
- Replace hardcoded toLocaleDateString with a config-driven formatter
- date-format key in config (default "d MMMM YYYY") supports tokens:
d, dd, M, MM, MMM, MMMM, YY, YYYY (regex replace, longest match first)
Paper format
- paper-format key in config: "a4" (default) or "letter"
- Passed directly to jsPDF; page dimensions adjusted accordingly
Tax system redesign
- Remove single-select tax-rates from config; add tax-types list, each
with a key and per-language labels (VAT, GST, Sales Tax, Withholding,
Other)
- Totals section now has a dynamic tax-tbody: each row has a number
field, a % / Amount selector, and a tax-type label selector; rows
are added with "+ Add new tax" and removed with ×
- calcTotals iterates tLines: % rows compute sub × (val/100), Amount
rows use the fixed value directly; each row shows its calculated
amount live
- gatherData collects all tax lines into a taxes[] array with lineLabel
(e.g. "VAT 7%" or "GST (50.00)") and amt; both preview and PDF
render one row per tax entry
- relabel() rebuilds tax-line dropdowns on language switch
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
- Date picker: change invoice date from month picker to full date picker
(type=date, formatted as "19 May 2026" in output)
- Invoice currency: add currency selector under invoice date, populated
from config.currencies list, saved to localStorage; shown in invoice
meta block on both preview and PDF
- Recipient currency: add currency field to each charge-to entry in
config.yml; selecting a predefined recipient auto-sets invoice currency
- Lock predefined recipients: selecting a predefined charge-to entry
locks all its fields (pointer-events off + muted style via #ct-fields
.locked); switching to Other or clearing unlocks them
- Fix foreign-currency exchange rate calculation: the formula was
inverted (per / rate instead of per * rate). If 1 USD = 32 local and
per-item is USD 100, local price is now correctly 100 × 32 = 3200,
not 100 / 32 = 3.125. Fix applied in calcFxFromPer, calcLine display,
and gatherData (foreignTot = per × qty, the foreign-currency total).
Updated fx note text to the specified format:
"Converted from USD: 1 USD = 32.00000 THB. Per item: USD 100.00,
line total: USD 500.00"
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
The browser print-to-PDF approach requires the user to manually toggle
"Background graphics" on and "Headers and footers" off — impossible to
control programmatically. Replace with direct jsPDF generation:
- Loads jsPDF 2.5.1 UMD from CDN
- buildPDF() draws the invoice programmatically using Helvetica (PDF
built-in font, no embedding needed)
- Layout: sender/title header, navy rule, charge-to block with navy
accent bar, line-items table (navy header, alternating rows, multi-
line description wrapping, foreign-currency sub-row), totals with
navy To-Pay bar — all in mm on A4
- PDF downloads directly as invoice-{no}.pdf with no dialog
- HTML preview overlay retained for review before download
- Replace "print-invoice" config key with "download-pdf"
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
Static single-page invoice app per spec. Features:
- Multilingual UI (EN/DE/FR/NO) loaded from config.yml; form always
prints in the default language
- Sender fields + invoice details (date, project code, invoice no.)
persisted in localStorage; invoice number auto-increments after
each generation
- Predefined charge-to recipients selectable from config, or manual entry
- Dynamic invoice line items: predefined products with UOM/price
pre-fill, free-text fallback, foreign-currency sub-row with
exchange-rate and per-item price that calculates back to local currency
- Subtotal / tax (configurable rates) / paid / to-pay calculated live
- "Generate Invoice" renders a clean A4-formatted preview overlay;
"Print / Save as PDF" triggers browser print-to-PDF
https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u