Stop the page flickering through states it is not in - #44
Merged
Conversation
Versioning static asset URLs closed the caching hole, but the list of assets to version is maintained by hand. Adding a script to a template and forgetting to wrap it reintroduces the same failure for that file, and nothing about it is visible locally: the page works, and the damage appears only at a CDN edge after the next deploy. Three checks, all mechanical. Every first-party stylesheet and script referenced from a template goes through the asset helper. Every path the templates ask the helper to version is in the list that gets hashed at startup. Every path in that list exists on disk, since the helper falls back to an unversioned URL when it cannot read a file. Verified by introducing both mistakes and confirming each fails with a message that names the file and the fix. Claude-Session: https://claude.ai/code/session_01X8JeP7AQwpd2coSfwbMopt
Three separate causes, all of them the server-rendered markup disagreeing with what the component initialises to. x-cloak was doing nothing. Alpine only removes the attribute; the rule that hides it has to be supplied, and it never was. The delete confirmation and the Show more control were therefore painted and then hidden on every load. The connection indicator claimed to be Live before anything had connected. The component starts in the connecting state, so the fallback says so now, and the label reads Connecting rather than Reconnecting because nothing has dropped yet. The results counter rendered "0 results" before any request had been counted. A count nobody has computed is unknown, not zero, so it renders empty. The dot's class binding also had to change shape: as a string expression Alpine adds the computed class without removing the static fallback, so a live connection showed a green class and an amber dot together and stylesheet order decided which won. Object syntax toggles both. Claude-Session: https://claude.ai/code/session_01X8JeP7AQwpd2coSfwbMopt
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Investigates the flicker on refresh. Three separate causes, all the same shape: the server-rendered markup disagreed with what the component initialises to, so the first paint showed something the page was never in.
x-cloakwas doing nothingAlpine only removes the attribute once it initialises. The CSS rule that hides it has to be supplied by the app, and it never was — so
[x-cloak]was inert. The delete confirmation and the Show more control were painted on every load and then hidden a moment later.That is the delete-warning flicker.
The connection indicator claimed to be Live before connecting
connectioninitialises to"connecting", but the fallback text said Live and the dot carried no colour class at all. Every load ran: colourless + "Live" → amber + "Reconnecting…" → green + "Live". Two visible transitions to reach the truth.The fallback now states the connecting state, and the label reads Connecting… rather than Reconnecting, because on first load nothing has dropped.
The results counter rendered a wrong number
Its fallback was
0 results, so a page holding three requests showed "0 results" until hydration. A count nobody has computed is unknown, not zero — it renders empty.One bug found while fixing the above
Giving the dot a static
bg-pendingfallback did not work the way it looked. Alpine's string:classsyntax adds the computed class without removing the static one, so a live connection carriedbg-pendingandbg-livetogether and stylesheet order picked the winner: green label, amber dot. Caught it in a screenshot after the "fix". Object syntax toggles both correctly.Also: a guard for the asset versioning
The previous PR fixed the stale-stylesheet problem but left the list of versioned assets maintained by hand. Adding a script and forgetting to wrap it silently reintroduces the same failure for that file — invisible locally, visible only at a CDN edge after a deploy.
Three mechanical checks now cover it: every first-party stylesheet and script referenced from a template goes through the asset helper; every path the templates version is in the startup list; every path in that list exists on disk. Verified by introducing both mistakes and confirming each fails with a message naming the file and the fix.
Verification
go vet,gofmt,go build, unit tests,prettier --check .clean, detector clean, Playwright 39/39. Checked in the browser under 20x CPU throttling and Slow 3G to observe the pre-hydration frame, and confirmed after load that the dot's colour and its label agree and that both cloaked elements compute todisplay: none.🤖 Generated with Claude Code
https://claude.ai/code/session_01X8JeP7AQwpd2coSfwbMopt