+194
.status_history/2025-12.md
+194
.status_history/2025-12.md
···
412
412
- 6 original artists (people uploading their own distributed music)
413
413
414
414
**documentation**: see `docs/moderation/atproto-labeler.md`
415
+
416
+
---
417
+
418
+
## Mid-December 2025 Work (Dec 8-16)
419
+
420
+
### visual customization (PRs #595-596, Dec 16)
421
+
422
+
**custom backgrounds** (PR #595):
423
+
- users can set a custom background image URL in settings with optional tiling
424
+
- new "playing artwork as background" toggle - uses current track's artwork as blurred page background
425
+
- glass effect styling for track items (translucent backgrounds, subtle shadows)
426
+
- new `ui_settings` JSONB column in preferences for extensible UI settings
427
+
428
+
**bug fix** (PR #596):
429
+
- removed 3D wheel scroll effect that was blocking like/share button clicks
430
+
- root cause: `translateZ` transforms created z-index stacking that intercepted pointer events
431
+
432
+
---
433
+
434
+
### performance & UX polish (PRs #586-593, Dec 14-15)
435
+
436
+
**performance improvements** (PRs #590-591):
437
+
- removed moderation service call from `/tracks/` listing endpoint
438
+
- removed copyright check from tag listing endpoint
439
+
- faster page loads for track feeds
440
+
441
+
**moderation agent** (PRs #586, #588):
442
+
- added moderation agent script with audit trail support
443
+
- improved moderation prompt and UI layout
444
+
445
+
**bug fixes** (PRs #589, #592, #593):
446
+
- fixed liked state display on playlist detail page
447
+
- preserved album track order during ATProto sync
448
+
- made header sticky on scroll for better mobile navigation
449
+
450
+
**iOS Safari fixes** (PRs #573-576):
451
+
- fixed AddToMenu visibility issue on iOS Safari
452
+
- menu now correctly opens upward when near viewport bottom
453
+
454
+
---
455
+
456
+
### mobile UI polish & background task expansion (PRs #558-572, Dec 10-12)
457
+
458
+
**background task expansion** (PRs #558, #561):
459
+
- moved like/unlike and comment PDS writes to docket background tasks
460
+
- API responses now immediate; PDS sync happens asynchronously
461
+
- added targeted album list sync background task for ATProto record updates
462
+
463
+
**performance caching** (PR #566):
464
+
- added Redis cache for copyright label lookups (5-minute TTL)
465
+
- fixed 2-3s latency spikes on `/tracks/` endpoint
466
+
- batch operations via `mget`/pipeline for efficiency
467
+
468
+
**mobile UX improvements** (PRs #569, #572):
469
+
- mobile action menus now open from top with all actions visible
470
+
- UI polish for album and artist pages on small screens
471
+
472
+
**misc** (PRs #559, #562, #563, #570):
473
+
- reduced docket Redis polling from 250ms to 5s (lower resource usage)
474
+
- added atprotofans support link mode for ko-fi integration
475
+
- added alpha badge to header branding
476
+
- fixed web manifest ID for PWA stability
477
+
478
+
---
479
+
480
+
### confidential OAuth client (PRs #578, #580-582, Dec 12-13)
481
+
482
+
**confidential client support** (PR #578):
483
+
- implemented ATProto OAuth confidential client using `private_key_jwt` authentication
484
+
- when `OAUTH_JWK` is configured, plyr.fm authenticates with a cryptographic key
485
+
- confidential clients earn 180-day refresh tokens (vs 2-week for public clients)
486
+
- added `/.well-known/jwks.json` endpoint for public key discovery
487
+
- updated `/oauth-client-metadata.json` with confidential client fields
488
+
489
+
**bug fixes** (PRs #580-582):
490
+
- fixed client assertion JWT to use Authorization Server's issuer as `aud` claim (not token endpoint URL)
491
+
- fixed JWKS endpoint to preserve `kid` field from original JWK
492
+
- fixed `OAuthClient` to pass `client_secret_kid` for JWT header
493
+
494
+
**atproto fork updates** (zzstoatzz/atproto#6, #7):
495
+
- added `issuer` parameter to `_make_token_request()` for correct `aud` claim
496
+
- added `client_secret_kid` parameter to include `kid` in client assertion JWT header
497
+
498
+
**outcome**: users now get 180-day refresh tokens, and "remember this account" on the PDS authorization page works (auto-approves subsequent logins). see #583 for future work on account switching via OAuth `prompt` parameter.
499
+
500
+
---
501
+
502
+
### pagination & album management (PRs #550-554, Dec 9-10)
503
+
504
+
**tracks list pagination** (PR #554):
505
+
- cursor-based pagination on `/tracks/` endpoint (default 50 per page)
506
+
- infinite scroll on homepage using native IntersectionObserver
507
+
- zero new dependencies - uses browser APIs only
508
+
- pagination state persisted to localStorage for fast subsequent loads
509
+
510
+
**album management improvements** (PRs #550-552, #557):
511
+
- album delete and track reorder fixes
512
+
- album page edit mode matching playlist UX (inline title editing, cover upload)
513
+
- optimistic UI updates for album title changes (instant feedback)
514
+
- ATProto record sync when album title changes (updates all track records + list record)
515
+
- fixed album slug sync on rename (prevented duplicate albums when adding tracks)
516
+
517
+
**playlist show on profile** (PR #553):
518
+
- restored "show on profile" toggle that was lost during inline editing refactor
519
+
- users can now control whether playlists appear on their public profile
520
+
521
+
---
522
+
523
+
### public cost dashboard (PRs #548-549, Dec 9)
524
+
525
+
- `/costs` page showing live platform infrastructure costs
526
+
- daily export to R2 via GitHub Action, proxied through `/stats/costs` endpoint
527
+
- dedicated `plyr-stats` R2 bucket with public access (shared across environments)
528
+
- includes fly.io, neon, cloudflare, and audd API costs
529
+
- ko-fi integration for community support
530
+
531
+
### docket background tasks & concurrent exports (PRs #534-546, Dec 9)
532
+
533
+
**docket integration** (PRs #534, #536, #539):
534
+
- migrated background tasks from inline asyncio to docket (Redis-backed task queue)
535
+
- copyright scanning, media export, ATProto sync, and teal scrobbling now run via docket
536
+
- graceful fallback to asyncio for local development without Redis
537
+
- parallel test execution with xdist template databases (#540)
538
+
539
+
**concurrent export downloads** (PR #545):
540
+
- exports now download tracks in parallel (up to 4 concurrent) instead of sequentially
541
+
- significantly faster for users with many tracks or large files
542
+
- zip creation remains sequential (zipfile constraint)
543
+
544
+
**ATProto refactor** (PR #534):
545
+
- reorganized ATProto record code into `_internal/atproto/records/` by lexicon namespace
546
+
- extracted `client.py` for low-level PDS operations
547
+
- cleaner separation between plyr.fm and teal.fm lexicons
548
+
549
+
**documentation & observability**:
550
+
- AudD API cost tracking dashboard (#546)
551
+
- promoted runbooks from sandbox to `docs/runbooks/`
552
+
- updated CLAUDE.md files across the codebase
553
+
554
+
---
555
+
556
+
### artist support links & inline playlist editing (PRs #520-532, Dec 8)
557
+
558
+
**artist support link** (PR #532):
559
+
- artists can set a support URL (Ko-fi, Patreon, etc.) in their portal profile
560
+
- support link displays as a button on artist profile pages next to the share button
561
+
- URLs validated to require https:// prefix
562
+
563
+
**inline playlist editing** (PR #531):
564
+
- edit playlist name and description directly on playlist detail page
565
+
- click-to-upload cover art replacement without modal
566
+
- cleaner UX - no more edit modal popup
567
+
568
+
**platform stats enhancements** (PRs #522, #528):
569
+
- total duration displayed in platform stats (e.g., "42h 15m of music")
570
+
- duration shown per artist in analytics section
571
+
- combined stats and search into single centered container for cleaner layout
572
+
573
+
**navigation & data loading fixes** (PR #527):
574
+
- fixed stale data when navigating between detail pages of the same type
575
+
- e.g., clicking from one artist to another now properly reloads data
576
+
577
+
**copyright moderation improvements** (PR #480):
578
+
- enhanced moderation workflow for copyright claims
579
+
- improved labeler integration
580
+
581
+
**status maintenance workflow** (PR #529):
582
+
- automated status maintenance using claude-code-action
583
+
- reviews merged PRs and updates STATUS.md narratively
584
+
585
+
---
586
+
587
+
### playlist fast-follow fixes (PRs #507-519, Dec 7-8)
588
+
589
+
**public playlist viewing** (PR #519):
590
+
- playlists now publicly viewable without authentication
591
+
- ATProto records are public by design - auth was unnecessary for read access
592
+
- shared playlist URLs no longer redirect unauthenticated users to homepage
593
+
594
+
**inline playlist creation** (PR #510):
595
+
- clicking "create new playlist" from AddToMenu previously navigated to `/library?create=playlist`
596
+
- this caused SvelteKit to reinitialize the layout, destroying the audio element and stopping playback
597
+
- fix: added inline create form that creates playlist and adds track in one action without navigation
598
+
599
+
**UI polish** (PRs #507-509, #515):
600
+
- include `image_url` in playlist SSR data for og:image link previews
601
+
- invalidate layout data after token exchange - fixes stale auth state after login
602
+
- fixed stopPropagation blocking "create new playlist" link clicks
603
+
- detail page button layouts: all buttons visible on mobile, centered AddToMenu on track detail
604
+
- AddToMenu smart positioning: menu opens upward when near viewport bottom
605
+
606
+
**documentation** (PR #514):
607
+
- added lexicons overview documentation at `docs/lexicons/overview.md`
608
+
- covers `fm.plyr.track`, `fm.plyr.like`, `fm.plyr.comment`, `fm.plyr.list`, `fm.plyr.actor.profile`
+60
-276
STATUS.md
+60
-276
STATUS.md
···
47
47
48
48
### December 2025
49
49
50
-
#### rate limit moderation endpoint (PR #629, Dec 21)
50
+
#### supporter-gated content (PR #637, Dec 22-23)
51
51
52
-
**incident response**: detected suspicious activity - 72 requests in 17 seconds from a single IP targeting `/moderation/sensitive-images`. investigation via Logfire showed:
53
-
- single IP generating all traffic with no User-Agent header
54
-
- requests spaced ~230ms apart (too consistent for human browsing)
55
-
- no corresponding user activity (page loads, audio streams)
52
+
**atprotofans paywall integration** - artists can now mark tracks as "supporters only":
53
+
- tracks with `support_gate` require atprotofans validation before playback
54
+
- non-supporters see lock icon and "become a supporter" CTA linking to atprotofans
55
+
- artists can always play their own gated tracks
56
56
57
-
**fix**: added `10/minute` rate limit to the endpoint using existing slowapi infrastructure. verified rate limiting works correctly post-deployment.
57
+
**backend architecture**:
58
+
- audio endpoint validates supporter status via atprotofans API before serving gated content
59
+
- HEAD requests return 200/401/402 for pre-flight auth checks (avoids CORS issues)
60
+
- `R2Storage.move_audio()` moves files between public/private buckets when toggling gate
61
+
- background task handles bucket migration asynchronously
62
+
- ATProto record syncs when toggling gate (updates `supportGate` field and `audioUrl`)
63
+
64
+
**frontend**:
65
+
- `playback.svelte.ts` guards queue operations with gated checks BEFORE modifying state
66
+
- clicking locked track shows toast with CTA - does NOT interrupt current playback
67
+
- portal shows support gate toggle in track edit UI
58
68
59
69
---
60
70
61
-
#### end-of-year sprint (Dec 20-31)
71
+
#### supporter badges (PR #627, Dec 21-22)
72
+
73
+
**phase 1 of atprotofans integration**:
74
+
- supporter badge displays on artist pages when logged-in viewer supports the artist
75
+
- calls atprotofans `validateSupporter` API directly from frontend (public endpoint)
76
+
- badge only shows when viewer is authenticated and not viewing their own profile
62
77
63
-
**focus**: two foundational systems need solid experimental implementations by 2026.
78
+
---
64
79
65
-
**track 1: moderation architecture overhaul**
66
-
- consolidate sensitive images into moderation service
67
-
- add event-sourced audit trail
68
-
- implement configurable rules (replace hard-coded thresholds)
69
-
- informed by [Roost Osprey](https://github.com/roostorg/osprey) patterns and [Bluesky Ozone](https://github.com/bluesky-social/ozone) workflows
80
+
#### rate limit moderation endpoint (PR #629, Dec 21)
70
81
71
-
**track 2: atprotofans paywall integration**
72
-
- phase 1: read-only supporter validation (show badges)
73
-
- phase 2: platform registration (artists create support tiers)
74
-
- phase 3: content gating (track-level access control)
82
+
**incident response**: detected suspicious activity - 72 requests in 17 seconds from a single IP targeting `/moderation/sensitive-images`. added `10/minute` rate limit using existing slowapi infrastructure.
83
+
84
+
---
85
+
86
+
#### end-of-year sprint planning (PR #626, Dec 20)
87
+
88
+
**focus**: two foundational systems need solid experimental implementations by 2026.
89
+
90
+
| track | focus | status |
91
+
|-------|-------|--------|
92
+
| moderation | consolidate architecture, add rules engine | in progress |
93
+
| atprotofans | supporter validation, content gating | shipped (phase 1-3) |
75
94
76
95
**research docs**:
77
96
- [moderation architecture overhaul](docs/research/2025-12-20-moderation-architecture-overhaul.md)
78
97
- [atprotofans paywall integration](docs/research/2025-12-20-atprotofans-paywall-integration.md)
79
98
80
-
**tracking**: issue #625
81
-
82
99
---
83
100
84
101
#### beartype + moderation cleanup (PRs #617-619, Dec 19)
···
87
104
- enabled beartype runtime type validation across the backend
88
105
- catches type errors at runtime instead of silently passing bad data
89
106
- test infrastructure improvements: session-scoped TestClient fixture (5x faster tests)
90
-
- disabled automatic perpetual task scheduling in tests
91
107
92
108
**moderation cleanup** (PRs #617-618):
93
109
- consolidated moderation code, addressing issues #541-543
94
110
- `sync_copyright_resolutions` now runs automatically via docket Perpetual task
95
-
- removed `init_db()` from lifespan (handled by alembic migrations)
111
+
- removed dead `init_db()` from lifespan (handled by alembic migrations)
96
112
97
113
---
98
114
···
111
127
- mobile modals now use full screen positioning
112
128
- fixed `/tag/` routes in hasPageMetadata check
113
129
114
-
**misc** (PRs #598-601):
115
-
- upload button added to desktop header nav
116
-
- background settings UX improvements
117
-
- switched support link to atprotofans
118
-
- AudD costs now derived from track duration for accurate billing
119
-
120
130
---
121
131
122
132
#### offline mode foundation (PRs #610-611, Dec 17)
123
133
124
134
**experimental offline playback**:
125
-
- new storage layer using Cache API for audio bytes + IndexedDB for metadata
135
+
- storage layer using Cache API for audio bytes + IndexedDB for metadata
126
136
- `GET /audio/{file_id}/url` backend endpoint returns direct R2 URLs for client-side caching
127
137
- "auto-download liked" toggle in experimental settings section
128
-
- when enabled, bulk-downloads all liked tracks and auto-downloads future likes
129
138
- Player checks for cached audio before streaming from R2
130
-
- works offline once tracks are downloaded
131
-
132
-
**robustness improvements**:
133
-
- IndexedDB connections properly closed after each operation
134
-
- concurrent downloads deduplicated via in-flight promise tracking
135
-
- stale metadata cleanup when cache entries are missing
136
139
137
140
---
138
141
139
-
#### visual customization (PRs #595-596, Dec 16)
140
-
141
-
**custom backgrounds** (PR #595):
142
-
- users can set a custom background image URL in settings with optional tiling
143
-
- new "playing artwork as background" toggle - uses current track's artwork as blurred page background
144
-
- glass effect styling for track items (translucent backgrounds, subtle shadows)
145
-
- new `ui_settings` JSONB column in preferences for extensible UI settings
146
-
147
-
**bug fix** (PR #596):
148
-
- removed 3D wheel scroll effect that was blocking like/share button clicks
149
-
- root cause: `translateZ` transforms created z-index stacking that intercepted pointer events
150
-
151
-
---
152
-
153
-
#### performance & UX polish (PRs #586-593, Dec 14-15)
154
-
155
-
**performance improvements** (PRs #590-591):
156
-
- removed moderation service call from `/tracks/` listing endpoint
157
-
- removed copyright check from tag listing endpoint
158
-
- faster page loads for track feeds
159
-
160
-
**moderation agent** (PRs #586, #588):
161
-
- added moderation agent script with audit trail support
162
-
- improved moderation prompt and UI layout
163
-
164
-
**bug fixes** (PRs #589, #592, #593):
165
-
- fixed liked state display on playlist detail page
166
-
- preserved album track order during ATProto sync
167
-
- made header sticky on scroll for better mobile navigation
168
-
169
-
**iOS Safari fixes** (PRs #573-576):
170
-
- fixed AddToMenu visibility issue on iOS Safari
171
-
- menu now correctly opens upward when near viewport bottom
172
-
173
-
---
174
-
175
-
#### mobile UI polish & background task expansion (PRs #558-572, Dec 10-12)
176
-
177
-
**background task expansion** (PRs #558, #561):
178
-
- moved like/unlike and comment PDS writes to docket background tasks
179
-
- API responses now immediate; PDS sync happens asynchronously
180
-
- added targeted album list sync background task for ATProto record updates
181
-
182
-
**performance caching** (PR #566):
183
-
- added Redis cache for copyright label lookups (5-minute TTL)
184
-
- fixed 2-3s latency spikes on `/tracks/` endpoint
185
-
- batch operations via `mget`/pipeline for efficiency
186
-
187
-
**mobile UX improvements** (PRs #569, #572):
188
-
- mobile action menus now open from top with all actions visible
189
-
- UI polish for album and artist pages on small screens
190
-
191
-
**misc** (PRs #559, #562, #563, #570):
192
-
- reduced docket Redis polling from 250ms to 5s (lower resource usage)
193
-
- added atprotofans support link mode for ko-fi integration
194
-
- added alpha badge to header branding
195
-
- fixed web manifest ID for PWA stability
196
-
197
-
---
198
-
199
-
#### confidential OAuth client (PRs #578, #580-582, Dec 12-13)
200
-
201
-
**confidential client support** (PR #578):
202
-
- implemented ATProto OAuth confidential client using `private_key_jwt` authentication
203
-
- when `OAUTH_JWK` is configured, plyr.fm authenticates with a cryptographic key
204
-
- confidential clients earn 180-day refresh tokens (vs 2-week for public clients)
205
-
- added `/.well-known/jwks.json` endpoint for public key discovery
206
-
- updated `/oauth-client-metadata.json` with confidential client fields
207
-
208
-
**bug fixes** (PRs #580-582):
209
-
- fixed client assertion JWT to use Authorization Server's issuer as `aud` claim (not token endpoint URL)
210
-
- fixed JWKS endpoint to preserve `kid` field from original JWK
211
-
- fixed `OAuthClient` to pass `client_secret_kid` for JWT header
212
-
213
-
**atproto fork updates** (zzstoatzz/atproto#6, #7):
214
-
- added `issuer` parameter to `_make_token_request()` for correct `aud` claim
215
-
- added `client_secret_kid` parameter to include `kid` in client assertion JWT header
216
-
217
-
**outcome**: users now get 180-day refresh tokens, and "remember this account" on the PDS authorization page works (auto-approves subsequent logins). see #583 for future work on account switching via OAuth `prompt` parameter.
218
-
219
-
---
220
-
221
-
#### pagination & album management (PRs #550-554, Dec 9-10)
222
-
223
-
**tracks list pagination** (PR #554):
224
-
- cursor-based pagination on `/tracks/` endpoint (default 50 per page)
225
-
- infinite scroll on homepage using native IntersectionObserver
226
-
- zero new dependencies - uses browser APIs only
227
-
- pagination state persisted to localStorage for fast subsequent loads
228
-
229
-
**album management improvements** (PRs #550-552, #557):
230
-
- album delete and track reorder fixes
231
-
- album page edit mode matching playlist UX (inline title editing, cover upload)
232
-
- optimistic UI updates for album title changes (instant feedback)
233
-
- ATProto record sync when album title changes (updates all track records + list record)
234
-
- fixed album slug sync on rename (prevented duplicate albums when adding tracks)
235
-
236
-
**playlist show on profile** (PR #553):
237
-
- restored "show on profile" toggle that was lost during inline editing refactor
238
-
- users can now control whether playlists appear on their public profile
239
-
240
-
---
241
-
242
-
#### public cost dashboard (PRs #548-549, Dec 9)
243
-
244
-
- `/costs` page showing live platform infrastructure costs
245
-
- daily export to R2 via GitHub Action, proxied through `/stats/costs` endpoint
246
-
- dedicated `plyr-stats` R2 bucket with public access (shared across environments)
247
-
- includes fly.io, neon, cloudflare, and audd API costs
248
-
- ko-fi integration for community support
249
-
250
-
#### docket background tasks & concurrent exports (PRs #534-546, Dec 9)
251
-
252
-
**docket integration** (PRs #534, #536, #539):
253
-
- migrated background tasks from inline asyncio to docket (Redis-backed task queue)
254
-
- copyright scanning, media export, ATProto sync, and teal scrobbling now run via docket
255
-
- graceful fallback to asyncio for local development without Redis
256
-
- parallel test execution with xdist template databases (#540)
257
-
258
-
**concurrent export downloads** (PR #545):
259
-
- exports now download tracks in parallel (up to 4 concurrent) instead of sequentially
260
-
- significantly faster for users with many tracks or large files
261
-
- zip creation remains sequential (zipfile constraint)
262
-
263
-
**ATProto refactor** (PR #534):
264
-
- reorganized ATProto record code into `_internal/atproto/records/` by lexicon namespace
265
-
- extracted `client.py` for low-level PDS operations
266
-
- cleaner separation between plyr.fm and teal.fm lexicons
267
-
268
-
**documentation & observability**:
269
-
- AudD API cost tracking dashboard (#546)
270
-
- promoted runbooks from sandbox to `docs/runbooks/`
271
-
- updated CLAUDE.md files across the codebase
272
-
273
-
---
274
-
275
-
#### artist support links & inline playlist editing (PRs #520-532, Dec 8)
276
-
277
-
**artist support link** (PR #532):
278
-
- artists can set a support URL (Ko-fi, Patreon, etc.) in their portal profile
279
-
- support link displays as a button on artist profile pages next to the share button
280
-
- URLs validated to require https:// prefix
281
-
282
-
**inline playlist editing** (PR #531):
283
-
- edit playlist name and description directly on playlist detail page
284
-
- click-to-upload cover art replacement without modal
285
-
- cleaner UX - no more edit modal popup
286
-
287
-
**platform stats enhancements** (PRs #522, #528):
288
-
- total duration displayed in platform stats (e.g., "42h 15m of music")
289
-
- duration shown per artist in analytics section
290
-
- combined stats and search into single centered container for cleaner layout
291
-
292
-
**navigation & data loading fixes** (PR #527):
293
-
- fixed stale data when navigating between detail pages of the same type
294
-
- e.g., clicking from one artist to another now properly reloads data
295
-
296
-
**copyright moderation improvements** (PR #480):
297
-
- enhanced moderation workflow for copyright claims
298
-
- improved labeler integration
299
-
300
-
**status maintenance workflow** (PR #529):
301
-
- automated status maintenance using claude-code-action
302
-
- reviews merged PRs and updates STATUS.md narratively
303
-
304
-
---
305
-
306
-
#### playlist fast-follow fixes (PRs #507-519, Dec 7-8)
307
-
308
-
**public playlist viewing** (PR #519):
309
-
- playlists now publicly viewable without authentication
310
-
- ATProto records are public by design - auth was unnecessary for read access
311
-
- shared playlist URLs no longer redirect unauthenticated users to homepage
312
-
313
-
**inline playlist creation** (PR #510):
314
-
- clicking "create new playlist" from AddToMenu previously navigated to `/library?create=playlist`
315
-
- this caused SvelteKit to reinitialize the layout, destroying the audio element and stopping playback
316
-
- fix: added inline create form that creates playlist and adds track in one action without navigation
317
-
318
-
**UI polish** (PRs #507-509, #515):
319
-
- include `image_url` in playlist SSR data for og:image link previews
320
-
- invalidate layout data after token exchange - fixes stale auth state after login
321
-
- fixed stopPropagation blocking "create new playlist" link clicks
322
-
- detail page button layouts: all buttons visible on mobile, centered AddToMenu on track detail
323
-
- AddToMenu smart positioning: menu opens upward when near viewport bottom
324
-
325
-
**documentation** (PR #514):
326
-
- added lexicons overview documentation at `docs/lexicons/overview.md`
327
-
- covers `fm.plyr.track`, `fm.plyr.like`, `fm.plyr.comment`, `fm.plyr.list`, `fm.plyr.actor.profile`
328
-
329
-
---
330
-
331
-
#### playlists, ATProto sync, and library hub (PR #499, Dec 6-7)
332
-
333
-
**playlists** (full CRUD):
334
-
- create, rename, delete playlists with cover art upload
335
-
- add/remove/reorder tracks with drag-and-drop
336
-
- playlist detail page with edit modal
337
-
- "add to playlist" menu on tracks with inline create
338
-
- playlist sharing with OpenGraph link previews
339
-
340
-
**ATProto integration**:
341
-
- `fm.plyr.list` lexicon for syncing playlists/albums to user PDSes
342
-
- `fm.plyr.actor.profile` lexicon for artist profiles
343
-
- automatic sync of albums, liked tracks, profile on login
344
-
345
-
**library hub** (`/library`):
346
-
- unified page with tabs: liked, playlists, albums
347
-
- nav changed from "liked" → "library"
348
-
349
-
**related**: scope upgrade OAuth flow (PR #503), settings consolidation (PR #496)
350
-
351
-
---
352
-
353
-
#### sensitive image moderation (PRs #471-488, Dec 5-6)
354
-
355
-
- `sensitive_images` table flags problematic images
356
-
- `show_sensitive_artwork` user preference
357
-
- flagged images blurred everywhere: track lists, player, artist pages, search, embeds
358
-
- Media Session API respects sensitive preference
359
-
- SSR-safe filtering for og:image link previews
142
+
### Earlier December 2025
360
143
361
-
---
362
-
363
-
#### teal.fm scrobbling (PR #467, Dec 4)
364
-
365
-
- native scrobbling to user's PDS using teal's ATProto lexicons
366
-
- scrobble at 30% or 30 seconds (same threshold as play counts)
367
-
- toggle in settings, link to pdsls.dev to view records
368
-
369
-
---
144
+
See `.status_history/2025-12.md` for detailed history including:
145
+
- visual customization with custom backgrounds (PRs #595-596, Dec 16)
146
+
- performance & moderation polish (PRs #586-593, Dec 14-15)
147
+
- mobile UI polish & background task expansion (PRs #558-572, Dec 10-12)
148
+
- confidential OAuth client for 180-day sessions (PRs #578-582, Dec 12-13)
149
+
- pagination & album management (PRs #550-554, Dec 9-10)
150
+
- public cost dashboard (PRs #548-549, Dec 9)
151
+
- docket background tasks & concurrent exports (PRs #534-546, Dec 9)
152
+
- artist support links & inline playlist editing (PRs #520-532, Dec 8)
153
+
- playlist fast-follow fixes (PRs #507-519, Dec 7-8)
154
+
- playlists, ATProto sync, and library hub (PR #499, Dec 6-7)
155
+
- sensitive image moderation (PRs #471-488, Dec 5-6)
156
+
- teal.fm scrobbling (PR #467, Dec 4)
157
+
- unified search with Cmd+K (PR #447, Dec 3)
158
+
- light/dark theme system (PR #441, Dec 2-3)
159
+
- tag filtering and bufo easter egg (PRs #431-438, Dec 2)
370
160
371
-
### Earlier December / November 2025
161
+
### November 2025
372
162
373
-
See `.status_history/2025-12.md` and `.status_history/2025-11.md` for detailed history including:
374
-
- unified search with Cmd+K (PR #447)
375
-
- light/dark theme system (PR #441)
376
-
- tag filtering and bufo easter egg (PRs #431-438)
163
+
See `.status_history/2025-11.md` for detailed history including:
377
164
- developer tokens (PR #367)
378
165
- copyright moderation system (PRs #382-395)
379
166
- export & upload reliability (PRs #337-344)
···
388
175
| track | focus | status |
389
176
|-------|-------|--------|
390
177
| moderation | consolidate architecture, add rules engine | planning |
391
-
| atprotofans | supporter validation, content gating | planning |
178
+
| atprotofans | supporter validation, content gating | shipped |
392
179
393
180
### known issues
394
181
- playback auto-start on refresh (#225)
···
443
230
- ✅ copyright moderation with ATProto labeler
444
231
- ✅ docket background tasks (copyright scan, export, atproto sync, scrobble)
445
232
- ✅ media export with concurrent downloads
233
+
- ✅ supporter-gated content via atprotofans
446
234
447
235
**albums**
448
236
- ✅ album CRUD with cover art
···
479
267
see live dashboard: [plyr.fm/costs](https://plyr.fm/costs)
480
268
481
269
- fly.io (plyr apps only): ~$12/month
482
-
- relay-api (prod): $5.80
483
-
- relay-api-staging: $5.60
484
-
- plyr-moderation: $0.24
485
-
- plyr-transcoder: $0.02
486
270
- neon postgres: $5/month
487
271
- cloudflare (R2 + pages + domain): ~$1.16/month
488
272
- audd audio fingerprinting: $0-10/month (6000 free/month)
···
551
335
552
336
---
553
337
554
-
this is a living document. last updated 2025-12-21.
338
+
this is a living document. last updated 2025-12-23.
update.wav
update.wav
This is a binary file and will not be displayed.