A central management surface, a sibling tab to "My Videos" (gated by
FEATURE_PLAYLISTS — hidden when off).
- components/profile-modal.html: "Playlists" nav item + #profilePanePlaylists.
- js/ui/profileModal/ProfilePlaylistsController.js: lists the user's playlists
(incl. empty) with a collapsible row each — Rename, Delete, and an expand to a
video sublist with ↑/↓ reorder and Remove. Fetches the referenced creators'
videos so the sublist shows real titles/thumbnails. Edits are optimistic and
re-sync on failure; publishes via the facade; dispatches
bitvid:playlists-changed so the channel section + this pane stay in sync.
- profileModalController.js: instantiate + bind nav/pane + cache DOM + register
listeners + selectPane("playlists") refresh; hide the tab when the flag is off.
- promptDialog.js: showTextPrompt (plain text prompt) for rename.
- playlistFacade.js: deletePlaylist — NIP-09 kind-5 (a + e refs) plus an empty
republish so kind-5-ignoring readers also drop it.
- tailwind.source.css: .playlist-row* styles (token-only).
Lint + build clean; profile-modal-controller tests 26/26.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Network wiring on top of the pure playlistService core:
- fetchCreatorPlaylists(pubkey) — lists kind-30082 by author via the L1
SubscriptionManager, collapses to newest-per-`d` (NIP-33 replaceable), drops
empty playlists from the listing (a cleared playlist republishes empty),
sorts newest-first.
- fetchPlaylist(pubkey, id) — newest matching event, parsed.
- publishPlaylist(playlist) — build + sign + publish via
nostrClient.signAndPublishEvent (active signer), replacing the same `d`.
All entry points take an injectable client for hermetic testing.
tests/playlist-facade.test.mjs (5): newest-per-id + empty/other-author drop +
sort, includeEmpty, blank/no-relay guards, fetch-one newest, publish emits a
kind-30082 event with the right d/title/ordered a-refs under the "playlist"
context. Mock client; no relays/signer. Lint clean; 5/5.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
First slice of creator playlists. Adds a bitvid-native addressable playlist
event and the pure, hermetic core that builds/parses/mutates it. No UI or network
yet — those land in follow-up commits.
- nostrEventSchemas.js: PLAYLIST_KIND=30082 (+ NOTE_TYPES.PLAYLIST, BASE_SCHEMAS
entry) and buildPlaylistEvent(). Data lives in tags: d (id), title/description/
image, and ordered `a` refs (30078:pubkey:d) to videos; order = tag order;
repeated refs deduped. Deliberately NOT NIP-51's 30005 "video curation set" —
bitvid already reads 30005 as legacy interest-sets, so reuse would collide.
- js/playlists/playlistService.js: parsePlaylistEvent, buildPlaylist, and
immutable add/remove/reorder item helpers + videoCoordinate() (reuses the
canonical buildVideoAddressPointer, same 30078 contract as the zap fix) +
generatePlaylistId(). Pure — fully unit-testable with no relays/signer.
- tests/playlist-service.test.mjs: 12 scenarios (build tags + order + dedup,
d-tag required, parse structure/rejects/defaults, build→parse round-trip,
add/remove/reorder immutability + clamping, coordinate derivation incl. the
kind-0 internal-object case).
- docs/nostr-event-schemas.md: catalogue row for the new kind.
Schema validation, lint, and build all pass; tests 12/12.
Co-Authored-By: Claude Opus 4.8 <[email protected]>