Method · Changelog · 24 Aug

Every change, in the words it was made with

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.

Monday 24 August 2026 · 6 changes

A merged pair shares its history, and absence proves a delisting

10:14 pm AWST · 819857a

The convergence check's last two finds. The delist-integrity rule now accepts a complete traversal on either side of an event: a merged pair's runs justify each other's events (the mtfordmtbarker delisting whose paperwork the merge orphaned), and a car that stays gone through subsequent complete traversals is proven by absence — a fabricated delisting would be relisted on sight by the first complete pass after it. Eight orphaned events from the campaign week's killed crawls closed on that rule; a fresh fabrication still alarms immediately. The family map is a hashed CTE — the first draft's per-row union subquery turned a 760ms check into a timeout.

daily-rollup moves to 12:50: at 15:55 it collided with the 15:44 deep pass, which is how 2026-08-23 became a day with no rollup row. It stays absent — write_daily_rollup's own guard refuses to describe a past day with today's snapshot, and that refusal is the product's discipline, not a bug.

Suite verdict after: 21 checks passed, two known work-queue warnings. health.ts: zero alerts. DATA-QUALITY 48.

Every integrity check gets a shape that fits its question

11:23 am AWST · a33fb9e

Confirming "green" surfaced the rest of the outgrown-scan family, each flapping — passing quiet, dying under crawl IO:

check_fields_empty_flag (0204): the gate now counts a partial index built on the violating predicate — free to maintain, all history, microseconds — after a windowed rewrite still measured 116s. Info counts sample 6 hours.

run_slow_integrity_checks died at exactly its 900s budget even in the 02:51 quiet window, so 0205's reschedule was necessary but not sufficient. 0206 splits it by what each check asks: the two regression gates window to where a regression can appear (price-vs-identifier reads a second partial index over all history plus a 48h external_id window; cross-view pairs watch 7 days of events), and the VIN work-queue count — the expensive half, which must see the whole VIN-less population — moves to its own daily quiet-window cron with the full budget to itself. check-integrity's VIN freshness limit widens 12h -> 26h to match.

0205 also re-ran 0083's re-runnable merge for two returned mirror pairs (mtfordmtbarker/mountbarkerford, hurstvilletoyota/fergusonstoyota — both rebrands where discovery found the new domain before the old id retired): zero live URLs held by two ids again.

Convergence is scheduled, not hoped for: index build 11:36 in today's quiet window, VIN seed 11:55, windowed checks refresh 13:45. cross_view was seeded by hand at 03:21 (n=0, clean).

The click is acknowledged in the beat before the skeleton

10:21 am AWST · a926d21

Route-level loading.tsx paints the destination once navigation is underway, but the beat between click and skeleton still read as a dead click — and on "View your yard" it literally was one: the dropdown closed on click, unmounting the link and any feedback it could carry, while the force-dynamic yard rendered for seconds. Kiel reported it verbatim: no loading animation.

Three fixes. NavPending — useLinkStatus inside the Link — pulses a dot on the clicked link itself: the yard menu item and all six top-nav links carry it. The dealer menu now closes when the destination ARRIVES (pathname change), not when the link is clicked, so the pulse survives the navigation. And the sign-in form gives way to a visible "Signed in — opening your yard…" state after auth instead of a disabled button; a dealer who is already signed in and clicks the footer's Dealer sign-in link is recognised and taken straight through with the same status line.

Sold cars become facts, not scans — and every slow click gets a face

10:11 am AWST · 1360d6e

Four request-path functions re-derived the same sold-car set from the heavy view on every call, and the corpus outgrew all four at once: recent_sales 115s (home), turn_filtered 53.7s (/moving), turn_by_model 53s (/market), days_to_turn 2.2s (every valuation). These are the pages reported as "up to ten seconds". One narrow facts table now holds a row per sold car, rebuilt half-hourly off the request path — a sale is only detectable at crawl cadence, so a 30-minute fact loses nothing — and the four read it in 3-46ms with their filters preserved verbatim. catalogue_json's nine-pass 51s scan, which survived as the value picker's fallback, now reads the existing board on both paths. depth-boards moves to the two windows the crawl box is quiet: at 18:25 and 00:25 refresh_live_snapshot was losing an IO race against the deep pass and dying on timeout, which is how the catalogue and the dealer snapshot quietly went 13h stale. New boards join board_spec so staleness alerts instead of rotting.

0203 corrects 0202 measured mid-flight: the facts build skipped the view's fields_empty fallback and lost HALF the days_old data — a delisted car's final observation is so often an empty-fields probe that the fallback is load-bearing for exactly this population. 8,109 of the view's 8,133 dated cars after the fix; the 0201 boards now read facts too. DATA-QUALITY 46.

Loading states for moving, value, yard, market and dealer pages: grey skeletons echoing each page's layout, one visible status line, reduced-motion safe, no invented numbers. A slow render now shows work instead of a frozen click.

tonylahood's collapse alert: investigated, not a break — their restructure withdrew the Lidcombe yard's stock from the domain (verified: clean 3-page pagination, sitemap agrees at 81 cars). Acknowledged with the finding; tonylahoodmotorscroydon mirrors the same inventory, flagged for dedup.

Board discount_by_time_on_market and turn_segments too

09:39 am AWST · c42ba08

The pulse's two siblings on /market (DATA-QUALITY 44 named them) get the same board treatment: a single-row jsonb board the read function expands back into its table shape, refreshed every 6h by pg_cron, read in 0.6ms on the request path instead of scanning the full delisted set live.

The refresh reads base tables, not listing_current. Lifting the original `from listing_current where status='delisted'` into the refresh took 311s — the view runs a price-change-count subquery and a double lateral per row for all 257k listings before the delisted filter removes 87% of them. Reading listing directly (listing_status_idx for the 34k delisted rows, then observation_pick_current_idx per row) is ~107s, off the request path, inside the cron budget. DATA-QUALITY 45 records the trap: a heavy view plus a WHERE is not a filtered heavy view.

15%->83% by time on market; SUV/Ute/Wagon leading turn), read functions at 0.6ms, /market and /value back to ~0.3s.

The pulse bar reads a board, not the whole corpus — site restored

09:10 am AWST · 64de611

The site went slow-to-unresponsive: /value and /sign-in ~19s, /coverage timing out, /market fine at 0.24s. The root layout renders the credibility bar from market_pulse() on every page, so a slow pulse was a slow everything.

market_pulse was a live scan — sold_total counts every delisting ever, with a correlated NOT EXISTS per row. Fine at its documented 104ms. Then today's own backlog drain (recovering Westside and the dealer-studio/imotor cohorts wrote hundreds of delistings and left dead tuples) tipped it past the Small instance's cache: 30-100s, disk-bound. The in-app cache protects a warm process but every cold Vercel instance pays full cost, so pages hung, more instances span up, and ten concurrent disk scans became an unloadable site.

0200 gives the pulse the board treatment coverage (0154) and the depth boards already use: a single row refreshed every 5 min by pg_cron, read in 0.75ms on the request path. No live-scan fallback on an empty board — that is the stampede returning. Applied as a DB function, so every cold instance was fixed at once with no deploy. Verified: every page back to sub-2s.

DATA-QUALITY 44. The lesson: a fix that recovers a lot of data is a write spike, and anything the root layout awaits is on the critical path for the entire site.