Recovery Packages are encrypted but security-sensitive offline Passphrase-guessing targets. Do not upload them, attach them to Issues/PRs, or commit them to a repository.
-
-
-
-
-
-
-
-
-
-
+
+
Recovery Package
+
Recovery Packages are encrypted but security-sensitive offline Passphrase-guessing targets. Do not upload them, attach them to Issues/PRs, or commit them to a repository.
Flash the CI-built M5StickS3 firmware directly from this site. Firmware images are same-origin static files; authenticator credentials are never part of a firmware package.
-
+
`;
From e2313250f40850a2c4a345444140179550ed1e88 Mon Sep 17 00:00:00 2001
From: Miso Tanaka <22117028+miso-develop@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:56:27 +0900
Subject: [PATCH 4/8] test(web): enforce sticky header and single-divider
layout contracts
---
.../browser/post-provisioning-layout-smoke.ts | 119 +++++++++++++++++-
1 file changed, 117 insertions(+), 2 deletions(-)
diff --git a/web/tests/browser/post-provisioning-layout-smoke.ts b/web/tests/browser/post-provisioning-layout-smoke.ts
index 5b77e8d..9a60479 100644
--- a/web/tests/browser/post-provisioning-layout-smoke.ts
+++ b/web/tests/browser/post-provisioning-layout-smoke.ts
@@ -35,6 +35,7 @@ export async function runPostProvisioningLayoutSmoke(): Promise {
await verifyProductionProvisioningLifecycle();
await verifyProductionFirmwareGeometry();
await verifySharedRouteGeometryPolicy();
+ await verifyStickyHeaderPolicy();
document.body.dataset.postProvisioningLayoutStatus = "pass";
} catch (error) {
document.body.dataset.postProvisioningLayoutError = error instanceof Error ? error.message : "Unknown layout smoke failure";
@@ -318,6 +319,25 @@ async function verifyProductionProvisioningLifecycle(): Promise {
assert(parseFloat(style.paddingTop) >= 32, `Provisioning section ${index + 1} requires at least 32px boundary padding`);
}
});
+ const recoverySubsection = required(shell, '[data-security-subsection="recovery-package"]');
+ const passphraseSubsection = required(shell, '[data-security-subsection="change-passphrase"]');
+ for (const [name, subsection] of [
+ ["Recovery Package", recoverySubsection],
+ ["Change Recovery Passphrase", passphraseSubsection],
+ ] as const) {
+ const heading = subsection.querySelector(":scope > h3");
+ assert(heading?.textContent === name, `${name} must retain its semantic h3 heading`);
+ const style = getComputedStyle(subsection);
+ assert(style.borderTopStyle !== "none" && parseFloat(style.borderTopWidth) >= 1, `${name} must have one visible subsection divider`);
+ assert(parseFloat(style.marginTop) > 0 && parseFloat(style.paddingTop) > 0, `${name} divider must retain subsection spacing`);
+ assert(subsection.querySelector(":scope > hr") === null, `${name} must not duplicate the CSS divider with an hr`);
+ assert(getComputedStyle(heading).marginTop === "0px", `${name} heading must start immediately after the shared divider spacing`);
+ }
+ assert(
+ getComputedStyle(recoverySubsection).borderTopColor === getComputedStyle(passphraseSubsection).borderTopColor,
+ "Recovery subsections must share the same neutral divider treatment",
+ );
+
const regularDivider = getComputedStyle(majorSections[0]!).borderTopColor;
const dangerDivider = getComputedStyle(required(shell, "section.danger")).borderTopColor;
assert(dangerDivider !== regularDivider, "Factory Reset must retain its danger-tinted divider semantics");
@@ -561,6 +581,27 @@ async function verifyProductionFirmwareGeometry(): Promise {
assert(status.querySelectorAll("button").length === 2, "Successful Firmware resolution must render First install and Update controls");
assert(buildIdentity.textContent?.includes(FIRMWARE_SMOKE_BUILD_COMMIT) === true, "Successful Firmware resolution must display the pinned fixture build identity");
+ const flashChoices = Array.from(status.querySelectorAll(":scope > section.panel"));
+ assert(flashChoices.length === 2, "Firmware status must contain exactly First install and Update peer panels");
+ const statusStyle = getComputedStyle(status);
+ assert(
+ statusStyle.borderTopStyle === "none" || parseFloat(statusStyle.borderTopWidth) === 0,
+ "Firmware structural status container must not add a duplicate top divider",
+ );
+ const firstInstallStyle = getComputedStyle(flashChoices[0]!);
+ const updateStyle = getComputedStyle(flashChoices[1]!);
+ assert(
+ firstInstallStyle.borderTopStyle !== "none" && parseFloat(firstInstallStyle.borderTopWidth) >= 1,
+ "First install must have exactly one panel boundary supplied by its own section",
+ );
+ assert(
+ updateStyle.borderTopStyle !== "none" && parseFloat(updateStyle.borderTopWidth) >= 1,
+ "Update must retain exactly one panel boundary",
+ );
+ assert(parseFloat(firstInstallStyle.marginTop) === 0, "First install must not retain redundant nested-panel top spacing");
+ assert(parseFloat(updateStyle.marginTop) > 0, "Update must remain visually separated from First install");
+ assert(status.querySelector("hr") === null, "Firmware divider normalization must not introduce decorative hr elements");
+
const afterShell = shell.getBoundingClientRect();
const afterNav = nav.getBoundingClientRect();
const afterBuildSection = buildSection.getBoundingClientRect();
@@ -616,6 +657,80 @@ async function verifySharedRouteGeometryPolicy(): Promise {
}
}
+async function verifyStickyHeaderPolicy(): Promise {
+ const routes = ["index.html", "flash.html", "help.html"] as const;
+ const frames: HTMLIFrameElement[] = [];
+ try {
+ for (const route of routes) frames.push(await loadRouteFrame(route, 360));
+
+ for (let index = 0; index < frames.length; index += 1) {
+ const frame = frames[index]!;
+ const route = routes[index]!;
+ const doc = requiredFrameDocument(frame);
+ const win = requiredFrameWindow(frame);
+ const header = required(doc, "#site-header");
+ const nav = required(doc, ".site-nav-shell");
+ const product = required(doc, ".product-mark");
+ const tabs = required(doc, ".site-tabs");
+ const language = required(doc, ".language-switcher");
+ flushLayout(doc);
+
+ const headerStyle = getComputedStyle(header);
+ assert(headerStyle.position === "sticky", `${route} must use the shared sticky header contract`);
+ assert(headerStyle.top === "0px", `${route} sticky header must pin to viewport top`);
+ assert(Number.parseInt(headerStyle.zIndex, 10) > 0, `${route} sticky header needs positive stacking order`);
+ assert(headerStyle.backgroundColor !== "rgba(0, 0, 0, 0)", `${route} sticky header background must be opaque/readable`);
+ assert(header.contains(nav) && nav.contains(product) && nav.contains(tabs) && nav.contains(language), `${route} sticky header must retain product, tabs, and language switcher`);
+ assert(doc.documentElement.scrollHeight > win.innerHeight, `${route} sticky-header fixture must be vertically scrollable`);
+
+ const beforeNav = nav.getBoundingClientRect();
+ const beforeShell = required(doc, ".shell").getBoundingClientRect();
+ const beforeScrollWidth = doc.documentElement.scrollWidth;
+ win.scrollTo(0, Math.min(480, doc.documentElement.scrollHeight - win.innerHeight));
+ await new Promise((resolve) => window.setTimeout(resolve, 20));
+ flushLayout(doc);
+
+ const afterHeader = header.getBoundingClientRect();
+ const afterNav = nav.getBoundingClientRect();
+ const afterShell = required(doc, ".shell").getBoundingClientRect();
+ assert(near(afterHeader.top, 0), `${route} sticky header did not remain at viewport top while scrolling`);
+ assert(near(beforeNav.left, afterNav.left) && near(beforeNav.width, afterNav.width), `${route} sticky navigation shifted horizontally while scrolling`);
+ assert(near(beforeShell.left, afterShell.left) && near(beforeShell.width, afterShell.width), `${route} content shell shifted horizontally while scrolling`);
+ assert(doc.documentElement.scrollWidth === beforeScrollWidth && doc.documentElement.scrollWidth <= win.innerWidth, `${route} sticky header introduced horizontal overflow or width jitter`);
+ }
+
+ const provisioningDoc = requiredFrameDocument(frames[0]!);
+ const headerZ = Number.parseInt(getComputedStyle(required(provisioningDoc, "#site-header")).zIndex, 10);
+ const presenceOverlay = required(provisioningDoc, ".presence-overlay");
+ const overlayZ = Number.parseInt(getComputedStyle(presenceOverlay).zIndex, 10);
+ assert(overlayZ > headerZ, "Security presence overlay must retain stacking priority over the sticky header");
+
+ const responsiveFrame = await loadRouteFrame("help.html", 360, 540);
+ frames.push(responsiveFrame);
+ const responsiveDoc = requiredFrameDocument(responsiveFrame);
+ const responsiveWin = requiredFrameWindow(responsiveFrame);
+ flushLayout(responsiveDoc);
+ const responsiveHeader = required(responsiveDoc, "#site-header");
+ const responsiveNav = required(responsiveDoc, ".site-nav-shell");
+ const responsiveTabs = required(responsiveDoc, ".site-tabs");
+ const responsiveLanguage = required(responsiveDoc, ".language-switcher");
+ const responsiveProduct = required(responsiveDoc, ".product-mark");
+ assert(getComputedStyle(responsiveTabs).gridRowStart === "2", "Responsive top-level tabs must retain the shared second-row layout");
+ for (const element of [responsiveProduct, responsiveTabs, responsiveLanguage]) {
+ const rect = element.getBoundingClientRect();
+ const headerRect = responsiveHeader.getBoundingClientRect();
+ assert(rect.left >= headerRect.left - GEOMETRY_EPSILON_PX && rect.right <= headerRect.right + GEOMETRY_EPSILON_PX, "Responsive header controls must remain horizontally visible");
+ }
+ assert(responsiveDoc.documentElement.scrollWidth <= responsiveWin.innerWidth, "Responsive sticky header must not create a horizontal scrollbar");
+ responsiveWin.scrollTo(0, 300);
+ await new Promise((resolve) => window.setTimeout(resolve, 20));
+ assert(near(responsiveHeader.getBoundingClientRect().top, 0), "Responsive two-row header must remain sticky while scrolling");
+ assert(responsiveNav.getBoundingClientRect().width > 0, "Responsive navigation must remain rendered after scrolling");
+ } finally {
+ for (const frame of frames) frame.remove();
+ }
+}
+
function createSnapshot(
provisioned: boolean,
factoryResetPresenceRequired = false,
@@ -669,9 +784,9 @@ function createSnapshot(
};
}
-async function loadRouteFrame(path: string, heightPx: number): Promise {
+async function loadRouteFrame(path: string, heightPx: number, widthPx = ROUTE_WIDTH_PX): Promise {
const frame = document.createElement("iframe");
- frame.width = String(ROUTE_WIDTH_PX);
+ frame.width = String(widthPx);
frame.height = String(heightPx);
frame.style.position = "fixed";
frame.style.left = "-20000px";
From d62e4ec7c43f2e05ed848245fb3cf7da667fc72d Mon Sep 17 00:00:00 2001
From: Miso Tanaka <22117028+miso-develop@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:56:41 +0900
Subject: [PATCH 5/8] refactor(web): keep Firmware status container structural
---
web/src/flasher.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/flasher.ts b/web/src/flasher.ts
index fc857d2..e292df6 100644
--- a/web/src/flasher.ts
+++ b/web/src/flasher.ts
@@ -22,7 +22,7 @@ app.innerHTML = `
Flash the CI-built M5StickS3 firmware directly from this site. Firmware images are same-origin static files; authenticator credentials are never part of a firmware package.
-
+
`;
From f837486f4e7e479c120fa5a576f30a56fb4cebeb Mon Sep 17 00:00:00 2001
From: Miso Tanaka <22117028+miso-develop@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:58:42 +0900
Subject: [PATCH 6/8] test(web): verify Recovery dividers on full production
route
---
.../browser/post-provisioning-layout-smoke.ts | 39 ++++++++++---------
1 file changed, 20 insertions(+), 19 deletions(-)
diff --git a/web/tests/browser/post-provisioning-layout-smoke.ts b/web/tests/browser/post-provisioning-layout-smoke.ts
index 9a60479..480a139 100644
--- a/web/tests/browser/post-provisioning-layout-smoke.ts
+++ b/web/tests/browser/post-provisioning-layout-smoke.ts
@@ -319,25 +319,6 @@ async function verifyProductionProvisioningLifecycle(): Promise {
assert(parseFloat(style.paddingTop) >= 32, `Provisioning section ${index + 1} requires at least 32px boundary padding`);
}
});
- const recoverySubsection = required(shell, '[data-security-subsection="recovery-package"]');
- const passphraseSubsection = required(shell, '[data-security-subsection="change-passphrase"]');
- for (const [name, subsection] of [
- ["Recovery Package", recoverySubsection],
- ["Change Recovery Passphrase", passphraseSubsection],
- ] as const) {
- const heading = subsection.querySelector(":scope > h3");
- assert(heading?.textContent === name, `${name} must retain its semantic h3 heading`);
- const style = getComputedStyle(subsection);
- assert(style.borderTopStyle !== "none" && parseFloat(style.borderTopWidth) >= 1, `${name} must have one visible subsection divider`);
- assert(parseFloat(style.marginTop) > 0 && parseFloat(style.paddingTop) > 0, `${name} divider must retain subsection spacing`);
- assert(subsection.querySelector(":scope > hr") === null, `${name} must not duplicate the CSS divider with an hr`);
- assert(getComputedStyle(heading).marginTop === "0px", `${name} heading must start immediately after the shared divider spacing`);
- }
- assert(
- getComputedStyle(recoverySubsection).borderTopColor === getComputedStyle(passphraseSubsection).borderTopColor,
- "Recovery subsections must share the same neutral divider treatment",
- );
-
const regularDivider = getComputedStyle(majorSections[0]!).borderTopColor;
const dangerDivider = getComputedStyle(required(shell, "section.danger")).borderTopColor;
assert(dangerDivider !== regularDivider, "Factory Reset must retain its danger-tinted divider semantics");
@@ -639,6 +620,26 @@ async function verifySharedRouteGeometryPolicy(): Promise {
const productionPanels = Array.from(provisioningShell.querySelectorAll(":scope > section.panel"));
assert(productionPanels.length >= 7, "Production Provisioning route must keep all major peer sections on the shared panel contract");
assert(provisioningShell.lastElementChild === productionBuildSection, "Production Web Build information section must remain the final Provisioning peer section");
+
+ const recoverySubsection = required(provisioningShell, '[data-security-subsection="recovery-package"]');
+ const passphraseSubsection = required(provisioningShell, '[data-security-subsection="change-passphrase"]');
+ for (const [name, subsection] of [
+ ["Recovery Package", recoverySubsection],
+ ["Change Recovery Passphrase", passphraseSubsection],
+ ] as const) {
+ const heading = subsection.querySelector(":scope > h3");
+ assert(sourceTextOf(heading!) === name, `${name} must retain its semantic localized h3 heading source`);
+ const style = getComputedStyle(subsection);
+ assert(style.borderTopStyle !== "none" && parseFloat(style.borderTopWidth) >= 1, `${name} must have one visible subsection divider`);
+ assert(parseFloat(style.marginTop) > 0 && parseFloat(style.paddingTop) > 0, `${name} divider must retain subsection spacing`);
+ assert(subsection.querySelector(":scope > hr") === null, `${name} must not duplicate the CSS divider with an hr`);
+ assert(getComputedStyle(heading!).marginTop === "0px", `${name} heading must start immediately after the shared divider spacing`);
+ }
+ assert(
+ getComputedStyle(recoverySubsection).borderTopColor === getComputedStyle(passphraseSubsection).borderTopColor,
+ "Recovery subsections must share the same neutral divider treatment",
+ );
+
for (const [index, section] of productionPanels.entries()) {
const style = getComputedStyle(section);
assert(style.borderTopStyle !== "none" && parseFloat(style.borderTopWidth) >= 1, `Production Provisioning peer section ${index + 1} must retain a visible top divider`);
From c8de6efed263fab53efa26b92093b00dff00e9db Mon Sep 17 00:00:00 2001
From: Miso Tanaka <22117028+miso-develop@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:59:48 +0900
Subject: [PATCH 7/8] fix(web): preserve existing header height while sticky
---
web/src/style.css | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/web/src/style.css b/web/src/style.css
index 6326bb0..5c27f34 100644
--- a/web/src/style.css
+++ b/web/src/style.css
@@ -14,7 +14,7 @@ a { color: inherit; }
position: sticky;
top: 0;
z-index: 100;
- padding: 20px 24px 8px;
+ padding: 20px 24px 0;
background: #f5f5f5;
}
@@ -453,7 +453,7 @@ button:disabled, input:disabled, select:disabled {
padding: 12px;
}
- #site-header { padding: 12px 12px 8px; }
+ #site-header { padding: 12px 12px 0; }
#app, #flash-app, #help-app { padding: 12px; }
.shell { padding: 24px; }
.status div, .build-identity div { grid-template-columns: 1fr; gap: 4px; }
From 91babb2c8e7f6e6da7091a35c0b9902fa07978c5 Mon Sep 17 00:00:00 2001
From: Miso Tanaka <22117028+miso-develop@users.noreply.github.com>
Date: Fri, 18 Sep 2026 22:01:00 +0900
Subject: [PATCH 8/8] test(web): validate rendered Recovery subsection
localization
---
web/tests/browser/post-provisioning-layout-smoke.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/web/tests/browser/post-provisioning-layout-smoke.ts b/web/tests/browser/post-provisioning-layout-smoke.ts
index 480a139..f3b7a37 100644
--- a/web/tests/browser/post-provisioning-layout-smoke.ts
+++ b/web/tests/browser/post-provisioning-layout-smoke.ts
@@ -628,7 +628,12 @@ async function verifySharedRouteGeometryPolicy(): Promise {
["Change Recovery Passphrase", passphraseSubsection],
] as const) {
const heading = subsection.querySelector(":scope > h3");
- assert(sourceTextOf(heading!) === name, `${name} must retain its semantic localized h3 heading source`);
+ const expectedHeading = name === "Recovery Package"
+ ? "Recovery Package"
+ : provisioningDoc.documentElement.lang === "ja"
+ ? "Recovery Passphraseを変更"
+ : "Change Recovery Passphrase";
+ assert(heading?.textContent === expectedHeading, `${name} must retain its semantic localized h3 heading`);
const style = getComputedStyle(subsection);
assert(style.borderTopStyle !== "none" && parseFloat(style.borderTopWidth) >= 1, `${name} must have one visible subsection divider`);
assert(parseFloat(style.marginTop) > 0 && parseFloat(style.paddingTop) > 0, `${name} divider must retain subsection spacing`);