honest-format
A value's element declares how it reads. The formatter is a pure function; the format is a static reference the gate resolves.
The concept
You render a price. In one table it shows $1,234.5; on another page, $1,234.50; on a third it shows the raw 1234.5, because someone forgot to call the formatter at all. Three call sites, three answers, and nothing tells you they disagree until a customer sends a screenshot.
honest-format removes the call site. The element that carries a value declares how it reads:
<span hf-format="currency" hf-currency="USD" hf-decimals="2">1299.99</span>
<!-- renders: $1,299.99 -->
There is nowhere left to forget the formatter, and no second place to format the same value differently. The element that holds the value is the single place its display is decided.
Declarative, so it is checkable
Because the format is declared in an attribute rather than computed in code, it is statically knowable, and honest-check resolves it. An hf-format="curency" typo names no formatter, so the span would render raw and silently format nothing. honest-check catches that at the gate (HC-REF004): the value must be a member of honest-format's declared vocabulary, exactly as an hx-get must reach a mounted route. A format that resolves to nothing is a dead reference, caught before render, not in the browser.
The vocabulary
honest-format is a closed set of format names, each a pure function from a value to a string:
- Numbers —
number,currency,percent,scientific,accounting,abbreviated(1.2M),filesize(1.07 GB),duration(1h 1m 1s),fraction,compact,millions/billions/trillions - Dates and times —
date(short / medium / long / full / iso / a custom pattern),time,datetime,relative(5 minutes ago) - Text —
uppercase,lowercase,capitalize,trim,truncate - Structured —
phone,ssn,creditcard(masked by default) smart— auto-detect the type from the value, then format it
Options ride on sibling attributes: hf-decimals, hf-currency, hf-phone-format, hf-date-format. An hf-type converts the raw value first: cents to dollars, a Unix timestamp to a date, kilobytes to bytes.
The DOM is the state
The formatter is a pure function; the DOM is where the value lives. The client binding reads the element's source — recorded in hf-raw on the first pass, so a re-format works from the original value rather than the already-formatted display — formats it, and writes the result back only when it changes. It marks the element done with a DOM-visible attribute, so content swapped in after the initial scan (an HTMX fragment) formats on the next pass, and re-scanning an already-formatted element is a no-op. No client-side store, no imperative formatting call, no internal state that can drift from what the page shows.
The point is not the formatters
Every language has formatters. The point is that formatting is *declared on the value* and *verified statically* — so "displayed raw," "formatted two different ways," and "a formatter name that silently does nothing" stop being bugs you catch in review and become states the gate makes structurally impossible.