Release Device Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 13:08:36 +02:00
parent 06f697e180
commit 0ae5c7b7af
21 changed files with 1490 additions and 50 deletions
+7
View File
@@ -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.
</p>
<p>
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.
</p>
<p>
Reports contain no timestamp, user agent, locale, identifier, hash or
uniqueness score. Exact high-entropy measurements are bucketed before
+329
View File
@@ -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<Capability[]>(() =>
collectPassiveCapabilities(),
@@ -21,6 +44,23 @@ export function Workbench() {
);
const [running, setRunning] = useState<ProbeId | null>(null);
const [filter, setFilter] = useState("");
const [profileSource, setProfileSource] = useState(EXAMPLE_MANIFEST);
const [profile, setProfile] = useState<RequirementProfile | null>(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<MediaCapabilityResult | null>(
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<void> {
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<void> {
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 (
<main className="workbench">
<section className="hero">
@@ -153,6 +244,12 @@ export function Workbench() {
<td>{item.value}</td>
<td>
<Risk value={item.privacy} />
{item.state !== "available" && item.availability && (
<small>
{item.availability}
{item.remediation ? ` · ${item.remediation}` : ""}
</small>
)}
</td>
</tr>
))}
@@ -167,6 +264,185 @@ export function Workbench() {
</div>
</section>
<section className="panel" aria-labelledby="compatibility-title">
<div className="panel-heading">
<div>
<p className="eyebrow">Application compatibility</p>
<h2 id="compatibility-title">Will this Toolbox app work here?</h2>
</div>
{requirementEvaluation && (
<span
className={`compatibility-status ${requirementEvaluation.status}`}
>
{requirementEvaluation.status}
</span>
)}
</div>
<p className="muted">
Paste a Toolbox manifest or capability profile. The file is parsed
locally and matched against the passive inventory; no application code
is loaded.
</p>
<div className="compatibility-layout">
<label>
Manifest JSON
<textarea
value={profileSource}
onChange={(event) => setProfileSource(event.target.value)}
spellCheck={false}
rows={13}
/>
</label>
<div className="compatibility-output">
<button type="button" onClick={inspectProfile}>
Analyse requirements
</button>
{profileError && (
<p className="validation-error" role="alert">
{profileError}
</p>
)}
{requirementEvaluation && (
<>
<p>
<strong>{requirementEvaluation.profile.name}</strong> is{" "}
<strong>{requirementEvaluation.status}</strong> in this
context.
</p>
{requirementEvaluation.requirements.length === 0 ? (
<p className="muted">The profile declares no requirements.</p>
) : (
<ul className="requirement-list">
{requirementEvaluation.requirements.map((item) => (
<li key={`${item.importance}-${item.id}`}>
<span>
<State state={item.state} />
<strong>{item.label}</strong>
<small>{item.importance}</small>
</span>
<p>{item.reason}</p>
{item.remediation && <p>{item.remediation}</p>}
</li>
))}
</ul>
)}
</>
)}
</div>
</div>
</section>
<section className="panel" aria-labelledby="media-capabilities-title">
<div className="panel-heading">
<div>
<p className="eyebrow">Opt-in decoding check</p>
<h2 id="media-capabilities-title">MediaCapabilities lab</h2>
</div>
<Risk value="high" />
</div>
<p className="muted">
Ask the browser about one exact local decoding configuration. This can
reveal device-dependent performance characteristics, so it never runs
automatically and exported numbers are bucketed.
</p>
<div className="media-form">
<label>
Media kind
<select
value={mediaKind}
onChange={(event) => {
const kind = event.target.value as "video" | "audio";
setMediaKind(kind);
setMediaContentType(
kind === "video"
? 'video/mp4; codecs="avc1.42E01E"'
: 'audio/webm; codecs="opus"',
);
setMediaBitrate(kind === "video" ? 8_000_000 : 192_000);
setMediaResult(null);
}}
>
<option value="video">Video</option>
<option value="audio">Audio</option>
</select>
</label>
<label className="wide-field">
MIME type and codec
<input
value={mediaContentType}
onChange={(event) => setMediaContentType(event.target.value)}
spellCheck={false}
/>
</label>
{mediaKind === "video" ? (
<>
<NumberField
label="Width"
value={mediaWidth}
onChange={setMediaWidth}
/>
<NumberField
label="Height"
value={mediaHeight}
onChange={setMediaHeight}
/>
<NumberField
label="Frames per second"
value={mediaFramerate}
onChange={setMediaFramerate}
/>
</>
) : (
<>
<label>
Channels
<input
inputMode="numeric"
value={mediaChannels}
onChange={(event) => setMediaChannels(event.target.value)}
/>
</label>
<NumberField
label="Sample rate (Hz)"
value={mediaSampleRate}
onChange={setMediaSampleRate}
/>
</>
)}
<NumberField
label="Bitrate (bit/s)"
value={mediaBitrate}
onChange={setMediaBitrate}
/>
</div>
<div className="button-row">
<button
type="button"
className="primary-button"
disabled={mediaRunning}
onClick={() => void inspectMediaConfiguration()}
>
{mediaRunning ? "Checking…" : "Check this configuration"}
</button>
{mediaResult && (
<button
type="button"
onClick={() =>
download(
new Blob([exportMediaCapabilityResult(mediaResult)], {
type: "application/json",
}),
"media-capability-redacted.json",
)
}
>
Download bucketed result
</button>
)}
</div>
{mediaResult && <MediaOutcome result={mediaResult} />}
</section>
<section className="panel" aria-labelledby="probes-title">
<div className="panel-heading">
<div>
@@ -257,11 +533,64 @@ export function Workbench() {
);
}
function NumberField({
label,
value,
onChange,
}: {
label: string;
value: number;
onChange: (value: number) => void;
}) {
return (
<label>
{label}
<input
type="number"
value={value}
onChange={(event) => onChange(event.target.valueAsNumber)}
/>
</label>
);
}
function MediaOutcome({ result }: { result: MediaCapabilityResult }) {
return (
<div className={`media-result ${result.state}`} role="status">
<strong>{result.state}</strong>
<p>{result.summary}</p>
{result.supported !== undefined && (
<dl>
<div>
<dt>Supported</dt>
<dd>{result.supported ? "Yes" : "No"}</dd>
</div>
<div>
<dt>Smooth</dt>
<dd>{result.smooth ? "Yes" : "No"}</dd>
</div>
<div>
<dt>Power efficient</dt>
<dd>{result.powerEfficient ? "Yes" : "No"}</dd>
</div>
</dl>
)}
{result.remediation && <p>{result.remediation}</p>}
</div>
);
}
function ProbeOutcome({ result }: { result: ProbeResult }) {
return (
<div className={"probe-result " + result.state}>
<strong>{result.state}</strong>
<p>{result.summary}</p>
{result.availability && result.availability !== "available" && (
<p>
<strong>Reason:</strong> {result.availability}
{result.remediation ? ` · ${result.remediation}` : ""}
</p>
)}
{result.details.length > 0 && (
<dl>
{result.details.map((detail) => (