feat: stream list view and tasks (#279)

* first attempt at stream sidebar, tasks, and events

* fix folder from root

* cleanup folders and events, and condense changes

* cleanup and add toggle for sidebar

* cleanup

* fix nits
This commit was merged in pull request #279.
This commit is contained in:
Arjun Patel
2026-06-12 12:26:49 -07:00
committed by GitHub
parent a358774106
commit 095b9876f9
37 changed files with 2384 additions and 791 deletions
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useState } from 'react';
import { Pencil } from 'lucide-react';
import type { Particle } from '@/api/types';
import type { ParticlePath } from '@/lib/particle-path';
@@ -20,6 +20,7 @@ import { ParticleAttachments } from '@/features/particles/particle-attachments';
import { TextEditOverlay } from '@/features/particles/text-edit-overlay';
import { RelativeTimestamp } from '@/components/relative-timestamp';
import { useAuthStore } from '@/stores/auth-store';
import { useFixedDwell } from '@/hooks/use-fixed-dwell';
import { MarkdownEditor } from '@/features/compose/markdown-editor';
type TextParticle = Extract<Particle, { type: 'text' }>;
@@ -36,7 +37,6 @@ interface TextParticleViewProps {
const CHARS_PER_MINUTE = 1000;
const MIN_DURATION_S = 3;
const MAX_DURATION_S = 15;
const TICK_MS = 100;
const EXTRA_S_PER_LINK = 2;
const EXTRA_S_PER_ATTACHMENT = 2;
@@ -97,29 +97,14 @@ export function TextParticleView({
urls.length,
attachments.length,
);
const elapsedRef = useRef(0);
// Reset elapsed when particle changes
useEffect(() => {
elapsedRef.current = 0;
}, [particle.id]);
useEffect(() => {
if (paused) return;
const interval = setInterval(() => {
elapsedRef.current += TICK_MS / 1000;
const ratio = Math.min(elapsedRef.current / durationS, 1);
onProgress?.(ratio);
if (ratio >= 1) {
clearInterval(interval);
onEnded();
}
}, TICK_MS);
return () => clearInterval(interval);
}, [paused, durationS, onEnded, onProgress, particle.id]);
useFixedDwell({
id: particle.id,
durationS,
paused,
onEnded,
onProgress,
});
// Content is just bare URLs with no surrounding text
const contentTrimmed = content.trim();