Simplify frontend room surfaces

This commit is contained in:
Yahya Alhinai
2026-06-28 14:24:04 +00:00
parent dec6557cad
commit 3cb66e3f27
2 changed files with 13 additions and 164 deletions
+5 -101
View File
@@ -2,14 +2,8 @@ import { useEffect, useState } from 'react';
import type { Room } from 'livekit-client'; import type { Room } from 'livekit-client';
import { import {
AlertCircleIcon, AlertCircleIcon,
BrainCircuitIcon,
CircleDotIcon,
RadioTowerIcon,
RefreshCwIcon, RefreshCwIcon,
SparklesIcon, SparklesIcon,
UsersIcon,
WifiIcon,
ShieldCheckIcon,
} from 'lucide-react'; } from 'lucide-react';
import type { Pod, PodInput } from '@podman/shared'; import type { Pod, PodInput } from '@podman/shared';
import { joinPod } from './lib/pod.js'; import { joinPod } from './lib/pod.js';
@@ -19,7 +13,6 @@ import { CreatePodForm } from './components/CreatePodForm.js';
import { PodView } from './components/PodView.js'; import { PodView } from './components/PodView.js';
import { GraphView } from './components/GraphView.js'; import { GraphView } from './components/GraphView.js';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
Card, Card,
@@ -40,7 +33,6 @@ import {
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
const SESSION_KEY = 'podman.session'; const SESSION_KEY = 'podman.session';
const fmt = new Intl.NumberFormat('en', { notation: 'compact' });
function pathPodId(): string | null { function pathPodId(): string | null {
const [segment] = window.location.pathname.split('/').filter(Boolean); const [segment] = window.location.pathname.split('/').filter(Boolean);
@@ -68,7 +60,6 @@ export default function App() {
const [pending, setPending] = useState<Set<string>>(new Set()); const [pending, setPending] = useState<Set<string>>(new Set());
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [presence, setPresence] = useState<Record<string, string[]>>({}); const [presence, setPresence] = useState<Record<string, string[]>>({});
const [memory, setMemory] = useState<api.MemoryStats | null>(null);
const [joinedPodId, setJoinedPodId] = useState<string | null>(null); const [joinedPodId, setJoinedPodId] = useState<string | null>(null);
const [member, setMember] = useState(''); const [member, setMember] = useState('');
const [devMode, setDevMode] = useState(false); const [devMode, setDevMode] = useState(false);
@@ -81,14 +72,12 @@ export default function App() {
async function refresh() { async function refresh() {
setLoading(true); setLoading(true);
try { try {
const [nextPods, nextPresence, nextMemory] = await Promise.all([ const [nextPods, nextPresence] = await Promise.all([
api.listPods(), api.listPods(),
api.getPresence().catch(() => presence), api.getPresence().catch(() => presence),
api.getMemoryStats().catch(() => memory),
]); ]);
setPods(nextPods); setPods(nextPods);
setPresence(nextPresence); setPresence(nextPresence);
setMemory(nextMemory);
setError(null); setError(null);
} catch (e) { } catch (e) {
setError((e as Error).message); setError((e as Error).message);
@@ -130,13 +119,9 @@ export default function App() {
let alive = true; let alive = true;
const tick = async () => { const tick = async () => {
try { try {
const [p, m] = await Promise.all([ const p = await api.getPresence();
api.getPresence(),
api.getMemoryStats().catch(() => memory),
]);
if (alive) { if (alive) {
setPresence(p); setPresence(p);
setMemory(m);
} }
} catch { } catch {
/* presence is best-effort */ /* presence is best-effort */
@@ -279,14 +264,6 @@ export default function App() {
} }
const showReconnecting = restoring || (joinedPodId !== null && joinedPod === null); const showReconnecting = restoring || (joinedPodId !== null && joinedPod === null);
const liveNames = Array.from(new Set(Object.values(presence).flat()));
const liveTotal = liveNames.length;
const totalMembers = pods.reduce((sum, p) => sum + p.members.length, 0);
const activeRooms = Object.values(presence).filter((names) => names.length > 0).length;
const podManOnline = liveNames.some((name) => name.toLowerCase() === 'podman');
const latestActivity = memory
? memory.observations + memory.collisions + memory.interventions + memory.outcomes
: 0;
if (joinedPod) { if (joinedPod) {
return ( return (
@@ -301,46 +278,22 @@ export default function App() {
return ( return (
<div className="min-h-screen bg-background text-foreground"> <div className="min-h-screen bg-background text-foreground">
<div className="mx-auto flex min-h-screen w-full max-w-[1440px] flex-col gap-6 px-4 py-4 sm:px-6 lg:px-8"> <div className="mx-auto flex min-h-screen w-full max-w-[1440px] flex-col gap-6 px-4 py-4 sm:px-6 lg:px-8">
<header className="sticky top-0 z-10 -mx-4 flex flex-col gap-5 border-b bg-background/86 px-4 pb-5 pt-2 backdrop-blur-xl sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> <header className="sticky top-0 z-10 -mx-4 border-b bg-background/86 px-4 pb-4 pt-2 backdrop-blur-xl sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
<div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm"> <div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm">
PM PM
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<div className="flex flex-wrap items-center gap-2"> <h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">PodMan</h1>
<h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">
PodMan
</h1>
<Badge variant={podManOnline ? 'default' : 'secondary'}>
<CircleDotIcon data-icon="inline-start" />
{podManOnline ? 'online' : 'standby'}
</Badge>
</div>
<p className="text-sm text-muted-foreground">
Live engineering rooms, team memory, and intervention routing.
</p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2">
<Badge variant="outline" className="h-8 rounded-lg px-3">
<ShieldCheckIcon data-icon="inline-start" />
Privacy-limited
</Badge>
<Button variant="outline" onClick={() => void refresh()} disabled={loading}> <Button variant="outline" onClick={() => void refresh()} disabled={loading}>
<RefreshCwIcon data-icon="inline-start" /> <RefreshCwIcon data-icon="inline-start" />
Refresh Refresh
</Button> </Button>
</div> </div>
</div>
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
<StatPill icon={WifiIcon} label="Live" value={fmt.format(liveTotal)} />
<StatPill icon={RadioTowerIcon} label="Rooms" value={fmt.format(activeRooms)} />
<StatPill icon={UsersIcon} label="Roster" value={fmt.format(totalMembers)} />
<StatPill icon={BrainCircuitIcon} label="Memory" value={fmt.format(latestActivity)} />
</div>
</header> </header>
{error && ( {error && (
@@ -374,9 +327,6 @@ export default function App() {
<p className="text-xs font-medium uppercase text-muted-foreground">Workspaces</p> <p className="text-xs font-medium uppercase text-muted-foreground">Workspaces</p>
<h2 className="text-xl font-semibold tracking-tight">Active pods</h2> <h2 className="text-xl font-semibold tracking-tight">Active pods</h2>
</div> </div>
<p className="max-w-xl text-sm leading-6 text-muted-foreground">
Join the room that matches your current workstream.
</p>
</div> </div>
{loading ? ( {loading ? (
@@ -420,21 +370,6 @@ export default function App() {
<aside className="flex flex-col gap-4"> <aside className="flex flex-col gap-4">
<CreatePodForm busy={pending.has('new')} onCreate={handleCreate} /> <CreatePodForm busy={pending.has('new')} onCreate={handleCreate} />
<Card>
<CardHeader>
<CardTitle>Operating brief</CardTitle>
<CardDescription>Current coordination signals.</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<BriefLine label="Notification default" value="Card" />
<BriefLine label="Escalation" value="Hermes voice only when urgent" />
<BriefLine label="Memory events" value={fmt.format(latestActivity)} />
<BriefLine
label="Live people"
value={liveNames.length ? liveNames.join(', ') : 'None'}
/>
</CardContent>
</Card>
</aside> </aside>
</main> </main>
)} )}
@@ -443,37 +378,6 @@ export default function App() {
); );
} }
function StatPill({
icon: Icon,
label,
value,
}: {
icon: React.ComponentType<{ className?: string }>;
label: string;
value: string;
}) {
return (
<div className="flex min-h-16 items-center gap-3 rounded-lg border bg-card/90 px-3 py-2 shadow-sm">
<div className="grid size-8 place-items-center rounded-md bg-muted">
<Icon className="size-4 text-muted-foreground" />
</div>
<div className="min-w-0">
<p className="text-xs font-medium uppercase text-muted-foreground">{label}</p>
<p className="text-base font-medium">{value}</p>
</div>
</div>
);
}
function BriefLine({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-start justify-between gap-4 rounded-md bg-muted/45 px-3 py-2">
<span className="text-sm text-muted-foreground">{label}</span>
<span className="max-w-44 text-right text-sm font-medium">{value}</span>
</div>
);
}
function PodSkeleton() { function PodSkeleton() {
return ( return (
<Card> <Card>
+4 -59
View File
@@ -51,7 +51,6 @@ import {
} from '../lib/api.js'; } from '../lib/api.js';
import { useInterventions, primeSpeech } from '../livekit/useInterventions.js'; import { useInterventions, primeSpeech } from '../livekit/useInterventions.js';
import { usePodActivity } from '../hooks/use-pod-activity.js'; import { usePodActivity } from '../hooks/use-pod-activity.js';
import LiveWaveform from '@/components/ruixen/live-waveform';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Avatar, AvatarBadge, AvatarFallback } from '@/components/ui/avatar'; import { Avatar, AvatarBadge, AvatarFallback } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@@ -145,7 +144,7 @@ export function PodView({
const [testingVoice, setTestingVoice] = useState(false); const [testingVoice, setTestingVoice] = useState(false);
const [note, setNote] = useState<string | null>(null); const [note, setNote] = useState<string | null>(null);
const [audioBlocked, setAudioBlocked] = useState(false); const [audioBlocked, setAudioBlocked] = useState(false);
const [remoteAudioTracks, setRemoteAudioTracks] = useState(0); const [, setRemoteAudioTracks] = useState(0);
const [micOn, setMicOn] = useState(false); const [micOn, setMicOn] = useState(false);
const [historyMember, setHistoryMember] = useState<string | null>(null); const [historyMember, setHistoryMember] = useState<string | null>(null);
const [history, setHistory] = useState<MemberWorkHistory | null>(null); const [history, setHistory] = useState<MemberWorkHistory | null>(null);
@@ -567,8 +566,6 @@ export function PodView({
} }
} }
const podmanPresent = participants.some((p) => p.name.toLowerCase() === 'podman');
return ( return (
<SidebarProvider <SidebarProvider
open={leftStreamOpen} open={leftStreamOpen}
@@ -586,7 +583,7 @@ export function PodView({
title="My stream" title="My stream"
collapsedLabel="Mine" collapsedLabel="Mine"
testId="my-stream-sidebar" testId="my-stream-sidebar"
description={`${me}'s live screen, git, intervention, and conflict log.`} description={`${me}'s activity feed.`}
events={activity.mine} events={activity.mine}
connected={activity.connected} connected={activity.connected}
open={leftStreamOpen} open={leftStreamOpen}
@@ -681,12 +678,6 @@ export function PodView({
</Button> </Button>
</div> </div>
</div> </div>
<div className="grid gap-3 sm:grid-cols-3">
<Metric label="Participants" value={participants.length || 1} />
<Metric label="Screen" value={sharing ? 'sharing' : 'idle'} />
<Metric label="PodMan" value={podmanPresent ? 'online' : 'waiting'} />
</div>
</section> </section>
{devMode && ( {devMode && (
@@ -922,41 +913,6 @@ export function PodView({
</CardContent> </CardContent>
</Card> </Card>
<Card>
<CardHeader>
<CardTitle>Status</CardTitle>
<CardDescription>Connection, media, and agent presence.</CardDescription>
</CardHeader>
<CardContent>
<LiveWaveform
processing={!!room || beat.on}
active={false}
height={32}
barWidth={2}
barGap={3}
className="mb-4 px-3 py-2 text-muted-foreground"
/>
<div className="flex flex-col gap-3">
<StatusLine label="LiveKit" value={room ? 'connected' : 'offline'} />
<StatusLine label="Screen" value={sharing ? 'published' : 'not shared'} />
<StatusLine
label="Audio"
value={
audioBlocked
? 'blocked'
: remoteAudioTracks > 0
? `${remoteAudioTracks} remote track${remoteAudioTracks === 1 ? '' : 's'}`
: beat.on
? beat.mine
? 'publishing'
: `${beat.by} playing`
: 'ready'
}
/>
<StatusLine label="Agent" value={podmanPresent ? 'watching' : 'waiting'} />
</div>
</CardContent>
</Card>
</aside> </aside>
</main> </main>
<div <div
@@ -980,7 +936,7 @@ export function PodView({
title="Team stream" title="Team stream"
collapsedLabel="Team" collapsedLabel="Team"
testId="team-stream-sidebar" testId="team-stream-sidebar"
description="Everyone else in this pod, merged into one realtime feed." description="Team activity feed."
events={activity.team} events={activity.team}
connected={activity.connected} connected={activity.connected}
open={rightStreamOpen} open={rightStreamOpen}
@@ -993,17 +949,6 @@ export function PodView({
); );
} }
function Metric({ label, value }: { label: string; value: number | string }) {
return (
<Card size="sm">
<CardContent>
<p className="text-xs text-muted-foreground">{label}</p>
<p className="mt-1 text-lg font-medium">{value}</p>
</CardContent>
</Card>
);
}
function Participant({ function Participant({
participant, participant,
onOpenHistory, onOpenHistory,
@@ -1548,7 +1493,7 @@ const ACTIVITY_CATEGORIES: {
{ {
id: 'signal', id: 'signal',
label: 'Signals', label: 'Signals',
hint: 'Screen logs and local git activity routed to this stream.', hint: 'Recent screen and git activity.',
icon: RadioTowerIcon, icon: RadioTowerIcon,
}, },
{ {