Song links, House of the Rising Sun showcase, and a wake-lock fix (v2026.08.26.005) - #34
Merged
Merged
Conversation
Installing one song currently means saving a file and picking it out of the
Files app. The app could export a setlist as a file but had no way to hand
someone a single song.
shareSong() packs {app,kind:"song",version:1,song} into the URL fragment,
deflate-raw'd via CompressionStream ("z." tag, "j." plain-JSON fallback).
A full chart fits in ~800 chars. The fragment is load-bearing: browsers never
send it to the server, so a shared chart never reaches a Pages access log --
a ?query= would break the no-server constraint in spirit.
Two receive paths, both needed. maybeImportFromLink() reads a #s= fragment at
boot, always confirms, then strips the hash so a reload cannot re-prompt.
The Library-tools paste field takes a pasted URL -- on iOS an installed A2HS
app has storage separate from Safari's, so a tapped link lands in a copy of
the app the owner never opens, and pasting is the only route that reaches the
installed one. Incoming songs are untrusted input and go through
mergeSetImport's coerce/clamp/fresh-id shape.
index.html now redirects in script; a meta refresh drops the fragment.
Showcase: House of the Rising Sun (traditional, public domain) grows from a
four-line stub into a full arrangement -- intro/solo/outro bar rows, five
verses, a {soc} chorus, section headings and a banter line. retireShowcase()
removed Danny Boy in .26.001 and left the app with no worked example. One-time
upgradeShowcase() reaches existing libraries but replaces the body only when
it still matches the old stub exactly, so an owner-edited chart is never
overwritten.
Also fixed: restoreData's toast("Restored") had been swallowed into a trailing
// comment and never ran.
Verified with Playwright -- 24 assertions on the link round-trip, malformed
payloads, cold end-to-end open in a separate browser context, the paste path
and duplicate detection; 11 more on showcase rendering, two-column layout and
the migration's three cases. CI equivalents run locally: both syntax checks,
duplicate ids, brand == CACHE, manifest parse, PRECACHE existence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RFni8j63y7ynwzmNLMPMTB
…6.005) The owner reported the device sleeping while a song is open, starting when gig mode was removed. Both halves of that are correct. navigator.wakeLock.request() returns a WakeLockSentinel that the browser auto-releases every time the page hides, but `wakeLock` kept pointing at the dead sentinel. The guard `if(!wakeLock)requestWake()` was therefore never true again, so from the FIRST background/foreground cycle onward the app held no lock at all -- on every screen, song view included. This was masked until v2026.08.21.001: show() used to re-request the lock on each navigation and gig mode held its own, so the stale handle kept getting replaced. Making the lock app-wide removed those calls and left the broken guard as the only re-acquire path. requestWake() now tests wakeLock.released rather than the handle, nulls the handle from the sentinel's release event so an OS reclaim is recoverable, and self-guards so redundant calls are free. visibilitychange re-requests unconditionally; openCurrent() and startScroll() re-assert, since those are the two moments that matter on stage. Also removed: releaseWake() (defined, never called -- nothing releases the lock deliberately, the OS does) and an orphaned gig-mode comment block documenting code deleted in .21.001. Corrected CLAUDE.md 4, which still said show() manages the wake lock. Verified by stubbing navigator.wakeLock with a sentinel that auto-releases on hide, matching real UA behaviour. The suite was written against the unfixed build first and failed 8 of 11 assertions (live:0 after one hide/show cycle); it passes 11/11 after. test-links.js (24) and test-showcase.js (11) still green. CI equivalents run locally. Needs a real-device check: headless proves the logic, not iPadOS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RFni8j63y7ynwzmNLMPMTB
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
Two commits. The wake-lock fix is the urgent one — the app has been failing to keep the screen awake since v2026.08.21.001.
Fix: the screen slept on a song (v2026.08.26.005)
navigator.wakeLock.request()returns aWakeLockSentinelthat the browser auto-releases every time the page hides, butwakeLockkept pointing at the dead sentinel. The guardif(!wakeLock)requestWake()was therefore never true again, so from the first background/foreground cycle onward the app held no lock at all — on every screen, song view included.This was masked until v2026.08.21.001:
show()used to re-request the lock on each navigation and gig mode held its own, so the stale handle kept getting replaced. Making the lock app-wide removed those calls and left the broken guard as the only re-acquire path.requestWake()now testswakeLock.releasedrather than the handle, nulls the handle from the sentinel'sreleaseevent so an OS reclaim is recoverable, and self-guards so redundant calls are freevisibilitychangere-requests unconditionally;openCurrent()andstartScroll()re-assertreleaseWake()(defined, never called — nothing releases the lock deliberately, the OS does) and an orphaned gig-mode comment block describing code deleted in.21.001show()manages the wake lockFeature: share a song as a link (v2026.08.26.003)
Previously a song could only be moved between devices as a file.
shareSong()packs{app,kind:"song",version:1,song}into the URL fragment, deflate-raw'd viaCompressionStream(z.tag;j.plain-JSON fallback). A full chart fits in ~800 characters.The fragment is load-bearing: browsers never send it to the server, so a shared chart never reaches a Pages access log. A
?query=would break the no-server constraint in spirit.Two receive paths, both needed.
maybeImportFromLink()reads a#s=fragment at boot, always confirms before writing, then strips the hash so a reload can't re-prompt. The Library-tools paste field takes a pasted URL — on iOS an installed A2HS app has storage separate from Safari's, so a tapped link lands in a copy of the app the owner never opens. Incoming songs are untrusted input and go throughmergeSetImport's coerce/clamp/fresh-id shape.index.htmlnow redirects in script; a meta refresh drops the fragment.Feature: House of the Rising Sun as the showcase (v2026.08.26.004)
retireShowcase()removed Danny Boy in.26.001and left the app with no worked example at all. HotRS (traditional, public domain) grows from a four-line stub into a full arrangement — intro/solo/outro bar rows, five verses, a{soc}chorus, section headings and a banter line. Fits an iPad screen in two columns with no scrolling.One-time
upgradeShowcase()reaches existing libraries but replaces the body only when it still matches the old stub exactly, so an owner-edited chart is never overwritten.Also fixed:
restoreData'stoast("Restored")had been swallowed into a trailing//comment and never ran.Verification
The wake-lock suite was written against the unfixed build first and failed 8 of 11 assertions (
live: 0after a single hide/show cycle); it passes 11/11 after.navigator.wakeLockis stubbed with a sentinel that auto-releases on hide, matching real UA behaviour, since the real API isn't usable headless.CI equivalents run locally:
node --checkon the extracted script andsw.js, duplicate-id scan, brand == CACHE, manifest parse, PRECACHE existence.Still outstanding: a real-device check. Headless proves the wake-lock logic, not iPadOS's actual behaviour. After deploy, leave a song open, switch apps, come back, and confirm the screen stays lit.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RFni8j63y7ynwzmNLMPMTB
Generated by Claude Code