74b5192110
Defect fixes ahead of relaunch — no redesign, no new copy, no media. Accessibility: - The mobile menu declares `aria-modal`, which promises the dialog owns interaction, but focus never entered it and never came back. Focus now moves inside on open and returns to the toggle on close. The toggle node is captured before cleanup rather than read from the ref inside it, which would race React clearing it. - The terminal player had `role="tablist"` with none of the pattern behind it. Adds arrow-key/Home/End movement, a roving tabindex, and aria-controls/aria-labelledby wiring between each tab and the panel. - Context text ran at white/45 on ink — 4.32:1, below the 4.5:1 WCAG AA floor for normal text. Now white/55, 5.78:1. Metadata: - SoftwareApplication JSON-LD on the home page. Every field traces to a repo-sourced fact already rendered on the page; `softwareVersion` is omitted rather than guessed when facts carry none. - `id` was missing from the OpenGraph locale map, so Indonesian pages emitted no og:locale. Drafted by Kimi K3 in Codewhale exec, reviewed here. The claimed contrast ratio was recomputed independently (5.78:1 vs its stated 5.75:1 — a rounding difference against the exact ink token, and the before value does genuinely fail AA). Gates re-run here: 250 tests, eslint clean. Note for follow-up, not introduced by this change: `npm audit` in web/ now reports 1 high-severity js-yaml advisory. The release contract requires 0.
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import {
|
|
detectFromBrowserSignals,
|
|
type Arch,
|
|
type UserAgentArchitecture,
|
|
} from "@/lib/install-platform";
|
|
import { SNIPPETS, VERIFY } from "@/lib/install-binary-snippets";
|
|
import { InstallCodeBlock } from "./install-code-block";
|
|
|
|
const LABELS: Record<Arch, string> = {
|
|
"macos-arm64": "macOS · Apple Silicon",
|
|
"macos-x64": "macOS · Intel",
|
|
"linux-x64": "Linux · x64",
|
|
"linux-arm64": "Linux · arm64",
|
|
"windows-x64": "Windows · x64",
|
|
"windows-arm64": "Windows · arm64",
|
|
};
|
|
|
|
interface NavigatorWithUserAgentData extends Navigator {
|
|
userAgentData?: {
|
|
getHighEntropyValues(hints: string[]): Promise<UserAgentArchitecture>;
|
|
};
|
|
}
|
|
|
|
async function detect(): Promise<Arch> {
|
|
if (typeof navigator === "undefined") return "macos-arm64";
|
|
const browserNavigator = navigator as NavigatorWithUserAgentData;
|
|
let architecture: UserAgentArchitecture | undefined;
|
|
if (navigator.userAgent.toLowerCase().includes("win")) {
|
|
try {
|
|
architecture = await browserNavigator.userAgentData?.getHighEntropyValues([
|
|
"architecture",
|
|
"bitness",
|
|
]);
|
|
} catch {
|
|
// The manual platform buttons and frozen-UA fallback remain available.
|
|
}
|
|
}
|
|
return detectFromBrowserSignals(navigator.userAgent, architecture);
|
|
}
|
|
|
|
interface Props {
|
|
copyLabel?: string;
|
|
copiedLabel?: string;
|
|
verifyHeading?: string;
|
|
}
|
|
|
|
export function InstallBinary({ copyLabel, copiedLabel, verifyHeading = "Verify checksum" }: Props) {
|
|
const [arch, setArch] = useState<Arch>("macos-arm64");
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
void detect().then((detected) => {
|
|
if (active) setArch(detected);
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex flex-wrap gap-0 mb-3 hairline-t hairline-b hairline-l hairline-r">
|
|
{(Object.keys(SNIPPETS) as Arch[]).map((a, i) => (
|
|
<button
|
|
key={a}
|
|
type="button"
|
|
onClick={() => setArch(a)}
|
|
aria-pressed={arch === a}
|
|
className={`px-3 py-1.5 font-mono text-[0.7rem] tracking-wider transition-colors ${
|
|
i > 0 ? "hairline-l" : ""
|
|
} ${arch === a ? "bg-ink text-paper" : "bg-paper hover:bg-paper-deep"}`}
|
|
>
|
|
{LABELS[a]}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<InstallCodeBlock cmd={SNIPPETS[arch]} copyLabel={copyLabel} copiedLabel={copiedLabel} />
|
|
|
|
<div className="mt-4">
|
|
<div className="eyebrow mb-2">{verifyHeading}</div>
|
|
<InstallCodeBlock cmd={VERIFY[arch]} copyLabel={copyLabel} copiedLabel={copiedLabel} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|