16 Commits
Author SHA1 Message Date
mrleo1nidandClaude Opus 5.5 d91ff89c64 feat(admin): wave metrics split by model version (M6)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 51s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m5s
ci / ts / ts (packages + web) (push) Successful in 1m3s
ci / api / api (push) Successful in 29s
ci / db / db (push) Failing after 12s
The library page compares listen-through, early-skip and like rates per
impression model version, so v1 and the trained ranker can be compared online.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:57:11 +03:00
mrleo1nidandClaude Opus 5.5 fb07f962eb feat: taste onboarding for new listeners (M6)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Failing after 1m29s
ci / ts / ts (packages + web) (push) Successful in 49s
ci / api / api (push) Successful in 1m25s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 48s
ci / recommender / recommender (push) Successful in 21s
- GET /onboarding offers the library's top artists (with album covers)
  and genres and says whether to show it (not done and < 10 plays);
  PUT /me/onboarding stores the choice or a skip (migration 00009).
- The recommender seeds the taste from it while the user has fewer than
  30 scored tracks: chosen artists' tracks +1, chosen genres +0.5.
- Web: onboarding replaces the wave on the home page until done or
  skipped; artist grid with covers, genre chips, at least 3 artists.
- e2e journey and the recommender DB test cover it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:35:43 +03:00
mrleo1nidandClaude Opus 5.5 1c4e209e86 feat(web): align the web UI with the design prototype
ci / api / api (push) Successful in 43s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 52s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m36s
ci / ts / ts (packages + web) (push) Successful in 1m39s
- Ambient background in the app shell: three drifting blurred blobs,
  the third one tinted with the current track's cover; can be turned off
  in settings and stays still with reduced motion.
- Island player variant (floating glass panel) next to the bar, chosen
  in settings; player source line ("Album · …", "My Wave"), reactions
  around the transport controls, queue drawer.
- Sidebar: like / dislike counters, pending uploads badge on Queue,
  admin Settings page holding the upload queue limits.
- Collection: header tile with kicker, underline tabs, time / number
  lead column with an equalizer for the current track, album column,
  dimmed dislikes with undo, Play all / Shuffle pills.
- Album: cover-tinted header and Play / Shuffle; artist: artist wave,
  Play and popular tracks (new GET /artists/{id}/top-tracks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:13:24 +03:00
mrleo1nidandClaude Opus 5.5 ca106e8eb1 feat(api): upload queue with quota, tus intake, processing and import (M5.5)
ci / api / api (push) Successful in 46s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m3s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
ci / ts / ts (packages + web) (push) Successful in 1m38s
- Migration 00008: upload_batches, upload_files, upload_quota.
- Quota reserved under a row lock when a batch is created; every status
  change goes through one transition that frees space in the same
  transaction. SHA-256 duplicates are rejected before any upload.
- tus 1.0.0 core (HEAD/PATCH) for the author only; offset is the real
  staging file size, data beyond the declared size is refused (ADR-015).
- Processing worker: ffprobe checks, SHA-256 verification, exact and
  Chromaprint duplicates against library and queue, metadata from tags
  or the dropped folder path, possible_duplicate on metadata match, and
  the target path from write_template reusing existing folder spelling.
- Import worker: edited tags written with ffmpeg stream copy, file
  written by storage-agent, point scan of the new file, imported status;
  idempotent and resumed after restart. Stale uploads expire after 24 h,
  orphan staging files are removed at startup.
- API: quota, precheck, batches, my uploads, withdraw, admin list,
  metadata edit, bulk review, retry and queue settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:23:05 +03:00
mrleo1nidandClaude Opus 5.5 f66be2ba0a feat: storage writes via storage-agent, accepts_uploads and write_template (M5.5)
ci / api / api (push) Successful in 33s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 43s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m11s
ci / ts / ts (packages + web) (push) Successful in 1m10s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 51s
- storage-agent: POST /internal/files copies a staged file into a source
  (temp file, fsync, SHA-256 check, hard link so nothing is overwritten,
  " (2)" suffix on conflict, owner PIPU_FILE_UID/GID) and
  POST /internal/files/test. NFS sources that accept uploads are mounted
  rw in an agent-only /library-rw and bind-mounted read-only for others;
  local sources are written through /library-host-rw.
- meta: write templates ({track:02}, {disc:1-}, [optional]) with name
  sanitising and length limits, suggestion from the source's regexes
  and a reversibility check against 50 catalogue tracks.
- API and admin UI: accepts_uploads / write_template on sources,
  write-template suggest and check endpoints, write test result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:46:56 +03:00
mrleo1nidandClaude Opus 5.5 6a67c27f50 feat(api): Android self-update endpoints proxied from Gitea releases (M5)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 43s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m12s
ci / api / api (push) Successful in 35s
ci / ts / ts (packages + web) (push) Successful in 1m20s
GET /api/v1/app/android/latest reads the latest Gitea release with the
server token (cached for 10 minutes); the download endpoint caches the
APK under /updates, verifies its SHA-256 and serves it with Range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:52:41 +03:00
mrleo1nidandClaude Opus 5.5 eaedd1fc62 feat(web): My Wave button, mood picker and endless wave playback (M4)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 41s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m13s
ci / ts / ts (packages + web) (push) Successful in 1m14s
ci / api / api (push) Successful in 36s
- WaveButton: TypeScript port of the prototype's canvas animation
  (orb and liquid), breathing at rest, pulsing to the track's BPM and
  energy in the cover palette, following the cursor, pressing with a
  spring; reduced motion and hidden tabs slow or stop it
- home: the wave in the centre with activity and mood chips; changing
  them while playing rebuilds the upcoming tracks without stopping
- player wave mode: asks for the next batch when two tracks are left,
  sends complete / skip / early skip / like / dislike as feedback and
  refreshes the queue right after a reaction or an early skip
- tracks expose bpm, energy and loudness once analysed; library page
  shows analysis progress and 7-day wave metrics; wave style setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:29:38 +03:00
mrleo1nidandClaude Opus 5.5 3908814cda feat(api): My Wave sessions with a Go fallback and wave metrics (M4)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 40s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 9s
ci / api / api (push) Successful in 35s
ci / ts / ts (packages + web) (push) Successful in 51s
- /wave/options, POST /wave/sessions (activity, mood, seed),
  /next with reactions, PATCH to switch the context on the fly, DELETE
- the recommender is called with an 800 ms budget; on failure the api
  serves a fallback wave of likes, favourite artists and random
  available tracks with the same dislike / recent / served filters
- every served track is an impression with its source and model; wave
  events (context_type wave) set its outcome, reactions win
- library status shows the analysis queue and 7-day wave metrics:
  listen-through, early skip and like rate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:23:17 +03:00
mrleo1nidandClaude Opus 5.5 c05e523cbe feat(api): streaming, transcoding, events, history and likes (M3)
ci / api / api (push) Successful in 29s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 35s
ci / ts / ts (packages + web) (push) Failing after 49s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s
- tracks carry a stream_url signed for the user for 12 h and the
  user's reaction; /stream checks the signature, expiry and that the
  user is not blocked, then hands the file to nginx by X-Accel-Redirect
  (or serves it with Range support when X_ACCEL=false)
- original by default; formats browsers can't play and explicit
  high/medium/low go through Opus transcoding, never upwards: a lossy
  source under the target bitrate stays original; transcodes are
  cached per file and size, deduplicated in flight, limited by
  TRANSCODE_CONCURRENCY and trimmed hourly by LRU to the size limit
- /events takes up to 500 events with client UUIDs, drops repeats and
  events older than 30 days, counts plays (≥ half or 4 min, or
  finished), early skips and history entries (≥ 30 s)
- history with clearing, likes and dislikes with cursors and totals,
  track reactions, playback state with last-write-wins, tracks batch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:25:15 +03:00
mrleo1nidandClaude Opus 5.5 8b76aa3964 feat: album covers with palettes and signed links (M2)
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 55s
ci / ts / ts (packages + web) (push) Successful in 1m0s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 34s
ci / api / api (push) Successful in 26s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 34s
- covers are taken from the embedded picture of the first track or a
  cover/folder/front image in the album folder, converted by ffmpeg to
  WebP 64/300/1200 and named by content hash, so albums sharing a cover
  share the files; a full scan looks again for albums without one
- a 3-colour palette (dominant, second, most saturated accent) from a
  deterministic k-means feeds placeholders, page tints and the wave
- /covers/{id} is public but HMAC-signed and immutable; nginx serves
  the file via X-Accel-Redirect, X_ACCEL=false serves it from api
- the web client picks the size by display size and pixel density
- install.sh and update.sh generate STREAM_SIGNING_KEY

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:06:15 +03:00
mrleo1nidandClaude Opus 5.5 c1160cccd3 feat(api): search with Meilisearch (M2)
ci / api / api (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 34s
ci / ts / ts (packages + web) (push) Successful in 1m1s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 25s
- artists, albums and tracks indexes are rebuilt from PostgreSQL next
  to the live ones and swapped in, so search keeps working; a rebuild
  runs on start, after every scan and when a source is removed
- transliteration fields (Кино ↔ kino) and a retry in the other
  keyboard layout (ghbdtn → привет) reported as corrected_query
- /search/suggest (3 artists, 3 albums, 5 tracks), /search by type
  with cursor pages, /admin/search/reindex; Meilisearch is part of
  /readyz
- found ids are hydrated from the catalog, so a lagging index never
  returns deleted entries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:51:50 +03:00
mrleo1nidandClaude Opus 5.5 c583991d14 feat(api): library sources, scanner and catalog (M2)
ci / api / api (push) Successful in 26s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 22s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 37s
ci / ts / ts (packages + web) (push) Successful in 51s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 53s
- schema for sources, path patterns, media files, artists, albums,
  tracks, genres (migration 00003)
- path patterns (RE2 with named groups, skip), merge with tags by
  source priority, fallbacks from the file name and folder, name
  normalization and multi-artist split
- scanner: walk, skip unchanged files by size and mtime, ffprobe in 8
  workers with a single catalog writer, renames by content hash keep
  the track, duplicates fold into one track with the best file as
  primary, missing files are kept 30 days
- storage-agent mounts NFS with validated server, export and an option
  whitelist, no shell; test mount counts audio files
- admin API: sources CRUD, connection test, patterns with preview,
  scan, library status, file errors, host folder picker; scheduled
  scans and health checks; mounts are restored on start
- catalog API: artist, artist albums, album with tracks, track
- api image is alpine with ffmpeg for ffprobe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:48:37 +03:00
mrleo1nidandClaude Opus 5.5 f075863f15 feat(api): user management and audit log for admins (M1)
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 47s
ci / ts / ts (packages + web) (push) Successful in 38s
ci / api / api (push) Successful in 18s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 35s
- /admin/users: list with search, role filter and cursor pages; create
  with a one-time temporary password; rename, change role, block;
  reset password; delete
- every change is checked against the role policy, a forbidden field
  rejects the whole patch; the last active admin is locked in the
  transaction and cannot be demoted, blocked or deleted
- role change and blocking revoke access tokens at once; blocking and
  password reset also end all sessions
- /admin/audit: newest-first journal with actor names
- path and query parameter errors are problem+json now

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:18:02 +03:00
mrleo1nidandClaude Opus 5.5 ed4a492dba feat(api): sign-in, refresh rotation and bootstrap admin (M1)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m15s
ci / api / api (push) Successful in 1m31s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 47s
ci / ts / ts (packages + web) (push) Successful in 39s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 26s
- users, refresh_tokens and audit_log schema (migration 00002)
- argon2id passwords with rehash on login, password policy, lockout
  with exponential back-off and a per-IP rate limit in Redis
- Ed25519 JWT access tokens with kid-based key rotation; rotating
  refresh tokens with reuse detection (cookie for web, body for Android)
- bootstrap admin on first start; must_change_credentials and
  must_change_password gate every other endpoint
- /auth/login, /auth/refresh, /auth/logout(-all), /me, /me/password,
  /me/bootstrap-credentials, /me/sessions; server-info reports
  bootstrap_required
- role policy from docs/auth-and-roles.md with table tests
- problem+json errors with field errors; ru/en translations of the codes
- install.sh and update.sh generate JWT_PRIVATE_KEY

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:12:25 +03:00
mrleo1nidandClaude Opus 5.5 4fa1add80d fix: address project review findings
ci / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 48s
ci / api (push) Successful in 51s
ci / recommender (push) Canceled after 2s
ci / ts (packages + web) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (apps/web/Dockerfile, web, ) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, api, api) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/recommender/Dockerfile, recommender, ) (push) Canceled after 0s
ci / mobile (Metro-бандл Android) (push) Canceled after 0s
Security and robustness:
- storage-agent rejects an empty INTERNAL_TOKEN (config notEmpty + middleware)
- api takes the client IP only from X-Real-IP (chi RealIP trusted True-Client-IP)
- /readyz reports ok/fail and logs the cause; checks run in parallel with a timeout
- storage-agent shuts down gracefully; shared httpx.NewServer/Serve with IdleTimeout
- goose migrations take a Postgres advisory lock
- recommender readyz: per-check timeout, redis socket timeouts

Clients:
- Android release allows cleartext HTTP for LAN servers (expo-build-properties)
- checkServer times out after 10s; connect button is disabled while checking

Deploy and CI:
- install.sh hands cache/uploads/updates/models to the non-root service users
- update.sh refuses downgrades unless PIPU_ALLOW_DOWNGRADE=1
- recommender/analyzer get only the env they need; required secrets fail fast
- nginx security headers and CSP; strip True-Client-IP
- dev compose binds ports to 127.0.0.1; postgres/redis images pinned
- version.mjs fails on a malformed explicit tag instead of releasing 0.0.0-dev
- contract check catches untracked generated files
- final releases fail without a release keystore; changelog skips rc tags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:46:46 +03:00
mrleo1nidandClaude Opus 5.5 ebf296542c feat: M0 monorepo skeleton with services, clients, deploy and CI
ci / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 3m55s
ci / api (push) Successful in 51s
ci / mobile (Metro-бандл Android) (push) Canceled after 0s
release / images (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
release / images (services/recommender/Dockerfile, recommender, ) (push) Canceled after 0s
ci / recommender (push) Successful in 1m11s
ci / ts (packages + web) (push) Canceled after 3s
release / verify (push) Successful in 1m16s
release / images (apps/web/Dockerfile, web, ) (push) Failing after 2m40s
release / android + release (push) Skipped
release / images (services/api/Dockerfile, api, api) (push) Canceled after 1s
- api/openapi.yaml: contract with /healthz, /readyz, /api/v1/server-info
- services/api (Go): api with oapi-codegen strict server, goose migrations,
  readiness checks for Postgres/Redis; storage-agent stub with internal token
- services/recommender (Python, uv): FastAPI health/readiness/internal status,
  `pipu-rec serve` and `pipu-rec analyze` entrypoints
- packages: generated api-client, core (server URL + server check),
  i18n (ru/en + dictionary check), design-tokens (CSS vars + TS from tokens.json)
- apps/web: Vite + React shell showing server status, language switch
- apps/mobile: Expo (SDK 53) Android app with server connect screen
- deploy: Dockerfiles, compose (prod + dev), nginx template, install/update scripts
- .gitea/workflows: ci.yml and tag-based release.yml (images, signed APK, release)
- scripts/version.mjs: version and Android versionCode from git tag
- tests/fixtures/music/generate.sh: synthetic test library via ffmpeg
- docs: versionCode formula, OpenAPI 3.0.3, dev instructions in README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:55:11 +03:00