Repository navigation
fix(pwa,a11y,deps): land Plan 161/162 — offline precache, restorable backups, hydration-safe reduced motion, React 19.3.0 #925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
83a6392
fix(pwa): precache every emitted chunk so lazy views load on a first …
do-ops885 04676c2
chore(deps): refresh Next to 16.3.7 and the React runtime to 19.3.0
do-ops885 58addb7
docs(plans): reconcile roadmap records and add Plan 162
do-ops885 609ae90
docs(plans): record reproduction of the recovery/backup-outcome gap
do-ops885 0ebe5a3
feat(studio): make the pre-import backup truthful and restorable
do-ops885 eb1f44f
fix(a11y,offline): reserve the offline banner's measured height so it…
do-ops885 cddfc14
fix(a11y): make useReducedMotion hydration-safe via useSyncExternalStore
do-ops885 a89647b
ci: gate the production offline suite behind an `offline` paths filte…
do-ops885 3a0b4ee
docs(plans): record both Plan 161 follow-on fixes and the CI offline …
do-ops885 1a4d2d8
fix(review): address all GitNexus findings on the PR #925 batch
do-ops885 e41cd96
fix(review): close the three GitNexus findings on this batch
do-ops885 3b0019b
chore(deps): reconcile the lockfile with main's resolved tree
do-ops885 a53dacf
chore(pwa): regenerate the precache manifest from this tree
do-ops885 515359c
test(offline): prove the banner exit ordering at frame resolution
do-ops885 6646d53
chore(pwa): rebuild the precache manifest from the clean tree
do-ops885 61ce583
fix(studio): report refused snapshot clear and reserve banner height …
do-ops885 604cd22
fix(studio): clear the stale snapshot when serialization fails, and c…
do-ops885 ecb1f7c
fix(studio): tell the user when a restore left the snapshot behind, a…
do-ops885 c32b1be
fix(studio): stop promising the snapshot is from the last import, and…
do-ops885 8dab6e6
fix(pwa): serve the .avif files the manifest precaches
do-ops885 4edf187
fix(pwa,offline): serve the web manifest offline and keep measuring t…
do-ops885 76cc8bf
test(pwa): check the generator's code extensions in the parity guard
do-ops885 a5ec93e
Merge branch 'main' into fix/offline-lazy-view-precache
d-oit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
| import { waitForAppReady } from './helpers/navigation'; | ||
|
|
||
| /** | ||
| * A view must hydrate cleanly when the OS asks for reduced motion. | ||
| * | ||
| * `useReducedMotion` is consumed by ~25 animated elements to decide whether to | ||
| * skip their entry animation (`initial={reducedMotion ? false : {...}}`). If the | ||
| * hook answers differently on the server than on the client's first pass, the | ||
| * markup React hydrates does not match the HTML it was sent, and React reports | ||
| * a mismatch and re-renders that subtree from scratch — on every animated | ||
| * element, on every load, for exactly the users who asked for less motion. | ||
| * | ||
| * The failure is invisible to a screenshot and invisible to a unit test that | ||
| * mocks the hook, so it is pinned here in a real browser instead. | ||
| * | ||
| * Runs at every configured viewport, because the media state is per-context | ||
| * rather than per-viewport and a view can be entered by reloading on it. | ||
| */ | ||
|
|
||
| /** Readable React message in development; minified codes are #418/#423 in production. */ | ||
| const HYDRATION_MISMATCH = /hydrat|server rendered HTML didn't match|Minified React error #(418|423)/i; | ||
|
|
||
| /** | ||
| * React reports a hydration mismatch *after* the tree has committed: the report | ||
| * is queued, not thrown, so it arrives some time after `data-app-ready` — and an | ||
| * absence assertion cannot poll for a message that never comes. Measured on the | ||
| * defective code: reading the console immediately after readiness captures | ||
| * nothing, and the report is present well within this window. | ||
| */ | ||
| const HYDRATION_REPORT_SETTLE_MS = 2000; | ||
|
|
||
| test.describe('Hydration under prefers-reduced-motion', () => { | ||
| test.use({ reducedMotion: 'reduce' }); | ||
|
|
||
| test('Home hydrates without a server/client mismatch', async ({ page }) => { | ||
| const consoleOutput: string[] = []; | ||
| page.on('console', (message) => { | ||
| consoleOutput.push(`[${message.type()}] ${message.text()}`); | ||
| }); | ||
| page.on('pageerror', (error) => consoleOutput.push(`[pageerror] ${error.message}`)); | ||
|
|
||
| // The listener must exist before the document loads: React reports the | ||
| // mismatch while hydrating, which is over by the time `goto` resolves. | ||
| await page.goto('/'); | ||
| await waitForAppReady(page); | ||
| await page.waitForTimeout(HYDRATION_REPORT_SETTLE_MS); | ||
|
|
||
| // Sanity: a blank page must not be able to pass this test. | ||
| await expect(page.getByRole('heading', { name: /recent work/i })).toBeVisible(); | ||
|
|
||
| const mismatches = consoleOutput.filter((text) => HYDRATION_MISMATCH.test(text)); | ||
| expect(mismatches, `hydration errors:\n${mismatches.join('\n')}`).toEqual([]); | ||
| }); | ||
|
|
||
| test('entry animations end visible rather than frozen mid-motion', async ({ page }) => { | ||
| await page.goto('/'); | ||
| await waitForAppReady(page); | ||
|
|
||
| // The section the Home view fades in. Under reduced motion it must reach | ||
| // full opacity immediately — the point of skipping the animation is to end | ||
| // at the final state, not to sit at the initial one. | ||
| await expect(page.locator('main section').first()).toHaveCSS('opacity', '1'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.