+ {/* Navigation bar */}
+
+
+ {/* View content */}
+
+
+
+ {activeView === "focus" && }
+ {activeView === "queue" && (
+ {
+ setFocus(id);
+ }}
+ onSwitchToFocus={() => setView("focus")}
+ />
+ )}
+ {activeView === "inbox" && (
+
+
Inbox view coming in Phase 4b
+
+ )}
+
+
+
+
+ {/* Quick capture overlay (above everything) */}
+
+
+ );
+}
diff --git a/src/components/BatchMode.tsx b/src/components/BatchMode.tsx
new file mode 100644
index 0000000..6b370ae
--- /dev/null
+++ b/src/components/BatchMode.tsx
@@ -0,0 +1,242 @@
+/**
+ * BatchMode -- full-screen rapid completion interface.
+ *
+ * Presents items of the same type one at a time with streamlined
+ * actions: Open in GL, Done, Skip. Shows progress bar and
+ * celebration screen on completion.
+ */
+
+import { useCallback, useEffect } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import { useBatchStore } from "@/stores/batch-store";
+import { formatIid } from "@/lib/format";
+
+interface BatchModeProps {
+ onOpenUrl: (url: string) => void;
+ onExit: () => void;
+}
+
+export function BatchMode({
+ onOpenUrl,
+ onExit,
+}: BatchModeProps): React.ReactElement {
+ const {
+ batchLabel,
+ items,
+ statuses,
+ currentIndex,
+ startedAt,
+ markDone,
+ markSkipped,
+ isFinished,
+ completedCount,
+ skippedCount,
+ } = useBatchStore();
+
+ const currentItem = currentIndex < items.length ? items[currentIndex] : null;
+ const processedCount = statuses.filter((s) => s !== "pending").length;
+ const finished = isFinished();
+
+ const handleOpenInGl = useCallback(() => {
+ if (currentItem?.url) {
+ onOpenUrl(currentItem.url);
+ }
+ }, [currentItem, onOpenUrl]);
+
+ const handleKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (finished) {
+ if (e.key === "Escape" || e.key === "Enter") {
+ e.preventDefault();
+ onExit();
+ }
+ return;
+ }
+
+ if (e.key === "Escape") {
+ e.preventDefault();
+ onExit();
+ } else if (e.key === "d" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ markDone();
+ } else if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ markSkipped();
+ } else if (e.key === "o" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ handleOpenInGl();
+ }
+ },
+ [finished, onExit, markDone, markSkipped, handleOpenInGl]
+ );
+
+ useEffect(() => {
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [handleKeyDown]);
+
+ if (finished) {
+ return (
+
+ {/* Header with label and progress */}
+
+
+ BATCH: {batchLabel}
+
+
+ {processedCount + 1} of {items.length}
+
+
+ {/* Progress bar */}
+
+
+
+
+
+ {/* Current item */}
+
+
+
+
+ {currentItem.title}
+
+
+ {formatIid(currentItem.type, currentItem.iid)} in{" "}
+ {currentItem.project}
+
+
+
+
+ {/* Action buttons */}
+
+
+
+
+
+
+
+ {/* Footer */}
+
+
+ );
+}
+
+function BatchButton({
+ label,
+ shortcut,
+ onClick,
+ variant = "default",
+}: {
+ label: string;
+ shortcut: string;
+ onClick: () => void;
+ variant?: "primary" | "default";
+}): React.ReactElement {
+ const base =
+ "flex flex-col items-center gap-1 rounded-lg border px-6 py-3 text-sm font-medium transition-colors";
+ const styles =
+ variant === "primary"
+ ? `${base} border-mc-fresh/40 bg-mc-fresh/10 text-mc-fresh hover:bg-mc-fresh/20`
+ : `${base} border-zinc-600 bg-surface-raised text-zinc-300 hover:bg-surface-overlay hover:text-zinc-100`;
+
+ return (
+