fix: add missing Rust imports for Windows build and check in pending changes
Add `use std::hash::{DefaultHasher, Hash, Hasher}` behind #[cfg(windows)]
in runtime_manager.rs — these types are used in hash_seed() which only
compiles on Windows, causing CI failure.
Also includes: app component refactors (agent-list, agent-status-bar,
stream-strategy-web), website index updates, server dep additions
(fast-uri, rotating-file-stream sort), lockfile sync, and removal of
RUNTIME_SIMPLIFICATION_PLAN.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
02d74777b2
commit
7f11b93e0f
@@ -6,187 +6,126 @@ import {
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
type ListRenderItem,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useCallback, useMemo, useState, type ReactElement } from "react";
|
||||
import { router, usePathname, type Href } from "expo-router";
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||
import { formatTimeAgo } from "@/utils/time";
|
||||
import { shortenPath } from "@/utils/shorten-path";
|
||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { AgentStatusDot } from "@/components/agent-status-dot";
|
||||
import {
|
||||
buildAgentNavigationKey,
|
||||
startNavigationTiming,
|
||||
} from "@/utils/navigation-timing";
|
||||
import { buildHostWorkspaceAgentRoute } from "@/utils/host-routes";
|
||||
} from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useCallback, useMemo, useState, type ReactElement } from 'react'
|
||||
import { router, usePathname, type Href } from 'expo-router'
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
|
||||
import { formatTimeAgo } from '@/utils/time'
|
||||
import { shortenPath } from '@/utils/shorten-path'
|
||||
import { type AggregatedAgent } from '@/hooks/use-aggregated-agents'
|
||||
import { useSessionStore } from '@/stores/session-store'
|
||||
import { AgentStatusDot } from '@/components/agent-status-dot'
|
||||
import { buildAgentNavigationKey, startNavigationTiming } from '@/utils/navigation-timing'
|
||||
import { buildHostWorkspaceAgentRoute } from '@/utils/host-routes'
|
||||
|
||||
interface AgentListProps {
|
||||
agents: AggregatedAgent[];
|
||||
showCheckoutInfo?: boolean;
|
||||
isRefreshing?: boolean;
|
||||
onRefresh?: () => void;
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: () => void;
|
||||
listFooterComponent?: ReactElement | null;
|
||||
agents: AggregatedAgent[]
|
||||
showCheckoutInfo?: boolean
|
||||
isRefreshing?: boolean
|
||||
onRefresh?: () => void
|
||||
selectedAgentId?: string
|
||||
onAgentSelect?: () => void
|
||||
listFooterComponent?: ReactElement | null
|
||||
}
|
||||
|
||||
interface AgentListSection {
|
||||
key: string;
|
||||
title: string;
|
||||
data: AggregatedAgent[];
|
||||
key: string
|
||||
title: string
|
||||
data: AggregatedAgent[]
|
||||
}
|
||||
|
||||
type SessionColumnKey = "session" | "project" | "host" | "status" | "updated";
|
||||
|
||||
interface SessionColumnDefinition {
|
||||
key: SessionColumnKey;
|
||||
label: string;
|
||||
flex: number;
|
||||
align?: "left" | "right";
|
||||
mobile?: boolean;
|
||||
requiresMultiHost?: boolean;
|
||||
}
|
||||
|
||||
const SESSION_COLUMNS: SessionColumnDefinition[] = [
|
||||
{ key: "session", label: "Session", flex: 2.3, mobile: true },
|
||||
{ key: "project", label: "Project", flex: 2.6 },
|
||||
{ key: "host", label: "Host", flex: 1.2, requiresMultiHost: true },
|
||||
{ key: "status", label: "Status", flex: 1.2, mobile: true },
|
||||
{ key: "updated", label: "Updated", flex: 1, align: "right", mobile: true },
|
||||
];
|
||||
|
||||
function deriveDateSectionLabel(lastActivityAt: Date): string {
|
||||
const now = new Date();
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000);
|
||||
const now = new Date()
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000)
|
||||
const activityStart = new Date(
|
||||
lastActivityAt.getFullYear(),
|
||||
lastActivityAt.getMonth(),
|
||||
lastActivityAt.getDate()
|
||||
);
|
||||
)
|
||||
|
||||
if (activityStart.getTime() >= todayStart.getTime()) {
|
||||
return "Today";
|
||||
return 'Today'
|
||||
}
|
||||
if (activityStart.getTime() >= yesterdayStart.getTime()) {
|
||||
return "Yesterday";
|
||||
return 'Yesterday'
|
||||
}
|
||||
|
||||
const diffTime = todayStart.getTime() - activityStart.getTime();
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
const diffTime = todayStart.getTime() - activityStart.getTime()
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
||||
if (diffDays <= 7) {
|
||||
return "This week";
|
||||
return 'This week'
|
||||
}
|
||||
if (diffDays <= 30) {
|
||||
return "This month";
|
||||
return 'This month'
|
||||
}
|
||||
return "Older";
|
||||
return 'Older'
|
||||
}
|
||||
|
||||
function formatStatusLabel(status: AggregatedAgent["status"]): string {
|
||||
function formatStatusLabel(status: AggregatedAgent['status']): string {
|
||||
switch (status) {
|
||||
case "initializing":
|
||||
return "Starting";
|
||||
case "idle":
|
||||
return "Idle";
|
||||
case "running":
|
||||
return "Running";
|
||||
case "error":
|
||||
return "Error";
|
||||
case "closed":
|
||||
return "Closed";
|
||||
case 'initializing':
|
||||
return 'Starting'
|
||||
case 'idle':
|
||||
return 'Idle'
|
||||
case 'running':
|
||||
return 'Running'
|
||||
case 'error':
|
||||
return 'Error'
|
||||
case 'closed':
|
||||
return 'Closed'
|
||||
default:
|
||||
return status;
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibleColumns(input: {
|
||||
isMobile: boolean;
|
||||
showHostColumn: boolean;
|
||||
}): SessionColumnDefinition[] {
|
||||
return SESSION_COLUMNS.filter((column) => {
|
||||
if (!input.showHostColumn && column.requiresMultiHost) {
|
||||
return false;
|
||||
}
|
||||
if (input.isMobile && !column.mobile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function SessionCell({
|
||||
align = "left",
|
||||
flex,
|
||||
children,
|
||||
}: {
|
||||
align?: "left" | "right";
|
||||
flex: number;
|
||||
children: ReactElement;
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.cell,
|
||||
{ flex },
|
||||
align === "right" ? styles.cellRight : styles.cellLeft,
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionBadge({
|
||||
label,
|
||||
tone = "neutral",
|
||||
tone = 'neutral',
|
||||
}: {
|
||||
label: string;
|
||||
tone?: "neutral" | "warning" | "danger";
|
||||
label: string
|
||||
tone?: 'neutral' | 'warning' | 'danger'
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.badge,
|
||||
tone === "warning" && styles.badgeWarning,
|
||||
tone === "danger" && styles.badgeDanger,
|
||||
tone === 'warning' && styles.badgeWarning,
|
||||
tone === 'danger' && styles.badgeDanger,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.badgeText,
|
||||
tone === "warning" && styles.badgeTextWarning,
|
||||
tone === "danger" && styles.badgeTextDanger,
|
||||
tone === 'warning' && styles.badgeTextWarning,
|
||||
tone === 'danger' && styles.badgeTextDanger,
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function SessionTableRow({
|
||||
function SessionRow({
|
||||
agent,
|
||||
columns,
|
||||
isMobile,
|
||||
selectedAgentId,
|
||||
onPress,
|
||||
onLongPress,
|
||||
}: {
|
||||
agent: AggregatedAgent;
|
||||
columns: SessionColumnDefinition[];
|
||||
isMobile: boolean;
|
||||
selectedAgentId?: string;
|
||||
onPress: (agent: AggregatedAgent) => void;
|
||||
onLongPress: (agent: AggregatedAgent) => void;
|
||||
agent: AggregatedAgent
|
||||
isMobile: boolean
|
||||
selectedAgentId?: string
|
||||
onPress: (agent: AggregatedAgent) => void
|
||||
onLongPress: (agent: AggregatedAgent) => void
|
||||
}) {
|
||||
const timeAgo = formatTimeAgo(agent.lastActivityAt);
|
||||
const agentKey = `${agent.serverId}:${agent.id}`;
|
||||
const isSelected = selectedAgentId === agentKey;
|
||||
const statusLabel = formatStatusLabel(agent.status);
|
||||
const projectPath = shortenPath(agent.cwd);
|
||||
const timeAgo = formatTimeAgo(agent.lastActivityAt)
|
||||
const agentKey = `${agent.serverId}:${agent.id}`
|
||||
const isSelected = selectedAgentId === agentKey
|
||||
const statusLabel = formatStatusLabel(agent.status)
|
||||
const projectPath = shortenPath(agent.cwd)
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@@ -200,128 +139,64 @@ function SessionTableRow({
|
||||
onLongPress={() => onLongPress(agent)}
|
||||
testID={`agent-row-${agent.serverId}-${agent.id}`}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<View style={styles.rowInner}>
|
||||
{columns.map((column) => {
|
||||
if (column.key === "session") {
|
||||
return (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<View style={styles.primaryCell}>
|
||||
<View style={styles.sessionTitleRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles.sessionTitle,
|
||||
(isSelected || hovered) && styles.sessionTitleHighlighted,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{agent.title || "New session"}
|
||||
</Text>
|
||||
{agent.archivedAt ? <SessionBadge label="Archived" /> : null}
|
||||
{(agent.pendingPermissionCount ?? 0) > 0 ? (
|
||||
<SessionBadge
|
||||
label={`${agent.pendingPermissionCount} pending`}
|
||||
tone="warning"
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
{isMobile ? (
|
||||
<View style={styles.sessionMetaRow}>
|
||||
<Text style={styles.sessionMetaText} numberOfLines={1}>
|
||||
{projectPath}
|
||||
</Text>
|
||||
<Text style={styles.sessionMetaSeparator}>·</Text>
|
||||
<Text style={styles.sessionMetaText}>{statusLabel}</Text>
|
||||
{agent.serverLabel ? (
|
||||
<>
|
||||
<Text style={styles.sessionMetaSeparator}>·</Text>
|
||||
<Text style={styles.sessionMetaText} numberOfLines={1}>
|
||||
{agent.serverLabel}
|
||||
</Text>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.secondaryBadgeRow}>
|
||||
{agent.requiresAttention ? (
|
||||
<SessionBadge label="Attention" tone="danger" />
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</SessionCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.key === "project") {
|
||||
return (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<View style={styles.projectCell}>
|
||||
<Text style={styles.projectPath} numberOfLines={1}>
|
||||
{projectPath}
|
||||
</Text>
|
||||
<Text style={styles.projectProvider} numberOfLines={1}>
|
||||
{agent.provider}
|
||||
</Text>
|
||||
</View>
|
||||
</SessionCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.key === "host") {
|
||||
return (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<Text style={styles.hostText} numberOfLines={1}>
|
||||
{agent.serverLabel}
|
||||
</Text>
|
||||
</SessionCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.key === "status") {
|
||||
return (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<View style={styles.statusCell}>
|
||||
<AgentStatusDot
|
||||
status={agent.status}
|
||||
requiresAttention={agent.requiresAttention}
|
||||
/>
|
||||
<Text style={styles.statusText} numberOfLines={1}>
|
||||
{statusLabel}
|
||||
</Text>
|
||||
</View>
|
||||
</SessionCell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<Text style={styles.updatedText} numberOfLines={1}>
|
||||
{timeAgo}
|
||||
</Text>
|
||||
</SessionCell>
|
||||
);
|
||||
})}
|
||||
<View style={styles.rowLeading}>
|
||||
<AgentStatusDot status={agent.status} requiresAttention={agent.requiresAttention} />
|
||||
</View>
|
||||
<View style={styles.rowContent}>
|
||||
<View style={styles.rowTitleRow}>
|
||||
<Text
|
||||
style={[styles.sessionTitle, isSelected && styles.sessionTitleHighlighted]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{agent.title || 'New session'}
|
||||
</Text>
|
||||
{agent.archivedAt ? <SessionBadge label="Archived" /> : null}
|
||||
{(agent.pendingPermissionCount ?? 0) > 0 ? (
|
||||
<SessionBadge label={`${agent.pendingPermissionCount} pending`} tone="warning" />
|
||||
) : null}
|
||||
{!isMobile && agent.requiresAttention ? (
|
||||
<SessionBadge label="Attention" tone="danger" />
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.rowMetaRow}>
|
||||
<Text style={styles.sessionMetaText} numberOfLines={1}>
|
||||
{projectPath}
|
||||
</Text>
|
||||
<Text style={styles.sessionMetaSeparator}>·</Text>
|
||||
<Text style={styles.sessionMetaText}>{statusLabel}</Text>
|
||||
<Text style={styles.sessionMetaSeparator}>·</Text>
|
||||
<Text style={styles.sessionMetaText}>{timeAgo}</Text>
|
||||
{agent.serverLabel ? (
|
||||
<>
|
||||
<Text style={styles.sessionMetaSeparator}>·</Text>
|
||||
<Text style={styles.sessionMetaText} numberOfLines={1}>
|
||||
{agent.serverLabel}
|
||||
</Text>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
{isMobile && agent.requiresAttention ? (
|
||||
<View style={styles.rowTrailing}>
|
||||
<SessionBadge label="Attention" tone="danger" />
|
||||
</View>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function SessionTableSection({
|
||||
section,
|
||||
columns,
|
||||
isMobile,
|
||||
selectedAgentId,
|
||||
onAgentPress,
|
||||
onAgentLongPress,
|
||||
}: {
|
||||
section: AgentListSection;
|
||||
columns: SessionColumnDefinition[];
|
||||
isMobile: boolean;
|
||||
selectedAgentId?: string;
|
||||
onAgentPress: (agent: AggregatedAgent) => void;
|
||||
onAgentLongPress: (agent: AggregatedAgent) => void;
|
||||
section: AgentListSection
|
||||
isMobile: boolean
|
||||
selectedAgentId?: string
|
||||
onAgentPress: (agent: AggregatedAgent) => void
|
||||
onAgentLongPress: (agent: AggregatedAgent) => void
|
||||
}) {
|
||||
return (
|
||||
<View style={styles.sectionBlock}>
|
||||
@@ -330,31 +205,14 @@ function SessionTableSection({
|
||||
<View style={styles.sectionLine} />
|
||||
</View>
|
||||
|
||||
<View style={styles.tableCard}>
|
||||
<View style={styles.tableHeader}>
|
||||
{columns.map((column) => (
|
||||
<SessionCell key={column.key} flex={column.flex} align={column.align}>
|
||||
<Text
|
||||
style={[
|
||||
styles.columnLabel,
|
||||
column.align === "right" && styles.columnLabelRight,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{column.label}
|
||||
</Text>
|
||||
</SessionCell>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={styles.listCard}>
|
||||
{section.data.map((agent, index) => (
|
||||
<View
|
||||
key={`${agent.serverId}:${agent.id}`}
|
||||
style={index > 0 ? styles.rowDivider : undefined}
|
||||
>
|
||||
<SessionTableRow
|
||||
<SessionRow
|
||||
agent={agent}
|
||||
columns={columns}
|
||||
isMobile={isMobile}
|
||||
selectedAgentId={selectedAgentId}
|
||||
onPress={onAgentPress}
|
||||
@@ -364,7 +222,7 @@ function SessionTableSection({
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentList({
|
||||
@@ -375,110 +233,96 @@ export function AgentList({
|
||||
onAgentSelect,
|
||||
listFooterComponent,
|
||||
}: AgentListProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const pathname = usePathname();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
const { theme } = useUnistyles()
|
||||
const pathname = usePathname()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null)
|
||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||
|
||||
const actionClient = useSessionStore((state) =>
|
||||
actionAgent?.serverId ? state.sessions[actionAgent.serverId]?.client ?? null : null
|
||||
);
|
||||
actionAgent?.serverId ? (state.sessions[actionAgent.serverId]?.client ?? null) : null
|
||||
)
|
||||
|
||||
const isActionSheetVisible = actionAgent !== null;
|
||||
const isActionDaemonUnavailable = Boolean(actionAgent?.serverId && !actionClient);
|
||||
const showHostColumn = useMemo(
|
||||
() => new Set(agents.map((agent) => agent.serverId)).size > 1,
|
||||
[agents]
|
||||
);
|
||||
const columns = useMemo(
|
||||
() => getVisibleColumns({ isMobile, showHostColumn }),
|
||||
[isMobile, showHostColumn]
|
||||
);
|
||||
const isActionSheetVisible = actionAgent !== null
|
||||
const isActionDaemonUnavailable = Boolean(actionAgent?.serverId && !actionClient)
|
||||
|
||||
const handleAgentPress = useCallback(
|
||||
(agent: AggregatedAgent) => {
|
||||
if (isActionSheetVisible) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const serverId = agent.serverId;
|
||||
const agentId = agent.id;
|
||||
const navigationKey = buildAgentNavigationKey(serverId, agentId);
|
||||
const serverId = agent.serverId
|
||||
const agentId = agent.id
|
||||
const navigationKey = buildAgentNavigationKey(serverId, agentId)
|
||||
startNavigationTiming(navigationKey, {
|
||||
from: "home",
|
||||
to: "agent",
|
||||
from: 'home',
|
||||
to: 'agent',
|
||||
params: { serverId, agentId },
|
||||
});
|
||||
})
|
||||
|
||||
const shouldReplace = pathname.startsWith("/h/");
|
||||
const navigate = shouldReplace ? router.replace : router.push;
|
||||
const shouldReplace = pathname.startsWith('/h/')
|
||||
const navigate = shouldReplace ? router.replace : router.push
|
||||
|
||||
onAgentSelect?.();
|
||||
onAgentSelect?.()
|
||||
|
||||
const route: Href = buildHostWorkspaceAgentRoute(
|
||||
serverId,
|
||||
agent.cwd,
|
||||
agentId
|
||||
) as Href;
|
||||
navigate(route);
|
||||
const route: Href = buildHostWorkspaceAgentRoute(serverId, agent.cwd, agentId) as Href
|
||||
navigate(route)
|
||||
},
|
||||
[isActionSheetVisible, pathname, onAgentSelect]
|
||||
);
|
||||
)
|
||||
|
||||
const handleAgentLongPress = useCallback((agent: AggregatedAgent) => {
|
||||
setActionAgent(agent);
|
||||
}, []);
|
||||
setActionAgent(agent)
|
||||
}, [])
|
||||
|
||||
const handleCloseActionSheet = useCallback(() => {
|
||||
setActionAgent(null);
|
||||
}, []);
|
||||
setActionAgent(null)
|
||||
}, [])
|
||||
|
||||
const handleArchiveAgent = useCallback(() => {
|
||||
if (!actionAgent || !actionClient) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
void actionClient.archiveAgent(actionAgent.id);
|
||||
setActionAgent(null);
|
||||
}, [actionAgent, actionClient]);
|
||||
void actionClient.archiveAgent(actionAgent.id)
|
||||
setActionAgent(null)
|
||||
}, [actionAgent, actionClient])
|
||||
|
||||
const sections = useMemo((): AgentListSection[] => {
|
||||
const order = ["Today", "Yesterday", "This week", "This month", "Older"] as const;
|
||||
const buckets = new Map<string, AggregatedAgent[]>();
|
||||
const order = ['Today', 'Yesterday', 'This week', 'This month', 'Older'] as const
|
||||
const buckets = new Map<string, AggregatedAgent[]>()
|
||||
for (const agent of agents) {
|
||||
const label = deriveDateSectionLabel(agent.lastActivityAt);
|
||||
const existing = buckets.get(label) ?? [];
|
||||
existing.push(agent);
|
||||
buckets.set(label, existing);
|
||||
const label = deriveDateSectionLabel(agent.lastActivityAt)
|
||||
const existing = buckets.get(label) ?? []
|
||||
existing.push(agent)
|
||||
buckets.set(label, existing)
|
||||
}
|
||||
|
||||
const result: AgentListSection[] = [];
|
||||
const result: AgentListSection[] = []
|
||||
for (const label of order) {
|
||||
const data = buckets.get(label);
|
||||
const data = buckets.get(label)
|
||||
if (!data || data.length === 0) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
result.push({ key: `date:${label}`, title: label, data });
|
||||
result.push({ key: `date:${label}`, title: label, data })
|
||||
}
|
||||
return result;
|
||||
}, [agents]);
|
||||
return result
|
||||
}, [agents])
|
||||
|
||||
const renderSection: ListRenderItem<AgentListSection> = useCallback(
|
||||
({ item: section }) => (
|
||||
<SessionTableSection
|
||||
section={section}
|
||||
columns={columns}
|
||||
isMobile={isMobile}
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentPress={handleAgentPress}
|
||||
onAgentLongPress={handleAgentLongPress}
|
||||
/>
|
||||
),
|
||||
[columns, handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId]
|
||||
);
|
||||
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId]
|
||||
)
|
||||
|
||||
const keyExtractor = useCallback((section: AgentListSection) => section.key, []);
|
||||
const keyExtractor = useCallback((section: AgentListSection) => section.key, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -510,10 +354,7 @@ export function AgentList({
|
||||
onRequestClose={handleCloseActionSheet}
|
||||
>
|
||||
<View style={styles.sheetOverlay}>
|
||||
<Pressable
|
||||
style={styles.sheetBackdrop}
|
||||
onPress={handleCloseActionSheet}
|
||||
/>
|
||||
<Pressable style={styles.sheetBackdrop} onPress={handleCloseActionSheet} />
|
||||
<View
|
||||
style={[
|
||||
styles.sheetContainer,
|
||||
@@ -522,7 +363,7 @@ export function AgentList({
|
||||
>
|
||||
<View style={styles.sheetHandle} />
|
||||
<Text style={styles.sheetTitle}>
|
||||
{isActionDaemonUnavailable ? "Host offline" : "Archive this session?"}
|
||||
{isActionDaemonUnavailable ? 'Host offline' : 'Archive this session?'}
|
||||
</Text>
|
||||
<View style={styles.sheetButtonRow}>
|
||||
<Pressable
|
||||
@@ -552,7 +393,7 @@ export function AgentList({
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
@@ -573,17 +414,17 @@ const styles = StyleSheet.create((theme) => ({
|
||||
marginTop: theme.spacing[2],
|
||||
},
|
||||
sectionHeading: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
marginBottom: theme.spacing[2],
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: "600",
|
||||
fontWeight: '600',
|
||||
color: theme.colors.foregroundMuted,
|
||||
textTransform: "uppercase",
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
sectionLine: {
|
||||
@@ -591,100 +432,66 @@ const styles = StyleSheet.create((theme) => ({
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
tableCard: {
|
||||
overflow: "hidden",
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: theme.colors.surface2,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
tableHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: {
|
||||
xs: theme.spacing[3],
|
||||
md: theme.spacing[4],
|
||||
},
|
||||
paddingVertical: theme.spacing[2],
|
||||
backgroundColor: theme.colors.surface0,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: theme.colors.surface2,
|
||||
},
|
||||
columnLabel: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: "600",
|
||||
color: theme.colors.foregroundMuted,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
columnLabelRight: {
|
||||
textAlign: "right",
|
||||
listCard: {
|
||||
overflow: 'hidden',
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
},
|
||||
rowDivider: {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: theme.colors.surface2,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
row: {
|
||||
paddingHorizontal: {
|
||||
xs: theme.spacing[3],
|
||||
md: theme.spacing[4],
|
||||
},
|
||||
paddingVertical: {
|
||||
xs: theme.spacing[2],
|
||||
md: theme.spacing[3],
|
||||
},
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
marginBottom: theme.spacing[1],
|
||||
},
|
||||
rowInner: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[3],
|
||||
rowLeading: {
|
||||
marginRight: theme.spacing[3],
|
||||
},
|
||||
rowContent: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
rowTitleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
rowMetaRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing[1],
|
||||
marginTop: 2,
|
||||
},
|
||||
rowTrailing: {
|
||||
marginLeft: theme.spacing[2],
|
||||
},
|
||||
rowSelected: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
rowHovered: {
|
||||
backgroundColor: theme.colors.surface0,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
rowPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
cell: {
|
||||
minWidth: 0,
|
||||
},
|
||||
cellLeft: {
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
cellRight: {
|
||||
alignItems: "flex-end",
|
||||
},
|
||||
primaryCell: {
|
||||
width: "100%",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
sessionTitleRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
sessionTitle: {
|
||||
flexShrink: 1,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: "500",
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: '500',
|
||||
color: theme.colors.foreground,
|
||||
opacity: 0.86,
|
||||
},
|
||||
sessionTitleHighlighted: {
|
||||
opacity: 1,
|
||||
},
|
||||
sessionMetaRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
sessionMetaText: {
|
||||
maxWidth: "100%",
|
||||
maxWidth: '100%',
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
@@ -693,42 +500,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
opacity: 0.7,
|
||||
},
|
||||
secondaryBadgeRow: {
|
||||
minHeight: theme.spacing[6],
|
||||
justifyContent: "center",
|
||||
},
|
||||
projectCell: {
|
||||
width: "100%",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
projectPath: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
projectProvider: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
hostText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
statusCell: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
statusText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
updatedText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
textAlign: "right",
|
||||
},
|
||||
badge: {
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
@@ -736,16 +507,16 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
badgeWarning: {
|
||||
backgroundColor: "rgba(245, 158, 11, 0.12)",
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.12)',
|
||||
},
|
||||
badgeDanger: {
|
||||
backgroundColor: "rgba(239, 68, 68, 0.14)",
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.14)',
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: "600",
|
||||
fontWeight: '600',
|
||||
color: theme.colors.foregroundMuted,
|
||||
textTransform: "uppercase",
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.4,
|
||||
},
|
||||
badgeTextWarning: {
|
||||
@@ -756,26 +527,26 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
sheetOverlay: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
sheetBackdrop: {
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.35)",
|
||||
backgroundColor: 'rgba(0,0,0,0.35)',
|
||||
},
|
||||
sheetContainer: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderTopLeftRadius: theme.borderRadius["2xl"],
|
||||
borderTopRightRadius: theme.borderRadius["2xl"],
|
||||
borderTopLeftRadius: theme.borderRadius['2xl'],
|
||||
borderTopRightRadius: theme.borderRadius['2xl'],
|
||||
paddingHorizontal: theme.spacing[6],
|
||||
paddingTop: theme.spacing[4],
|
||||
gap: theme.spacing[4],
|
||||
},
|
||||
sheetHandle: {
|
||||
alignSelf: "center",
|
||||
alignSelf: 'center',
|
||||
width: 40,
|
||||
height: 4,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
@@ -786,18 +557,18 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
color: theme.colors.foreground,
|
||||
textAlign: "center",
|
||||
textAlign: 'center',
|
||||
},
|
||||
sheetButtonRow: {
|
||||
flexDirection: "row",
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing[3],
|
||||
},
|
||||
sheetButton: {
|
||||
flex: 1,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
paddingVertical: theme.spacing[4],
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
sheetArchiveButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
@@ -818,4 +589,4 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user