paginate open streams #252

Merged
talksik merged 4 commits from paginate-open-streams into main 2026-06-09 17:43:27 +00:00
6 changed files with 38 additions and 33 deletions
+11 -4
View File
@@ -11,6 +11,7 @@ import NetworkSelector from '@/features/network-selector';
import NetworkRoot from '@/features/network-root';
import ParticleViewResolver from '@/features/particles/particle-view-resolver';
import Layout from '@/features/layout';
import { logError } from '@/lib/errors';
import NetworkSettingsPage from '@/features/network-settings';
import { Toaster } from '@/components/ui/sonner';
import { PusherProvider } from '@/lib/pusher-provider';
1
@@ -56,10 +57,16 @@ function DeepLinkNavigationListener() {
const navigate = useNavigate();
useEffect(() => {
window.electronDeepLink.getPending().then((path) => {
if (path) navigate(path);
});
return window.electronDeepLink.onNavigate((path) => navigate(path));
platform.deepLink
.getPending()
.then((path) => {
if (path) navigate(path);
})
.catch((err) => {
logError(err, { scope: 'deepLink.getPending' });
});
return platform.deepLink.onNavigate((path) => navigate(path));
}, [navigate]);
return null;
+1 -4
View File
@@ -30,8 +30,7 @@ export default function NetworkRoot() {
setSearchParams(
(prev) => {
const params = new URLSearchParams(prev);
if (next === 'open') params.delete('status');
else params.set('status', next);
params.set('status', next);
return params;
},
{ replace: true },
@@ -56,7 +55,6 @@ export default function NetworkRoot() {
return (
<div className="relative flex min-h-0 flex-1 flex-col">
{/* Top bar — stays in place */}
<div className="flex shrink-0 items-center p-1 border-b">
<Tabs
value={statusTab}
@@ -75,7 +73,6 @@ export default function NetworkRoot() {
</Tabs>
</div>
{/* Scrollable content */}
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain pb-14 py-2">
<ParticleListView
streams={streams}
+9 -25
View File
@@ -10,8 +10,8 @@ export type StreamParticle = Particle & {
properties: StreamProperties;
};
const CLOSED_INITIAL_PAGE_SIZE = 50;
const CLOSED_PAGE_INCREMENT = 50;
const INITIAL_PAGE_SIZE = 12;
const PAGE_INCREMENT = 12;
// Stable where-constraint references so the Firestore subscription only
// re-attaches when the tab actually changes, not on every render.
@@ -28,11 +28,7 @@ function useVisibilityScopes(userId?: string, networkId?: string) {
}
interface UseStreamParticlesOptions {
/**
* Which streams to subscribe to. Open streams are loaded in full (bounded
* by active work — full realtime coverage is needed for autoplay/huddles).
* Closed streams are paginated via `loadMore`.
*/
// Which streams to subscribe to
status: 'open' | 'closed';
}
@@ -40,9 +36,9 @@ interface UseStreamParticlesResult {
streams: StreamParticle[];
isLoading: boolean;
networkId: string;
/** True when more closed streams may exist beyond the current window. */
/** True when more streams may exist beyond the current window. */
canLoadMore: boolean;
/** Extend the pagination window. No-op on the open tab. */
/** Extend the pagination window. */
loadMore: () => void;
}
@@ -54,21 +50,10 @@ export function useStreamParticles(
const user = useAuthStore((s) => s.user);
const visibilityScopes = useVisibilityScopes(user?.id, networkId);
const [closedLimit, setClosedLimit] = useState(CLOSED_INITIAL_PAGE_SIZE);
const [prevStatus, setPrevStatus] = useState(status);
// Switching back to the closed tab starts a fresh window, avoiding an
// ever-growing subscription across a long session.
if (status !== prevStatus) {
setPrevStatus(status);
if (status === 'closed') {
setClosedLimit(CLOSED_INITIAL_PAGE_SIZE);
}
}
const [limit, setLimit] = useState(INITIAL_PAGE_SIZE);
const whereFilter: QueryFieldFilterConstraint =
status === 'open' ? OPEN_STATUS_FILTER : CLOSED_STATUS_FILTER;
const limit = status === 'closed' ? closedLimit : undefined;
const { children, isLoading } = useLiveParticleChildren(path, {
orderByField: 'last_child_created_at',
@@ -85,12 +70,11 @@ export function useStreamParticles(
// Heuristic: if we got back as many items as we asked for, assume there
// might be more. Clicking load-more when there are no more is a no-op.
const canLoadMore = status === 'closed' && streams.length >= closedLimit;
const canLoadMore = streams.length >= limit;
const loadMore = useCallback(() => {
if (status !== 'closed') return;
setClosedLimit((prev) => prev + CLOSED_PAGE_INCREMENT);
}, [status]);
setLimit((prev) => prev + PAGE_INCREMENT);
}, []);
return { streams, isLoading, networkId, canLoadMore, loadMore };
}
+5
View File
@@ -55,4 +55,9 @@ export const electronPlatform: Platform = {
setDockBadge: (count) => window.electronApp.setDockBadge(count),
getVersion: () => window.electronApp.getVersion(),
},
deepLink: {
getPending: () => window.electronDeepLink.getPending(),
onNavigate: (cb) => window.electronDeepLink.onNavigate(cb),
},
};
+5
View File
@@ -60,6 +60,11 @@ export interface Platform {
setDockBadge: (count: number) => void;
getVersion: () => Promise<string>;
};
deepLink: {
getPending: () => Promise<string | null>;
onNavigate: (callback: (path: string) => void) => () => void;
};
}
export const DESKTOP_DOWNLOAD_URL = 'https://flowylabs.ai/llink/download';
+7
View File
@@ -117,4 +117,11 @@ export const webPlatform: Platform = {
setDockBadge: applyDockBadge,
getVersion: async () => __APP_VERSION__,
},
deepLink: {
getPending: () => Promise.resolve(null),
onNavigate: (_) => {
return () => {};
},
},
};