fix(seerr): enable Home/Recommended visibility options for Seerr Individual Requests collections

Longstanding Plex bug not respecting label restrictions for Collections on Home/Recommended has been
fixed in PMS Beta 1.43.1.10540, confirmed working with Agregarr. Also removes previous easter egg
which enabled the option (intended for use when the project was going to be a PR for Overseerr,
allowing use of the option without waiting for an update)

fix #112
This commit is contained in:
Tom Wheeler
2026-03-17 09:26:16 +13:00
parent 07f0a14ef0
commit 95f77d64a4
7 changed files with 5 additions and 182 deletions
-2
View File
@@ -476,7 +476,6 @@ export interface PlexSettings {
collectionConfigs?: CollectionConfig[]; // Agregarr-created collections
hubConfigs?: PlexHubConfig[]; // Plex built-in hub configurations
preExistingCollectionConfigs?: PreExistingCollectionConfig[]; // Pre-existing Plex collections discovered by hub discovery
usersHomeUnlocked?: boolean; // Secret unlock for Users Home collections
autoEmptyTrash?: boolean; // Auto-empty Plex trash after placeholder cleanup (default: true)
}
@@ -738,7 +737,6 @@ class Settings {
collectionConfigs: [],
hubConfigs: [],
preExistingCollectionConfigs: [],
usersHomeUnlocked: false,
},
tautulli: {},
maintainerr: {},
@@ -13,8 +13,8 @@ const messages = defineMessages({
usersHome: 'Users Home',
serverOwnerHome: 'Server Owner Home',
libraryRecommended: 'Library Recommended',
userRequestCollectionsRestricted:
"Individual user request collections are restricted to Library Tab Only visibility due a Plex bug that doesn't respect label restrictions on the Home/Recommended screens. TMDB Franchise Collections and Plex Library Auto Director Collections are hidden so that you don't clog up your home/recommended screens.",
libraryOnlyRestricted:
'TMDB Franchise Collections and Plex Library Auto Director/Actor Collections are restricted to Library Tab Only visibility to avoid cluttering your home/recommended screens.',
serverOwnerOnlyRestricted:
"Server owner request collections can only appear on the server owner's home screen.",
noVisibilityHubWarning:
@@ -82,7 +82,7 @@ const VisibilitySection = ({
{/* Show restriction notice for collections restricted to library only */}
{restrictToLibraryOnly && (
<div className="mb-3 rounded border border-orange-500/20 bg-orange-500/10 p-3 text-sm text-orange-300">
{intl.formatMessage(messages.userRequestCollectionsRestricted)}
{intl.formatMessage(messages.libraryOnlyRestricted)}
</div>
)}
@@ -3255,8 +3255,6 @@ const CollectionFormConfigForm = ({
isEnhancedForm={false}
isDefaultPlexHub={isHub}
restrictToLibraryOnly={
(values.type === 'overseerr' &&
values.subtype === 'users') ||
(values.type === 'tmdb' &&
values.subtype === 'auto_franchise') ||
(values.type === 'plex' &&
@@ -537,24 +537,10 @@ export const SourceSubtypeBadge: React.FC<SourceSubtypeBadgeProps> = ({
// Item Count Badge
interface ItemCountBadgeProps {
maxItems: number;
onBadgeClick?: () => void;
}
export const ItemCountBadge: React.FC<ItemCountBadgeProps> = ({
maxItems,
onBadgeClick,
}) => {
export const ItemCountBadge: React.FC<ItemCountBadgeProps> = ({ maxItems }) => {
const intl = useIntl();
// Easter egg handling for maxItems === 69
if (maxItems === 69 && onBadgeClick) {
return (
<button type="button" onClick={onBadgeClick} className="cursor-pointer">
<Badge badgeType="success" className="!bg-opacity-40">
{intl.formatMessage(messages.items, { maxItems })}
</Badge>
</button>
);
}
return (
<Badge badgeType="default" className="!bg-opacity-30">
@@ -65,8 +65,6 @@ const messages = defineMessages({
failedLoadPlexLibraries:
'Failed to load Plex libraries. Please check your Plex connection.',
noCollectionsFound: 'No Collections found. Click Discover to get started!',
usersHomeUnlocked: 'Users Home collections unlocked!',
failedUnlockUsersHome: 'Failed to unlock Users Home collections',
collectionsSyncStarted: 'Collections sync started successfully!',
failedStartSync: 'Failed to start collections sync. Please try again.',
hubConfigSaved: 'Hub configuration saved successfully!',
@@ -272,9 +270,6 @@ const CollectionSettings = ({
// State to track when an inactive collection was just added (for pulsating button)
const [showInactiveHelp, setShowInactiveHelp] = useState(false);
// Badge click tracking (for easter eggs)
const [badgeClickCount, setBadgeClickCount] = useState(0);
// Hub discovery state
const [discoveringHubs, setDiscoveringHubs] = useState(false);
@@ -378,55 +373,6 @@ const CollectionSettings = ({
const shouldShowPlaceholderAlert =
!isFirstTimeUser && libraryIssues.length > 0;
const checkForUnlockSequence = () => {
// Check if there's an Overseerr user collection with 69 items and user has clicked 10 times
const overseerrUserCollectionWith69Items = collectionConfigs.find(
(config: CollectionFormConfig) =>
config.type === 'overseerr' &&
config.subtype === 'users' &&
config.maxItems === 69
);
if (
overseerrUserCollectionWith69Items &&
badgeClickCount >= 10 &&
!data?.usersHomeUnlocked
) {
// Unlock Users Home collections - preserve all existing settings
if (data) {
const writableSettings = Object.fromEntries(
Object.entries(data).filter(
([key]) => !['name', 'machineId', 'libraries'].includes(key)
)
);
fetch('/api/v1/settings/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...writableSettings,
usersHomeUnlocked: true, // Only change this field
}),
})
.then(() => {
revalidate();
addToast(`${intl.formatMessage(messages.usersHomeUnlocked)} 🏠✨`, {
autoDismiss: true,
appearance: 'success',
});
setBadgeClickCount(0);
})
.catch(() => {
addToast(intl.formatMessage(messages.failedUnlockUsersHome), {
autoDismiss: true,
appearance: 'error',
});
});
}
}
};
// Collection configuration handlers
const saveCollectionConfigs = async (
configs: CollectionFormConfig[],
@@ -2279,9 +2225,6 @@ const CollectionSettings = ({
onPromotePreExisting={handlePromotePreExisting}
onDemotePreExisting={handleDemotePreExisting}
onReorderItems={handleReorderItems}
badgeClickCount={badgeClickCount}
setBadgeClickCount={setBadgeClickCount}
checkForUnlockSequence={checkForUnlockSequence}
activeTab={activeTab}
onBulkEdit={() => setShowBulkEditModal(true)}
/>
@@ -2313,9 +2256,6 @@ const CollectionSettings = ({
onPromotePreExisting={handlePromotePreExisting}
onDemotePreExisting={handleDemotePreExisting}
onReorderItems={handleReorderItems}
badgeClickCount={badgeClickCount}
setBadgeClickCount={setBadgeClickCount}
checkForUnlockSequence={checkForUnlockSequence}
activeTab={activeTab}
onBulkEdit={() => setShowBulkEditModal(true)}
/>
@@ -123,9 +123,6 @@ interface LibraryCollectionGroupProps {
) & { configType: FormConfigType; position: number })[],
itemTypeName: string
) => Promise<void>;
badgeClickCount: number;
setBadgeClickCount: (value: number | ((prev: number) => number)) => void;
checkForUnlockSequence: () => void;
activeTab: 'home' | 'recommended' | 'library' | 'inactive' | 'unmanaged';
onBulkEdit?: () => void;
}
@@ -145,8 +142,6 @@ interface SortableItemProps {
onDemoteCollection?: (config: CollectionFormConfig) => Promise<void>;
onPromotePreExisting?: (config: PreExistingCollectionConfig) => Promise<void>;
onDemotePreExisting?: (config: PreExistingCollectionConfig) => Promise<void>;
setBadgeClickCount: (value: number | ((prev: number) => number)) => void;
checkForUnlockSequence: () => void;
activeTab: 'home' | 'recommended' | 'library' | 'inactive' | 'unmanaged';
onIndividualSync?: (collectionId: string) => Promise<void>;
isSyncing?: boolean;
@@ -164,8 +159,6 @@ const SortableItem = ({
onDemoteCollection,
onPromotePreExisting,
onDemotePreExisting,
setBadgeClickCount,
checkForUnlockSequence,
activeTab,
onIndividualSync,
isSyncing,
@@ -327,24 +320,7 @@ const SortableItem = ({
const maxItems = collectionConfig.maxItems;
if (maxItems === undefined) return null;
return (
<ItemCountBadge
maxItems={maxItems}
onBadgeClick={
maxItems === 69
? () => {
setBadgeClickCount((prev) => {
const newCount = prev + 1;
if (newCount >= 10) {
checkForUnlockSequence();
}
return newCount;
});
}
: undefined
}
/>
);
return <ItemCountBadge maxItems={maxItems} />;
})()}
{/* Missing Items Badge - Shows when grab missing is enabled for collections */}
@@ -553,15 +529,9 @@ const LibraryCollectionGroup = ({
onPromotePreExisting,
onDemotePreExisting,
onReorderItems,
badgeClickCount,
setBadgeClickCount,
checkForUnlockSequence,
activeTab,
onBulkEdit,
}: LibraryCollectionGroupProps) => {
// Ensure badgeClickCount is "used" to satisfy linter - this is part of easter egg state management
void badgeClickCount;
const intl = useIntl();
const [isCollapsed, setIsCollapsed] = useState(false);
const [syncingIds, setSyncingIds] = useState<Set<string>>(new Set());
@@ -878,8 +848,6 @@ const LibraryCollectionGroup = ({
onDemoteCollection={onDemoteCollection}
onPromotePreExisting={onPromotePreExisting}
onDemotePreExisting={onDemotePreExisting}
setBadgeClickCount={setBadgeClickCount}
checkForUnlockSequence={checkForUnlockSequence}
activeTab={activeTab}
onIndividualSync={handleIndividualSync}
isSyncing={syncingIds.has(config.id)}
-67
View File
@@ -2,7 +2,6 @@ import type {
CollectionFormConfig,
CollectionFormConfigForEditing,
Library,
VisibilityCheckboxState,
} from '@app/types/collections';
import { CollectionFormConfigUtils } from '@app/types/collections';
import { useCallback, useState } from 'react';
@@ -320,72 +319,6 @@ export const FormStateHelpers = {
return true;
});
},
/**
* Get visibility checkbox states based on collection type
*/
getVisibilityCheckboxStates: (
values: CollectionFormConfig,
data: { hasUsersHomeUnlock?: boolean }
): Record<string, VisibilityCheckboxState> => {
if (!values.type)
return {
usersHome: { enabled: false, label: 'Users Home' },
serverOwnerHome: { enabled: false, label: 'Server Owner Home' },
libraryRecommended: { enabled: false, label: 'Library Recommended' },
};
// For User Requests (overseerr + users), check if Users Home is unlocked
if (values.type === 'overseerr' && values.subtype === 'users') {
const isUsersHomeUnlocked = data?.hasUsersHomeUnlock || false;
return {
usersHome: { enabled: isUsersHomeUnlocked, label: 'Users Home' },
serverOwnerHome: { enabled: false, label: 'Server Owner Home' }, // Users collections shouldn't be on server owner home
libraryRecommended: { enabled: true, label: 'Library Recommended' },
};
}
// For Server Owner requests (overseerr + server_owner), only "Server Owner Home" should be available
if (values.type === 'overseerr' && values.subtype === 'server_owner') {
return {
usersHome: { enabled: false, label: 'Users Home' }, // Server owner collections shouldn't be on users' home
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
libraryRecommended: { enabled: true, label: 'Library Recommended' },
};
}
// For Hub configs, all options should be available
if (values.configType === 'hub') {
return {
usersHome: { enabled: true, label: 'Users Home' },
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
libraryRecommended: { enabled: true, label: 'Library Recommended' },
};
}
// For Source collections (Tautulli/Trakt/etc), all options should be available
if (
values.type === 'tautulli' ||
values.type === 'trakt' ||
values.type === 'tmdb' ||
values.type === 'imdb' ||
values.type === 'letterboxd' ||
values.type === 'multi-source'
) {
return {
usersHome: { enabled: true, label: 'Users Home' },
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
libraryRecommended: { enabled: true, label: 'Library Recommended' },
};
}
// For overseerr global collections, all options should be available
return {
usersHome: { enabled: true, label: 'Users Home' },
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
libraryRecommended: { enabled: true, label: 'Library Recommended' },
};
},
};
export default useFormStateManager;