@freqhole/playlistz 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/config.json +11 -0
- package/.changeset/nice-wolves-thank.md +5 -0
- package/.freqhole-versions.json +4 -0
- package/.github/copilot-instructions.md +201 -0
- package/.github/workflows/changesets.yml +50 -0
- package/.github/workflows/npm-publish.yml +124 -0
- package/.github/workflows/pr-checks.yml +103 -0
- package/README.md +30 -0
- package/build-component.js +141 -0
- package/build-zip-bundle-lib.js +44 -0
- package/config/playwright.config.ts +47 -0
- package/config/vite.config.ts +44 -0
- package/config/vitest.config.ts +39 -0
- package/dist/assets/automerge_wasm_bg-Cik4BF9l.wasm +0 -0
- package/dist/assets/index-CbOXzGiA.js +216 -0
- package/dist/assets/index-CbOXzGiA.js.map +1 -0
- package/dist/assets/index-TvJ6RFpy.css +1 -0
- package/dist/assets/midden-DceCrT_L.js +2 -0
- package/dist/assets/midden-DceCrT_L.js.map +1 -0
- package/dist/assets/midden_bg-BLhfGIU-.wasm +0 -0
- package/dist/index.html +55 -0
- package/dist/sw.js +134 -0
- package/docs/AUTOMERGE_P2P_PLAN.md +233 -0
- package/docs/COLLABORATIVE_SHARING_PLAN.md +188 -0
- package/docs/E2E_TESTID_PLAN.md +234 -0
- package/docs/IROH_P2P_PLAN.md +302 -0
- package/docs/ROADMAP.md +695 -0
- package/docs/TODO.md +167 -0
- package/docs/bundle-embedding-plan.md +134 -0
- package/docs/standalone-refactor.md +184 -0
- package/e2e/all-playlists.spec.ts +220 -0
- package/e2e/audio-player.spec.ts +226 -0
- package/e2e/collaborative-features.spec.ts +229 -0
- package/e2e/contexts.ts +238 -0
- package/e2e/edit-panel.spec.ts +87 -0
- package/e2e/fixtures/bare-glitch-1s.m4a +0 -0
- package/e2e/fixtures/bare-glitch-1s.mp3 +0 -0
- package/e2e/fixtures/bare-glitch-1s.ogg +0 -0
- package/e2e/fixtures/chord-stack-3s.wav +0 -0
- package/e2e/fixtures/cover-anim.gif +0 -0
- package/e2e/fixtures/cover-blue.png +0 -0
- package/e2e/fixtures/cover-checkers.png +0 -0
- package/e2e/fixtures/cover-gradient.jpg +0 -0
- package/e2e/fixtures/cover-mono.gif +0 -0
- package/e2e/fixtures/cover-noise.png +0 -0
- package/e2e/fixtures/cover-plasma.webp +0 -0
- package/e2e/fixtures/cover-portrait.jpg +0 -0
- package/e2e/fixtures/cover-red.png +0 -0
- package/e2e/fixtures/cover-thumb.jpg +0 -0
- package/e2e/fixtures/cover-wide.webp +0 -0
- package/e2e/fixtures/generate.mjs +257 -0
- package/e2e/fixtures/long-drone-90s.mp3 +0 -0
- package/e2e/fixtures/noisy-binaural-8s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.m4a +0 -0
- package/e2e/fixtures/tagged-a3-4s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.ogg +0 -0
- package/e2e/fixtures/tagged-c5-3s.m4a +0 -0
- package/e2e/fixtures/tagged-c5-3s.mp3 +0 -0
- package/e2e/fixtures/tagged-c5-3s.ogg +0 -0
- package/e2e/fixtures/tagged-f4-6s.m4a +0 -0
- package/e2e/fixtures/tagged-f4-6s.mp3 +0 -0
- package/e2e/fixtures/tagged-f4-6s.ogg +0 -0
- package/e2e/fixtures/tone-220hz-10s.wav +0 -0
- package/e2e/fixtures/tone-440hz-2s.wav +0 -0
- package/e2e/fixtures/tone-880hz-5s.wav +0 -0
- package/e2e/fixtures/tone-stereo-3s.wav +0 -0
- package/e2e/fixtures/user-provided/README.md +1 -0
- package/e2e/helpers/app.ts +143 -0
- package/e2e/helpers/hooks.ts +133 -0
- package/e2e/helpers/index.ts +12 -0
- package/e2e/helpers/media.ts +125 -0
- package/e2e/helpers.ts +10 -0
- package/e2e/p2p-collaboration.spec.ts +356 -0
- package/e2e/p2p-multi-peer.spec.ts +723 -0
- package/e2e/p2p-states.spec.ts +302 -0
- package/e2e/playback.spec.ts +56 -0
- package/e2e/playlist-crud.spec.ts +126 -0
- package/e2e/share-link-autoplay.spec.ts +129 -0
- package/e2e/sharing-access.spec.ts +205 -0
- package/e2e/sharing.spec.ts +195 -0
- package/e2e/song-cache-state.spec.ts +202 -0
- package/e2e/zip-bundle.spec.ts +855 -0
- package/eslint.config.js +114 -0
- package/index.html +54 -0
- package/package.json +119 -0
- package/public/sw.js +134 -0
- package/scripts/use-local.mjs +37 -0
- package/scripts/use-published.mjs +37 -0
- package/src/App.tsx +9 -0
- package/src/cli/check.ts +164 -0
- package/src/cli/generate.ts +184 -0
- package/src/cli/http.ts +88 -0
- package/src/cli/index.ts +65 -0
- package/src/cli/init.ts +18 -0
- package/src/components/AllPlaylistsPanel.tsx +713 -0
- package/src/components/AudioPlayer.tsx +122 -0
- package/src/components/MarqueeText.tsx +101 -0
- package/src/components/PlaylistCoverModal.tsx +519 -0
- package/src/components/PlaylistEditPanel.tsx +803 -0
- package/src/components/PlaylistSharePanel.tsx +1020 -0
- package/src/components/ShareLinkKnockPanel.tsx +144 -0
- package/src/components/SharePanel.tsx +584 -0
- package/src/components/SongEditModal.tsx +453 -0
- package/src/components/SongEditPanel.tsx +578 -0
- package/src/components/SongRow.tsx +689 -0
- package/src/components/index.tsx +494 -0
- package/src/components/playlist/index.tsx +1203 -0
- package/src/context/PlaylistzContext.tsx +74 -0
- package/src/dev-hooks.ts +35 -0
- package/src/hooks/createDocIndexQuery.ts +53 -0
- package/src/hooks/createDocStore.test.ts +303 -0
- package/src/hooks/createDocStore.ts +90 -0
- package/src/hooks/useDragAndDrop.test.ts +474 -0
- package/src/hooks/useDragAndDrop.ts +400 -0
- package/src/hooks/useImageModal.test.ts +174 -0
- package/src/hooks/useImageModal.ts +201 -0
- package/src/hooks/usePlaylistManager.test.ts +453 -0
- package/src/hooks/usePlaylistManager.ts +685 -0
- package/src/hooks/usePlaylistsQuery.test.tsx +120 -0
- package/src/hooks/usePlaylistsQuery.ts +44 -0
- package/src/hooks/useSongState.test.ts +236 -0
- package/src/hooks/useSongState.ts +114 -0
- package/src/hooks/useUIState.ts +71 -0
- package/src/index.tsx +18 -0
- package/src/services/audioService.dev.ts +22 -0
- package/src/services/audioService.test.ts +1226 -0
- package/src/services/audioService.ts +1395 -0
- package/src/services/automergeRepo.test.ts +269 -0
- package/src/services/automergeRepo.ts +226 -0
- package/src/services/blobTransferService.dev.ts +119 -0
- package/src/services/blobTransferService.test.ts +441 -0
- package/src/services/blobTransferService.ts +702 -0
- package/src/services/docIndexService.test.ts +179 -0
- package/src/services/docIndexService.ts +118 -0
- package/src/services/fileProcessingService.test.ts +554 -0
- package/src/services/fileProcessingService.ts +239 -0
- package/src/services/imageService.test.ts +701 -0
- package/src/services/imageService.ts +365 -0
- package/src/services/indexedDBService.integration.test.ts +104 -0
- package/src/services/indexedDBService.test.ts +202 -0
- package/src/services/indexedDBService.ts +436 -0
- package/src/services/offlineService.test.ts +661 -0
- package/src/services/offlineService.ts +382 -0
- package/src/services/p2pService.test.ts +305 -0
- package/src/services/p2pService.ts +344 -0
- package/src/services/playlistDocService.test.ts +448 -0
- package/src/services/playlistDocService.ts +707 -0
- package/src/services/playlistDownloadService.test.ts +674 -0
- package/src/services/playlistDownloadService.ts +389 -0
- package/src/services/sharingService.test.ts +812 -0
- package/src/services/sharingService.ts +1073 -0
- package/src/services/sharingState.ts +161 -0
- package/src/services/songReactivity.test.ts +620 -0
- package/src/services/songReactivity.ts +145 -0
- package/src/services/standaloneService.test.ts +1025 -0
- package/src/services/standaloneService.ts +588 -0
- package/src/services/streamingAudioService.test.ts +275 -0
- package/src/services/streamingAudioService.ts +166 -0
- package/src/styles.css +428 -0
- package/src/test-setup.ts +547 -0
- package/src/types/global.d.ts +40 -0
- package/src/types/playlist.ts +99 -0
- package/src/utils/hashUtils.ts +41 -0
- package/src/utils/log.ts +97 -0
- package/src/utils/m3u.test.ts +172 -0
- package/src/utils/m3u.ts +136 -0
- package/src/utils/mockData.ts +166 -0
- package/src/utils/standaloneTemplates.test.ts +175 -0
- package/src/utils/standaloneTemplates.ts +83 -0
- package/src/utils/swTemplate.ts +84 -0
- package/src/utils/timeUtils.ts +166 -0
- package/src/utils/typeGuards.ts +171 -0
- package/src/web-component.tsx +98 -0
- package/src/zip-bundle/index.ts +7 -0
- package/src/zip-bundle/m3u.ts +45 -0
- package/src/zip-bundle/types.ts +50 -0
- package/src/zip-bundle/utils.ts +33 -0
- package/src/zip-bundle/zipBuilder.ts +309 -0
- package/tailwind.config.js +55 -0
- package/tsconfig.json +43 -0
package/docs/TODO.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
in rough prio
|
|
4
|
+
|
|
5
|
+
## 0
|
|
6
|
+
|
|
7
|
+
[x] so need to refactor the "standalone" mode a bit; so i want to use playlistz.js convention (as in <script src="playlistz.js"></script> in html) file for one or more playlists data, instead of embedding into .html file, then don't need to template the .html file...
|
|
8
|
+
|
|
9
|
+
[x] some kind of interactive (and also script-able) cli mjs script to take a bunch of audio files + playlist metadata and generate a standalone playlistz.js, maybe also a .html and sw.js generator? then i could just build a freqhole-playlistz.js file. is there a way i could have the same freqhole-playlistz.js be used in a browser like <script type="module" src="freqhole-playlistz.js"></script> as well as in node like `node freqhole-playlistz.js --generate-html` or even start a lil' express static file server?
|
|
10
|
+
|
|
11
|
+
[] freqhole could dispense takeout download linkz that generated playlist data.zip with this (package lib thing?)
|
|
12
|
+
[] some kind of npm or github package lib thing, so prolly CI github action .yml filez to build + deploy npm package, some kind of cli tool, single .html + sw.js file
|
|
13
|
+
|
|
14
|
+
## 1
|
|
15
|
+
|
|
16
|
+
[x] need more immediate ui feedback for when drag-n-drop is loading playlist
|
|
17
|
+
|
|
18
|
+
[x] play/pause needs some more work so it resumes position. song positions (e.g. the background fill on the song row) shouldn't disappear when pausing song, or skipping to a different track. when playing a track, start playback at last-left-off position, unless that position is >95% of the track time, then can restart at the beginning but don't clear the background position fill until the track starts over again at the beginning (or if the user manually seeks the track time). so play/pause of each song should track per-song playback state, and the global play/pause should track playing and pausing the current song (right now it just starts back at the beginning). there should be code that's doing this? please make sure to dig before adding new code. keep test coverage up.
|
|
19
|
+
|
|
20
|
+
[x] this `<` `>` menu toggle is still not right =? could it be at the bottom of the page, after all the playlist songz? orrrr could it be only shown in the "modal" window view? so like edit buttonz on playlist will bring open a modal that's sized to the playlist size but include the `<` `>` menu toggle button then can show the sidebar stuff then! OH! the edit modalz could be in the sidebar content area! then there could be a `<` toggle button to current all-playlistz, search, + new playlist stuff!! yesss.
|
|
21
|
+
|
|
22
|
+
i'd like to re-work the edit playlist + edit song modals and how this `<` `>` sidebar menu toggle works.
|
|
23
|
+
|
|
24
|
+
so first, the edit playlist modal doesn't need to be a "modal", how i'd like it to work is by dropping out the song rows and then rendering a panel in the space where the song rows were that's the same content as the edit playlist modal. i'd like to animate the transitions here, so that the first, say 5 song rows drop out one by one, and then the animaton ramps up and clears them all out, so like one by one (in quick succession) each row falls straight down out of view. then the edit panel expands to fill in the space. when it's close, the reverse happens, panel shrinks down and song rows go back up.
|
|
25
|
+
|
|
26
|
+
when a song edit modal opens, in a similar way, the selected song row stays and all the other song rows + playlist header either drops down (if after/below the current song) or goes up and out if above the current song, including the playlist header section, then the edit song panel fills into the now-open space. when exiting, the animation goes in reverse, so the panel shrinks down to close and the other rows come back in from above or below.
|
|
27
|
+
|
|
28
|
+
right, so then it's only when one of these edit panels are open, that i want to show sidebar `<` `>` menu toggle button and sidebar contents. i guess also want to show the sidebar if there's 0 playlists.
|
|
29
|
+
|
|
30
|
+
## 2
|
|
31
|
+
|
|
32
|
+
[x] edit playlist form controlz + idb persistence for how background image filter is set
|
|
33
|
+
[] does sw.js need more work?
|
|
34
|
+
|
|
35
|
+
## 3
|
|
36
|
+
|
|
37
|
+
[] i guess it could have a midden iroh wasm client...
|
|
38
|
+
|
|
39
|
+
so this is a bigger ask: i'd like to introduce iroh p2p networking into playlistz. i'd like for it to be interoperable with the existing freqhole setup i've got already over in tomb/client/midden, and i think playlistz would also need to use the freqhole-api-client over in tomb/client-codegen/freqhole-api-client. the idea, for now is that playlistz can just use `file:../tomb/...` npm package ref for freqhole-api-client and midden (or we can just cp midden's dist output; i'll look into publishing these two packages at some point, maybe, for now just want to use file: path link). the playlistz data model will need some changes to fit better with freqhole's playlist schema, namely supportig multiple cover and song images (with one having a primary property), song image "waveform" types, playlist + song entity url linkz, and song lyrics.
|
|
40
|
+
|
|
41
|
+
for the playlistz app interface, i want to add a new "share" icon button to the playlist, after the zip download icon button, that will open a share panel (similar to the edit playlist panel), that will let a user do an initial midden endpoint setup, so setup things like a name, avatar image, some settings to set if all playlists are public or if they require knock access requests.
|
|
42
|
+
|
|
43
|
+
i'd like to start a new "freqhole-playlistz" ALPN that both freqhole and the playlistz web app can use to communicate. spume can track and generate base64 share links that contain node ids and some other helpful info to display a share link, e.g. https://spume.freqhole.net/#?share=eyJpIjoiOTRjZGZlMjhjMWE1MDJhOCIsImsiOiJwbGF5bGlzdCIsInMiOnsibiI6IjBkMGRlNTQwY2E1NWMzMzdkMzZhNmEwN2M3N2I1NGMwYTJhMjM0NDgyOWNiMzE0MDczY2U1YTA5NGVkZGQyNDAifSwidCI6ImRhbmNlMnRoaXMiLCJ2IjoxfQ so i'd like playlistz app to have a similar way to share. the idea would be playlistz app can read freqhole playlistz that are shared with it (and the user has proper access and the ability to send knock requests). otherwise the playlistz web app can share it's playlistz with other playlistz app users (e.g. without freqhole) so either anyone (if public) or require an access knock request (want to borrow as much from freqhole's knock stuff as possible here). the browser node will need to do like accept_bi on the iroh endpoint so it can process incoming requests from other nodes.
|
|
44
|
+
|
|
45
|
+
while i don't plan for this playlistz app to run on the same domain as spume, i want it to be able to read spume's indexed db, opfs, and iroh secret key if it is. so ideally we'd be able to share a lot of the same indexed db logic as well. in some sensible way, so that tomb/\* code doesn't have to know about playlistz app, so maybe some this has to come out of freqhole-api-client, or just have the same indexed and opfs key and schemas so it will interop-- gonna need to find a delicate balance here.
|
|
46
|
+
|
|
47
|
+
so, then, first i'd like you to dig around the tomb/ and playlistz/ code to get a better sense of how this will be implemented in these codebases and then start a new .md file in playlistz/docs/ with a plan for implementing all this stuff and track progress as we work thru and complete all the work, keep open questions out of the plan doc and let's just have a conversation about anything that comes up. ideally you would include as much technical details as possible in the plan doc, and then dispatch claude sonnet 4.6 subagents for as much work here as possible. this is a lot of "feature" but also a lot of the code needed to do this has pretty much been worked out in tomb/, so a big part of this is will be digging around the code there and finding good ways to share it in playlistz/ app, which might mean some refactoring or just re-implementing the same patterns in playlistz/ if it's too much of a chore to refactor and share (ideally i'd think a good amount of code from tomb could be shared, atleast in terms of biz logic, of course playlistz will have it's own presentational aesthetic and frontend ui implementation)
|
|
48
|
+
|
|
49
|
+
FREQHOLE_ALPN.to_vec(),
|
|
50
|
+
AUTOMERGE_ALPN.to_vec(),
|
|
51
|
+
FRIENDZ_ALPN.to_vec(),
|
|
52
|
+
ADMIN_ALPN.to_vec(),
|
|
53
|
+
EVENTS_ALPN.to_vec(),
|
|
54
|
+
iroh_blobs::ALPN.to_vec(),
|
|
55
|
+
|
|
56
|
+
in tomb/client/midden/src/lib.rs
|
|
57
|
+
|
|
58
|
+
# THU, JUN 11, 2026
|
|
59
|
+
|
|
60
|
+
so i guess if we could do webauthn-rs passkey stuff (in a https:// web browser) then it could be perhaps possible for two nodes to validate a user? like if the charnel tauri app and rathole cli could be, more or less, a hub server, where users can register their passkey for that user, then with passkey validation, they could write a new peer node id device for access? like a https://spume.freqhole.net/?query_auth_node_id=abcdef123456..&return_to_node_id=0202aaabbbdcba.. could open, init a new iroh endpoint that calls the query auth node id and validates passkey, then inserts a new allowed peer node for self, and then sends some kind of success/fail token back to the return_to node?
|
|
61
|
+
|
|
62
|
+
[] double click playlist in sidebar to start playing it.
|
|
63
|
+
[] move share modal from sidebar to main page content, try to emulate the edit playlist layout, so will need to animate out the current playlist details and then show like:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
[share modal, where the playlist header is, with transparent background so current playlist background image comes thru. x (close icon button in top right)]
|
|
67
|
+
|
|
68
|
+
[$(playlist img) $(playlisttitle) `<` `>` buttons to show share detail panel for stuff related to playlist, currently this is only the share link text input + copy to clipboard button; will include other stuff here in the near future]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
so then also move the share button from the sidebar nav, back into the playlist detail action buttons: after the edit button, before the download button.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
okay, so i'm hoping you can help me work on the playlistz/ code here in this workspace. what i'm hoping to do is re-work the layout and navigation to be a bit more consistent. so currently, the main layout for a playlist is like:
|
|
76
|
+
|
|
77
|
+
a playlist header section
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
PLAYLIST_NAME (editable div) | |
|
|
81
|
+
PLAYLIST_DESCRIPTION (also editable div) | cover |
|
|
82
|
+
song count + total time | img |
|
|
83
|
+
PLAY/PAUSE BUTTON [ACTION BUTTONS]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
and then under that song rows. when the edit mode is toggled, the header stays in place, and the song rows section switched out to the edit panel and the edit button in the action buttons section of the header gets a magenta border style. this is working great and how i'd like it to work.
|
|
87
|
+
|
|
88
|
+
however, then, the "all playlists" view and action button, and the share view and action button don't quite work like this.
|
|
89
|
+
|
|
90
|
+
the share view should be easy, we just need to keep the playlist header section in place, like the edit mode does it.
|
|
91
|
+
|
|
92
|
+
and then a similar thing for the all playlists view, the currently selected playlist header section then just becomes a row w/ sticky top + bottom position. and then the other playlists are added as rows also with the same header section as row. currently the row view here for all playlists is much different, which i want to change, and re-use the same playlist header section for row view as much as possible. it will probably need some variation when in row-form. having a dedicated row for adding a playlist is great, and i'd like to keep that, it should also be sticky bottom position (and play well with the other active playlist sticky row).
|
|
93
|
+
|
|
94
|
+
then the all playlists view and share view don't need `x` close or headers shown.
|
|
95
|
+
|
|
96
|
+
so thoughts about this? i really hope it's clear what i'm asking here, we'll probably need to refactor the edit playlist + song row container into a more generic app layout structure of files and components and hooks etc. needed to support this. do you have any questions for me about this work?
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
some issues now with the narrow mobile views? i think the playlist header
|
|
101
|
+
|
|
102
|
+
71144
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
some more things:
|
|
107
|
+
|
|
108
|
+
there should be a search query input somewhere, i think on the `new playlist` row in all playlists view? it needs so many playlists to show, let's remove that, and show a search input all the time. the search input can also take share links, then we can remove the `open a share link` and `browse a peer's playlistz` in the share panel (need to make it so it can figure out if a knock is needed)-- the search bar should be able to detect a node_id or a share link and handle accordingly.
|
|
109
|
+
|
|
110
|
+
for now, i want to hide the `node id:` display in the share panel.
|
|
111
|
+
|
|
112
|
+
also when there's no knocks, we don't need to show any empty state, like:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
knock inbox
|
|
116
|
+
no pending knockz
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
when browsing a users playlists, it should work the same way all playlists does in addition to a new field, after the date last modified in the playlists rows, that includes the display name of the remote user (and possibly an avatar, can we work this into the iroh sharing? might be more involved.); remember we need to keep the currently selected playlist visible in this all playlists view.
|
|
120
|
+
|
|
121
|
+
i also want to work more on a "collaborative" playlist sharing setting, where other users can do all the automerge syncing stuff so all the peers that are allowed to edit the playlist, sync together. otherwise, if not a collaborative playlist (or a peer that isn't allowed to collaborate in editing the playlist) should not send their automerge docs changes back. however, i do want to be able to let the peer keep getting playlist edits. it would be nice to have some ui feedback for when a playlist is in this "subscribed" mode and will get updates, i suppose then what to do if the user makes local changes to a subscribed playlist and then new changes come in? i feel like we should just let automerge sync, in the even there's any conflicts, it'd be great to show something in the share edit panel that let's the user either: 1. accept all the incoming changes (their local changes will get overwritten) or 2. add the remote playlist as a new playlist, and then unsubscribe their local copy from the remote automerge document since it's now a standalone fork. every playlist is gonna need it's own list of peer nodes that can collaborate.
|
|
122
|
+
|
|
123
|
+
i think also, each playlist is gonna need to have it's sharing mode explicitly set and started, so by default a playlist should not be shared until the user explicity chooses something from the "who can browse my playlistz?" options, which would be `public` (anyone can read and sync updates, but not make edits), knock first. and then i'm not quite sure where the new "collaborative" toggle would come in, or how it would work. maybe it could work like this:
|
|
124
|
+
|
|
125
|
+
so when a playlist creator shares a playlist with another user, say it's "knock first" setting, they paste the link into the search input, or just click it, then they go thru the knock flow (oh by the way the knock requests should also include a short free-form message text with the label "say who you are and mention something only the admin would know (but no passwords or secrets!)"), then when the playlist is added the UI is in, like read-only mode, so no new songs can get added, the name or description can't be edited, no new images or drag and drop reordering. in the edit menu, we can add two buttons, one that will turn the playlist into just a local fork copy that's basically like a user's own playlist and the other is a "collaborate" button that then will issue another knock access request to be a collaborator.
|
|
126
|
+
|
|
127
|
+
so then in the playlist creator's share panel, need to be able to accept both kinds of knock requests, and manage who's collaborators.
|
|
128
|
+
|
|
129
|
+
for now i want to keep the global share endpoint on/off toggle and display name, but i'd like to move the display name to the top of the share panel, and have the name just be a short text (like a pill badge) and then it can be clicked to open a text-input that can change the display name and then `x` to close (will auto set the displayname on key change events so no save button needed), perhaps this is where a user could set an avatar image? see skein/ for examples on how i handle profile avatars?
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
remote "playlist deleted" toast
|
|
134
|
+
|
|
135
|
+
✗registration failed: failed to start registration
|
|
136
|
+
|
|
137
|
+
6d8bdc333c74279db9bb090d9f2d61c0ec7b79e1da223e4accc4435adb1e6ae7
|
|
138
|
+
2914-whoosh-snail-koala-yodel
|
|
139
|
+
|
|
140
|
+
https://www.youtube.com/watch?v=HJgjZ6vbHI0&list=RDHJgjZ6vbHI0&start_radio=1
|
|
141
|
+
|
|
142
|
+
if there's precheck config, add review step to add music fetch from url flow so user can confirm what yt-dlp will fetch
|
|
143
|
+
|
|
144
|
+
route passkeys over p2p! browser users can use passkey to auth with a remote (in addition to knock access requests and invite codes). also can link out to spume.freqhole.net so charnel app users (desktop or android apps) so users can use browser for passkey.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
[x] update freqhole.net docs for recent features
|
|
149
|
+
|
|
150
|
+
[x] yank ci job that tries to update .mdx file updated at, just have the single script that reads git commits (force?) update them all in one go, i can just run this locally on my machine where i have the git history (unlike ci).
|
|
151
|
+
|
|
152
|
+
[x] after deleting a playlist, need to route to a different playlist (or show empty state if the last playlist was deleted).
|
|
153
|
+
|
|
154
|
+
[x] is it possible to register a new passkey with a web browser client that already has access to a remote? i guess i just need to test this more.
|
|
155
|
+
|
|
156
|
+
[x] users should be able to generate their own account-link invite codes. should be set to pretty quick expire time (say maybe around 30mins or 1hr). users should also be able to update their own usernames (but not to a username that's already in use in the db, so need friendly message for this case: "ohey, that handle is already taken; if you ask the admin nicely, they might be able to help."). perhaps there's a user profile view in the settings, linked where the "manage passkeys" is, so remove the "manage passkeys" link there and move the link to the username + role (after the "singed in as") and then add a new settings view, we can probably just add the passkey stuff from `#/settings/remotes/$(remote_name)/passkeys` to this new user profile view that can do all this stuff?
|
|
157
|
+
|
|
158
|
+
[ ] sync all music from library to another remote (i.e. push all music files, images, and most all metadata to a remote, de-dupe by blake3 or sha256 hashes). where in the ui to add this? maybe it's a rathole thing? would be nice in spume so collections built up in web browser can get synced to a more stable remote. >----
|
|
159
|
+
------> OR MAYBE: like "federation" so two freqhole instances can share up to all the sqlite (or indexed db) db data? but otherwise selectively pick & choose: like share these users, passkeys, or these tags, or just user favorites and playlists, etc etc etc.
|
|
160
|
+
|
|
161
|
+
[x] publish freqhole-api-client and midden to npm so it's easier to use in playlistz. so need to setup @freqhole npm org package namespace and update package names, and then publish once from local machine, then add ci to publish new packages on tag (avoid draft releases). >----> NEED TO PUBLISH ONCE FROM LOCALHOST:
|
|
162
|
+
|
|
163
|
+
[ ] playlistz <-> freqhole playlist sync
|
|
164
|
+
|
|
165
|
+
[ ] has the time come to improve the last_seen_at user peer nodes? would make gossip and updating peerz when playlist changes a bit easier
|
|
166
|
+
|
|
167
|
+
[ ] graph viz has bugz where sometimes a selected artist causes the graph to collapse and then the selected artist node disappears and a bunch of hub nodes are rendered but it's, like real wonky.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# bundle embedding plan
|
|
2
|
+
|
|
3
|
+
the zip bundle needs to include `freqhole-playlistz.js` and `freqhole-playlistz-cli.mjs`
|
|
4
|
+
so that the standalone `index.html` is fully self-contained (no network required).
|
|
5
|
+
|
|
6
|
+
## current approach (problems)
|
|
7
|
+
|
|
8
|
+
`buildPlaylistZip` fetches the bundle at zip-build time:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
fetch(`${window.location.origin}/freqhole-playlistz.js`);
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
problems:
|
|
15
|
+
|
|
16
|
+
- in vite dev mode, `origin/freqhole-playlistz.js` may not be served unless
|
|
17
|
+
`npm run build` has been run and the dev server is set to serve `dist/`
|
|
18
|
+
- in spume/charnel (tauri), the origin is `tauri://localhost` and the asset
|
|
19
|
+
may or may not be registered depending on tauri's asset protocol config
|
|
20
|
+
- the caller must pass `appBundleUrl` to override; the fallback is fragile
|
|
21
|
+
- the cli build path (`freqhole-playlistz-cli.mjs`) has the same fragility
|
|
22
|
+
|
|
23
|
+
## option A: seed zip baked into the package (recommended)
|
|
24
|
+
|
|
25
|
+
as part of the playlistz package build (`npm run build`), generate a
|
|
26
|
+
`dist/bundle-seed.zip` that contains only the two js files:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
dist/bundle-seed.zip
|
|
30
|
+
freqhole-playlistz.js
|
|
31
|
+
freqhole-playlistz-cli.mjs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`buildPlaylistZip` opens this seed zip and adds playlist content (audio, images,
|
|
35
|
+
html, playlistz.js) into it, rather than starting from scratch. this means:
|
|
36
|
+
|
|
37
|
+
- the bundle is always available as a package asset (no network request)
|
|
38
|
+
- callers pass nothing extra - it just works
|
|
39
|
+
- the zip builder imports the seed as a base64-encoded or raw buffer included
|
|
40
|
+
in the package (via `?url` or `?raw` vite import, or `fs.readFileSync` in node)
|
|
41
|
+
|
|
42
|
+
### implementation steps
|
|
43
|
+
|
|
44
|
+
1. add a `build:seed-zip` npm script that runs after `build:standalone`:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
node scripts/build-seed-zip.mjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
reads `dist/freqhole-playlistz.js` + `dist/freqhole-playlistz-cli.mjs`,
|
|
51
|
+
builds a zip, writes to `dist/bundle-seed.zip`
|
|
52
|
+
|
|
53
|
+
2. in `zipBuilder.ts`, import the seed zip:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// vite: import as url so it can be fetched from the package
|
|
57
|
+
import seedZipUrl from "../../dist/bundle-seed.zip?url";
|
|
58
|
+
// or in node/cli context: read via fs
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
then `JSZip.loadAsync(await fetch(seedZipUrl).then(r => r.arrayBuffer()))` to
|
|
62
|
+
get a base zip, then add content into it.
|
|
63
|
+
|
|
64
|
+
3. the cli path can read the seed zip via `fs.readFileSync` relative to the
|
|
65
|
+
package root - no network request needed.
|
|
66
|
+
|
|
67
|
+
### dev-mode handling
|
|
68
|
+
|
|
69
|
+
in vite dev mode, `?url` imports still work (vite serves them as assets). the
|
|
70
|
+
seed zip is static content, so vite just serves the file. **callers no longer
|
|
71
|
+
need to pass `appBundleUrl`** at all.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## option B: fetch from cdn / freqhole.net
|
|
76
|
+
|
|
77
|
+
provide fallback urls for npm cdn and a self-hosted mirror:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const BUNDLE_URLS = [
|
|
81
|
+
`${window.location.origin}/freqhole-playlistz.js`, // local server (preferred)
|
|
82
|
+
"https://cdn.freqhole.net/playlistz/0.0.1/freqhole-playlistz.js",
|
|
83
|
+
"https://unpkg.com/@freqhole/playlistz@0.0.1/dist/freqhole-playlistz.js",
|
|
84
|
+
"https://cdn.jsdelivr.net/npm/@freqhole/playlistz@0.0.1/dist/freqhole-playlistz.js",
|
|
85
|
+
];
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
problems with option B:
|
|
89
|
+
|
|
90
|
+
- requires publishing the package to npm (or maintaining a cdn)
|
|
91
|
+
- requires a network request at zip-build time (breaks offline use)
|
|
92
|
+
- version pinning is manual - easy to get stale bundles
|
|
93
|
+
- does not solve the vite dev mode problem
|
|
94
|
+
|
|
95
|
+
option B is useful as a **last-resort fallback** (e.g. if the seed zip is
|
|
96
|
+
somehow missing from the package), not as the primary mechanism.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## option C: embed the bundle as a base64 string at build time
|
|
101
|
+
|
|
102
|
+
embed the bundle bytes directly into the js bundle via a vite/rollup plugin or
|
|
103
|
+
a `?raw` import followed by btoa. this avoids a separate zip asset.
|
|
104
|
+
|
|
105
|
+
problems:
|
|
106
|
+
|
|
107
|
+
- doubles the package size (bundle is ~8MB; base64 is ~11MB)
|
|
108
|
+
- the embedded bundle cannot be updated without rebuilding the package
|
|
109
|
+
- not practical for the cli path
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## recommended approach
|
|
114
|
+
|
|
115
|
+
**primary**: option A (seed zip baked into the package).
|
|
116
|
+
|
|
117
|
+
**fallback order when seed zip is unavailable**:
|
|
118
|
+
|
|
119
|
+
1. try `${window.location.origin}/freqhole-playlistz.js` (local server)
|
|
120
|
+
2. warn and omit the bundle from the zip (zip still works for playing
|
|
121
|
+
on an http server that serves the bundle separately)
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## vite dev mode notes
|
|
126
|
+
|
|
127
|
+
in dev mode (`npm run dev`), vite does not serve `dist/`. to test the zip
|
|
128
|
+
bundle download in dev mode, either:
|
|
129
|
+
|
|
130
|
+
- run `npm run build:standalone` first (generates `dist/freqhole-playlistz.js`)
|
|
131
|
+
and configure vite to serve `dist/` as a static folder
|
|
132
|
+
- or use option A's seed zip which vite serves as a static asset import
|
|
133
|
+
|
|
134
|
+
this is tracked as a known limitation until option A is implemented.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# standalone refactor plan
|
|
2
|
+
|
|
3
|
+
## goal
|
|
4
|
+
|
|
5
|
+
refactor the standalone build from a single self-contained `freqhole-playlistz.html` (with all js/css inlined) into:
|
|
6
|
+
|
|
7
|
+
- `freqhole-playlistz.js` - dual-use ESM bundle: registers web component in browser, runs as CLI in Node
|
|
8
|
+
- `playlistz.js` - data file (generated by cli or freqhole takeout); sets `window.__PLAYLISTZ__`
|
|
9
|
+
- `index.html` - static, minimal, generated once by cli; never needs to be re-templated
|
|
10
|
+
- `sw.js` - static service worker, generated by cli
|
|
11
|
+
|
|
12
|
+
the `index.html` becomes truly static - only `playlistz.js` and `data/` change when playlist content changes.
|
|
13
|
+
|
|
14
|
+
`playlistz.js` is generated by two different paths that share the same template functions:
|
|
15
|
+
|
|
16
|
+
- **in-app download**: the existing "download zip" button generates `playlistz.js` + `index.html` + `sw.js` and bundles them into a zip
|
|
17
|
+
- **cli**: `npx freqhole-playlistz --init` / `--generate-data` writes the same files to disk
|
|
18
|
+
|
|
19
|
+
both paths import the shared template functions from `src/utils/standaloneTemplates.ts`.
|
|
20
|
+
|
|
21
|
+
## output directory layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
output/
|
|
25
|
+
freqhole-playlistz.js # app bundle (from npm or built locally)
|
|
26
|
+
playlistz.js # playlist data (generated by cli)
|
|
27
|
+
index.html # static shell (generated by cli)
|
|
28
|
+
sw.js # service worker (generated by cli)
|
|
29
|
+
data/
|
|
30
|
+
song1.mp3
|
|
31
|
+
song2.mp3
|
|
32
|
+
playlist-cover.jpg
|
|
33
|
+
song1-cover.jpg
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## freqhole-playlistz.js structure
|
|
37
|
+
|
|
38
|
+
the built file wraps everything in environment guards:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// css injector + solid web component bundle, guarded for browser
|
|
42
|
+
const isBrowser =
|
|
43
|
+
typeof window !== "undefined" && typeof customElements !== "undefined";
|
|
44
|
+
if (isBrowser) {
|
|
45
|
+
// inject compiled css into document.head at runtime
|
|
46
|
+
(() => {
|
|
47
|
+
const s = document.createElement("style");
|
|
48
|
+
s.textContent = "...";
|
|
49
|
+
document.head.appendChild(s);
|
|
50
|
+
})();
|
|
51
|
+
// solid web component registration
|
|
52
|
+
// ...vite-built bundle...
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// node cli entrypoint
|
|
56
|
+
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
57
|
+
if (isNode) {
|
|
58
|
+
const { fileURLToPath } = await import("node:url");
|
|
59
|
+
const isMain = process.argv[1] === fileURLToPath(import.meta.url);
|
|
60
|
+
if (isMain) {
|
|
61
|
+
// cli dispatch
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
format must be ESM (`format: 'es'`) to support `import.meta.url` and top-level `await` in both environments.
|
|
67
|
+
|
|
68
|
+
## playlistz.js data format
|
|
69
|
+
|
|
70
|
+
supports one or more playlists:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
window.__PLAYLISTZ__ = [
|
|
74
|
+
{
|
|
75
|
+
playlist: { id, title, description, rev, imageExtension, imageMimeType, safeFilename },
|
|
76
|
+
songs: [ { id, title, artist, album, duration, originalFilename, fileSize, sha, ... }, ... ]
|
|
77
|
+
},
|
|
78
|
+
// ...more playlists
|
|
79
|
+
];
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
the structure is validated with Zod schemas defined in `src/utils/standaloneTemplates.ts`. this is used:
|
|
83
|
+
|
|
84
|
+
- in `standaloneService.ts` when the web component reads `window.__PLAYLISTZ__` at init
|
|
85
|
+
- in the cli when reading a user-provided `playlistz.js` or generating one from audio files
|
|
86
|
+
- in the download zip service when assembling the export bundle
|
|
87
|
+
|
|
88
|
+
the web component checks `window.__PLAYLISTZ__` on init and calls `initializeStandalonePlaylist()` for each entry. `standaloneService.ts` already handles single-playlist init - needs to be extended to loop over multiple entries.
|
|
89
|
+
|
|
90
|
+
## shared template utilities (`src/utils/standaloneTemplates.ts`)
|
|
91
|
+
|
|
92
|
+
a new file that both the browser app and the Node cli can import. browser-compatible (no Node-only APIs). exports:
|
|
93
|
+
|
|
94
|
+
- `FreqholePlaylistSchema` - Zod schema for a single `{ playlist, songs }` entry (replaces raw `StandaloneData` interface in `standaloneService.ts`)
|
|
95
|
+
- `FreqholePlaylistzSchema` - Zod schema for `window.__PLAYLISTZ__`, i.e. `z.array(FreqholePlaylistSchema)`
|
|
96
|
+
- `generatePlaylistzJs(playlists)` - serializes the entries array to `window.__PLAYLISTZ__ = [...];
|
|
97
|
+
` string
|
|
98
|
+
- `generateIndexHtml()` - returns the minimal static `index.html` as a string; template is read from `src/templates/index.html` at Vite build time and inlined as a string constant
|
|
99
|
+
- `generateSwJs()` - returns `sw.js` content as a string; `public/sw.js` is read at Vite build time and inlined as a string constant
|
|
100
|
+
|
|
101
|
+
both `generateIndexHtml()` and `generateSwJs()` just return pre-baked string constants - no runtime file I/O in browser or Node.
|
|
102
|
+
|
|
103
|
+
the download zip service (`playlistDownloadService.ts`) already generates the zip bundle - it will call these functions instead of its own templating. the cli calls the same functions and writes files to disk.
|
|
104
|
+
|
|
105
|
+
## changes required
|
|
106
|
+
|
|
107
|
+
### phase 1 - build refactor (`build-component.js`)
|
|
108
|
+
|
|
109
|
+
- [x] output format changed to `'es'` for vite rollup, then post-processed to IIFE by esbuild
|
|
110
|
+
- [x] css minified via esbuild `transform` with `loader: 'css'`, newlines stripped, injected as a string constant prepended to the browser bundle
|
|
111
|
+
- [x] css asset removed from bundle output (no separate `.css` file)
|
|
112
|
+
- [x] emit `freqhole-playlistz.js` instead of inlining js into html
|
|
113
|
+
- [x] browser bundle wrapped in `if (isBrowser) { ... }` shell
|
|
114
|
+
- [x] `if (isNode) { ... }` cli shell appended (stub for phase 6)
|
|
115
|
+
- [x] emit static `index.html` from `generateIndexHtml()`
|
|
116
|
+
- [x] emit `sw.js` from `readServiceWorker()`
|
|
117
|
+
- [x] removed `generateStandaloneHtml()` from `build-component.js`
|
|
118
|
+
- [x] esbuild `transform` post-pass: minifies js + css together, produces `.map` sourcemap file, outputs IIFE format (no `type="module"` needed, works on `file://` urls)
|
|
119
|
+
- [x] `index.html` uses plain `<script src>` (not `type="module"`) for `file://` compatibility
|
|
120
|
+
|
|
121
|
+
### phase 2 - shared template utilities (`src/utils/standaloneTemplates.ts`)
|
|
122
|
+
|
|
123
|
+
- [x] `zod` added as a dependency
|
|
124
|
+
- [x] `FreqholePlaylistSchema` and `FreqholePlaylistzSchema` defined; inferred types exported
|
|
125
|
+
- [x] `generatePlaylistzJs(playlists)` implemented
|
|
126
|
+
- [x] `generateIndexHtml()` implemented (plain `<script src>`, no `type="module"`)
|
|
127
|
+
- [x] `generateSwJs()` implemented via `?raw` import of `public/sw.js`
|
|
128
|
+
- [x] unit tests for all three generators and schema parse/reject cases
|
|
129
|
+
|
|
130
|
+
### phase 3 - web component init (`web-component.tsx`, `components/index.tsx`)
|
|
131
|
+
|
|
132
|
+
- [x] `connectedCallback` checks `window.__PLAYLISTZ__`, parses with `FreqholePlaylistzSchema`, calls `initializeStandalonePlaylist()` for each entry
|
|
133
|
+
- [x] `window.STANDALONE_MODE` / `window.DEFERRED_PLAYLIST_DATA` mechanism removed
|
|
134
|
+
- [x] `global.d.ts` updated with `__PLAYLISTZ__` type declaration
|
|
135
|
+
|
|
136
|
+
### phase 4 - multi-playlist standalone support (`standaloneService.ts`)
|
|
137
|
+
|
|
138
|
+
- [x] raw interfaces replaced with Zod-inferred types from `standaloneTemplates.ts`; `StandaloneData` kept as a backwards-compat alias
|
|
139
|
+
- [x] `initializeAllStandalonePlaylists(playlists)` added as array wrapper
|
|
140
|
+
- [x] sha-based smart update logic unchanged
|
|
141
|
+
- [x] tests updated with multi-playlist init case
|
|
142
|
+
|
|
143
|
+
### phase 5 - download zip refactor (`playlistDownloadService.ts`)
|
|
144
|
+
|
|
145
|
+
- [x] `generatePlaylistzJs()`, `generateIndexHtml()`, `generateSwJs()` used instead of inline templating
|
|
146
|
+
- [x] zip layout: `playlistz.js` + `index.html` + `sw.js` in root, audio/image files in `data/`; `freqhole-playlistz.js` not included
|
|
147
|
+
- [x] no more `fetch('./sw.js')` network call during zip generation
|
|
148
|
+
- [x] download service tests updated
|
|
149
|
+
|
|
150
|
+
### phase 6 - cli (`dist/freqhole-playlistz-cli.mjs`)
|
|
151
|
+
|
|
152
|
+
- [x] two-file output: `freqhole-playlistz.js` (browser IIFE, no Node globals) + `freqhole-playlistz-cli.mjs` (Node ESM, no browser globals)
|
|
153
|
+
- [x] `--help` - usage
|
|
154
|
+
- [x] `--http [dir]` - static file server with range request support; defaults to `./data`; respects `PORT` env var
|
|
155
|
+
- [x] `--check [file]` - validates `playlistz.js` (defaults to `./playlistz.js`); field-level error messages; checks audio + image files exist on disk; skips `http://`/`https://` urls
|
|
156
|
+
- [x] `--init <dir>` - writes `index.html` + `sw.js` (baked in at build time) to target dir
|
|
157
|
+
- [x] `--generate-data <audio-dir>` - reads audio files, generates `playlistz.js` via `generatePlaylistzJs()`
|
|
158
|
+
|
|
159
|
+
cli source lives in `src/cli/` with modules: `index.ts` (dispatch), `http.ts` (server), `check.ts` (validator), `init.ts` (file writer).
|
|
160
|
+
|
|
161
|
+
### phase 7 - npm package setup
|
|
162
|
+
|
|
163
|
+
- [x] add `"bin": { "freqhole-playlistz": "./dist/freqhole-playlistz.js" }` to `package.json`
|
|
164
|
+
- [x] add `"type": "module"` to `package.json` if not already set
|
|
165
|
+
- [x] verify `npx freqhole-playlistz --help` works from a clean install
|
|
166
|
+
|
|
167
|
+
## testing considerations
|
|
168
|
+
|
|
169
|
+
- existing `standaloneService.test.ts` covers single-playlist init - extend with multi-playlist cases
|
|
170
|
+
- `standaloneTemplates.ts`: unit tests for all three generators and schema parse/reject cases
|
|
171
|
+
- new unit tests for cli arg parsing and file generation functions
|
|
172
|
+
- the http range request server should have tests for partial content responses
|
|
173
|
+
- `web-component.tsx` init path needs tests for `window.__PLAYLISTZ__` detection
|
|
174
|
+
- `playlistDownloadService.ts`: verify zip output contains correct files with correct content
|
|
175
|
+
|
|
176
|
+
## progress
|
|
177
|
+
|
|
178
|
+
- [x] phase 1 - build refactor
|
|
179
|
+
- [x] phase 2 - shared template utilities + zod schemas
|
|
180
|
+
- [x] phase 3 - web component init
|
|
181
|
+
- [x] phase 4 - multi-playlist standalone support
|
|
182
|
+
- [x] phase 5 - download zip refactor
|
|
183
|
+
- [x] phase 6 - cli (`--http`, `--check`, `--init`)
|
|
184
|
+
- [x] phase 7 - npm package setup
|