Skip to content

Commit 51e5a61

Browse files
committed
fix(web): focus routes before keyboard interaction
1 parent 89402e3 commit 51e5a61

6 files changed

Lines changed: 42 additions & 13 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The August 31 website update includes:
3131
- Opaque modal sheets with keyboard focus wrapping, Escape dismissal and blocked background taps; mobile safe-area layouts and accessible numeric entry.
3232
- A six-paper research bibliography with limitations, separate software/font notices, and optional Home Screen installation.
3333

34-
**Preview, not full native parity.** The appearance update passes 94 unit tests, all 47 browser tests, the production build and scoped scan of 95 output files on Windows. Browser coverage includes desktop Chromium/WebKit and Pixel 7 Chrome emulation, all 21 actual font files, 320px/200%-text layouts, both spacing extremes, preference failures, shared appearance and completed workouts after switching fonts. Both supplemental origin-stopped workout checks also pass. These results do not verify physical Android/iPhone devices, installed Home Screen lifecycle or VoiceOver. See the [acceptance record](web/docs/acceptance.md), [native parity gaps](web/docs/domain-parity.md) and [offline investigation](web/diagnostics/OFFLINE-WEBKIT.md).
34+
**Preview, not full native parity.** The appearance update passes 94 unit tests, all 49 browser tests, the production build and scoped scan of 95 output files on Windows. Browser coverage includes desktop Chromium/WebKit and Pixel 7 Chrome emulation, all 21 actual font files, 320px/200%-text layouts, both spacing extremes, preference failures, shared appearance and completed workouts after switching fonts. Both supplemental origin-stopped workout checks also pass. These results do not verify physical Android/iPhone devices, installed Home Screen lifecycle or VoiceOver. See the [acceptance record](web/docs/acceptance.md), [native parity gaps](web/docs/domain-parity.md) and [offline investigation](web/diagnostics/OFFLINE-WEBKIT.md).
3535

3636
**Live deployment verified, 31 August 2026:** [workflow run for `974ed50`](https://github.com/Yannam-Builds/Ironlog/actions/runs/33373046339) passed 85 unit tests, 29 browser tests, both supplemental offline tests, build/output checks and deployment. Pages now uses **GitHub Actions** and serves only the built `web/dist`, not the legacy README renderer. All nine phone-preview tests also passed against the public website, including embedded workout completion in Chromium, WebKit and Android Chrome emulation. The app-independent WebKit offline-emulation failure remains a diagnostic; the application gate closes its actual HTTP origin and verifies cached startup and workout persistence without it.
3737

docs/superpowers/plans/2026-08-31-shared-typography.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ Files: create `web/src/ui/typography.ts`, `web/src/ui/FontPicker.tsx`, `web/src/
3636

3737
Files: `ui/theme/IronLogTypography.kt`, new typography registry/runtime/preferences and picker files, `ui/IronLogApp.kt`, `ui/screens/settings/SettingsScreen.kt`, affected centralized text wrappers and tests under `app/src/test`/`app/src/androidTest`.
3838

39-
- [ ] Write failing JVM tests for registry/preset normalization, range clamping and invalid stored preference recovery.
40-
- [ ] Implement persistent selection in existing settings or isolated SharedPreferences, never requiring ObjectBox schema changes; load before screen rendering and update the root immediately after a successful save.
41-
- [ ] Map all Material text styles to the chosen family and preset, including explicit screen weights through a centralized wrapper if needed. Audit custom Canvas/Paint text separately; never replace icon glyphs.
42-
- [ ] Add a scrollable opaque picker with family names, preview, selected state and weight preset. Preserve all pre-existing dirty edits.
43-
- [ ] Run focused tests, complete JVM tests/lint/build and emulator checks where available. No phone install/uninstall/clear and no release publication in this task.
39+
- [x] Write failing JVM tests for registry/preset normalization, range clamping and invalid stored preference recovery.
40+
- [x] Implement persistent selection in existing settings or isolated SharedPreferences, never requiring ObjectBox schema changes; load before screen rendering and update the root immediately after a successful save.
41+
- [x] Map all Material text styles to the chosen family and preset, including explicit screen weights through a centralized wrapper if needed. Audit custom Canvas/Paint text separately; never replace icon glyphs.
42+
- [x] Add a scrollable opaque picker with family names, preview, selected state and weight preset. Preserve all pre-existing dirty edits.
43+
- [x] Run focused tests, complete JVM tests/lint/build and emulator checks where available. No phone install/uninstall/clear and no release publication in this task.
4444

4545
## 4. Verification, review and publication
4646

4747
- [ ] Browser tests: all 21 families load, persisted preference across landing/iframe/full-screen, invalid storage, all presets, 320px/large text, menus/inputs and complete workout after switching; offline reload retains the choice.
4848
- [ ] Run `npm test`, `npm run build`, `npm run verify:output`, complete Playwright suite and separate origin-stopped tests. Check screenshots and font payload totals.
49-
- [ ] Independent spec review followed by code-quality review; resolve findings and rerun affected gates.
49+
- [x] Independent spec review followed by code-quality review; resolve findings and rerun affected gates.
5050
- [ ] Update README and font acknowledgments. Publish only website-scoped changes via existing verified GitHub Pages workflow; native changes stay separate from unrelated Android work.
5151
- [ ] Verify the live website. Report native build/emulator evidence separately from physical-device testing and distinguish bundled font choices from platform-controlled text.
5252

web/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ The PWA uses prompt updates. Pending writes and active workouts disable update c
8181

8282
**Preview publication requested by the owner on 31 August 2026.** Repository publication is separate from full product acceptance. The workflow must pass before Pages deployment; never skip a failed test to publish. Real iPhone testing and the documented parity/acceptance gaps remain open. Do not include unrelated Android edits, APKs, keystores or user backups.
8383

84-
On Windows, the appearance update passed 94 unit tests and all 47 main browser tests (2 workers, 2 minutes); CI retains its single-worker configuration. All 21 fonts and presets are checked on the landing/embedded app at 320px and 200% text in Chromium/WebKit/Pixel 7 Chrome emulation. Settings changes, completed workouts, failed font/preference loads, both spacing extremes, unchanged text/icon dimensions and control minimums are covered. Both supplemental origin-unavailable workout tests pass (2/2, 21 seconds). The main offline test closes its actual HTTP server; the standalone WebKit offline-emulation failure remains documented and reproducible. See [the diagnostic evidence and limitations](diagnostics/OFFLINE-WEBKIT.md); this is not physical-device or installed-PWA verification.
84+
On Windows, the appearance update passed 94 unit tests and all 49 main browser tests (2 workers, 2.1 minutes); CI retains its single-worker configuration. All 21 fonts and presets are checked on the landing/embedded app at 320px and 200% text in Chromium/WebKit/Pixel 7 Chrome emulation. Settings changes, completed workouts, failed font/preference loads, both spacing extremes, unchanged text/icon dimensions and control minimums are covered. Both supplemental origin-unavailable workout tests pass (2/2, 23.1 seconds). The main offline test closes its actual HTTP server; the standalone WebKit offline-emulation failure remains documented and reproducible. See [the diagnostic evidence and limitations](diagnostics/OFFLINE-WEBKIT.md); this is not physical-device or installed-PWA verification.
8585

8686
OpenGym exercise pictures/GIFs are not included. Its [media notice](https://gitlab.com/DuarteSantos8/opengym/-/blob/main/NOTICE.md) and the [upstream dataset terms](https://github.com/hasaneyldrm/exercises-dataset#license--usage) require separate media permission; software licenses do not grant those image rights.
8787

web/docs/acceptance.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ Status: implementation and hardening in progress; **not fully accepted**. The ow
1818
### Appearance update — 31 August 2026
1919

2020
- 94 JVM-independent web unit tests pass; production build and scoped scan of 95 output files pass. Original font bytes total 5.84 MiB; revisioned offline precache is approximately 7.75 MiB.
21-
- All 47 browser tests pass on Windows with two workers. CI remains single-worker. New coverage loads every actual font family, all explicit weight presets, semantic heading weights, malformed preferences, storage denial, font-load failure, landing/iframe/full-screen synchronization, a complete workout after changing fonts, and offline selection of a previously unused font with the HTTP origin stopped.
21+
- All 49 browser tests pass on Windows with two workers. CI remains single-worker. New coverage loads every actual font family, all explicit weight presets, semantic heading weights, malformed preferences, storage denial, font-load failure, landing/iframe/full-screen synchronization, a complete workout after changing fonts, and offline selection of a previously unused font with the HTTP origin stopped.
2222
- Global spacing is tested at 85% and 125%, with reset/reload, 320px and 200% text, preserved text/icon sizes and representative 48px-or-larger controls. WebKit testing exposed and fixed long header/tab/link overflow and native-select overflow; navigation labels now wrap and bottom clearance accommodates enlarged text.
23-
- Both supplemental origin-stopped workout tests pass (2/2, 21 seconds). Typography and spacing received independent spec/code reviews; heading-role findings were fixed. Native code is developed in a separate dirty workspace and is not part of this website deployment.
23+
- Both supplemental origin-stopped workout tests pass (2/2, 23.1 seconds). Typography and spacing received independent spec/code reviews; heading-role findings were fixed. Native code is developed in a separate dirty workspace and is not part of this website deployment.
2424
- These are automated/emulated checks, not physical Safari, Home Screen or VoiceOver acceptance. Existing broader parity gaps below remain open.
25+
- The first appearance CI run exposed a route-focus race in the existing keyboard-overlay test. A delayed animation frame could steal focus between Enter events. Route focus now runs after the React route commit, before paint; a controlled delayed-frame regression failed before the fix and passes afterward. The complete local suite passes without weakening the original keyboard assertions.
2526

2627
The final run results should accompany this document. Current covered cases include:
2728

web/src/App.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef, useState, lazy, Suspense } from "react";
1+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, lazy, Suspense } from "react";
22
import { readSnapshot, subscribeSnapshot, reconcileBadges } from "./data/store";
33
import type { AppSnapshot } from "./domain/types";
44
import { deriveSnapshot } from "./domain/engine";
@@ -37,6 +37,12 @@ export function App() {
3737
const [now, setNow] = useState(Date.now());
3838
const [online, setOnline] = useState(navigator.onLine);
3939
const main = useRef<HTMLElement>(null);
40+
useLayoutEffect(() => {
41+
// Focus the committed route before it can be interacted with. A deferred
42+
// animation frame can otherwise steal focus between Enter's key events.
43+
window.scrollTo(0, 0);
44+
main.current?.focus({ preventScroll: true });
45+
}, [route]);
4046
useEffect(() => {
4147
applyTheme(currentTheme());
4248
return subscribeSnapshot(setData, (e) =>
@@ -48,8 +54,6 @@ export function App() {
4854
useEffect(() => {
4955
const hash = () => {
5056
setRoute(location.hash.replace(/^#\//, "") || "home");
51-
window.scrollTo(0, 0);
52-
requestAnimationFrame(() => main.current?.focus());
5357
};
5458
const visible = () => {
5559
if (document.visibilityState === "visible") {

web/tests/browser/overlays.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@ async function onboard(page: Page) {
1717
await expect(page.getByRole("heading", { name: "Overlay QA" })).toBeVisible();
1818
}
1919

20+
test("route focus does not steal a newly focused control on a delayed frame", async ({ page }) => {
21+
await onboard(page);
22+
await page.evaluate(() => {
23+
const callbacks: FrameRequestCallback[] = [];
24+
const original = window.requestAnimationFrame;
25+
window.requestAnimationFrame = (callback) => callbacks.push(callback);
26+
Object.assign(window, {
27+
flushRouteFrames: () => {
28+
window.requestAnimationFrame = original;
29+
callbacks.splice(0).forEach((callback) => callback(performance.now()));
30+
},
31+
});
32+
location.hash = "#/plans";
33+
});
34+
const opener = page.getByRole("button", { name: "Import JSON", exact: true });
35+
await opener.focus();
36+
await page.evaluate(() => {
37+
(window as unknown as { flushRouteFrames: () => void }).flushRouteFrames();
38+
});
39+
await expect(opener).toBeFocused();
40+
await page.keyboard.press("Enter");
41+
await expect(page.getByRole("dialog", { name: "Import a plan" })).toBeVisible();
42+
});
43+
2044
test("all themes keep sheets opaque, modal and keyboard dismissible", async ({
2145
page,
2246
}, info) => {

0 commit comments

Comments
 (0)