audio streaming app plyr.fm
38
fork

Configure Feed

Select the types of activity you want to include in your feed.

PDS tooltip on upload form, collapse API ref nav, rewrite your-data docs (#1109)

- replace PdsUploadNote with PdsTooltip: hover ? icon next to "audio file"
label linking to docs.plyr.fm/artists/#your-data
- collapse API reference section in docs sidebar by default
- rewrite "your data" section to accurately describe PDS storage, blob
fallback, and the distinction between data ownership and app export

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by

nate nowack
Claude Opus 4.6
and committed by
GitHub
aff4f119 d1606ee3

+91 -39
+1
docs-site/astro.config.mjs
··· 22 22 { slug: "developers/quickstart" }, 23 23 { 24 24 label: "API reference", 25 + collapsed: true, 25 26 autogenerate: { directory: "developers/api-reference" }, 26 27 }, 27 28 { slug: "developers/auth" },
+6 -5
docs/artists.md
··· 94 94 95 95 ![the artist portal — manage your profile, tracks, and albums](/screenshots/portal-dashboard.png) 96 96 97 - because tracks are atproto records, you can: 97 + every track you upload creates a record in your [PDS](https://atproto.com/guides/self-hosting#pds) under the `fm.plyr.track` collection — the metadata and, when your PDS accepts it, the audio blob itself. these are standard atproto records that belong to you, not to plyr.fm. you can inspect them directly in a [PDS viewer](https://pdsls.dev), and they travel with your account if you [migrate](https://atproto.com/guides/account-migration) to a different PDS. 98 98 99 - - **[export](https://plyr.fm/portal)** your entire catalog from your PDS at any time 100 - - **migrate** to a different PDS without losing anything 99 + if your PDS has a blob size limit that prevents storing the audio (common on shared hosting), plyr.fm stores the audio file in its own CDN instead. the metadata record still lives on your PDS either way. 100 + 101 + for convenience, the [portal](https://plyr.fm/portal) also offers a **bulk export** — it packages your tracks as a ZIP (using lossless originals when available) that you can download directly. 101 102 102 - your audio lives in your repo under the `fm.plyr.track` collection. see the [lexicons overview](/lexicons/overview/) for the full schema. 103 + see the [lexicons overview](/lexicons/overview/) for the full record schema. 103 104 104 105 ## leaving 105 106 106 - you can leave plyr.fm at any time. export your full catalog as a zip from the [portal](https://plyr.fm/portal). deleting your account removes all data from plyr.fm's infrastructure — your atproto records stay on your PDS by default, but you can choose to delete those too. for the full technical details, see the [offboarding documentation](https://github.com/zzstoatzz/plyr.fm/blob/main/docs-internal/offboarding.md). 107 + you can leave plyr.fm at any time. download your tracks as a ZIP from the [portal](https://plyr.fm/portal), then delete your account — this removes all data from plyr.fm's infrastructure. your atproto records stay on your PDS by default, but you can choose to delete those too. for the full technical details, see the [offboarding documentation](https://github.com/zzstoatzz/plyr.fm/blob/main/docs-internal/offboarding.md).
+72
frontend/src/lib/components/PdsTooltip.svelte
··· 1 + <script lang="ts"> 2 + import { preferences } from "$lib/preferences.svelte"; 3 + 4 + let enabled = $derived( 5 + preferences.uiSettings.pds_audio_uploads_enabled ?? false, 6 + ); 7 + </script> 8 + 9 + <span class="tooltip-wrapper"> 10 + <span class="tooltip-icon">?</span> 11 + <span class="tooltip-content"> 12 + {#if enabled} 13 + uploads are stored on your PDS. 14 + <a href="https://docs.plyr.fm/artists/#your-data" target="_blank" rel="noopener">learn more</a> 15 + {:else} 16 + PDS audio uploads available in <a href="/settings">settings</a>. 17 + <a href="https://docs.plyr.fm/artists/#your-data" target="_blank" rel="noopener">learn more</a> 18 + {/if} 19 + </span> 20 + </span> 21 + 22 + <style> 23 + .tooltip-wrapper { 24 + position: relative; 25 + display: inline-flex; 26 + } 27 + 28 + .tooltip-icon { 29 + display: inline-flex; 30 + align-items: center; 31 + justify-content: center; 32 + width: 1.1rem; 33 + height: 1.1rem; 34 + border-radius: 50%; 35 + border: 1px solid var(--text-tertiary); 36 + color: var(--text-tertiary); 37 + font-size: 0.7rem; 38 + font-weight: 600; 39 + cursor: help; 40 + line-height: 1; 41 + } 42 + 43 + .tooltip-content { 44 + display: none; 45 + position: absolute; 46 + left: 50%; 47 + top: calc(100% + 0.5rem); 48 + transform: translateX(-50%); 49 + background: var(--bg-primary); 50 + border: 1px solid var(--border-default); 51 + border-radius: var(--radius-sm); 52 + padding: 0.5rem 0.75rem; 53 + font-size: var(--text-sm); 54 + color: var(--text-secondary); 55 + white-space: nowrap; 56 + z-index: 10; 57 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); 58 + } 59 + 60 + .tooltip-wrapper:hover .tooltip-content { 61 + display: block; 62 + } 63 + 64 + .tooltip-content a { 65 + color: var(--accent); 66 + text-decoration: none; 67 + } 68 + 69 + .tooltip-content a:hover { 70 + text-decoration: underline; 71 + } 72 + </style>
-30
frontend/src/lib/components/PdsUploadNote.svelte
··· 1 - <script lang="ts"> 2 - import { preferences } from "$lib/preferences.svelte"; 3 - 4 - let enabled = $derived(preferences.uiSettings.pds_audio_uploads_enabled ?? false); 5 - </script> 6 - 7 - {#if !enabled} 8 - <p class="pds-note"> 9 - pds audio uploads available in <a href="/settings">settings</a> 10 - </p> 11 - {:else} 12 - <p class="pds-note">uploads will be stored on your pds</p> 13 - {/if} 14 - 15 - <style> 16 - .pds-note { 17 - margin-top: 0.5rem; 18 - font-size: var(--text-sm); 19 - color: var(--text-secondary); 20 - } 21 - 22 - .pds-note a { 23 - color: var(--accent); 24 - text-decoration: none; 25 - } 26 - 27 - .pds-note a:hover { 28 - text-decoration: underline; 29 - } 30 - </style>
+11 -3
frontend/src/routes/upload/+page.svelte
··· 4 4 import Header from "$lib/components/Header.svelte"; 5 5 import HandleSearch from "$lib/components/HandleSearch.svelte"; 6 6 import AlbumSelect from "$lib/components/AlbumSelect.svelte"; 7 - import PdsUploadNote from "$lib/components/PdsUploadNote.svelte"; 7 + import PdsTooltip from "$lib/components/PdsTooltip.svelte"; 8 8 import WaveLoading from "$lib/components/WaveLoading.svelte"; 9 9 import TagInput from "$lib/components/TagInput.svelte"; 10 10 import type { FeaturedArtist, AlbumSummary, Artist } from "$lib/types"; ··· 267 267 </div> 268 268 269 269 <div class="form-group"> 270 - <label for="file-input">audio file</label> 270 + <label for="file-input" class="label-with-tooltip"> 271 + audio file 272 + <PdsTooltip /> 273 + </label> 271 274 <input 272 275 id="file-input" 273 276 type="file" ··· 278 281 <p class="format-hint"> 279 282 supported: {getAcceptedExtensions().map(e => e.slice(1)).join(", ")} 280 283 </p> 281 - <PdsUploadNote /> 282 284 {#if file} 283 285 <p class="file-info"> 284 286 {file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB) ··· 533 535 font-size: var(--text-base); 534 536 font-family: inherit; 535 537 cursor: pointer; 538 + } 539 + 540 + .label-with-tooltip { 541 + display: inline-flex; 542 + align-items: center; 543 + gap: 0.4rem; 536 544 } 537 545 538 546 .format-hint {
+1 -1
loq.toml
··· 183 183 184 184 [[rules]] 185 185 path = "frontend/src/routes/upload/+page.svelte" 186 - max_lines = 712 186 + max_lines = 720 187 187 188 188 [[rules]] 189 189 path = "services/moderation/src/admin.rs"