From 0ae5c7b7af79b2071fdbad99cbe5378f015d52cc Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 2 Sep 2026 13:08:36 +0200 Subject: [PATCH] Release Device Tools 0.2.0 --- .gitea/workflows/verify.yml | 39 ++ CHANGELOG.md | 7 + README.md | 7 +- SOURCE.md | 4 +- package-lock.json | 40 +- package.json | 10 +- playwright.config.ts | 22 +- public/CHANGELOG.md | 7 + public/LICENSES/npm-runtime-licenses.txt | 6 +- public/README.md | 7 +- public/SOURCE.md | 4 +- public/sw.js | 2 +- public/toolbox-app.json | 16 +- src/components/HelpDialog.tsx | 7 + src/components/Workbench.tsx | 329 ++++++++++++ src/core/device.ts | 656 ++++++++++++++++++++++- src/styles.css | 168 +++++- src/toolbox/manifest.source.json | 24 +- src/version.ts | 2 +- tests/browser/responsive.spec.ts | 18 + tests/core/device.test.ts | 165 ++++++ 21 files changed, 1490 insertions(+), 50 deletions(-) create mode 100644 .gitea/workflows/verify.yml create mode 100644 tests/browser/responsive.spec.ts diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..b84a7cb --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,39 @@ +name: Verify + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: verify-${{ gitea.repository }}-${{ gitea.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + CI: "true" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + - name: Select declared npm version + run: npm install --global npm@11.17.0 + - name: Install dependencies + run: npm ci + - name: Audit runtime dependencies + run: npm audit --omit=dev --audit-level=moderate + - name: Check, test, and build + run: npm run check + - name: Install browser engines + run: npx playwright install --with-deps chromium firefox webkit + - name: Browser tests + run: npm run test:browser diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd644c..f4f05b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added Toolbox manifest compatibility analysis for legacy requirements and + required/optional capability profiles with explainable unknowns. +- Added an explicitly initiated MediaCapabilities codec lab and downloadable, + bucketed privacy-preserving evidence. + ## 0.1.0 - 2026-09-01 - Added passive display, input, codec, media-constraint, PWA and browser API inventory. diff --git a/README.md b/README.md index bc0c3b6..8c93e1c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A local browser capability and privacy lab. It shows useful platform evidence wi - Passive inventory for secure/top-level context, display/viewport/DPR, touch/pointer/hover, accessibility media queries, media constraints, codec declarations, storage/PWA and browser device-API presence. - Explicit individual probes for permission state, storage estimate/persistence, WebGL limits, WebGPU adapter presence, media-device counts, battery, network hints and camera/microphone/screen access. +- Explainable failures that distinguish a missing API, insecure or embedded context, Permissions Policy, user permission, and unavailable/busy operating-system devices, with concrete remediation. +- Toolbox application compatibility analysis from pasted manifest JSON, including legacy requirements plus required/optional capability profiles and unknown-capability evidence. +- An explicit MediaCapabilities decoding lab for video/audio codec, dimensions, rate and bitrate, with support/smoothness/power-efficiency results and a separately downloadable bucketed report. - Camera, microphone and screen streams are never recorded and every returned track stops immediately. - WebGL deliberately avoids unmasked vendor/renderer extensions; media device IDs and labels are discarded. - Deterministic redacted JSON/CSV reports with broad buckets for high-entropy values and no timestamp, user agent, locale, identifier or score. @@ -13,13 +16,13 @@ A local browser capability and privacy lab. It shows useful platform evidence wi ## Privacy boundary -The passive inventory runs locally on load and does not request permission. Optional probes run only after their named button is pressed; buttons labelled “may prompt” can show browser or operating-system UI. The application performs no canvas/audio sample fingerprinting, persistent storage, telemetry or external request. +The passive inventory runs locally on load and does not request permission. Optional probes run only after their named button is pressed; buttons labelled “may prompt” can show browser or operating-system UI. Application manifests are treated as inert JSON and no referenced code is loaded. MediaCapabilities checks are likewise opt-in because smoothness and power-efficiency answers can depend on the device. The application performs no canvas/audio sample fingerprinting, persistent storage, telemetry or external request. Downloaded reports are deliberately less precise than the on-screen local view. They bucket exact dimensions, pixel ratio, storage, graphics limits, hardware counts, battery timing and connection quality. This is a diagnostics report, not proof that an API will work in every embedded or enterprise-policy context. ## Development -Use Node.js 22+ and npm 11. Run `npm ci`, `npm run check`, `npm run test:browser`, then `npm run package:release -- --force`. The deterministic artifact is `release/device-tools-0.1.0.zip` with a SHA-256 sidecar. +Use Node.js 22+ and npm 11. Run `npm ci`, `npm run check`, `npm run test:browser`, then `npm run package:release -- --force`. The deterministic artifact is `release/device-tools-0.2.0.zip` with a SHA-256 sidecar. ## Licence diff --git a/SOURCE.md b/SOURCE.md index ef1f6a3..3c07db5 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -1,10 +1,10 @@ # Source identity - Project: Device Tools -- Version: 0.1.0 +- Version: 0.2.0 - Repository: https://git.add-ideas.de/lotobo/device-tools - Licence: GPL-3.0-or-later - Build: Node.js 22+, npm 11, `npm ci && npm run release:artifact` -- Artifact: `device-tools-0.1.0.zip` +- Artifact: `device-tools-0.2.0.zip` The lockfile pins dependencies exactly. Generated release manifests repeat this source identity and releases include detected runtime licence texts under `LICENSES/`. diff --git a/package-lock.json b/package-lock.json index c97df70..1ae3dc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "device-tools", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "device-tools", - "version": "0.1.0", + "version": "0.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", @@ -42,24 +42,24 @@ } }, "node_modules/@add-ideas/toolbox-contract": { - "version": "0.2.3", - "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.2.3/toolbox-contract-0.2.3.tgz", - "integrity": "sha512-T0PVSuMT40GjTDfQJhEEY3ZawQq8zz1/ry95JdKI6W39CdLacaRXdGnEpDCMHt+jUbf1Jz7Nat/M5dFCgKVM9A==", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.3.0/toolbox-contract-0.3.0.tgz", + "integrity": "sha512-dKrK7BjOFwqJaBfJuhKxZKIld4sH0AKjEn6a0yLnbdMUFY+fFv4VSLGV2tNSBD016gumc2iNqOjUj/ld7x4rtA==", "license": "Apache-2.0" }, "node_modules/@add-ideas/toolbox-helpers": { - "version": "0.1.0", - "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-helpers/-/0.1.0/toolbox-helpers-0.1.0.tgz", - "integrity": "sha512-UKl1Oxekedf8D2df86VrnVA53AcMhrnh6iUPXY+k8frirBXotb0yd8SGT+IF/3hcqYwcYe/v9WVFuSgKtIYVnw==", + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-helpers/-/0.2.0/toolbox-helpers-0.2.0.tgz", + "integrity": "sha512-SdOqkw+P+3J3fa5iVkzb5P15rVepB001GNV21Oh8w0CZcVL+YRltgD/s+MVcTyrNijWQf3E5vtQON/3N2LLyKg==", "license": "GPL-3.0-or-later" }, "node_modules/@add-ideas/toolbox-shell-react": { - "version": "0.2.3", - "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.2.3/toolbox-shell-react-0.2.3.tgz", - "integrity": "sha512-DT5lQDH48BFkFcmFLZnQh7+Cm73JzBPcmp5WzUXypfkUXpEyDYHzaXgmW4kZ0edSwh4RK4sPmx+JPtK0X4aKCQ==", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.3.0/toolbox-shell-react-0.3.0.tgz", + "integrity": "sha512-74p6JzAOG0YCAKdlc1hLofV4ZIko7vb448S75cIiM88PKm93EHl5VD7g8YVyfM56Ui97UY9dmy+Whiq4sGzpsg==", "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "peerDependencies": { "react": ">=18 <20", @@ -67,13 +67,13 @@ } }, "node_modules/@add-ideas/toolbox-testkit": { - "version": "0.2.3", - "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.2.3/toolbox-testkit-0.2.3.tgz", - "integrity": "sha512-sq1MwhKWfFKen+N+124hl74qQimRSvmQ9sOU7jdcI+2qCKZ67+2B8rWyezeV80uTFu4Jv6deHksfYQ/tKNV6XQ==", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.3.0/toolbox-testkit-0.3.0.tgz", + "integrity": "sha512-4Fk+oSvZFspOMIXr8Xy040nhAaBsIQAzsGyXWSpjn3+k3yBKq7nB1r5zCHhsXzfdLzvPDAx2KcmSNOhM330D9w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "bin": { "toolbox-check": "dist/cli.js" diff --git a/package.json b/package.json index e989a98..ba4cf83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "device-tools", - "version": "0.1.0", + "version": "0.2.0", "description": "Inspect browser capabilities and privacy-safe device probes locally.", "license": "GPL-3.0-or-later", "author": "Albrecht Degering", @@ -39,14 +39,14 @@ "release:artifact": "npm run check && npm run test:browser && npm run package:release -- --force" }, "dependencies": { - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", diff --git a/playwright.config.ts b/playwright.config.ts index 7d2a637..2a50369 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,7 +15,25 @@ export default defineConfig({ timeout: 180_000, }, projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { + name: "chromium", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Safari"] }, + }, + { + name: "mobile-chromium", + testMatch: /responsive\.spec\.ts/, + use: { ...devices["Pixel 5"] }, + }, ], }); diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index cdd644c..f4f05b0 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added Toolbox manifest compatibility analysis for legacy requirements and + required/optional capability profiles with explainable unknowns. +- Added an explicitly initiated MediaCapabilities codec lab and downloadable, + bucketed privacy-preserving evidence. + ## 0.1.0 - 2026-09-01 - Added passive display, input, codec, media-constraint, PWA and browser API inventory. diff --git a/public/LICENSES/npm-runtime-licenses.txt b/public/LICENSES/npm-runtime-licenses.txt index 45a7c95..4f412f1 100644 --- a/public/LICENSES/npm-runtime-licenses.txt +++ b/public/LICENSES/npm-runtime-licenses.txt @@ -1,5 +1,5 @@ ============================================================================== -@add-ideas/toolbox-contract@0.2.3 +@add-ideas/toolbox-contract@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- @@ -198,7 +198,7 @@ Declared licence: Apache-2.0 ============================================================================== -@add-ideas/toolbox-helpers@0.1.0 +@add-ideas/toolbox-helpers@0.2.0 Declared licence: GPL-3.0-or-later ============================================================================== --- LICENSE --- @@ -879,7 +879,7 @@ Public License instead of this License. But first, please read ============================================================================== -@add-ideas/toolbox-shell-react@0.2.3 +@add-ideas/toolbox-shell-react@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- diff --git a/public/README.md b/public/README.md index bc0c3b6..8c93e1c 100644 --- a/public/README.md +++ b/public/README.md @@ -6,6 +6,9 @@ A local browser capability and privacy lab. It shows useful platform evidence wi - Passive inventory for secure/top-level context, display/viewport/DPR, touch/pointer/hover, accessibility media queries, media constraints, codec declarations, storage/PWA and browser device-API presence. - Explicit individual probes for permission state, storage estimate/persistence, WebGL limits, WebGPU adapter presence, media-device counts, battery, network hints and camera/microphone/screen access. +- Explainable failures that distinguish a missing API, insecure or embedded context, Permissions Policy, user permission, and unavailable/busy operating-system devices, with concrete remediation. +- Toolbox application compatibility analysis from pasted manifest JSON, including legacy requirements plus required/optional capability profiles and unknown-capability evidence. +- An explicit MediaCapabilities decoding lab for video/audio codec, dimensions, rate and bitrate, with support/smoothness/power-efficiency results and a separately downloadable bucketed report. - Camera, microphone and screen streams are never recorded and every returned track stops immediately. - WebGL deliberately avoids unmasked vendor/renderer extensions; media device IDs and labels are discarded. - Deterministic redacted JSON/CSV reports with broad buckets for high-entropy values and no timestamp, user agent, locale, identifier or score. @@ -13,13 +16,13 @@ A local browser capability and privacy lab. It shows useful platform evidence wi ## Privacy boundary -The passive inventory runs locally on load and does not request permission. Optional probes run only after their named button is pressed; buttons labelled “may prompt” can show browser or operating-system UI. The application performs no canvas/audio sample fingerprinting, persistent storage, telemetry or external request. +The passive inventory runs locally on load and does not request permission. Optional probes run only after their named button is pressed; buttons labelled “may prompt” can show browser or operating-system UI. Application manifests are treated as inert JSON and no referenced code is loaded. MediaCapabilities checks are likewise opt-in because smoothness and power-efficiency answers can depend on the device. The application performs no canvas/audio sample fingerprinting, persistent storage, telemetry or external request. Downloaded reports are deliberately less precise than the on-screen local view. They bucket exact dimensions, pixel ratio, storage, graphics limits, hardware counts, battery timing and connection quality. This is a diagnostics report, not proof that an API will work in every embedded or enterprise-policy context. ## Development -Use Node.js 22+ and npm 11. Run `npm ci`, `npm run check`, `npm run test:browser`, then `npm run package:release -- --force`. The deterministic artifact is `release/device-tools-0.1.0.zip` with a SHA-256 sidecar. +Use Node.js 22+ and npm 11. Run `npm ci`, `npm run check`, `npm run test:browser`, then `npm run package:release -- --force`. The deterministic artifact is `release/device-tools-0.2.0.zip` with a SHA-256 sidecar. ## Licence diff --git a/public/SOURCE.md b/public/SOURCE.md index ef1f6a3..3c07db5 100644 --- a/public/SOURCE.md +++ b/public/SOURCE.md @@ -1,10 +1,10 @@ # Source identity - Project: Device Tools -- Version: 0.1.0 +- Version: 0.2.0 - Repository: https://git.add-ideas.de/lotobo/device-tools - Licence: GPL-3.0-or-later - Build: Node.js 22+, npm 11, `npm ci && npm run release:artifact` -- Artifact: `device-tools-0.1.0.zip` +- Artifact: `device-tools-0.2.0.zip` The lockfile pins dependencies exactly. Generated release manifests repeat this source identity and releases include detected runtime licence texts under `LICENSES/`. diff --git a/public/sw.js b/public/sw.js index 208ab06..7e8347c 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ const CACHE_PREFIX = "device-tools-shell-"; -const CACHE_NAME = CACHE_PREFIX + "0.1.0"; +const CACHE_NAME = CACHE_PREFIX + "0.2.0"; const CORE = ["./", "./manifest.webmanifest", "./favicon.svg"]; self.addEventListener("install", (event) => { event.waitUntil( diff --git a/public/toolbox-app.json b/public/toolbox-app.json index 112d8a7..5e8ade9 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -3,7 +3,7 @@ "schemaVersion": 1, "id": "de.add-ideas.device-tools", "name": "Device Tools", - "version": "0.1.0", + "version": "0.2.0", "description": "Inspect capabilities without fingerprinting.", "entry": "./", "icon": "./favicon.svg", @@ -28,6 +28,20 @@ "crossOriginIsolated": false, "topLevelContext": false }, + "io": { + "accepts": [ + { + "mediaType": "application/manifest+json", + "extensions": [".webmanifest"] + }, + { "mediaType": "application/json", "extensions": [".json"] } + ], + "produces": [{ "mediaType": "application/json", "extensions": [".json"] }] + }, + "capabilities": { + "required": [], + "optional": ["media-capabilities", "permissions-api", "secure-context"] + }, "privacy": { "processing": "local", "fileUploads": false, diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx index 7082292..7acf70c 100644 --- a/src/components/HelpDialog.tsx +++ b/src/components/HelpDialog.tsx @@ -41,6 +41,13 @@ export function HelpDialog({ microphone, screen and persistence checks can prompt; returned media tracks are stopped immediately and no content is recorded.

+

+ The compatibility panel reads pasted Toolbox manifests as inert JSON and + explains whether each required or optional browser capability is + available. The MediaCapabilities lab checks only the codec profile you + explicitly submit; its downloadable form replaces exact performance + parameters with broad buckets. +

Reports contain no timestamp, user agent, locale, identifier, hash or uniqueness score. Exact high-entropy measurements are bucketed before diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx index fa2ede8..ac0b417 100644 --- a/src/components/Workbench.tsx +++ b/src/components/Workbench.tsx @@ -3,15 +3,38 @@ import { triggerBlobDownload } from "@add-ideas/toolbox-helpers"; import { collectPassiveCapabilities, createRedactedReport, + evaluateRequirementProfile, + exportMediaCapabilityResult, exportReportCsv, exportReportJson, + parseRequirementProfile, PROBES, + probeMediaCapabilities, runProbe, type Capability, + type MediaCapabilityResult, + type MediaProbeConfig, type ProbeId, type ProbeResult, + type RequirementProfile, } from "../core/device"; +const EXAMPLE_MANIFEST = `{ + "id": "de.add-ideas.example-tool", + "name": "Example Tool", + "requirements": { + "secureContext": true, + "workers": true, + "indexedDb": false, + "crossOriginIsolated": false, + "topLevelContext": false + }, + "capabilities": { + "required": ["file-system"], + "optional": ["webgpu-api", "media-capabilities"] + } +}`; + export function Workbench() { const [capabilities, setCapabilities] = useState(() => collectPassiveCapabilities(), @@ -21,6 +44,23 @@ export function Workbench() { ); const [running, setRunning] = useState(null); const [filter, setFilter] = useState(""); + const [profileSource, setProfileSource] = useState(EXAMPLE_MANIFEST); + const [profile, setProfile] = useState(null); + const [profileError, setProfileError] = useState(""); + const [mediaKind, setMediaKind] = useState<"video" | "audio">("video"); + const [mediaContentType, setMediaContentType] = useState( + 'video/mp4; codecs="avc1.42E01E"', + ); + const [mediaWidth, setMediaWidth] = useState(1920); + const [mediaHeight, setMediaHeight] = useState(1080); + const [mediaBitrate, setMediaBitrate] = useState(8_000_000); + const [mediaFramerate, setMediaFramerate] = useState(30); + const [mediaChannels, setMediaChannels] = useState("2"); + const [mediaSampleRate, setMediaSampleRate] = useState(48_000); + const [mediaResult, setMediaResult] = useState( + null, + ); + const [mediaRunning, setMediaRunning] = useState(false); const [status, setStatus] = useState( "Passive API presence was checked locally. No permission was requested.", ); @@ -50,6 +90,10 @@ export function Workbench() { const available = capabilities.filter( (item) => item.state === "available", ).length; + const requirementEvaluation = useMemo( + () => (profile ? evaluateRequirementProfile(profile, capabilities) : null), + [profile, capabilities], + ); async function performProbe(id: ProbeId): Promise { setRunning(id); @@ -60,6 +104,53 @@ export function Workbench() { setStatus(result.summary); } + function inspectProfile(): void { + try { + setProfile(parseRequirementProfile(profileSource)); + setProfileError(""); + } catch (caught) { + setProfile(null); + setProfileError( + caught instanceof Error + ? caught.message + : "The profile could not be read.", + ); + } + } + + async function inspectMediaConfiguration(): Promise { + setMediaRunning(true); + setMediaResult(null); + try { + const configuration: MediaProbeConfig = + mediaKind === "video" + ? { + kind: "video", + contentType: mediaContentType, + width: mediaWidth, + height: mediaHeight, + bitrate: mediaBitrate, + framerate: mediaFramerate, + } + : { + kind: "audio", + contentType: mediaContentType, + channels: mediaChannels, + bitrate: mediaBitrate, + samplerate: mediaSampleRate, + }; + const result = await probeMediaCapabilities(configuration); + setMediaResult(result); + setStatus(result.summary); + } catch (caught) { + setStatus( + caught instanceof Error ? caught.message : "The media probe failed.", + ); + } finally { + setMediaRunning(false); + } + } + return (

@@ -153,6 +244,12 @@ export function Workbench() { {item.value} + {item.state !== "available" && item.availability && ( + + {item.availability} + {item.remediation ? ` · ${item.remediation}` : ""} + + )} ))} @@ -167,6 +264,185 @@ export function Workbench() {
+
+
+
+

Application compatibility

+

Will this Toolbox app work here?

+
+ {requirementEvaluation && ( + + {requirementEvaluation.status} + + )} +
+

+ Paste a Toolbox manifest or capability profile. The file is parsed + locally and matched against the passive inventory; no application code + is loaded. +

+
+