From ee40a398fd26409ec9fba633442339b17fc57190 Mon Sep 17 00:00:00 2001 From: Yevheniia Sorokina Date: Wed, 19 Aug 2026 12:12:03 +0100 Subject: [PATCH 1/3] fix(docs): validate the singular `framework` field and normalize its values docs-schema.yml defined the plural `frameworks` array with an enum but never defined the singular `framework` field at all. With additionalProperties: true it got no validation, and its values drifted. Canonical set: the enum is now the single source of truth for framework identifiers, and singular and plural share it - a page states one platform or several, never two different vocabularies. Normalized 117 pages: - netIos -> net-ios (31), netAndroid -> net-android (31), react -> react-native (30). Pure spelling drift; the canonical spellings already existed on other pages. - express (13) and bolt (12) were never frameworks. Those pages live under docs/hosted/, so they are `framework: hosted` with the value they were carrying moved to `product: express` / `product: id-bolt`, both of which already exist in the product enum. Two things the ticket's count could not have seen: - `kmp` is a sixth non-canonical value, on 37 pages - more than any entry in the ticket's table. It is not drift: those pages are the published /sdks/kmp/ tree, kmp is first-class in FRAMEWORK_MAPPING, QUERY_FRAMEWORK_TO_PATH and SkillsCallout, and being unreleased is already tracked by UNRELEASED_FRAMEWORK_SLUGS. The enum was stale, so kmp is added to it rather than the pages being rewritten. - FW_TO_PJ does not exist on main - PR #417 is unmerged. The real duplication is five maps, not three. Why nobody ever saw an error: docs-gate is a ratchet over changed files, so 37 pages violating the *plural* enum went unreported for months. Step 2 of the ticket alone would not have caught the other 117 either. scripts/verify-frameworks.cjs (yarn verify:frameworks, wired into CI) therefore checks the whole corpus, and the code maps as well: CONTENT no page may declare a framework outside the enum DRIFT no code map may key off a framework the enum does not define COVERAGE enum slugs missing from a map are reported as known gaps It found one live bug on its first run: FRAMEWORK_MAPPING had no `linux` entry, so parseSdksRoute() resolved no framework at all for every /sdks/linux/ page. Fixed here. Ticket acceptance test: a page set to `framework: netIos` fails the gate and passes after normalization - verified both ways. Step 4 (consolidating the five maps onto one) is deliberately NOT in this commit. Those maps have different membership, so merging them changes runtime behaviour - the linux gap above is exactly that kind of change - and it deserves its own reviewable diff. The DRIFT check above is what stops them diverging further in the meantime. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-docs.yml | 7 + docs-schema.yml | 20 +- .../input-configuration.md | 3 +- .../output-configuration.md | 3 +- .../custom-data-transfer/overview.md | 3 +- .../device-pairing/bluetooth-pairing.md | 3 +- .../device-pairing/device-pairing.md | 3 +- .../device-pairing/online-connection.md | 3 +- .../express/configuration/express-find.md | 3 +- docs/hosted/express/configuration/index.md | 3 +- .../express/configuration/inventory-count.md | 3 +- .../express/configuration/scan-labels.md | 3 +- .../express/getting-started/installation.md | 3 +- .../hosted/express/getting-started/rollout.md | 3 +- docs/hosted/express/overview.md | 3 +- docs/hosted/id-bolt/advanced.md | 3 +- docs/hosted/id-bolt/api-overview.md | 3 +- docs/hosted/id-bolt/callbacks.md | 3 +- docs/hosted/id-bolt/data-handling.md | 3 +- docs/hosted/id-bolt/document-selection.md | 3 +- docs/hosted/id-bolt/getting-started.md | 3 +- docs/hosted/id-bolt/overview.md | 3 +- docs/hosted/id-bolt/release-notes.md | 3 +- docs/hosted/id-bolt/text-overrides.md | 3 +- docs/hosted/id-bolt/theming.md | 3 +- docs/hosted/id-bolt/validators.md | 3 +- docs/hosted/id-bolt/workflow.md | 3 +- docs/sdks/net/android/add-sdk.md | 2 +- .../android/ai-powered-barcode-scanning.md | 2 +- .../configure-barcode-symbologies.md | 2 +- .../android/barcode-capture/get-started.md | 2 +- .../android/barcode-selection/get-started.md | 2 +- .../net/android/barcode-selection/intro.md | 2 +- docs/sdks/net/android/batch-scanning.md | 2 +- docs/sdks/net/android/id-capture/advanced.md | 2 +- .../net/android/id-capture/get-started.md | 2 +- docs/sdks/net/android/id-capture/intro.md | 2 +- .../android/id-capture/supported-documents.md | 2 +- .../net/android/matrixscan-ar/get-started.md | 2 +- docs/sdks/net/android/matrixscan-ar/intro.md | 2 +- .../net/android/matrixscan-count/advanced.md | 2 +- .../android/matrixscan-count/get-started.md | 2 +- .../net/android/matrixscan-count/intro.md | 2 +- .../net/android/matrixscan-find/advanced.md | 2 +- .../android/matrixscan-find/get-started.md | 2 +- .../sdks/net/android/matrixscan-find/intro.md | 2 +- .../net/android/matrixscan-pick/advanced.md | 2 +- .../android/matrixscan-pick/get-started.md | 2 +- .../sdks/net/android/matrixscan-pick/intro.md | 2 +- docs/sdks/net/android/matrixscan/advanced.md | 2 +- .../net/android/matrixscan/get-started.md | 2 +- docs/sdks/net/android/matrixscan/intro.md | 2 +- docs/sdks/net/android/parser/get-started.md | 2 +- docs/sdks/net/android/release-notes.md | 2 +- docs/sdks/net/android/single-scanning.md | 2 +- docs/sdks/net/android/sparkscan/advanced.md | 2 +- .../sdks/net/android/sparkscan/get-started.md | 2 +- docs/sdks/net/android/sparkscan/intro.md | 2 +- docs/sdks/net/ios/add-sdk.md | 2 +- .../net/ios/ai-powered-barcode-scanning.md | 2 +- .../configure-barcode-symbologies.md | 2 +- .../net/ios/barcode-capture/get-started.md | 2 +- .../net/ios/barcode-selection/get-started.md | 2 +- docs/sdks/net/ios/barcode-selection/intro.md | 2 +- docs/sdks/net/ios/batch-scanning.md | 2 +- docs/sdks/net/ios/id-capture/advanced.md | 2 +- docs/sdks/net/ios/id-capture/get-started.md | 2 +- docs/sdks/net/ios/id-capture/intro.md | 2 +- .../net/ios/id-capture/supported-documents.md | 2 +- .../sdks/net/ios/matrixscan-ar/get-started.md | 2 +- docs/sdks/net/ios/matrixscan-ar/intro.md | 2 +- .../sdks/net/ios/matrixscan-count/advanced.md | 2 +- .../net/ios/matrixscan-count/get-started.md | 2 +- docs/sdks/net/ios/matrixscan-count/intro.md | 2 +- docs/sdks/net/ios/matrixscan-find/advanced.md | 2 +- .../net/ios/matrixscan-find/get-started.md | 2 +- docs/sdks/net/ios/matrixscan-find/intro.md | 2 +- docs/sdks/net/ios/matrixscan-pick/advanced.md | 2 +- .../net/ios/matrixscan-pick/get-started.md | 2 +- docs/sdks/net/ios/matrixscan-pick/intro.md | 2 +- docs/sdks/net/ios/matrixscan/advanced.md | 2 +- docs/sdks/net/ios/matrixscan/get-started.md | 2 +- docs/sdks/net/ios/matrixscan/intro.md | 2 +- docs/sdks/net/ios/parser/get-started.md | 2 +- docs/sdks/net/ios/release-notes.md | 2 +- docs/sdks/net/ios/single-scanning.md | 2 +- docs/sdks/net/ios/sparkscan/advanced.md | 2 +- docs/sdks/net/ios/sparkscan/get-started.md | 2 +- docs/sdks/net/ios/sparkscan/intro.md | 2 +- docs/sdks/react-native/add-sdk.md | 2 +- .../configure-barcode-symbologies.md | 2 +- .../barcode-capture/get-started.md | 2 +- .../barcode-selection/get-started.md | 2 +- .../react-native/barcode-selection/intro.md | 2 +- docs/sdks/react-native/batch-scanning.md | 2 +- docs/sdks/react-native/id-capture/advanced.md | 2 +- .../react-native/id-capture/get-started.md | 2 +- docs/sdks/react-native/id-capture/intro.md | 2 +- .../label-capture/label-definitions.md | 2 +- .../react-native/matrixscan-ar/get-started.md | 2 +- docs/sdks/react-native/matrixscan-ar/intro.md | 2 +- .../react-native/matrixscan-count/advanced.md | 2 +- .../matrixscan-count/get-started.md | 2 +- .../react-native/matrixscan-count/intro.md | 2 +- .../react-native/matrixscan-find/advanced.md | 2 +- .../matrixscan-find/get-started.md | 2 +- .../react-native/matrixscan-find/intro.md | 2 +- .../react-native/matrixscan-pick/advanced.md | 2 +- .../matrixscan-pick/get-started.md | 2 +- .../react-native/matrixscan-pick/intro.md | 2 +- docs/sdks/react-native/matrixscan/advanced.md | 2 +- .../react-native/matrixscan/get-started.md | 2 +- docs/sdks/react-native/matrixscan/intro.md | 2 +- docs/sdks/react-native/parser/get-started.md | 2 +- docs/sdks/react-native/release-notes.md | 2 +- docs/sdks/react-native/single-scanning.md | 2 +- docs/sdks/react-native/sparkscan/advanced.md | 2 +- .../react-native/sparkscan/get-started.md | 2 +- docs/sdks/react-native/sparkscan/intro.md | 2 +- package.json | 3 +- scripts/verify-frameworks.cjs | 212 ++++++++++++++++++ src/components/utils/frameworks.ts | 3 + 122 files changed, 384 insertions(+), 120 deletions(-) create mode 100644 scripts/verify-frameworks.cjs diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 7d0bd37b5..157aea8f7 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -27,5 +27,12 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile + # Whole-corpus check: the docs gate is a ratchet on changed files, so it + # cannot see a pre-existing violation nobody touched. This is what makes a + # framework value outside the enum - in docs OR in the code maps that read + # it - break the build instead of silently mis-resolving. + - name: Verify framework identifiers + run: yarn verify:frameworks + - name: Build documentation run: yarn build diff --git a/docs-schema.yml b/docs-schema.yml index 8271e643a..108a2bf6c 100644 --- a/docs-schema.yml +++ b/docs-schema.yml @@ -35,14 +35,30 @@ properties: matrixscan-ar, matrixscan-pick, barcode-selection, barcode-generator, id-capture, id-bolt, label-capture, parser, express, core, platform] + # Canonical framework slugs. These are the SSOT for every framework identifier + # in the repo: they are exactly the `/sdks//` URL segments (with `hosted` + # for the /hosted/ tree), and src/components/utils/frameworks.ts must cover + # this same set - `yarn verify:frameworks` fails the build if the two drift. + # + # `kmp` (Kotlin Multiplatform) is documented only in the in-development docs + # version; that is tracked by UNRELEASED_FRAMEWORK_SLUGS in + # src/constants/unreleasedFrameworks.ts, not by leaving it out of this enum. + # + # Singular `framework` and plural `frameworks` share the set deliberately: a + # page states one platform or several, never two different vocabularies. + framework: + type: string + enum: [ios, android, web, react-native, flutter, cordova, capacitor, kmp, + titanium, linux, net-ios, net-android, hosted] + frameworks: type: array minItems: 1 uniqueItems: true items: type: string - enum: [ios, android, web, react-native, flutter, cordova, capacitor, titanium, - linux, net-ios, net-android, hosted] + enum: [ios, android, web, react-native, flutter, cordova, capacitor, kmp, + titanium, linux, net-ios, net-android, hosted] version_status: type: string diff --git a/docs/hosted/express/configuration/custom-data-transfer/input-configuration.md b/docs/hosted/express/configuration/custom-data-transfer/input-configuration.md index b79644b6e..9ed15192d 100644 --- a/docs/hosted/express/configuration/custom-data-transfer/input-configuration.md +++ b/docs/hosted/express/configuration/custom-data-transfer/input-configuration.md @@ -1,6 +1,7 @@ --- description: "Define the input fields Custom Data Transfer collects during scanning in Scandit Express, and set each field's type and format." -framework: express +framework: hosted +product: express sidebar_label: Input Configuration keywords: - express diff --git a/docs/hosted/express/configuration/custom-data-transfer/output-configuration.md b/docs/hosted/express/configuration/custom-data-transfer/output-configuration.md index 6685af88f..d724835de 100644 --- a/docs/hosted/express/configuration/custom-data-transfer/output-configuration.md +++ b/docs/hosted/express/configuration/custom-data-transfer/output-configuration.md @@ -1,6 +1,7 @@ --- description: "Configure how Custom Data Transfer structures, transforms, and exports scanned data to external systems in Scandit Express." -framework: express +framework: hosted +product: express sidebar_label: Output Configuration keywords: - express diff --git a/docs/hosted/express/configuration/custom-data-transfer/overview.md b/docs/hosted/express/configuration/custom-data-transfer/overview.md index 6ffaf7f15..39c0c0377 100644 --- a/docs/hosted/express/configuration/custom-data-transfer/overview.md +++ b/docs/hosted/express/configuration/custom-data-transfer/overview.md @@ -1,6 +1,7 @@ --- description: "Import and export data in Scandit Express using a configurable CSV file or Google Sheet, aligning formats with your system without coding." -framework: express +framework: hosted +product: express sidebar_label: Overview keywords: - express diff --git a/docs/hosted/express/configuration/device-pairing/bluetooth-pairing.md b/docs/hosted/express/configuration/device-pairing/bluetooth-pairing.md index 0f00bf19d..dde57be82 100644 --- a/docs/hosted/express/configuration/device-pairing/bluetooth-pairing.md +++ b/docs/hosted/express/configuration/device-pairing/bluetooth-pairing.md @@ -1,6 +1,7 @@ --- description: "Set up Bluetooth device pairing in Scandit Express to send scanned data from one device to another." -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/configuration/device-pairing/device-pairing.md b/docs/hosted/express/configuration/device-pairing/device-pairing.md index e2c0e6a0c..77c384ac4 100644 --- a/docs/hosted/express/configuration/device-pairing/device-pairing.md +++ b/docs/hosted/express/configuration/device-pairing/device-pairing.md @@ -1,6 +1,7 @@ --- description: "Use Device Pairing in Scandit Express to transfer scanned barcode data in real time from a sender device to the receiver running your app." -framework: express +framework: hosted +product: express sidebar_label: Overview keywords: - express diff --git a/docs/hosted/express/configuration/device-pairing/online-connection.md b/docs/hosted/express/configuration/device-pairing/online-connection.md index a7e467d7c..0200c9079 100644 --- a/docs/hosted/express/configuration/device-pairing/online-connection.md +++ b/docs/hosted/express/configuration/device-pairing/online-connection.md @@ -1,6 +1,7 @@ --- description: "Set up online device pairing in Scandit Express to send scanned data to a web application." -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/configuration/express-find.md b/docs/hosted/express/configuration/express-find.md index c83a8ac11..5f9bfab77 100644 --- a/docs/hosted/express/configuration/express-find.md +++ b/docs/hosted/express/configuration/express-find.md @@ -1,7 +1,8 @@ --- description: "Find Items in Scandit Express, powered by MatrixScan Find, speeds up picking by scanning many items and highlighting the right ones in AR." -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/configuration/index.md b/docs/hosted/express/configuration/index.md index 31dc2449d..9a778e7f3 100644 --- a/docs/hosted/express/configuration/index.md +++ b/docs/hosted/express/configuration/index.md @@ -2,7 +2,8 @@ description: "Overview of the configuration options available in Scandit Express and how to set them up." toc_max_heading_level: 4 -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/configuration/inventory-count.md b/docs/hosted/express/configuration/inventory-count.md index 6d53502c8..becf26dff 100644 --- a/docs/hosted/express/configuration/inventory-count.md +++ b/docs/hosted/express/configuration/inventory-count.md @@ -1,6 +1,7 @@ --- description: "Use the Inventory Count mode in Scandit Express for fast, accurate stock counting." -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/configuration/scan-labels.md b/docs/hosted/express/configuration/scan-labels.md index 461ef8a28..10f4b20b1 100644 --- a/docs/hosted/express/configuration/scan-labels.md +++ b/docs/hosted/express/configuration/scan-labels.md @@ -1,6 +1,7 @@ --- description: "Scan Labels in Scandit Express, powered by Smart Label Capture, reads multiple barcodes and printed text on a label in one scan." -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/getting-started/installation.md b/docs/hosted/express/getting-started/installation.md index 31bef8f80..5f8657239 100644 --- a/docs/hosted/express/getting-started/installation.md +++ b/docs/hosted/express/getting-started/installation.md @@ -3,7 +3,8 @@ description: "Install Scandit Express on iOS or Android and activate it to add b sidebar_label: 'Installation' displayed_sidebar: expressSidebar sidebar_position: 1 -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/getting-started/rollout.md b/docs/hosted/express/getting-started/rollout.md index 563246a0a..03f64942c 100644 --- a/docs/hosted/express/getting-started/rollout.md +++ b/docs/hosted/express/getting-started/rollout.md @@ -4,7 +4,8 @@ description: "Roll out Scandit Express to production with a mobile device manage sidebar_label: 'Rollout via MDM/EMM' displayed_sidebar: expressSidebar sidebar_position: 2 -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/express/overview.md b/docs/hosted/express/overview.md index 8f6d5fe04..4e56cfb6d 100644 --- a/docs/hosted/express/overview.md +++ b/docs/hosted/express/overview.md @@ -5,7 +5,8 @@ sidebar_position: 1 sidebar_label: 'Overview' title: 'Scandit Express' displayed_sidebar: expressSidebar -framework: express +framework: hosted +product: express keywords: - express --- diff --git a/docs/hosted/id-bolt/advanced.md b/docs/hosted/id-bolt/advanced.md index 4da47a46f..4602b1ddf 100644 --- a/docs/hosted/id-bolt/advanced.md +++ b/docs/hosted/id-bolt/advanced.md @@ -5,7 +5,8 @@ sidebar_label: "Advanced Options" title: "Advanced Options" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - advanced diff --git a/docs/hosted/id-bolt/api-overview.md b/docs/hosted/id-bolt/api-overview.md index 1281e6312..6f426e3a6 100644 --- a/docs/hosted/id-bolt/api-overview.md +++ b/docs/hosted/id-bolt/api-overview.md @@ -5,7 +5,8 @@ sidebar_label: "API Overview" title: "API Overview" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt --- diff --git a/docs/hosted/id-bolt/callbacks.md b/docs/hosted/id-bolt/callbacks.md index 3df255e78..265ba2c58 100644 --- a/docs/hosted/id-bolt/callbacks.md +++ b/docs/hosted/id-bolt/callbacks.md @@ -5,7 +5,8 @@ sidebar_label: "Callbacks" title: "Callbacks" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - callbacks diff --git a/docs/hosted/id-bolt/data-handling.md b/docs/hosted/id-bolt/data-handling.md index b82a4553e..c1bf812f4 100644 --- a/docs/hosted/id-bolt/data-handling.md +++ b/docs/hosted/id-bolt/data-handling.md @@ -5,7 +5,8 @@ sidebar_label: "Data Handling" title: "Data Handling" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - data handling diff --git a/docs/hosted/id-bolt/document-selection.md b/docs/hosted/id-bolt/document-selection.md index 9d08fb11a..032a8b157 100644 --- a/docs/hosted/id-bolt/document-selection.md +++ b/docs/hosted/id-bolt/document-selection.md @@ -5,7 +5,8 @@ sidebar_label: "Document Selection" title: "Document Selection" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - document selection diff --git a/docs/hosted/id-bolt/getting-started.md b/docs/hosted/id-bolt/getting-started.md index 3b9c70460..d886d459d 100644 --- a/docs/hosted/id-bolt/getting-started.md +++ b/docs/hosted/id-bolt/getting-started.md @@ -5,7 +5,8 @@ sidebar_label: "Getting Started" title: "Getting Started" hide_title: false displayed_sidebar: boltSidebar -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt --- diff --git a/docs/hosted/id-bolt/overview.md b/docs/hosted/id-bolt/overview.md index dbfad5ab9..0deda5a56 100644 --- a/docs/hosted/id-bolt/overview.md +++ b/docs/hosted/id-bolt/overview.md @@ -5,7 +5,8 @@ sidebar_position: 1 sidebar_label: "Overview" title: "ID Bolt" displayed_sidebar: boltSidebar -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt --- diff --git a/docs/hosted/id-bolt/release-notes.md b/docs/hosted/id-bolt/release-notes.md index c3f9a15d5..ce84a952f 100644 --- a/docs/hosted/id-bolt/release-notes.md +++ b/docs/hosted/id-bolt/release-notes.md @@ -2,7 +2,8 @@ description: "Release notes for ID Bolt: new features, changes, and fixes by date." toc_max_heading_level: 3 displayed_sidebar: boltSidebar -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt hide_title: true diff --git a/docs/hosted/id-bolt/text-overrides.md b/docs/hosted/id-bolt/text-overrides.md index dfdae7735..a6d15db3b 100644 --- a/docs/hosted/id-bolt/text-overrides.md +++ b/docs/hosted/id-bolt/text-overrides.md @@ -5,7 +5,8 @@ sidebar_label: "Text Overrides" title: "Text Overrides" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - customization diff --git a/docs/hosted/id-bolt/theming.md b/docs/hosted/id-bolt/theming.md index f16294afc..6348acef0 100644 --- a/docs/hosted/id-bolt/theming.md +++ b/docs/hosted/id-bolt/theming.md @@ -5,7 +5,8 @@ sidebar_label: "Theming" title: "Theming" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - theming diff --git a/docs/hosted/id-bolt/validators.md b/docs/hosted/id-bolt/validators.md index 6764ae008..a78b46253 100644 --- a/docs/hosted/id-bolt/validators.md +++ b/docs/hosted/id-bolt/validators.md @@ -5,7 +5,8 @@ sidebar_label: "Validators" title: "Validators" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - validators diff --git a/docs/hosted/id-bolt/workflow.md b/docs/hosted/id-bolt/workflow.md index bec467005..f2ebb5c2c 100644 --- a/docs/hosted/id-bolt/workflow.md +++ b/docs/hosted/id-bolt/workflow.md @@ -5,7 +5,8 @@ sidebar_label: "Workflow Options" title: "Workflow & Scanner Options" displayed_sidebar: boltSidebar toc_max_heading_level: 4 -framework: bolt +framework: hosted +product: id-bolt keywords: - bolt - workflow diff --git a/docs/sdks/net/android/add-sdk.md b/docs/sdks/net/android/add-sdk.md index 09e21e216..be6caefd5 100644 --- a/docs/sdks/net/android/add-sdk.md +++ b/docs/sdks/net/android/add-sdk.md @@ -3,7 +3,7 @@ description: "Add the Scandit Data Capture SDK to your .NET Android project: dep sidebar_position: 1 toc_max_heading_level: 4 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/ai-powered-barcode-scanning.md b/docs/sdks/net/android/ai-powered-barcode-scanning.md index a1f870996..a31f738ef 100644 --- a/docs/sdks/net/android/ai-powered-barcode-scanning.md +++ b/docs/sdks/net/android/ai-powered-barcode-scanning.md @@ -1,7 +1,7 @@ --- description: "import AIPoweredBarcodeScanning from '../../../partials/_ai-powered-barcode-scanning.mdx';" toc_max_heading_level: 4 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/barcode-capture/configure-barcode-symbologies.md b/docs/sdks/net/android/barcode-capture/configure-barcode-symbologies.md index 3f069dbfc..1d8561d15 100644 --- a/docs/sdks/net/android/barcode-capture/configure-barcode-symbologies.md +++ b/docs/sdks/net/android/barcode-capture/configure-barcode-symbologies.md @@ -2,7 +2,7 @@ description: "Enable and configure barcode symbologies for Barcode Capture on .NET Android." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/barcode-capture/get-started.md b/docs/sdks/net/android/barcode-capture/get-started.md index 3edd00a2a..302ca319a 100644 --- a/docs/sdks/net/android/barcode-capture/get-started.md +++ b/docs/sdks/net/android/barcode-capture/get-started.md @@ -2,7 +2,7 @@ description: "Add Barcode Capture to your .NET Android app: configure the mode, set up the camera and view, and handle scanned barcodes." sidebar_position: 2 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/barcode-selection/get-started.md b/docs/sdks/net/android/barcode-selection/get-started.md index 5a397349e..113e69919 100644 --- a/docs/sdks/net/android/barcode-selection/get-started.md +++ b/docs/sdks/net/android/barcode-selection/get-started.md @@ -2,7 +2,7 @@ description: "Add Barcode Selection to your .NET Android app so users can tap or aim to select a single barcode among many." sidebar_position: 2 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/barcode-selection/intro.md b/docs/sdks/net/android/barcode-selection/intro.md index c9425a30f..8c3d5101a 100644 --- a/docs/sdks/net/android/barcode-selection/intro.md +++ b/docs/sdks/net/android/barcode-selection/intro.md @@ -2,7 +2,7 @@ description: "Barcode Selection lets users tap or aim to pick one barcode among many in your .NET Android app." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/batch-scanning.md b/docs/sdks/net/android/batch-scanning.md index 951e7f210..f00602635 100644 --- a/docs/sdks/net/android/batch-scanning.md +++ b/docs/sdks/net/android/batch-scanning.md @@ -1,7 +1,7 @@ --- description: "Scan and process many barcodes in one session in your .NET Android app with the Scandit Data Capture SDK." pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/id-capture/advanced.md b/docs/sdks/net/android/id-capture/advanced.md index 3427969a3..a989225cb 100644 --- a/docs/sdks/net/android/id-capture/advanced.md +++ b/docs/sdks/net/android/id-capture/advanced.md @@ -2,7 +2,7 @@ description: "Advanced ID Capture settings for .NET Android: configure document types, extracted fields, and verification." sidebar_position: 4 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/id-capture/get-started.md b/docs/sdks/net/android/id-capture/get-started.md index 891379e6b..18cb3cfa9 100644 --- a/docs/sdks/net/android/id-capture/get-started.md +++ b/docs/sdks/net/android/id-capture/get-started.md @@ -1,7 +1,7 @@ --- description: "Add ID Capture to your .NET Android app to scan and extract data from IDs such as driver's licenses, passports, and ID cards." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/id-capture/intro.md b/docs/sdks/net/android/id-capture/intro.md index b80b187ad..93d74fee2 100644 --- a/docs/sdks/net/android/id-capture/intro.md +++ b/docs/sdks/net/android/id-capture/intro.md @@ -5,7 +5,7 @@ title: About ID Capture sidebar_position: 1 toc_max_heading_level: 4 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/id-capture/supported-documents.md b/docs/sdks/net/android/id-capture/supported-documents.md index bede24184..c71bbf325 100644 --- a/docs/sdks/net/android/id-capture/supported-documents.md +++ b/docs/sdks/net/android/id-capture/supported-documents.md @@ -6,7 +6,7 @@ hide_title: true toc_min_heading_level: 2 toc_max_heading_level: 4 sidebar_position: 3 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-ar/get-started.md b/docs/sdks/net/android/matrixscan-ar/get-started.md index 968ddcdfb..c3e34762a 100644 --- a/docs/sdks/net/android/matrixscan-ar/get-started.md +++ b/docs/sdks/net/android/matrixscan-ar/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan AR to your .NET Android app: set up the view and the overlays that guide users to the right items." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-ar/intro.md b/docs/sdks/net/android/matrixscan-ar/intro.md index 9722380f7..78ab765fd 100644 --- a/docs/sdks/net/android/matrixscan-ar/intro.md +++ b/docs/sdks/net/android/matrixscan-ar/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan AR adds augmented-reality overlays that highlight barcodes and show extra information in your .NET Android app." displayed_sidebar: netIosSidebar pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-count/advanced.md b/docs/sdks/net/android/matrixscan-count/advanced.md index f04ab1aad..feb1aa806 100644 --- a/docs/sdks/net/android/matrixscan-count/advanced.md +++ b/docs/sdks/net/android/matrixscan-count/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Count settings for .NET Android to tune counting accuracy, performance, and the user experience." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-count/get-started.md b/docs/sdks/net/android/matrixscan-count/get-started.md index d6ff7eadc..0ce8f3234 100644 --- a/docs/sdks/net/android/matrixscan-count/get-started.md +++ b/docs/sdks/net/android/matrixscan-count/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Count to your .NET Android app: set up the mode, scan and count multiple barcodes, and handle the results." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-count/intro.md b/docs/sdks/net/android/matrixscan-count/intro.md index bebf4b152..6c163ef44 100644 --- a/docs/sdks/net/android/matrixscan-count/intro.md +++ b/docs/sdks/net/android/matrixscan-count/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Count scans and counts many barcodes at once in your .NET Android app." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-find/advanced.md b/docs/sdks/net/android/matrixscan-find/advanced.md index 1414b202d..f6b76905a 100644 --- a/docs/sdks/net/android/matrixscan-find/advanced.md +++ b/docs/sdks/net/android/matrixscan-find/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Find settings for .NET Android: customize matching, overlays, and search behavior." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-find/get-started.md b/docs/sdks/net/android/matrixscan-find/get-started.md index 0114b2313..6904d2d80 100644 --- a/docs/sdks/net/android/matrixscan-find/get-started.md +++ b/docs/sdks/net/android/matrixscan-find/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Find to your .NET Android app to search for and highlight specific items with the camera." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-find/intro.md b/docs/sdks/net/android/matrixscan-find/intro.md index 63079ef67..2288a3666 100644 --- a/docs/sdks/net/android/matrixscan-find/intro.md +++ b/docs/sdks/net/android/matrixscan-find/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Find helps users locate specific items in your .NET Android app by highlighting matches in the camera feed." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-pick/advanced.md b/docs/sdks/net/android/matrixscan-pick/advanced.md index affdc4c26..046af10ef 100644 --- a/docs/sdks/net/android/matrixscan-pick/advanced.md +++ b/docs/sdks/net/android/matrixscan-pick/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Pick settings for .NET Android: customize picking logic, overlays, and workflows." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-pick/get-started.md b/docs/sdks/net/android/matrixscan-pick/get-started.md index c4427dcfb..4b73c2327 100644 --- a/docs/sdks/net/android/matrixscan-pick/get-started.md +++ b/docs/sdks/net/android/matrixscan-pick/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Pick to your .NET Android app to guide users through order picking with AR cues." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan-pick/intro.md b/docs/sdks/net/android/matrixscan-pick/intro.md index 646aae7e2..283657703 100644 --- a/docs/sdks/net/android/matrixscan-pick/intro.md +++ b/docs/sdks/net/android/matrixscan-pick/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Pick guides order picking in your .NET Android app with on-screen AR cues for the items to pick." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan/advanced.md b/docs/sdks/net/android/matrixscan/advanced.md index 9b084e231..f867a6f92 100644 --- a/docs/sdks/net/android/matrixscan/advanced.md +++ b/docs/sdks/net/android/matrixscan/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan settings for .NET Android: tune tracking, overlays, and scanning performance." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid diff --git a/docs/sdks/net/android/matrixscan/get-started.md b/docs/sdks/net/android/matrixscan/get-started.md index d805d9c40..d8af7e29a 100644 --- a/docs/sdks/net/android/matrixscan/get-started.md +++ b/docs/sdks/net/android/matrixscan/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan to your .NET Android app to detect, track, and highlight multiple barcodes in the same frame." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/matrixscan/intro.md b/docs/sdks/net/android/matrixscan/intro.md index da33c690f..32fd7cc4d 100644 --- a/docs/sdks/net/android/matrixscan/intro.md +++ b/docs/sdks/net/android/matrixscan/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan detects, tracks, and highlights multiple barcodes in the same frame in your .NET Android app." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/parser/get-started.md b/docs/sdks/net/android/parser/get-started.md index 589016758..c9f39926c 100644 --- a/docs/sdks/net/android/parser/get-started.md +++ b/docs/sdks/net/android/parser/get-started.md @@ -3,7 +3,7 @@ description: "Use the Scandit parser in your .NET Android app to turn barcode da sidebar_position: 2 pagination_prev: null pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/release-notes.md b/docs/sdks/net/android/release-notes.md index 6ebcd675d..893499f94 100644 --- a/docs/sdks/net/android/release-notes.md +++ b/docs/sdks/net/android/release-notes.md @@ -5,7 +5,7 @@ displayed_sidebar: netAndroidSidebar hide_title: true title: Release Notes pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/single-scanning.md b/docs/sdks/net/android/single-scanning.md index b76eeb751..e6bd4c4eb 100644 --- a/docs/sdks/net/android/single-scanning.md +++ b/docs/sdks/net/android/single-scanning.md @@ -2,7 +2,7 @@ description: "Scan a single barcode at a time in your .NET Android app with the Scandit Data Capture SDK." toc_max_heading_level: 4 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/sparkscan/advanced.md b/docs/sdks/net/android/sparkscan/advanced.md index f1a3c90b7..34ba5085a 100644 --- a/docs/sdks/net/android/sparkscan/advanced.md +++ b/docs/sdks/net/android/sparkscan/advanced.md @@ -2,7 +2,7 @@ description: "Advanced SparkScan settings for .NET Android: customize the UI, scanning behavior, and feedback beyond the defaults." sidebar_position: 3 pagination_next: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/sparkscan/get-started.md b/docs/sdks/net/android/sparkscan/get-started.md index 179a89bfe..7aab41b2f 100644 --- a/docs/sdks/net/android/sparkscan/get-started.md +++ b/docs/sdks/net/android/sparkscan/get-started.md @@ -1,7 +1,7 @@ --- description: "Add SparkScan to your .NET Android app: create a data capture context, configure the mode, add the view, and handle scanned barcodes." sidebar_position: 2 -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/android/sparkscan/intro.md b/docs/sdks/net/android/sparkscan/intro.md index e6e48d0b9..b9d873799 100644 --- a/docs/sdks/net/android/sparkscan/intro.md +++ b/docs/sdks/net/android/sparkscan/intro.md @@ -2,7 +2,7 @@ description: "SparkScan is a prebuilt smartphone scanning UI for fast, ergonomic barcode scanning that drops on top of any .NET Android app." sidebar_position: 1 pagination_prev: null -framework: netAndroid +framework: net-android keywords: - netAndroid --- diff --git a/docs/sdks/net/ios/add-sdk.md b/docs/sdks/net/ios/add-sdk.md index 44dad6c57..8b0f652bc 100644 --- a/docs/sdks/net/ios/add-sdk.md +++ b/docs/sdks/net/ios/add-sdk.md @@ -3,7 +3,7 @@ description: "Add the Scandit Data Capture SDK to your .NET iOS project: depende sidebar_position: 1 toc_max_heading_level: 4 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/ai-powered-barcode-scanning.md b/docs/sdks/net/ios/ai-powered-barcode-scanning.md index cff13dce6..5d508e8e8 100644 --- a/docs/sdks/net/ios/ai-powered-barcode-scanning.md +++ b/docs/sdks/net/ios/ai-powered-barcode-scanning.md @@ -1,7 +1,7 @@ --- description: "import AIPoweredBarcodeScanning from '../../../partials/_ai-powered-barcode-scanning.mdx';" toc_max_heading_level: 4 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/barcode-capture/configure-barcode-symbologies.md b/docs/sdks/net/ios/barcode-capture/configure-barcode-symbologies.md index 4c139ba71..e7aac752b 100644 --- a/docs/sdks/net/ios/barcode-capture/configure-barcode-symbologies.md +++ b/docs/sdks/net/ios/barcode-capture/configure-barcode-symbologies.md @@ -2,7 +2,7 @@ description: "Enable and configure barcode symbologies for Barcode Capture on .NET iOS." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/barcode-capture/get-started.md b/docs/sdks/net/ios/barcode-capture/get-started.md index 01f6d4e42..fb2548638 100644 --- a/docs/sdks/net/ios/barcode-capture/get-started.md +++ b/docs/sdks/net/ios/barcode-capture/get-started.md @@ -2,7 +2,7 @@ description: "Add Barcode Capture to your .NET iOS app: configure the mode, set up the camera and view, and handle scanned barcodes." sidebar_position: 2 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/barcode-selection/get-started.md b/docs/sdks/net/ios/barcode-selection/get-started.md index d00b80c27..a38152ae6 100644 --- a/docs/sdks/net/ios/barcode-selection/get-started.md +++ b/docs/sdks/net/ios/barcode-selection/get-started.md @@ -2,7 +2,7 @@ description: "Add Barcode Selection to your .NET iOS app so users can tap or aim to select a single barcode among many." sidebar_position: 2 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/barcode-selection/intro.md b/docs/sdks/net/ios/barcode-selection/intro.md index 583e41b0c..46c1f3616 100644 --- a/docs/sdks/net/ios/barcode-selection/intro.md +++ b/docs/sdks/net/ios/barcode-selection/intro.md @@ -2,7 +2,7 @@ description: "Barcode Selection lets users tap or aim to pick one barcode among many in your .NET iOS app." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/batch-scanning.md b/docs/sdks/net/ios/batch-scanning.md index e137ef4e1..22da320d5 100644 --- a/docs/sdks/net/ios/batch-scanning.md +++ b/docs/sdks/net/ios/batch-scanning.md @@ -1,7 +1,7 @@ --- description: "Scan and process many barcodes in one session in your .NET iOS app with the Scandit Data Capture SDK." pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/id-capture/advanced.md b/docs/sdks/net/ios/id-capture/advanced.md index 09f9a0f7f..0d1f34952 100644 --- a/docs/sdks/net/ios/id-capture/advanced.md +++ b/docs/sdks/net/ios/id-capture/advanced.md @@ -2,7 +2,7 @@ description: "Advanced ID Capture settings for .NET iOS: configure document types, extracted fields, and verification." sidebar_position: 4 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/id-capture/get-started.md b/docs/sdks/net/ios/id-capture/get-started.md index 3817a37e0..a217ddb04 100644 --- a/docs/sdks/net/ios/id-capture/get-started.md +++ b/docs/sdks/net/ios/id-capture/get-started.md @@ -1,7 +1,7 @@ --- description: "Add ID Capture to your .NET iOS app to scan and extract data from IDs such as driver's licenses, passports, and ID cards." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/id-capture/intro.md b/docs/sdks/net/ios/id-capture/intro.md index 89a749028..939aa3694 100644 --- a/docs/sdks/net/ios/id-capture/intro.md +++ b/docs/sdks/net/ios/id-capture/intro.md @@ -5,7 +5,7 @@ title: About ID Capture sidebar_position: 1 toc_max_heading_level: 4 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/id-capture/supported-documents.md b/docs/sdks/net/ios/id-capture/supported-documents.md index e9c45de56..12326edbf 100644 --- a/docs/sdks/net/ios/id-capture/supported-documents.md +++ b/docs/sdks/net/ios/id-capture/supported-documents.md @@ -6,7 +6,7 @@ hide_title: true toc_min_heading_level: 2 toc_max_heading_level: 4 sidebar_position: 3 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-ar/get-started.md b/docs/sdks/net/ios/matrixscan-ar/get-started.md index e03da39ba..90305bc79 100644 --- a/docs/sdks/net/ios/matrixscan-ar/get-started.md +++ b/docs/sdks/net/ios/matrixscan-ar/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan AR to your .NET iOS app: set up the view and the overlays that guide users to the right items while scanning." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-ar/intro.md b/docs/sdks/net/ios/matrixscan-ar/intro.md index e74a49871..32131b482 100644 --- a/docs/sdks/net/ios/matrixscan-ar/intro.md +++ b/docs/sdks/net/ios/matrixscan-ar/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan AR adds augmented-reality overlays that highlight barcodes and show extra information in your .NET iOS app." displayed_sidebar: netIosSidebar pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-count/advanced.md b/docs/sdks/net/ios/matrixscan-count/advanced.md index aee4d7583..a936d066d 100644 --- a/docs/sdks/net/ios/matrixscan-count/advanced.md +++ b/docs/sdks/net/ios/matrixscan-count/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Count settings for .NET iOS to tune counting accuracy, performance, and the user experience." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-count/get-started.md b/docs/sdks/net/ios/matrixscan-count/get-started.md index 7579fc49b..e4514f870 100644 --- a/docs/sdks/net/ios/matrixscan-count/get-started.md +++ b/docs/sdks/net/ios/matrixscan-count/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Count to your .NET iOS app: set up the mode, scan and count multiple barcodes, and handle the results." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-count/intro.md b/docs/sdks/net/ios/matrixscan-count/intro.md index f9b529e2e..96fcc20c7 100644 --- a/docs/sdks/net/ios/matrixscan-count/intro.md +++ b/docs/sdks/net/ios/matrixscan-count/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Count scans and counts many barcodes at once in your .NET iOS app." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-find/advanced.md b/docs/sdks/net/ios/matrixscan-find/advanced.md index c6978574b..533886992 100644 --- a/docs/sdks/net/ios/matrixscan-find/advanced.md +++ b/docs/sdks/net/ios/matrixscan-find/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Find settings for .NET iOS: customize matching, overlays, and search behavior." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-find/get-started.md b/docs/sdks/net/ios/matrixscan-find/get-started.md index 258d631a3..49dd94fdb 100644 --- a/docs/sdks/net/ios/matrixscan-find/get-started.md +++ b/docs/sdks/net/ios/matrixscan-find/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Find to your .NET iOS app to search for and highlight specific items with the camera." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-find/intro.md b/docs/sdks/net/ios/matrixscan-find/intro.md index 0791d8af0..944a279e8 100644 --- a/docs/sdks/net/ios/matrixscan-find/intro.md +++ b/docs/sdks/net/ios/matrixscan-find/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Find helps users locate specific items in your .NET iOS app by highlighting matches in the camera feed." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-pick/advanced.md b/docs/sdks/net/ios/matrixscan-pick/advanced.md index 75091c07e..abe132765 100644 --- a/docs/sdks/net/ios/matrixscan-pick/advanced.md +++ b/docs/sdks/net/ios/matrixscan-pick/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan Pick settings for .NET iOS: customize picking logic, overlays, and workflows." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-pick/get-started.md b/docs/sdks/net/ios/matrixscan-pick/get-started.md index e0a1d7adb..24669d833 100644 --- a/docs/sdks/net/ios/matrixscan-pick/get-started.md +++ b/docs/sdks/net/ios/matrixscan-pick/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan Pick to your .NET iOS app to guide users through order picking with AR cues." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan-pick/intro.md b/docs/sdks/net/ios/matrixscan-pick/intro.md index b8a7f9712..5921c6ddb 100644 --- a/docs/sdks/net/ios/matrixscan-pick/intro.md +++ b/docs/sdks/net/ios/matrixscan-pick/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan Pick guides order picking in your .NET iOS app with on-screen AR cues for the items to pick." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan/advanced.md b/docs/sdks/net/ios/matrixscan/advanced.md index 5a246c1ba..dd3405f5d 100644 --- a/docs/sdks/net/ios/matrixscan/advanced.md +++ b/docs/sdks/net/ios/matrixscan/advanced.md @@ -2,7 +2,7 @@ description: "Advanced MatrixScan settings for .NET iOS: tune tracking, overlays, and scanning performance." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos diff --git a/docs/sdks/net/ios/matrixscan/get-started.md b/docs/sdks/net/ios/matrixscan/get-started.md index 64de292bd..32dbac3dd 100644 --- a/docs/sdks/net/ios/matrixscan/get-started.md +++ b/docs/sdks/net/ios/matrixscan/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan to your .NET iOS app to detect, track, and highlight multiple barcodes in the same frame." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/matrixscan/intro.md b/docs/sdks/net/ios/matrixscan/intro.md index 0d72393a4..21bf1df85 100644 --- a/docs/sdks/net/ios/matrixscan/intro.md +++ b/docs/sdks/net/ios/matrixscan/intro.md @@ -2,7 +2,7 @@ description: "MatrixScan detects, tracks, and highlights multiple barcodes in the same frame in your .NET iOS app." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/parser/get-started.md b/docs/sdks/net/ios/parser/get-started.md index 08fc5e455..1801a70f3 100644 --- a/docs/sdks/net/ios/parser/get-started.md +++ b/docs/sdks/net/ios/parser/get-started.md @@ -3,7 +3,7 @@ description: "Use the Scandit parser in your .NET iOS app to turn barcode data s sidebar_position: 2 pagination_prev: null pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/release-notes.md b/docs/sdks/net/ios/release-notes.md index 61a8bfc75..a14ca5223 100644 --- a/docs/sdks/net/ios/release-notes.md +++ b/docs/sdks/net/ios/release-notes.md @@ -5,7 +5,7 @@ displayed_sidebar: netIosSidebar hide_title: true title: Release Notes pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/single-scanning.md b/docs/sdks/net/ios/single-scanning.md index 22f337184..23b5bd96f 100644 --- a/docs/sdks/net/ios/single-scanning.md +++ b/docs/sdks/net/ios/single-scanning.md @@ -2,7 +2,7 @@ description: "Scan a single barcode at a time in your .NET iOS app with the Scandit Data Capture SDK." toc_max_heading_level: 4 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/sparkscan/advanced.md b/docs/sdks/net/ios/sparkscan/advanced.md index fe265e530..506e66859 100644 --- a/docs/sdks/net/ios/sparkscan/advanced.md +++ b/docs/sdks/net/ios/sparkscan/advanced.md @@ -2,7 +2,7 @@ description: "Advanced SparkScan settings for .NET iOS: customize the UI, scanning behavior, and feedback beyond the defaults." sidebar_position: 3 pagination_next: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/sparkscan/get-started.md b/docs/sdks/net/ios/sparkscan/get-started.md index d316aeb21..1539abf94 100644 --- a/docs/sdks/net/ios/sparkscan/get-started.md +++ b/docs/sdks/net/ios/sparkscan/get-started.md @@ -1,7 +1,7 @@ --- description: "Add SparkScan to your .NET iOS app: create a data capture context, configure the mode, add the view, and handle scanned barcodes." sidebar_position: 2 -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/net/ios/sparkscan/intro.md b/docs/sdks/net/ios/sparkscan/intro.md index ccfdde90a..39f9bcf29 100644 --- a/docs/sdks/net/ios/sparkscan/intro.md +++ b/docs/sdks/net/ios/sparkscan/intro.md @@ -2,7 +2,7 @@ description: "SparkScan is a prebuilt scanning UI for fast, ergonomic barcode scanning that drops on top of any .NET iOS app." sidebar_position: 1 pagination_prev: null -framework: netIos +framework: net-ios keywords: - netIos --- diff --git a/docs/sdks/react-native/add-sdk.md b/docs/sdks/react-native/add-sdk.md index 1be838094..fedea715c 100644 --- a/docs/sdks/react-native/add-sdk.md +++ b/docs/sdks/react-native/add-sdk.md @@ -3,7 +3,7 @@ description: "Add the Scandit Data Capture SDK to your React Native project: dep sidebar_position: 1 toc_max_heading_level: 4 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/barcode-capture/configure-barcode-symbologies.md b/docs/sdks/react-native/barcode-capture/configure-barcode-symbologies.md index 0d9b45a95..5aaaf4879 100644 --- a/docs/sdks/react-native/barcode-capture/configure-barcode-symbologies.md +++ b/docs/sdks/react-native/barcode-capture/configure-barcode-symbologies.md @@ -3,7 +3,7 @@ description: "Learn about the available symbologies and the corresponding config sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/barcode-capture/get-started.md b/docs/sdks/react-native/barcode-capture/get-started.md index cf2b565e9..ceee63c01 100644 --- a/docs/sdks/react-native/barcode-capture/get-started.md +++ b/docs/sdks/react-native/barcode-capture/get-started.md @@ -2,7 +2,7 @@ description: "Add Barcode Capture to your React Native app: configure the mode, set up the camera and view, and handle scanned barcodes." sidebar_position: 2 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/barcode-selection/get-started.md b/docs/sdks/react-native/barcode-selection/get-started.md index 5c9a2b1ce..07063b689 100644 --- a/docs/sdks/react-native/barcode-selection/get-started.md +++ b/docs/sdks/react-native/barcode-selection/get-started.md @@ -3,7 +3,7 @@ description: "Add Barcode Selection to your React Native app so users can tap or sidebar_position: 2 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/barcode-selection/intro.md b/docs/sdks/react-native/barcode-selection/intro.md index cf82c55cc..4da37cb3a 100644 --- a/docs/sdks/react-native/barcode-selection/intro.md +++ b/docs/sdks/react-native/barcode-selection/intro.md @@ -3,7 +3,7 @@ description: "Barcode Selection lets users tap or aim to pick one barcode among sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/batch-scanning.md b/docs/sdks/react-native/batch-scanning.md index 8c87fa671..c0b6d7971 100644 --- a/docs/sdks/react-native/batch-scanning.md +++ b/docs/sdks/react-native/batch-scanning.md @@ -4,7 +4,7 @@ toc_max_heading_level: 4 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/id-capture/advanced.md b/docs/sdks/react-native/id-capture/advanced.md index 9d8863d8f..935a15fe3 100644 --- a/docs/sdks/react-native/id-capture/advanced.md +++ b/docs/sdks/react-native/id-capture/advanced.md @@ -3,7 +3,7 @@ description: "Advanced ID Capture settings for React Native: configure document sidebar_position: 4 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/id-capture/get-started.md b/docs/sdks/react-native/id-capture/get-started.md index 486f0cb09..2ce9897a9 100644 --- a/docs/sdks/react-native/id-capture/get-started.md +++ b/docs/sdks/react-native/id-capture/get-started.md @@ -2,7 +2,7 @@ description: "Add ID Capture to your React Native app to scan and extract data from IDs such as driver's licenses, passports, and ID cards." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/id-capture/intro.md b/docs/sdks/react-native/id-capture/intro.md index 02a2c044c..effc62f62 100644 --- a/docs/sdks/react-native/id-capture/intro.md +++ b/docs/sdks/react-native/id-capture/intro.md @@ -6,7 +6,7 @@ title: About ID Capture sidebar_position: 1 toc_max_heading_level: 4 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/label-capture/label-definitions.md b/docs/sdks/react-native/label-capture/label-definitions.md index 429bca565..8f6c9fe7d 100644 --- a/docs/sdks/react-native/label-capture/label-definitions.md +++ b/docs/sdks/react-native/label-capture/label-definitions.md @@ -1,7 +1,7 @@ --- description: "Define label layouts for Smart Label Capture on React Native: the barcodes and text fields to extract." -framework: react +framework: react-native toc_max_heading_level: 4 keywords: - react diff --git a/docs/sdks/react-native/matrixscan-ar/get-started.md b/docs/sdks/react-native/matrixscan-ar/get-started.md index b4973f45a..de1386742 100644 --- a/docs/sdks/react-native/matrixscan-ar/get-started.md +++ b/docs/sdks/react-native/matrixscan-ar/get-started.md @@ -2,7 +2,7 @@ description: "Add MatrixScan AR to your React Native app: set up the view and the overlays that guide users to the right items." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-ar/intro.md b/docs/sdks/react-native/matrixscan-ar/intro.md index cc031bfbf..7613f8c43 100644 --- a/docs/sdks/react-native/matrixscan-ar/intro.md +++ b/docs/sdks/react-native/matrixscan-ar/intro.md @@ -3,7 +3,7 @@ description: "MatrixScan AR adds augmented-reality overlays that highlight barco sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-count/advanced.md b/docs/sdks/react-native/matrixscan-count/advanced.md index 1b7828c3a..110e6c412 100644 --- a/docs/sdks/react-native/matrixscan-count/advanced.md +++ b/docs/sdks/react-native/matrixscan-count/advanced.md @@ -3,7 +3,7 @@ description: "Advanced MatrixScan Count settings for React Native to tune counti sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-count/get-started.md b/docs/sdks/react-native/matrixscan-count/get-started.md index 41ff85ae0..846d7ba43 100644 --- a/docs/sdks/react-native/matrixscan-count/get-started.md +++ b/docs/sdks/react-native/matrixscan-count/get-started.md @@ -2,7 +2,7 @@ description: "Add MatrixScan Count to your React Native app: set up the mode, scan and count multiple barcodes, and handle the results." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-count/intro.md b/docs/sdks/react-native/matrixscan-count/intro.md index 1fb9378b2..9725735c2 100644 --- a/docs/sdks/react-native/matrixscan-count/intro.md +++ b/docs/sdks/react-native/matrixscan-count/intro.md @@ -3,7 +3,7 @@ description: "MatrixScan Count scans and counts many barcodes at once in your Re sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-find/advanced.md b/docs/sdks/react-native/matrixscan-find/advanced.md index b6d861fda..78d238475 100644 --- a/docs/sdks/react-native/matrixscan-find/advanced.md +++ b/docs/sdks/react-native/matrixscan-find/advanced.md @@ -3,7 +3,7 @@ description: "Advanced MatrixScan Find settings for React Native: customize matc sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-find/get-started.md b/docs/sdks/react-native/matrixscan-find/get-started.md index 047f413bb..ca291d386 100644 --- a/docs/sdks/react-native/matrixscan-find/get-started.md +++ b/docs/sdks/react-native/matrixscan-find/get-started.md @@ -2,7 +2,7 @@ description: "Add MatrixScan Find to your React Native app to search for and highlight specific items with the camera." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-find/intro.md b/docs/sdks/react-native/matrixscan-find/intro.md index a2aa33e5d..1084a3a3f 100644 --- a/docs/sdks/react-native/matrixscan-find/intro.md +++ b/docs/sdks/react-native/matrixscan-find/intro.md @@ -3,7 +3,7 @@ description: "MatrixScan Find helps users locate specific items in your React Na sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-pick/advanced.md b/docs/sdks/react-native/matrixscan-pick/advanced.md index 952ee0656..0a8ed9e93 100644 --- a/docs/sdks/react-native/matrixscan-pick/advanced.md +++ b/docs/sdks/react-native/matrixscan-pick/advanced.md @@ -3,7 +3,7 @@ description: "Advanced MatrixScan Pick settings for React Native: customize pick sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-pick/get-started.md b/docs/sdks/react-native/matrixscan-pick/get-started.md index f5c6a92e3..46ddd473c 100644 --- a/docs/sdks/react-native/matrixscan-pick/get-started.md +++ b/docs/sdks/react-native/matrixscan-pick/get-started.md @@ -2,7 +2,7 @@ description: "Add MatrixScan Pick to your React Native app to guide users through order picking with AR cues." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan-pick/intro.md b/docs/sdks/react-native/matrixscan-pick/intro.md index 1d3388827..fc06cac40 100644 --- a/docs/sdks/react-native/matrixscan-pick/intro.md +++ b/docs/sdks/react-native/matrixscan-pick/intro.md @@ -3,7 +3,7 @@ description: "MatrixScan Pick guides order picking in your React Native app with sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/matrixscan/advanced.md b/docs/sdks/react-native/matrixscan/advanced.md index 41451a77a..afd691c01 100644 --- a/docs/sdks/react-native/matrixscan/advanced.md +++ b/docs/sdks/react-native/matrixscan/advanced.md @@ -3,7 +3,7 @@ description: "Advanced MatrixScan settings for React Native: tune tracking, over sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react diff --git a/docs/sdks/react-native/matrixscan/get-started.md b/docs/sdks/react-native/matrixscan/get-started.md index 7c552551b..14ede8e82 100644 --- a/docs/sdks/react-native/matrixscan/get-started.md +++ b/docs/sdks/react-native/matrixscan/get-started.md @@ -1,7 +1,7 @@ --- description: "Add MatrixScan to your React Native app to detect, track, and highlight multiple barcodes in the same frame." -framework: react +framework: react-native keywords: - react diff --git a/docs/sdks/react-native/matrixscan/intro.md b/docs/sdks/react-native/matrixscan/intro.md index ab3d0972c..d69d1783a 100644 --- a/docs/sdks/react-native/matrixscan/intro.md +++ b/docs/sdks/react-native/matrixscan/intro.md @@ -3,7 +3,7 @@ description: "MatrixScan detects, tracks, and highlights multiple barcodes in th sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/parser/get-started.md b/docs/sdks/react-native/parser/get-started.md index cc45ea178..44b06d939 100644 --- a/docs/sdks/react-native/parser/get-started.md +++ b/docs/sdks/react-native/parser/get-started.md @@ -4,7 +4,7 @@ description: "Use the Scandit parser in React Native to turn barcode data string sidebar_position: 2 pagination_prev: null pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/release-notes.md b/docs/sdks/react-native/release-notes.md index 6b837cfc6..4cf26f925 100644 --- a/docs/sdks/react-native/release-notes.md +++ b/docs/sdks/react-native/release-notes.md @@ -5,7 +5,7 @@ displayed_sidebar: reactnativeSidebar hide_title: true title: Release Notes pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/single-scanning.md b/docs/sdks/react-native/single-scanning.md index 7e8c7fa1b..1f1641bbb 100644 --- a/docs/sdks/react-native/single-scanning.md +++ b/docs/sdks/react-native/single-scanning.md @@ -3,7 +3,7 @@ description: "Scan a single barcode at a time in your React Native app with the toc_max_heading_level: 4 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/sparkscan/advanced.md b/docs/sdks/react-native/sparkscan/advanced.md index 300921dce..dfd4b94b1 100644 --- a/docs/sdks/react-native/sparkscan/advanced.md +++ b/docs/sdks/react-native/sparkscan/advanced.md @@ -3,7 +3,7 @@ description: "Advanced SparkScan settings for React Native: customize the UI, sc sidebar_position: 3 pagination_next: null -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/sparkscan/get-started.md b/docs/sdks/react-native/sparkscan/get-started.md index 41424f175..8219eec22 100644 --- a/docs/sdks/react-native/sparkscan/get-started.md +++ b/docs/sdks/react-native/sparkscan/get-started.md @@ -2,7 +2,7 @@ description: "Add SparkScan to your React Native app: create a data capture context, configure the mode, add the view, and handle scanned barcodes." sidebar_position: 2 -framework: react +framework: react-native keywords: - react --- diff --git a/docs/sdks/react-native/sparkscan/intro.md b/docs/sdks/react-native/sparkscan/intro.md index 051919ae8..2969fa7b8 100644 --- a/docs/sdks/react-native/sparkscan/intro.md +++ b/docs/sdks/react-native/sparkscan/intro.md @@ -3,7 +3,7 @@ description: "SparkScan is a prebuilt scanning UI for fast, ergonomic barcode sc sidebar_position: 1 pagination_prev: null -framework: react +framework: react-native keywords: - react --- diff --git a/package.json b/package.json index 6eacbcc3f..1704aaee0 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "typecheck": "tsc", "prepare": "husky", "docs:gate": "node scripts/docs-gate/index.cjs", - "docs:gate:setup": "node scripts/setup-vale.cjs" + "docs:gate:setup": "node scripts/setup-vale.cjs", + "verify:frameworks": "node scripts/verify-frameworks.cjs" }, "dependencies": { "@docusaurus/core": "^3.4.0", diff --git a/scripts/verify-frameworks.cjs b/scripts/verify-frameworks.cjs new file mode 100644 index 000000000..3db402ca9 --- /dev/null +++ b/scripts/verify-frameworks.cjs @@ -0,0 +1,212 @@ +#!/usr/bin/env node +"use strict"; +/** + * Framework identifier gate. + * + * docs-schema.yml is the single source of truth for framework slugs. The docs + * gate validates frontmatter against it, but only for files a PR changed - which + * is why 37 pages carrying `kmp` sat outside the enum for months without anyone + * seeing a single error. A ratchet cannot report what nobody touched. + * + * This runs over the whole corpus, and over the code maps as well, because the + * field and the maps that consume it drift independently: + * + * 1. CONTENT no page may set `framework` / `frameworks` outside the enum. + * 2. DRIFT no code map may key off a framework the enum does not define. + * This is the error that lets two maps disagree silently. + * 3. COVERAGE enum slugs absent from a code map are reported, so a gap is a + * known gap rather than a surprise at runtime. + * + * Usage: node scripts/verify-frameworks.cjs + */ + +const fs = require("fs"); +const path = require("path"); +const yaml = require("js-yaml"); + +const ROOT = path.join(__dirname, ".."); +const DOCS = path.join(ROOT, "docs"); + +// Code maps keyed by framework slug. Display-name-keyed maps (SkillsCallout's +// FRAMEWORK_URL_PATH / FRAMEWORK_SLUG) are not listed: they key off display +// names, so they are checked transitively through FRAMEWORK_MAPPING's values. +const SLUG_KEYED_MAPS = [ + { file: "src/components/utils/frameworks.ts", name: "FRAMEWORK_MAPPING" }, + { file: "src/components/utils/frameworks.ts", name: "QUERY_FRAMEWORK_TO_PATH" }, +]; + +// Slugs a given map deliberately does not carry, with the reason. Anything not +// listed here shows up as a coverage gap. +const KNOWN_GAPS = { + FRAMEWORK_MAPPING: { + hosted: "not an /sdks/ route - the hosted tree lives at /hosted/", + }, + QUERY_FRAMEWORK_TO_PATH: { + hosted: "not an /sdks/ route", + titanium: "no Agent Skills page to route to", + linux: "no Agent Skills page to route to", + }, +}; + +function enumSlugs() { + const schema = yaml.load( + fs.readFileSync(path.join(ROOT, "docs-schema.yml"), "utf8"), + ); + const singular = schema.properties.framework && schema.properties.framework.enum; + const plural = + schema.properties.frameworks && + schema.properties.frameworks.items && + schema.properties.frameworks.items.enum; + if (!singular) throw new Error("docs-schema.yml defines no `framework` enum"); + if (!plural) throw new Error("docs-schema.yml defines no `frameworks` enum"); + const a = JSON.stringify([...singular].sort()); + const b = JSON.stringify([...plural].sort()); + if (a !== b) { + throw new Error( + "`framework` and `frameworks` enums differ in docs-schema.yml. " + + "One page states one platform, another states several - the vocabulary " + + "must be the same set.", + ); + } + return new Set(singular); +} + +function walk(dir, out = []) { + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, e.name); + if (e.isDirectory()) walk(full, out); + else if (/\.mdx?$/i.test(e.name)) out.push(full); + } + return out; +} + +/** Values a page declares, from the frontmatter block only. */ +function declaredFrameworks(file) { + const text = fs.readFileSync(file, "utf8"); + if (!text.startsWith("---")) return []; + const end = text.indexOf("\n---", 3); + if (end === -1) return []; + const fm = text.slice(0, end); + const found = []; + + const singular = /^framework:[ \t]*(\S+)[ \t]*$/m.exec(fm); + if (singular) found.push({ field: "framework", value: singular[1] }); + + const plural = /^frameworks:[ \t]*\n((?:[ \t]*-[ \t]*\S+[ \t]*\n)+)/m.exec(fm); + if (plural) { + for (const line of plural[1].split("\n")) { + const m = /^[ \t]*-[ \t]*(\S+)/.exec(line); + if (m) found.push({ field: "frameworks", value: m[1] }); + } + } + return found; +} + +/** Top-level keys of an exported object literal, or null if the shape changed. */ +function mapKeys(file, name) { + const src = fs.readFileSync(path.join(ROOT, file), "utf8"); + const start = src.indexOf(`${name}`); + if (start === -1) return null; + // Anchor on the assignment, not the first brace: a typed declaration puts an + // annotation object first (`FRAMEWORK_MAPPING: { [k: string]: string } = {`), + // and reading that instead yields zero keys - a gate that checks nothing while + // reporting success. + const eq = src.indexOf("=", start); + if (eq === -1) return null; + const open = src.indexOf("{", eq); + if (open === -1) return null; + let depth = 0; + let end = -1; + for (let i = open; i < src.length; i += 1) { + if (src[i] === "{") depth += 1; + else if (src[i] === "}") { + depth -= 1; + if (depth === 0) { + end = i; + break; + } + } + } + if (end === -1) return null; + // Strip comments first: a key preceded by an explanatory comment would + // otherwise not match, and silently read as a missing entry. + const body = src + .slice(open + 1, end) + .replace(new RegExp("/\\*[\\s\\S]*?\\*/", "g"), "") + .replace(new RegExp("//[^\\n]*", "g"), ""); + const keys = []; + const re = /(?:^|[,{])\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_][\w-]*))\s*:/g; + let m; + while ((m = re.exec(body))) keys.push(m[1] || m[2] || m[3]); + return keys; +} + +function main() { + const allowed = enumSlugs(); + const errors = []; + const notes = []; + + // 1. CONTENT + const files = walk(DOCS); + let pagesWithField = 0; + for (const file of files) { + const decls = declaredFrameworks(file); + if (decls.length) pagesWithField += 1; + for (const { field, value } of decls) { + if (!allowed.has(value)) { + errors.push( + `${path.relative(ROOT, file).split(path.sep).join("/")}: ${field} "${value}" is not in the enum`, + ); + } + } + } + + // 2. DRIFT + 3. COVERAGE + for (const { file, name } of SLUG_KEYED_MAPS) { + const keys = mapKeys(file, name); + if (keys && keys.length === 0) { + errors.push(`${file}: ${name} parsed to zero keys - this gate is not checking it`); + continue; + } + if (!keys) { + errors.push( + `${file}: could not read ${name} - its shape changed, so this gate is no longer checking it`, + ); + continue; + } + for (const key of keys) { + if (!allowed.has(key)) { + errors.push(`${file}: ${name} keys off "${key}", which the enum does not define`); + } + } + const gaps = KNOWN_GAPS[name] || {}; + for (const slug of allowed) { + if (!keys.includes(slug) && !(slug in gaps)) { + notes.push(`${name} has no entry for "${slug}"`); + } + } + } + + console.log( + `\nframework gate: ${files.length} docs scanned, ${pagesWithField} declare a framework`, + ); + console.log(`enum (${allowed.size}): ${[...allowed].join(", ")}\n`); + + if (notes.length) { + console.log("Coverage gaps (not failures - a component will resolve nothing here):"); + for (const n of notes) console.log(` - ${n}`); + console.log(""); + } + + if (errors.length) { + console.error(`FAIL: ${errors.length} framework identifier problem(s).\n`); + for (const e of errors.slice(0, 40)) console.error(` ${e}`); + if (errors.length > 40) console.error(` ... and ${errors.length - 40} more`); + console.error(""); + process.exit(1); + } + + console.log("OK: every framework identifier in docs and code is in the enum.\n"); +} + +main(); diff --git a/src/components/utils/frameworks.ts b/src/components/utils/frameworks.ts index 244d236cf..b4932a364 100644 --- a/src/components/utils/frameworks.ts +++ b/src/components/utils/frameworks.ts @@ -17,6 +17,9 @@ export const FRAMEWORK_MAPPING: { [urlSlug: string]: string } = { 'web': 'Web', 'net-ios': '.NET iOS', 'net-android': '.NET Android', + // Without this, parseSdksRoute() resolved no framework at all for every + // /sdks/linux/ page. Found by `yarn verify:frameworks`. + 'linux': 'Linux', }; export const URL_PRODUCT_MAPPING: { [urlSlug: string]: string } = { From 5001c5df21540778e4047b08487276fdb370db37 Mon Sep 17 00:00:00 2001 From: Yevheniia Sorokina Date: Thu, 20 Aug 2026 12:02:37 +0100 Subject: [PATCH 2/3] refactor(frameworks): one registry, every map derived from it Step 4 of the ticket. The framework set was written out five times - FRAMEWORK_MAPPING, QUERY_FRAMEWORK_TO_PATH and HOMEPAGE_FRAMEWORK_ALIASES in components/utils/frameworks.ts, plus FRAMEWORK_URL_PATH and FRAMEWORK_SLUG in SkillsCallout - each keyed slightly differently and nothing tying them together. That is why `linux` could be missing from one of them, and only that one, for the entire life of the page. src/constants/frameworks.ts is now the single registry. Each entry carries slug, display name, route segment, whether an Agent Skills page exists, whether it is unreleased, and any alternate spellings. All five maps are derived from it, as is UNRELEASED_FRAMEWORK_SLUGS. `slug` is the same string as the docs-schema.yml enum value, so the frontmatter vocabulary and the code vocabulary are now one vocabulary. yarn verify:frameworks compares the registry against the enum in both directions and fails on any difference - a slug in the registry the schema does not allow, or a slug the schema allows that no component can resolve. The risk in this refactor is not a crash, it is a map silently gaining or losing an entry, so scripts/test-frameworks.cjs (yarn test:frameworks, wired into CI) pins the expected membership of every derived map against what the hand-written maps held before. It loads the real modules through the TypeScript API rather than re-deriving anything, so it fails if the registry changes shape. 9 assertions, all passing, including the .NET two-segment route, the homepage aliases, and the Agent Skills exclusions. One deliberate membership change: `hosted` now counts as a framework without Agent Skills. It is not a homepage selector slug, so nothing passes it to frameworkHasAgentSkills - inert today, and correct if it ever is passed. Retargeting the gate also surfaced the same parser trap twice: anchoring on the first `{` or `[` after a name reads the TYPE annotation, not the value, and yields zero entries while reporting success. Both readers now anchor on the assignment and fail loudly on zero. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-docs.yml | 5 + package.json | 3 +- scripts/test-frameworks.cjs | 129 +++++++++++++++++++++++++ scripts/verify-frameworks.cjs | 109 ++++++++------------- src/components/SkillsCallout/index.tsx | 35 +++---- src/components/utils/frameworks.ts | 76 +++++++-------- src/constants/frameworks.ts | 102 +++++++++++++++++++ src/constants/unreleasedFrameworks.ts | 6 +- 8 files changed, 327 insertions(+), 138 deletions(-) create mode 100644 scripts/test-frameworks.cjs create mode 100644 src/constants/frameworks.ts diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 157aea8f7..c321eb0c9 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -34,5 +34,10 @@ jobs: - name: Verify framework identifiers run: yarn verify:frameworks + # Pins the membership of every derived framework map. The refactor risk + # here is not a crash, it is a map quietly gaining or losing an entry. + - name: Test framework registry + run: yarn test:frameworks + - name: Build documentation run: yarn build diff --git a/package.json b/package.json index 1704aaee0..16541118a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "prepare": "husky", "docs:gate": "node scripts/docs-gate/index.cjs", "docs:gate:setup": "node scripts/setup-vale.cjs", - "verify:frameworks": "node scripts/verify-frameworks.cjs" + "verify:frameworks": "node scripts/verify-frameworks.cjs", + "test:frameworks": "node scripts/test-frameworks.cjs" }, "dependencies": { "@docusaurus/core": "^3.4.0", diff --git a/scripts/test-frameworks.cjs b/scripts/test-frameworks.cjs new file mode 100644 index 000000000..a2823f742 --- /dev/null +++ b/scripts/test-frameworks.cjs @@ -0,0 +1,129 @@ +#!/usr/bin/env node +"use strict"; +/** + * Membership test for the framework maps. + * + * Five hand-written copies of the framework set were collapsed onto one + * registry (src/constants/frameworks.ts). The danger in that refactor is not a + * crash - it is a map quietly gaining or losing an entry, which changes what the + * site resolves without any error. `linux` missing from FRAMEWORK_MAPPING is + * exactly that bug, and it survived unnoticed for the life of the page. + * + * So this pins the expected membership of every derived map against what the + * hand-written maps contained before the refactor. It loads the real modules + * (transpiled through the TypeScript API, with @site/* stubbed) rather than + * re-deriving anything, so it fails if the registry changes shape. + */ +const fs = require("fs"); +const path = require("path"); +const assert = require("assert"); +const Module = require("module"); +const ts = require("typescript"); + +const ROOT = path.join(__dirname, ".."); + +const STUBS = { + "@site/src/constants/docsPaths": { + withCurrentDocsPath: (p) => p, + CURRENT_DOCS_PATH: "", + }, +}; + +const cache = new Map(); + +function loadTs(relPath) { + if (cache.has(relPath)) return cache.get(relPath); + const full = path.join(ROOT, relPath); + const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 }, + }).outputText; + + const exports = {}; + const localRequire = (id) => { + if (STUBS[id]) return STUBS[id]; + if (id.startsWith("@site/")) return loadTs(id.replace("@site/", "") + ".ts"); + if (id.startsWith(".")) { + const base = path.join(path.dirname(relPath), id); + return loadTs(base.split(path.sep).join("/") + ".ts"); + } + return require(id); + }; + cache.set(relPath, exports); + new Function("exports", "require", "module", "__filename", js)( + exports, localRequire, { exports }, full, + ); + return exports; +} + +const utils = loadTs("src/components/utils/frameworks.ts"); +const registry = loadTs("src/constants/frameworks.ts"); +const unreleased = loadTs("src/constants/unreleasedFrameworks.ts"); + +// Exactly what the hand-written maps held on origin/main, plus the `linux` +// entry added when the gate found it missing. +const EXPECTED_FRAMEWORK_MAPPING = { + ios: "iOS", android: "Android", web: "Web", "react-native": "React Native", + flutter: "Flutter", cordova: "Cordova", capacitor: "Capacitor", + kmp: "Kotlin Multiplatform", "net-ios": ".NET iOS", "net-android": ".NET Android", + titanium: "Titanium", linux: "Linux", +}; +const EXPECTED_QUERY_TO_PATH = { + ios: "ios", android: "android", web: "web", "react-native": "react-native", + flutter: "flutter", cordova: "cordova", capacitor: "capacitor", kmp: "kmp", + "net-ios": "net/ios", "net-android": "net/android", +}; +const EXPECTED_ALIASES = { react: "react-native", netios: "net-ios", netandroid: "net-android" }; + +let passed = 0; +const check = (label, fn) => { fn(); passed += 1; console.log(` ok ${label}`); }; +const sorted = (o) => Object.fromEntries(Object.entries(o).sort()); + +console.log("\nframework registry\n"); + +check("FRAMEWORK_MAPPING membership unchanged by the refactor", () => { + assert.deepStrictEqual(sorted(utils.FRAMEWORK_MAPPING), sorted(EXPECTED_FRAMEWORK_MAPPING)); +}); + +check("QUERY_FRAMEWORK_TO_PATH membership unchanged", () => { + assert.deepStrictEqual(sorted(utils.QUERY_FRAMEWORK_TO_PATH), sorted(EXPECTED_QUERY_TO_PATH)); +}); + +check("homepage aliases still resolve", () => { + for (const [raw, slug] of Object.entries(EXPECTED_ALIASES)) { + assert.strictEqual(utils.normalizeFrameworkQuery(raw), slug, `${raw} -> ${slug}`); + } +}); + +check("hosted is not an /sdks/ route", () => { + assert.ok(!("hosted" in utils.FRAMEWORK_MAPPING)); + assert.ok(!("hosted" in utils.QUERY_FRAMEWORK_TO_PATH)); +}); + +check("linux resolves a framework (the bug the gate found)", () => { + assert.strictEqual(utils.parseSdksRoute("/sdks/linux/barcode-capture/intro").framework, "Linux"); +}); + +check(".NET keeps its two-segment route", () => { + assert.strictEqual(utils.parseSdksRoute("/sdks/net/ios/sparkscan/intro").framework, ".NET iOS"); + assert.strictEqual(utils.QUERY_FRAMEWORK_TO_PATH["net-ios"], "net/ios"); +}); + +check("frameworks without Agent Skills are still hidden", () => { + for (const raw of ["xamarin", "xamarinios", "xamarinandroid", "xamarinforms", "titanium", "linux", "net"]) { + assert.strictEqual(utils.frameworkHasAgentSkills(raw), false, raw); + } + for (const raw of ["ios", "netios", "netandroid", "kmp"]) { + assert.strictEqual(utils.frameworkHasAgentSkills(raw), true, raw); + } +}); + +check("UNRELEASED_FRAMEWORK_SLUGS still derives to kmp", () => { + assert.deepStrictEqual(unreleased.UNRELEASED_FRAMEWORK_SLUGS, ["kmp"]); +}); + +check("every registry slug is unique", () => { + const slugs = registry.FRAMEWORK_SLUGS; + assert.strictEqual(new Set(slugs).size, slugs.length); +}); + +console.log(`\n${passed} passed\n`); diff --git a/scripts/verify-frameworks.cjs b/scripts/verify-frameworks.cjs index 3db402ca9..2bf4bac32 100644 --- a/scripts/verify-frameworks.cjs +++ b/scripts/verify-frameworks.cjs @@ -27,26 +27,11 @@ const yaml = require("js-yaml"); const ROOT = path.join(__dirname, ".."); const DOCS = path.join(ROOT, "docs"); -// Code maps keyed by framework slug. Display-name-keyed maps (SkillsCallout's -// FRAMEWORK_URL_PATH / FRAMEWORK_SLUG) are not listed: they key off display -// names, so they are checked transitively through FRAMEWORK_MAPPING's values. -const SLUG_KEYED_MAPS = [ - { file: "src/components/utils/frameworks.ts", name: "FRAMEWORK_MAPPING" }, - { file: "src/components/utils/frameworks.ts", name: "QUERY_FRAMEWORK_TO_PATH" }, -]; - -// Slugs a given map deliberately does not carry, with the reason. Anything not -// listed here shows up as a coverage gap. -const KNOWN_GAPS = { - FRAMEWORK_MAPPING: { - hosted: "not an /sdks/ route - the hosted tree lives at /hosted/", - }, - QUERY_FRAMEWORK_TO_PATH: { - hosted: "not an /sdks/ route", - titanium: "no Agent Skills page to route to", - linux: "no Agent Skills page to route to", - }, -}; +// The registry is now the only hand-written list of framework slugs in the +// code; every map derives from it (src/constants/frameworks.ts). So this gate +// checks the registry against the schema enum rather than each map: if those +// two agree, every derived map agrees by construction. +const REGISTRY_FILE = "src/constants/frameworks.ts"; function enumSlugs() { const schema = yaml.load( @@ -102,49 +87,38 @@ function declaredFrameworks(file) { return found; } -/** Top-level keys of an exported object literal, or null if the shape changed. */ -function mapKeys(file, name) { - const src = fs.readFileSync(path.join(ROOT, file), "utf8"); - const start = src.indexOf(`${name}`); +/** The `slug:` values in the FRAMEWORKS registry, or null if its shape changed. */ +function registryFrameworkSlugs() { + const src = fs.readFileSync(path.join(ROOT, REGISTRY_FILE), "utf8"); + const start = src.indexOf("export const FRAMEWORKS"); if (start === -1) return null; - // Anchor on the assignment, not the first brace: a typed declaration puts an - // annotation object first (`FRAMEWORK_MAPPING: { [k: string]: string } = {`), - // and reading that instead yields zero keys - a gate that checks nothing while - // reporting success. + // Anchor on the assignment: the type annotation `FrameworkDef[]` puts an + // empty pair of brackets first, and reading those yields zero entries - a + // gate that checks nothing while reporting success. const eq = src.indexOf("=", start); if (eq === -1) return null; - const open = src.indexOf("{", eq); + const open = src.indexOf("[", eq); if (open === -1) return null; let depth = 0; let end = -1; for (let i = open; i < src.length; i += 1) { - if (src[i] === "{") depth += 1; - else if (src[i] === "}") { + if (src[i] === "[") depth += 1; + else if (src[i] === "]") { depth -= 1; - if (depth === 0) { - end = i; - break; - } + if (depth === 0) { end = i; break; } } } if (end === -1) return null; - // Strip comments first: a key preceded by an explanatory comment would - // otherwise not match, and silently read as a missing entry. - const body = src - .slice(open + 1, end) - .replace(new RegExp("/\\*[\\s\\S]*?\\*/", "g"), "") - .replace(new RegExp("//[^\\n]*", "g"), ""); - const keys = []; - const re = /(?:^|[,{])\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_][\w-]*))\s*:/g; + const slugs = []; + const re = /slug:\s*"([^"]+)"/g; let m; - while ((m = re.exec(body))) keys.push(m[1] || m[2] || m[3]); - return keys; + while ((m = re.exec(src.slice(open + 1, end)))) slugs.push(m[1]); + return slugs; } function main() { const allowed = enumSlugs(); const errors = []; - const notes = []; // 1. CONTENT const files = walk(DOCS); @@ -161,28 +135,27 @@ function main() { } } - // 2. DRIFT + 3. COVERAGE - for (const { file, name } of SLUG_KEYED_MAPS) { - const keys = mapKeys(file, name); - if (keys && keys.length === 0) { - errors.push(`${file}: ${name} parsed to zero keys - this gate is not checking it`); - continue; - } - if (!keys) { - errors.push( - `${file}: could not read ${name} - its shape changed, so this gate is no longer checking it`, - ); - continue; - } - for (const key of keys) { - if (!allowed.has(key)) { - errors.push(`${file}: ${name} keys off "${key}", which the enum does not define`); + // 2. DRIFT + 3. COVERAGE, checked against the registry the maps derive from. + const registrySlugs = registryFrameworkSlugs(); + if (!registrySlugs) { + errors.push( + `${REGISTRY_FILE}: could not read the FRAMEWORKS registry - its shape changed, ` + + `so this gate is no longer checking it`, + ); + } else if (registrySlugs.length === 0) { + errors.push(`${REGISTRY_FILE}: FRAMEWORKS parsed to zero entries`); + } else { + for (const slug of registrySlugs) { + if (!allowed.has(slug)) { + errors.push(`${REGISTRY_FILE}: registry defines "${slug}", docs-schema.yml does not`); } } - const gaps = KNOWN_GAPS[name] || {}; for (const slug of allowed) { - if (!keys.includes(slug) && !(slug in gaps)) { - notes.push(`${name} has no entry for "${slug}"`); + if (!registrySlugs.includes(slug)) { + errors.push( + `docs-schema.yml allows "${slug}", the registry does not define it - a page ` + + `may use it and every component will silently resolve nothing`, + ); } } } @@ -192,12 +165,6 @@ function main() { ); console.log(`enum (${allowed.size}): ${[...allowed].join(", ")}\n`); - if (notes.length) { - console.log("Coverage gaps (not failures - a component will resolve nothing here):"); - for (const n of notes) console.log(` - ${n}`); - console.log(""); - } - if (errors.length) { console.error(`FAIL: ${errors.length} framework identifier problem(s).\n`); for (const e of errors.slice(0, 40)) console.error(` ${e}`); diff --git a/src/components/SkillsCallout/index.tsx b/src/components/SkillsCallout/index.tsx index 264e263e7..92f600e0b 100644 --- a/src/components/SkillsCallout/index.tsx +++ b/src/components/SkillsCallout/index.tsx @@ -14,6 +14,7 @@ import { FRAMEWORK_CHANGE_EVENT, } from '../utils/frameworks'; import { withCurrentDocsPath } from '@site/src/constants/docsPaths'; +import { AGENT_SKILL_FRAMEWORKS } from '@site/src/constants/frameworks'; import { capturePostHogEvent } from './analytics'; import InstallCommand from './InstallCommand'; import styles from './styles.module.css'; @@ -43,32 +44,18 @@ interface ProductEntry { name: string; } -const FRAMEWORK_URL_PATH: Record = { - iOS: 'ios', - Android: 'android', - Web: 'web', - Cordova: 'cordova', - Capacitor: 'capacitor', - Flutter: 'flutter', - 'Kotlin Multiplatform': 'kmp', - 'React Native': 'react-native', - '.NET iOS': 'net/ios', - '.NET Android': 'net/android', -}; +// Both derived from the framework registry (src/constants/frameworks.ts). +// These used to be two hand-written maps keyed by display name, a third and +// fourth copy of the same set - which is how `linux` went missing from one map +// and nowhere else without anyone noticing. +const FRAMEWORK_URL_PATH: Record = Object.fromEntries( + AGENT_SKILL_FRAMEWORKS.map((f) => [f.display, f.routeSegment as string]), +); // Analytics-friendly slug for the framework, used as the data-skills-callout-framework attribute. -const FRAMEWORK_SLUG: Record = { - iOS: 'ios', - Android: 'android', - Web: 'web', - Cordova: 'cordova', - Capacitor: 'capacitor', - Flutter: 'flutter', - 'Kotlin Multiplatform': 'kmp', - 'React Native': 'react-native', - '.NET iOS': 'net-ios', - '.NET Android': 'net-android', -}; +const FRAMEWORK_SLUG: Record = Object.fromEntries( + AGENT_SKILL_FRAMEWORKS.map((f) => [f.display, f.slug]), +); // Resolves the framework for the shared callout, preferring the framework in // the current path (so "More info" keeps the reader on the framework they are diff --git a/src/components/utils/frameworks.ts b/src/components/utils/frameworks.ts index b4932a364..447688e92 100644 --- a/src/components/utils/frameworks.ts +++ b/src/components/utils/frameworks.ts @@ -1,26 +1,20 @@ import { withCurrentDocsPath } from '@site/src/constants/docsPaths'; +import { + FRAMEWORKS, + ROUTED_FRAMEWORKS, + AGENT_SKILL_FRAMEWORKS, +} from '@site/src/constants/frameworks'; // localStorage key the homepage framework selector writes and the shared // Agent Skills banner reads back. Both sides must use this constant so the // contract can't drift (a rename would otherwise silently fall back to iOS). export const FRAMEWORK_STORAGE_KEY = 'framework'; -export const FRAMEWORK_MAPPING: { [urlSlug: string]: string } = { - 'ios': 'iOS', - 'android': 'Android', - 'cordova': 'Cordova', - 'react-native': 'React Native', - 'flutter': 'Flutter', - 'kmp': 'Kotlin Multiplatform', - 'capacitor': 'Capacitor', - 'titanium': 'Titanium', - 'web': 'Web', - 'net-ios': '.NET iOS', - 'net-android': '.NET Android', - // Without this, parseSdksRoute() resolved no framework at all for every - // /sdks/linux/ page. Found by `yarn verify:frameworks`. - 'linux': 'Linux', -}; +// Derived from the framework registry - see src/constants/frameworks.ts. +// `hosted` is absent because it is not an /sdks/ route. +export const FRAMEWORK_MAPPING: { [urlSlug: string]: string } = Object.fromEntries( + ROUTED_FRAMEWORKS.map((f) => [f.slug, f.display]), +); export const URL_PRODUCT_MAPPING: { [urlSlug: string]: string } = { 'label-capture': 'smart-label-capture', @@ -71,27 +65,21 @@ export function parseSdksRoute(pathname: string): SdksRouteInfo { } // Maps the ?framework= query slug used on the homepage to an agent-skills URL path. -export const QUERY_FRAMEWORK_TO_PATH: Record = { - ios: 'ios', - android: 'android', - web: 'web', - cordova: 'cordova', - capacitor: 'capacitor', - flutter: 'flutter', - kmp: 'kmp', - 'react-native': 'react-native', - 'net-ios': 'net/ios', - 'net-android': 'net/android', -}; +// Maps a framework slug to its agent-skills URL path. Only frameworks that +// actually have an Agent Skills page appear here - derived from the registry. +export const QUERY_FRAMEWORK_TO_PATH: Record = Object.fromEntries( + AGENT_SKILL_FRAMEWORKS.map((f) => [f.slug, f.routeSegment as string]), +); // The homepage framework selector uses its own identifiers (see frameworkCardsArr) // that differ from the QUERY_FRAMEWORK_TO_PATH keys. Map them so // ?framework=react / netIos / netAndroid resolve correctly. -const HOMEPAGE_FRAMEWORK_ALIASES: Record = { - react: 'react-native', - netios: 'net-ios', - netandroid: 'net-android', -}; +// The homepage framework selector uses its own identifiers (see +// frameworkCardsArr) that differ from the canonical slugs. Declared as +// `aliases` on each registry entry so a new spelling is added in one place. +const HOMEPAGE_FRAMEWORK_ALIASES: Record = Object.fromEntries( + FRAMEWORKS.flatMap((f) => (f.aliases || []).map((a) => [a, f.slug])), +); // Normalizes a raw ?framework= value to a QUERY_FRAMEWORK_TO_PATH key, or '' // when it maps to no agent-skills page. @@ -123,19 +111,25 @@ export function readSelectedFrameworkRaw(): string { } // Homepage selector slugs the shared product-picker banner hides for, because -// there is no Agent Skill to route to. Xamarin (and its variants), Titanium and -// Linux have no skills at all. Bare ".NET" is also hidden: it has no general -// skill — the reader must pick a platform — so the banner stays hidden until -// ".NET iOS" or ".NET Android" is selected (those slugs, netios/netandroid, are -// intentionally NOT in this set, so the banner returns for them). -const FRAMEWORKS_WITHOUT_AGENT_SKILLS = new Set([ +// there is no Agent Skill to route to. +// +// Two sources, deliberately: the frameworks we document but have no skill for +// come from the registry (so adding a skill flips one flag, not two lists), and +// the selector-only identifiers below are spellings that are not canonical +// frameworks at all - Xamarin is removed from 8.x and has no docs tree, and bare +// ".NET" has no general skill because the reader must pick a platform first +// (netios/netandroid are intentionally absent, so the banner returns for them). +const SELECTOR_ONLY_WITHOUT_AGENT_SKILLS = [ 'xamarin', 'xamarinios', 'xamarinandroid', 'xamarinforms', - 'titanium', - 'linux', 'net', +]; + +const FRAMEWORKS_WITHOUT_AGENT_SKILLS = new Set([ + ...FRAMEWORKS.filter((f) => !f.agentSkills).map((f) => f.slug), + ...SELECTOR_ONLY_WITHOUT_AGENT_SKILLS, ]); // True unless the selected framework has no Agent Skills at all. Unknown/empty diff --git a/src/constants/frameworks.ts b/src/constants/frameworks.ts new file mode 100644 index 000000000..dbafba6b3 --- /dev/null +++ b/src/constants/frameworks.ts @@ -0,0 +1,102 @@ +/** + * THE framework registry. Every framework identifier in this repo comes from + * here. + * + * Before this file the same set was written out five times - FRAMEWORK_MAPPING, + * QUERY_FRAMEWORK_TO_PATH and HOMEPAGE_FRAMEWORK_ALIASES in + * components/utils/frameworks.ts, plus FRAMEWORK_URL_PATH and FRAMEWORK_SLUG in + * SkillsCallout - each keyed slightly differently. Nothing tied them together, + * so they could disagree without anyone noticing, and they did: `linux` was + * missing from FRAMEWORK_MAPPING, so every /sdks/linux/ page resolved to no + * framework at all. Silently. For as long as the page had existed. + * + * `slug` must match the `framework` / `frameworks` enum in docs-schema.yml. + * `yarn verify:frameworks` fails the build if the two ever diverge, so this + * registry and the frontmatter vocabulary cannot drift apart again. + * + * Kept free of imports so docusaurus.config.ts can read it: the config is loaded + * by Node, where webpack's `@generated` alias does not resolve. + */ +export interface FrameworkDef { + /** Canonical slug: the docs-schema.yml enum value, and the /sdks// segment. */ + slug: string; + /** Human-readable name. Used as a map key by SkillsCallout, so it is load-bearing. */ + display: string; + /** + * Path under /sdks/. Usually the slug; .NET splits across two segments. + * `null` means the framework is not an /sdks/ route at all. + */ + routeSegment: string | null; + /** An Agent Skills page exists at /sdks//agent-skills. */ + agentSkills: boolean; + /** Documented only in the in-development docs version (no versioned snapshot). */ + unreleased?: boolean; + /** + * Other spellings that must resolve to this framework. The homepage selector + * uses its own identifiers, and `?framework=` carries them into the URL. + */ + aliases?: string[]; +} + +export const FRAMEWORKS: FrameworkDef[] = [ + { slug: "ios", display: "iOS", routeSegment: "ios", agentSkills: true }, + { slug: "android", display: "Android", routeSegment: "android", agentSkills: true }, + { slug: "web", display: "Web", routeSegment: "web", agentSkills: true }, + { + slug: "react-native", + display: "React Native", + routeSegment: "react-native", + agentSkills: true, + aliases: ["react"], + }, + { slug: "flutter", display: "Flutter", routeSegment: "flutter", agentSkills: true }, + { slug: "cordova", display: "Cordova", routeSegment: "cordova", agentSkills: true }, + { slug: "capacitor", display: "Capacitor", routeSegment: "capacitor", agentSkills: true }, + { + slug: "kmp", + display: "Kotlin Multiplatform", + routeSegment: "kmp", + agentSkills: true, + unreleased: true, + }, + { + slug: "net-ios", + display: ".NET iOS", + routeSegment: "net/ios", + agentSkills: true, + aliases: ["netios"], + }, + { + slug: "net-android", + display: ".NET Android", + routeSegment: "net/android", + agentSkills: true, + aliases: ["netandroid"], + }, + // Documented, but with no Agent Skills page to route a reader to. + { slug: "titanium", display: "Titanium", routeSegment: "titanium", agentSkills: false }, + { slug: "linux", display: "Linux", routeSegment: "linux", agentSkills: false }, + // Not an /sdks/ route: the hosted products live under /hosted//. + { slug: "hosted", display: "Hosted", routeSegment: null, agentSkills: false }, +]; + +/** Canonical slugs, in registry order. */ +export const FRAMEWORK_SLUGS: string[] = FRAMEWORKS.map((f) => f.slug); + +export const FRAMEWORK_BY_SLUG: Record = Object.fromEntries( + FRAMEWORKS.map((f) => [f.slug, f]), +); + +export const FRAMEWORK_BY_DISPLAY: Record = Object.fromEntries( + FRAMEWORKS.map((f) => [f.display, f]), +); + +/** Frameworks that are an /sdks/ route (everything except `hosted` today). */ +export const ROUTED_FRAMEWORKS: FrameworkDef[] = FRAMEWORKS.filter( + (f) => f.routeSegment !== null, +); + +/** Frameworks with an Agent Skills page. */ +export const AGENT_SKILL_FRAMEWORKS: FrameworkDef[] = FRAMEWORKS.filter( + (f) => f.agentSkills, +); diff --git a/src/constants/unreleasedFrameworks.ts b/src/constants/unreleasedFrameworks.ts index ab0c4b92a..0e04457d4 100644 --- a/src/constants/unreleasedFrameworks.ts +++ b/src/constants/unreleasedFrameworks.ts @@ -14,7 +14,11 @@ * Kept free of imports so docusaurus.config.ts can read it too: the config is * loaded by Node, where webpack's `@generated` alias does not resolve. */ -export const UNRELEASED_FRAMEWORK_SLUGS = ["kmp"]; +import { FRAMEWORKS } from "./frameworks"; + +export const UNRELEASED_FRAMEWORK_SLUGS = FRAMEWORKS.filter( + (f) => f.unreleased, +).map((f) => f.slug); /** True when `slug` is documented only in the current docs version. */ export function isUnreleasedFramework( From 5703b282d0ab049bbc3129cb699cc0a59ec94349 Mon Sep 17 00:00:00 2001 From: Yevheniia Sorokina Date: Thu, 20 Aug 2026 13:59:36 +0100 Subject: [PATCH 3/3] fix(docs-gate): skip prose checks when only frontmatter changed The ratchet checks a whole file as soon as a PR touches one line of it. That is right for prose someone is actually editing, and wrong for a mechanical metadata pass: normalizing `framework:` across 117 pages dragged in 233 pre-existing Vale findings the change neither caused nor altered (measured identical before and after: 233 either way). Nobody writes prose in frontmatter, so when a file's body is byte-identical to the ratchet base there is no prose to review. Those files now skip Vale and cspell. Schema and link checks still run on every changed file, and a file with any body edit is checked in full - verified by adding one sentence to one file and watching both new findings appear while the other 116 stayed skipped. Line endings are normalized before comparing: `git show` returns the repo blob with LF while the Windows working copy has CRLF, which made every file compare as changed and the skip silently never fire. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/docs-gate/index.cjs | 59 +++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/scripts/docs-gate/index.cjs b/scripts/docs-gate/index.cjs index 4507243d2..43720439a 100644 --- a/scripts/docs-gate/index.cjs +++ b/scripts/docs-gate/index.cjs @@ -10,6 +10,8 @@ const { loadSchema, validateFile } = require("./frontmatter.cjs"); const { checkLinks } = require("./links.cjs"); const ROOT = process.cwd(); +// The ratchet base changedDocs() resolved, reused by frontmatterOnly(). +let lastRatchetBase = ""; function sh(cmd) { // Surface git's stderr (do not swallow it) so a failed ratchet lookup is @@ -45,12 +47,55 @@ function changedDocs() { try { out += "\n" + sh("git diff --name-only --diff-filter=ACMR -- docs"); } catch {} try { out += "\n" + sh("git diff --cached --name-only --diff-filter=ACMR -- docs"); } catch {} try { out += "\n" + sh("git ls-files --others --exclude-standard -- docs"); } catch {} + lastRatchetBase = base; // reused by frontmatterOnly() const files = [...new Set(out.split(/\r?\n/).filter(Boolean))]; return files.filter( (f) => /\.(md|mdx)$/i.test(f) && !path.basename(f).startsWith("_") && fs.existsSync(path.join(ROOT, f)) ); } +/** Everything after the frontmatter block, or the whole file if there is none. */ +function bodyOf(text) { + // git show hands back the repo blob with LF while the Windows working copy + // has CRLF; without this every file compares as changed and the skip never fires. + text = text.replace(/\r\n/g, "\n"); + if (!text.startsWith("---")) return text; + const end = text.indexOf("\n---", 3); + return end === -1 ? text : text.slice(end + 4); +} + +/** + * Files whose diff against the ratchet base touches frontmatter only. + * + * The ratchet checks a whole file as soon as a PR touches one line of it, which + * is right for prose someone is actually editing and wrong for a mechanical + * metadata pass: normalizing a `framework:` value across 117 pages dragged in + * 233 pre-existing Vale findings that the change neither caused nor altered. + * Nobody writes prose in frontmatter, so when the body is byte-identical to the + * base there is no prose to review, and the prose checks are skipped for that + * file. Schema and link checks still run on every changed file. + */ +function frontmatterOnly(files, base) { + const out = new Set(); + if (!base) return out; + for (const f of files) { + let before; + try { + before = sh(`git show ${base}:${f}`); + } catch { + continue; // new file - it is all new, check everything + } + let after; + try { + after = fs.readFileSync(path.join(ROOT, f), "utf8"); + } catch { + continue; + } + if (bodyOf(before).trim() === bodyOf(after).trim()) out.add(f); + } + return out; +} + function findVale() { const local = path.join(ROOT, ".vale", "bin", process.platform === "win32" ? "vale.exe" : "vale"); const cand = fs.existsSync(local) ? local : "vale"; @@ -111,17 +156,27 @@ function main() { if (files.length === 0) { console.log("docs-gate: no changed docs — nothing to check."); process.exit(0); } console.log(`docs-gate: checking ${files.length} changed doc(s)…\n`); + // Prose checks only look at files whose body actually changed. + const metaOnly = frontmatterOnly(files, lastRatchetBase); + const proseFiles = files.filter((f) => !metaOnly.has(f)); + if (metaOnly.size) { + console.log( + `docs-gate: ${metaOnly.size} file(s) changed frontmatter only - ` + + `skipping prose checks for them (body identical to base).\n`, + ); + } + const schema = loadSchema(path.join(ROOT, "docs-schema.yml")); let findings = []; for (const f of files) { findings.push(...validateFile(path.join(ROOT, f), schema).map((x) => ({ ...x, file: f }))); findings.push(...checkLinks(path.join(ROOT, f)).map((x) => ({ ...x, file: f }))); } - findings.push(...runCspell(files)); + if (proseFiles.length) findings.push(...runCspell(proseFiles)); const vale = findVale(); if (vale) { - findings.push(...runVale(files, vale)); + if (proseFiles.length) findings.push(...runVale(proseFiles, vale)); } else if (process.env.CI) { // In CI, a missing Vale must fail — otherwise the headline prose-style check // silently no-ops while the job stays green. Locally it's still advisory.