Merge branch 'agent-header-styling'
This commit is contained in:
@@ -14,12 +14,12 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
import { shortenPath } from "@/utils/shorten-path";
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
|
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
||||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
CHECKOUT_STATUS_STALE_TIME,
|
CHECKOUT_STATUS_STALE_TIME,
|
||||||
checkoutStatusQueryKey,
|
checkoutStatusQueryKey,
|
||||||
type CheckoutStatusPayload,
|
|
||||||
useCheckoutStatusCacheOnly,
|
useCheckoutStatusCacheOnly,
|
||||||
} from "@/hooks/use-checkout-status-query";
|
} from "@/hooks/use-checkout-status-query";
|
||||||
import {
|
import {
|
||||||
@@ -105,35 +105,6 @@ export function AgentList({
|
|||||||
setActionAgent(null);
|
setActionAgent(null);
|
||||||
}, [actionAgent, deleteAgent]);
|
}, [actionAgent, deleteAgent]);
|
||||||
|
|
||||||
const deriveBranchLabel = useCallback((checkout: CheckoutStatusPayload | null): string | null => {
|
|
||||||
if (!checkout || !checkout.isGit) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const currentBranch: string | null = checkout.currentBranch ?? null;
|
|
||||||
const baseRef: string | null = checkout.baseRef ?? null;
|
|
||||||
if (!currentBranch) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (baseRef && currentBranch === baseRef) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return currentBranch;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const deriveProjectPath = useCallback(
|
|
||||||
(agent: AggregatedAgent, checkout: CheckoutStatusPayload | null): string => {
|
|
||||||
const basePath = checkout?.isGit ? (checkout.repoRoot ?? agent.cwd) : agent.cwd;
|
|
||||||
const worktreeMarker = ".paseo/worktrees/";
|
|
||||||
const idx = basePath.indexOf(worktreeMarker);
|
|
||||||
if (idx !== -1) {
|
|
||||||
const afterMarker = basePath.slice(idx + worktreeMarker.length);
|
|
||||||
return afterMarker;
|
|
||||||
}
|
|
||||||
return basePath;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewabilityConfig = useMemo(
|
const viewabilityConfig = useMemo(
|
||||||
() => ({ itemVisiblePercentThreshold: 30 }),
|
() => ({ itemVisiblePercentThreshold: 30 }),
|
||||||
[]
|
[]
|
||||||
@@ -190,7 +161,7 @@ export function AgentList({
|
|||||||
agentId: agent.id,
|
agentId: agent.id,
|
||||||
});
|
});
|
||||||
const checkout = checkoutQuery.data ?? null;
|
const checkout = checkoutQuery.data ?? null;
|
||||||
const projectPath = deriveProjectPath(agent, checkout);
|
const projectPath = deriveProjectPath(agent.cwd, checkout);
|
||||||
const branchLabel = deriveBranchLabel(checkout);
|
const branchLabel = deriveBranchLabel(checkout);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -233,8 +204,6 @@ export function AgentList({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
deriveBranchLabel,
|
|
||||||
deriveProjectPath,
|
|
||||||
handleAgentLongPress,
|
handleAgentLongPress,
|
||||||
handleAgentPress,
|
handleAgentPress,
|
||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Pressable, Text } from "react-native";
|
import { Pressable, Text, View } from "react-native";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { Menu, PanelLeft } from "lucide-react-native";
|
import { Menu, PanelLeft } from "lucide-react-native";
|
||||||
import { ScreenHeader } from "./screen-header";
|
import { ScreenHeader } from "./screen-header";
|
||||||
@@ -7,10 +7,11 @@ import { usePanelStore } from "@/stores/panel-store";
|
|||||||
|
|
||||||
interface MenuHeaderProps {
|
interface MenuHeaderProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
rightContent?: ReactNode;
|
rightContent?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MenuHeader({ title, rightContent }: MenuHeaderProps) {
|
export function MenuHeader({ title, subtitle, rightContent }: MenuHeaderProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
@@ -32,9 +33,16 @@ export function MenuHeader({ title, rightContent }: MenuHeaderProps) {
|
|||||||
<MenuIcon size={16} color={menuIconColor} />
|
<MenuIcon size={16} color={menuIconColor} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{title && (
|
{title && (
|
||||||
<Text style={styles.title} numberOfLines={1}>
|
<View style={styles.titleContainer}>
|
||||||
{title}
|
<Text style={styles.title} numberOfLines={1}>
|
||||||
</Text>
|
{title}
|
||||||
|
</Text>
|
||||||
|
{subtitle && (
|
||||||
|
<Text style={styles.subtitle} numberOfLines={1}>
|
||||||
|
{subtitle}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -55,13 +63,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
},
|
},
|
||||||
title: {
|
titleContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: theme.fontSize.lg,
|
gap: theme.spacing[0],
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
fontWeight: {
|
fontWeight: {
|
||||||
xs: theme.fontWeight.semibold,
|
xs: "400",
|
||||||
md: "400",
|
md: "300",
|
||||||
},
|
},
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
fontWeight: "300",
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ import {
|
|||||||
import { extractAgentModel } from "@/utils/extract-agent-model";
|
import { extractAgentModel } from "@/utils/extract-agent-model";
|
||||||
import { startPerfMonitor } from "@/utils/perf-monitor";
|
import { startPerfMonitor } from "@/utils/perf-monitor";
|
||||||
import { shortenPath } from "@/utils/shorten-path";
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
|
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
||||||
|
import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -406,6 +408,13 @@ function AgentScreenContent({
|
|||||||
? branchError ?? "Unavailable"
|
? branchError ?? "Unavailable"
|
||||||
: branchLabel ?? "Unknown";
|
: branchLabel ?? "Unknown";
|
||||||
|
|
||||||
|
// Checkout status for header subtitle
|
||||||
|
const checkoutStatusQuery = useCheckoutStatusQuery({
|
||||||
|
serverId,
|
||||||
|
agentId: resolvedAgentId ?? "",
|
||||||
|
});
|
||||||
|
const checkout = checkoutStatusQuery.status;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!resolvedAgentId) {
|
if (!resolvedAgentId) {
|
||||||
setFocusedAgentId(null);
|
setFocusedAgentId(null);
|
||||||
@@ -490,6 +499,20 @@ function AgentScreenContent({
|
|||||||
|
|
||||||
const effectiveAgent = agent ?? placeholderAgent;
|
const effectiveAgent = agent ?? placeholderAgent;
|
||||||
|
|
||||||
|
// Header subtitle: project path + branch (matching agent list row format)
|
||||||
|
const headerProjectPath = effectiveAgent
|
||||||
|
? deriveProjectPath(effectiveAgent.cwd, checkout)
|
||||||
|
: null;
|
||||||
|
const headerBranchLabel = deriveBranchLabel(checkout);
|
||||||
|
const headerSubtitle = useMemo(() => {
|
||||||
|
if (!headerProjectPath) return undefined;
|
||||||
|
const path = shortenPath(headerProjectPath);
|
||||||
|
if (headerBranchLabel) {
|
||||||
|
return `${path} · ${headerBranchLabel}`;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}, [headerProjectPath, headerBranchLabel]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPendingCreateForRoute || !pendingCreate) {
|
if (!isPendingCreateForRoute || !pendingCreate) {
|
||||||
return;
|
return;
|
||||||
@@ -596,6 +619,7 @@ function AgentScreenContent({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<MenuHeader
|
<MenuHeader
|
||||||
title={effectiveAgent.title || "Agent"}
|
title={effectiveAgent.title || "Agent"}
|
||||||
|
subtitle={headerSubtitle}
|
||||||
rightContent={
|
rightContent={
|
||||||
<View style={styles.headerRightContent}>
|
<View style={styles.headerRightContent}>
|
||||||
<Pressable onPress={toggleFileExplorer} style={styles.menuButton}>
|
<Pressable onPress={toggleFileExplorer} style={styles.menuButton}>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import type { CheckoutStatusPayload } from "@/hooks/use-checkout-status-query";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the branch label to display for an agent.
|
||||||
|
* Returns null if there's no branch to show (not a git repo, or on the base branch).
|
||||||
|
*/
|
||||||
|
export function deriveBranchLabel(
|
||||||
|
checkout: CheckoutStatusPayload | null
|
||||||
|
): string | null {
|
||||||
|
if (!checkout || !checkout.isGit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const currentBranch: string | null = checkout.currentBranch ?? null;
|
||||||
|
const baseRef: string | null = checkout.baseRef ?? null;
|
||||||
|
if (!currentBranch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (baseRef && currentBranch === baseRef) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return currentBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the project path to display for an agent.
|
||||||
|
* If inside a Paseo worktree, shows just the worktree-relative path.
|
||||||
|
* Otherwise uses the repo root or cwd.
|
||||||
|
*/
|
||||||
|
export function deriveProjectPath(
|
||||||
|
cwd: string,
|
||||||
|
checkout: CheckoutStatusPayload | null
|
||||||
|
): string {
|
||||||
|
const basePath = checkout?.isGit ? (checkout.repoRoot ?? cwd) : cwd;
|
||||||
|
const worktreeMarker = ".paseo/worktrees/";
|
||||||
|
const idx = basePath.indexOf(worktreeMarker);
|
||||||
|
if (idx !== -1) {
|
||||||
|
const afterMarker = basePath.slice(idx + worktreeMarker.length);
|
||||||
|
return afterMarker;
|
||||||
|
}
|
||||||
|
return basePath;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user