audio streaming app plyr.fm
38
fork

Configure Feed

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

chore: fix OAuth permission set display, update status (#889)

- change permission set field from `description` to `detail` (ATProto spec)
- modernize publish script to use pydantic settings
- update STATUS.md with recent work (PRs #886-888)

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

authored by

nate nowack
Claude Opus 4.6
and committed by
GitHub
6954819b 9984094b

+48 -16
+23 -1
STATUS.md
··· 47 47 48 48 ### February 2026 49 49 50 + #### OAuth permission set cleanup + docs audit (PR #889, Feb 8) 51 + 52 + **OAuth permission set**: authorization page was showing raw NSID (`fm.plyr.authFullApp`) instead of human-readable description. root cause: ATProto permission sets use `detail` field (not `description`) for the subtitle text. updated lexicon and publish script, republished to PDS. also modernized publish script from raw `os.environ` to pydantic settings. 53 + 54 + **docs audit (PR #888)**: fixed stale/broken documentation across 6 files — wrong table names in copyright docs, outdated pool_recycle values, broken links in docs index, missing tools entries. updated README to reflect full feature set. added semantic search and playlists to search.md. 55 + 56 + --- 57 + 58 + #### auth state refresh + backend refactor (PRs #886-887, Feb 8) 59 + 60 + **auth state refresh (PR #887)**: after account switch or login, stale user data persisted because `AuthManager.initialize()` no-ops once the `initialized` flag is set. added `refresh()` that resets the flag before re-fetching, used in all exchange-token call sites. 61 + 62 + **backend package split (PR #886)**: split three monolith files into focused packages: 63 + - `auth.py` (1,400 lines) → `auth/` package (8 modules) 64 + - `background_tasks.py` (803 lines) → `tasks/` package (5 domain modules: copyright, ml, pds, storage, sync) 65 + - 5 `*_client.py` files → `clients/` package 66 + - extracted upload pipeline into 7 named phase functions, shared tag ops to `utilities/tags.py` 67 + 68 + all public APIs preserved via `__init__.py` re-exports. 424 tests pass. 69 + 70 + --- 71 + 50 72 #### portal pagination + perf optimization (PRs #878-879, Feb 8) 51 73 52 74 **portal pagination (PR #878)**: `GET /tracks/me` now supports `limit`/`offset` pagination (default 10 per page). portal loads first 10 tracks with a "load more" button. export section uses total count for accurate messaging. ··· 167 189 168 190 ### current focus 169 191 170 - ML-powered track features: genre classification (Replicate effnet-discogs) auto-runs on upload with optional auto-tagging. mood search (CLAP + turbopuffer) feature-flagged behind `vibe-search`, runs parallel to keyword search. performance optimization on hot paths (GET /tracks/top p95 cut from 1.2s to ~550ms). repo reorganized — services and infrastructure in dedicated directories. 192 + code quality and developer experience: backend split into focused packages (auth, tasks, clients), docs audit to fix stale references, OAuth permission set cleanup. ML features stable — genre classification and mood search running in production. performance optimization on hot paths (GET /tracks/top p95 cut from 1.2s to ~550ms). 171 193 172 194 ### known issues 173 195 - iOS PWA audio may hang on first play after backgrounding
+2 -2
lexicons/authFullApp.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "permission-set", 7 - "title": "plyr.fm", 8 - "description": "Upload and manage audio content, playlists, likes, and comments.", 7 + "title": "Full plyr.fm Access", 8 + "detail": "Provides full access to all plyr.fm features including uploading and managing tracks, playlists, likes, and comments.", 9 9 "permissions": [ 10 10 { 11 11 "type": "permission",
+23 -13
scripts/publish_permission_set.py
··· 2 2 """publish permission set lexicon to ATProto repo with specific rkey.""" 3 3 4 4 import asyncio 5 - import os 5 + from pathlib import Path 6 6 7 7 from atproto import AsyncClient 8 + from pydantic import Field 9 + from pydantic_settings import BaseSettings, SettingsConfigDict 10 + 11 + ROOT = Path(__file__).parent.parent 12 + 13 + 14 + class Settings(BaseSettings): 15 + model_config = SettingsConfigDict(env_file=str(ROOT / ".env"), extra="ignore") 16 + 17 + plyrfm_handle: str = Field(validation_alias="PLYRFM_HANDLE") 18 + plyrfm_password: str = Field(validation_alias="PLYRFM_PASSWORD") 19 + namespace: str = Field(default="fm.plyr", validation_alias="NAMESPACE") 8 20 9 21 10 22 async def main(): 11 - handle = os.environ["PLYRFM_HANDLE"] 12 - password = os.environ["PLYRFM_PASSWORD"] 13 - namespace = os.environ.get("NAMESPACE", "fm.plyr") 23 + settings = Settings() 14 24 15 25 client = AsyncClient() 16 - await client.login(handle, password) 26 + await client.login(settings.plyrfm_handle, settings.plyrfm_password) 17 27 18 - permission_set_id = f"{namespace}.authFullApp" 28 + permission_set_id = f"{settings.namespace}.authFullApp" 19 29 20 30 record = { 21 31 "$type": "com.atproto.lexicon.schema", ··· 24 34 "defs": { 25 35 "main": { 26 36 "type": "permission-set", 27 - "title": "plyr.fm", 28 - "description": "Upload and manage audio content, playlists, likes, and comments.", 37 + "title": "Full plyr.fm Access", 38 + "detail": "Provides full access to all plyr.fm features including uploading and managing tracks, playlists, likes, and comments.", 29 39 "permissions": [ 30 40 { 31 41 "type": "permission", 32 42 "resource": "repo", 33 43 "action": ["create", "update", "delete"], 34 44 "collection": [ 35 - f"{namespace}.track", 36 - f"{namespace}.like", 37 - f"{namespace}.comment", 38 - f"{namespace}.list", 39 - f"{namespace}.actor.profile", 45 + f"{settings.namespace}.track", 46 + f"{settings.namespace}.like", 47 + f"{settings.namespace}.comment", 48 + f"{settings.namespace}.list", 49 + f"{settings.namespace}.actor.profile", 40 50 ], 41 51 } 42 52 ],