From d10de739b3d154829dffb5208f19fcdb4bd4295c Mon Sep 17 00:00:00 2001
From: talksik
Date: Mon, 30 Mar 2026 13:33:02 -0700
Subject: [PATCH] show indicator of stream expiring soon
---
.../features/particles/particle-list-view.tsx | 32 +++++++++++++------
js/src/features/particles/stream-card.tsx | 10 ++++++
js/src/hooks/use-expiring-soon.ts | 20 ++++++++++++
3 files changed, 52 insertions(+), 10 deletions(-)
create mode 100644 js/src/hooks/use-expiring-soon.ts
diff --git a/js/src/features/particles/particle-list-view.tsx b/js/src/features/particles/particle-list-view.tsx
index 8093e5b..786afab 100644
--- a/js/src/features/particles/particle-list-view.tsx
+++ b/js/src/features/particles/particle-list-view.tsx
@@ -9,6 +9,7 @@ import {
FileText,
CircleCheck,
StickyNote,
+ Timer,
type LucideIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
@@ -26,6 +27,7 @@ import { Progress } from "@/components/ui/progress";
import { Small } from "@/components/ui/typography";
import type { Particle, StreamProperties } from "@/api/types";
import { useNetwork } from "@/hooks/use-networks";
+import { useExpiringSoon } from "@/hooks/use-expiring-soon";
import { useStreamParticles } from "@/hooks/use-stream-particles";
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
@@ -94,6 +96,11 @@ function StreamRow({
useStreamAutoplay(latestChild, particle);
+ const expiringSoon = useExpiringSoon(
+ particle.last_child_created_at,
+ network?.message_retention_hours ?? 24,
+ );
+
const isDM =
particle.visible_to.length === 2 &&
particle.visible_to.every((v) => v.startsWith("human:"));
@@ -181,16 +188,21 @@ function StreamRow({
>
{particle.properties.name}
- {latestChild && (
-
-
-
- )}
+
+ {expiringSoon && (
+
+ )}
+ {latestChild && (
+
+
+
+ )}
+
(function S
useStreamAutoplay(latestChild, particle);
+ const expiringSoon = useExpiringSoon(
+ particle.last_child_created_at,
+ network?.message_retention_hours ?? 24,
+ );
+
const isDM =
particle.visible_to.length === 2 &&
particle.visible_to.every((v) => v.startsWith("human:"));
@@ -134,6 +141,9 @@ export const StreamCard = forwardRef(function S
{particle.properties.name}
+ {expiringSoon && (
+
+ )}
{latestChild && (
{
+ if (!lastChildCreatedAt) return false;
+
+ const retentionMs = retentionHours * 60 * 60 * 1000;
+ const expiresAt = lastChildCreatedAt.getTime() + retentionMs;
+ const remaining = expiresAt - Date.now();
+
+ return remaining > 0 && remaining < retentionMs * 0.1;
+ }, [lastChildCreatedAt, retentionHours]);
+}