Skip to content

Song links, House of the Rising Sun showcase, and a wake-lock fix (v2026.08.26.005) - #34

Merged
cdburgess75 merged 2 commits into
mainfrom
claude/hello-lr09iw
Aug 26, 2026
Merged

cdburgess75 merged 2 commits into
mainfrom
claude/hello-lr09iw

Conversation

@cdburgess75

Copy link
Copy Markdown
Owner

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 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
  • Removed releaseWake() (defined, never called — nothing releases the lock deliberately, the OS does) and an orphaned gig-mode comment block describing code deleted in .21.001
  • Corrected CLAUDE.md §4, which still claimed show() manages the wake lock

Feature: 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 via CompressionStream (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 through mergeSetImport's coerce/clamp/fresh-id shape.

index.html now 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.001 and 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's toast("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: 0 after a single hide/show cycle); it passes 11/11 after. navigator.wakeLock is stubbed with a sentinel that auto-releases on hide, matching real UA behaviour, since the real API isn't usable headless.

  • wake lock: 11/11 — boot, 1/2/5 hide-show cycles, idempotence, OS-initiated release recovery, song view, auto-scroll, refused permission
  • song links: 24/24 — field round-trip incl. unicode/quotes/tabs, six malformed payloads rejected without throwing, cold end-to-end open in a separate browser context, fragment stripping, no re-prompt on reload, paste path, duplicate detection
  • showcase: 11/11 — headings, chorus bar, banter, every chord resolving to a diagram, two-column layout, and the migration's untouched/edited/missing/re-run cases

CI equivalents run locally: node --check on the extracted script and sw.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

claude added 2 commits August 26, 2026 18:25
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
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cdburgess75
cdburgess75 merged commit 609ac20 into main Aug 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants