diff --git a/frontend/src/App.css b/frontend/src/App.css
index c641cf9..ccd3f53 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -2776,6 +2776,20 @@ a.app-footer__repo:hover {
white-space: nowrap;
}
+.pipeline-video__review-link {
+ color: var(--color-accent);
+ font-size: 0.75rem;
+ text-decoration: none;
+ white-space: nowrap;
+ opacity: 0.7;
+ transition: opacity 0.15s;
+}
+
+.pipeline-video__review-link:hover {
+ opacity: 1;
+ text-decoration: underline;
+}
+
.pipeline-video__actions {
display: flex;
gap: 0.375rem;
diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx
index f8f9c73..6f10b80 100644
--- a/frontend/src/pages/AdminPipeline.tsx
+++ b/frontend/src/pages/AdminPipeline.tsx
@@ -245,16 +245,15 @@ function EventLog({ videoId }: { videoId: string }) {
className={`pipeline-events__view-btn${viewMode === "head" ? " pipeline-events__view-btn--active" : ""}`}
onClick={() => { setViewMode("head"); setOffset(0); }}
>
- Head
+ Oldest first
-
@@ -368,28 +367,21 @@ function WorkerStatus() {
// ── Debug Mode Toggle ────────────────────────────────────────────────────────
-function DebugModeToggle() {
- const [debugMode, setDebugModeState] = useState(null);
+function DebugModeToggle({
+ debugMode,
+ onDebugModeChange,
+}: {
+ debugMode: boolean | null;
+ onDebugModeChange: (mode: boolean) => void;
+}) {
const [debugLoading, setDebugLoading] = useState(false);
- useEffect(() => {
- let cancelled = false;
- fetchDebugMode()
- .then((res) => {
- if (!cancelled) setDebugModeState(res.debug_mode);
- })
- .catch(() => {
- // silently fail — toggle stays hidden
- });
- return () => { cancelled = true; };
- }, []);
-
async function handleToggle() {
if (debugMode === null || debugLoading) return;
setDebugLoading(true);
try {
const res = await setDebugMode(!debugMode);
- setDebugModeState(res.debug_mode);
+ onDebugModeChange(res.debug_mode);
} catch {
// swallow — leave previous state
} finally {
@@ -463,6 +455,7 @@ export default function AdminPipeline() {
const [actionLoading, setActionLoading] = useState(null);
const [actionMessage, setActionMessage] = useState<{ id: string; text: string; ok: boolean } | null>(null);
const [activeFilter, setActiveFilter] = useState(null);
+ const [debugMode, setDebugModeState] = useState(null);
const load = useCallback(async () => {
setLoading(true);
@@ -481,6 +474,18 @@ export default function AdminPipeline() {
void load();
}, [load]);
+ useEffect(() => {
+ let cancelled = false;
+ fetchDebugMode()
+ .then((res) => {
+ if (!cancelled) setDebugModeState(res.debug_mode);
+ })
+ .catch(() => {
+ // silently fail — toggle stays hidden
+ });
+ return () => { cancelled = true; };
+ }, []);
+
const handleTrigger = async (videoId: string) => {
setActionLoading(videoId);
setActionMessage(null);
@@ -538,7 +543,7 @@ export default function AdminPipeline() {
e.stopPropagation()}>
@@ -597,7 +610,7 @@ export default function AdminPipeline() {
disabled={actionLoading === video.id}
title="Retrigger pipeline"
>
- {actionLoading === video.id ? "…" : "▶ Trigger"}
+ {actionLoading === video.id ? "…" : debugMode ? "▶ Trigger (debug)" : "▶ Trigger"}