A heading never strands at a page foot
07:51 pm AWST · f0524dd
'Did the cuts work?' sat alone at the bottom of page one with its verdict overleaf. The section travels as one block, like the aging table before it.
Method · Changelog · 29 Aug
This is the repository’s own history, newest first, each entry the message its change was committed with and nothing added after the fact. The messages say what changed, why, and what they found wrong on the way, including in earlier entries. A figure quoted in one is the figure on the day it was written. A day to a page.
746 changes over 22 days since Monday 10 August 2026 · as held in the repository at 10 Sept 2026, 09:03 pm AWST, when this build was deployed. The defects that reached the database have their own register on the data-quality page.
07:51 pm AWST · f0524dd
'Did the cuts work?' sat alone at the bottom of page one with its verdict overleaf. The section travels as one block, like the aging table before it.
07:49 pm AWST · a8867b7
The atomic table hopped to page two over ~30 missing points, leaving a third of page one blank. Section rhythm tightens (h2 16/10 -> 11/8, stat margins trimmed) and the charts give up a few points each; the table now fits where the whitespace was.
07:47 pm AWST · abde133
Taj's read of the August proof: "stock age — is that sales or current stock?" It is the cars standing today, and now every surface says so plainly. The section is "Age of current stock" with "the cars standing on the yard today, not the cars that sold" printed beneath, the NADA table is "Current stock by age: units and capital" with the same line, and the sales-side charts keep "What moved…" so the two populations can never be read as one. Em dashes are gone from the statement headings.
number) — it now says "Aug"; the over-45/over-60 bullets drop their stray band stubs (the number does the work, the fresh-share row keeps its 50% target tick); and the aging table moves up beside the distributions so page one carries it instead of trailing whitespace.
Web and PDF updated together — the two must never drift.
07:41 pm AWST · 70aebd6
The PDF got the charted overview in phase 2 while Kiel's web page still showed v1 panels; the two now agree. StatementCharts renders the page-one forms in the site's own idiom — server-side SVG with labels layered in HTML (ChartText, because SVG text scales with the viewBox), CSS-variable inks so both themes hold, and the same honesty furniture as the document: detection cadence printed under the pace line, backlog bars hollow, unwatched days not drawn at all.
The page gains: the three leading-indicator bullets with the 50% target tick, the cumulative exits line with last month in grey behind it, daily cut bars, the two distributions (90d+ on yellow carrying black), NADA's units-and-capital aging table, and — whenever a comparable prior month exists — the against-last-month panel comparing per-crawled-day rates with both window lengths printed. Nothing compares raw partial totals.
And /yard grows a banner for a statement issued in the last ten days: the archive is silent by design (issued means frozen), so the portal is where a new document announces itself. On the first of September, Kiel signs in to a yellow strip saying his August statement is ready.
07:35 pm AWST · 765f413
Its header and first row sat alone at the foot of page one while the rest flowed overleaf — a four-row table split for no reason. wrap={false} on the section keeps NADA's units-and-capital form whole wherever it lands.
07:33 pm AWST · 64e5b6a
Phase 2 of v2. The RPC (0212) grows the chart series — daily exits and cut dollars, price bands, age buckets of moved cars — plus the trade's leading indicators (fresh share under 30 days against the 50% benchmark, shares over 45 and 60 days) and the honest denominators: active_days (days we crawled) and detection_days (days exits were confirmed), printed wherever a daily figure appears so cadence is never mistaken for rhythm.
Page one recomposed: headline band, three risk bullets, the cumulative exits line (starting AT the watch day — an unwatched day is not a zero-exit day — with a dashed provisional tail in the running month and last month in grey behind it), daily cut sparkbars with backlog days hollow, the two distribution charts, and NADA's aging table with dollars beside units.
Month-over-month activates itself: both routes fetch the prior month when the watch covers it, and the slopegraph compares per-crawled-day rates with both window lengths printed — so the first of October needs no code change, and no raw partial totals ever sit side by side. Westside's August already tells the aging story loudly: fresh share 11% against the 50% guide, 80% of dated stock over 45 days.
dryRun gains returnPdf so a deploy's page one can be eyeballed without issuing anything.
07:27 pm AWST · ab21542
Slopegraph label-spreading nudged downward only, so a cluster near the bottom walked its last value off the chart — the proof sheet clipped '29.1' exactly there; the run now shifts back inside the viewport and re-spaces from the top if needed. And bullet bands go light-greys-only, per Few: the darkest ink in a bullet is the value bar, and the proof sheet's dark first band muddied it.
07:25 pm AWST · 7ca56b9
The chart-proof bench rendered charts without the statement document and threw 'Font family not registered: Archivo' in production — registration lived inside statement-pdf.tsx, so any surface that imported the kit alone was un-fonted. pdf-fonts.ts makes importing the module the registration; both the document and the kit import it, and neither can render un-fonted again regardless of load order.
07:22 pm AWST · 2c094f0
Phase 1 of statement v2. CumulativeLine (running total with prior month in grey and a dashed provisional tail — incompleteness encoded, not footnoted), SparkBars (axis-free daily bars, peak annotated, backlog days drawn hollow), BucketBars (distributions with values printed on the marks, yellow only as a field carrying black), Bullet (Few's strip: grey bands, black bar, target tick), Slopegraph (two time points, values printed at both ends, colliding labels nudged apart).
No chart library: every mark is a path we chose, which is how the research rules stay rules — no legends, no frames, strokes >= 0.75pt, labels >= 6.5pt (Svg text defaults to 18pt Helvetica and colour-by-fill; both set explicitly everywhere), greyscale-survivable throughout. Charts are atomic by react-pdf's own constraint (an Svg never splits across pages) and sized in pt for small-multiple layouts, with explicit shared maxima because per-panel auto-scaling is the commonest small-chart lie.
/api/statements/chart-proof is the kit's test bench: react-pdf will not load outside a bundler, so the proof sheet renders every component on the real Vercel runtime with Westside's actual August series in exactly the shapes phase 2 will feed it.
06:42 pm AWST · 6ebe9e5
The daily trigger issues the previous month, which until 1 September is July — a month before Westside's watch existed. The route would have frozen an empty July document tomorrow. Same rule as 0190: 'none moved' and 'we were not there' are different claims, and only one is true. Pre-watch months are skipped, saying when the watch began.
06:40 pm AWST · b8965ce
question as dealers ask it, Hilux against Landcruiser, uncapped — and cars_moved: every car that left, named, with final ask, cuts given, days advertised and the date it went. Westside's August: 151 models, 501 cars, Hilux 52.
The document: a branded PDF (@react-pdf, vendored Archivo, yellow carrying black, tabular figures) — headline stats, cut-outcome verdict, by-model table, and an appendix naming all 501 cars. Rendering lives in the Next routes only, because react-pdf's font engine requires ESM-only subpaths that raw Node refuses; one render path beats two that drift. The box's daily trigger (generate-statements.ts, wired into the context chain) is a thin authenticated POST.
The archive: dealer_statement_archive + a private statements bucket. ISSUED MEANS FROZEN — a (dealer, month) with a row is never regenerated; the jsonb snapshot rides with the PDF so the issued numbers survive every future parser improvement. Only completed months are issued; the running month downloads live, marked "to date". The daily trigger self-heals: if the site was down on the 1st, the 2nd issues the month, then every run is a no-op.
/yard/statement grows the by-model table (each row opens the model's own exits page), the PDF button, and the issued-statements list.
06:18 pm AWST · b9292f5
dealer_monthly_statement (0210) computes one month of a dealer's business per source id, in the house discipline: cars MOVED at their FINAL ADVERTISED price — never "sold", never "revenue", because the contract is invisible and the last ask is the ceiling of the deal. Presumed-gone reuses the dealer's own exit window so the statement agrees with the exit feed they already read; `partial` is computed from the watch, not hardcoded, and the page wears it as a chip on the statement's face.
/yard/statement renders it: headline row (moved, value at final ask, median ask, median days advertised by the dealer's own dates), pricing discipline (dollars given, % moved after a cut, and the outcome split — cut-and-moved within a fortnight vs cut-and-still-standing), standing capital past 90 days, mix moved by make, and a month nav that only offers months the watch actually covers. Skeleton loading, yellow entry button on /yard carrying black, per BRAND.
Verified against the live corpus before any UI existed: Westside August returns 501 moved, $22.64M at final ask, 80 cut-and-moved vs 188 cut-and-still-standing, $56.8M past 90 days by their own dates; September and July return honest zeros flagged partial. Fan-out is the membership
06:09 pm AWST · bb9f4dc
The week's audit, in three truths about one red check. 10,986 live listings unconfirmed a fortnight decomposed into: marketplace window tails that can never be re-seen, three deliberately-withdrawn sources whose listings were stranded live forever, and keep-the-page-up dealers whose cars the probes verify every few days invisibly.
Lapsed (0208): watching ended — not live (unclaimable), not delisted (the sold tape would fabricate ~10k sales). Withdrawn sources and marketplace listings unseen 14 days lapse nightly, and relist on any sighting. Dealer index-absence never lapses: vonbibra enumerates 24 of 223 cars today, and lapsing on our own gap would retire real stock.
Probe sightings stamp last_seen: the delist verdict "still answered 200, not sold" keeps a car live on evidence the freshness clock never saw — 847 dealer listings sat red exactly there.
The Penrith unknown-unknown class gets its systemic fix: the dealer-studio extractor reads the dealer's own URL slug when the DMS payload says Unknown (fourth recurrence in three days; hand-edits were a treadmill). The four standing rows were identified from slugs and a VIN chassis code first. And check_found_collapse's prior window widens to 30 days — an enabled source silent past the old 9-day window escaped the alarm entirely.