diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index 98febe44..020cad49 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -31,6 +31,7 @@ import { DocumentModal } from "@/components/document-modal" import { DocumentsCommandPalette } from "@/components/documents-command-palette" import { FullscreenNoteModal } from "@/components/fullscreen-note-modal" import type { HighlightItem } from "@/components/highlights-card" +import { DigestsView } from "@/components/digests-view" import { HotkeysProvider } from "react-hotkeys-hook" import { useHotkeys } from "react-hotkeys-hook" import { useIsMobile } from "@hooks/use-mobile" @@ -577,7 +578,10 @@ export default function NewPage() { const isChatView = viewMode === "chat" const showNovaBackdrop = - viewMode === "graph" || viewMode === "list" || viewMode === "dashboard" + viewMode === "graph" || + viewMode === "list" || + viewMode === "dashboard" || + viewMode === "digests" const isDashboardShell = viewMode === "dashboard" || (viewMode === "graph" && isMobile) const isGraphMode = viewMode === "graph" @@ -591,7 +595,8 @@ export default function NewPage() {
void setViewMode("integrations")} /> + ) : viewMode === "digests" ? ( +
+ +
) : viewMode === "graph" ? (
@@ -763,6 +773,7 @@ export default function NewPage() { onHighlightsChat={handleHighlightsChat} onHighlightsShowRelated={handleHighlightsShowRelated} onResetHighlights={handleResetHighlights} + onOpenDigests={() => void setViewMode("digests")} memoryOfDay={memoryOfDay} /> )} diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index fe2e3f0c..6edbc1ca 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -41,6 +41,7 @@ import { } from "@/hooks/use-personalization" import { normalizePluginClientId } from "@/lib/plugin-catalog" import { detectPluginSpace } from "@/lib/plugin-space" +import { useDigests } from "@/hooks/use-digests" type DocumentsResponse = z.infer type DocumentWithMemories = DocumentsResponse["documents"][0] @@ -1153,6 +1154,7 @@ export function DashboardView({ onHighlightsChat, onHighlightsShowRelated, onResetHighlights, + onOpenDigests, memoryOfDay, }: { spaceLabel: string @@ -1173,6 +1175,7 @@ export function DashboardView({ onHighlightsChat: (highlightContent: string, userReply: string) => void onHighlightsShowRelated: (query: string) => void onResetHighlights: () => void + onOpenDigests: () => void memoryOfDay: MemoryOfDay | null }) { const { user, org } = useAuth() @@ -1279,6 +1282,9 @@ export function DashboardView({ setProfession, } = usePersonalization() + const { data: digestList } = useDigests() + const latestDigest = digestList?.[0] + const recents = recentsData?.documents ?? [] const recentToolUsageItems = toolUsageItems .filter((item) => item.type === "Plugin" && item.lastDocument) @@ -1472,6 +1478,43 @@ export function DashboardView({

+ {/* Weekly digest preview */} + {latestDigest && ( + +
+ +
+
+ + Weekly digest + +

+ {latestDigest.title || "Your week in Supermemory"} ·{" "} + {latestDigest.memoryCount} memories +

+
+ + View + + +
+ )} + {/* Recently saved + Suggested for you */} = { + connections: "feat-router.png", + chat: "feat-memory.png", + extension: "feat-retrieval.png", + plugins: "feat-profiles.png", + mcp: "feat-router.png", + search: "feat-retrieval.png", +} +const BRAIN_IMG = "https://supermemory.ai/images/brain-head.png" + +function formatIsoWeek(isoWeek: string): string { + const match = isoWeek.match(/^(\d{4})-W(\d{2})$/) + if (!match) return isoWeek + const year = Number.parseInt(match[1] as string, 10) + const week = Number.parseInt(match[2] as string, 10) + const jan4 = new Date(year, 0, 4) + const dow = jan4.getDay() || 7 + const start = new Date(jan4) + start.setDate(jan4.getDate() - dow + 1 + (week - 1) * 7) + const end = new Date(start) + end.setDate(start.getDate() + 6) + const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ] + const sm = months[start.getMonth()] ?? "" + const em = months[end.getMonth()] ?? "" + return `${sm} ${start.getDate()}–${sm === em ? "" : `${em} `}${end.getDate()}, ${year}` +} + +function isoWeekLabel(isoWeek: string): string { + const m = isoWeek.match(/-W(\d{2})$/) + return m ? `W${Number.parseInt(m[1] as string, 10)}` : isoWeek +} + +// ─── Left list row ──────────────────────────────────────────────────────────── +function DigestRow({ + digest, + featured, + selected, + onSelect, +}: { + digest: DigestSummary + featured: boolean + selected: boolean + onSelect: () => void +}) { + return ( + + ) +} + +// ─── Right pane: a single digest, faithful to the email layout ───────────────── +function DigestContent({ + digestId, + isoWeek, +}: { + digestId: string + isoWeek: string +}) { + const { data: digest, isLoading } = useDigest(digestId) + const [rating, setRating] = useState<"up" | "down" | null>(null) + const [showInput, setShowInput] = useState(false) + const [message, setMessage] = useState("") + + // reset feedback state when switching digests + useEffect(() => { + setRating(null) + setShowInput(false) + setMessage("") + analytics.digestViewed({ digest_id: digestId, iso_week: isoWeek }) + }, [digestId, isoWeek]) + + if (isLoading) { + return ( +
+ +
+ ) + } + if (!digest) { + return ( +
+ Digest not found. +
+ ) + } + + const { digestData } = digest + + const rate = (r: "up" | "down") => { + setRating(r) + analytics.digestFeedback({ + digest_id: digestId, + iso_week: isoWeek, + rating: r, + }) + } + const submitDetail = () => { + if (!message.trim()) return + analytics.digestFeedbackDetail({ + digest_id: digestId, + iso_week: isoWeek, + rating, + message: message.trim(), + }) + setMessage("") + setShowInput(false) + toast.success("Thanks for the feedback!") + } + + return ( +
+ {/* Header */} +

+ Weekly digest · {formatIsoWeek(digest.isoWeek)} +

+

+ {digestData.title || "Your week in Supermemory"} +

+ + {/* Greeting + intro, brain floated right */} +
+ +

+ {digestData.intro} +

+
+
+ +
+ + {/* Highlights — numbered, no boxes */} + {digestData.highlights.length > 0 && ( + <> +

+ This week's highlights +

+
+ {digestData.highlights.map((h, i) => ( +
+ + {String(i + 1).padStart(2, "0")} + +
+

+ {h.title} +

+

+ {h.content} +

+
+
+ ))} +
+ + )} + + {/* Worth trying — the one place we use boxes (subtle) */} + {digestData.featureRecommendations.length > 0 && ( +
+

+ Worth trying +

+
+ {digestData.featureRecommendations.map((r) => ( + + +
+

+ {r.headline} +

+

+ {r.body}{" "} + + {r.ctaLabel} → + +

+
+
+ ))} +
+
+ )} + + {/* Feedback */} +
+
+ + Was this digest useful? + +
+ + + +
+
+ + {showInput && ( +
+