# Session diary

## 2026-08-24 (later) — workspace move

Moved the app project to /srv/apps/dutyfreeprofessor and gave /srv/apps/
professormediagroup to the WordPress site (gateway rename), with a symlink at
professormediagroup/main/dutyfreeprofessor standing in for the nesting the gateway
cannot do. Migration via dump/restore rather than copying the postgres volume.
Caught in the act: rsync overwrote the new project's .project.json with the old
project's identity — recreated the project and re-copied with exclusions; the
compose image tag was the one non-parameterised name. Verified end to end: health,
stats identical, both domains gated and serving, audit clean.

## 2026-08-24 — domain swap

Moved the app to dutyfreeprofessor.demoing.info (add-dns, verify cert + gate, then
remove-dns on the old host) and created the professormedia WordPress project on the
freed professormediagroup.demoing.info (template clone). Gateway rename only supports
WordPress projects, so the app workspace keeps its name. Verified: cert issued, gate
fires on both hosts with deep-link redirects intact, WP renders with the template
stack, audit clean. Gotcha hit: removing then recreating a DNS name leaves a few
minutes of negative-cache misses on machines that queried during the gap.

## 2026-08-23 (evening) — the quality pass

Rian asked for four things: unfound problems, workarounds for known limits, a data-quality
assessment with manual spot checks, and a discussion-list review. Ran seventeen
independent auditors (data/backend/frontend/copy finders with adversarial verifiers,
live price re-checks against real retailer pages, workaround probes). 51 findings
confirmed, 0 refuted. Fixed everything critical the same session; v0.13.0 → v0.13.2.

Data quality verdict: the foundations held (FX consistent, extremes real, live samples
matched to the penny), but four systemic bugs were live: variant prices recorded against
full-size bottles, one medal in three on the wrong bottle, 327 duplicate products from
a one-line resolution bug, and out-of-stock prices winning "cheapest". All fixed with
migrations dry-run on a snapshot copy first.

The compliance find outranks everything: the whole Heinemann platform family disallows
the search endpoint under modern robots matching — stdlib's parser predates wildcards
and read it as permission. Stopped collection across the family, removed one mistaken
run, built a stricter-than-RFC matcher into the collectors, told the discussion list.

Process notes: the audit's own live checks used the same broken stdlib semantics and
initially cleared BorderShop — re-checking every host with the new matcher caught it.
Deploys kill in-flight collections (hit twice); batched commits saved the partial work.
My savings-page layout swapped under a shopper's second click — caught by clicking
through the flow in a real browser, not by the typecheck.

Four new rules in agents.md: modern robots matching with any-Disallow-is-no; parse the
price the shopper pays and quarantine the implausible; one tokenizer does not fit every
matcher; a fallback identity lookup considers every key-holder.

## 2026-08-23 — the trip becomes a persistent airport set

Reworked the headline feature on rian's instruction: the shopper picks *which airports
they will be at* rather than entering a flight path, and that choice then follows them
across the whole site. Added Adam's wireframe navigation, announcement ticker and header
search, and rebuilt the home page around his editorial layout.

- New `MyAirportsProvider` (localStorage) + `AirportPicker`; header control; `at=` query
  scoping on the catalogue, similar-products and savings endpoints.
- `/savings` replaces the flight-path trip page, with search / category / minimum-saving
  / sort / awards / exclusives filters. `/trip` redirects.
- Product page gained an "On your trip" verdict and marks the shopper's shops in the
  price table; the coverage page's airport cards double as the picker.
- Exclusives and Awards became real filtered pages (the flags already existed); Reviews,
  Price tracker, Buying guides, News and Newsletter are explanatory.
- Stats strip removed from the home page, reduced to one line, per rian.

Verified in the browser end to end: selecting ATH + DXB + LHR switched the home page from
the generic featured route to "Where to buy on your trip", and the product page to a
per-trip verdict.

Four data bugs found by looking at the result rather than the tests:
- Mexico City's store quotes USD beside pesos; we stored the USD figure as MXN, so the
  whole shop published at ~1/17 of its real prices and an $18 XO cognac led "cheapest
  anywhere". Collector now reads the page's declared `priceCurrency`. Audited the other
  six Avolta stores by comparing local-price medians against their currency — all sane,
  so the fault was isolated.
- A related-products rail kept an unscoped query behind the label "in 3 of your shops".
- Seven Open Food Facts image URLs contained `/products/invalid/` and 404'd; rejected on
  ingest now, migration `b8c9d0e1f2a3` clears the stored ones.
- Madrid (configured, zero products) was offered in the picker and counted as a shop.

A fifth, found while checking the explainers: `spec.html` had no route and fell through to
the SPA catch-all, returning 200 with the app inside it. Explainers now serve by rule.

Recorded three rules in `agents.md`: never infer currency from a symbol; a personalised
label needs a personalised query behind it; a catch-all route makes a missing route look
like a working one.

## 2026-08-21 — build session: PoC from empty workspace to live app

Started from a scaffolded workspace with no code. Ended with the proof of concept
deployed and populated with real data.

Order of work was deliberately riskiest-first: proved the retailer APIs returned usable
data before writing any application code, and proved the barcode join produced real
cross-retailer matches before building any UI.

- Verified Dubai's store API (33,871 products, 100% GTIN coverage) and Heinemann's
  global catalogue search (10,336 products, GTIN present) by direct probe.
- Built the fetch port, normalisers, both collectors, ingest with barcode matching,
  FX conversion, the API, and the React SPA.
- Deployed, ran collections in the container, imported competition medals.
- Fixed in-session: GTIN zero-padding rejected long barcodes; brand repeated in a name
  broke the fallback match key; Heinemann pagination used the wrong field name;
  Dubai paged the entire 33k catalogue looking for liquor; packaging words were being
  stored as categories (with a backfill migration for rows already written).

Substrate notes worth keeping: `srv-gw db-query --format json` returns a bare list, not
an object with a `rows` key, and returns numeric columns as strings.

## 2026-08-22 — autonomous session: from working PoC to near-launchable demo

Ran roughly four hours unattended with three helper agents: two building and verifying
collectors, one drafting the decisions content. Orchestration held; all three delivered
usable work.

Added Dublin & Cork and Keflavik as collectors, taking the site to 8 shops and ~730
comparable products. Keflavik was the valuable one — 100% barcode coverage, which is
what let Dubai and Reykjavik match across continents.

Rebuilt the front end around the trip search: a real home page, a working airports
coverage page, decluttered consumer filters with build-time toggles moved to settings,
in-app pages for every future feature, and a themed decisions-and-realities page.

Fixed in-session: a GET endpoint that wrote to the database and deadlocked against a
running collection; a stylesheet that nothing imported (silent, unstyled render); an
image search that sent duplicated brands and sizes and therefore matched nothing; a
single delisted product aborting a whole collection; and "was" prices equal to the
current price being shown as savings.

Added `main/check.sh` — regenerates the typed API client and typechecks in seconds,
after a three-minute Docker build failed on a one-character JSX error.

## 2026-08-25 14:24 — rian

**Changed** (40 files, detected via mtime):
- `.archive/public-index-placeholder-pre-app.html`
- `.env`

## 2026-09-04 22:49 — rian

**Changed** (2 files, detected via git):
- `main/docs/COLLECTORS.md`
- `main/static`

## 2026-09-04 22:56 — rian

**Changed** (4 files, detected via git):
- `main/docs/COLLECTORS.md`
- `main/docs/DATA-MODEL.md`
- `main/docs/RUNBOOK.md`
- `main/static`

## 2026-09-04 23:05 — rian

**Changed** (1 files, detected via git):
- `main/docs/DATA-MODEL.md`

## 2026-09-05 09:53 — rian

**Changed** (3 files, detected via git):
- `main/app/services/collectors/fetch.py`
- `main/browser/server.py`
- `main/tests/test_render.py`

## 2026-09-05 10:05 — rian

**Changed** (1 files, detected via git):
- `import/issues.md`

## 2026-09-05 15:15 — rian

**Changed** (3 files, detected via git):
- `main/CHANGELOG.md`
- `main/docs/SEO.md`
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-05 15:47 — rian

**Changed** (3 files, detected via git):
- `import/items.json`
- `main/CHANGELOG.md`
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-09 20:04 — rian

**Changed** (2 files, detected via git):
- `main/web/src/api/schema.ts`
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-09 20:06 — rian

**Changed** (2 files, detected via git):
- `main/web/src/api/schema.ts`
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-09 21:20 — rian

**Changed** (5 files, detected via git):
- `import/items.json`
- `import/progress.json`
- `main/docs/RUNBOOK.md`
- `.claude/commands/stream-s.md`
- `notes/review-drafts-2026-09-10.md`

## 2026-09-09 22:36 — rian

**Changed** (3 files, detected via git):
- `brief.md`
- `import/progress.json`
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-10 10:30 — rian

**Changed** (1 files, detected via git):
- `main/web/tsconfig.tsbuildinfo`

## 2026-09-10 13:25 — rian

**Changed** (11 files, detected via git):
- `brief.md`
- `import/items.json`
- `import/progress.json`
- `main/CHANGELOG.md`
- `main/app/main.py`
- `main/web/tsconfig.tsbuildinfo`
- `.claude/commands/stream-r.md`
- `.claude/commands/stream-r2.md`
- `.claude/commands/stream-r3.md`
- `deploy/`
- `main/tests/test_catch_all_confinement.py`

## 2026-09-10 14:59 — rian

**Changed** (4 files, detected via git):
- `main/web/tsconfig.tsbuildinfo`
- `deploy/`
- `(1).md"`
- `notes/Duty_Free_Professor_Airport_Guide_Heathrow.md`

## 2026-09-10 17:40 — rian

**Changed** (3 files, detected via git):
- `main/web/tsconfig.tsbuildinfo`
- `(1).md"`
- `notes/Duty_Free_Professor_Airport_Guide_Heathrow.md`

## 2026-09-10 17:43 — rian

**Changed** (3 files, detected via git):
- `main/web/tsconfig.tsbuildinfo`
- `(1).md"`
- `notes/Duty_Free_Professor_Airport_Guide_Heathrow.md`

## 2026-09-10 19:08 — rian

**Changed** (4 files, detected via git):
- `brief.md`
- `main/web/tsconfig.tsbuildinfo`
- `(1).md"`
- `notes/Duty_Free_Professor_Airport_Guide_Heathrow.md`

## 2026-09-10 20:55 — rian

**Changed** (5 files, detected via git):
- `brief.md`
- `main/web/tsconfig.tsbuildinfo`
- `(1).md"`
- `notes/Duty_Free_Professor_Airport_Guide_Heathrow.md`
- `notes/home-hero.jpg`

## 2026-09-11 17:28 — rian

**Changed:** nothing detected (via git)

## 2026-09-11 19:05 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.
## 2026-09-11 19:00 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-11 19:00 — Stream G (by hand: the hook's first run in this worktree recorded a baseline only)

**Changed:** eight `G:` commits on `claude/stream-g-735ea7` (`7ac9242..e4ad0c2`): `services/hours/` (base, registry, store, heathrow, dublin, toronto), `models/hours.py`, migration `c7d8e9f0a1b2`, `cli_hours.py`, `routers/airports.py`, `services/airport_featured.py`, the airport page's query, renderer and SPA (hours with their date, featured by family, the shopping-feature chips, the comparison preset, the category rail), `pages/AirportCategoryPage.tsx` and `seo.airport_category_body`, `urls.py` and `urls.ts` (feature flags, category slugs, the pair path, the savings preset), `coverage.py` (the pair bar), fixtures `hours_*`, tests `test_hours_*`, `test_airport_*`, the route and SEO pins, COLLECTORS.md, RUNBOOK.md, SEO.md, CHANGELOG Unreleased.
**Verified:** `main/check.sh` green on the final tree (1154 tests, vendor check, typecheck, doc gates 0 fail); the migration rehearsed up, down and up on `dfp_g` (a copy of the 11 Sep nightly, since dropped); `hours collect` end to end against `dfp_g` (LHR, DUB, YYZ rows), `hours set` and `hours show`; the featured rows, the hours line, the pairing page and the sitemap proved on the copy.
**Network:** one robots.txt read per airport operator domain (eighteen domains for the nineteen airports), sitemap discovery and one or two pages on the readable ones, the collect run and one re-read of Toronto, all listed in `.logs/runs/hours-robots-2026-09-11.md`; Toronto's second read met a Radware captcha, recorded as a refusal, no further request. No retail collector ran; nothing deployed.
**Incident:** the G7 commit went in red (a pipe masked the exit codes); fixed in `e4ad0c2`.
## 2026-09-11 19:12 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-11 (Fri, night) — Stream M, autonomous, in the worktree branch `claude/stream-m-fdcfa4`

**Ran:** a read-only `pg_dump` of the staging database into `backups/dfp-2026-09-11-stream-m-rehearsal.dump`
(the 11 Sep nightly predates the production refresh and lacks nine of the brief's fixture rows); the
scratch database `dfp_m` on dfp-devdb restored from it three times; migration `c6d7e8f9a0b1` up, down
and up; `backfill lines`, `backfill variations`, `rederive`, `backfill merges` and `suggest`, each twice,
the second run changing nothing; the Paco Rabanne alias simulated once on the copy and the copy then
restored, so no alias is set anywhere but by rian. No collection, no deploy, the live database untouched.
**Verified:** `main/check.sh` green after every task (1,185 tests at the end, 122 new); the numbers in the
handoff were read from `dfp_m`, not typed. The Merge page was typechecked and its routes tested; it was
not opened in a browser, since the branch is not deployed. The hook's mechanical entry above is empty
because the worktree had no baseline; this entry stands in for it.

## 2026-09-13 21:46 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-13 21:47 — Stream R2b (hand-written: the hook's first run in this worktree saw only its baseline)

**Changed:** eight `R:` commits on `claude/stream-r2b-a21dc7` (T11 to T18): the one side panel over the pack's `SidePanel`, migration `e9f0a1b2c3d4`, the read state, topics, the people route, the closing word, the follow-up mark, the move write and `discussion rethread`, the legacy-mention backfill, the digest narrowed to needs-you.
**Verified:** `main/check.sh` green after every task (1323 tests at the start, 1347 at the end); `vite build` once at T11; migration rehearsed up, down and up on `dfp-devdb` from the 13 Sep nightly (`dfp_r2b`, dropped); the re-threading map and the legacy-mention backfill rehearsed on a read-only `pg_dump` of staging restored there (21 entries applied, the second apply zero; the hand-emitted mention row not doubled, and rung once when absent). Nothing deployed, pushed, crawled, or written to the live database; three read-only SELECTs and one read-only dump of staging.

## 2026-09-15 01:27 — rian

**Changed** (3 files, detected via git):
- `main/web/tsconfig.tsbuildinfo`
- `notes/image-ask-2026-09-14/raw.tsv`
- `notes/inbox/`

## 2026-09-15 12:23 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-15 12:25 — the table's room and the database map (hand-written: the hook's first run in this worktree saw only its baseline)

**Changed:** three commits on `claude/table-layout-db-structure-1d5bc2`: the Listings table's scrollbar above the header, Wide and Full screen (`ListingsTable.tsx`, `ListingsTable.css`, `CollectorsPage.tsx`, `app.css`, CHANGELOG Unreleased); docmap's `relations` block and `AREAS`, `dbmap.py`, the Relationships section of `DATA-MODEL.md`, one sentence in `ARCHITECTURE.md`; the memory note `.memory/database-map-artifact.md`; the brief's NOW block (0.49.0, the deploy, the next milestone).
**Verified:** `main/check.sh` green twice (1506 tests, the typecheck, the generated blocks current); `vite build` once; the map page looked at once in headless Chrome (1400 px, and 500 px, its minimum window) and published; staging read only (`alembic current` = `b3c4d5e6f7a8`, `locations`, two SELECTs). Nothing deployed, pushed or crawled.

## 2026-09-15 14:10 — the location model documented (hand-written: no code changed, so the hook sees only docs)

**Changed:** `.logs/planning/places-and-shops-2026-09-15.md` (new), two new running-list issues and three
updated ones (`import/items.json` and the two generated registers), `.memory/clean-schema-and-non-airport-places.md`,
a cross-link in `.memory/database-map-artifact.md`, the handoff.
**Verified:** every number in the document read from staging read-only or counted with grep on this tree;
`docmap.py --check` current, `docs-check.sh` 0 fail. Nothing deployed, pushed or crawled; no app code touched.

## 2026-09-15 22:51 — rian

**Changed** (40 files, detected via mtime):
- `brief.md`
- `import/items.json`
- `agents.md`
- `.gitignore`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/DiscussionPanel.tsx`
- `main/web/src/components/collectors/ListingsTable.tsx`
- `main/web/src/components/collectors/ListingsTable.css`
- `main/web/src/components/collectors/MergeDesk.tsx`
- `main/web/src/components/MentionScope.tsx`
- `main/web/src/components/SubjectThread.tsx`
- `main/web/src/components/MentionScope.css`
- `main/web/src/styles/app.css`
- `main/web/src/pages/ImagesPage.tsx`
- `main/web/src/pages/CollectorsPage.tsx`
- `main/web/src/api/schema.ts`
- `main/scripts/docmap.py`
- `main/scripts/dbmap.py`
- `main/scripts/staging-refresh.py`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/docs/DATA-MODEL.md`
- `main/docs/VOCABULARY.md`
- `main/CHANGELOG.md`
- `main/app/models/catalog.py`
- `main/app/services/image_ask.py`
- `main/app/services/overrides.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/listings_table.py`
- `main/app/services/keying.py`
- `main/app/services/taxonomy.py`
- `main/app/services/merges.py`
- `main/app/services/suggest.py`
- `main/app/services/lines.py`
- `main/app/services/normalize.py`
- `main/app/services/merge_desk.py`
- `main/app/services/merge_session.py`
- `main/app/cli.py`
- `main/tests/test_house_style.py`

## 2026-09-16 19:20 — rian

**Changed** (40 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `agents.md`
- `.claude/commands/stream-k7.md`
- `.claude/commands/stream-k6.md`
- `.claude/commands/stream-k3.md`
- `.claude/commands/stream-k1.md`
- `.claude/commands/stream-k5.md`
- `.claude/commands/stream-k0.md`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/DiscussionPanel.tsx`
- `main/web/src/components/DfpThreadCard.css`
- `main/web/src/components/collectors/MergeDesk.tsx`
- `main/web/src/components/DeepLinkOpener.tsx`
- `main/web/src/components/DfpThreadCard.tsx`
- `main/web/src/pages/LoginPage.tsx`
- `main/web/src/api/schema.ts`
- `main/web/src/App.tsx`
- `main/scripts/staging-refresh.py`
- `main/docs/REVIEW-PROCESS.md`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/docs/DATA-MODEL.md`
- `main/docs/VOCABULARY.md`
- `main/CHANGELOG.md`
- `main/app/models/catalog.py`
- `main/app/services/overrides.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/listings_table.py`
- `main/app/services/keying.py`
- `main/app/services/taxonomy.py`
- `main/app/services/merges.py`
- `main/app/services/suggest.py`
- `main/app/services/lines.py`
- `main/app/services/normalize.py`
- `main/app/services/merge_desk.py`
- `main/app/services/merge_session.py`
- `main/app/cli.py`

## 2026-09-16 20:47 — rian

**Changed** (40 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `.claude/commands/stream-k2.md`
- `.claude/commands/stream-k4.md`
- `.claude/commands/checkpoint.md`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/MegaMenu.tsx`
- `main/web/src/components/AirportGuide.tsx`
- `main/web/src/components/collectors/ListingsTable.tsx`
- `main/web/src/components/collectors/MergeDesk.tsx`
- `main/web/src/components/SiteSearch.tsx`
- `main/web/src/components/SiteMap.tsx`
- `main/web/src/components/SiteFooter.tsx`
- `main/web/src/components/ProductCard.tsx`
- `main/web/src/components/PriceTable.tsx`
- `main/web/src/lib/urls.ts`
- `main/web/src/lib/search.ts`
- `main/web/src/pages/ImagesPage.tsx`
- `main/web/src/pages/HomePage.tsx`
- `main/web/src/pages/BrandPage.tsx`
- `main/web/src/pages/SavingsPage.tsx`
- `main/web/src/pages/SourcesPage.tsx`
- `main/web/src/pages/DiscussionPage.tsx`
- `main/web/src/pages/StructurePage.tsx`
- `main/web/src/pages/ProductPage.tsx`
- `main/web/src/pages/AirportsPage.tsx`
- `main/web/src/pages/CollectorsPage.tsx`
- `main/web/src/pages/DataPage.tsx`
- `main/web/src/pages/AirportPage.tsx`
- `main/web/src/pages/SettingsPage.tsx`
- `main/web/src/pages/AirportCategoryPage.tsx`
- `main/web/src/api/schema.ts`
- `main/web/src/api/queries.ts`
- `main/web/src/api/types.ts`
- `main/scripts/export_competition_winners.py`
- `main/scripts/docmap.py`
- `main/scripts/dbmap.py`
- `main/scripts/beauty-candidates.py`

## 2026-09-16 22:19 — rian

**Changed** (40 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `.claude/commands/stream-k2.md`
- `.claude/commands/stream-k4.md`
- `.claude/commands/stream-k7.md`
- `.claude/commands/stream-k6.md`
- `.claude/commands/stream-k3.md`
- `.claude/commands/stream-k1.md`
- `.claude/commands/stream-k5.md`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/api/schema.ts`
- `main/scripts/staging-refresh.py`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/docs/DATA-MODEL.md`
- `main/docs/VOCABULARY.md`
- `main/CHANGELOG.md`
- `main/app/models/catalog.py`
- `main/app/models/accounts.py`
- `main/app/models/__init__.py`
- `main/app/models/decisions.py`
- `main/app/models/places.py`
- `main/app/cli_replay.py`
- `main/app/services/overrides.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/listings_table.py`
- `main/app/services/proposals_store.py`
- `main/app/services/keying.py`
- `main/app/services/attributes.py`
- `main/app/services/collector_view.py`
- `main/app/services/publish.py`
- `main/app/services/merges.py`
- `main/app/services/decisions/writer.py`
- `main/app/services/decisions/natural_keys.py`
- `main/app/services/decisions/appliers.py`
- `main/app/services/decisions/__init__.py`
- `main/app/services/decisions/undo.py`
- `main/app/services/decisions/replay.py`
- `main/app/services/decisions/effective.py`

## 2026-09-16 23:12 — rian

**Changed** (40 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `import/proposals/2026-09-17-chanel-rehearsal.json`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/review/EvidenceText.css`
- `main/web/src/components/review/EvidenceText.tsx`
- `main/web/src/components/review/ReviewPanels.tsx`
- `main/web/src/components/AccountMenu.tsx`
- `main/web/src/pages/ReviewPage.css`
- `main/web/src/pages/ReviewPage.tsx`
- `main/web/src/api/schema.ts`
- `main/web/src/App.tsx`
- `main/docs/REVIEW-PROCESS.md`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/CHANGELOG.md`
- `main/app/main.py`
- `main/app/models/catalog.py`
- `main/app/models/schemas.py`
- `main/app/routers/review.py`
- `main/app/services/proposal_rules.py`
- `main/app/services/proposals_schema.json`
- `main/app/services/access.py`
- `main/app/services/proposals.py`
- `main/app/services/keying.py`
- `main/app/services/collectors/extime.py`
- `main/app/services/collectors/base.py`
- `main/app/services/collectors/shopify.py`
- `main/app/services/product_lines.py`
- `main/app/services/merges.py`
- `main/app/services/seo.py`
- `main/app/services/ingest.py`
- `main/app/services/normalize.py`
- `main/app/cli.py`
- `main/app/cli_proposals.py`
- `main/tests/test_review_approval.py`
- `main/tests/test_raw_records.py`
- `main/tests/test_read_one.py`

## 2026-09-16 23:16 — rian

**Changed** (9 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/api/schema.ts`
- `main/app/services/merges.py`
- `main/tests/test_slug_hook.py`
- `main/tests/test_arrivals.py`

## 2026-09-17 04:50 — rian

**Changed** (25 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/lib/flags.ts`
- `main/web/src/lib/urls.ts`
- `main/web/src/pages/ProductLinePage.css`
- `main/web/src/pages/ProductLinePage.tsx`
- `main/web/src/api/schema.ts`
- `main/web/src/App.tsx`
- `main/CHANGELOG.md`
- `main/app/main.py`
- `main/app/config.py`
- `main/app/models/schemas.py`
- `main/app/routers/catalog.py`
- `main/app/services/urls.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/access.py`
- `main/app/services/seo.py`
- `main/tests/test_site_routes.py`
- `main/tests/fixtures/open_routes.json`
- `main/tests/test_seo_line.py`
- `main/tests/test_search_suggest.py`
- `main/tests/test_product_line_page.py`

## 2026-09-17 07:32 — rian

**Changed** (14 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `deploy/launch-dump.sh`
- `deploy/production.sh`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/app/main.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/publish.py`
- `main/tests/test_site_routes.py`
- `main/tests/test_brand_slug.py`
- `main/tests/test_production_script.py`
- `main/tests/test_publish.py`

## 2026-09-17 07:53 — rian

**Changed** (30 files, detected via mtime):
- `brief.md`
- `import/progress.json`
- `import/items.json`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/review/IndexCandidates.tsx`
- `main/web/src/components/review/ReviewPanels.tsx`
- `main/web/src/pages/ReviewPage.tsx`
- `main/web/src/api/schema.ts`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/CHANGELOG.md`
- `main/app/main.py`
- `main/app/routers/publish.py`
- `main/app/services/robots_policy.py`
- `main/app/services/indexnow.py`
- `main/app/services/catalog_queries.py`
- `main/app/services/access.py`
- `main/app/services/feeds.py`
- `main/app/services/publish.py`
- `main/app/services/seo.py`
- `main/app/cli.py`
- `main/app/cli_index.py`
- `main/app/cli_pages.py`
- `main/tests/test_site_routes.py`
- `main/tests/test_crawl_surface.py`
- `main/tests/test_publish_confirm.py`
- `main/tests/test_publish.py`
- `main/tests/test_review_sheet.py`
- `main/tests/test_seo_article.py`

## 2026-09-18 18:44 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-18 21:54 — rian

**Changed** (1 files, detected via git):
- `notes/Duty_Free_Professor_Category_Descriptions.md`

## 2026-09-19 13:52 — rian

**Stream AW3 hand-back** (worktree `collectors-live-page-plan-8951e2`, `2d32358` to `c2382f2` plus the handoff commit): five
tasks done on `/plan`, an adversarial review and its fixes, `main/check.sh` "checks passed" on the final tree (2230 tests,
docs-check 0 fail). Verified on dfp-devdb copies of the 19 Sep nightly only: migration `aw3b1c2d3e4f` up, down, up; `backfill
image_sources`, `images import`, `images lines` and `images coverage` rehearsed (numbers in the handoff). `images stage` ran on
the host inside `window-2026-09-19-images.md`; the `images logos --limit 3 --check` probe ran there and is now refused by the
robots guard. Nothing deployed, no staging or production write, no collection, no push. Running list: the folder-import issue
resolved; `decide-wikimedia-api-under-disallow` and the trip-cards issue open.

## 2026-09-19 19:28 — rian

**Changed** (40 files, detected via mtime):
- `import/progress.json`
- `import/items.json`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/MegaMenu.tsx`
- `main/web/src/components/collectors/liveFormat.ts`
- `main/web/src/components/collectors/LiveBoard.css`
- `main/web/src/components/collectors/LiveBoard.tsx`
- `main/web/src/components/collectors/CollectorRow.css`
- `main/web/src/components/collectors/CollectorRow.tsx`
- `main/web/src/components/AirportFeatured.tsx`
- `main/web/src/styles/app.css`
- `main/web/src/lib/useCountUp.ts`
- `main/web/src/lib/useLivePoll.ts`
- `main/web/src/pages/HomePage.tsx`
- `main/web/src/pages/BrandPage.tsx`
- `main/web/src/pages/FamilyPage.tsx`
- `main/web/src/pages/HowWeChoosePage.tsx`
- `main/web/src/pages/HowWeChoosePage.css`
- `main/web/src/pages/BrowsePage.tsx`
- `main/web/src/pages/CollectorsPage.tsx`
- `main/web/src/pages/CategoryPage.tsx`
- `main/web/src/pages/AirportCategoryPage.tsx`
- `main/web/src/api/schema.ts`
- `main/web/src/api/queries.ts`
- `main/web/src/api/types.ts`
- `main/web/src/App.tsx`
- `main/docs/FEATURED.md`
- `main/docs/SEO.md`
- `main/docs/RUNBOOK.md`
- `main/docs/ARCHITECTURE.md`
- `main/docs/COLLECTORS.md`
- `main/docs/DATA-MODEL.md`
- `main/CHANGELOG.md`
- `main/app/main.py`
- `main/app/config.py`
- `main/app/models/catalog.py`
- `main/app/models/schemas.py`
- `main/app/routers/featured.py`
- `main/app/routers/collectors.py`

## 2026-09-20 01:42 — rian

**Changed** (40 files, detected via mtime):
- `.app.env.example`
- `import/progress.json`
- `import/articles/samples/is-perfume-a-bargain-at-the-airport.md`
- `import/articles/samples/reading-a-shelf-price.md`
- `import/articles/samples/the-pre-flight-shopping-checklist.md`
- `import/articles/samples/allowances-and-why-they-differ.md`
- `import/articles/samples/reading-a-whisky-age-statement.md`
- `import/articles/samples/README.md`
- `import/articles/samples/how-to-read-an-airport-shopping-guide.md`
- `import/articles/samples/which-categories-are-worth-buying-airside.md`
- `import/articles/samples/how-airport-pricing-works.md`
- `import/articles/samples/what-a-competition-medal-tells-you.md`
- `import/articles/samples/litre-bottles-and-travel-exclusives.md`
- `import/items.json`
- `deploy/production.sh`
- `main/web/tsconfig.tsbuildinfo`
- `main/web/openapi.json`
- `main/web/src/components/Badge.css`
- `main/web/src/components/PageStatusBadge.css`
- `main/web/src/components/ArticleCard.css`
- `main/web/src/components/HubSkeleton.tsx`
- `main/web/src/components/collectors/CollectorRow.tsx`
- `main/web/src/components/Badge.tsx`
- `main/web/src/components/PageStatusBadge.tsx`
- `main/web/src/components/ArticleCard.tsx`
- `main/web/src/components/SiteHeader.tsx`
- `main/web/src/lib/articles.ts`
- `main/web/src/lib/flags.ts`
- `main/web/src/lib/clientPages.ts`
- `main/web/src/lib/auth.tsx`
- `main/web/src/pages/ArticlePage.css`
- `main/web/src/pages/ArticlesPage.tsx`
- `main/web/src/pages/ArticlesPage.css`
- `main/web/src/pages/ArticlePage.tsx`
- `main/web/src/api/editorial.ts`
- `main/web/src/api/schema.ts`
- `main/web/src/App.tsx`
- `main/scripts/page-probe.py`
- `main/scripts/route-sweep.py`
- `main/docs/ACCOUNTS.md`

**What this session did** (the away run's close-out, in the worktree on
`claude/collectors-live-page-plan-8951e2`): waited out **deploy D6**, which fast-forwarded master
`f8f0af3..dec7aa8` and deployed AW7's four accessibility fixes to staging at 01:41
(`check.sh` green, `/api/health` `{"status":"ok","version":"0.52.0","database":"ok","role":"staging"}`,
marker removed, `srv-gw security-audit` 0 new findings). Then the orchestrator handoff entry, the NOW
block, the sweep (167 open; nothing resolved here, since each stream resolved what it closed), the doc
gates (`0 fail, 9 warn`), and the Claude inbox (empty since 19 Sep). No collection ran, nothing was
written to staging's or production's database, no `.env` file was read or edited, nothing was pushed.
