From 9a410f68c096102f2893da13abbdc5ac97ba7346 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Fri, 22 May 2026 12:29:37 +0530 Subject: [PATCH 1/4] enable timeline memory selection --- apps/web/components/memories-grid.tsx | 3 + apps/web/components/timeline-view.tsx | 239 ++++++++++++++++++++------ 2 files changed, 190 insertions(+), 52 deletions(-) diff --git a/apps/web/components/memories-grid.tsx b/apps/web/components/memories-grid.tsx index d2f93d544..a4e7f1ba7 100644 --- a/apps/web/components/memories-grid.tsx +++ b/apps/web/components/memories-grid.tsx @@ -775,6 +775,9 @@ export function MemoriesGrid({ hasNextPage={hasNextPage} isFetchingNextPage={isFetchingNextPage} onLoadMore={loadMoreDocuments} + isSelectionMode={isSelectionMode} + selectedDocumentIds={selectedDocumentIds} + onToggleSelection={onToggleSelection} /> ) : ( type DocumentWithMemories = DocumentsResponse["documents"][0] @@ -100,6 +100,39 @@ function getPreviewText(doc: DocumentWithMemories): string { return doc.summary || doc.content || doc.title || "" } +function isTemporaryId(id: string | null | undefined): boolean { + if (!id) return false + return id.startsWith("temp-") || id.startsWith("temp-file-") +} + +function SelectionBox({ + isSelected, + isPartial = false, +}: { + isSelected: boolean + isPartial?: boolean +}) { + return ( + + {isSelected ? ( + + ) : isPartial ? ( + + ) : null} + + ) +} + // ─── Grouped data structures ───────────────────────────────────────────────── type TypeGroup = { categoryInfo: CategoryInfo; docs: DocumentWithMemories[] } @@ -126,7 +159,7 @@ function groupDocuments( } return periodOrder.map((label) => { - const docs = periodMap.get(label)! + const docs = periodMap.get(label) ?? [] const categoryMap = new Map< string, { info: CategoryInfo; docs: DocumentWithMemories[] } @@ -144,9 +177,9 @@ function groupDocuments( return { label, - typeGroups: categoryOrder.map((key) => { - const entry = categoryMap.get(key)! - return { categoryInfo: entry.info, docs: entry.docs } + typeGroups: categoryOrder.flatMap((key) => { + const entry = categoryMap.get(key) + return entry ? [{ categoryInfo: entry.info, docs: entry.docs }] : [] }), } }) @@ -157,10 +190,16 @@ function groupDocuments( function TimelineCard({ doc, onOpenDocument, + isSelectionMode = false, + isSelected = false, + onToggleSelection, indent = false, }: { doc: DocumentWithMemories onOpenDocument: (doc: DocumentWithMemories) => void + isSelectionMode?: boolean + isSelected?: boolean + onToggleSelection?: (doc: DocumentWithMemories) => void indent?: boolean }) { const preview = getPreviewText(doc) @@ -168,19 +207,37 @@ function TimelineCard({ ? doc.type.charAt(0).toUpperCase() + doc.type.slice(1).replace(/_/g, " ") : "Document" const totalMemories = doc.memoryEntries.length + const canSelect = !isTemporaryId(doc.id) && !isTemporaryId(doc.customId) + + const handleClick = () => { + if (isSelectionMode && canSelect) { + onToggleSelection?.(doc) + return + } + onOpenDocument(doc) + } return ( + )} - 0 ? "pl-0" : "pl-4", + dmSansClassName(), )} - /> - + onClick={onToggle} + aria-expanded={isExpanded} + > +
+ + + {countLabel} + + {preview && ( + + · {preview} + + )} + {totalMemories > 0 && ( + + {totalMemories} + + )} +
+ + + + {isExpanded && (
{ + if (doc.id) onToggleSelection?.(doc.id) + }} indent /> ))} @@ -341,6 +453,9 @@ interface TimelineViewProps { hasNextPage?: boolean isFetchingNextPage?: boolean onLoadMore?: () => void + isSelectionMode?: boolean + selectedDocumentIds?: Set + onToggleSelection?: (documentId: string) => void } export function TimelineView({ @@ -349,6 +464,9 @@ export function TimelineView({ hasNextPage, isFetchingNextPage, onLoadMore, + isSelectionMode = false, + selectedDocumentIds = new Set(), + onToggleSelection, }: TimelineViewProps) { const [now] = useState(() => new Date()) const [expandedGroups, setExpandedGroups] = useState>(new Set()) @@ -378,6 +496,12 @@ export function TimelineView({ }, []) const periodGroups = groupDocuments(documents, now) + const handleTimelineCardSelection = useCallback( + (doc: DocumentWithMemories) => { + if (doc.id) onToggleSelection?.(doc.id) + }, + [onToggleSelection], + ) return (
) } @@ -416,6 +548,9 @@ export function TimelineView({ isExpanded={expandedGroups.has(expandKey)} onToggle={() => toggleGroup(expandKey)} onOpenDocument={onOpenDocument} + isSelectionMode={isSelectionMode} + selectedDocumentIds={selectedDocumentIds} + onToggleSelection={onToggleSelection} /> ) })} From 092e80ca55d83f8598e1b801c710da96c956b8a9 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sun, 24 May 2026 00:36:51 +0530 Subject: [PATCH 2/4] add proper responsiveness --- apps/web/components/timeline-view.tsx | 232 ++++++++++++++++++-------- 1 file changed, 162 insertions(+), 70 deletions(-) diff --git a/apps/web/components/timeline-view.tsx b/apps/web/components/timeline-view.tsx index ce5d512a8..7e509ab9d 100644 --- a/apps/web/components/timeline-view.tsx +++ b/apps/web/components/timeline-view.tsx @@ -1,6 +1,7 @@ "use client" import { useState, useEffect, useRef, useCallback } from "react" +import { AnimatePresence, motion } from "motion/react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" import { cn } from "@lib/utils" @@ -194,6 +195,7 @@ function TimelineCard({ isSelected = false, onToggleSelection, indent = false, + isLast = false, }: { doc: DocumentWithMemories onOpenDocument: (doc: DocumentWithMemories) => void @@ -201,6 +203,7 @@ function TimelineCard({ isSelected?: boolean onToggleSelection?: (doc: DocumentWithMemories) => void indent?: boolean + isLast?: boolean }) { const preview = getPreviewText(doc) const typeLabel = doc.type @@ -221,10 +224,11 @@ function TimelineCard({
- {isExpanded && ( -
- {group.docs.map((doc) => ( - { - if (doc.id) onToggleSelection?.(doc.id) - }} - indent - /> - ))} -
- )} + + {isExpanded && ( + + {group.docs.map((doc, index) => ( + + { + if (doc.id) onToggleSelection?.(doc.id) + }} + indent + isLast={index === group.docs.length - 1} + /> + + ))} + + )} +
) } @@ -506,57 +534,121 @@ export function TimelineView({ return (
- {periodGroups.map((period) => ( -
-
- - {period.label} - -
- -
- {period.typeGroups.map((group) => { - const expandKey = `${period.label}::${group.categoryInfo.key}` - - if (group.docs.length === 1) { - const doc = group.docs[0] - if (!doc) return null + {periodGroups.map((period, periodIndex) => { + const periodHasExpandedGroup = period.typeGroups.some((group) => + expandedGroups.has(`${period.label}::${group.categoryInfo.key}`), + ) + + return ( +
+
+ + {period.label} + +
+ +
+
+ + + + {periodHasExpandedGroup && ( + + )} + + + +
+ +
+ {period.typeGroups.map((group) => { + const expandKey = `${period.label}::${group.categoryInfo.key}` + + if (group.docs.length === 1) { + const doc = group.docs[0] + if (!doc) return null + + return ( + + ) + } return ( - toggleGroup(expandKey)} onOpenDocument={onOpenDocument} isSelectionMode={isSelectionMode} - isSelected={ - doc.id ? selectedDocumentIds.has(doc.id) : false - } - onToggleSelection={handleTimelineCardSelection} + selectedDocumentIds={selectedDocumentIds} + onToggleSelection={onToggleSelection} /> ) - } - - return ( - toggleGroup(expandKey)} - onOpenDocument={onOpenDocument} - isSelectionMode={isSelectionMode} - selectedDocumentIds={selectedDocumentIds} - onToggleSelection={onToggleSelection} - /> - ) - })} + })} +
-
- ))} + ) + })}
From 3fa7113270aed91b6f8546efdf5217f36e5704e6 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sun, 24 May 2026 13:30:56 +0530 Subject: [PATCH 3/4] fix timeline responsiveness --- apps/web/components/timeline-view.tsx | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/web/components/timeline-view.tsx b/apps/web/components/timeline-view.tsx index 7e509ab9d..18437d774 100644 --- a/apps/web/components/timeline-view.tsx +++ b/apps/web/components/timeline-view.tsx @@ -226,10 +226,11 @@ function TimelineCard({ className={cn( "relative w-full text-left px-3 py-3 cursor-pointer transition-colors sm:px-4", indent - ? "bg-[#121820]/95 hover:bg-[#17202A]" + ? "bg-[#121820] hover:bg-[#17202A]" : "rounded-xl border border-[#252B35] bg-[#1B1F24] hover:bg-[#21262D] sm:rounded-2xl", indent && !isLast && "border-b border-[#252B35]/70", - isSelectionMode && canSelect && "pl-10", + indent && isLast && "rounded-b-xl sm:rounded-b-2xl", + isSelectionMode && canSelect && "pl-14 sm:pl-16", isSelectionMode && isSelected && "border-[#369BFD]/70 bg-[#00173C]/45", dmSansClassName(), )} @@ -237,7 +238,7 @@ function TimelineCard({ aria-pressed={isSelectionMode ? isSelected : undefined} > {isSelectionMode && canSelect && ( - + )} @@ -350,20 +351,27 @@ function GroupCard({ } return ( -
+
{isSelectionMode && selectableDocs.length > 0 && (