A page that will not load no longer strands you - #126
Merged
PathGao merged 3 commits intoAug 31, 2026
Conversation
added 3 commits
September 1, 2026 06:36
Each was run as a wallpaper and failed there: a dead link, a page that draws nothing, a transparent page that is a second layer over the desktop rather than a clock, a URL carrying the maintainer's own username. Reasons are in sites/NOT-INCLUDED.md so a re-submission has something to beat. Three copies of the registry pointed at entries that no longer exist. The first-launch line in Add Website typed one site's address into the field, so the suggestion outlived the entry; it is now a description of the range, with nothing to keep in step. sites/README.md built its worked example out of a file that has been deleted; it uses a surviving one. CANDIDATES.md still recommended the fluid simulation on the grounds it is colour in motion, which is what it was removed for not being. The published-catalogue test asserted a count of 38, so every site added or removed failed it while saying nothing about decoding. It now compares against the number of YAML files in sites/, which is the invariant it wanted. 29 entries, 8 featured, ranks unchanged. 221 tests pass.
The panel disabled a display's whole column while a page was on its way, on the argument that the controls would be aimed at a display already going somewhere, and that a load 'lasts a few seconds and lets go by itself, so nothing has to be exempt from it'. The second half is false. A page that never answers holds the column for the full thirty-second load timeout, and the website chooser — the one control that takes the display off the page that is stuck — was disabled along with everything else. The only way out was to add a different site from the gallery, which is another window. Nothing depended on the blanket disable. Crop and Mute ask hasPage, which is false for the whole of a swap because hasLoadedItsWebsite compares the page against the website the display is heading for; both also refuse the case themselves, for the keyboard paths. Browsing Mode deliberately does not ask it, and its own comment argued for this change already. The arrows and the mode ask canRotate. What is left is the two choosers and the power switch, all of which are requests to go elsewhere. Choosing during a load was already safe underneath: loadBySwapping cancels pendingLoad, replaces pendingWebView, and its defer clears that only when it is still the same object, so an abandoned load cannot report over the newer one. The plain path re-enters WKWebView, which drops the previous navigation itself. The spinner beside the chooser is unchanged and is now the only thing a load does to the panel. A test holds that: one reader of column.isLoading in the file. Verified by putting the disable back and watching it fail. 222 tests pass.
One line in DisplayPanel.picker sat flush against the left margin, which put the three arguments under it out of alignment with it. swiftlint lint --strict is CI's lint job, and it counted three violations and exited 2, so every pull request against main was red on a file it had not touched. Whitespace only. Repo-wide strict lint is clean after it.
PathGao
enabled auto-merge (squash)
August 31, 2026 22:54
PathGao
deleted the
fix/a-page-that-will-not-load-no-longer-strands-you
branch
August 31, 2026 22:55
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.
What this is
Two faults, both found in one sitting while running the registry's own entries as wallpapers.
Nine entries come out of
sites/, each one tried and each one failing there rather than failing review: a dead room URL (Douyu), a page that loads and draws nothing (WebClock), a page slow enough to hit the load timeout more often than not (Polish TV Clock), a WebGL scene that is almost entirely black unless something stirs it and nothing stirs a wallpaper, two clocks whose CSS makes them transparent — which on a wallpaper is a second layer over the desktop rather than a clock on a background — a login sheet that comes up transparent and cannot be got through (Google Calendar), an interactive drink picker, and Gitstalk, whose URL carried the maintainer's own username so that every user's wallpaper showed the maintainer's GitHub activity. Reasons are insites/NOT-INCLUDED.md, next to the platforms that were never added, so a re-submission has something to beat.And the panel stops disabling a display's column while a page is on its way to it.
Mechanism
The column.
DisplayPanelput.disabled(column.isLoading)over the whole column, with a 0.45 veil over most of it. The comment argued that every control there would be aimed at a display already on its way somewhere, and that a load "lasts a few seconds and lets go by itself, so nothing has to be exempt from it". The second half is what was wrong.WallpaperScene.loadTimeoutis thirty seconds, and a page that never answers holds the column for all of it — so the control that would take the display off the page that is stuck, the website chooser, was disabled along with everything that had a reason to be. The one page you most want to leave was the one page you could not leave from here. Adding a different site from the gallery still worked, because that window is not this column, so the way out of a stuck load was a different window entirely. That is how this was noticed.Nothing was relying on the blanket disable. Every control that must not act while the page and the website disagree already says so itself, and is the reason
hasPageexists: Crop and Mute both ask it, and it is false for the whole of a swap becausehasLoadedItsWebsitecompares the page against the website the display is heading for. Both also refuse the case insidebeginCropSelectionandtoggleSound, for the keyboard paths that never touch a button. Browsing Mode deliberately does not ask it, and its own comment already argued for this change — a failed load is the moment a user most wants to reach the page by hand. The arrows and the mode askcanRotate. What is left is the two choosers and the power switch, all three of which are requests to go somewhere else, which is what a stuck load makes urgent rather than premature.Choosing during a load was already safe underneath, which is why this is a deletion and not a mechanism.
loadBySwappingcancelspendingLoad, replacespendingWebViewsynchronously, and itsdeferclears that only when it is still the same object — so an abandoned load cannot report over the newer one, and cannot clear the newer one's busy flag. Its threeTask.isCancelledchecks all return beforeadopt, and the timeout task is cancelled by its owndefer. The plain path re-entersWKWebView, which drops the previous navigation itself.The spinner beside the chooser is unchanged and is now the only thing a load does to the panel.
The registry. Three places pointed at entries that can be deleted, which is the shape where two things answer the same question and nothing makes them agree. The first-launch line in Add Website typed one site's address into the field — the catalogue's answer written out a second time in Swift — so the suggestion outlived the entry. It is now a description of the range, in the README's own words, with nothing left to keep in step.
sites/README.mdbuilt its worked example out ofhelvetictoc.yml, which this PR deletes; it uses a surviving entry.CANDIDATES.mdstill recommended the fluid simulation on the grounds that "left alone it is colour in motion", which is exactly what it was removed for not being.Same shape in the tests:
SiteCatalogTestsassertedpublished().count == 38, the registry's size stated a second time in a place nothing updates. Every site added or removed failed that test while saying nothing about the decode failure it exists to catch. It now compares against the number of YAML files insites, which is the invariant it wanted — a generator that drops entries breaks it, and adding a site does not.Scope
Left alone: the gallery's Add/Added button. It reads "is this address in your list", so deleting a website offers it again and editing a website's URL offers the original again, but editing only its CSS, zoom or interval leaves the row stuck on "Added" with no way to get the recommended settings back. The obvious fix — compare the installed website against the entry and offer "Added" only when they match — is wrong here, and
bilibili-live.ymlis why: its URL is half-finished on purpose, meant to have a room appended. Every user who completes it would read as drift from the entry. Worth solving, not worth guessing at in this PR.Left alone: two app faults these entries exposed. Google Calendar's sign-in sheet comes up transparent over the wallpaper and Browsing Mode cannot be reached to get through it —
requiresLoginis not enough for that page, and removing the entry hides the fault rather than fixing it. And what should show through a page whose CSS makes it transparent is an open question; two entries were removed for landing on the wrong side of it, and nothing here decides it.Not left alone, because it had to be: one line of indentation in
DisplayPanel.picker.chooser's first argument sat flush against the left margin, which put the three arguments under it out of alignment with it. CI's lint job isswiftlint lint --strict, so those three counted as violations and exited 2 — every pull request againstmainwas red on a file it had not touched, this one included. Its own commit, whitespace only, and repo-wide strict lint is clean after it. Worth knowing separately: the last three runs onmainfailed every job, not only this one, so there may be more to it than the indentation.Left alone:
clockandcalendarinsites/schema.json. Both tags now have no entries, but the schema is the vocabulary a contributor may use, not an inventory of what is currently in the directory. A clock that works is a wanted contribution.Verification
macOS 26.6.2, Apple silicon.
swift test: 222 tests in 49 suites, all passing. Xcode build clean.swiftlintclean on every file this changes; the three warnings noted above are onmain.Tools/check-strings.pyagainst the build: 269 strings, every one in the catalogue and shown by something — the two removed strings are gone and the new one is reached.Tools/validate-sites.py: 29 entries, 0 problems, and the eight featured ranks are the same eight in the same order.The new guardrail was checked the only way a guardrail is worth anything:
.disabled(column.isLoading)was put back onmodeButtonsand the test went red, naming the second reader. Restored, green.What was not verified by hand: the panel change itself. It was not run. No display was driven, no page was made to hang, no chooser was clicked mid-load. The reasoning above is read off the source, not observed — the cancel path in particular is an argument about
pendingWebViewidentity andTask.isCancelled, not a measurement. What a person should do before trusting it: add a URL that never answers, pick it on one display, and while the spinner is up, use that same column's chooser to pick something else. The stuck load should be abandoned, the new page should arrive, and the column should never have been grey. Worth doing on two displays, since the argument for disabling was about one column speaking for another.