How we go from the validated Vue v2.3.4 monolith to a true multi-framework library, without breaking anything along the way.
7 / 7 publishable @pulse-music/* packages are LIVE on the npm registry after a 21-tag alpha cycle. Vue v2.3.4 src/lib/ is bit-for-bit identical across 25 consecutive alphas. 139 / 139 unit tests passing across the monorepo, 0 production vulnerabilities, 6 GitHub Actions workflows green (CI + visual + a11y + coverage + release-please + CodeQL).
| Package | Version on npm | Bundle gzip |
|---|---|---|
@pulse-music/types |
3.0.0-rc.0 | 0.1 kB |
@pulse-music/core |
3.0.0-rc.0 | 2 kB |
@pulse-music/tokens |
3.0.0-rc.0 | 0.6 kB |
@pulse-music/web-component |
3.0.0-rc.1 | 8.5 kB |
@pulse-music/react |
3.0.0-rc.0 | 1 kB |
@pulse-music/svelte |
3.0.0-rc.0 | 0.4 kB |
@pulse-music/react-native |
3.0.0-rc.1 | 12 kB |
pulse-player (Vue v2.3.4 reference) |
2.3.4 | 14 kB |
The phases below are the historical record of how we got here. The forward-looking plan to v3.0.0 stable + Phase 2 monetisation lives in VERSION_STRATEGY.md and PRICING.md. The adoption metrics that gate Phase 2 are in METRICS_TRACKING.md. The condensed one-paragraph-per-alpha narrative is in ALPHA_HISTORY.md.
The 49-tag in 48-hour alpha cycle was deliberate: an audit-driven loop where every alpha closed an externally-named gap. The same discipline doesn't apply post-publish — the cost of a tag is now an npm publish per affected package, not a free git tag. The forward cadence:
- rc.X patch: at most one per week. Encourages users to actually integrate before the next version drops. Triggered by real bug reports or adopter-requested API tweaks. Not by maintainer initiative.
- v3.0.0 stable cut: when the
METRICS_TRACKING.mdDay 30 target is met (≥ 200 stars + ≥ 1 000 dl/week + ≥ 10 external issues/PRs) AND no critical bug open for 2 consecutive weeks. - Post-stable: standard SemVer cadence — patch as bugfix lands, minor as feature batch (monthly), major as breaking-change-with-migration-guide (annually max).
The 49-alpha lineage stays in the git history as transparency — they're not deleted, force-pushed, or squashed. See VERSION_STRATEGY.md for the rationale.
Lays the monorepo foundation. No code moved. The v2.3.4 Vue codebase at src/lib/ continues to work bit-for-bit identical.
What lands:
pnpm-workspace.yaml+npm workspacesconfiguration in rootpackage.jsonturbo.jsonfor build orchestration (optional — installs only withpnpm add -D turbo)packages/with 9 scaffolds:types,core,tokens,web-component,vue,react,react-native,angular,svelte@pulse-music/typesis the only package with real code in this alpha — it ships the shared TypeScript types (zero runtime, zero risk)docs/universal/ARCHITECTURE.md,docs/universal/ROADMAP.md, per-framework doc placeholders- Vue demo + tests verified to still pass after the workspaces field is added
What doesn't land:
- Actual code migration. The Vue code stays at
src/lib/. - No new framework wrappers are implemented yet.
- No CI changes (existing matrix continues to gate
pulse-playerroot package).
@pulse-music/core— portsrc/lib/useAudioStore.tsinto a plain TypeScriptPulseEngineclass. Strip Vue refs / Pinia plumbing. Re-publish the same actions (toggle,next,prev,loadTrack,seek,setAudioTracks,dispose) and the typed event bus (subscribe<E>).@pulse-music/tokens— movesrc/lib/shared/variants.csshere verbatim. Addbase.css(the--pulse-scalesystem, shadows) andanimations.css(the@keyframesfromMusicPlayer.vue/MiniPlayer.vue).- Add Vitest tests against
@pulse-music/core(port the existing tests intests/useAudioStore.test.ts). - Validation gate:
@pulse-music/vue'suseAudioStorecontinues to import from the local file (no change for the demo).
@pulse-music/web-component— write the Lit-based<pulse-player>and<pulse-fab>Custom Elements. Markup, CSS variables, animations — all copied from the validated v2.3.4 MusicPlayer.vue / MiniPlayer.vue. Lit reactive controllers replace Vue refs.- Set up Playwright visual regression: render the v2.3.4 demo, render an equivalent page built on
<pulse-player>, diff at the pixel level. Goal: zero meaningful diff. - Add browser support note: Lit needs Custom Elements v1 (every evergreen browser since 2018) + ES2019.
@pulse-music/vuebecomes a thin adapter:<MusicPlayer />and<MiniPlayer />now embed<pulse-player>and<pulse-fab>from@pulse-music/web-componentinstead of owning their own template.useAudioStore()projects@pulse-music/core's state into a Pinia store for API parity.- Move
src/lib/*intopackages/vue/src/and update the import paths in the demo. - Visual regression must show zero pixel diff against tagged v2.3.4. If anything moves, the refactor blocks until it's identical.
- v2.3.4 stays tagged on
mainso downstream users can pin to it during their own migration.
@pulse-music/react—<PulsePlayer />,<PulseFab />,usePulseAudio(). ~80 LOC each.apps/demo-react/— equivalent of the Vue demo, same scenario, same scripted tour.- Examples ported from
examples/toexamples/react-*/.
@pulse-music/react-native— separate renderer using React Native primitives. Audio engine wrapsreact-native-audio-api(Swansion) for AnalyserNode compatibility.apps/demo-react-native/— Expo demo.- Document feature parity matrix honestly. Drag-to-resize is dropped (no DOM resize concept on mobile native).
- All four primary wrappers stable: Vue, React, Web Components (direct), React Native.
- npm publish for every package under
@pulse-music/scope. - GitHub Pages docs site published.
@pulse-music/angular(v3.1.0)@pulse-music/svelte(v3.1.0)@pulse-music/solid(v3.2.0)- Future: Qwik, Lit re-export, vanilla JS examples.
- Flutter / Swift / Kotlin native ports. The audio engine surface alone would take more effort than the rest of the project combined.
- A jQuery wrapper. The Custom Elements work directly in any DOM.
- A "no-build" CDN bundle. The library mode build already targets that use case (
<script type="module" src="https://unpkg.com/@pulse-music/web-component">).
Each phase ships a shippable artefact without breaking previous phases:
- The Vue demo continues to render identically through phases 0–3 because the visual regression gate enforces zero diff.
- Each new framework wrapper is additive — it doesn't touch the others.
- React Native is last among the primary targets because its renderer is the most foreign, and waiting until everything else is stable means we can borrow architecture patterns from the other wrappers.