Commit Graph
267 Commits
Author SHA1 Message Date
QuickandGitHub 681c846da8 Merge pull request #42 from fluxis/fix/vitest4-recipes-spy 2026-06-02 15:37:54 -04:00
Silo Server DeveloperandClaude Opus 4.8 54a04958b7 fix(web): vitest 4 compatible fetch spy in recipes.test (unblocks build after vitest 4.1.0 bump)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:10:46 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
621349a085 build(deps-dev): bump vitest from 3.2.4 to 4.1.0 in /web (#40)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.2.4 to 4.1.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.0/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-02 11:35:22 -04:00
ea3b5a2e29 feat(requests): multi-instance Sonarr/Radarr routing with HD/4K defaults and anime overrides (#39)
* docs: design spec for multi-instance Sonarr/Radarr request routing

Seerr-style multi-instance arr management inside Silo's request system:
many instances per kind, HD/4K default routing, entitlement-driven
dual-quality fan-out, per-instance anime overrides (keyword 210024),
and a one-to-many media_request_targets model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: implementation plan for multi-instance arr request routing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(requests): migration for multi-instance arr routing

Adds migration 169 to convert request_integrations from a one-row-per-kind
table keyed on `kind` to a multi-instance table keyed on `id`, with HD/4K
defaults, anime overrides, and a new one-to-many media_request_targets table
for per-quality fulfillment tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): instance, target, and dual-quality types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): id-based integration CRUD

Replace upsert-by-kind (UpsertIntegration/UpsertIntegrations) with
GetIntegration, CreateIntegration, UpdateIntegration, DeleteIntegration,
and ClearDefault. Rewrites scanIntegration and integrationColumns to cover
all new multi-instance columns (id, name, is_4k, is_default, is_default_4k,
anime_* fields). Updates the Store interface accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): target persistence and aggregate status

* feat(tmdb): expose keyword ids and original language on detail

* feat(requests): Seerr-exact anime detection (keyword 210024)

* feat(requests): quality/anime routing engine

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(requests): force_dual_quality setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(requests): multi-target fulfillment, reconcile, retry, and instance CRUD

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(api): request integration CRUD endpoints, targets in responses, entitlement wiring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(web): multi-instance request integration types and CRUD hooks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(web): multi-instance arr manager, dual-quality toggle, per-target queue

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): UX review fixes for arr manager (delete confirm, switch hints, test feedback, dirty + target status)

* fix(requests): address code-review findings (test-connection by id, HD-only default ceiling, retryable partial failure, idempotent submit, transactional defaults, presence/target reconcile, auto-approve gate)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review (anime override fallback, non-null slices, save gate, a11y, DeleteTarget not-found)

- routing: anime fields only override standard root/profile/tags when set,
  so enabling anime with blank fields reuses standard values instead of
  clearing them into an invalid submission
- api: normalize nil Tags/AnimeTags to [] so they serialize as arrays not null
- web: require an API key before saving a NEW instance; add aria-expanded/
  aria-controls to the anime-overrides disclosure toggle
- repo: DeleteTarget returns ErrNotFound when no row was deleted

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(requests): address PR review findings

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
2026-06-02 11:25:18 -04:00
QuickandGitHub a6b510c7c5 fix(catalog): derive next-up anchors without progress cap (#41) 2026-06-02 11:24:33 -04:00
Quick 0fb06670d9 docs: add discord link to readme 2026-06-01 18:07:14 -04:00
QuickandGitHub 8bed6c6452 fix(auth): keep plugin-only passwords bcrypt-safe (#33) 2026-05-31 19:59:08 -04:00
QuickandGitHub bc921cac0e [codex] show plugin OAuth providers on login (#32)
* fix(auth): show oauth login providers

* fix(auth): hide oauth providers when routes are unavailable
2026-05-31 17:34:48 -04:00
zZebrahzandGitHub 9b82535190 Merge pull request #29 from Silo-Server/investigate/user-settings-session-invalidations
fix(auth): avoid unnecessary session invalidation on updates that do not affect security-relevant fields
2026-05-31 13:53:06 -07:00
zZebrahz cb1bc6ab62 fix(auth): preserve profile tokens on library changes 2026-05-30 21:39:10 -07:00
zZebrahz 07e50b72b6 fix(auth): keep sessions on library scope changes 2026-05-30 19:31:46 -07:00
zZebrahz af2c54c8f0 fix(auth): revoke sessions on library scope nil changes 2026-05-30 19:21:25 -07:00
zZebrahz 3f55440752 fix(auth): avoid unchanged access policy invalidation 2026-05-30 19:01:22 -07:00
zZebrahz 0a27dac9cd fix(auth): avoid revoking sessions for unchanged user policy 2026-05-30 18:42:03 -07:00
Quick 9e601d4d56 fix(sections): honor manual bulk library selection 2026-05-30 14:52:49 -04:00
QuickandGitHub 14b54cab0c [codex] fix ASS subtitle font loading (#28) 2026-05-30 14:26:07 -04:00
QuickandGitHub f11bd7ba05 Merge pull request #27 from Silo-Server/feat/ai-subtitle-translation
feat(subtitles): on-demand AI subtitle translation with live streaming
2026-05-30 01:00:37 -04:00
QuickandClaude Opus 4.8 dd99e3e54c test(player): assert full parsed events in realtime-protocol tests
Use toEqual on the whole parsed envelope (not just .name) for the
subtitle_translation_* parser cases so payload-field parsing is covered, and
clarify the optional-label validation comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:36:06 -04:00
QuickandClaude Opus 4.8 0b5f815c7b fix(subtitles): address CodeRabbit review feedback
Backend:
- client.go: handle the io.ReadAll error on the chat response (retry rather
  than misparse a truncated read), and stop logging raw upstream response
  bodies (log byte count instead of subtitle/model text).
- srt.go: reject negative/reversed cue time ranges in parseTimingLine.
- translator.go: fail fast on a nil client, non-positive batch size, or empty
  target language instead of panicking / looping.

Frontend:
- VideoPlayer: clear the pending translation resume timer when the file
  changes, so a stale 30s callback can't fire against the new media.
- useSubtitleTracks: dedup cues on source times, not delay-shifted display
  times, so adjusting subtitle delay can't re-add the same cue.
- SubtitleMenu: only show "Translate with AI" when a translatable (non-live,
  text/non-bitmap) source exists, matching the modal's filter.
- IntegrationsSettings: validate AI settings before saving (non-empty
  endpoint/model when enabled, positive concurrency) instead of persisting an
  unusable config.
- realtime-protocol: validate optional label/message fields are strings; add
  parser tests for the subtitle_translation_* events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:29:28 -04:00
QuickandClaude Opus 4.8 4f2e94c8c5 fix(subtitles): guard AI job state transitions against terminal rows
UpdateProgress/CompleteJob/FailJob now only update a job that is still
pending/running, so a job cancelled or reaped as stale can't be resurrected
by a late write from its own worker goroutine. Defense-in-depth alongside the
queued-job heartbeat fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:23:16 -04:00
QuickandClaude Opus 4.8 669050d025 fix(subtitles): address review feedback on AI translation
- Heartbeat queued jobs (before the semaphore), not just running ones, so
  the stale-job reaper can't fail a job that is alive but waiting for a slot
  (which would otherwise let it resurrect itself on acquire, or admit a
  duplicate once the partial unique index no longer sees it as active).
- Filter the translate modal's source list to the formats the server accepts
  (text external/downloaded, non-bitmap embedded), so an unsupported source
  is prevented up front instead of failing the job asynchronously.
- Tell a duplicate requester (e.g. a second viewer, or after a reload) that a
  translation is already in progress instead of silently closing; the
  finished track still arrives via the subtitle_ready refresh.
- Note in the admin card that AI subtitle settings take effect after a
  restart (the running service snapshots them at construction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:19:02 -04:00
QuickandGitHub 13f8d7bc23 Merge pull request #26 from Silo-Server/web-ui-improvements
fix(catalog) Improve library browsing state, sort metadata, and stale refresh behavior
2026-05-30 00:18:00 -04:00
QuickandClaude Opus 4.8 e441d2d6e9 feat(subtitles): on-demand AI subtitle translation with live streaming
Add server-side AI subtitle translation backed by any OpenAI-compatible
chat endpoint (OpenAI, Groq, a local Ollama/llama.cpp server). A viewer
picks a source track and target language in the player; the server runs a
bounded, resumable job pipeline that translates SRT/VTT cues in batches and
streams them back over the realtime websocket so playback pauses, fills in
cues near the playhead, and resumes. The finished track is persisted as an
ordinary downloaded subtitle, so it reaches every client through the
existing subtitle pipeline with no client changes.

- Job lifecycle persisted in subtitle_ai_jobs (migration 168): enqueue with
  idempotency, bounded concurrency, progress/heartbeat, cancellation, and
  crash recovery.
- New realtime events (subtitle_ready + subtitle_translation_*) with a
  per-session notifier; the player renders a synthetic "live" track fed by
  websocket cues. Timestamps never leave the server, so timing can't drift.
- Admin settings card for endpoint / model / concurrency.

Player + lifecycle hardening (from the code review of this feature):
- Hand off from the live track to the persisted track on completion
  (selected by downloaded-subtitle id) and on the subtitle_ready broadcast,
  so the saved track survives a reload and a mid-stream socket drop.
- Never persist the synthetic live-track sentinel index as a subtitle
  preference; restore the prior selection on failure; only auto-resume
  playback if the viewer was actually playing.
- Resume promptly when the playhead is past the last cue; rebuild the live
  track on a new job; O(batch) live-cue ingestion instead of O(n^2).

Reliability:
- Root translation jobs in the application context so shutdown cancels them.
- Heartbeat-based stale-job reaper (safe across multiple instances) replaces
  the table-wide startup reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 23:59:12 -04:00
zZebrahz 179506e8be fix(people) load immediately and foreground refresh 2026-05-30 03:36:56 +00:00
zZebrahz 123d05b7d3 fix(people): refresh incomplete metadata inline 2026-05-30 03:28:51 +00:00
zZebrahz c76ec2b61a fix(web): clear applied library state marker 2026-05-30 02:56:40 +00:00
zZebrahz 20da980cc8 fix(catalog): include user sort metrics 2026-05-30 02:55:05 +00:00
zZebrahz a2d8118978 fix(web): preserve date-only item card days 2026-05-30 02:51:17 +00:00
zZebrahz 0cdd6c543f fix(web): avoid library saved-state layout shift 2026-05-30 02:48:29 +00:00
QuickandClaude Opus 4.8 9123cfb1f4 fix(api): accept forwarded host in websocket origin check
The shared WebSocket upgrader rejected handshakes unless the browser's
Origin host exactly matched r.Host. Behind a TLS-terminating CDN/proxy
that rewrites Host to the internal origin (carrying the public host in
X-Forwarded-Host), this comparison always failed and every realtime
socket 403'd at the handshake — playback control, events, watch-together
rooms, and admin log streaming all share the upgrader.

checkWebSocketOrigin now also accepts an Origin matching X-Forwarded-Host,
keeping the same-origin CSRF guard intact while supporting proxied
deployments. Extract a shared forwardedHost helper (first hop of a
multi-proxy list) and reuse it from requestBaseURL, replacing the
duplicated inline parse. Also reject opaque (empty-host) origins
explicitly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 22:40:55 -04:00
zZebrahz 1bfbdd2826 fix(people): refresh stale person metadata on view 2026-05-30 02:19:11 +00:00
zZebrahz 449fcf394c fix(web): preserve active library query from sidebar 2026-05-30 02:07:37 +00:00
zZebrahz 9c56f40170 feat(web): remember library page state 2026-05-30 02:07:37 +00:00
zZebrahz 96256ecbf8 fix(catalog): avoid skeletons after short result pages 2026-05-30 02:07:37 +00:00
zZebrahz bdc46b8c6c feat(catalog): show sort metadata on item cards 2026-05-30 02:07:37 +00:00
QuickandGitHub 66cfb0750d Merge pull request #25 from Silo-Server/feat/calendar-presets
feat(calendar): personalized presets (Following default) with watched overlay
2026-05-29 14:57:57 -04:00
QuickandClaude Opus 4.8 de8b8189d7 refactor(calendar): simplify preset handler and reuse storage util
- Extract hardcoded trending snapshot source/window to named constants.
- Collapse the three identical personal-preset nil-checks into one case.
- Persist the selected preset through the shared storage util (try/catch
  wrapped) instead of raw localStorage with manual SSR guards.
- Derive KNOWN_FILTERS from PRESET_OPTIONS so the lists can't drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:56:16 -04:00
QuickandClaude Opus 4.8 4c3911b5e1 feat(calendar): hide server-wide Popular preset in web UI for now
Popular reflects server-wide watch counts, which are sparse on a
low-traffic server. Hidden from the selector, URL allowlist, and
empty-state nudge; backend filter and the CalendarFilter type are
left intact so re-enabling is a one-line change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:24:18 -04:00
QuickandClaude Opus 4.8 c2d8dbd746 feat(calendar): dim and check-mark already-watched event cards
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:13:51 -04:00
QuickandClaude Opus 4.8 c32b5703ac feat(calendar): preset selector with responsive pills, persistence, empty-state nudge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:13:51 -04:00
QuickandClaude Opus 4.8 431341c235 feat(calendar): add watched field to CalendarEvent type
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 14:13:51 -04:00
QuickandClaude Opus 4.8 4dc0522cf2 feat(calendar): wire popular and trending sources into calendar handler
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:56:14 -04:00
QuickandClaude Opus 4.8 2bd0edb264 feat(calendar): resolve presets to id-sets and overlay watched status
Also drops the now-unused Filter/UserID/ProfileID fields from the
blendUpcomingIntoDiscoverRows CalendarFilter literal in recommendations.go,
which only wants an unrestricted windowed query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:55:39 -04:00
QuickandClaude Opus 4.8 1ba76af353 feat(calendar): add per-profile followed/favorites/watchlist/watched resolvers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:50:48 -04:00
QuickandClaude Opus 4.8 62b3861da0 feat(calendar): generalize personal filter to an id-set restriction
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:49:34 -04:00
QuickandClaude Opus 4.8 f113b32de0 docs(calendar): add presets design spec and implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:47:45 -04:00
QuickandClaude Opus 4.8 58fc4abca8 fix(calendar): order events by viewer-local wall-clock time
The local-airtime change re-sorted calendar events in Go using air_at,
the absolute UTC instant, which is nil whenever air_timezone is unset.
Since air_timezone is only inferred for a few networks/countries, most
events fell through to the alphabetical title tiebreak while still
displaying their raw air_time, so each day appeared scrambled.

Sort each local day by the wall-clock time the viewer actually sees,
mirroring the client: zoned events convert air_at into the viewer
timezone, unzoned events use the raw air_time, and date-only entries
(no air_time) sort last. The timezone reasoning lives in the new
catalog.CalendarEventLocalTime helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:16:18 -04:00
QuickandGitHub 3fe38c8c63 Merge pull request #24 from Silo-Server/claude/amazing-thompson-b2843d
fix(catalog): project air_timezone in episode catalog subquery
2026-05-29 11:55:56 -04:00
QuickandClaude Opus 4.8 32302e7b17 fix(catalog): project air_timezone in episode catalog subquery
episodeCatalogSelectBody is the derived "mi" relation that episode catalog
hydration and preview read qualifiedListItemColumns("mi") from. The local
episode airtimes feature (26370616) added air_timezone to the shared column
lists but not to this hand-written subquery, so the outer projection
referenced mi.air_timezone, which the subquery never exposed.

Postgres returns SQLSTATE 42703 (undefined_column), which is not one of the
codes episodeCatalogEntriesUnavailable treats as "fast path unavailable" (it
only catches 42P01/42883), so episode catalog requests failed with HTTP 500
instead of degrading. movie and series scopes query media_items directly, so
the column is present there and only episode scope broke.

Add si.air_timezone to the subquery, and add a regression test asserting that
episodeCatalogSelectBody exposes every column qualifiedListItemColumns reads
off mi, so future additions to the shared column lists cannot silently drift
from the episode read model again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 11:53:32 -04:00
QuickandGitHub 98d2596fbf Merge pull request #23 from Silo-Server/feat/trending-section
feat(sections): persist trending_discover via background-refreshed snapshot
2026-05-29 11:35:05 -04:00