From 608ee99ffca346f570cb8a74ea89535bb1dfa399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Zbytovsk=C3=BD?= Date: Mon, 13 Jul 2026 16:59:32 +0200 Subject: [PATCH 1/2] SearchBox: fix search broken from id-tagging-schema@7.0.0 (#120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description `@openstreetmap/id-tagging-schema` translations are fetched from CDN unpinned (always `latest`). Upstream changed `terms` from a comma-separated string to a `string[]` — confirmed on the live CDN data: 1324 of 1731 presets now have `terms` as an array. `getPresetTermsTranslation()` still called `.split(',')` on it, throwing for almost every preset and breaking SearchBox preset search entirely (Sentry: `TypeError: (0, y.MG).split is not a function`, ~1800+ events). - `getPresetTermsTranslation()` now normalizes both shapes (array passthrough, legacy string gets split, missing → `[]`) and returns `string[]` directly. - Removed the now-redundant `.split(',')` at the call site in `preset.tsx`. - Added `translations.test.ts` covering all 4 cases (array, legacy string, missing terms, unknown preset key). ### Checklist - [x] server-side-rendering (SSR) - n/a, client-only search code - [x] all texts are localized (in vocabulary.ts) - n/a, no new UI text https://claude.ai/code/session_01YChsE67uVMznndDeUHwbwA --- _Generated by [Claude Code](https://claude.ai/code/session_01YChsE67uVMznndDeUHwbwA)_ --------- (cherry picked from commit 2f7c69f032460819f8ee961b070ee555f5c87ccd) --- src/components/SearchBox/options/preset.tsx | 2 +- .../tagging/__tests__/translations.test.ts | 39 +++++++++++++++++++ src/services/tagging/translations.ts | 8 +++- 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/services/tagging/__tests__/translations.test.ts diff --git a/src/components/SearchBox/options/preset.tsx b/src/components/SearchBox/options/preset.tsx index 023ce8e42..6ae1cc5f9 100644 --- a/src/components/SearchBox/options/preset.tsx +++ b/src/components/SearchBox/options/preset.tsx @@ -41,7 +41,7 @@ const getPresetsForSearch = async () => { tags, tagsAsOneString: tagsAsStrings.join(', '), texts: [ - ...getPresetTermsTranslation(presetKey).split(','), + ...getPresetTermsTranslation(presetKey), ...tagsAsStrings, presetKey, ], diff --git a/src/services/tagging/__tests__/translations.test.ts b/src/services/tagging/__tests__/translations.test.ts new file mode 100644 index 000000000..41ed68816 --- /dev/null +++ b/src/services/tagging/__tests__/translations.test.ts @@ -0,0 +1,39 @@ +import { intl } from '../../intl'; +import { + getPresetTermsTranslation, + mockSchemaTranslations, +} from '../translations'; + +intl.lang = 'en'; + +describe('getPresetTermsTranslation', () => { + it('returns terms as-is when already a string[] (current id-tagging-schema format)', () => { + mockSchemaTranslations({ + en: { presets: { presets: { shop: { terms: ['retailer', 'store'] } } } }, + }); + + expect(getPresetTermsTranslation('shop')).toEqual(['retailer', 'store']); + }); + + it('splits a comma-separated string (older id-tagging-schema format)', () => { + mockSchemaTranslations({ + en: { presets: { presets: { shop: { terms: 'retailer,store' } } } }, + }); + + expect(getPresetTermsTranslation('shop')).toEqual(['retailer', 'store']); + }); + + it('returns [] when the preset has no terms', () => { + mockSchemaTranslations({ + en: { presets: { presets: { shop: { name: 'Shop' } } } }, + }); + + expect(getPresetTermsTranslation('shop')).toEqual([]); + }); + + it('returns [] for an unknown preset key', () => { + mockSchemaTranslations({ en: { presets: { presets: {} } } }); + + expect(getPresetTermsTranslation('does-not-exist')).toEqual([]); + }); +}); diff --git a/src/services/tagging/translations.ts b/src/services/tagging/translations.ts index 3461960c5..559c109b0 100644 --- a/src/services/tagging/translations.ts +++ b/src/services/tagging/translations.ts @@ -43,8 +43,12 @@ export const mockSchemaTranslations = (mockTranslations) => { export const getPresetTranslation = (key: string): string => translations?.[intl.lang]?.presets?.presets?.[key]?.name ?? `[${key}]`; -export const getPresetTermsTranslation = (key: string) => - translations?.[intl.lang]?.presets?.presets?.[key]?.terms ?? ''; +export const getPresetTermsTranslation = (key: string): string[] => { + const terms = translations?.[intl.lang]?.presets?.presets?.[key]?.terms; + if (Array.isArray(terms)) return terms; + if (typeof terms === 'string') return terms ? terms.split(',') : []; + return []; +}; export const getAllTranslations = () => translations?.[intl.lang]; From de8c75ff4cc2b31e9ccebf04a9472d7ffd71967f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Zbytovsk=C3=BD?= Date: Mon, 13 Jul 2026 17:14:34 +0200 Subject: [PATCH 2/2] tagging: pin id-tagging-schema to 7.x (#122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CDN fetch for `id-tagging-schema` translations was unpinned (always `latest`), which is how a breaking change (`terms` string → string[]) reached production unannounced and broke SearchBox (#120). Pins the CDN URL to `7.x` and bumps the npm dependency to match, so we still get updates but a future `8.0.0` won't silently break us again. - CDN fetch now resolves `@7` instead of unpinned latest. - `package.json`/`yarn.lock`: `@openstreetmap/id-tagging-schema` 6.12.0 → 7.0.1. - 7.x ships `.d.json.ts` declarations for its JSON exports - added `allowArbitraryExtensions` to `tsconfig.json` so `tsc` picks them up. - Updated one test snapshot for the resulting (alphabetical) field order from the schema bump. https://claude.ai/code/session_01YChsE67uVMznndDeUHwbwA --- _Generated by [Claude Code](https://claude.ai/code/session_01YChsE67uVMznndDeUHwbwA)_ (cherry picked from commit af0abc989cd0960352dbc1474b7761e709cfb6ef) --- package.json | 2 +- .../tagging/__tests__/idTaggingScheme.test.ts | 28 +++++++++---------- src/services/tagging/translations.ts | 9 +++++- tsconfig.json | 1 + yarn.lock | 8 +++--- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index f815b7f4f..c3fcf01ae 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@mui/lab": "^7.0.0-beta.14", "@mui/material": "^7.1.2", "@mui/material-nextjs": "^7.1.1", - "@openstreetmap/id-tagging-schema": "^6.12.0", + "@openstreetmap/id-tagging-schema": "^7.0.1", "@sentry/nextjs": "^8.34.0", "@teritorio/openmaptiles-gl-language": "^1.5.4", "@turf/turf": "^7.2.0", diff --git a/src/services/tagging/__tests__/idTaggingScheme.test.ts b/src/services/tagging/__tests__/idTaggingScheme.test.ts index 70f80238c..fdfd53ba3 100644 --- a/src/services/tagging/__tests__/idTaggingScheme.test.ts +++ b/src/services/tagging/__tests__/idTaggingScheme.test.ts @@ -134,22 +134,22 @@ describe('idTaggingScheme', () => { 'level', 'polling_station', 'wheelchair', - 'wikimedia_commons', - 'wikidata', - 'start_date', - 'short_name', - 'reg_name', - 'panoramax', - 'official_name', - 'note', - 'nat_name', - 'mapillary', - 'loc_name', - 'fixme', - 'ele_node', - 'description', 'alt_name', + 'description', + 'ele_node', + 'fixme', + 'loc_name', + 'mapillary', + 'nat_name', + 'note', + 'official_name', + 'panoramax', 'ref/linz/place_id-NZ', + 'reg_name', + 'short_name', + 'start_date', + 'wikidata', + 'wikimedia_commons', 'architect', ]); }); diff --git a/src/services/tagging/translations.ts b/src/services/tagging/translations.ts index 559c109b0..5ccd1054c 100644 --- a/src/services/tagging/translations.ts +++ b/src/services/tagging/translations.ts @@ -9,13 +9,20 @@ import { getOurTranslations } from './ourPresets'; // https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@6.1.0/dist/translations/en.min.json const cdnUrl = `https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema`; +// Pinned to the same major as the `@openstreetmap/id-tagging-schema` dependency +// in package.json (bump both together) - an unpinned `latest` silently pulled +// in a breaking change to the `terms` format once (see getPresetTermsTranslation). +const CDN_VERSION_RANGE = '7'; + // TODO download up-to-date or use node_module? let translations = {}; export const fetchSchemaTranslations = async () => { if (translations[intl.lang]) return; try { - const presetsPackage = await fetchJson(`${cdnUrl}/package.json`); + const presetsPackage = await fetchJson( + `${cdnUrl}@${CDN_VERSION_RANGE}/package.json`, + ); const { version } = presetsPackage; // this request is cached in browser diff --git a/tsconfig.json b/tsconfig.json index cb770944d..5e9ef5706 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, + "allowArbitraryExtensions": true, "isolatedModules": true, "jsx": "preserve", "incremental": true diff --git a/yarn.lock b/yarn.lock index 02887e940..a22f2abf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1515,10 +1515,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openstreetmap/id-tagging-schema@^6.12.0": - version "6.12.0" - resolved "https://registry.yarnpkg.com/@openstreetmap/id-tagging-schema/-/id-tagging-schema-6.12.0.tgz#963c0340850b4e40680a87344fea89e90b9a1a70" - integrity sha512-uAe0YyXv/QjQGhudc2FBmTg+TT+mks8H8p1FlN/hby9KKpvF9CwLU/ZLe3oDqK0jye6t3GlJ9HAyN/hxk7fTwQ== +"@openstreetmap/id-tagging-schema@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@openstreetmap/id-tagging-schema/-/id-tagging-schema-7.0.1.tgz#c383d34ba993ad7488f485cb8fd64e548e8022ce" + integrity sha512-y58T1905O3SJZlf3SLjhKkxxLsZAvYRNpmjE8zsag39lHh23f0IC20wNRDGrl9iUcBdD8zNs5Z/DxEDa3+Fbcg== "@opentelemetry/api-logs@0.53.0": version "0.53.0"