diff --git a/components/board/todo-create-dialog.tsx b/components/board/todo-create-dialog.tsx
new file mode 100644
index 0000000..c659fdd
--- /dev/null
+++ b/components/board/todo-create-dialog.tsx
@@ -0,0 +1,95 @@
+"use client";
+
+import { useState } from "react";
+import { useTheme } from "next-themes";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { MDEditor } from "@/components/markdown/markdown-widgets";
+import { useBoard } from "@/components/board/board-context";
+
+const TITLE_MAX = 20;
+
+// Same fields and `addTodo` call as TodoCreatePopover's inline form, but as
+// a controlled Dialog instead of owning its own popover trigger -- this is
+// opened from the compact view's "+" dropdown menu rather than from an
+// inline "+ Add to-do" button.
+export function TodoCreateDialog({
+ groupId,
+ categoryId,
+ open,
+ onOpenChange,
+}: {
+ groupId: string;
+ categoryId: string;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}) {
+ const { addTodo } = useBoard();
+ const { resolvedTheme } = useTheme();
+ const colorMode = resolvedTheme === "dark" ? "dark" : "light";
+
+ const [title, setTitle] = useState("");
+ const [details, setDetails] = useState("");
+ const [pending, setPending] = useState(false);
+
+ function handleOpenChange(next: boolean) {
+ if (!next) {
+ setTitle("");
+ setDetails("");
+ }
+ onOpenChange(next);
+ }
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ const trimmed = title.trim();
+ if (!trimmed) return;
+ setPending(true);
+ const ok = await addTodo(groupId, categoryId, trimmed, details.trim() || undefined);
+ setPending(false);
+ if (ok) handleOpenChange(false);
+ }
+
+ return (
+
+ );
+}
diff --git a/components/board/view-switcher.tsx b/components/board/view-switcher.tsx
new file mode 100644
index 0000000..1b1829e
--- /dev/null
+++ b/components/board/view-switcher.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import { LayoutList, Rows3 } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useBoardView, type BoardView } from "@/components/board/board-view-provider";
+
+// New views just need one more entry here (and one more BoardView literal)
+// -- no redesign of the trigger or menu required.
+const OPTIONS: { value: BoardView; label: string; icon: typeof LayoutList }[] = [
+ { value: "default", label: "Default", icon: LayoutList },
+ { value: "compact", label: "Compact", icon: Rows3 },
+];
+
+export function ViewSwitcher() {
+ const { view, setView } = useBoardView();
+ const current = OPTIONS.find((o) => o.value === view) ?? OPTIONS[0];
+ const Icon = current.icon;
+
+ return (
+
+
+
+ {current.label}
+
+ }
+ />
+
+ setView(v as BoardView)}>
+ {OPTIONS.map((option) => (
+
+
+ {option.label}
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/scheduled/scheduled-panel.tsx b/components/scheduled/scheduled-panel.tsx
index 94e6162..e1fc504 100644
--- a/components/scheduled/scheduled-panel.tsx
+++ b/components/scheduled/scheduled-panel.tsx
@@ -92,6 +92,10 @@ export function ScheduledPanel() {
}
const overdueCount = board?.overdue.length ?? 0;
+ // Today's occurrences list includes already-checked-off ones (so they can
+ // still be toggled back), unlike overdue -- so this needs its own filter
+ // rather than just `board.today.occurrences.length`.
+ const todayCount = board?.today.occurrences.filter((o) => !o.completed).length ?? 0;
return (
<>
@@ -102,7 +106,12 @@ export function ScheduledPanel() {
)}
>
{collapsed ? (
-
+
) : (
void;
onExpand: () => void;
}) {
return (
<>
-
+
- {overdueCount > 0 && (
-
- {overdueCount > 9 ? "9+" : overdueCount}
-
- )}
}
/>
-
- {overdueCount > 0 ? `${overdueCount} overdue` : "Scheduled to-dos"}
-
+ Scheduled to-dos
+
+ {/* Sits right below the calendar icon rather than as a corner
+ overlay -- a ping ring behind the solid circle makes overdue
+ work as loud/eye-catching as possible. */}
+ {overdueCount > 0 && (
+
+
+
+
+ {overdueCount > 9 ? "9+" : overdueCount}
+
+
+ }
+ />
+ {overdueCount} overdue
+
+ )}
+
+ {/* Below the overdue circle when both are present, otherwise right
+ below the calendar icon -- a plain opacity pulse (no ping ring)
+ keeps it visibly calmer than overdue's. */}
+ {todayCount > 0 && (
+
+
+ {todayCount > 9 ? "9+" : todayCount}
+
+ }
+ />
+ {todayCount} due today
+
+ )}