No description
  • TypeScript 67%
  • Svelte 22.9%
  • HTML 6.4%
  • CSS 2.3%
  • JavaScript 0.7%
  • Other 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Luke Jameson 7c1727f326
Some checks failed
Deploy / test (push) Has been skipped
Deploy / images (push) Has been skipped
Deploy / build-and-push (push) Has been skipped
Deploy / deploy-test (push) Has been skipped
Deploy / deploy-production (push) Has been skipped
Deploy / build-dev (push) Successful in 3m42s
Deploy / deploy-dev (push) Successful in 1m7s
Deploy / e2e (push) Failing after 1h32m21s
Read the weekday from the date, so "next saturday" stops searching Thursday
The report was that the search could not handle days and times. It already could:
parseQuery returned time 17:00 and day "sat" for "Route De Cobo to town for 5pm on
saturday" before any of this. The day was then thrown away one file later.

MEASURED, before: src/lib/search/journey.ts took the weekday column from
`parsed.day` and filtered `AND c.<col> = true`. The parser returns an ISO date and
leaves `day` undefined for every RELATIVE date — "next saturday", "next monday",
"tomorrow" — and getDayColumn(undefined) returned TODAY's weekday. So
"next saturday at 5pm" carried the correct date, 2026-10-03, and searched
Thursday's timetable against it. No error, no empty state, and both weekdays
returned buses (26 direct trips on the sat column, 28 on fri), which is why nobody
noticed. A bare day name was the only correct form, because it is the only one that
sets `day`.

It was worse in the API. api/src/routes/search.ts did `if (date) parsed.day = date`
— a DATE written into the day field — and api/src/routes/journey.ts did the same
with `day: date`. Both hit `map[value] ?? 'mon'`, so a client that sent an explicit
date got MONDAY's timetable. Removed rather than repaired: the weekday is derived
from the date now, in the one place search, the API and the route timetable all
route through.

A TEST HAD RECORDED THE BUG. tests/unit/matching.test.ts drove the Route 92
turnaround with `date: '2026-09-19'` — a Saturday — and no day word, so it asserted
11:18, the value from TODAY's Friday timetable, while its own comment on the line
above said 11:17 for that Saturday. The assertion now matches the comment.

The date also came from toISOString() in four places (nlp.ts's resolveDay,
resolveDateFromDayName and its "next" branch, and journey.ts's dateStr default),
which is UTC in a codebase that insists on Guernsey wall-clock everywhere else:
between 00:00 and 01:00 BST, "today" resolved to yesterday and selected yesterday's
column. All four use luxon in Europe/Guernsey now.

The parser's second flaw was structural. Origin/destination extraction carried its
own hand-written list of patterns whose only job was to guess what the time and day
resolvers had matched, and the two had drifted: ten of fourteen leak cases came back
with the framing word attached, which is how the example produced a destination of
"town for on". matchStop's substring fallback hid it, but a leaked word only has to
prefer another place once. resolveTime and resolveDay now report WHERE they matched
and the strip derives from that, so there is one vocabulary instead of two. Verified
against a 21,242-query corpus of framings, prepositions, times, days and garbage:
6,249 destination improvements, 0 regressions, 0 rewordings. "Destinations
byte-identical" was not achievable and is not claimed — the old list and the
resolvers were already inconsistent in ways that only surface once deletion is
positional.

That refactor exposed four defects of its own, three of them pre-existing:
resolveTimeSpan read "next" as a relative TIME, so "at noon next saturday" gave a
time of now and — once the span was deleted — shattered the day expression into
"week"; the destination regex terminated on a THIRD hand-written word list that knew
"morning" and "noon" but not "lunch", "midnight" or "next"; a preposition stranded
by a removal ("... to Cobo at") produced 531 regressions until it was trimmed; and
"tonight" was missing from the vocabulary because \bnight\b cannot match inside it,
so it leaked whenever a different time won.

Then the day and time the reader asked for had to become visible. The results card
offered only Now / In 30 min / In 1 hour and read its label from that shortcut
alone, so a Saturday-5pm search rendered Saturday 17:00 under a chip saying "Now",
and choosing an offset from there searched Saturday at now+30min. It now has a day
picker, a native date input for anything beyond the next week, and a native time
input, and the label is derived from the resolved values so it cannot disagree with
the results beneath it. The resolution order is stated day, then stated clock, then
the words, then the shortcut, then now.

Guernsey's holidays needed a name-to-date map and nothing more, because the scraper
already writes the real bus-holiday calendar into calendar_exceptions as
exception_type='removed' and searchDirect already filters on it. Good Friday and
Easter Monday come from the Gregorian computus; Boxing Day moves to the Monday when
26 December is a weekend. Nine of the ten removed dates match the published calendar
exactly. The tenth does not, and it is a data question for the operator rather than
a parser bug: Guernsey's Spring Bank Holiday 2027 is Monday 31 May, but the feed
removes service on 24 May instead and does not remove the 31st. The cross-check test
records both directions explicitly so a rescrape that changes it fails loudly. A
date with no service at all now says so — the old copy claimed "the first buses
tomorrow are on the next tab along", which was false on Christmas Day and pointed at
In 1 hour, not tomorrow.

A journey opened for another day no longer claims a live bus. Live positions are
today's, and trip ids are date-independent, so matching them against a Saturday plan
would name a different departure as "on the road now"; the journey page does not
even ask for a snapshot when the day is not today.

Also removed the unused `compromise` dependency, imported nowhere. It is not a size
win and is not claimed as one: client JS measures 1,418,961 -> 1,418,956 bytes, a
5-byte chunk-hash difference. It leaves four packages out of the install.

Verification: check, lint and 1,221 unit and contract tests green. The browser suite
is 256 passed / 5 failed / 29 skipped, and the five failures are environmental rather
than ours — each times out after 30s waiting for a live bus dot and reports
"Received: 0", because GET /api/live returns {"count":0,"stale":true} with its newest
record 20 minutes old. Confirmed by running the same specs against the stashed
original tree, which failed live-bus-marker identically while routes-page-layout
passed; two runs failing different specs is the signature of live-data dependence.

Plan and research brief: nlp-dates.todo.yaml, .research/nlp-dates.research-brief.yaml.
Two items are deliberately left open in the plan's `discovered` list.
2026-09-25 16:26:14 +01:00
.forgejo Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
.plans Give the map page a cache window, and make the browser honour it 2026-09-23 10:05:20 +01:00
.research Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
api Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
config Make search work on the refreshed timetable (place-aware, local only) 2026-09-12 13:24:41 +01:00
docs Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
placeholder Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
routes-gg-helpful-state-when-a-timetable Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
scripts Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
src Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
static Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
tests Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
.dockerignore deploy: move routes.gg onto the self-hosted Forgejo + odin stack 2026-09-14 16:59:46 +01:00
.env.example Land the map workstream and the UI work together 2026-09-14 17:21:18 +01:00
.gitignore Ignore the per-environment env files, which are one git add -A from being committed 2026-09-15 18:50:17 +01:00
.prettierrc Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
api-split.todo.yaml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
bus-location.sh add placeholder site 2026-03-14 09:13:48 +00:00
CLAUDE.md Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
delay-per-route.sql add placeholder site 2026-03-14 09:13:48 +00:00
docker-compose.dev.yml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
docker-compose.prod.yml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
docker-compose.test.yml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
Dockerfile Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
drizzle.config.ts Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
eslint.config.js Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
full_project_spec.md Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
IMPLEMENTATION_SUMMARY.md Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
live-buses.todo.yaml Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
map-nav.todo.yaml Tell the reader when their stop is next, out of the worker already there 2026-09-17 14:46:16 +01:00
map.todo.yaml Turn the map into a navigation surface, not just a picture of one 2026-09-17 11:05:26 +01:00
nlp-dates.todo.yaml Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
package-lock.json Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
package.json Read the weekday from the date, so "next saturday" stops searching Thursday 2026-09-25 16:26:14 +01:00
performance.todo.yaml Tell the reader a deploy is waiting instead of replacing the page under them 2026-09-23 10:43:06 +01:00
playwright.config.ts One timetable predicate, a refresh for stale departures, and the map-nav groundwork 2026-09-15 17:16:18 +01:00
polish-seo-branding.todo.yaml Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
postcodes.csv Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
roads.csv Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
routes-page.todo.yaml Turn the map into a navigation surface, not just a picture of one 2026-09-17 11:05:26 +01:00
small-location-sample.json Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
svelte.config.js Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
timetable-journey.todo.yaml Plan a ride by tapping the timetable, and put the ride beside it 2026-09-16 11:04:51 +01:00
todo.yaml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
total-delays.sql add placeholder site 2026-03-14 09:13:48 +00:00
tsconfig.json Unify into a single SvelteKit 5 app at the repo root 2026-09-12 13:09:38 +01:00
ui-redesign.todo.yaml Make the repo pass prettier, including two YAML files that would not parse at all 2026-09-15 19:56:31 +01:00
ux-flows.todo.yaml Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
vite.config.ts Serve every /api endpoint from the Hono service, not from SvelteKit 2026-09-23 15:35:59 +01:00
vitest.config.ts Tell the reader a deploy is waiting instead of replacing the page under them 2026-09-23 10:43:06 +01:00