honest framework

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.

You know template filters like floatformat and date. honest-format declares the format in an attribute the gate resolves — a typo is a caught dead reference, not silent.

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:

  • Numbersnumber, currency, percent, scientific, accounting, abbreviated (1.2M), filesize (1.07 GB), duration (1h 1m 1s), fraction, compact, millions / billions / trillions
  • Dates and timesdate (short / medium / long / full / iso / a custom pattern), time, datetime, relative (5 minutes ago)
  • Textuppercase, lowercase, capitalize, trim, truncate
  • Structuredphone, 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.