diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx
index 8f0a2c93..b0383468 100644
--- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx
+++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx
@@ -49,6 +49,15 @@ export default function StreamystatsPage() {
);
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
+ const searchLocked = pluginSettings?.searchEngine?.locked === true;
+ const movieRecsLocked =
+ pluginSettings?.streamyStatsMovieRecommendations?.locked === true;
+ const seriesRecsLocked =
+ pluginSettings?.streamyStatsSeriesRecommendations?.locked === true;
+ const promotedWatchlistsLocked =
+ pluginSettings?.streamyStatsPromotedWatchlists?.locked === true;
+ const hideWatchlistsTabLocked =
+ pluginSettings?.hideWatchlistsTab?.locked === true;
const isStreamystatsEnabled = !!url;
const onSave = useCallback(() => {
@@ -146,7 +155,9 @@ export default function StreamystatsPage() {
placeholder={t(
"home.settings.plugins.streamystats.server_url_placeholder",
)}
- value={url}
+ value={
+ isUrlLocked ? (settings?.streamyStatsServerUrl ?? "") : url
+ }
keyboardType='url'
returnKeyType='done'
autoCapitalize='none'
@@ -171,11 +182,18 @@ export default function StreamystatsPage() {
>
+ {/* Locked controls show the live admin value and can't be toggled —
+ local form state would let the switch flip while the write guard
+ drops the change. */}
@@ -183,52 +201,62 @@ export default function StreamystatsPage() {
title={t(
"home.settings.plugins.streamystats.enable_movie_recommendations",
)}
- disabledByAdmin={
- pluginSettings?.streamyStatsMovieRecommendations?.locked === true
- }
+ disabledByAdmin={movieRecsLocked}
>
diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts
index f4c6c7dd..2b8c7aca 100644
--- a/utils/atoms/settings.ts
+++ b/utils/atoms/settings.ts
@@ -504,7 +504,17 @@ export const useSettings = () => {
if (!_settings) {
return;
}
- const hasChanges = Object.entries(update).some(
+ // Admin-locked settings are enforced at write time too: a control that
+ // isn't disabled in the UI must not persist a value the admin pinned.
+ // The read memo already overrides locked keys, but without this guard the
+ // write would silently land in user storage and resurface once unlocked.
+ const sanitizedUpdate = Object.fromEntries(
+ Object.entries(update).filter(
+ ([key]) => pluginSettings?.[key as keyof Settings]?.locked !== true,
+ ),
+ ) as Partial;
+
+ const hasChanges = Object.entries(sanitizedUpdate).some(
([key, value]) => _settings[key as keyof Settings] !== value,
);
@@ -513,7 +523,7 @@ export const useSettings = () => {
const newSettings = {
...defaultValues,
..._settings,
- ...update,
+ ...sanitizedUpdate,
} as Settings;
setSettings(newSettings);
saveSettings(newSettings);