@kahwee/sf-map-svg 4.3.1 → 4.3.2

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/llms-full.txt CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  > Offline San Francisco SVG maps with explicit geographic imports, no runtime dependencies, static rendering, and a browser controller.
4
4
 
5
- @kahwee/sf-map-svg 4.3.1 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
5
+ @kahwee/sf-map-svg 4.3.2 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
6
6
 
7
7
  These are working-tree docs. Unreleased changelog entries may describe APIs absent from npm; verify the installed version before copying them.
8
8
 
9
9
  Use renderMap(data, options) for SVG strings; createMap(data, options) returns a controller. Mount map.element and destroy it on unmount. Choose mode explicitly: createMap defaults to basemap. Geography is separate; staticMapData and fullMapData have different shapes.
10
10
 
11
- <!-- Generated from README.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
11
+ <!-- Generated from README.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
12
12
 
13
13
  # SF Map SVG
14
14
 
@@ -16,6 +16,8 @@ Offline, self-contained San Francisco SVG maps. The package has no runtime depen
16
16
 
17
17
  [Getting started](https://github.com/kahwee/sf-map-svg/blob/main/docs/developer-guide.md) · [API reference](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md) · [Design playground](https://kahwee.github.io/sf-map-svg/playground.html) · [Live examples](https://kahwee.github.io/sf-map-svg/examples.html) · [Storybook source](https://github.com/kahwee/sf-map-svg/blob/main/stories/) · [Geographic sources](https://github.com/kahwee/sf-map-svg/blob/main/SOURCES.md) · [v3 migration](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md)
18
18
 
19
+ [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.
20
+
19
21
  ## Install
20
22
 
21
23
  ```sh
@@ -83,11 +85,125 @@ Call `destroy()` when removing an interactive map.
83
85
 
84
86
  Canonical geography is in [`data/`](https://github.com/kahwee/sf-map-svg/blob/main/data/README.md), with provenance in [`SOURCES.md`](https://github.com/kahwee/sf-map-svg/blob/main/SOURCES.md). The public `/data` entry exposes lookup, catalog, district maps, and source-specific neighborhood collections. These are deeply frozen; clone before editing.
85
87
 
86
- Install with `pnpm install --frozen-lockfile`. See the [contributing guide](https://github.com/kahwee/sf-map-svg/blob/main/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](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md).
88
+ 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](https://github.com/kahwee/sf-map-svg/blob/main/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](https://github.com/kahwee/sf-map-svg/blob/main/docs/migration-v3.md).
89
+
90
+ ## Browser checks and marker performance
91
+
92
+ The focused interaction suite checks a production bundle in Chromium, Firefox, and WebKit at 1440 px and 390 px. It covers 2,000-marker viewport culling and reattachment, overlapping-place selection, marker keyboard navigation and focus recovery, camera input, resizing, touch-mode scroll policy, reduced motion, and disposal. Phone widths check responsive layout in these engines; they do not replace physical-device gesture testing. The full Storybook and screenshot suites continue to use Chromium.
93
+
94
+ ```sh
95
+ pnpm exec playwright install --with-deps chromium firefox webkit
96
+ pnpm test:browsers
97
+ pnpm benchmark:markers --browsers=chromium,firefox,webkit
98
+ ```
99
+
100
+ The benchmark uses headless engines and a deterministic city grid with 500 and 2,000 labeled markers at 800 px and 390 px viewport widths. It loads the production bundle and fonts before timing, disables motion, and starts each operation from a consistent camera and clustering state. Each operation has three warmups and 20 measured samples. Results include mount, pan, zoom, clustering changes, retained-ID updates, replacement, and identical updates. Run benchmarks alone on an otherwise idle machine. Use `--labels=false` to compare label cost and `--output=test-results/another-run.json` to keep separate results.
101
+
102
+ `test-results/marker-benchmark.json` records browser versions, machine details, and p50/p95/max timings. Synchronous times include the API call and forced layout; settled times include two animation frames and are not GPU-paint measurements. Six continuous workloads record 60 frame intervals each: pan, rapid zoom, and resize with clustering on and off. Four initial frame callbacks precede the measurements. An untimed allocation probe also counts newly created pin groups, including detached nodes. These measurements describe the test machine, rather than a universal performance limit or a mobile-device guarantee. The report also records mounted pin/cluster counts at overview and two zoom levels, with clustering on and off; the picker retains all records. CI saves a shorter 2,000-marker Chromium benchmark for comparison without timing thresholds.
103
+
104
+ Measured October 10–11, 2026 against renderer commit `1d523d9`, in a Linux x64 container on an Intel Xeon Platinum 8573C with a four-CPU quota and 16 GiB memory limit, using Node 26.11.1 and Playwright 1.63.0. All 36 focused browser checks passed. The 2,000-marker results below use clustering and labels. Mount, update (retained IDs), and replacement (all new IDs) show median synchronous milliseconds; pan shows the 95th-percentile frame interval in milliseconds.
105
+
106
+ | Engine | Viewport | Mount | Update | Replace | Pan frame p95 |
107
+ | --- | ---: | ---: | ---: | ---: | ---: |
108
+ | Chromium 153 | 800 px | 147.2 | 28.3 | 92.0 | 166.7 |
109
+ | Chromium 153 | 390 px | 180.6 | 24.4 | 104.4 | 183.3 |
110
+ | Firefox 155 | 800 px | 351.0 | 55.0 | 222.0 | 200.3 |
111
+ | Firefox 155 | 390 px | 386.0 | 211.0 | 161.0 | 116.8 |
112
+ | WebKit 26.6 | 800 px | 468.0 | 105.0 | 896.0 | 302.0 |
113
+ | WebKit 26.6 | 390 px | 235.0 | 107.0 | 889.0 | 118.0 |
114
+
115
+ The baseline retained all supplied pin nodes in the SVG, including clustered pins. These frame intervals exceeded the 16.7 ms budget for 60 Hz interaction. Prefer smaller marker lists and stable IDs, and avoid repeatedly replacing every marker during interaction. Identical 2,000-marker updates took 0.7–2.0 ms at the median across these runs; full replacement was especially costly in WebKit. Keep camera-call timings separate from frame timings: a quick synchronous call does not imply a smooth rendered frame.
116
+
117
+ In that baseline, a separate 20-sample Chromium run with 2,000 markers at 800 px and `labels: false` reduced the pan frame p95 from 166.7 ms to 50.0 ms and median mount time from 147.2 ms to 115.6 ms. Disabling labels helps this workload but still does not establish 60 Hz performance. Reproduce the comparison with `pnpm benchmark:markers --counts=2000 --widths=800 --labels=false --output=test-results/marker-benchmark-no-labels.json`.
118
+
119
+ ### Viewport renderer results
120
+
121
+ The viewport renderer detaches offscreen pins and clustered members, reuses their nodes when they return, caches cluster groups during pan, and skips unchanged SVG writes. Selected and focused pins stay mounted; every supplied record remains selectable. Cluster membership uses the full catalog, so panning does not change counts at the edges.
122
+
123
+ The same 20-sample, 2,000-marker benchmark was rerun October 11, 2026 on the same host, browser versions, and label settings. Columns use the same units as the baseline above.
124
+
125
+ | Engine | Viewport | Mount | Update | Replace | Pan frame p95 |
126
+ | --- | ---: | ---: | ---: | ---: | ---: |
127
+ | Chromium 153 | 800 px | 92.3 | 17.0 | 52.8 | 16.8 |
128
+ | Chromium 153 | 390 px | 97.6 | 12.9 | 63.6 | 16.7 |
129
+ | Firefox 155 | 800 px | 106.0 | 21.0 | 54.0 | 49.4 |
130
+ | Firefox 155 | 390 px | 123.0 | 45.0 | 78.0 | 17.1 |
131
+ | WebKit 26.6 | 800 px | 160.0 | 32.0 | 805.0 | 49.0 |
132
+ | WebKit 26.6 | 390 px | 164.0 | 20.0 | 781.0 | 36.0 |
133
+
134
+ Desktop pan frame p95 improved from 166.7 → 16.8 ms in Chromium, 200.3 → 49.4 ms in Firefox, and 302.0 → 49.0 ms in WebKit. Firefox and WebKit still exceed a 60 Hz frame budget in this run. Prefer stable IDs, clustering, and fewer enabled label layers for dense interactive views; full replacement still creates all new records and picker options. These headless measurements do not establish smoothness on a physical phone.
135
+
136
+ With clustering off at 800 px, the mounted pin count fell from 2,000 in city view to 228 at the sampled 4× zoom; all 2,000 records stayed in the picker. **Checks / 2000 markers / Desktop** and **Phone** in Storybook show live counts and test zoom, pan, retained-ID updates, offscreen selection, and reset. The [plan and storyboard](https://github.com/kahwee/sf-map-svg/blob/main/docs/marker-performance-plan.md) describe the regression contract. [Saved baseline and updated JSON](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-2026-10-11.json) include all timing percentiles and viewport counts.
137
+
138
+ ```sh
139
+ pnpm benchmark:markers --counts=2000 --browsers=chromium,firefox,webkit \
140
+ --output=test-results/marker-benchmark-viewport.json
141
+ ```
142
+
143
+ ### Lazy pin creation and expanded workloads
144
+
145
+ Individual SVG pins now allocate on first visibility or selection, then retain their nodes for reuse. With 2,000 clustered records, an untimed probe counted zero pin groups at mount, zero after replacing every ID, and one after selecting a pin. The previous renderer created 2,000 groups at mount and another 2,000 on replacement. The complete picker, selection, keyboard focus, and deferred entrance animations remain available.
146
+
147
+ October 11, 2026 results with the same engines, labeled grid, and 20 operation samples (median synchronous milliseconds):
148
+
149
+ | Engine | Viewport | Mount | Replace all IDs |
150
+ | --- | ---: | ---: | ---: |
151
+ | chromium | 800 px | 78.1 | 39.3 |
152
+ | chromium | 390 px | 87.9 | 47.9 |
153
+ | firefox | 800 px | 88.0 | 24.0 |
154
+ | firefox | 390 px | 95.0 | 46.0 |
155
+ | webkit | 800 px | 128.0 | 717.0 |
156
+ | webkit | 390 px | 132.0 | 710.0 |
157
+
158
+ A separate run of the same expanded harness against `e41aa2b` measured Chromium mount medians of 115.1/123.6 ms and replacement medians of 57.1/89.8 ms at 800/390 px, versus 78.1/87.9 ms and 39.3/47.9 ms after lazy allocation. This paired control covers Chromium only. WebKit replacement remains expensive at 717/710 ms; stable IDs remain preferable.
159
+
160
+ The expanded stress cases reveal work that culling alone does not remove. Unclustered frame interval p95 values, in milliseconds:
161
+
162
+ | Engine | Viewport | Pan | Rapid zoom | Resize |
163
+ | --- | ---: | ---: | ---: | ---: |
164
+ | chromium | 800 px | 16.7 | 133.4 | 83.3 |
165
+ | chromium | 390 px | 33.3 | 33.4 | 50.0 |
166
+ | firefox | 800 px | 33.8 | 34.2 | 100.2 |
167
+ | firefox | 390 px | 34.2 | 49.4 | 50.7 |
168
+ | webkit | 800 px | 60.0 | 106.0 | 342.0 |
169
+ | webkit | 390 px | 114.0 | 124.0 | 192.0 |
170
+
171
+ These runs do not demonstrate a consistent frame-rate gain: desktop Chromium clustered pan p95 was 50.1 ms in the control and 66.8 ms after lazy allocation, compared with 16.8 ms in the earlier run. Rapid zoom alternates reciprocal 1.2× factors; resize alternates host widths by 80 px. Each workload runs with clustering both on and off, samples counts outside the timed loop, and retains 60 measured intervals. Headless frame timings vary and do not establish physical-device smoothness. [Saved lazy and control results](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-lazy-2026-10-11.json) preserve every scenario and allocation count.
172
+
173
+ ```sh
174
+ pnpm benchmark:markers --counts=2000 --browsers=chromium,firefox,webkit \
175
+ --output=test-results/marker-benchmark-lazy.json
176
+ ```
177
+
178
+ ### Cached label preparation and renderer components
179
+
180
+ Neighborhood priority is sorted once per source. Styled label candidates are reused until selection, catalog, source, layers, appearance, or zoom/width eligibility changes. Overlay bounds are measured before marker and cluster SVG updates; secondary-road visibility and scale furniture skip unchanged writes. Marker catalog reconciliation and cluster rendering now have separate modules, reducing the explorer controller from 2,316 to 2,151 lines. The interactive renderer is 34.6 KiB gzip, up from 34.2 KiB, within the unchanged 35 KiB limit; static rendering remains 7.9 KiB.
181
+
182
+ A fresh Chromium comparison against `e774fbd` used 2,000 labeled markers, 20 operation samples, both widths, and all six frame workloads in basemap and neighborhood modes. Neighborhood-mode frame interval p95 values (milliseconds):
183
+
184
+ | Workload | 800 px before | 800 px after | 390 px before | 390 px after |
185
+ | --- | ---: | ---: | ---: | ---: |
186
+ | clustered pan | 66.7 | 50.0 | 16.7 | 16.8 |
187
+ | clustered rapid zoom | 66.7 | 83.4 | 33.4 | 16.8 |
188
+ | clustered resize | 16.7 | 66.7 | 66.6 | 16.8 |
189
+ | unclustered pan | 16.7 | 49.9 | 33.3 | 66.6 |
190
+ | unclustered rapid zoom | 116.6 | 33.4 | 66.7 | 83.4 |
191
+ | unclustered resize | 66.7 | 33.3 | 50.0 | 50.1 |
192
+
193
+ Results are mixed rather than a consistent frame-rate gain. Basemap desktop clustered pan p95 changed from 50.0 to 16.8 ms, while phone unclustered pan changed from 16.8 to 50.0 ms. Basemap mount medians were 72.1 → 88.8 ms at 800 px and 88.3 → 89.1 ms at 390 px; neighborhood-mode mount medians were 81.5 → 73.3 ms and 93.9 → 89.0 ms. Viewport counts and allocation probes match before and after. [Saved comparisons](https://github.com/kahwee/sf-map-svg/blob/main/docs/benchmarks/markers-refactor-2026-10-11.json) include every operation and scenario; these headless runs do not establish physical-device smoothness.
194
+
195
+ ```sh
196
+ pnpm benchmark:markers --counts=2000 --mode=neighborhoods \
197
+ --output=test-results/marker-benchmark-neighborhoods.json
198
+ ```
199
+
200
+ ## CI maintenance
201
+
202
+ [GitHub Actions maintenance](https://github.com/kahwee/sf-map-svg/blob/main/.github/ACTIONS.md) covers workflows, parallel checks, action versions, and weekly updates.
87
203
 
88
204
  ---
89
205
 
90
- <!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
206
+ <!-- Generated from docs/developer-guide.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
91
207
 
92
208
  # Developer guide
93
209
 
@@ -231,7 +347,7 @@ See [API options](https://github.com/kahwee/sf-map-svg/blob/main/docs/API.md), [
231
347
 
232
348
  ---
233
349
 
234
- <!-- Generated from docs/API.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
350
+ <!-- Generated from docs/API.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
235
351
 
236
352
  # Map options and lifecycle
237
353
 
@@ -274,7 +390,13 @@ The controller also supports marker, neighborhood, and district selection; distr
274
390
 
275
391
  If a district callback destroys the map or selects a newer district, the interrupted activation stops without moving keyboard focus or sending a stale event.
276
392
 
277
- `setMarkers()` reconciles by stable marker `id`: retained markers keep their DOM nodes, focus, and in-progress entrance animations; only new IDs animate in. Identical ordered marker updates are a visual no-op. Camera state and `viewportchange` events remain synchronous, while animated camera steps and their dependent label/marker layout commit in the same browser frame.
393
+ `setMarkers()` reconciles by stable marker `id`: retained markers keep any previously created nodes, focus, and in-progress entrance animations. New pin entrances start when the pin first becomes visible; a cached pin does not animate again merely because it returns to the viewport. Disabling entrances, reduced-motion changes, and destruction cancel pending and active entrances. Identical ordered marker updates are a visual no-op. Camera state and `viewportchange` events remain synchronous, while animated camera steps and their dependent label/marker layout commit in the same browser frame.
394
+
395
+ Interactive maps create individual pin nodes only on first visibility or selection, then cache them for reuse. Record and style updates before first visibility apply to the eventual node. They mount only pins and cluster symbols intersecting a padded viewport; clustered member pins detach. All records remain in the native picker and selectable through the API. Selected and keyboard-focused pins stay mounted, even outside the viewport, and retained nodes return when panning back. Cluster membership uses the complete catalog and stays stable during pan at the same zoom. Static SVG exports are unaffected.
396
+
397
+ Visible markers and clusters share one tab stop. Use `[` and `]` to move focus, Enter or Space to activate, and Tab to leave the group. Arrow keys retain map panning. Marker replacement preserves the focused ID when available and moves focus to a surviving marker when it is removed.
398
+
399
+ Activating coincident pins opens a place chooser, including when compact embeds hide the native marker picker. Clusters zoom when their members can separate at maximum zoom; otherwise they open the chooser. Escape or Close returns focus to the originating pin or cluster when it remains visible. Choosing a place selects its marker; replacing markers or destroying the map closes the chooser. `clusteractivate` still reports the activated cluster's markers.
278
400
 
279
401
  For a small guide, import `guideMapData` from `/guide/data` and pass it to `createMap`. The optional `/guide` entry also provides `createGuideMap`, `mountGuideMap`, and detailed-data loading for existing guide layouts. `/transit` provides the standalone schematic transit animation. See [examples](https://github.com/kahwee/sf-map-svg/blob/main/docs/EXAMPLES.md) and [consumer integration](https://github.com/kahwee/sf-map-svg/blob/main/docs/consumer-integration.md).
280
402
 
@@ -331,13 +453,13 @@ Neighborhood IDs are exact source-scoped feature IDs, rather than display names
331
453
 
332
454
  ## Coordinates and units
333
455
 
334
- GeoJSON and static projection inputs use `[longitude, latitude]` in WGS84; markers use `{ lng, lat }`. Static `project` returns SVG units. `projectToScreen(lng, lat)` returns pixels relative to the mounted canvas. Camera viewports and pan deltas use the fixed 800 × 800 projected map space, independent of canvas size. Marker radius uses screen pixels in browser maps and SVG units in static output.
456
+ GeoJSON and static projection inputs use `[longitude, latitude]` in WGS84; markers use `{ lng, lat }`. Static `project` returns SVG units. `projectToScreen(lng, lat)` returns pixels relative to the mounted canvas. Camera viewports use the fixed 800 × 800 projected map space, independent of canvas size. Pan deltas use screen pixels, converted using the mounted canvas width. Marker radius uses screen pixels in browser maps and SVG units in static output.
335
457
 
336
458
  Explore presentation options in the [live map design playground](https://kahwee.github.io/sf-map-svg/playground.html). The page identifies its package version; released builds use compatible options for that installed version.
337
459
 
338
460
  ---
339
461
 
340
- <!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
462
+ <!-- Generated from docs/EXAMPLES.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
341
463
 
342
464
  # Examples
343
465
 
@@ -448,7 +570,7 @@ For the controller and explicit data imports, see [the v3 migration guide](https
448
570
 
449
571
  ---
450
572
 
451
- <!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
573
+ <!-- Generated from docs/consumer-integration.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
452
574
 
453
575
  # Embedding and progressive enhancement
454
576
 
@@ -500,7 +622,7 @@ See the [exact API contract](https://github.com/kahwee/sf-map-svg/blob/main/docs
500
622
 
501
623
  ---
502
624
 
503
- <!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
625
+ <!-- Generated from docs/migration-v3.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
504
626
 
505
627
  # Migrate to version 3
506
628
 
@@ -541,7 +663,7 @@ District styles are now prepared once per district on construction and style/yea
541
663
 
542
664
  ---
543
665
 
544
- <!-- Generated from SOURCES.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
666
+ <!-- Generated from SOURCES.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
545
667
 
546
668
  # Geometry and provenance
547
669
 
@@ -725,7 +847,7 @@ display geometry, and no precinct polygons or raw workbook data are shipped.
725
847
 
726
848
  ---
727
849
 
728
- <!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 4.3.1 · local working tree. -->
850
+ <!-- Generated from CHANGELOG.md; @kahwee/sf-map-svg 4.3.2 · local working tree. -->
729
851
 
730
852
  # Changelog
731
853
 
@@ -733,6 +855,24 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
733
855
 
734
856
  ## Unreleased
735
857
 
858
+ ## 4.3.2 — 2026-10-10
859
+
860
+ - Update compatible development tooling and refresh dependency security fixes.
861
+
862
+ - Cache label candidates across camera frames, prepare neighborhood priority once per source, and measure overlays before SVG updates.
863
+ - Extract marker catalog reconciliation and cluster rendering with explicit cache and listener ownership; add label refresh checks across catalog, appearance, layers, and source changes.
864
+
865
+ - Create individual marker visuals only on first visibility or selection, keeping the full catalog and retained nodes. Defer new pin entrances and cancel pending entrances with active animations.
866
+ - Benchmark clustered and unclustered continuous pan, rapid zoom, and resize, with separate allocation probes.
867
+
868
+ - Cull offscreen pins and cluster symbols while preserving the full catalog, cached nodes, selection, and keyboard focus. Cache cluster groups during pan and skip unchanged SVG attributes.
869
+ - Add interactive 2,000-marker desktop and phone stories, viewport count benchmarks, and a Bun 1.4.3 compatibility gate.
870
+
871
+ - Add production-bundle interaction checks in Chromium, Firefox, and WebKit, plus reproducible marker benchmarks and CI performance artifacts.
872
+ - Add optimized desktop and phone marker screencasts to the Pages examples, with captions and playback size checks.
873
+ - Add a place chooser for coincident pins and clusters that cannot separate at maximum zoom, including compact embeds with hidden native pickers.
874
+ - Give visible markers and clusters one tab stop with bracket-key navigation, preserving focus across marker reordering and recovering focus when a marker is removed.
875
+
736
876
  ## 4.3.1 — 2026-10-04
737
877
 
738
878
  - Reject unknown flat static options and invalid values before rendering or invoking district styling callbacks, matching grouped-option validation.
@@ -1051,7 +1191,7 @@ consistent home, and makes animation and subscription ownership predictable.
1051
1191
 
1052
1192
  # Published type contracts
1053
1193
 
1054
- @kahwee/sf-map-svg 4.3.1 · local working tree. These declarations are generated by TypeScript. Use package export paths, not the internal filenames below. Imported relative contracts are included for context, not as additional public entry points.
1194
+ @kahwee/sf-map-svg 4.3.2 · local working tree. These declarations are generated by TypeScript. Use package export paths, not the internal filenames below. Imported relative contracts are included for context, not as additional public entry points.
1055
1195
 
1056
1196
  | Public import | Declaration |
1057
1197
  | --- | --- |
@@ -1232,7 +1372,7 @@ export interface MapCamera {
1232
1372
  fit(geometry: Geometry, options?: CameraOptions & {
1233
1373
  padding?: number | MapPadding;
1234
1374
  }): void;
1235
- /** Delta in projected map units. */
1375
+ /** Delta in screen pixels, converted using the mounted canvas width. */
1236
1376
  pan(x: number, y: number, options?: CameraOptions): void;
1237
1377
  zoom(factor: number, options?: CameraOptions): void;
1238
1378
  reset(options?: CameraOptions): void;
@@ -1249,7 +1389,7 @@ export interface MapController {
1249
1389
  getResolvedConfiguration(): ResolvedMapConfiguration;
1250
1390
  getCapabilities(): MapCapabilities;
1251
1391
  on<K extends keyof MapEvents>(type: K, listener: (detail: MapEvents[K]) => void): () => void;
1252
- /** Reconcile stable IDs; retained markers preserve nodes, focus, and entrance animations. */
1392
+ /** Reconcile stable IDs; preserve existing nodes and focus. Pin visuals allocate on first visibility/selection. */
1253
1393
  setMarkers(markers: readonly MapMarker[]): void;
1254
1394
  setOverlays(overlays: readonly MapOverlay[]): void;
1255
1395
  /** False for missing IDs or a source/year other than the current geography. */
@@ -1663,7 +1803,7 @@ export interface NeighborhoodExplorerElement extends HTMLElement {
1663
1803
  fitGeometry(geometry: Geometry, padding?: number | MapPadding, options?: CameraOptions): void;
1664
1804
  /** Explicitly engage map touch gestures; false restores page gestures. */
1665
1805
  setTouchNavigation(enabled: boolean): void;
1666
- /** Reconcile stable IDs; retained markers preserve nodes, focus, and entrance animations. */
1806
+ /** Reconcile stable IDs; preserve existing nodes and focus. Pin visuals allocate on first visibility/selection. */
1667
1807
  setMarkers(markers: readonly MapMarker[]): void;
1668
1808
  setOverlays(overlays: readonly MapOverlay[]): void;
1669
1809
  selectMarker(id: string | null, options?: {
package/llms.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Offline San Francisco SVG maps with explicit geographic imports, no runtime dependencies, static rendering, and a browser controller.
4
4
 
5
- @kahwee/sf-map-svg 4.3.1 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
5
+ @kahwee/sf-map-svg 4.3.2 · local working tree. Node >=24.0.0 for server rendering; browsers need a DOM and a JSON-capable bundler.
6
6
 
7
7
  These are working-tree docs. Unreleased changelog entries may describe APIs absent from npm; verify the installed version before copying them.
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kahwee/sf-map-svg",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
4
4
  "description": "Offline SVG maps of San Francisco with district boundaries, parks, landmarks, and BART stations.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/api.js",
@@ -135,22 +135,22 @@
135
135
  "node": ">=24.0.0"
136
136
  },
137
137
  "devDependencies": {
138
- "@biomejs/biome": "2.5.14",
139
- "@storybook/addon-a11y": "10.6.0",
140
- "@storybook/addon-docs": "^10.6.0",
141
- "@storybook/addon-vitest": "10.6.0",
142
- "@storybook/html-vite": "^10.6.0",
143
- "@vitest/browser-playwright": "^5.0.2",
144
- "@vitest/coverage-v8": "5.0.2",
138
+ "@biomejs/biome": "2.5.15",
139
+ "@storybook/addon-a11y": "10.6.1",
140
+ "@storybook/addon-docs": "^10.6.1",
141
+ "@storybook/addon-vitest": "10.6.1",
142
+ "@storybook/html-vite": "^10.6.1",
143
+ "@vitest/browser-playwright": "^5.0.3",
144
+ "@vitest/coverage-v8": "5.0.3",
145
145
  "@xmldom/xmldom": "^0.9.12",
146
146
  "playwright": "^1.63.0",
147
147
  "polygon-clipping": "^0.15.7",
148
148
  "react": "^19.3.0",
149
149
  "react-dom": "^19.3.0",
150
- "storybook": "^10.6.0",
150
+ "storybook": "^10.6.1",
151
151
  "typescript": "^7.0.2",
152
- "vite": "^8.3.1",
153
- "vitest": "^5.0.2"
152
+ "vite": "^8.3.3",
153
+ "vitest": "^5.0.3"
154
154
  },
155
155
  "homepage": "https://kahwee.github.io/sf-map-svg/",
156
156
  "bugs": {
@@ -172,6 +172,9 @@
172
172
  "test:stories": "pnpm build && vitest run --project=storybook",
173
173
  "test:stories:coverage": "pnpm build && vitest run --project=storybook --coverage",
174
174
  "test:visual": "playwright test --config=playwright.visual.config.mjs",
175
+ "test:browsers": "playwright test --config=playwright.browser.config.mjs",
176
+ "test:bun": "pnpm build && bun scripts/smoke-bun.mjs",
177
+ "benchmark:markers": "node scripts/benchmark-markers.mjs",
175
178
  "demo": "pnpm build && node examples/build.mjs",
176
179
  "format": "biome check --write .",
177
180
  "format:check": "biome format .",