@kahwee/sf-map-svg 4.5.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 5.1.0 — 2026-11-05
8
+
9
+ - Add official November 2026 precinct aggregates (`data/elections/precincts-<report time>.json` with a time-ordered `precincts-index.json`) and the 2022 precinct boundaries (`data/precincts-2022.json`), reconciled with the department's summary report. Only aggregates are committed, never the ballot-level export. Export the boundaries as `precinctMaps` from `@kahwee/sf-map-svg/data` and `@kahwee/sf-map-svg/data/precincts`; results snapshots stay Pages data.
10
+ - Update Biome to 2.6.3 and the aligned Storybook packages to 10.6.4.
11
+ - Gate library output replacement on Bun source checking, then emit JavaScript, declarations, and JSON with TypeScript `--noCheck`; install pinned Bun in every build job.
12
+ - Align Storybook on 10.6.3 and Vitest/browser/coverage tooling on 5.0.5, with compatible dependency updates for current security fixes.
13
+ - Record identical-output library build benchmarks alongside the earlier build-reuse measurements.
14
+ - Reuse completed library builds in CI demo, Storybook, coverage, and Pages steps; retain fresh builds for standalone commands and archive packing.
15
+ - Add a reproducible compilation-phase benchmark that checks emitted-output equivalence.
16
+ - Use Bun for consumer, Storybook, and website project checks; run them once in the Bun CI job instead of repeating them in the Node matrix.
17
+ - Add a Pages precinct results page (`precincts.html`) that shades the 514 precincts by any contest in the latest official snapshot, and compact the committed precinct snapshot to about 0.6 MB with a 5 KB reconciliation fixture.
18
+ - Fix election page checks on 390 px phones and in macOS Chromium, and wait for the scrollbar gutter in popover checks.
19
+
20
+ ## 5.0.0 — 2026-10-19
21
+
22
+ - Breaking: controller `setMode` and `setSource` preserve the camera; remove their `{ resetView }` argument. Call `camera.reset()` or `camera.fit()` explicitly.
23
+ - Breaking: controller `configure` treats all omitted and undefined values as no-ops and merges nested feature records. Add `resetConfiguration(groups?)` to clear presentation overrides explicitly.
24
+ - Breaking: controller `selectNeighborhood` selects exact IDs in the current source; resolve names through source-specific data lookup or use source/year-qualified `selectFeature`.
25
+ - Include a [v5 migration guide](docs/migration-v5.md), migrated consumer examples, and regression checks. Retain Node 24+, offline rendering, zero runtime dependencies, geographic data, and default appearance.
26
+
27
+ - Add offline `geometryBounds()` and `geometryContainsPoint()` helpers to `/geometry`, with readonly inputs, polygon holes and islands, nested collections, explicit boundary semantics, and coordinate validation. Export `Bounds`, `Geometry`, and `Position` from the same entry.
28
+ - Breaking: markers now require `position: [longitude, latitude]`; remove the numeric `projectToScreen(lng, lat)` form in favor of one readonly GeoJSON position. Export shared `ScreenPoint` and `ProjectToScreen` types and cover camera changes, resizing, invalid coordinates, and controller disposal in browser regressions.
29
+ - Document geographic queries, screen-coordinate integration, and API consistency follow-ups. Correct the developer guide: camera pan deltas are CSS pixels, not projected map units.
30
+
7
31
  ## 4.5.0 — 2026-10-14
8
32
 
9
33
  - Add the optional `/cards` entry with `bindMapCards()`: hover/focus previews a pin without changing selection or camera, native buttons select places, map selection reveals matching cards without stealing focus, and explicit cleanup restores owned DOM attributes.
package/README.md CHANGED
@@ -6,6 +6,11 @@ Offline, self-contained San Francisco SVG maps. The package has no runtime depen
6
6
 
7
7
  [Map preview](https://github.com/kahwee/sf-map-svg/blob/main/docs/map-preview.png) · 2,000-marker screencasts: [desktop](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/markers-2000-desktop.webm), [390 px phone](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/markers-2000-phone.webm). [Screenshots and capture instructions](https://github.com/kahwee/sf-map-svg/blob/main/docs/media/README.md) show zoom, viewport culling, clustering, label updates, and selection from the complete catalog. These recordings illustrate behavior; frame timings are reported below.
8
8
 
9
+ ## Upgrading to v5
10
+
11
+ Version 5.0.0 standardizes coordinate tuples and controller reset behavior.
12
+ Read the [migration guide](docs/migration-v5.md) before upgrading from v4.
13
+
9
14
  ## Install
10
15
 
11
16
  ```sh
@@ -76,12 +81,39 @@ function disposeMap() {
76
81
  }
77
82
  ```
78
83
 
79
- `createMap(data, options)` returns a controller. Use `map.element` for mounting, `map.configure({ features, layers, controls, appearance, mode, source, year, labels })` for runtime switches, `map.camera` for pan/zoom/fit/reset, and `map.on()` for typed events. Appearance can change live while preserving camera, selection, and focus. `getResolvedConfiguration()` explains effective layers; `getCapabilities()` reports supplied geography. SFAR realtor neighborhoods are the default when supplied; SF Find and analysis are explicit alternate sources. The `/data/full` preset includes all three collections and historical districts.
84
+ `createMap(data, options)` returns a controller. Use `map.element` for mounting, `map.configure({ features, layers, controls, appearance, mode, source, year, labels })` for runtime switches, `map.camera` for pan/zoom/fit/reset, and `map.on()` for typed events. Appearance can change live while preserving camera, selection, and focus. `getResolvedConfiguration()` explains effective layers; `getCapabilities()` reports supplied geography. SFAR realtor neighborhoods are the default when supplied; SF Find and analysis are explicit alternate sources. The `/data/full` preset includes all three collections and historical districts. `precinctMaps[2022]` from `@kahwee/sf-map-svg/data/precincts` holds the 514 Department of Elections precinct boundaries (data only, not a rendered layer).
80
85
 
81
86
  See [controller options, events, and lifecycle](docs/API.md#interactive-maps),
82
87
  [examples](docs/EXAMPLES.md), and [consumer integration](docs/consumer-integration.md).
83
88
  Call `destroy()` when removing an interactive map.
84
89
 
90
+ ## Geographic queries and screen coordinates
91
+
92
+ New in v5. See the [v5 migration guide](docs/migration-v5.md) for breaking changes.
93
+
94
+ Use the data-free `/geometry` entry to calculate geographic bounds or test which
95
+ polygons contain a location, without constructing a map or requiring a browser:
96
+
97
+ ```ts
98
+ import { geometryBounds, geometryContainsPoint } from '@kahwee/sf-map-svg/geometry';
99
+ import { neighborhoods } from '@kahwee/sf-map-svg/data/realtor';
100
+
101
+ const location = [-122.42, 37.76] as const;
102
+ const matches = neighborhoods.features.filter(({ geometry }) =>
103
+ geometryContainsPoint(geometry, location),
104
+ );
105
+ const bounds = matches[0] ? geometryBounds(matches[0].geometry) : null;
106
+ ```
107
+
108
+ Polygon holes are respected; shared boundaries can match multiple areas.
109
+ Bounds are `[west, south, east, north]` in degrees, not camera coordinates.
110
+ The [query contract](docs/API.md#geographic-queries) covers empty geometry,
111
+ validation, and geographic limitations.
112
+
113
+ For mounted browser maps, `map.projectToScreen(location)` returns canvas-relative
114
+ CSS pixels and viewport visibility. All coordinate inputs now use GeoJSON tuples. See the [overlay recipe](docs/consumer-integration.md#position-html-overlays)
115
+ and [coordinate units](docs/API.md#coordinates-and-units).
116
+
85
117
  ## Documentation for coding assistants
86
118
 
87
119
  [Developer guide](docs/developer-guide.md) · [llms.txt index](llms.txt) · [Complete text documentation](llms-full.txt). Generated from project docs and TypeScript declarations; run `pnpm docs:llms` after changing those inputs.
@@ -90,7 +122,7 @@ Call `destroy()` when removing an interactive map.
90
122
 
91
123
  Canonical geography is in [`data/`](data/README.md), with provenance in [`SOURCES.md`](SOURCES.md). The public `/data` entry exposes lookup, catalog, district maps, and source-specific neighborhood collections. These are deeply frozen; clone before editing.
92
124
 
93
- Install with `pnpm install --frozen-lockfile`. Bun 1.4.3 is pinned in `.bun-version`; `pnpm test:bun` checks compiled-package imports and static rendering. Strict TypeScript 7.0.2 declarations and packed consumers remain part of the Node/pnpm checks. See the [contributing guide](CONTRIBUTING.md) for architecture and the [validation and release runbook](https://github.com/kahwee/sf-map-svg/blob/main/docs/maintenance.md) for checks, browser inspection, and publishing. If you are upgrading from v2, follow the [v3 migration guide](docs/migration-v3.md).
125
+ Install with `pnpm install --frozen-lockfile`. Bun 1.4.4 is pinned in `.bun-version` for development builds and type checks; `pnpm test:bun` also checks compiled-package imports and static rendering. Strict TypeScript 7.0.2 declarations and packed consumers remain part of the Node/pnpm checks. See the [contributing guide](CONTRIBUTING.md) for architecture and the [validation and release runbook](https://github.com/kahwee/sf-map-svg/blob/main/docs/maintenance.md) for checks, browser inspection, and publishing. If you are upgrading from v2, follow the [v3 migration guide](docs/migration-v3.md).
94
126
 
95
127
  ## Browser checks and marker performance
96
128
 
@@ -205,3 +237,18 @@ pnpm benchmark:markers --counts=2000 --mode=neighborhoods \
205
237
  ## CI maintenance
206
238
 
207
239
  [GitHub Actions maintenance](.github/ACTIONS.md) covers workflows, parallel checks, action versions, and weekly updates.
240
+
241
+ Project type checks use Bun: `pnpm check:types:built` checks compiled-package
242
+ consumers, Storybook, and the website in parallel after a build. `pnpm check` runs
243
+ the full gate. CI runs `check:node` on Node 24/26 and these Bun checks once in the
244
+ Bun job. TypeScript remains responsible for declaration emission and packed-package
245
+ consumer compatibility tests. Published consumers do not need Bun.
246
+
247
+ On the session’s Linux executor, three-run median project checks measured
248
+ 0.402/0.688/0.209 seconds with Bun for consumers/stories/website, versus
249
+ 3.156/4.110/2.496 seconds with `pnpm exec tsc -p <project>`. These include command
250
+ startup; they do not predict hosted CI duration.
251
+
252
+ CI reuses completed library output for demos, Storybook, and Pages. Standalone
253
+ commands still build from a clean checkout. See the [toolchain benchmark report](docs/toolchain-benchmarks.md)
254
+ for raw samples, output-equivalence checks, and reproducible commands.
package/SOURCES.md CHANGED
@@ -177,3 +177,56 @@ index is https://sfelections.org/results/20121106/detail.php .
177
177
  reconciles all 2002, 2012, and 2022 district totals. The election JSON files
178
178
  are Pages datasets; the historical district maps retain their existing
179
179
  display geometry, and no precinct polygons or raw workbook data are shipped.
180
+
181
+ ## November 2026 ballot and pending results
182
+
183
+ Downloaded November 4, 2026 UTC (November 3 Pacific):
184
+ https://media.api.sf.gov/documents/N26_EN_VIP.pdf .
185
+ `elections/2026-11-03.json` records its SHA-256 and verification time. Printed
186
+ page 248 lists all 14 state, one regional and ten local measures. Pages 24–51
187
+ supply candidate statement names. The official roster at
188
+ https://www.sf.gov/candidates-november-3-2026-general-election was verified
189
+ on November 4 UTC to complete the 15 displayed candidate contests, adding
190
+ Charles Hoelter, Manuel Noris-Barrera, Philip Louis Wing, Nicholas Berg and
191
+ Jamo Muhammad. The California Secretary of State certified list, downloaded
192
+ November 4 UTC from
193
+ https://elections.cdn.sos.ca.gov/statewide-elections/2026-general/cert-list-candidates.pdf
194
+ (certified August 27, 2026; SHA-256 in the feed), supplies nine additional
195
+ state/Board of Equalization contests on printed pages 1–2 and 13 Supreme Court
196
+ and First Appellate District retention contests on pages 33–34. The latter
197
+ explicitly includes San Francisco County and matches SF pamphlet pp. 246–247.
198
+ Other appellate districts are excluded. Printed candidate coverage does not
199
+ assert independently verified late statewide write-in qualifications.
200
+ The November results endpoint
201
+ https://www.sfelections.org/results/20261103w/index.html returned HTTP 403;
202
+ no November vote totals, report time, winners or geographic results are asserted.
203
+ The existing 2022 supervisorial boundaries are reused only for ballot district
204
+ selection, with neutral fills. See `elections/README.md` for the feed contract
205
+ and the ingestion blocker. Historical snapshots remain separate elections.
206
+
207
+
208
+ The independent California Secretary of State public media index at
209
+ https://media.sos.ca.gov/media/json-endpoints.csv identifies the November 3,
210
+ 2026 election and links JSON reports. Downloaded November 4 UTC: the reports
211
+ still carry the November 2, 6:28 p.m. zero-report time. The adapter extracts
212
+ only exact San Francisco County rows for 40 supported state/federal candidate, proposition and judicial
213
+ contests. It does not use statewide totals, infer precinct boundaries, or map
214
+ county totals onto districts. Actual extracted fixtures and their URLs are
215
+ recorded in `test/fixtures/sos/README.md`. Zero reports stay pending, and report
216
+ time is preserved verbatim separately from fetch time.
217
+
218
+ ### November 2026 plain-language ballot descriptions (verified November 3, 2026 Pacific)
219
+
220
+ - SF official voter pamphlet: https://media.api.sf.gov/documents/N26_EN_VIP.pdf — printed pp. 52–53 (regional transit), 64 (A), 70–71 (B), 78 (C), 84 (D), 94–95 (E), 100–101 (F), 108 (G), 124–125 (H), 132–133 (I), 144 (J). Summaries use impartial digests, not campaign arguments. PDF viewer page numbers are two higher than printed pages.
221
+ - California Secretary of State official November 3, 2026 voter guide: https://vig.cdn.sos.ca.gov/2026/general/pdf/complete-vig.pdf, downloaded November 4 UTC; SHA-256 `9509a4e3ac10b09e3c36d8b9b6c5d2f3935af3c7e2473c392b64352811cac1d8`. Official titles/summaries and Legislative Analyst analysis: printed pp. 14 (1), 20–23 (2), 26–27 (3), 30 (4), 34–37 (5), 40–41 (37), 44 (38), 48–51 (39), 54–55 (40), 58–59 (41), 62–63 (42), 66–67 (43), 70–71 (44), 74–77 (45).
222
+ - `shortTitle`, `summary`, and `summarySource` in the shared election feed preserve official full titles and source page provenance. Descriptions are neutral paraphrases; no endorsements or outcome predictions.
223
+
224
+ The November 3 impartiality review checked all 25 descriptions against the official digests and legislative analyses. Clarifications include SF C printed pp. 78–79 (freeze annual increases, not all contributions), F p. 100 (Board objection), E p. 94 (60-day default approval); California 2 pp. 22–23, 38 pp. 44–45, 39 p. 50, 40 pp. 54–55, 41 pp. 58–59, 43 pp. 66–67, 44 pp. 70–71 (operative scope/waivers pp. 153–156), and 45 pp. 75–76. Each feed record carries its revised printed-page reference.
225
+
226
+ Report-history points preserve only actual validated source counts and their distinct first observation times. Tests use explicitly synthetic mutations of the official zero-report fixtures; no synthetic counts or history points are in the checked-in ballot seed or deployed as results.
227
+
228
+ ## November 2026 precinct results and boundaries
229
+
230
+ - `data/elections/precincts-<report time>.json`, such as `precincts-2026-11-03-230421.json` (local department clock, `YYYY-MM-DD-HHmmss`), aggregates the San Francisco Department of Elections cast vote record export `CVR_Export_20261103231904.zip` (downloaded November 5, 2026; the department's report time was supplied as 11/3/2026 11:04:21 PM, kept verbatim with no timezone conversion) to per-precinct, per-contest counts in a compact format (`precincts[id].c[contestIndex] = [undervotes, overvotes, ...votes in option order]`, with `contests[]` giving each index's options; schema version 2, about 0.6 MB). Generate it with `scripts/import-cvr-precincts.mjs --zip <export> --report-time "<M/D/YYYY h:mm:ss AM|PM>" --out-dir data/elections`. Each report time keeps its own file, and `data/elections/precincts-index.json` lists them in time order; consumers read `latest`, the newest report time. The export, which is ballot-level, is an ingestion input and is never committed; its SHA-256 is recorded in the file. Counts use each ballot card's current record (`Modified` when adjudicated). Ranked-choice contests count a single rank-1 mark only, matching the department's first-round figures. These are not winners.
231
+ - Reconciliation: `test/election-precincts.test.mjs` sums the precincts and compares every candidate and option of all 65 contests with the official Election Summary Report 4 (`test/fixtures/sfelections/summary-report-4.json`, a 5 KB extract of the report's option totals made with `scripts/extract-summary-totals.mjs`, with the XML's SHA-256 recorded; 514 of 514 precincts reported). Every total matches exactly. Five precincts have no ballots in the export and are absent from the file.
232
+ - `data/precincts-2022.json` holds the 514 polygons of DataSF "Election Precincts - Current, Defined 2022" (`d6x4-hefw`, https://data.sfgov.org/resource/d6x4-hefw.geojson, downloaded November 5, 2026), reduced to the precinct ID with coordinates rounded to six decimals. All 514 IDs match the export's precinct manifest exactly.
@@ -110,6 +110,13 @@ const pathData = geometryPath(mission.geometry, map.project);
110
110
  // Use pathData as an SVG <path d="..."> over map.svg.
111
111
  ```
112
112
 
113
+ The v5 `/geometry` helpers `geometryBounds(geometry)` and
114
+ `geometryContainsPoint(geometry, position)` also work with these readonly dataset
115
+ features. They import no geography themselves. Containment respects holes and
116
+ includes shared boundaries, so keep all matches when querying a source. See the
117
+ [area-lookup example](../docs/EXAMPLES.md#find-areas-containing-a-location) and
118
+ [query contract](../docs/API.md#geographic-queries). These additions are available in v5.
119
+
113
120
  `geometryPath` handles Polygon, MultiPolygon, LineString, MultiLineString, and GeometryCollection; points use `map.project` and a marker element. Storybook's **Data / Neighborhood explorer** demonstrates the complete overlay and offers JSON downloads.
114
121
 
115
122
  ## District display metadata
@@ -4,6 +4,7 @@ export { districtMaps } from './districts.js';
4
4
  export { highways } from './highways.js';
5
5
  export { landmarks } from './landmarks.js';
6
6
  export { getNeighborhood, neighborhoodCollections, neighborhoodSources } from './lookup.js';
7
+ export { precinctMaps } from './precincts.js';
7
8
  export { getRealtorNeighborhood, neighborhoods } from './realtor.js';
8
9
  export { keyRoads } from './roads.js';
9
10
  export { bartStations } from './stations.js';
@@ -4,6 +4,7 @@ export { districtMaps } from './districts.js';
4
4
  export { highways } from './highways.js';
5
5
  export { landmarks } from './landmarks.js';
6
6
  export { getNeighborhood, neighborhoodCollections, neighborhoodSources } from './lookup.js';
7
+ export { precinctMaps } from './precincts.js';
7
8
  export { getRealtorNeighborhood, neighborhoods } from './realtor.js';
8
9
  export { keyRoads } from './roads.js';
9
10
  export { bartStations } from './stations.js';