rastrillo / fiesta Public

Leave the page for the cache only once its frames have loaded

TestTheSchemeFollowsAPageBackFromTheCache left Form's first example with history.back() as soon as its readyState was complete. Its preview frames are lazy, and at 390px most are below the fold, so some were often still loading when the page went; Chromium does not keep a page whose frames are navigating, and the Forward was a reload. The drive's own control caught it ('Forward was not a back/forward cache restore', notRestoredReasons naming the child frames 'navigating'), about one run in four on this box while two make ci runs shared it.

Before leaving, the drive now loads every frame eagerly and waits for each to settle (galleryrig.Eagerly) and for gallery.js to have painted it (data-gallery-painted on every frame). When the engine still refuses the cache, the leg fails as before by assertion, naming notRestoredReasons, not by timeout.

The history role, 15 runs in a row, green with the change, alone and again interleaved with the old version while make ci's check slices ran (the old version passed those too: the race needs a slower box). With every frame document held back 400ms in a scratch copy of the tree handler, the old version failed 15 of 15 (not restored; the engine's reason masked) and the new one passed 15 of 15.

Gated with make ci in a detached /var/tmp worktree at 448955d with only this file copied in, every role green.
Human

Prompt

Fix the flaky TestTheSchemeFollowsAPageBackFromTheCache as you diagnosed: wait until the page's lazy frames have all loaded (and painted, if the gallery marks that) before navigating away, so the page is eligible for the back/forward cache. When the cache refuses, fail naming notRestoredReasons rather than timing out. Prove it: the history role at least 15 times in a row green, and the old version's failure rate on the same loop for comparison.

From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.

Summary

The cache drive settles and paints every frame of the example page before it leaves it, so the page is kept in the back/forward cache and the Forward is a restore.
Agent notes
Unloaded, the old version did not fail in 30 runs; its failures were seen only under two concurrent make ci runs (3 of 10). The 400ms frame delay reproduces the race every time and was a scratch edit, not committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d08500a93db875a1304c27d07d53163c13699cc9 parent 448955d
1 file changed, +12 −1
  • internal/gallery/history/cache_browser_test.go +12 −1

CI passed — run details

CI log
=== gofmt ===
=== no-rastrillo ===
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
ok  	amadan.net/rastrillo/fiesta	0.111s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN   TestTheOneLinkIsUnder25KiBGzipped
    build_test.go:138: fiesta-plain.css: 152856 bytes, 22303 bytes gzipped (21.8 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta-signal.css: 152951 bytes, 22414 bytes gzipped (21.9 KiB of the 25.0 KiB limit)
    build_test.go:138: fiesta.css: 155770 bytes, 23018 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.02s)
PASS
ok  	amadan.net/rastrillo/fiesta/internal/build	0.021s
=== suite ===
--- shard 0 of 15 ---
==> shard 0 of 15: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 go test -count=1 ./...
ok  	amadan.net/rastrillo/fiesta	1.374s
ok  	amadan.net/rastrillo/fiesta/check	5.611s
ok  	amadan.net/rastrillo/fiesta/cmd/fiesta	94.322s
ok  	amadan.net/rastrillo/fiesta/example	0.249s
ok  	amadan.net/rastrillo/fiesta/internal/axe	0.173s
ok  	amadan.net/rastrillo/fiesta/internal/build	1.361s
ok  	amadan.net/rastrillo/fiesta/internal/codeview	0.016s
ok  	amadan.net/rastrillo/fiesta/internal/gallery	85.218s
?   	amadan.net/rastrillo/fiesta/internal/harness	[no test files]
ok  	amadan.net/rastrillo/fiesta/internal/markup	0.018s
ok  	amadan.net/rastrillo/fiesta/internal/nodetest	0.391s
ok  	amadan.net/rastrillo/fiesta/internal/site	3.646s
ok  	amadan.net/rastrillo/fiesta/internal/tokens	0.008s
ok  	amadan.net/rastrillo/fiesta/internal/www	81.894s
--- shard 1 of 15 ---
==> shard 1 of 15: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
--- shard 2 of 15 ---
==> shard 2 of 15: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
--- shard 3 of 15 ---
==> shard 3 of 15: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
3:26AM INF Unknown SCM platform. Use --platform to include links in findings. host=amadan.net
3:26AM INF 1 commits scanned.
3:26AM INF scanned ~6232472 bytes (6.23 MB) in 3.31s
3:26AM INF no leaks found
--- shard 4 of 15 ---
==> shard 4 of 15: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok  	amadan.net/rastrillo/fiesta/internal/harness	12.427s
--- shard 5 of 15 ---
==> shard 5 of 15: make browser.check~1of3
./hack/gotest.sh browser 'check~1of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	228.796s
--- shard 6 of 15 ---
==> shard 6 of 15: make browser.check~2of3
./hack/gotest.sh browser 'check~2of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 3 of ./check/ runs 95 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	243.045s
--- shard 7 of 15 ---
==> shard 7 of 15: make browser.check~3of3
./hack/gotest.sh browser 'check~3of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 3 of ./check/ runs 96 of 286 tests
ok  	amadan.net/rastrillo/fiesta/check	243.556s
--- shard 8 of 15 ---
==> shard 8 of 15: make browser.internal.gallery~1of4
./hack/gotest.sh browser 'internal.gallery~1of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 4 of ./internal/gallery/ runs 1 of 104 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	123.584s
--- shard 9 of 15 ---
==> shard 9 of 15: make browser.internal.gallery~2of4
./hack/gotest.sh browser 'internal.gallery~2of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 4 of ./internal/gallery/ runs 32 of 104 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	112.146s
--- shard 10 of 15 ---
==> shard 10 of 15: make browser.internal.gallery~3of4
./hack/gotest.sh browser 'internal.gallery~3of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 4 of ./internal/gallery/ runs 35 of 104 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	86.396s
--- shard 11 of 15 ---
==> shard 11 of 15: make browser.internal.gallery~4of4
./hack/gotest.sh browser 'internal.gallery~4of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 4 of 4 of ./internal/gallery/ runs 36 of 104 tests
ok  	amadan.net/rastrillo/fiesta/internal/gallery	107.665s
--- shard 12 of 15 ---
==> shard 12 of 15: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok  	amadan.net/rastrillo/fiesta/internal/gallery/history	34.851s
--- shard 13 of 15 ---
==> shard 13 of 15: make browser.internal.www~1of2
./hack/gotest.sh browser 'internal.www~1of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	135.469s
--- shard 14 of 15 ---
==> shard 14 of 15: make browser.internal.www~2of2
./hack/gotest.sh browser 'internal.www~2of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 2 of ./internal/www/ runs 17 of 34 tests
ok  	amadan.net/rastrillo/fiesta/internal/www	122.742s
diff --git a/internal/gallery/history/cache_browser_test.go b/internal/gallery/history/cache_browser_test.go
index f55de3d..18b60a2 100644
--- a/internal/gallery/history/cache_browser_test.go
+++ b/internal/gallery/history/cache_browser_test.go
@@ -16,6 +16,7 @@ import (
"github.com/chromedp/cdproto/target"
"github.com/chromedp/chromedp"
+ "amadan.net/rastrillo/fiesta/internal/gallery/galleryrig"
"amadan.net/rastrillo/fiesta/internal/harness"
)
@@ -35,7 +36,10 @@ const markedJS = `[...document.querySelectorAll("[rst-scheme]")].map(m => {
// Forward, and the page must be dark, previews and all, with every
// scheme menu on it marking Dark. The control first: the Forward
// really was a cache restore. A leg that silently reloaded the page
-// would pass on start-up code alone.
+// would pass on start-up code alone. The example's frames are all
+// loaded and painted before it is left, since the engine does not keep
+// a page whose frames are still navigating; when it refuses anyway the
+// leg fails naming the engine's notRestoredReasons.
//
// The index's rows open family pages, which frame nothing, so the
// page under test is the first example of the Form family, one more
@@ -67,6 +71,13 @@ func TestTheSchemeFollowsAPageBackFromTheCache(t *testing.T) {
t.Fatalf("%s: opening Form's first example: %v", step, err)
}
until(t, ctx, step+", the example", `!location.pathname.endsWith("/form.html") && document.readyState === "complete" && document.querySelectorAll(".ds-view__frame").length > 0`)
+ // Every frame loaded and painted before the page is left. The
+ // frames are lazy, and at 390px most are below the fold: a
+ // frame still navigating when the page goes is a reason the
+ // engine gives for not keeping the page (notRestoredReasons:
+ // "navigating"), and the Forward below would be a reload.
+ galleryrig.Eagerly(t, ctx, step+", the example's frames")
+ until(t, ctx, step+", the example's frames painted", `[...document.querySelectorAll(".ds-view__frame")].every(f => f.hasAttribute("data-gallery-painted"))`)
var started float64
if err := chromedp.Run(ctx, chromedp.Evaluate(`performance.getEntriesByType("navigation")[0].startTime + performance.timeOrigin`, &started)); err != nil {
t.Fatalf("%s: reading the example's navigation: %v", step, err)