Skip to main content

Client-side geocoder demo — architecture spec (#377)

2026-06-14. The marquee: flesh the demo into a Google-Maps-style geocoder that runs fully in the browser — type an address, get a map pin with a calibrated radius and the resolved hierarchy, with no server round-trip. This spec resolves the architecture (the hard parts are the sync/async worker boundary and the byte-range data layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.), records the measurement that de-risks it, and lays out the implementation in executable steps. Written headless during the 2026-06-14 night shiftnight shiftAn autonomous overnight agent session — training launches, evals, publishing, issue triage — that ends with a structured postmortem (what shipped, regressions, open questions) committed for handoff.; the build itself needs a browser (Playwright via run-docs) + R2 hosting, so it is scoped as the next session's work, de-risked and specified here.

The claim, and why it's now de-risked

The demo already byte-ranges the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. DB (docs/src/shared/httpvfs-resolver.ts, sql.js-httpvfs, ~3.6 MB/session out of 53 MB). The open question for streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-level geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. was whether the same trick survives a 3.3 GB situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. (California, 13.5 M points) — or whether a lookup drags the whole file.

Measured (/tmp/situs-byterange-probe.mjs, CA shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row.): the geocode lookup is a clean indexed point query —

SEARCH address_point USING INDEX idx_ap_postcode (postcode=? AND street_norm=? AND number=?)

The index B-tree is depth 4, so a lookup descends ~6 pages ≈ 24 KB, out of 3.3 GB (0.0007%). The total file size is irrelevant to lookup cost — that is the entire point of byte-range over an indexed SQLite. If CA works, every stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. works, exactly as the plan predicted. A full geocode fires a few such lookups (situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. by postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one., situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. by localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. fallback, interp), so the data-fetch cost is a handful of round-trips ≈ low-hundreds of KB, RTT-bound (~350 ms/query same-regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. from the spike), not byte-bound.

One data-layer tuning note

The situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. are page_size 4096; the existing httpvfs resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. fetches in 64 KB requestChunkSize chunksunknown tokenA placeholder for input not in the tokenizer's vocabulary. Byte fallback largely removes the need for one by encoding unseen characters as raw bytes instead. (16 pages). A situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. point lookup touches ~6 scattered B-tree pages, so it lands in a few 64 KB chunksunknown tokenA placeholder for input not in the tokenizer's vocabulary. Byte fallback largely removes the need for one by encoding unseen characters as raw bytes instead. — this is precisely the "sparse single-row access over-fetches" tradeoff the httpvfs-resolver.ts header already calls out for the polygon DB. Two levers for the hosted demo shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row., to measure (not assume):

  • Rebuild hosted shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. at a larger page_size (32–64 KB) so a B-tree level is one chunk → fewer round-trips per lookup.
  • Tune requestChunkSize down for the situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. DB specifically (sparse access) vs up for the FTS-walk WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. DB — they want opposite chunk sizes.

The architecture: extend the demo's existing async cascade (corrected)

A first reading of geocode-core.ts suggests a hard constraint: geocodeAddress() resolves via resolver.resolveTree(tree, { addressPoints, interpolation }), and the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. calls the lookups' synchronous find() (AddressPointLookup.find(): AddressPointHit | null — sync by contract). That would force "run the whole cascade inside a worker against synchronous sql.js handles," because sql.js-httpvfs's fetches are sync XHR only inside the worker.

But the demo does not use geocodeAddress/resolveTree. It has its own async cascade — runCascade() in docs/src/shared/demo-helpers.ts — that already awaits lookup.findPlace(...) over the Comlink-proxied httpvfs worker on the main threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases.. So the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. tierstierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. slot in as async lookups (mirroring the existing WofHTTPVFSPlaceLookup), with no sync-interface problem and no worker-internal-sync requirement. The sync AddressPointLookup contract is a node concern (the CLI / server path); the browser has always resolved async.

main thread (extends the existing demo cascade)
──────────────────────────────────────────────
onnxruntime-web parse → ParsedNodes
street + number + (postcode|locality) present?
├─ await HTTPVFSAddressPointLookup.find(...) situs-<state>.db → address_point tier
├─ else await HTTPVFSInterpolator.find(...) interp-<state>.db → interpolated tier
└─ else runCascade(...) (existing) wof-hot.db → admin tier
→ coordinate + tier + calibrated uncertainty_m → map pin + radius

The situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge./interp handles are sql.js-httpvfs workers (one per loaded stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., lazy by parsed regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.), exactly like the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. one the demo already opens.

Is a Web Worker still wanted? Yes — but as an enhancement, not a necessity

The cascade is heavy (ONNXONNX (Open Neural Network Exchange). An open format for machine learning models that enables interoperability between training frameworks and inference runtimes. Mailwoman ships its trained model as an ONNX file so it can run in Node.js and the browser via onnxruntime. + several sync-XHR byte-range walks); on a cold cache it can block the main threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. long enough to jank the typeahead and the map. So moving the whole cascade into a Web Worker is still the right call for UI responsiveness — but it is now an optimization layered on a correct async main-threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. implementation, not the thing that makes correctness possible. Build the async streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. first (it works on the main threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases., like today's WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. resolve), then lift it into a worker. If/when lifted, the page↔worker contract is:

// page → worker
{ type: "geocode", id: number, input: string, opts?: { defaultCountry?: string } }
// worker → page
{ type: "result", id: number, result: GeocodeResult, timing: { parse, resolve, situs, interp, total } }
{ type: "error", id: number, message: string }
{ type: "progress", id: number, bytesRead: number } // live transfer readout, like the WOF demo

GeocodeResult mirrors the existing geocode-core.ts type — coordinate, tier (address_point > interpolated > admin), uncertainty_m (calibrated), resolved hierarchy.

Latency budget + graceful degradation (per the DeepSeek review)

  • Budget: a geocode returns in < 3 s (parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. + resolve + situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. + interp). Beyond that the user reads it as broken.
  • Per-tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. timeout with admin-centroid fallback. If the situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge./interp byte-range walk stalls (cold cache, slow link), abandon the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. and return the admin-centroid result the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. resolve already produced — a coarse pin beats a spinner. The tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. field tells the UI to widen the radius and caption it plainly. The cascade already degrades tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous.-by-tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous.; the worker adds the wall-clock guard.
  • Warm-up on idle, mirroring WofHTTPVFSPlaceLookup.warmUp() — pull the situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. index root + the hot WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. pages during browser idle so the first submit isn't paying cold serial RTTs.

Service Worker (build on 2026-06-06-demo-service-worker-design.mdx)

Cache the immutable assets (sql.js-httpvfs UMD + worker + WASM, the ONNXONNX (Open Neural Network Exchange). An open format for machine learning models that enables interoperability between training frameworks and inference runtimes. Mailwoman ships its trained model as an ONNX file so it can run in Node.js and the browser via onnxruntime. modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.', the tokenizertokenizerThe component that converts a raw address string into a sequence of numeric token IDs the model can process. Mailwoman's tokenizer is a SentencePiece unigram model trained specifically on postal addresses.) and the byte-range responses (range requests are cacheable; a regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.'s hot index pages stay warm across queries → near-instant repeat lookups + offline resilience once warm). Cap the cache — the CA shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row.'s range responses could accumulate; an LRU eviction with a size ceiling (e.g. 50 MB of range chunksunknown tokenA placeholder for input not in the tokenizer's vocabulary. Byte fallback largely removes the need for one by encoding unseen characters as raw bytes instead.) keeps it under storage quota. Indiscriminate caching of a 3.3 GB shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row.'s pages is the failure mode to avoid.

Launch shards: NY / MI / CA

A deliberate size spread to validate byte-range latency across scales, hosted on R2 with Range support and immutable Cache-Control:

stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. rowssitussitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. sizerole
MI858 K229 MBmid-size baseline
NY6.5 M1.44 GBdense urban (NYC)
CA13.5 M3.30 GBthe stress test — if it's OK, every stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. is

StateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. routing: regionSlugFromTree() (already in geocode-core.ts) picks the shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. from the parsed regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.; the worker lazy-loads that stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.'s situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge.+interp handles on first use and caches them. The demo's regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. constraint already biases the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. resolve; the same slug selects the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row..

The autocomplete nuance (per the DeepSeek hint)

The shipped mailwoman autocomplete (#547) walks the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. FSTFST (finite-state transducer). A compact automaton that reads an input sequence and emits an output sequence. Mailwoman encodes gazetteer names and street affixes as FSTs for fast prefix matching and prior injection without search overhead. → it suggests places (localities, counties: "San Diego", "San Juan"), ranked by importance. That is the right typeahead for the localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. field, but a Google-Maps-grade box also wants address-level suggestions ("350 5th Ave" → "350 5th Avenue, New York, NY"). Those are a different index — streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-name prefixes over the situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row., not the admin FSTadmin FSTA finite-state transducer encoding Who's On First admin place names (countries, regions, localities, postcodes) for fast gazetteer lookup and emission priors at inference time. Ships to the browser (~9 MB for 94K US admin places).. Three honest options, in increasing cost:

  1. Place-level typeahead only (ship now): wire the existing FSTFST (finite-state transducer). A compact automaton that reads an input sequence and emits an output sequence. Mailwoman encodes gazetteer names and street affixes as FSTs for fast prefix matching and prior injection without search overhead. autocomplete into the search box. Suggests cities/regionsregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.; the user types the full streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. themselves. Lowest cost, real value.
  2. StreetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-name autocomplete within a resolved localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy.: once the user has a citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., prefix-complete streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. names from that stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.'s situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. (street_norm has a natural prefix index). Mid cost.
  3. Full address autocomplete (true Google-Maps): house-number + streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. + citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. as one suggestion stream. Highest cost; needs a dedicated suggestion index and ranking. A later milestone.

Recommend shipping (1) with the demo, designing the box so (2) slots in. Flagging (3) as its own epic — it is more than "wire the existing featurefeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them.," which is the nuance worth naming up front.

Implementation steps

  1. HTTPVFSAddressPointLookup + HTTPVFSInterpolator in docs/src/shared/async find() over a sql.js-httpvfs handle (worker.db.exec + inline SQL), mirroring AddressPointSqliteLookup / the interpolationinterpolationA geocoding technique that estimates a coordinate along a street segment based on the house number range. Used as the middle tier of Mailwoman's geocode cascade when exact address-point data is unavailable. lookup query-for-query (same street-normalize, same postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.-then-localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. scoping) and the existing WofHTTPVFSPlaceLookup async idiom.
  2. A resolveStreet() streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. in demo-helpers.ts — given the parsed streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels./number/postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one./ localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. + the two lookups, return { lat, lon, tier, uncertaintyM } (situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. → interp → null), so index.tsx runs it before runCascade and falls back to admin on a null. Main-threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. async — no worker needed for correctness.
  3. Host MI first (229 MB — smallest) byte-range; wire the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. on Michigan end-to-end; verify via run-docs that the browser issues Range requests (pulls ~KB, not the full shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row.) on a real geocode. The gate — confirm before NY/CA. (Local Range-serving for the verification; R2 for prod.)
  4. Add NY, then CA; measure CA's real in-browser geocode latency against the < 3 s budget.
  5. UX (#377): map pin + calibrated-radius circle (the per-regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. factor from data/calibration/interp-radius-conformal.json) + tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. caption; spanspanA contiguous range of characters or tokens in the input string, tagged with an address component type (street, locality, postcode, etc.). Parsed addresses are represented as collections of spans, possibly nested in a tree.-highlight by tag; resolved- hierarchy tree; per-stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). timing; place-level autocomplete typeahead (option 1).
  6. Lift the cascade into a Web Worker (UI responsiveness) + a Service Worker with the capped cache. Both are enhancements over the working main-threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. version, not prerequisites.

What's done headless vs. what needs a browser

Steps 1–2 are pure code mirroring an existing, tested pattern — written + query-verified against a real shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. with node:sqlite (the httpvfs version runs the identical SQL). Steps 3+ change browser code whose correctness is only observable in a browser (Range requests fired, WASM loaded, map render) and need shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. served with byte-range. The two architectural risks — does byte-range survive the 3.3 GB stress shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row., and does the sync find() contract force a worker — are both retired here (it does; it doesn't, because the demo's cascade is already async). So the remaining work is execution against a de-risked spec, not open questions.

Sources

  • /tmp/situs-byterange-probe.mjs — the CA byte-range measurement (reproduce: node … <shard>.db)
  • docs/src/shared/httpvfs-resolver.ts — the proven WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. byte-range pattern this extends
  • docs/articles/evals/2026-06-06-demo-service-worker-design.mdx — the SW design to build on
  • mailwoman/geocode-core.tsgeocodeAddress, regionSlugFromTree, GeocodeResult
  • core/resolver/types.ts — the sync AddressPointLookup / InterpolationLookup contracts
  • DeepSeek project review, 2026-06-14 (latency budget, SW cap, autocomplete depth)