From 3a0374c825d65acf49890cdb5d61ce546edaba96 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Wed, 18 Mar 2026 15:48:47 -0400 Subject: [PATCH] chore(lint): add perf category to oxlint and fix resulting warnings - Enable `perf: warn` in `.oxlintrc.json` and add `builtin` env; disable `no-await-in-loop` and `react/no-array-index-key` globally - Reorder plugins so `oxc` is first - Add `.oxfmtrc.json` override to suppress trailing commas in JSON/JSONC files - Replace spread-into-new-object patterns (`{ ...x, key: val }`) with `Object.assign(x, { key: val })` in discover, explore, integrations, search, credits, and image-cache to satisfy perf rules - Memoize `ChartContext` and `ToggleGroupContext` provider values to avoid unnecessary re-renders --- .oxfmtrc.json | 8 ++++++++ .oxlintrc.json | 12 +++++++++--- apps/server/src/orpc/procedures/discover.ts | 5 ++--- apps/server/src/orpc/procedures/explore.ts | 10 ++++------ apps/server/src/orpc/procedures/integrations.ts | 5 ++--- apps/server/src/orpc/procedures/search.ts | 9 +++------ apps/web/src/components/ui/chart.tsx | 3 ++- apps/web/src/components/ui/toggle-group.tsx | 8 +++++--- packages/core/src/credits.ts | 7 +++---- packages/core/src/image-cache.ts | 9 +++++---- 10 files changed, 43 insertions(+), 33 deletions(-) diff --git a/.oxfmtrc.json b/.oxfmtrc.json index b1435fd..b27f0e9 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -38,6 +38,14 @@ ], "functions": ["cn", "clsx", "cva", "useResolveClassNames"] }, + "overrides": [ + { + "files": ["**/*.json", "**/*.jsonc"], + "options": { + "trailingComma": "none" + } + } + ], "ignorePatterns": [ "node_modules", "dist", diff --git a/.oxlintrc.json b/.oxlintrc.json index 01a914b..286f2f3 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,9 +1,13 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "import"], + "plugins": ["oxc", "eslint", "typescript", "react", "import", "unicorn"], + "env": { + "builtin": true + }, "categories": { "correctness": "error", - "suspicious": "warn" + "suspicious": "warn", + "perf": "warn" }, "rules": { "react/react-in-jsx-scope": "off", @@ -12,7 +16,9 @@ "import/no-named-as-default-member": "off", "unicorn/no-array-sort": "off", "unicorn/consistent-function-scoping": "off", - "no-new": "off" + "no-new": "off", + "no-await-in-loop": "off", + "react/no-array-index-key": "off" }, "ignorePatterns": ["node_modules", "dist", "build", "docs", "**/routeTree.gen.ts"] } diff --git a/apps/server/src/orpc/procedures/discover.ts b/apps/server/src/orpc/procedures/discover.ts index 46bb420..6af6d74 100644 --- a/apps/server/src/orpc/procedures/discover.ts +++ b/apps/server/src/orpc/procedures/discover.ts @@ -50,11 +50,10 @@ export const discover = os.discover.use(authed).handler(async ({ input, context const titleMap = ensureBrowseTitlesExist(baseItems); const items = baseItems.map((item) => { const entry = titleMap.get(`${item.tmdbId}-${item.type}`); - return { - ...item, + return Object.assign(item, { id: entry?.id ?? "", posterThumbHash: entry?.posterThumbHash ?? null, - }; + }); }); const titleIds = items.map((r) => r.id); diff --git a/apps/server/src/orpc/procedures/explore.ts b/apps/server/src/orpc/procedures/explore.ts index 74fce68..baf8002 100644 --- a/apps/server/src/orpc/procedures/explore.ts +++ b/apps/server/src/orpc/procedures/explore.ts @@ -64,11 +64,10 @@ export const trending = os.explore.trending.use(authed).handler(async ({ input, const items = baseItems.map((item) => { const entry = titleMap.get(`${item.tmdbId}-${item.type}`); - return { - ...item, + return Object.assign(item, { id: entry?.id ?? "", posterThumbHash: entry?.posterThumbHash ?? null, - }; + }); }); const heroEntry = heroResult @@ -125,11 +124,10 @@ export const popular = os.explore.popular.use(authed).handler(async ({ input, co const titleMap = ensureBrowseTitlesExist(baseItems); const items = baseItems.map((item) => { const entry = titleMap.get(`${item.tmdbId}-${item.type}`); - return { - ...item, + return Object.assign(item, { id: entry?.id ?? "", posterThumbHash: entry?.posterThumbHash ?? null, - }; + }); }); const titleIds = items.map((r) => r.id); diff --git a/apps/server/src/orpc/procedures/integrations.ts b/apps/server/src/orpc/procedures/integrations.ts index a59ef50..52ba8d2 100644 --- a/apps/server/src/orpc/procedures/integrations.ts +++ b/apps/server/src/orpc/procedures/integrations.ts @@ -55,8 +55,7 @@ export const list = os.integrations.list.use(authed).handler(({ context }) => { const result = userIntegrations.map((integration) => { const events = eventsByIntegration.get(integration.id) ?? []; - return { - ...serializeIntegration(integration), + return Object.assign(serializeIntegration(integration), { recentEvents: events.map((e) => ({ id: e.id, eventType: e.eventType, @@ -65,7 +64,7 @@ export const list = os.integrations.list.use(authed).handler(({ context }) => { status: e.status, receivedAt: e.receivedAt.toISOString(), })), - }; + }); }); return { integrations: result }; diff --git a/apps/server/src/orpc/procedures/search.ts b/apps/server/src/orpc/procedures/search.ts index 978566c..a43a483 100644 --- a/apps/server/src/orpc/procedures/search.ts +++ b/apps/server/src/orpc/procedures/search.ts @@ -53,10 +53,7 @@ export const search = os.search.use(authed).handler(async ({ input }) => { })), ); return { - results: personItems.map((r) => ({ - ...r, - id: personMap.get(r.tmdbId), - })), + results: personItems.map((r) => Object.assign(r, { id: personMap.get(r.tmdbId) })), page: personResults.page ?? input.page, totalPages: personResults.total_pages ?? 1, totalResults: personResults.total_results ?? 0, @@ -140,9 +137,9 @@ export const search = os.search.use(authed).handler(async ({ input }) => { ); const results = mapped.map((r) => { - if (r.type === "person") return { ...r, id: personMap.get(r.tmdbId) }; + if (r.type === "person") return Object.assign(r, { id: personMap.get(r.tmdbId) }); const entry = titleMap.get(`${r.tmdbId}-${r.type}`); - return { ...r, id: entry?.id }; + return Object.assign(r, { id: entry?.id }); }); return { diff --git a/apps/web/src/components/ui/chart.tsx b/apps/web/src/components/ui/chart.tsx index 565d94f..6d49f2f 100644 --- a/apps/web/src/components/ui/chart.tsx +++ b/apps/web/src/components/ui/chart.tsx @@ -44,9 +44,10 @@ function ChartContainer({ }) { const uniqueId = React.useId(); const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + const contextValue = React.useMemo(() => ({ config }), [config]); return ( - +
({ variant, size, spacing, orientation }), + [variant, size, spacing, orientation], + ); return ( - - {children} - + {children} ); } diff --git a/packages/core/src/credits.ts b/packages/core/src/credits.ts index 9761492..259a90a 100644 --- a/packages/core/src/credits.ts +++ b/packages/core/src/credits.ts @@ -361,8 +361,7 @@ export function getCastForTitle(titleId: string): CastMember[] { .orderBy(titleCast.displayOrder) .all(); - return rows.map((r) => ({ - ...r, - profilePath: tmdbImageUrl(r.profilePath, "profiles"), - })); + return rows.map((r) => + Object.assign(r, { profilePath: tmdbImageUrl(r.profilePath, "profiles") }), + ); } diff --git a/packages/core/src/image-cache.ts b/packages/core/src/image-cache.ts index 1494387..135a62e 100644 --- a/packages/core/src/image-cache.ts +++ b/packages/core/src/image-cache.ts @@ -146,10 +146,11 @@ export async function cacheImagesForTitle(titleId: string) { // Parallel cache checks instead of sequential awaits const checks = await Promise.all( - candidates.map(async (c) => ({ - ...c, - cached: await isImageCached(c.category, path.basename(c.imgPath)), - })), + candidates.map(async (c) => + Object.assign(c, { + cached: await isImageCached(c.category, path.basename(c.imgPath)), + }), + ), ); const tasks = checks .filter((c) => !c.cached)