Repository navigation
Keep a pasted link over a waiting save, and a size the drawer could not take - #65
Merged
Merged
Conversation
…ake, and the set-aside record in step A link pasted over a page as an edit's save came due was written over: a page still drawing came to the waiting save before the paste's hashchange, the save wrote the page's own design into the address, and the hashchange then reloaded that. The save now leaves an address the page did not write alone (ownHash), so the link loads, and the layout it replaces, with the edit, is set aside. With the drawer's saves refused, a hand-over carried a size the drawer did not have, and the page at the other end caught up with the drawer's older one. A drawer with both marks as the note has them has not moved on since the page that handed over, so it is not caught up with. And with the storage full, a layout set aside whose link record would not fit kept the record of the layout set aside before. That record now goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
… the guide's own pages From #63's review: the links tests waited a fixed 900 ms before going to another address, which drops a save still waiting; they now wait for the save itself (saved), and for a Bins field edit still to be read in. The guide anchor test goes on by the guide's own link to "Drawer size to grid", the polls name what they wait for, the declined-link test checks the link is still recorded before the edit, and markHere's comment no longer blames file://. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted (its VERSION line): taken from main and rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
…ffer a pasted-over edit on Back From #65's review. The page that took a hand-over with the storage full was caught up with the drawer's older size again when reloaded, gone Back to or shown from the back-forward cache: the guard covered hand-overs only. Now any arrival whose marks show the drawer as the page last knew it (unmoved), and the pageshow return, are not caught up. Back from a link pasted as an edit's save came due goes to an address that never had the edit, which is aside: it is now offered there. The O1 test checks the old record is gone, and the pasted-link test makes the edit and the paste in one task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
…ixes Only sw.js conflicted, on its VERSION line: took main's and rebuilt the pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only the CI workflow changed on main; no conflict, and the pages and sw.js are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
This was referenced Oct 9, 2026
…r only the pasted-over save on Back Round 2 of the review of the pasted-link and storage-full fixes: - A page keeps a size the drawer does not have only when its design holds a change the drawer refused. Equal marks alone did not prove that: a page that saves keeps another tab's size where it has not changed its own, and sets its mark all the same, so a tab that saved after another tab resized the drawer kept the old size through a reload or Back. The refusal now travels in the address's mark, the hand-over note and the tab's record of its last save. - A drawer only one tool has saved into counts too. - The kept change is counted from the drawer's settings, so the next save that goes through writes it into the drawer. - Back to the address a pasted link came over offers only the save the paste came over, recorded for the tab; any other layout aside could be older than the page Back brings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line: took main's and rebuilt the pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line: took main's and rebuilt the pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
…learing Start fresh cleared this browser's save and set the address, and the page ran on until the bare one arrived. A change that landed in between was saved after the clearing, and the bare page came back with it, saying it had been restored. It now leaves by leaveFor, which stops the page's saves, on both pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
A page whose saves into the drawer are refused passes its base (its shared keys as it last saved them or arrived with them) along with the flag: in the address's mark, the hand-over note, the raced reload's record, and the notes for a catch-up and for restore. The page that takes the change on counts it from that base, so the change is still to be saved and whatever another tab moved on since is caught up, as on main. Round 2 counted it from the drawer's settings instead, so a page whose last save had kept another tab's 420 took its own stale 400 for the change and wrote it over that tab's once there was room (M2). The catch-up and restore notes dropped the flag, so a refused size went back after either (N1, R1). The pageshow return is main's again, and the marks no longer decide it. Start fresh and Put back also forget the pasted-link record, which offered an edit older than the fresh page on a later Back (O14b). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line: taken from main and rebuilt with node build.js, which regenerated both pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
This was referenced Oct 9, 2026
Only sw.js conflicted, on its VERSION line: taken from main and rebuilt with node build.js, which regenerated both pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line: taken from main and rebuilt with node build.js, which regenerated both pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
A page holding a size the drawer refused hands its base on with an id for the refusal. A save that goes through while the page holds it records the id for the tab, and a mark, note or record whose refusal has landed counts for nothing. Back to the page after its size was saved on the other page and set back there now shows the size set back and leaves the drawer at it, rather than saving the refused size again over it. Also: a catch-up that changes nothing in the page takes the drawer's settings as its base without reloading; PASTED_KEY is declared with SAVE_KEY, so Start fresh clears it even when the boot stopped short; and a page left by Start fresh and shown again from the back-forward cache saves again, as it does loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line; taken from main and rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
A page now holds the ids of the refusals it arrived with, and adds one of its own for the saves the drawer refuses before it next hands them on. Records carry the list, and count as landed only once every id in it has. With one id for a whole chain, a save on an earlier page counted a later page's refused depth as saved, and Forward lost it. Save as and deleting the drawer drop the ids, so a refusal in the new drawer no longer counts Kitchen's as saved. From the back-forward cache, a page whose refusals have landed keeps "not saving" while its own half differs from the drawer's, and a catch-up with nothing to reload rewrites the address's mark with the new base. leaveFor clears freshLeft, ids are read only as short lowercase words, and the tab keeps the last 128 landed. S3 and O1 are also tested with a real back-forward cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Only sw.js conflicted, on its VERSION line; node build.js regenerated it and the pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Each id a page holds now carries the base its own changes count from: the page's shared keys as it last sealed, arriving, saving or coming back from the cache. A record counts from its first id still to land, and an id counts as landed once it or any id after it has, since the page that held the later one saved the earlier ones' changes with its own. Counted from the first page's base, a later page whose save as it arrived was refused took the size handed to it for its own change after that size was saved and set back, and Forward to it wrote the size over the one set back. A page keeps the last 32 ids, the first of them counted from the page's base, so a long chain of hand-overs still saves the first page's size, and Back to the page holding the first id finds it landed. From the cache, a page whose drawer was deleted on the other page says it is not saved, and Start fresh pressed twice before the page goes still saves again from the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk
Oliver-Johnson
marked this pull request as ready for review
October 10, 2026 12:40
3 tasks done
Oliver-Johnson
pushed a commit
that referenced
this pull request
Oct 10, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uqqb1cvhXxrpb3P6y6ykBY
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.
Requested by Oliver · project thread
What this changes
The page-code half of the optional findings left after #31, with #63's review optionals on top.
A link pasted over a page as an edit's save comes due (R11O1).
Before: you change the drawer width, and within the 400 ms before that save you paste a link into the address bar. The paste puts the link in the address at once, and its
hashchange, which reloads onto it, comes after. A page still drawing the edit came to the waiting save first. That save wrote the page's own design back over the link, thehashchangefound that design and reloaded it, and the link was gone with nothing said. It failed on every build, main included (the reviewer's R11O1 probe on Baseplates).After: the link loads, with "This link replaced the layout you had here." Put back brings your layout, the edit included. The save is still made, so the layout the link sets aside has the edit. It just leaves an address the page did not write alone.
Back from the link goes to the address before the paste, which never had the edit. The edit is aside and in no earlier address, so Back offers it there: "This page went back to an earlier layout of yours. The later one is set aside." (round 1, O-a). Back offers only that save. Any other layout aside can be older than the page Back brings (round 2, S2).
How: each page keeps the address as it last left it (
ownHash: as it arrived, then as each save wrote it). A save that finds a different address with a layout in it does not callreplaceState. It records, for the tab, the fingerprints of the save and of the address it left (PASTED_KEY), and Back offers the layout aside only when it is that save and the address is that one. Both pages have the same code, so both get the fix.A size changed with the drawer's saves refused (O4).
Before: with the browser's storage full, Kitchen open, you change the drawer width on Baseplates from 400 to 410 and go to Bins. The drawer could not take 410, so the hand-over carried it in the address alone. Bins caught up with the drawer on the way in, as it does when another tab has moved the drawer on, and showed 400. It did the same on main.
After: Bins shows 410, and so does Baseplates after the trip back. So does Bins reloaded, shown again from the back-forward cache, or gone Back to (round 1, S1). So does the page that made the change, reloaded, in a drawer only its own tool has saved into (round 2, S1b). Once the storage has room, the next change on either page saves 410 into the drawer.
The 410 also outlives a catch-up for something another tab changed, such as the depth, reloaded or from the cache (round 3, the reviewer's N1 and N1f). It outlives a hand-over onto a page that takes its own half from the drawer (round 3, R1). And a size another tab moved on is still caught up, even when the page's last save that went through kept that tab's size (round 3, M2).
Once the 410 is saved, it is no longer the page's change to keep (round 4, S3). Say Bins saves it into the drawer once there is room, and you then set the width back to 400 there. Back to Baseplates shows 400, and the drawer stays at 400. It does not matter whether Bins saved the 410 as it arrived, with a change of its own first, or as you set it back, or whether Baseplates saved it on the way to Bins.
A change refused on one page also outlives a save of another page's change (round 5). Say Bins, handed the 410, has its depth changed to 510, which the drawer refuses too. Back on Baseplates, with room, a change saves the 410. Forward to Bins still shows 510, and its next change saves it (F1). And a 410 handed over and then saved as another drawer, Pantry, is still Kitchen's change on Back to Baseplates, whatever Pantry's saves do (SA).
And once the 410 is saved and set back, it is no change of a later page's (round 6, F2). Say Bins, handed the 410, has its own save as it arrives refused too. Back on Baseplates, with room, a change saves the 410, and you set the width back to 400. Forward to Bins shows 400, and its next change keeps it.
How: a page that holds a change the drawer refused passes on its own
basewith it. That is the page's shared keys (size, bed, printer) as it last saved them or arrived with them. The flag and that base go in the address's own mark, the hand-over note, the tab's record of its last save, and the notes for a catch-up and forrestore. A page that arrives with them counts its changes from that base, not from its design. The change is still to be saved, and whatever another tab moved on since is caught up, as on main. Thepageshowreturn from the back-forward cache is main's again, since the page's base is already its own.Each page's refusals also have ids: the ones it arrived with, and one of its own for the saves the drawer refuses before it next hands them on, by a hand-over or into the back-forward cache. Its records carry the list. When a save goes through while the page holds refusals, the page notes their ids for the tab, keeping the last 128. A record whose ids have all landed then counts for nothing, so Back to it is a page of yours come back, caught up with the drawer. The ids go when a refusal is dropped rather than saved: by Save as, or by the drawer being deleted.
Each id also has the base its own changes are counted from (round 6): the page's shared keys as it arrived, last saved, or came back from the cache. A record counts from its first id still to land. An id counts as landed once it or any id after it has, since the page that held the later one saved the earlier ones' changes with its own. A page keeps the last 32 ids, the first of them counted from the page's base, so the first page's change in a long chain is still saved.
A page shown again from the back-forward cache does the same once its refusals have all landed, and counts from its first id still to land once some have (round 6). A change to its own half refused with the size is the exception, since the later page saved only the size: the bar still says "not saving" until a save goes through (round 5, O1). And a catch-up that leaves the page's design as it is takes the drawer's settings as its base without reloading (round 4, optional 1), and rewrites the address's mark to say so (round 5, CU1).
mergeDesignkeeps a size the page did not change. That page then took 420 as its base and its own stale 400 as the change, and once there was room it wrote 400 over the other tab's 420. Now it catches up to 420 and keeps it.The set-aside record with the storage full (O1).
Before: a layout set aside where the layout fit and its record of whether it is a link's did not kept the record of the layout set aside before, which then answered for it.
After: that record goes, so put back, the layout counts as yours. The layout itself is kept and still offered back. Removing it as well, as suggested, would have left it only in the kept-back save until the first change on the link.
Start fresh undone by a change as the page goes.
Before: Start fresh cleared this browser's save and set the address, and the page ran on until the bare one arrived. A change that landed in between, such as a field's change or a Bins edit still being read in, was saved after the clearing. The bare page came back with it and said it had been restored. Main does the same. The reviewer's random sequence on #69 hit it once (seed 11, sequence 4).
After: Start fresh leaves by
leaveFor, as Put back does, which stops the page's saves. It is still wired before the boot reads a link.hashReadyandhashSaveTare declared above the wiring, so leaving works from there too.Start fresh and Put back also forget the pasted-link record (round 3, O14b). Kept past Start fresh, a page at the address it names offered the edit as "the later one" after another link and Back, though Start fresh came after the edit. The record's key is now declared with the save's, so Start fresh clears it even when the boot stopped before reading a link (round 4, optional 2).
A page left by Start fresh and shown again from the back-forward cache saves again, as it does loaded (round 4, optional 3). This applies after Start fresh only. After Put back or Try anyway, the same page loaded is a link again and sets your layout aside first. From the cache, its first save would go over the layout just put back. So Put back or Try anyway pressed after Start fresh, before the fresh page arrives, turns it off again (round 5, optional 1). Start fresh pressed twice keeps it on (round 6, optional 5).
A page shown again from the cache after its drawer was deleted.
Before: delete Kitchen on Bins, then go Back to Baseplates, which the browser shows again from the back-forward cache. Its bar still says "Kitchen". Main does the same.
After: the bar says "not saved", as it does when the page is loaded (round 6, optional 4).
From #63's review:
arrive(), as Run the links tests on the served pages, and poll the saves they wait for #63 did inpersist.spec.js. The newsaved()also waits for a Bins field edit still to be read in.markHerecomment no longer saysreplaceStateis refused onfile://.Also in round 1:
toBeNull).sw.jsconflicted, on its VERSION line.Round 2 (the re-check of round 1) is M1, S1b and S2, described above, with the reviewer's B3, B4, B5 and O8 as tests.
Round 3 (the re-check of round 2) is M2 and optionals 1 to 3, described above, with the reviewer's P2s, N1, R1 and O14b as tests. Optional 4 has no test: the page's own first save, 400 ms after it arrives, sets the same base, so only a race inside that window could tell the line is there.
Round 4 (the re-check of round 3) is S3 and optionals 1 to 3, described above, with the reviewer's A1 and A1b among the tests.
Round 5 (the re-check of round 4) is F1, SA, O1 and CU1, described above, with the reviewer's F1, SA and CU1 as tests. Of its optionals:
leaveForclearsfreshLeft, and Start fresh sets it after./^[a-z0-9]{1,16}$/, from a record or the tab's note.Round 6 (the re-check of round 5) is F2, described above, with the reviewer's F2 and SEALW as tests. Of its optionals:
drop()on deleting the drawer stays, as a defence.O2 (no visible effect) and O3 (new on-page wording) stay open, as do round 1's O-b and O-c, which main has too.
Checks
src/, not the generatedindex.html/bins/index.htmlnode build.jsand committed the regenerated pages alongside the sourcesnpm run test:allpasses:npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean;npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean, and GitHub's check and ui jobs green;npm testandnode test/ci-sim.jsclean;npm testandnode test/ci-sim.jsclean;npm testandnode test/ci-sim.jsclean. The branch's own changed lines come through each merge of main unchanged.rid, nowrids;ridsreads 30 of 30 OK, and a landed list of 100,000 ids is cut to the last 128 (a 1,400-character note);hashReadyfalse; Start fresh alone sets it true;leaveForfor the other tool and shown again from the cache, still saves nothing, as on main. No button leaves that way.saved()waiting for nothing, 11 links tests fail, so the wait is doing the work.drawers.jsas round 2 left it: the four P2s tests (400 where the other tab set 420), the four catch-up tests and the tworestoretests (400 where 410 was typed);restorenote dropping it: the tworestoretests;restoretests, and the mark also S1b and the reloaded catch-up;drawers.jsas round 4 left it: the F1, SA and CU1 tests and the cache test with an own change refused, on either page (the size or depth of the drawer before, and "Kitchen" for "not saving · Kitchen");leaveForleavingfreshLeftset: the Start fresh then Put back test, on either page;drawers.jsas round 5 left it: the F2 and SEALW tests on either page, the 33 hand-overs, and the bar after a delete on either page;channel: 'chromium', whichnpx playwright install chromiuminstalls beside the headless shell). The headless shell the other tests run in keeps nothing in the cache.If this touches geometry
It does not.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XjNf7SQF9o4ZVeWiSbWQmk