From 6d84fbe20c71709855cea5c3803b3a0c20d8aaab Mon Sep 17 00:00:00 2001 From: talksik Date: Wed, 18 Mar 2026 18:00:12 -0700 Subject: [PATCH] feat: show latest particle preview in stream list --- .../features/particles/particle-list-view.tsx | 60 ++++++++++++++++--- js/src/hooks/use-particle.ts | 38 +++++++++++- js/src/lib/firestore-particles.ts | 20 +++++++ 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/js/src/features/particles/particle-list-view.tsx b/js/src/features/particles/particle-list-view.tsx index 8e5d362..0d5f157 100644 --- a/js/src/features/particles/particle-list-view.tsx +++ b/js/src/features/particles/particle-list-view.tsx @@ -1,8 +1,14 @@ import { useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Radio } from "lucide-react"; -import { useLiveParticleChildren } from "@/hooks/use-particle"; -import { parseParticlePath, type ParticlePath } from "@/lib/particle-path"; +import { useLiveParticleChildren, useLiveLatestChild } from "@/hooks/use-particle"; +import { + parseParticlePath, + particlePath, + type ParticlePath, +} from "@/lib/particle-path"; +import { getInitials } from "@/lib/utils"; +import { formatDistanceToNow } from "@/lib/time-utils"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; @@ -10,14 +16,42 @@ import { Progress } from "@/components/ui/progress"; import { Small } from "@/components/ui/typography"; import type { Particle, StreamProperties } from "@/api/types"; +function getMessagePreview(particle: Particle): string { + switch (particle.type) { + case "text": + return particle.properties.content; + case "media": + return "Clip received"; + case "file": + return particle.properties.filename; + case "quest": + return particle.properties.title; + case "paper": + return particle.properties.title; + default: + return particle.type; + } +} + function StreamRow({ particle, + networkId, onClick, }: { particle: Particle & { type: "stream"; properties: StreamProperties }; + networkId: string; onClick: () => void; }) { - const initials = particle.properties.name.slice(0, 2).toUpperCase(); + const streamPath = particlePath(networkId, [particle.id]); + const { latestChild } = useLiveLatestChild(streamPath); + + const initials = latestChild + ? getInitials(latestChild.created_by_email) + : particle.properties.name.slice(0, 2).toUpperCase(); + + const subtitle = latestChild + ? getMessagePreview(latestChild) + : particle.properties.status; return (