Add first-time artist setup flow for album + publisher feeds#71
Open
ChadFarrow wants to merge 150 commits into
Open
Add first-time artist setup flow for album + publisher feeds#71ChadFarrow wants to merge 150 commits into
ChadFarrow wants to merge 150 commits into
Conversation
Introduces an "Artist Setup" path that creates both an album feed and a publisher catalog simultaneously with GUIDs cross-linked, so new artists aren't left to manually wire the two feeds together. - NewFeedChoiceModal: adds Artist Setup button (album mode only) with description explaining the linked-feed approach - App.tsx: handleArtistSetup generates both feeds with pre-linked GUIDs and deposits them into state; user lands on the album editor - Editor.tsx: Publisher Feed section shows a green "linked" banner with a "Go to Publisher" shortcut when a local publisher feed is already cross-referenced; updates description text for the unlinked case - CatalogFeedsSection: shows an inline prompt to add the current album when it isn't yet in the catalog, or a confirmation when it is - SaveModal: adds "Download Feed Package (album + publisher)" option that downloads both XMLs plus a next-steps.txt when feeds are linked - xmlGenerator: adds downloadText helper used by the package download - PublisherFeedReminderSection: replaces wall-of-text with a visual checklist (title, artwork, catalog count) and adds a reminder to submit the album feed to Podcast Index too Existing flows are completely unchanged — new paths are additive only. https://claude.ai/code/session_013pnx6R6iNhgVxMJFkmLMrN
Shows a 3-step onboarding modal (Welcome, Choose Mode, Quick Start) on first visit using a localStorage flag. Re-openable from the hamburger menu via "Getting Started". Dismissal at any step marks onboarding complete. https://claude.ai/code/session_01MPtgXUYSqMGU8b52RoYLvM
- Replace OnboardingModal with OnboardingPage (full-screen takeover via early return from AppContent, mirroring the AdminPage pattern). - Add a step-0 gate that asks returning users if they want to skip the tour; first-visit auto-shows the gate, hamburger menu opens directly at step 1. - Fix undefined --accent-color CSS variable (use --accent-primary). - Misc copy edits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a new 'artist' feedType for editing album and publisher feeds on a single page, eliminating the mode-switch friction in PR #63's Artist Setup flow. MVP stacks existing editor sections; v2 may de-duplicate shared fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11-task plan covering FeedType extension, ArtistEditor composition, handleSwitchFeedType auto-creation logic, CatalogFeedsSection dead-code fix, and manual verification. References spec at docs/superpowers/specs/2026-05-21-combined-artist-editor-design.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
handleStartBlank now creates fresh cross-linked album+publisher with new GUIDs when invoked from Artist mode; previously it silently fell through to album-only SET_ALBUM, demoting feedType back to 'album'. The New button tooltip also gains an Artist branch so it stops displaying "New Album" while the user is in Artist mode.
The section's purpose is linking an externally-hosted publisher feed by URL — it does a Podcast Index lookup to populate album.publisher.feedGuid. In Artist mode the publisher feed is created locally with a fresh GUID and cross-linked via state, so the URL-lookup flow is contradictory. Task 6 only hid the banner/text inside the section; this hides the whole Section element so the URL input and PI-lookup UI don't appear at all in the combined editor.
Hide CatalogFeedsSection, PublisherFeedReminderSection, DownloadCatalogSection, and PublishSection when feedType is 'artist'. Artist mode is first-time setup — the user is building one album and one publisher feed simultaneously, not managing a catalog of other albums. The current album is auto-included in the publisher's remoteItems by handleSwitchFeedType, and Save → Download Feed Package covers the export, so these sections only add noise. They remain available in standalone Publisher mode.
New hostedBoth SaveModal mode that hosts album + publisher feeds in sequence via /api/hosted using Nostr auth, surfacing the album's hosted state and showing both PI-submission URLs on success. The internal /api/hosted handler already calls notifyPodcastIndex for each feed, so both feeds end up submitted to Podcast Index (pubnotify + add/byfeedurl) and podping'd without a second round trip. In Artist mode, the dropdown swaps the single Host on MSP option for this combined variant when the user is Nostr-logged-in. Without Nostr login, the single Host on MSP option remains as fallback (hosts album only — same as today). Also updated the Download Feed Package next-steps.txt to point users at Host Both as the one-click alternative, and dropped the mode-switching instructions that are no longer necessary.
Renders a 'Publish your feeds' panel at the bottom of ArtistEditor with two primary action buttons inline: - Host on MSP — album + publisher (one click), gated on Nostr login - Download Feed Package (host yourself) Both call shared helpers in src/utils/artistPublish.ts that SaveModal's hostedBoth and package cases were refactored to use, eliminating duplication. Status, progress, and per-feed result URLs render inline in the section after the action completes. A 'More save options →' link opens the SaveModal for the longer tail of destinations (Submit to PodcastIndex by URL, Publish to Nostr Music, etc.), so the inline section guides the common case without hiding the rest.
The link was redundant — the bottom toolbar's Save button already opens the SaveModal from any mode. Clean up the now-unused onOpenSaveModal prop chain through ArtistEditor and App.tsx.
When the user clicks 'Host on MSP — album + publisher', render a live
step list inside the publish panel showing:
1. Host album feed on MSP + submit to Podcast Index
2. Host publisher feed on MSP + submit to Podcast Index
3. Verify both feeds appear in Podcast Index
Each step transitions ○ → ◐ → ✓ as work progresses. Hosting steps
expand to show the hosted URL and PI ID once done. After both feeds
are uploaded, the verification step polls /api/pisearch?q={guid} for
each feed with timed retries (~22 s budget). When PI confirms both,
the row goes green with 'Both feeds are searchable in Podcast Index'
and lookup links; if either is still pending the row shows a
'check back later' message — Podcast Index can take a few minutes to
crawl new submissions.
hostBothOnMSP's progress signature changed from `onProgress(msg)` to
`onStep(step)` with structured step events. SaveModal's caller was
updated to convert in-progress steps back to a single message string
so the modal's behavior is unchanged.
Replace ~22 s waitForBothFeedsInIndex with a multi-phase poll: 5 fast attempts in the first 22 s (PI typically commits new submissions in a few seconds), then back off to 15-60 s intervals across the next ~6 minutes. Each tick emits a VerifyProgress event so the section can render which feed has been confirmed, which is still pending, and how long until the next check. Cancellation token plumbed through so unmount or a re-click aborts in-flight polling. The verify row now shows per-feed status while polling — '✓ Album: found' / '⏳ Publisher: not yet found · check manually →' plus a countdown line 'Checking again in 30s · attempt 8 of 16'. When both land green, the row consolidates back to the single 'Both feeds are searchable in Podcast Index' confirmation. After the full ~6.5 min budget exhausts, it falls back to a 'stopped checking — refresh later' note. Host on MSP button stays disabled and changes its label once the upload starts AND remains disabled for the rest of the session even after polling ends — so the user can't accidentally re-trigger the whole flow. To re-host they refresh or use the bottom-toolbar Save.
Two fixes surfaced by the first end-to-end test:
1. Disable Host Both until both album.title and publisherFeed.title are
non-empty. PI silently drops feeds with empty titles, leaving the
verify step stuck on 'not yet found' even though the upload
succeeded. Helper text under the button now points at the missing
side ('Add a title to your publisher feed above…').
2. In handleSwitchFeedType('artist'), when an existing publisher is
preserved but its remoteItems doesn't reference the current album,
append the link. Without this, switching into Artist mode after
restoring an old session's publisher (or after the album GUID has
changed) results in a hosted publisher XML carrying a stale
<podcast:remoteItem feedGuid=…> that doesn't match the album it's
supposed to catalog.
The hamburger menu's '🧪 Load Test Data' button only dispatched SET_ALBUM, which (a) left the publisher feed in whatever state it happened to be in (often empty or stale from a prior session) and (b) reset feedType to 'album', yanking the user out of Artist mode. Net result: clicking Load Test Data in Artist mode produced a fully populated album feed paired with a blank publisher feed carrying a stale remoteItem from earlier work. Hosting that publisher to MSP uploaded an empty XML which Podcast Index couldn't index. Now: in Artist mode, the button generates a matched album+publisher fixture pair with cross-linked GUIDs and restores feedType to 'artist' after dispatching. In other modes, behavior is unchanged.
3-5 s checks were too eager — Podcast Index typically takes 30 s to a few minutes to commit new submissions to its byguid index, so the fast initial polls burned API calls without finding anything. New schedule starts at 20 s, ramps to 60 s, then 120 s. Total budget ~10.5 minutes across 12 attempts.
If the user clicks Host Both, refreshes mid-flow, and the publisher feed is still being indexed by Podcast Index, the section used to present an unlocked 'Host on MSP' button — easy to accidentally re-submit. Now: on mount, check localStorage for hosted info for both feeds. If both are present, hydrate the section as if the host just completed: result is set (button locks), the two host steps show ✓, the verify step flips to in-progress, and PI polling resumes automatically. Polling honors the same cancellation token, so unmounting still cleans up. The effect depends only on the GUIDs (not other album/ publisher fields) so editing a title or other field while polling won't restart the verification cycle.
When Podcast Index's add/byfeedurl returns an empty body (which it appears to do for medium=publisher submissions), the previous code parsed JSON unconditionally and the whole call collapsed into a generic 'Unexpected end of JSON input'. Now: capture response.text() first, surface the HTTP status + content-type when the body is empty or non-JSON, and only attempt JSON parsing on a non-empty payload. Useful permanently as error visibility; immediately useful for diagnosing why publisher feeds aren't reaching PI's registry.
PI's add/byfeedurl returns an empty body for our publisher submissions even though album submissions through the same code path eventually land in the index. To avoid blocking the verify step on a feed that may take much longer to crawl, the success criterion is now album-only. The publisher's host step keeps its own row showing the hosted URL and a manual lookup link. To improve discoverability regardless of PI's behavior, hostBothOnMSP now precomputes both feeds' hosted URLs (feedId == podcastGuid in /api/hosted) and patches them into the album's <podcast:publisher feedUrl=…> reference and the publisher's <podcast:remoteItem feedUrl=…> before serializing the XMLs. Crawlers that follow Podcasting 2.0 references can now reach the publisher organically. Existing user-set feedUrls are preserved — we only fill in when empty. After upload, the injected URLs are also dispatched back into the feed store so the editor reflects the cross-links the user just shipped.
Hydration on refresh used to permanently lock the Host Both button once result was set, blocking the most-common need: 'I edited a feed, let me re-host with the latest XML.' Now the button only locks while hosting is actively in flight (uploads + verify polling). After that completes, it stays clickable with a 'Re-host both feeds (update with latest XML)' label, distinguishing the re-host case from a fresh first-time host. This is needed RIGHT NOW because the previously-hosted publisher XMLs are missing feedUrl on their remoteItems — the new cross-link injection in 8557c7d only takes effect on the next upload. Users need to be able to click Host Both again to push the updated XML to MSP, which triggers PI to re-crawl with the proper cross-link.
Confirmed with a known-working example (Longy publisher, PI ID 7696544) that PI does index publisher feeds — they just need the remoteItem feedUrls populated so PI's crawler can follow them. The 8557c7d cross-link injection now provides that, so the verify step can again poll both feeds with confidence. The step is 'done' only when both album AND publisher land in PI. While polling, the row shows per-feed status (✓ Album · ⏳ Publisher) with a countdown to the next attempt. If the poll budget exhausts, the fallback copy still suggests checking manually later.
MSP's create-empty factories seed a single blank ValueRecipient (name='', address='', split=0, type='lnaddress') so the editor has something to render. That placeholder was getting serialized into the hosted XML even when the user never filled it in — producing a <podcast:value> block with one empty recipient and split='0'. This appears to be what's tripping Podcast Index's automated parser on publisher submissions: PI registers the URL but fails to extract title/guid/image, leaving a half-indexed entry that only gets properly populated after a manual web-form re-add (which seems to trigger a more forgiving parse). generateValueXml now filters recipients that have no address before emitting. If nothing remains, the whole <podcast:value> block is suppressed (return ''). Albums with real value blocks keep their existing behavior unchanged because their recipients all have real addresses.
Artist Profile (src/components/Profile/): a returning-artist home that lists the user's MSP-hosted feeds with Edit / Copy URL / RSS / PI / New Album actions. Logged-in owners of >=1 hosted feed auto-route here on load; reachable anytime via hamburger > My Profile. - useMyHostedFeeds hook: owner-filtered GET /api/hosted/ fetch (shared, skips the redundant per-feed XML re-fetch) - Edit loads a hosted feed into the editor and persists hosted creds (buildHostedInfoForEdit) so Save does a PUT, not a 409 POST - New Album attaches to the existing publisher (no second publisher), with an unsaved-changes confirmation - Onboarding gate "Yes, used before" routes toward the profile, offering Nostr sign-in when logged out (onChooseReturning) - Sign In modal: drop "New to Nostr?" tab, context-aware Google copy for returning users, note that an account is only needed for MSP hosting, fix spurious tab scrollbar, align the Quick Sign In panel - Trim Getting Started and Feature Preferences from the hamburger menu Also adds the onboarding landing-flow design doc (docs/superpowers/specs/). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gate's 'I'm new' branch now asks where the feed will live: self-host goes straight to the editor (no account, no wizard); MSP-host opens the guided wizard. Replaces onChooseFirstTime with onChooseSelfHost/onChooseMspHost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'Yes, I've used this before' now lands in the editor instead of auto- opening the sign-in modal, so a self-host returner isn't walled. The header Sign In remains the offer; the auto-route effect still moves a signed-in owner to their Profile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wizard already exits to the editor via the top-right X; this adds a labeled footer button so an MSP-host newcomer who decides to self-host isn't stuck. Reuses handleDismiss; entered fields are preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final whole-branch review (ready to merge, no Critical/Important) raised two Minor clarity items: note WHY self-host uses plain album mode (not artist mode, which scaffolds publisher/V4V), and mark the now-unreachable tour steps 1-4 as dead-until-re-wired per the Phase 1 plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Gate hosting-choice: MSP hosts your 'feed and media' - New Artist self-host helper: list 'your own website' first; drop CDN - Downloaded next-steps guide (downloadArtistFeedPackage): align STEP 1 host examples with the UI (own website / GitHub Pages / S3), and fix the STEP 2 faster-path to point at the New Artist page's 'Publish your feeds -> Host on MSP (album + publisher)' section (there is no such option in the Save modal) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Gate now forks 'I'm new' into self-host (-> album editor) vs MSP-host (-> wizard); 'Yes, used before' offers (not forces) sign-in - Wizard 'Skip to editor' exit (bottom-right) - New Artist Profile section: auto-route, useMyHostedFeeds, edit/add-album (PUT-vs-POST cred + one-publisher invariant) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The footer 'Skip to editor' button sat flush against the primary Next and invited mis-clicks. The top-right X already closes the wizard into the editor (handleDismiss), so drop the footer button and keep that as the single exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A managed keypair starts with no kind-0 profile, so there's nothing to pull — hide the button when connectionMethod === 'managed' (mirrors the V4V hide in ValueStep). NIP-07/NIP-46 users still see it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an opt-in hideNpub prop to PersonsSection (default false); the wizard's ExtrasStep passes it for managed (Google) users, who have no Nostr context. Everywhere else (per-track persons in TrackList, the main editor) keeps the npub field unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PersonsSection gains an opt-in myNpub prop; the wizard's ExtrasStep passes the logged-in Nostr user's npub (not for managed/Google, whose field is hidden) so an artist can fill a person's npub with their own in one click. Mirrors the existing 'use mine' button on the album Artist npub field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each track with an enclosureUrl renders a native audio control (preload 'none' so it only fetches on play) so the artist can listen before publishing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'View on Podcast Index' link was buried in the feed-URL help text; move it to a bordered card with a primary-styled button below the album feed URL — it's the key 'is it live?' action. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PI CTA CLAUDE.md wizard section: 'Use my Nostr name & photo' hidden for managed keys (publisher step); PersonsSection hideNpub/myNpub props in the Credits step; per-track audio preview on Review; post-publish PI link as a standout card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ch fallback) PI's web /search?q= matches title/author, never the podcast:guid, so the old guid-search fallback could never find the feed. And the numeric /podcast/<id> page was resolved only once at publish, before PI finished indexing. - New side-effect-free GET /api/pi-resolve?guid= (byguid -> numeric URL), safe to poll (no pubnotify ping / podping, unlike /api/pubnotify) - OnboardingWizard polls it with backoff after publish and upgrades the link to /podcast/<id> once PI indexes; cancelled on unmount - ReviewStep fallback now searches by artist/album NAME instead of guid Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expands BLOSSOM_MEDIA_SERVERS from 2 to 4 servers for upload redundancy, and surfaces "Hosted on X of Y servers" on every successful upload (not just partial failures) so users can see how many servers hold the file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces an "Artist Setup" path that creates both an album feed and a
publisher catalog simultaneously with GUIDs cross-linked, so new artists
aren't left to manually wire the two feeds together.
description explaining the linked-feed approach
and deposits them into state; user lands on the album editor
a "Go to Publisher" shortcut when a local publisher feed is already
cross-referenced; updates description text for the unlinked case
when it isn't yet in the catalog, or a confirmation when it is
that downloads both XMLs plus a next-steps.txt when feeds are linked
checklist (title, artwork, catalog count) and adds a reminder to
submit the album feed to Podcast Index too
Existing flows are completely unchanged — new paths are additive only.
https://claude.ai/code/session_013pnx6R6iNhgVxMJFkmLMrN