Skip to content

Use openapi schemas directly#488

Open
valentin9 wants to merge 2 commits intomaxdorninger:masterfrom
valentin9:openapi-export-schema
Open

Use openapi schemas directly#488
valentin9 wants to merge 2 commits intomaxdorninger:masterfrom
valentin9:openapi-export-schema

Conversation

@valentin9
Copy link

@valentin9 valentin9 commented Mar 11, 2026

This PR adjusts the configuration to the openapi-typescript generation to export the schema types directly and allow importing them directly in the frontend code:
image

Alternatively without the --root-types-no-schema-prefix option, the schemas would all be prefixed with "Schema" like SchemaMetaDataProviderSearchResult which could be safer, if there are type collisions.

LMK what's preferred.


Checklist:

  • run npm run openapi to re-generate the api.d.ts
  • run npm run lint
  • run npm run format

Summary by CodeRabbit

  • Refactor
    • Simplified and standardised TypeScript typings across the web app by switching to concise public type aliases for API schemas (e.g. Movie, Show, Episode, User, Notification, etc.), improving code clarity and consistency across components and pages; purely type-level changes with no runtime behaviour impact.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4761fb73-567c-4e27-83b2-3b3c6d0be731

📥 Commits

Reviewing files that changed from the base of the PR and between e9bba10 and 2aeef58.

📒 Files selected for processing (1)
  • web/src/routes/dashboard/movies/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/routes/dashboard/movies/+page.svelte

📝 Walkthrough

Walkthrough

Updated OpenAPI TypeScript generation to emit root-level type aliases, then replaced nested components['schemas'] type references with those new explicit aliases across ~30 web files; changes are purely type-level with no runtime behaviour modifications.

Changes

Cohort / File(s) Summary
OpenAPI Configuration
web/package.json
Updated openapi script to use --root-types --root-types-no-schema-prefix when generating TypeScript types.
Generated API Types
web/src/lib/api/api.d.ts
Added ~43 root-level public type aliases (e.g., AuthMetadata, Movie, Show, UserRead, Notification, etc.) re-exporting schema definitions.
Component & UI Type Imports
web/src/lib/components/..., web/src/routes/...
Replaced nested components['schemas'][...] references with explicit imports of new aliases (e.g., MetaDataProviderSearchResult, PublicMovie, PublicShow, LibraryItem, MediaImportSuggestion, Notification, UserRead) in props, variables and context types across many Svelte components and route pages.
Download / Torrent Related Components
web/src/lib/components/download-dialogs/*, web/src/lib/components/torrents/*
Switched prop and local variable typings to explicit types (Show, Movie, Season, MovieTorrent, RichSeasonTorrent) and updated function signatures/casts accordingly.
Utilities & Misc
web/src/lib/utils.ts, web/src/routes/login/+page.svelte
Updated function signature and derived variable types to use explicit aliases (`Show

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through schemas, neat and bright,
Turned nested types into aliases light.
Imports trimmed and tidy, spry—
No runtime hops, just clearer sky.
The rabbit nods: "Nice types, good night!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Use openapi schemas directly' is concise (28 characters, well under 50), descriptive, and accurately summarises the main change: updating OpenAPI configuration to export schema types directly for simpler imports.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/src/lib/components/import-media/import-candidates-dialog.svelte (1)

26-80: ⚠️ Potential issue | 🟠 Major

Abort the import flow when the create step does not return an id.

If the first POST fails, or comes back without data.id, the code still calls the import endpoint with 'no_id'. That turns one backend failure into a second, misleading one and hides the real cause. Bail out immediately after the create call when no id is available.

Proposed fix
 async function handleImportMedia(media: MetaDataProviderSearchResult) {
 	isImporting = true;
 	submitRequestError = null;
 	let errored = null;
 	if (isTv) {
-		let { data } = await client.POST('/api/v1/tv/shows', {
+		const { data, error } = await client.POST('/api/v1/tv/shows', {
 			params: {
 				query: {
 					metadata_provider: media.metadata_provider as 'tmdb' | 'tvdb',
 					show_id: media.external_id
 				}
 			}
 		});
-		let showId = data?.id ?? 'no_id';
-		const { error } = await client.POST('/api/v1/tv/importable/{show_id}', {
+		if (error || !data?.id) {
+			submitRequestError = 'Failed to create show before import.';
+			isImporting = false;
+			toast.error(submitRequestError);
+			return;
+		}
+		const showId = data.id;
+		const { error: importError } = await client.POST('/api/v1/tv/importable/{show_id}', {
 			params: {
 				path: {
 					show_id: showId
 				},
 				query: {
 					directory: name
 				}
 			}
 		});
-		errored = error;
+		errored = importError;
 	} else {
-		let { data } = await client.POST('/api/v1/movies', {
+		const { data, error } = await client.POST('/api/v1/movies', {
 			params: {
 				query: {
 					metadata_provider: media.metadata_provider as 'tmdb' | 'tvdb',
 					movie_id: media.external_id
 				}
 			}
 		});
-		let movieId = data?.id ?? 'no_id';
-		const { error } = await client.POST('/api/v1/movies/importable/{movie_id}', {
+		if (error || !data?.id) {
+			submitRequestError = 'Failed to create movie before import.';
+			isImporting = false;
+			toast.error(submitRequestError);
+			return;
+		}
+		const movieId = data.id;
+		const { error: importError } = await client.POST('/api/v1/movies/importable/{movie_id}', {
 			params: {
 				path: {
 					movie_id: movieId
 				},
 				query: {
 					directory: name
 				}
 			}
 		});
-		errored = error;
+		errored = importError;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/components/import-media/import-candidates-dialog.svelte` around
lines 26 - 80, The create step can return no id which leads to calling the
import endpoint with 'no_id'; update handleImportMedia to detect when the first
POST result has no data.id (check data?.id) after the client.POST to
'/api/v1/tv/shows' and '/api/v1/movies', set isImporting = false, set
submitRequestError (or errored) to a meaningful error, show a toast.error and
return early so the subsequent client.POST to '/api/v1/tv/importable/{show_id}'
or '/api/v1/movies/importable/{movie_id}' is not invoked with the placeholder
'no_id'. Ensure you reference the created id via showId/movieId only when
present and do not proceed if missing.
web/src/lib/components/library-combobox.svelte (1)

34-45: ⚠️ Potential issue | 🟠 Major

Don't cast optional API payloads straight into libraries.

client.GET(...) can leave .data undefined on failure, and these casts hide that. If either request comes back without data, the later libraries.length / libraries.push(...) path throws during onMount, so the combobox breaks instead of degrading cleanly.

💡 Suggested fix
 		if (mediaType === 'tv') {
-			libraries = tvLibraries.data as LibraryItem[];
+			libraries = tvLibraries.data ?? [];
 		} else {
-			libraries = movieLibraries.data as LibraryItem[];
+			libraries = movieLibraries.data ?? [];
 		}
 
 		if (!value && libraries.length > 0) {
 			value = 'Default';
 		}
-		libraries.push({
-			name: 'Default',
-			path: 'Default'
-		} as LibraryItem);
+		if (!libraries.some((item) => item.name === 'Default')) {
+			libraries = [...libraries, { name: 'Default', path: 'Default' }];
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/components/library-combobox.svelte` around lines 34 - 45, The
code currently casts tvLibraries.data/movieLibraries.data directly to
LibraryItem[] which can be undefined and causes runtime errors; update the
assignment to safely handle missing payloads by using a nullish fallback (e.g.,
set libraries = (tvLibraries.data ?? []) or (movieLibraries.data ?? [])), ensure
libraries is an array before checking length or pushing, and append the Default
LibraryItem using an array-safe operation so the onMount path never assumes
.data exists (references: tvLibraries, movieLibraries, libraries, value,
LibraryItem).
🧹 Nitpick comments (4)
web/package.json (1)

15-15: Keep the schema prefix unless you have a collision policy.

--root-types-no-schema-prefix removes the default Schema prefix from root-level type aliases (so User instead of SchemaUser). While this improves import ergonomics, it removes the built-in mitigation against naming collisions. When duplicate aliases occur, the tool only deconflicts within generated root aliases themselves (using _2, _3 suffixes), but there is no collision-resolution between schema-derived aliases and other non-alias root exports like ApiPaths, paths, components, or operations. As your OpenAPI specification grows, this increases the risk of accidental collisions. Keep the default prefix and only flatten names deliberately via manual re-exports if you want a stable public API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/package.json` at line 15, The OpenAPI generation script removes the
Schema prefix via the --root-types-no-schema-prefix flag which risks naming
collisions; update the "openapi" npm script (the openapi script entry in
package.json) to omit --root-types-no-schema-prefix so generated root aliases
keep the Schema prefix (e.g., SchemaUser), and if you need flattened names
expose them via deliberate manual re-exports instead of changing the generator
flag.
web/src/routes/dashboard/+page.svelte (1)

10-10: Change import to use module path instead of declaration file.

Importing from '$lib/api/api.d.ts' is fragile—TypeScript will resolve '$lib/api/api' to the declaration file automatically, and Vite's module resolution doesn't include .d.ts in its default extensions. This can cause "works in editor but not in bundler" issues. Import from the module path instead, as the rest of the PR does.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/routes/dashboard/`+page.svelte at line 10, The import of
MetaDataProviderSearchResult should reference the module path instead of the
declaration file; update the import statement that currently points to
'$lib/api/api.d.ts' to use '$lib/api/api' so Vite can resolve it correctly and
avoid bundler-only failures—locate the import of MetaDataProviderSearchResult in
+page.svelte and replace the module specifier accordingly.
web/src/routes/dashboard/notifications/+page.svelte (1)

9-13: Alias the imported Notification type to avoid shadowing the Web Notifications API.

In a browser Svelte module, Notification is a global type from the Web Notifications API. Importing a schema type with the same name shadows that global within this module's type scope, making future platform references awkward and reducing clarity. Aliasing the import is a recommended practice when both types may coexist.

💡 Suggested change
-import type { Notification } from '$lib/api/api';
+import type { Notification as ApiNotification } from '$lib/api/api';
 
-let unreadNotifications: Notification[] = [];
-let readNotifications: Notification[] = [];
+let unreadNotifications: ApiNotification[] = [];
+let readNotifications: ApiNotification[] = [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/routes/dashboard/notifications/`+page.svelte around lines 9 - 13, The
imported type Notification conflicts with the browser's global Notification
type; update the import to alias the schema type (e.g., import type {
Notification as ApiNotification } from '$lib/api/api') and then update uses of
Notification in this module — such as the declarations unreadNotifications:
Notification[] and readNotifications: Notification[] — to use the aliased name
(ApiNotification[]) so the global Web Notification type is not shadowed.
web/src/lib/components/torrents/torrent-table.svelte (1)

94-97: Consider removing unnecessary non-null assertions.

According to the RichSeasonTorrent schema definition in api.d.ts, the seasons and episodes fields are required (not optional), so the non-null assertions (!) are unnecessary when casting to RichSeasonTorrent.

♻️ Proposed fix
 				<Table.Cell>
-					{convertTorrentSeasonRangeToIntegerRange((torrent as RichSeasonTorrent).seasons!)}
+					{convertTorrentSeasonRangeToIntegerRange((torrent as RichSeasonTorrent).seasons)}
 				</Table.Cell>
 				<Table.Cell>
-					{convertTorrentEpisodeRangeToIntegerRange((torrent as RichSeasonTorrent).episodes!)}
+					{convertTorrentEpisodeRangeToIntegerRange((torrent as RichSeasonTorrent).episodes)}
 				</Table.Cell>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/components/torrents/torrent-table.svelte` around lines 94 - 97,
The code casts torrent to RichSeasonTorrent and calls
convertTorrentSeasonRangeToIntegerRange and
convertTorrentEpisodeRangeToIntegerRange using non-null assertions on seasons
and episodes; remove the unnecessary `!` after `(torrent as
RichSeasonTorrent).seasons` and `(torrent as RichSeasonTorrent).episodes`
because RichSeasonTorrent defines those fields as required—update the two
Table.Cell expressions to pass the properties directly without `!`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/lib/components/user-data-table.svelte`:
- Around line 15-18: Currently the code assigns the live row object to
selectedUser and mutates it in the radio handlers, leaking unsaved changes into
the table; instead introduce a separate draft state (e.g., selectedUserDraft:
UserRead | null = $state(null)), clone the row into that draft when opening the
edit dialog (use structuredClone or {...user}), update only selectedUserDraft in
the radio handlers, and on successful PATCH apply the draft back to the real
object or update the users array (and clear the draft); on cancel simply discard
selectedUserDraft so sortedUsers/users remain unchanged.

In `@web/src/routes/dashboard/movies/`+page.svelte:
- Line 9: The import of MediaImportSuggestion uses an explicit .d.ts extension;
update the import to match the rest of the PR by removing the file extension
(i.e., import MediaImportSuggestion from '$lib/api/api' instead of
'$lib/api/api.d.ts') so resolution and style are consistent with other files
that reference the MediaImportSuggestion symbol.

---

Outside diff comments:
In `@web/src/lib/components/import-media/import-candidates-dialog.svelte`:
- Around line 26-80: The create step can return no id which leads to calling the
import endpoint with 'no_id'; update handleImportMedia to detect when the first
POST result has no data.id (check data?.id) after the client.POST to
'/api/v1/tv/shows' and '/api/v1/movies', set isImporting = false, set
submitRequestError (or errored) to a meaningful error, show a toast.error and
return early so the subsequent client.POST to '/api/v1/tv/importable/{show_id}'
or '/api/v1/movies/importable/{movie_id}' is not invoked with the placeholder
'no_id'. Ensure you reference the created id via showId/movieId only when
present and do not proceed if missing.

In `@web/src/lib/components/library-combobox.svelte`:
- Around line 34-45: The code currently casts
tvLibraries.data/movieLibraries.data directly to LibraryItem[] which can be
undefined and causes runtime errors; update the assignment to safely handle
missing payloads by using a nullish fallback (e.g., set libraries =
(tvLibraries.data ?? []) or (movieLibraries.data ?? [])), ensure libraries is an
array before checking length or pushing, and append the Default LibraryItem
using an array-safe operation so the onMount path never assumes .data exists
(references: tvLibraries, movieLibraries, libraries, value, LibraryItem).

---

Nitpick comments:
In `@web/package.json`:
- Line 15: The OpenAPI generation script removes the Schema prefix via the
--root-types-no-schema-prefix flag which risks naming collisions; update the
"openapi" npm script (the openapi script entry in package.json) to omit
--root-types-no-schema-prefix so generated root aliases keep the Schema prefix
(e.g., SchemaUser), and if you need flattened names expose them via deliberate
manual re-exports instead of changing the generator flag.

In `@web/src/lib/components/torrents/torrent-table.svelte`:
- Around line 94-97: The code casts torrent to RichSeasonTorrent and calls
convertTorrentSeasonRangeToIntegerRange and
convertTorrentEpisodeRangeToIntegerRange using non-null assertions on seasons
and episodes; remove the unnecessary `!` after `(torrent as
RichSeasonTorrent).seasons` and `(torrent as RichSeasonTorrent).episodes`
because RichSeasonTorrent defines those fields as required—update the two
Table.Cell expressions to pass the properties directly without `!`.

In `@web/src/routes/dashboard/`+page.svelte:
- Line 10: The import of MetaDataProviderSearchResult should reference the
module path instead of the declaration file; update the import statement that
currently points to '$lib/api/api.d.ts' to use '$lib/api/api' so Vite can
resolve it correctly and avoid bundler-only failures—locate the import of
MetaDataProviderSearchResult in +page.svelte and replace the module specifier
accordingly.

In `@web/src/routes/dashboard/notifications/`+page.svelte:
- Around line 9-13: The imported type Notification conflicts with the browser's
global Notification type; update the import to alias the schema type (e.g.,
import type { Notification as ApiNotification } from '$lib/api/api') and then
update uses of Notification in this module — such as the declarations
unreadNotifications: Notification[] and readNotifications: Notification[] — to
use the aliased name (ApiNotification[]) so the global Web Notification type is
not shadowed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4b7a2aaf-639d-4e69-a2d5-7688fb1cb36c

📥 Commits

Reviewing files that changed from the base of the PR and between f525399 and e9bba10.

📒 Files selected for processing (32)
  • web/package.json
  • web/src/lib/api/api.d.ts
  • web/src/lib/components/add-media-card.svelte
  • web/src/lib/components/delete-media-dialog.svelte
  • web/src/lib/components/download-dialogs/download-custom-dialog.svelte
  • web/src/lib/components/download-dialogs/download-movie-dialog.svelte
  • web/src/lib/components/download-dialogs/download-season-dialog.svelte
  • web/src/lib/components/download-dialogs/download-selected-episodes-dialog.svelte
  • web/src/lib/components/download-dialogs/download-selected-seasons-dialog.svelte
  • web/src/lib/components/download-dialogs/file-path-suffix-selector.svelte
  • web/src/lib/components/download-dialogs/select-file-path-suffix-dialog.svelte
  • web/src/lib/components/import-media/import-candidates-dialog.svelte
  • web/src/lib/components/import-media/suggested-media-card.svelte
  • web/src/lib/components/library-combobox.svelte
  • web/src/lib/components/nav/user-details.svelte
  • web/src/lib/components/recommended-media-carousel.svelte
  • web/src/lib/components/torrents/edit-torrent-dialog.svelte
  • web/src/lib/components/torrents/torrent-table.svelte
  • web/src/lib/components/user-data-table.svelte
  • web/src/lib/utils.ts
  • web/src/routes/dashboard/+layout.svelte
  • web/src/routes/dashboard/+page.svelte
  • web/src/routes/dashboard/movies/+page.svelte
  • web/src/routes/dashboard/movies/[movieId=uuid]/+page.svelte
  • web/src/routes/dashboard/movies/add-movie/+page.svelte
  • web/src/routes/dashboard/notifications/+page.svelte
  • web/src/routes/dashboard/settings/+page.svelte
  • web/src/routes/dashboard/tv/+page.svelte
  • web/src/routes/dashboard/tv/[showId=uuid]/+page.svelte
  • web/src/routes/dashboard/tv/[showId=uuid]/[SeasonId=uuid]/+page.svelte
  • web/src/routes/dashboard/tv/add-show/+page.svelte
  • web/src/routes/login/+page.svelte
📜 Review details
🔇 Additional comments (17)
web/src/lib/components/download-dialogs/file-path-suffix-selector.svelte (1)

5-14: Nice cleanup of the media prop type.

Movie | Show keeps this component contract much easier to read without changing what saveDirectoryPreview receives.

web/src/lib/components/download-dialogs/download-season-dialog.svelte (1)

13-20: Type migration is consistent here.

Using Show removes the noisy schema lookup while keeping the existing show access pattern unchanged.

web/src/lib/components/nav/user-details.svelte (1)

3-4: Clean context typing update.

UserRead makes the context contract much clearer with no behavioural change in this component.

web/src/lib/components/recommended-media-carousel.svelte (1)

6-15: This alias fits the component well.

MetaDataProviderSearchResult[] matches the fields used here and makes the prop signature much easier to scan.

web/src/routes/dashboard/+page.svelte (1)

13-25: The alias use is nicely consistent.

Using MetaDataProviderSearchResult[] for both state variables and casts makes the two recommendation flows much easier to follow.

web/src/lib/components/download-dialogs/select-file-path-suffix-dialog.svelte (1)

5-14: Clean prop typing here as well.

The direct Movie | Show union keeps this dialog aligned with its child component and is much easier to read.

web/src/lib/components/add-media-card.svelte (1)

7-13: Good use of the exported search-result alias.

MetaDataProviderSearchResult matches the fields this card actually consumes and makes the prop contract a lot clearer.

web/src/routes/dashboard/tv/+page.svelte (1)

11-17: This context type reads much better now.

MediaImportSuggestion[] keeps the importableShows contract explicit without changing the surrounding logic.

web/src/lib/components/delete-media-dialog.svelte (1)

2-2: Please verify the $lib/api/api.ts specifier.

The rest of this PR imports the generated OpenAPI types from $lib/api/api, while this hunk still points at $lib/api/api.ts. If the generator only emits web/src/lib/api/api.d.ts, that explicit .ts suffix will not resolve for the type checker.

💡 Suggested change
-import type { PublicMovie, PublicShow } from '$lib/api/api.ts';
+import type { PublicMovie, PublicShow } from '$lib/api/api';

Run this to confirm whether a concrete web/src/lib/api/api.ts file exists and to compare current import usage:

#!/bin/bash
set -euo pipefail

fd 'api(\.d)?\.ts$' web/src/lib/api -t f
printf '\n--- imports using $lib/api/api ---\n'
rg -n --fixed-strings "\$lib/api/api'" web/src
printf '\n--- imports using $lib/api/api.ts ---\n'
rg -n --fixed-strings "\$lib/api/api.ts'" web/src
web/src/routes/dashboard/tv/[showId=uuid]/[SeasonId=uuid]/+page.svelte (1)

7-7: LGTM!

The type imports and variable declarations have been correctly updated to use the explicit public types (PublicEpisodeFile, Season, Show) instead of nested components['schemas'] references. This aligns with the PR objective and improves readability without changing runtime behaviour.

Also applies to: 14-16

web/src/lib/components/torrents/torrent-table.svelte (1)

10-10: LGTM!

The type imports and declarations have been correctly updated to use the explicit public types. The prop typing, context typing, and function parameter typing are all consistent with the PR's objective.

Also applies to: 25-25, 31-33

web/src/routes/dashboard/tv/add-show/+page.svelte (1)

15-15: LGTM!

The type import, variable declaration, and cast have been correctly updated to use MetaDataProviderSearchResult directly. The explicit cast on line 43 appropriately bridges the API client's return type with the component's local typing.

Also applies to: 20-20, 43-43

web/src/routes/dashboard/movies/[movieId=uuid]/+page.svelte (1)

8-8: LGTM!

The type imports and variable declarations have been correctly updated to use the explicit public types (PublicMovie, PublicMovieFile, UserRead). Since these are type aliases to the underlying components['schemas'] types, they remain structurally compatible with the loader's return types.

Also applies to: 20-22

web/src/routes/dashboard/+layout.svelte (1)

10-10: LGTM!

The type import and state variable declarations have been correctly updated to use MediaImportSuggestion directly. The context setup remains unchanged and will provide the correctly typed data to child components.

Also applies to: 13-14

web/src/routes/dashboard/movies/add-movie/+page.svelte (1)

15-15: LGTM!

The type import, variable declaration, and cast have been correctly updated to use MetaDataProviderSearchResult directly, consistent with the parallel changes in the add-show page.

Also applies to: 20-20, 42-42

web/src/routes/dashboard/movies/+page.svelte (1)

17-17: LGTM!

The context retrieval with the explicit MediaImportSuggestion[] return type is correctly updated.

web/src/routes/dashboard/tv/[showId=uuid]/+page.svelte (1)

10-10: LGTM!

The type imports and variable declarations have been correctly updated to use the explicit public types (PublicShow, RichShowTorrent, UserRead). The usage of torrents.torrents on line 356 confirms the structural expectation that RichShowTorrent contains a nested torrents array property.

Also applies to: 30-32

Comment on lines +15 to +18
let { users }: { users: UserRead[] } = $props();
let sortedUsers = $derived(users.sort((a, b) => a.email.localeCompare(b.email)));
let selectedUser: components['schemas']['UserRead'] | null = $state(null);
let userToDelete: components['schemas']['UserRead'] | null = $state(null);
let selectedUser: UserRead | null = $state(null);
let userToDelete: UserRead | null = $state(null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a draft object for selectedUser, not the live table row.

The edit flow later assigns selectedUser = user, then mutates that object in place in the radio handlers. That means cancelling the dialog, or a failed PATCH, can still leave the unsaved flag changes visible in the table. Keep a cloned draft in dialog state and only apply it after a successful save.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/components/user-data-table.svelte` around lines 15 - 18,
Currently the code assigns the live row object to selectedUser and mutates it in
the radio handlers, leaking unsaved changes into the table; instead introduce a
separate draft state (e.g., selectedUserDraft: UserRead | null = $state(null)),
clone the row into that draft when opening the edit dialog (use structuredClone
or {...user}), update only selectedUserDraft in the radio handlers, and on
successful PATCH apply the draft back to the real object or update the users
array (and clear the draft); on cancel simply discard selectedUserDraft so
sortedUsers/users remain unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant