Commit Graph
153 Commits
Author SHA1 Message Date
Silo Server Migration 7926d25c79 chore(web): format request search section 2026-05-25 18:20:39 -04:00
Silo Server Migration 09b97c0d32 feat(catalog): render RequestToAddSection grid with empty-state suppression 2026-05-25 18:19:42 -04:00
Silo Server Migration bcc28795e4 feat(search): render RequestToAddSection in the Cmd+K dialog with empty-state suppression 2026-05-25 18:17:31 -04:00
Silo Server Migration c5e2770bab feat(search): add RequestToAddSection grid variant for Catalog page 2026-05-25 18:15:52 -04:00
Silo Server Migration eda8b7938d feat(search): add RequestToAddSection dialog variant 2026-05-25 18:15:10 -04:00
Silo Server Migration b3cdf438f9 feat(request-card): make onRequest optional on discover variant 2026-05-25 18:13:31 -04:00
Silo Server Migration 4d51608118 test(requests): document viewer-keyed cache isolation and invalidation cascade 2026-05-25 18:12:45 -04:00
Silo Server Migration e97286da01 feat(requests): key useRequestSearch by viewer, forward signal, raise staleTime 2026-05-25 18:12:19 -04:00
Silo Server Migration 4f0ecbe3c0 refactor(keys): add viewerKey to requestKeys.search 2026-05-25 18:11:11 -04:00
Silo Server Migration 5fef177e36 feat(hooks): add useCanRequest gating hook for discovery eligibility 2026-05-25 18:10:58 -04:00
Silo Server Migration e1f711dea7 test(api): pin AbortSignal forwarding contract on api() 2026-05-25 18:10:06 -04:00
Silo Server MigrationandClaude Opus 4.7 08ba09c955 docs(plans): address Codex adversarial review for search request section
Fixes the high-severity finding that RequestToAddSection's internal
useRequestSearch call was not gated on discoveryEnabled, allowing
/api/v1/requests/search and TMDB lookups to fire for users without
request access. The plan now (1) passes { enabled: discoveryEnabled }
to the section's hook, (2) gates the parent mount in GlobalSearch and
Catalog on canRequest.discoveryEnabled as defense in depth, and (3)
adds tests asserting both the enabled forwarding and the no-mount
behavior when discovery is disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 18:02:58 -04:00
Silo Server MigrationandClaude Opus 4.7 f2a6b1081e docs(plans): add implementation plan for search request section
Twelve TDD tasks covering: api() signal contract test, useCanRequest
hook, viewer-keyed requestKeys.search, useRequestSearch extension
(signal + viewer key + 5min staleTime + enabled override), invalidation
cascade tests, RequestPosterCard optional onRequest, RequestToAddSection
component (dialog + grid variants), GlobalSearch and Catalog wiring
with empty-state suppression for the library-0/TMDB-pending edge case,
final lint/test pass, and manual smoke. Notes a single deviation from
the spec: submitDisabledReason is null in the initial implementation,
with per-row request data driving disabled UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:35:10 -04:00
Silo Server MigrationandClaude Opus 4.7 9fc70631a8 docs(specs): address Codex adversarial review for search request section
Splits discovery eligibility from submission eligibility so blocked
and quota-exhausted viewers still see the requestable section with
disabled per-row CTAs, matching the documented behavior. Documents
the required extensions to useRequestSearch — signal forwarding,
viewer-identity-keyed cache, and invalidation on auth/profile/
settings/limit changes — so the planned 5-minute staleTime is
safe and cancellation works as described.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:19:37 -04:00
Silo Server MigrationandClaude Opus 4.7 c205da8b2b docs(specs): add design for TMDB-backed request section in search
Adds the design for surfacing requestable TMDB results inside the main
catalog search (Cmd+K dialog and full results page) as a clearly
delimited "Request to Add" section that never blocks or displaces
library results.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:53:03 -04:00
Silo Server Migration 8f818ac0c9 ci(docker): build image on push to main via self-hosted runner
- Trigger Docker image builds on pushes to main instead of nightly cron
- Run on self-hosted Linux runner
- Drop the `nightly` tag
2026-05-25 13:03:56 -04:00
Silo Server Migration a639cf60de fix(catalog): gate search overview-only matches behind title FTS
- Always apply stats CTE + CROSS JOIN so single-word queries no longer flood results with description-only hits
- Require overview_rank >= 0.15 for overview-only fallback rows
- Switch title gate from contiguous LIKE to title_rank > 0 so reordered-token title matches aren't demoted
2026-05-25 12:49:38 -04:00
Silo Server Migration b2fae0aa70 refactor(scantrigger): drop redundant Target.LibraryID field
- Read library ID from Target.Folder.ID everywhere
- Guard scan queue enqueue against nil Folder
- Simplify admin API key auth error plumbing
2026-05-25 12:11:56 -04:00
Silo Server Migration c3cf485cd9 chore: add planning docs and requests updates
- Add plans for date-named episodes and Jellyfin autoscan compat
- Update requests handlers, service, and UI hooks
- Remove Makefile.local.example
2026-05-25 12:07:50 -04:00
Silo Server Migration 4a5ca0b9eb fix(jellycompat): harden autoscan auth and batch scan enqueue
- Reject nil API keys and bound last-used update with a 5s timeout
- Stop leaking internal queue errors in autoscan responses
- Batch scan enqueues via new CreateBatch and reuse folder list across path resolves
2026-05-25 11:39:35 -04:00
Silo Server Migration 03f97e63d6 docs: document jellyfin autoscan setup 2026-05-25 11:18:02 -04:00
Silo Server Migration f73febbd55 feat(jellycompat): add autoscan media update route 2026-05-25 11:18:02 -04:00
Silo Server Migration 6ceb75e717 feat(jellycompat): accept admin api keys for autoscan 2026-05-25 11:18:02 -04:00
Silo Server Migration bf0aefa050 refactor(api): share scan target resolution 2026-05-25 11:18:02 -04:00
Silo Server Migration 4f46580210 refactor(scan): extract scan trigger resolver 2026-05-25 11:18:02 -04:00
Silo Server Migration 77510cd12a docs: design jellyfin autoscan scan compatibility 2026-05-25 11:18:02 -04:00
QuickandGitHub a75d232ceb Merge pull request #7 from Silo-Server/t3code/discover-studios-networks-genres-clean
feat(requests): add media request system
2026-05-25 10:56:57 -04:00
Silo Server Migration c157be571f feat(requests): tighten browse grid with more columns
- Increase poster density across breakpoints and use fluid cards
2026-05-25 10:55:33 -04:00
Silo Server Migration 1ea3e078d3 feat(requests): sync search and tab state with URL params
- Persist tab, query, media type, and page in the URL so requests views are shareable and survive reloads
- Rename the "mine" tab to "yours" with backward-compatible normalization
- Require at least 2 characters before submitting a search
2026-05-25 10:48:48 -04:00
Silo Server Migration 7a9d7bcc94 fix(requests): clear prior failed rows on re-request
{"subject":"fix(requests): clear prior failed rows on re-request","body":""}
2026-05-25 10:02:06 -04:00
Silo Server Migration b5d9243942 fix(requests/radarr): decode tmdb lookup as single object
- Radarr's /api/v3/movie/lookup/tmdb returns a single MovieResource, not an array
- Update test fixtures to match the actual response shape
2026-05-25 02:22:14 -04:00
Silo Server Migration ee8aefb311 fix(web): remove padding on request detail routes 2026-05-25 02:09:28 -04:00
Silo Server Migration cc09e14154 feat(tmdb): cache discover and external ID responses
- Add TTL caches with singleflight for DiscoverSection, DiscoverPage, and GetExternalIDs to reduce upstream TMDB calls
- Reuse CastCarousel with a fullBleed variant on RequestDetail and drop the bespoke RequestCastRow
2026-05-25 02:02:33 -04:00
Silo Server Migration 9cf5062bf0 chore: restore tmdb project api key fallback and move requests nav
- tmdb client falls back to bundled project API key when none configured
- move Requests link next to Recommendations in the sidebar
2026-05-25 01:12:47 -04:00
Silo Server MigrationandClaude Opus 4.7 8a86e0cf08 refactor: tmdb and requests polish
- GetExternalIDs now uses the dedicated /movie/{id}/external_ids and
  /tv/{id}/external_ids endpoints instead of fetching the full detail
  with append_to_response=external_ids. The dedicated payload is
  one or two orders of magnitude smaller for the same fields.
- Document PosterPath/BackdropPath on MediaResult as raw TMDB path
  fragments that callers must prefix with the image base URL.
- normalizeCast switches from inline insertion sort to sort.SliceStable.
  The output is identical; the new form is one line and O(n log n).
- normalizeIntegration no longer reuses integration.Tags' backing
  array via Tags[:0]; the slice is callable code, so reusing the
  array would silently corrupt the caller's slice if it kept a
  reference. Allocate a fresh slice instead.
- HandleGet now requires a profile, matching the rest of the
  /requests user-group handlers. Router middleware enforces this
  already, but the inline check is defense-in-depth for any future
  remount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:27:39 -04:00
Silo Server MigrationandClaude Opus 4.7 e8143d5757 chore: tighten local-path-leak guard scope and tooling
- Narrow the worktree-id/T3-path check to docs/superpowers/specs and
  docs/superpowers/plans. Scanning the whole repo flagged any
  legitimate reference to .t3/worktrees or t3code-<hex> (fixtures,
  example configs) and would block unrelated commits.
- Switch the pre-commit hook shebang to bash and call the check via
  an absolute path derived from git rev-parse so the hook works
  regardless of the cwd git happens to invoke it from.
- install-hooks now warns when it overwrites an existing
  core.hooksPath rather than silently clobbering a custom setup.
- Add make verify-local-paths to the AGENTS.md pre-MR checklist so
  contributors run it explicitly even when --no-verify is used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:27:29 -04:00
Silo Server MigrationandClaude Opus 4.7 64765126b8 feat(web): replace decline prompt with dialog and polish list query
window.prompt for the admin decline reason was inaccessible (screen
reader announcements are inconsistent), unstylable, and blocked in
hardened environments. Replace with a controlled Dialog and a small
textarea so the decline UX matches the rest of the admin pages.

buildListQuery used a truthiness check on params.offset/params.limit
that silently dropped explicit zero values, leaving the URL out of
sync with the query key. Compare against null and positive numbers
instead.

Poster <img> elements had alt="" with no surrounding aria-label, so
screen readers reaching the cards via image-mode browsing got no
title. Use the poster's own title as the alt text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:27:21 -04:00
Silo Server MigrationandClaude Opus 4.7 9250661aaa fix(tmdb): require an api key instead of falling back to a default
The client previously fell back to a hardcoded project-level API key
when the caller passed an empty string. Once committed the key cannot
be rotated without a rebuild, and the silent fallback meant that
misconfiguration produced unattributed traffic against a shared key.
Remove the default. Clients constructed with an empty key now return
ErrMissingAPIKey from every API call so operators see the
configuration problem immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:56 -04:00
Silo Server MigrationandClaude Opus 4.7 34f92fdc1d refactor(requests): consolidate shared Arr client helpers
The radarr and sonarr clients carried byte-identical copies of
rootFolderResource, qualityProfileResource, tagResource (and the
corresponding list helpers) plus acceptedWithoutResponse and
statusFromQueueEvaluation. Move the shared wire types and helpers
into the arrclient package and update the callers to use the
exported helpers. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:50 -04:00
Silo Server MigrationandClaude Opus 4.7 b382136e6d perf(requests): parallelize DiscoverAll across discovery sections
Each homepage discovery section fired a serial TMDB round trip with
its own presence lookup, so the response time grew linearly with the
number of sections (~1.2 s at 6 sections * 200 ms). Fan the calls out
across a bounded errgroup using the same concurrency cap as
external-id hydration. The first section to error cancels the rest
via the group context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:21:44 -04:00
Silo Server MigrationandClaude Opus 4.7 ab520d2743 fix(metadata): skip refresh-failure mask when provider id is missing
syncRefreshDebtFailure unconditionally set RefreshDebtReasonRefreshFailure
on matched items, even when the refresh failed because the item is
missing a provider id. The priority logic already preferred
ProviderIDIncomplete (priority 240) over RefreshFailure (priority
190), so scheduling was correct — but the persisted reason mask
combined both bits and the reason-count metric reported these items
as "refresh failures" instead of "provider id incomplete". Guard the
OR so the failure bit only lands on items that actually have a
provider id and failed for a different reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:16:48 -04:00
Silo Server MigrationandClaude Opus 4.7 38868b2128 feat(requests): add cancel endpoint and tighten state guards
Owners can now POST /requests/{id}/cancel to withdraw a pending
request; admins can cancel any active request that has not entered
the fulfillment pipeline. The route is mounted on both the user
group (with profile required) and the admin group. The cancelled
outcome was already reserved in the migration's CHECK constraint
but was unreachable from any handler.

Decline now also rejects approved requests — between Approve setting
StatusApproved and the reconciler picking the request up, an admin
could declare the request declined while submission was about to
fire. The reconciler's outcome filter would skip the request, but
the narrow window meant external state could diverge from Silo's
view. Refuse decline once a request is approved; callers should
wait for completion or use the failed/retry path.

Reconcile now emits a slog.WarnContext at the per-request failure
site with request id, media type, tmdb id, status, and integration
kind. Aggregated counters in ReconcileResult are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:16:41 -04:00
Silo Server MigrationandClaude Opus 4.7 860978cf2f fix(requests): recover external id after empty arr add response
Radarr and Sonarr can return HTTP 201 with no body when a movie or
series is added. The previous code returned an "accepted_without_response"
result with an empty ExternalID, which trapped the reconciler: every
subsequent CheckStatus call short-circuited on the empty ID and the
request never advanced past queued.

When the add POST decodes empty, look the freshly-added record up by
TMDB or TVDB ID via the standard list endpoints and use the resulting
Arr ID. Fall back to the previous accepted-without-response result
only when the lookup also returns no match, preserving the original
behavior as a safety net.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:12:53 -04:00
Silo Server MigrationandClaude Opus 4.7 fca23d77e9 fix(requests): serialize quota check and clamp list limits
CreateRequest previously read the user's request count outside the
insert transaction, so two concurrent submissions at MaxRequests-1
could both pass the quota gate and end up at MaxRequests+1. Move the
count inside the same transaction as the insert and acquire a per-user
advisory lock so concurrent inserts serialize. The store reports
ErrQuotaExceeded when the racing path catches the user at the limit
and the service maps it back to QuotaError.

normalizeListFilter previously reset limit to 50 when callers asked
for more than 100, which is surprising. Clamp to the cap instead so a
request for 150 returns 100 and a request for 1_000_000 still cannot
hit the database with an unbounded scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:12:45 -04:00
Silo Server Migration 4e99df18eb fix(metadata): preserve refresh debt reason masks 2026-05-24 23:11:44 -04:00
Silo Server Migration dbb33c630d fix(metadata): refresh items missing tmdb ids 2026-05-24 23:08:05 -04:00
Silo Server Migration 8a8ed6998d fix(requests): bound external id hydration 2026-05-24 23:04:04 -04:00
Silo Server Migration e470760ab4 fix(requests): hydrate external ids before availability 2026-05-24 22:57:44 -04:00
Silo Server Migration 703797e0bb fix(requests): ignore nil presence repositories 2026-05-24 22:52:56 -04:00
Silo Server Migration 8b76dc0bf2 fix(requests): ignore nil presence backfill repo 2026-05-24 22:50:06 -04:00