chore: extract shared tsconfig, move display-status to core, add i18n tests

- Add `@sofa/tsconfig` package with a shared `base.json` (target, lib, strict, moduleResolution, etc.) and migrate all packages/apps to `"extends": "@sofa/tsconfig/base.json"` to eliminate duplicated compiler options
- Move `display-status.ts` (and its `DisplayStatus` type + `getDisplayStatus` fn) from `@sofa/api` to `@sofa/core`; remove the `./display-status` export from `@sofa/api` and update all import sites in `discovery.ts` and `tracking.ts`
- Add `packages/core/test/display-status.test.ts`: 15 tests covering `ONGOING_TMDB_STATUSES` and all `getDisplayStatus` branches (movie, TV watchlist/in_progress/caught_up/completed, edge cases)
- Add `packages/i18n/vitest.config.ts` and two new test files: `date-buckets.test.ts` and `format.test.ts` covering date bucketing and locale formatting utilities
This commit is contained in:
2026-03-22 14:46:25 -04:00
parent 6be586bd53
commit 1d67b201a0
34 changed files with 508 additions and 180 deletions
+1 -1
View File
@@ -1,4 +1,3 @@
import type { DisplayStatus } from "@sofa/api/display-status";
import {
getAllTrackedTitleIds,
getAvailabilityByTitleIds,
@@ -25,6 +24,7 @@ import {
} from "@sofa/db/queries/discovery";
import { tmdbImageUrl } from "@sofa/tmdb/image";
import type { DisplayStatus } from "./display-status";
import { getDisplayStatusesByTitleIds } from "./tracking";
function formatLocalDate(d: Date): string {
+32
View File
@@ -0,0 +1,32 @@
export type StoredStatus = "watchlist" | "in_progress" | "completed";
export type DisplayStatus = "in_watchlist" | "watching" | "caught_up" | "completed";
export const ONGOING_TMDB_STATUSES = ["Returning Series", "In Production"];
/**
* Derive the user-facing display status from stored status + context.
*
* Movies: watchlist → in_watchlist, completed → completed
* TV: watchlist → in_watchlist, in_progress → watching | caught_up | completed
*/
export function getDisplayStatus(
storedStatus: StoredStatus,
titleType: "movie" | "tv",
tmdbStatus: string | null,
episodeProgress: { watched: number; total: number } | null,
): DisplayStatus {
if (storedStatus === "watchlist") return "in_watchlist";
if (titleType === "movie") return storedStatus === "completed" ? "completed" : "in_watchlist";
// TV with in_progress: derive from episode progress + TMDB show status
if (
episodeProgress &&
episodeProgress.total > 0 &&
episodeProgress.watched >= episodeProgress.total
) {
return ONGOING_TMDB_STATUSES.includes(tmdbStatus ?? "") ? "caught_up" : "completed";
}
return "watching";
}
+3 -2
View File
@@ -1,5 +1,3 @@
import type { DisplayStatus } from "@sofa/api/display-status";
import { getDisplayStatus } from "@sofa/api/display-status";
import { getTitlesByIds } from "@sofa/db/queries/discovery";
import { getTitleById } from "@sofa/db/queries/title";
import {
@@ -25,6 +23,9 @@ import {
upsertTitleStatus,
} from "@sofa/db/queries/tracking";
import type { DisplayStatus } from "./display-status";
import { getDisplayStatus } from "./display-status";
export function setTitleStatus(
userId: string,
titleId: string,