From 095f66affd7206b6a6eff0009f4c66cc90be485d Mon Sep 17 00:00:00 2001 From: MoldyTaint Date: Tue, 23 Jun 2026 03:01:10 +0000 Subject: [PATCH 001/320] fix(release): show old theatrical-only titles as Released after 3 years Movies with a past theatrical date but no digital/physical/TV date were labeled 'In Theaters' indefinitely (e.g. The Watch 2012), since TMDB never recorded granular home-media dates for many catalog titles. Add a 3-year fallback (THEATRICAL_ONLY_TO_RELEASED_DAYS) so they resolve to 'Released'. --- messages/de.json | 1 + messages/en.json | 1 + messages/es.json | 1 + src/lib/config/constants.ts | 7 +++++++ src/lib/utils/movieAvailability.ts | 1 + src/lib/utils/releaseLineText.ts | 2 ++ src/lib/utils/releaseStage.ts | 15 +++++++++++++-- src/lib/utils/smartReleaseLine.test.ts | 15 ++++++++++++++- src/lib/utils/smartReleaseLine.ts | 3 +++ 9 files changed, 43 insertions(+), 3 deletions(-) diff --git a/messages/de.json b/messages/de.json index 1d0f595f..d65dd6b0 100644 --- a/messages/de.json +++ b/messages/de.json @@ -342,6 +342,7 @@ "common_unmonitored": "Nicht überwacht", "common_unreleased": "Noch nicht veröffentlicht", "common_inTheaters": "Im Kino", + "common_released": "Veröffentlicht", "common_availableDigital": "Verfügbar - Digital", "common_availablePhysical": "Verfügbar - Physisch", "common_availableStreaming": "Verfügbar - Streaming", diff --git a/messages/en.json b/messages/en.json index 61767874..5f8b4bb5 100644 --- a/messages/en.json +++ b/messages/en.json @@ -342,6 +342,7 @@ "common_unmonitored": "Unmonitored", "common_unreleased": "Unreleased", "common_inTheaters": "In Theaters", + "common_released": "Released", "common_availableDigital": "Available - Digital", "common_availablePhysical": "Available - Physical", "common_availableStreaming": "Available - Streaming", diff --git a/messages/es.json b/messages/es.json index 47232876..f5276292 100644 --- a/messages/es.json +++ b/messages/es.json @@ -342,6 +342,7 @@ "common_unmonitored": "No monitoreado", "common_unreleased": "No lanzado", "common_inTheaters": "En cines", + "common_released": "Estrenada", "common_availableDigital": "Disponible - Digital", "common_availablePhysical": "Disponible - Físico", "common_availableStreaming": "Disponible - Streaming", diff --git a/src/lib/config/constants.ts b/src/lib/config/constants.ts index 0f4e439a..0ac8b1d0 100644 --- a/src/lib/config/constants.ts +++ b/src/lib/config/constants.ts @@ -21,6 +21,13 @@ export const RELEASE = { /** Days after theatrical release to treat a movie as "released" when no home-media date is known. */ THEATRICAL_TO_RELEASED_DAYS: 90, + /** + * Days after a theatrical-only release (no digital/physical/TV date ever recorded) + * after which the title is presumed generally released rather than still "In Theaters". + * ~3 years — covers older catalog titles TMDB never gave granular home dates. + */ + THEATRICAL_ONLY_TO_RELEASED_DAYS: 1095, + /** Current-year in-library age (days) after which a movie is presumed released (legacy heuristic). */ CURRENT_YEAR_LIBRARY_DAYS: 120 } as const; diff --git a/src/lib/utils/movieAvailability.ts b/src/lib/utils/movieAvailability.ts index 0dc870b5..707d8cfe 100644 --- a/src/lib/utils/movieAvailability.ts +++ b/src/lib/utils/movieAvailability.ts @@ -31,6 +31,7 @@ function stageToLevel(stage: ReleaseStage): MovieAvailabilityLevel { case 'availableDigital': case 'availablePhysical': case 'availableStreaming': + case 'released': return 'released'; case 'digitalUpcoming': case 'physicalUpcoming': diff --git a/src/lib/utils/releaseLineText.ts b/src/lib/utils/releaseLineText.ts index 3e20c930..e9801a77 100644 --- a/src/lib/utils/releaseLineText.ts +++ b/src/lib/utils/releaseLineText.ts @@ -12,6 +12,8 @@ export function formatReleaseLine(result: SmartReleaseLineResult): string { switch (result.key) { case 'inTheaters': return m.common_inTheaters(); + case 'released': + return m.common_released(); case 'comingToTheaters': return m.common_comingToTheaters({ days }); case 'availableDigital': diff --git a/src/lib/utils/releaseStage.ts b/src/lib/utils/releaseStage.ts index a3c9d0a2..fd06af45 100644 --- a/src/lib/utils/releaseStage.ts +++ b/src/lib/utils/releaseStage.ts @@ -1,3 +1,7 @@ +import { RELEASE } from '$lib/config/constants.js'; + +const DAY_MS = 24 * 60 * 60 * 1000; + export type ReleaseStageKind = | 'availableDigital' | 'availablePhysical' @@ -6,6 +10,7 @@ export type ReleaseStageKind = | 'physicalUpcoming' | 'streamingUpcoming' | 'inTheaters' + | 'released' | 'comingToTheaters' | 'announced'; @@ -62,8 +67,10 @@ const UPCOMING_KIND: Record = { * theaters, which says nothing about home/digital availability — so a movie is * only "Available" when it has a past digital, physical, or TV/streaming * release date. A movie whose theatrical date is past but has no home-release - * date is "In Theaters" (real catalog titles carry their own past home dates - * and resolve to "Available" on their own). + * date is "In Theaters" — until enough time has passed + * ({@link RELEASE.THEATRICAL_ONLY_TO_RELEASED_DAYS}, ~3 years), after which it + * is presumed generally "Released" (covers older catalog titles TMDB never gave + * granular digital/physical dates). * * Both the display layer (`getSmartReleaseLine`) and the availability layer * (`getMovieAvailabilityLevel`) derive from this so they can never contradict. @@ -90,6 +97,10 @@ export function resolveReleaseStage( if (next) { return { kind: UPCOMING_KIND[next.type], days: daysUntil(next.ms, nowMs) }; } + const ageDays = isValid(theatricalMs) ? (nowMs - theatricalMs) / DAY_MS : 0; + if (ageDays > RELEASE.THEATRICAL_ONLY_TO_RELEASED_DAYS) { + return { kind: 'released' }; + } return { kind: 'inTheaters' }; } diff --git a/src/lib/utils/smartReleaseLine.test.ts b/src/lib/utils/smartReleaseLine.test.ts index 9013ad79..a7de7923 100644 --- a/src/lib/utils/smartReleaseLine.test.ts +++ b/src/lib/utils/smartReleaseLine.test.ts @@ -61,7 +61,7 @@ describe('getSmartReleaseLine', () => { expect(result).toEqual({ key: 'inTheaters', variant: 'theaters' }); }); - it('ignores TMDB status: past theatrical with no home date is always In Theaters', () => { + it('returns released for an old theatrical-only title (past the 3-year fallback)', () => { const result = getSmartReleaseLine( { releaseDate: '1957-04-10', @@ -71,6 +71,19 @@ describe('getSmartReleaseLine', () => { }, now ); + expect(result).toEqual({ key: 'released', variant: 'released' }); + }); + + it('keeps inTheaters for a recent theatrical-only title (within the 3-year window)', () => { + const result = getSmartReleaseLine( + { + releaseDate: '2025-01-01', + digitalReleaseDate: null, + physicalReleaseDate: null, + status: 'Released' + }, + now + ); expect(result).toEqual({ key: 'inTheaters', variant: 'theaters' }); }); diff --git a/src/lib/utils/smartReleaseLine.ts b/src/lib/utils/smartReleaseLine.ts index a04c8ede..d8c31808 100644 --- a/src/lib/utils/smartReleaseLine.ts +++ b/src/lib/utils/smartReleaseLine.ts @@ -4,6 +4,7 @@ export type ReleaseLineVariant = 'released' | 'upcoming' | 'theaters' | 'announc export type ReleaseLineKey = | 'inTheaters' + | 'released' | 'comingToTheaters' | 'availableDigital' | 'availablePhysical' @@ -64,6 +65,8 @@ export function getSmartReleaseLine( return { key: 'streamingInDays', params: { days: stage.days ?? 0 }, variant: 'upcoming' }; case 'inTheaters': return { key: 'inTheaters', variant: 'theaters' }; + case 'released': + return { key: 'released', variant: 'released' }; case 'comingToTheaters': return { key: 'comingToTheaters', params: { days: stage.days ?? 0 }, variant: 'upcoming' }; case 'announced': From a48c7e89e0b7d0f2055b789cc76afddd1d854023 Mon Sep 17 00:00:00 2001 From: Keonramses Date: Mon, 22 Jun 2026 00:17:24 +0000 Subject: [PATCH 002/320] feat(ui): add logo, version, and Discord/GitHub links to login and setup screens --- src/routes/login/+page.server.ts | 5 +++ src/routes/login/+page.svelte | 47 +++++++++++++++++++++++++++ src/routes/setup/+page.server.ts | 5 +++ src/routes/setup/+page.svelte | 56 +++++++++++++++++++++++++++++--- 4 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 src/routes/login/+page.server.ts create mode 100644 src/routes/setup/+page.server.ts diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts new file mode 100644 index 00000000..f98cb6d7 --- /dev/null +++ b/src/routes/login/+page.server.ts @@ -0,0 +1,5 @@ +import { resolveAppVersion } from '$lib/server/version.js'; + +export function load() { + return { version: resolveAppVersion() }; +} diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 737f817b..b58d3e0f 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -4,6 +4,11 @@ import { authClient } from '$lib/auth/client.js'; import * as m from '$lib/paraglide/messages.js'; + const GITHUB_URL = 'https://github.com/MoldyTaint/Cinephage'; + const DISCORD_URL = 'https://discord.gg/scGCBTSWEt'; + + let { data } = $props(); + let username = $state(''); let password = $state(''); let rememberMe = $state(false); @@ -136,6 +141,48 @@ {/if} + + diff --git a/src/routes/setup/+page.server.ts b/src/routes/setup/+page.server.ts new file mode 100644 index 00000000..f98cb6d7 --- /dev/null +++ b/src/routes/setup/+page.server.ts @@ -0,0 +1,5 @@ +import { resolveAppVersion } from '$lib/server/version.js'; + +export function load() { + return { version: resolveAppVersion() }; +} diff --git a/src/routes/setup/+page.svelte b/src/routes/setup/+page.svelte index 741f50a5..fe67b432 100644 --- a/src/routes/setup/+page.svelte +++ b/src/routes/setup/+page.svelte @@ -1,8 +1,13 @@ -
- -
- {#if movie.backdropPath} - - {/if} -
-
+
+ +
+ + + + +
+ + + + {#if onImport} + + {/if} + +
- -
- -
- -