Render on phones, carry the seller's comment, and refresh to 33 sales #5

Merged
forgejo-admin merged 8 commits from chore/celilo-4-bumps into main 2026-09-22 07:30:21 +00:00

Problem

The map had no mobile version. The phone shell — fixed chrome, one scrolling
region, a docked card, the map/list toggle — was all written and none of it
ever ran: the page carried no <meta name="viewport">, so phones laid it out
at ~980px and scaled down, and @media(max-width:820px) never matched.

Three other things surfaced while fixing it:

  • bun run convert-csv defaulted to a local CSV export whose newest response
    was a year old, and it overwrote site/data/sales.json, yard-sale-info.json
    and the fleet-is-down snippet with no guard. A source that answers with
    something other than the sheet parses to zero rows rather than to an error,
    so a bad run would have blanked the map and the contingency together.
  • Four responses were dropped as unplaceable — trailing prose, a unit
    designator, a misspelled street, and one written as an intersection.
  • The form's comments question never reached the map.

Solution

The page renders on phones. Adds the viewport meta, plus the doctype and
charset — without a doctype the 100svh app-shell sizing runs in quirks mode.
The build now refuses a page with no viewport meta, beside the existing
<title> check.

The lede is gone, along with the helpers only it used (spell, cap,
bareStreet, the two address regexes). A failed load still reports through
say() and through the empty-list line. The page is 2.8KB smaller.

The seller's comment reaches the map. Optional at every step:
findOptionalColumn returns null for a source that predates the column, the
field is omitted when the seller left it blank, and both the page and the
snippet render it only when present. Capped at 500 characters and set as text,
like every other seller-typed field — build.ts's XSS guard now covers it, so
a refactor to innerHTML for the note fails the build.

The published sheet is the default source, and assertNotEmpty refuses to
write when a run produced nothing.

bun run dev serves site/dist. Worth a note for whoever reads this next:
bun site/dist/index.html looks like it would do this and does not — it is a
bundler dev server with SPA fallback, so it answers sales.json with
index.html, and the page then reports that the sales could not be loaded.

Verification

  • bun run check — 62 pass, 0 fail. Three new tests cover the optional-column
    branches, two cover the empty-write guard.
  • celilo module check . — 8 ok, 0 warn, 0 fail.
  • The pipeline was run end to end against stub sheet and Nominatim servers in a
    throwaway directory: comment carried, whitespace trimmed, omitted when blank.
  • Both new build guards were verified by deleting the thing they guard and
    confirming the build refused.

Data

33 sales, 11 with notes, nothing dropped — the four bad addresses were
corrected at the source. 3612 North Kerby Avenue and 3612 N Kerby still
share a point and are both on the map; they list different goods.

Not included

Comments are not searchable and do not feed the category chips — matches()
still reads address and goods only. Deliberate: a stray word in a note would
otherwise pull a sale into an unrelated category.

🤖 Generated with Claude Code

## Problem The map had no mobile version. The phone shell — fixed chrome, one scrolling region, a docked card, the map/list toggle — was all written and none of it ever ran: the page carried no `<meta name="viewport">`, so phones laid it out at ~980px and scaled down, and `@media(max-width:820px)` never matched. Three other things surfaced while fixing it: - `bun run convert-csv` defaulted to a local CSV export whose newest response was a year old, and it overwrote `site/data/sales.json`, `yard-sale-info.json` and the fleet-is-down snippet with no guard. A source that answers with something other than the sheet parses to zero rows rather than to an error, so a bad run would have blanked the map and the contingency together. - Four responses were dropped as unplaceable — trailing prose, a unit designator, a misspelled street, and one written as an intersection. - The form's comments question never reached the map. ## Solution **The page renders on phones.** Adds the viewport meta, plus the doctype and charset — without a doctype the `100svh` app-shell sizing runs in quirks mode. The build now refuses a page with no viewport meta, beside the existing `<title>` check. **The lede is gone**, along with the helpers only it used (`spell`, `cap`, `bareStreet`, the two address regexes). A failed load still reports through `say()` and through the empty-list line. The page is 2.8KB smaller. **The seller's comment reaches the map.** Optional at every step: `findOptionalColumn` returns null for a source that predates the column, the field is omitted when the seller left it blank, and both the page and the snippet render it only when present. Capped at 500 characters and set as text, like every other seller-typed field — `build.ts`'s XSS guard now covers it, so a refactor to `innerHTML` for the note fails the build. **The published sheet is the default source**, and `assertNotEmpty` refuses to write when a run produced nothing. **`bun run dev`** serves `site/dist`. Worth a note for whoever reads this next: `bun site/dist/index.html` looks like it would do this and does not — it is a bundler dev server with SPA fallback, so it answers `sales.json` with `index.html`, and the page then reports that the sales could not be loaded. ## Verification - `bun run check` — 62 pass, 0 fail. Three new tests cover the optional-column branches, two cover the empty-write guard. - `celilo module check .` — 8 ok, 0 warn, 0 fail. - The pipeline was run end to end against stub sheet and Nominatim servers in a throwaway directory: comment carried, whitespace trimmed, omitted when blank. - Both new build guards were verified by deleting the thing they guard and confirming the build refused. ## Data 33 sales, 11 with notes, nothing dropped — the four bad addresses were corrected at the source. `3612 North Kerby Avenue` and `3612 N Kerby` still share a point and are both on the map; they list different goods. ## Not included Comments are not searchable and do not feed the category chips — `matches()` still reads address and goods only. Deliberate: a stray word in a note would otherwise pull a sale into an unrelated category. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The runner materializes .mise.toml with `mise install`, so this file is
the whole toolchain decision for CI as well as for a laptop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@celilo/capabilities 5.0.0 took public_web to its overdue major — the
publish request types had lost `sourceDir` three releases earlier with no
version move — and it is explicit that every consumer manifest moves with
it. caddy provides 4.0.0 now, so a manifest still asking for 3.1.0 fails
at deploy, not at check time.

No source change was needed. 5.0.0 also removed the hook return-value
channel, narrowing on_install to void, and this module's on_install
already returned void.

Nothing local could have caught the stale number: `celilo module check`
reads the manifest against whatever capability registry the CLI on PATH
bundles, and the one here was 1.10.0, still reporting the pre-4.0.0
registry. The manifest comment now says so. What caught it is the e2e,
which deploys against a real caddy — the suite passes end to end, with
the certificate issued and 33 sales served over HTTPS.

Pins move to @celilo/cli 4.0.0 and @celilo/e2e 0.20.4, exact rather than
caret, for the reason release.yml already gives: the lockfile is the
single source of truth for which packager built the .netapp.

README picks up the e2e gotcha that cost a run — build-infra stages the
standard modules inside node_modules/@celilo/e2e, so bumping that package
throws them away — and two rows of the file table that named hooks by
paths which have never existed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`bun run convert-csv <published-sheet-url>` is meant to run unattended, but
an address Nominatim could not place still opened a readline prompt. With no
terminal that either hangs forever or reads EOF, so the refresh could not be
driven by anything but a person at a keyboard.

An unattended run now reports the skip and returns, and the caller records it
through the drop path that already existed — "could not be placed on the map",
printed with every other drop and its reason.

`interactive` is a parameter defaulting to `input.isTTY` rather than a read of
it inside the body, so the unattended branch is testable without a terminal.

The cost is real and worth naming: at a terminal you get asked and can fix the
address; unattended those sellers fall off the map, and only the run's output
says so. openspec add-data-refresh task 3.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
First pull straight off the Google Sheets "publish to web" CSV rather than a
hand export: 26 responses, 23 placed, 3 dropped as unplaceable.

The three dropped are real sellers and all three look correctable at the
source — an alley description appended to a street address, a unit number, and
"Vancover" for "Vancouver". They are left dropped deliberately for now; fixing
them belongs in the spreadsheet, where it also fixes every later run.

Version moves to 0.3.2 because site/data/ is shipped source: refreshing it
without touching manifest.yml puts `celilo module check` into stale-version
drift and blocks the publish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page never carried a viewport meta, so phones laid it out at ~980px
and scaled down, and `@media(max-width:820px)` never matched. The whole
phone shell was there and had simply never activated. Adds the doctype
and charset alongside it, since without a doctype the app-shell sizing
runs in quirks mode.

The build now refuses a page with no viewport meta, next to the existing
<title> check — this is exactly the class of bug that would otherwise
only be found in front of the neighborhood.

Also removes the lede and the helpers that only it used (spell, cap,
bareStreet, and the two address regexes). A failed load still reports
through say() and through the empty-list line.

site/dev.ts serves site/dist, because `bun site/dist/index.html` is a
bundler dev server with SPA fallback: it answers sales.json with
index.html, and the page then reports that the sales could not be loaded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The form's comments question now reaches the map. It is optional at every
step: findOptionalColumn returns null for a source that predates the
column, the field is omitted from an entry when the seller left it blank,
and both the page and the contingency snippet render it only when present.
Capped at 500 characters, shorter than the goods list, and set as text
like every other seller-typed field.

Two other changes to the same run:

The published sheet is now the default source. A local export is stale the
moment it is saved — neighbors keep filling out the form — and the
previous default quietly read a year-old file.

assertNotEmpty refuses to write when a run produced nothing. The source
can answer with something that is not the sheet, which parses to zero rows
rather than to an error, and the writes would have replaced the data file,
the map and the fleet-is-down snippet with an empty list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four addresses that had been dropped as unplaceable were corrected at the
source — trailing prose, a unit designator, a misspelled street, and one
written as an intersection — so the run places every response. Nothing was
dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chore(release): 0.4.0
All checks were successful
pr-validate / validate (pull_request) Successful in 8s
7d4eb422bf
New backward-compatible behavior since 0.3.2: the page renders on phones,
the pipeline carries the seller's comment, and the published sheet is the
default source. Adds the `dev` script alongside site/dev.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
celilo/bna-yard-sale!5
No description provided.