Support multiple player profiles with switching - #3
Open
carochacs wants to merge 1 commit into
Open
Conversation
Player identity, XP/level/streak, per-song practice stats, favorites,
career progression (paths/challenges/quests/wallet/shop), and
achievements/Feats were all keyed to an implicit single device-wide
profile (several tables even had a CHECK(id=1) constraint baked in).
Households sharing one feedBack instance had no way to keep separate
progress, so this adds real multi-profile support: a `profiles` table,
a device-local active-profile pointer, and profile_id scoping threaded
through every one of those subsystems (including the achievements
plugin's own sqlite tables). Existing installs migrate their single
profile's data to profile 1 automatically, with zero behavior change
until a second profile is added.
New endpoints: GET/POST /api/profiles, POST /api/profiles/{id}/activate,
DELETE /api/profiles/{id}. Existing endpoints are unchanged in shape —
they now implicitly read/write whichever profile is active. A "Switch
profile" button on the Profile screen opens a picker to switch or add
profiles; switching reloads the page since there's no session/auth
system to scope a switch to more narrowly.
Playlists/collections and saved practice loops remain device-wide for
now (documented as a known v1 gap, not silently dropped).
carochacs
commented
Jul 31, 2026
carochacs
left a comment
Collaborator
Author
There was a problem hiding this comment.
Reviewed the full diff (metadata_db.py schema/migration, lib/routers/profile.py, plugins/achievements/routes.py, static/v3/profile.js, tests).
- No TODOs/NotImplementedError; profile scoping is consistently applied across favorites, XP, streaks, progression, wallet/shop via idempotent migration helpers that read live schema rather than hardcoding columns.
delete_profilecorrectly refuses to delete the active or last remaining profile, with lock-guarded cleanup across all associated tables.- Follows repo conventions: flat imports,
threading.Lockaround writes, parameterized SQL throughout, vanilla JS, no new frontend frameworks. - No auth-bypass or path-traversal concerns (this is explicitly a non-auth "who's playing" picker, per the PR).
display_nameis escaped client-side before insertion into innerHTML — no stored-XSS surface. All SQL uses parameterized placeholders. - Minor non-blocking note:
activate_profile's existence check + insert/update inside_lockisn't strictly atomic against a concurrentdelete_profileof the same id — low risk given the single-device usage model, and no unsafe SQL either way.
No blocking issues found — clear to merge once out of draft. (Could not submit as an "Approve" — GitHub blocks self-approval since this PR and the reviewing account share the same author.)
Generated by Claude Code
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.
What
Adds "who's playing" style multi-profile support so more than one person can share a feedBack install without mixing progress. Player identity, XP/level/streak, per-song practice stats, favorites, and career progression (paths/challenges/quests/wallet/shop) — plus the achievements/Feats plugin's own local store — are now scoped per-profile instead of one implicit device-wide profile.
profilestable replacing the oldCHECK(id=1)singletonprofilerow; a device-localactive_profilepointer (no auth system exists to hang a session off, so switching is a full-page reload rather than per-request).GET /api/profiles(list),POST /api/profiles(create),POST /api/profiles/{id}/activate(switch),DELETE /api/profiles/{id}(delete a profile and everything scoped to it — refuses to delete the active or the only remaining profile)./api/profile,/api/stats,/api/progression,/api/shop, …) are unchanged in shape — they now implicitly operate on whichever profile is active, so no caller elsewhere in the app (including the career plugin, which readsmeta_dbdirectly) needed to change.unlocks,counters,comp_ledger) get the same profile_id treatment.Known v1 scope boundary, called out explicitly rather than silently dropped: playlists/collections and saved practice loops remain device-wide (not yet per-profile) — a documented follow-up.
feedpak surface
web_library.db,achievements.db), not the sloppak/feedpak chart format.Checklist
CHANGELOG.md[Unreleased]updated (user-visible changes)git commit -s)Validation
Generated by Claude Code