cleanup message retention code

This commit is contained in:
talksik
2026-04-07 14:42:06 -07:00
parent e7384cb45c
commit 5ec24d9542
8 changed files with 9 additions and 138 deletions
-9
View File
@@ -178,15 +178,6 @@ class ApiClient {
);
}
async setMessageRetentionHours(networkId: string, hours: number) {
return this.request(
NetworkSchema,
"PUT",
`/networks/${networkId}/message-retention`,
{ hours },
);
}
// --- Invitations ---
async listNetworkInvitations(networkId: string) {
+1 -1
View File
@@ -14,7 +14,6 @@ export const NetworkSchema = z.object({
name: z.string(),
admin_human: HumanSchema,
humans: z.array(HumanSchema),
message_retention_hours: z.number(),
created_at: z.coerce.date(),
});
@@ -186,6 +185,7 @@ export const ParticleSchema = z.discriminatedUnion("type", [
last_child_created_at: z.coerce.date().optional(),
// Array of humanIds currently in the huddle (updated via LiveKit webhooks)
huddle_active_participants: z.array(z.string()).optional(),
status: z.enum(["open", "closed"]).optional(),
}),
ParticleBaseSchema.extend({
type: z.literal("folder"), properties: FolderPropertiesSchema,
-62
View File
@@ -8,11 +8,9 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Slider } from "@/components/ui/slider";
import { Muted } from "@/components/ui/typography";
import { WindowControls } from "@/components/window-controls";
import { useNetworks } from "@/hooks/use-networks";
import { useSetMessageRetention } from "@/hooks/use-network-settings";
import {
useNetworkInvitations,
useInviteMembers,
@@ -150,54 +148,6 @@ function SettingsGroup({
);
}
function formatRetentionDays(hours: number): string {
const days = Math.round(hours / 24);
return days === 1 ? "1 day" : `${days} days`;
}
function EphemeralitySettings({ networkId, retentionHours }: { networkId: string; retentionHours: number }) {
const setRetention = useSetMessageRetention(networkId);
const [days, setDays] = useState(Math.round(retentionHours / 24));
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
// Sync local state if server value changes externally
useEffect(() => {
setDays(Math.round(retentionHours / 24));
}, [retentionHours]);
const handleChange = useCallback((value: number[]) => {
const newDays = value[0];
setDays(newDays);
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
setRetention.mutate(newDays * 24, {
onSuccess: () => toast.success("Retention window updated"),
onError: (err) => toast.error(err.message || "Failed to update retention"),
});
}, 500);
}, [setRetention]);
return (
<div className="flex flex-col gap-3 px-4 py-3">
<div className="flex items-baseline justify-between">
<p className="text-sm font-medium">Messages disappear after</p>
<p className="text-sm font-semibold">{formatRetentionDays(days * 24)}</p>
</div>
<Slider
min={1}
max={14}
step={1}
value={[days]}
onValueChange={handleChange}
/>
<Muted className="text-xs">
Older messages are no longer visible to anyone.
</Muted>
</div>
);
}
export default function NetworkSettingsPage() {
const navigate = useNavigate();
const { networkId } = useParams<{ networkId: string }>();
@@ -240,18 +190,6 @@ export default function NetworkSettingsPage() {
))}
</SettingsGroup>
{isAdmin && network && (
<>
<Separator className="mt-4" />
<SettingsGroup title="Ephemerality">
<EphemeralitySettings
networkId={networkId!}
retentionHours={network.message_retention_hours}
/>
</SettingsGroup>
</>
)}
<Separator className="mt-4" />
{isAdmin && network && (
@@ -9,7 +9,6 @@ import {
FileText,
CircleCheck,
StickyNote,
Timer,
Headphones,
type LucideIcon,
} from "lucide-react";
@@ -28,7 +27,6 @@ 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";
@@ -97,11 +95,6 @@ function StreamRow({
useStreamAutoplay(latestChild, particle, networkId, network ?? undefined);
const expiringSoon = useExpiringSoon(
particle.last_child_created_at,
network?.message_retention_hours ?? 24,
);
const hasActiveHuddle =
particle.huddle_active_participants && particle.huddle_active_participants.length > 0;
const huddleCount = particle.huddle_active_participants?.length ?? 0;
@@ -201,9 +194,6 @@ function StreamRow({
<span className="text-[10px] font-medium text-red-400">{huddleCount}</span>
</span>
)}
{expiringSoon && (
<Timer className="size-3 text-muted-foreground/60" />
)}
{latestChild && (
<Small
className={cn(
+1 -10
View File
@@ -1,12 +1,11 @@
import { forwardRef, useMemo } from "react";
import { Timer, Headphones } from "lucide-react";
import { Headphones } from "lucide-react";
import { cn, getInitials } from "@/lib/utils";
import { useLiveLatestChild } from "@/hooks/use-particle";
import { useAuthStore } from "@/stores/auth-store";
import { particlePath } from "@/lib/particle-path";
import type { Particle, StreamProperties } from "@/api/types";
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
import { useExpiringSoon } from "@/hooks/use-expiring-soon";
import { ParticlePreview } from "@/features/particles/particle-preview";
import { useNetwork } from "@/hooks/use-networks";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
@@ -29,11 +28,6 @@ export const StreamCard = forwardRef<HTMLDivElement, StreamCardProps>(function S
useStreamAutoplay(latestChild, particle, networkId, network ?? undefined);
const expiringSoon = useExpiringSoon(
particle.last_child_created_at,
network?.message_retention_hours ?? 24,
);
const hasActiveHuddle =
particle.huddle_active_participants && particle.huddle_active_participants.length > 0;
const huddleCount = particle.huddle_active_participants?.length ?? 0;
@@ -155,9 +149,6 @@ export const StreamCard = forwardRef<HTMLDivElement, StreamCardProps>(function S
<span className="text-[10px] font-medium text-red-400">{huddleCount}</span>
</span>
)}
{expiringSoon && (
<Timer className="size-3 text-muted-foreground/60" />
)}
{latestChild && (
<Small
className={cn(
-20
View File
@@ -1,20 +0,0 @@
import { useMemo } from "react";
/**
* Returns true when a stream is within the last 10% of its retention window.
* For example, with 24h retention, this fires when < 2.4h remain.
*/
export function useExpiringSoon(
lastChildCreatedAt: Date | undefined,
retentionHours: number,
): boolean {
return useMemo(() => {
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]);
}
-13
View File
@@ -1,13 +0,0 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiClient } from "@/api/client";
export function useSetMessageRetention(networkId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (hours: number) =>
apiClient.setMessageRetentionHours(networkId, hours),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["networks"] });
},
});
}
+7 -13
View File
@@ -4,8 +4,6 @@ import type { Particle } from "@/api/types";
import { useLiveParticleChildren } from "@/hooks/use-particle";
import { parseParticlePath, toFirestoreDocPath, type ParticlePath } from "@/lib/particle-path";
import { updateStreamPlaybackMarker } from "@/lib/firestore-particles";
import { where, Timestamp } from "firebase/firestore";
import { useNetwork } from "@/hooks/use-networks";
// --- Playback reducer (ID-based) ---
@@ -96,9 +94,6 @@ export function useStreamPlayback(
path: ParticlePath,
): UseStreamPlaybackResult {
const userId = useAuthStore((s) => s.user?.id);
const { networkId } = parseParticlePath(path);
const network = useNetwork(networkId);
const retentionHours = network?.message_retention_hours ?? 24;
const [state, dispatch] = useReducer(playbackReducer, initialState);
// Track the stream ID we've initialized for, to reset when navigating between streams
const initializedForRef = useRef<string | null>(null);
@@ -118,15 +113,14 @@ export function useStreamPlayback(
});
});
const [recencyCutoff] = useState(() => {
const d = new Date();
d.setHours(d.getHours() - retentionHours);
return Timestamp.fromDate(d);
});
const { children } = useLiveParticleChildren(
path, "created_at", "asc", undefined, onParticleAdded, onParticleRemoved,
where("created_at", ">=", recencyCutoff),
path,
{
orderByField: "created_at",
orderDirection: "asc",
onAdded: onParticleAdded,
onRemoved: onParticleRemoved
}
);
// Derive current index and particle from ID