When displayValue:true, the input shows the selected option's value (e.g. a
state code "TX") while the dropdown stays label-searchable ("Texas") — for a
field searched by name but displayed compactly. Backward-compatible
(default false). 20/20 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Passing a parent component's reactive array into the nested combobox x-data
needs x-effect="setOptions(vpicMakes)" — a plain closure over the parent
scope doesn't track Alpine updates. Falls back to config.options for static
lists. 18/18 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
comboboxData(config) Alpine factory: typeable input + fuzzysort-ranked
dropdown (prefix/substring/abbreviation subsequence matching), keyboard
nav (Arrow/Enter/Escape/Tab), a11y roles, x-modelable value binding.
allowFree (default) accepts off-list typed values so classics/kit cars are
never blocked; allowFree:false for closed lists (US states) snaps back to a
valid option on blur. Foundation for replacing native <select> chrome with
inputs that match our text fields. 17/17 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Our Go/Turso layer emits SQLite datetime('now') as "YYYY-MM-DD HH:MM:SS"
(UTC, no zone), which new Date() can't parse → "Invalid Date" in admin.
formatDate/formatDateTime normalize that shape (space→T + Z) and RFC3339,
returning '' for falsy/invalid. Tests cover both formats + empties.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- formatLabel: letter→digit boundary spacing ("level2" → "Level 2"),
acronym casing (DCFC/EV/SUV/VIN/DC/A\C), bare numeric ranges pass
through untouched
- new formatRange: "150-200" → "150–200 mi", "200-plus"/"200+" →
"200+ mi"; empty → '' (callers render their own placeholder)
- formatBudget is now the numeric (min, max) dollar formatter matching
the API payload; the wizard's slug map moves to formatBudgetBucket
- new vehicleName({year, make, model, commissioned})
Breaking rename of formatBudget is safe: no frontend consumes 0.1.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stands up the shared browser-helpers package (consumed by public/vendor/admin
via the Gitea npm registry) to de-duplicate copy-pasted frontend code:
- format.js: formatLabel/formatStatus/formatTimeline/formatBudget (single source
of truth — these had drifted, see SEV-310).
- markdown.js: renderMarkdown (marked + strict DOMPurify; both peer deps).
- wait-image.js: waitForImage (was byte-identical in public + vendor).
ESM source package (no build step — Vite consumers import directly). publishConfig
points at the Gitea npm registry. node --test green (formatters).
Next: publish v0.1.0, then migrate each site to consume it and delete the copies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>