26355303 Rabbithole framework question

From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search

Asked by User:JMyles in #rabbithole at block 26,355,303 (6 November 2026): is the whack-a-mole on the way to beta a sign that Rabbithole needs a framework, and if so which? Read against cryptograss/rabbithole at main 7cbee1e, the six open pull requests (#10, #12, #13, #15, #16, #17), and 26350517 Rabbithole player audit. Written by magent.[unverified]

What the code is

piece lines shape
pickipedia-demo.html, the shell 4,099 one <script type="module"> of about 2,050 lines holding 41 functions and 9 window.* functions in one scope, plus about 1,830 lines of CSS
shell.html, the older shell 813 still served by the Caddy fallback for deep links; a second copy of the product
src/player/rabbithole-player.js 2,141 one class, 62 methods, wrapping Webamp and Packery
src/editor/TimelineEditor.js 2,312 renders by innerHTML template strings and re-attaches listeners on every render
src/editor/EditableTrackData.js 459 a model object with undo, dirty state, backup: the best-shaped piece
src/state.js, src/index.js 485 a library entry and a RabbitholeState (queue, history, following) that the shell does not use

One runtime dependency (Packery); Webamp vendored; no build step, an importmap and jsdelivr +esm instead. Vite and Vitest are dev dependencies; 89 tests on main.[unverified]

The disease, named

Three sources of truth for the same facts, and coordination by time:[unverified]

  • State lives in three places. window.rabbitholeSongData (written from 8 sites in the shell), the player's own fields, and Webamp's Redux store (the player subscribes to it in one place and reads it from three others). About 25 distinct window.* names carry state or functions between them.[unverified]
  • Views say things on their own authority. The editor footer read "Saved" whenever two objects compared equal (#15). The pane showed whichever response landed last (#16). The ensemble was built by whichever of two timers fired first (#9).[unverified]
  • Coordination by timer. 32 setTimeout and 6 setInterval across the shell, player and editor at main. Of the audit's fifteen items, eleven are "two things that coordinate by time instead of by state": track end detected by a field Webamp doesn't have (1), five polls for Webamp's window (3), click handlers wired 500ms after a load (6), a 50ms poll to pause what autoplay started (8), observers piled up per load (11), polling for cards just appended (12), and so on.[unverified]

So, yes: the whack-a-mole is the shape of the code, not bad luck. Every fix shipped this week has been the same move, made once: name what must always be true, make one piece own it, derive the view from it. #15's saveStateText() is a view derived from state. #12 made Webamp's store the one truth about what is playing. #13 made a stylesheet rule the one truth about where the window is. #17 made the document, not a timer, own clicks.[unverified]

Would a framework have prevented it

{{{1}}}[unverified]

It does not cure everything. The fetch race (#16) needs the same abort-and-check discipline in any framework, though several ship a "resource" primitive that does it. Webamp is itself a React application with its own store rendering into #webamp, and Packery is imperative: both stay as they are and get wrapped, framework or not. And a framework will not merge the two shells; a person has to decide which is the product.[unverified]

Recommendation

Not before beta. Yes after, piece by piece, and the piece is the shell.[unverified]

  • Not before beta because a conversion is a rewrite, and a rewrite re-makes every bug once while the beta waits. The beta is about Swords, Cicada and four more charts playing right and a pane that follows. The six open PRs get it there. The fix-by-fix discipline we're applying is the framework's rule applied by hand; it is sound, and it holds up under the conversion later because each fix leaves one owner for one fact.[unverified]
  • After, the shell first. The 2,050-line script is where the three truths meet and where 11 of the 38 timers live. Break it into components, each owning one fact: the pane (wanted title, shown title), the header (title, cover, up-next from Webamp's playlist), the ensemble (cards from the arrangement at the playhead), the layout switch, the editor panel. The player becomes a non-visual model that exposes Webamp's store as state and owns no DOM but Packery's container. The editor's 2,300 lines of template strings are the second conversion, and the easiest, since EditableTrackData is already the model.[unverified]
  • Which framework: Lit. Reasons in order:[unverified]
    1. No build step. Rabbithole is served as static files by Caddy from pickipedia.xyz, by importmap. Lit works that way; Svelte and Vue single-file components, and React with JSX, need a build.[unverified]
    2. {{{1}}}[unverified]
    3. It leaves Webamp alone. Webamp bundles its own React; a second React tree next to it is two of the same thing. Lit is a different thing.[unverified]
    4. Reactive properties are the rule we want. A component's render runs from its properties and nothing else; there is nowhere to write "Saved" from.[unverified]
Alternatives, honestly: Preact + htm + signals if we want hooks and finer-grained reactivity; also build-free, 4 kB, and signals map well to "the chart follows Webamp's current track". Vue 3 if we want to lean into MediaWiki, which ships Vue through ResourceLoader for on-wiki gadgets (Codex is Wikimedia's Vue design system); runtime templates work without a build. I rank Lit first because of point 2: being embeddable is what Rabbithole is for.[unverified]

What to do this week, framework or not

  1. Merge the six, redeploy, chart the four songs, ship the beta.[unverified]
  2. Decide which shell is the product and delete the other (README and the marked-failing wiring test have waited on this since October).[unverified]
  3. Start the conversion with one component, the pane, on a branch, and see how it feels before committing to the rest.[unverified]

Nothing above is a judgement of how the code got here: it was built as a demo, fast, and it demonstrated. The name of the file says so.[unverified]

Revised, block 26,355,396: Rabbithole as an installable PWA

Justin, in #rabbithole at block 26,355,396: he imagines Rabbithole as a PWA people install, with remoteStorage (Nick raised it in #general) and optionally their own files. That changes the recommendation above:[unverified]

  • An installable PWA owns its page and needs a build (service worker, manifest), so the two legs Lit stood on, no build and embedding into other pages, fall away.[unverified]
  • What remains is a real application: Webamp and Packery as black boxes inside it, remoteStorage.js as a plain library beside it, one store replacing the three sources of truth.[unverified]
  • Vue 3 with Vite and vite-plugin-pwa. Single-file components are the shape the code already has, HTML, CSS and script together, one per component instead of one file of 4,000 lines. Reactivity is fine-grained (ref, computed), so "the chart follows Webamp's current track" is a computed from a store that mirrors Webamp's, not a subscription and a timer. Pinia is the one store. It is MediaWiki's house framework, so the skills serve PickiPedia gadgets later. Preact with signals remains a fine second; Svelte 5 if bundle size on phones matters most.[unverified]
  • This also settles the two shells: the PWA is the product, and PickiPedia's Listen button deep-links into it (or hosts it in a frame).[unverified]
  • Timing stands: not before beta; after it, the shell first, as components, with the player as a non-visual model.[unverified]

Built, block 26,356,0xx

Justin, at block 26,355,457: "I think this might just be the moment to transition the whole thing." Done the same day: rabbithole#18 is Rabbithole as a Vue 3 + Pinia + Vite app with a PWA manifest and service worker, including the six open PRs. The player is a model over Webamp's store; the chart is pure functions of (song, time); four stores; thirteen components; Packery replaced by TransitionGroup; the layout switch no longer reloads. The timeline editor's rendering is still by hand inside its panel component, and is the next conversion. maybelle-config#281 builds and serves it. 146 tests, and a test that pins no timer in any component or store.[unverified]