diff --git a/internal/check-map-props-unit.mjs b/internal/check-map-props-unit.mjs index 371bc73eb..ed8447660 100644 --- a/internal/check-map-props-unit.mjs +++ b/internal/check-map-props-unit.mjs @@ -2,7 +2,7 @@ /** * check-map-props-unit — the engine and token props, decided in the library, measured WITHOUT a browser. * - * resolveMapProvider ('auto': Mapbox when a token is available, MapLibre otherwise; an explicit provider + * resolveMapProvider ('auto' is ALWAYS MapLibre since 2026-09-28; a token no longer selects an engine; an explicit provider * wins), and the page-wide token (useSharedMapToken): publish, CLEAR, and a token-service URL. Runs the * real library modules in Node against a stand-in window/location, so it is fast and needs no browser. * @@ -31,13 +31,23 @@ const TK = ['pk', 'eyJ1IjoidGVzdCJ9', 'c2ln'].join('.') console.log('MAP-PROPS: the library, in Node (no browser)') eq('default is auto', DEFAULT_PROVIDER, 'auto') reset(); eq('auto, no token anywhere → maplibre', resolveMapProvider({}), 'maplibre') -reset(); eq('auto, tk in profile-config → mapbox', resolveMapProvider({ tk: TK }), 'mapbox') +// ★ THESE FOUR ASSERTED THE OLD RULE, in which merely HOLDING a token chose Mapbox. They are +// inverted rather than deleted, because the point is not that a token is ignored — it is that a +// token is a CAPABILITY and the engine is a DECISION, and only the decision belongs here. Each of +// these is a host who pasted a real token into a published snippet and set no provider: that is +// the entire migration surface, and it is exactly what changes. +reset(); eq('auto, tk in profile-config → maplibre (was mapbox)', resolveMapProvider({ tk: TK }), 'maplibre') reset(); eq('auto, placeholder tk YOUR_MAPBOX_TOKEN → maplibre', resolveMapProvider({ tk: 'YOUR_MAPBOX_TOKEN' }), 'maplibre') -reset(); window.MAP_TOKEN = TK; eq('auto, host window.MAP_TOKEN → mapbox', resolveMapProvider({}), 'mapbox') -reset(); eq('auto, a token the caller holds (useMapInstance) → mapbox', resolveMapProvider({}, { token: TK }), 'mapbox') -reset(); eq('auto, tokenUrl in profile-config → mapbox (U1)', resolveMapProvider({ tokenUrl: 'https://example.org/token' }), 'mapbox') +reset(); window.MAP_TOKEN = TK; eq('auto, host window.MAP_TOKEN → maplibre (was mapbox)', resolveMapProvider({}), 'maplibre') +reset(); eq('auto, a token the caller holds (useMapInstance) → maplibre (was mapbox)', resolveMapProvider({}, { token: TK }), 'maplibre') +reset(); eq('auto, tokenUrl in profile-config → maplibre (was mapbox, U1)', resolveMapProvider({ tokenUrl: 'https://example.org/token' }), 'maplibre') reset(); eq('explicit maplibre with a token → maplibre', resolveMapProvider({ provider: 'maplibre', tk: TK }), 'maplibre') reset(); eq('explicit mapbox without a token → mapbox', resolveMapProvider({ provider: 'mapbox' }), 'mapbox') +// The opt-in still works WITH a token, which is the path a migrating host takes. +reset(); eq('explicit mapbox WITH a token → mapbox (the documented way to keep Mapbox)', resolveMapProvider({ provider: 'mapbox', tk: TK }), 'mapbox') +// And a token remains VISIBLE — it just does not vote. If this ever returned false the engine +// would still be right while authentication silently broke, so the two are asserted apart. +reset(); eq('a real token is still SEEN by hasMapToken (capability, not choice)', hasMapToken({ tk: TK }), true) reset(); eq('mixed case "MapLibre" is honoured (U7)', resolveMapProvider({ provider: 'MapLibre', tk: TK }), 'maplibre') reset(); search = '?provider=maplibre'; window.MAP_TOKEN = TK; eq('?provider=maplibre wins over a host token', resolveMapProvider({}), 'maplibre') reset(); window.MAP_PROVIDER = 'mapbox'; eq('window.MAP_PROVIDER is explicit', explicitMapProvider({}), 'mapbox') @@ -79,4 +89,4 @@ eq('a non-public (sk.) answer is refused', secret, '') eq('…and nothing is published', window.MAP_TOKEN || '', '') if (bad) { console.log(`MAP-PROPS: FAIL — ${bad} case(s).`); process.exit(1) } -console.log('MAP-PROPS: PASS — auto picks the engine from the token ladder, explicit wins, Clear clears, tokenUrl is honoured.') +console.log('MAP-PROPS: PASS — auto is ALWAYS MapLibre, a token is a capability and never a choice, explicit wins, Clear clears.') diff --git a/library/composables/useMapProvider.js b/library/composables/useMapProvider.js index c499e1cfe..811104af6 100644 --- a/library/composables/useMapProvider.js +++ b/library/composables/useMapProvider.js @@ -61,7 +61,24 @@ export function hasMapToken(cfg) { */ export function resolveMapProvider(profileConfig, { token } = {}) { const cfg = unwrapConfig(profileConfig) - return explicitMapProvider(cfg) || (hasMapToken(cfg) || hasMapToken({ tk: token }) ? 'mapbox' : 'maplibre') + // ★ AUTO IS MAPLIBRE. A TOKEN NO LONGER CHOOSES AN ENGINE (Driver, 2026-09-28: MapLibre is the + // default everywhere, on cost and on how it looks; Mapbox stays supported as an explicit choice). + // + // It used to read: explicit || (token present ? 'mapbox' : 'maplibre') — so merely HOLDING a + // Mapbox token selected Mapbox. That conflated two different things: "I have a token" is a + // capability, "I want Mapbox" is a decision, and only the second belongs in this function. + // + // The token is still honoured for what it IS: with provider:'mapbox' it authenticates the map, + // and the `token` argument stays in the signature because callers pass one they hold outside the + // config. It simply no longer votes on the engine. + // + // WHO CHANGES: a host that pasted a real token into a published embed snippet and set no + // provider. All 36 shipped snippets carry a tk placeholder and NO provider key, so that is the + // whole migration surface. An UNFILLED snippet was already MapLibre — hasMapToken rejects + // YOUR_-prefixed placeholders — and the DX Life branches pass no token at all. + // To keep Mapbox, such a host adds "provider":"mapbox" to profile-config; ?provider=mapbox and + // window.MAP_PROVIDER are unchanged. + return explicitMapProvider(cfg) || 'maplibre' } /** The engine someone ASKED for (rungs 1-3), or '' when the default ('auto') decides. */ diff --git a/package.json b/package.json index 9a5da6ed7..05bcf137b 100644 --- a/package.json +++ b/package.json @@ -285,7 +285,7 @@ "fix:sri": "node internal/fix-sri.mjs", "gallery": "node internal/generate-gallery.mjs", "gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:frozen-labels", - "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary", + "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit", "gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs", "internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs", "library-index": "node internal/generate-library-index.mjs",