diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx
index 9133f7b..eac7b72 100644
--- a/frontend/src/components/Feed.tsx
+++ b/frontend/src/components/Feed.tsx
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useInfiniteQuery, useQuery, useQueryClient } from "@tanstack/react-query";
+import { RefreshCw } from "lucide-react";
import { api, type FeedFilters, type Video } from "../lib/api";
import i18n from "../i18n";
import { notify } from "../lib/notifications";
@@ -9,6 +10,25 @@ import PlayerModal from "./PlayerModal";
const PAGE = 60;
+// Sort + content-type live in the feed toolbar (above the cards), not the filter sidebar.
+const SORT_IDS = [
+ "newest",
+ "oldest",
+ "views",
+ "duration_desc",
+ "duration_asc",
+ "title",
+ "subscribers",
+ "priority",
+ "shuffle",
+];
+const CONTENT = [
+ { key: "includeNormal", label: "sidebar.content.normal" },
+ { key: "includeShorts", label: "sidebar.content.shorts" },
+ { key: "includeLive", label: "sidebar.content.live" },
+] as const;
+const rollSeed = () => Math.floor(Math.random() * 1_000_000_000);
+
function matchesView(status: string, show: string): boolean {
switch (show) {
case "hidden":
@@ -166,11 +186,10 @@ export default function Feed({
if (query.isLoading) return
{t("feed.loading")}
;
if (query.isError) return
{t("feed.loadError")}
;
- if (items.length === 0) {
- // No YouTube connection and looking at their own (empty) subscriptions: offer both
- // connecting YouTube and just browsing the shared library (no read scope needed).
- if (!canRead && filters.scope === "my")
- return (
+ // Onboarding empty state (no YouTube + own subscriptions): a dedicated full-page prompt,
+ // with no toolbar (content-type/sort would be meaningless here).
+ if (items.length === 0 && !canRead && filters.scope === "my")
+ return (
{t("feed.emptyTitle")}
@@ -190,20 +209,73 @@ export default function Feed({