fix(app): float composer track pills consistently

Keep agent and task pills as a transparent transcript overlay while measuring their rendered height for scroll-tail clearance. Share geometry and spacing with the import-session pill so borders, type, corners, and composer separation match across platforms.
This commit is contained in:
Mohamed Boudra
2026-08-17 22:55:45 +02:00
parent d7f892251f
commit df3e646fbe
13 changed files with 350 additions and 134 deletions
+57 -28
View File
@@ -25,6 +25,7 @@ import { FileDropZone } from "@/components/file-drop/file-drop-zone";
import { useRetainedPanelActive } from "@/components/retained-panel";
import { SidebarCallout } from "@/components/sidebar-callout";
import { Composer } from "@/composer";
import { COMPOSER_PILL_CLEARANCE, COMPOSER_PILL_MIN_HEIGHT } from "@/composer/pill-styles";
import { getActiveMessageSubmissions } from "@/composer/submission/model";
import { RewindComposerRestoreProvider } from "@/components/rewind/composer-restore";
import { getProviderIcon } from "@/components/provider-icons";
@@ -74,7 +75,7 @@ import {
deriveRouteBottomAnchorRequest,
} from "@/screens/agent/agent-ready-screen-bottom-anchor";
import { WorkspaceDraftAgentTab } from "@/composer/draft/workspace-tab";
import { AgentTracks } from "@/panels/agent-tracks";
import { AgentTracks, hasAgentTracks } from "@/panels/agent-tracks";
import { useCreateFlowStore } from "@/stores/create-flow-store";
import { buildDraftStoreKey, generateDraftId } from "@/stores/draft-keys";
import { usePanelStore } from "@/stores/panel-store";
@@ -88,7 +89,8 @@ import { useWorkspaceLayoutStore } from "@/stores/workspace-layout-store";
import { buildWorkspaceTabPersistenceKey } from "@/workspace-tabs/model";
import type { Theme } from "@/styles/theme";
import type { PendingPermission } from "@/types/shared";
import type { StreamItem } from "@/types/stream";
import type { StreamItem, TodoEntry } from "@/types/stream";
import { useArchiveFinishedSubagents, useSubagentsForParent } from "@/subagents";
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
import { derivePendingPermissionKey, normalizeAgentSnapshot } from "@/utils/agent-snapshots";
import { applyLegacyDaemonWorkspaceOwnership } from "@/workspace/legacy-daemon-workspaces";
@@ -1210,6 +1212,23 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
}) {
const { t } = useTranslation();
const subagentRows = useSubagentsForParent({ serverId, parentAgentId: agentId });
const tasks = useSessionStore((state): TodoEntry[] | undefined =>
state.sessions[serverId]?.agentTasks.get(agentId),
);
const archiveFinishedSubagents = useArchiveFinishedSubagents({
serverId,
parentAgentId: agentId,
rows: subagentRows,
});
const showAgentTracks = hasAgentTracks({
subagentRows,
tasks,
archiveFinishedStatus: archiveFinishedSubagents.status,
});
const [agentTracksHeight, setAgentTracksHeight] = useState(
COMPOSER_PILL_MIN_HEIGHT + COMPOSER_PILL_CLEARANCE,
);
const rawAgentInputDraft = useAgentInputDraft({
draftKey: buildDraftStoreKey({
serverId,
@@ -1256,20 +1275,6 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
composerState,
],
);
const streamSection = (
<RenderProfile id={`AgentStreamSection:${agentId}`}>
<AgentStreamSection
streamViewRef={streamViewRef}
serverId={serverId}
agentId={agentId}
agent={effectiveAgent}
routeBottomAnchorRequest={routeBottomAnchorRequest}
hasAppliedAuthoritativeHistory={hasAppliedAuthoritativeHistory}
toast={toastApi}
onOpenWorkspaceFile={onOpenWorkspaceFile}
/>
</RenderProfile>
);
const composerSection = (
<RenderProfile id={`AgentComposerSection:${agentId}`}>
<AgentComposerSection
@@ -1288,14 +1293,32 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
/>
</RenderProfile>
);
// The tracks float at the foot of the transcript, inside the same transform the composer uses,
// so the pills sit on its top edge with the timeline running under them. They follow the
// composer: an archived or archiving agent has none, and its children can no longer be acted on.
const hasComposer = !agentState.archivedAt && !isArchivingCurrentAgent;
const streamContent = (
<ReanimatedAnimated.View style={animatedContentStyle}>
{streamSection}
{hasComposer ? <AgentTracks serverId={serverId} agentId={agentId} /> : null}
<RenderProfile id={`AgentStreamSection:${agentId}`}>
<AgentStreamSection
streamViewRef={streamViewRef}
serverId={serverId}
agentId={agentId}
agent={effectiveAgent}
routeBottomAnchorRequest={routeBottomAnchorRequest}
hasAppliedAuthoritativeHistory={hasAppliedAuthoritativeHistory}
bottomOverlayHeight={showAgentTracks ? agentTracksHeight : 0}
bottomOverlayClearance={showAgentTracks ? COMPOSER_PILL_CLEARANCE : 0}
toast={toastApi}
onOpenWorkspaceFile={onOpenWorkspaceFile}
/>
</RenderProfile>
{showAgentTracks ? (
<AgentTracks
serverId={serverId}
subagentRows={subagentRows}
tasks={tasks}
archiveFinishedStatus={archiveFinishedSubagents.status}
onArchiveFinished={archiveFinishedSubagents.archiveFinished}
onHeightChange={setAgentTracksHeight}
/>
) : null}
</ReanimatedAnimated.View>
);
const contentContainer = <View style={styles.contentContainer}>{streamContent}</View>;
@@ -1347,6 +1370,8 @@ const AgentStreamSection = memo(function AgentStreamSection({
agent,
routeBottomAnchorRequest,
hasAppliedAuthoritativeHistory,
bottomOverlayHeight,
bottomOverlayClearance,
toast,
onOpenWorkspaceFile,
}: {
@@ -1356,6 +1381,8 @@ const AgentStreamSection = memo(function AgentStreamSection({
agent: AgentScreenAgent;
routeBottomAnchorRequest: RouteBottomAnchorRequest;
hasAppliedAuthoritativeHistory: boolean;
bottomOverlayHeight: number;
bottomOverlayClearance: number;
toast: ReturnType<typeof useToastHost>["api"];
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
}) {
@@ -1414,6 +1441,8 @@ const AgentStreamSection = memo(function AgentStreamSection({
pendingPermissions={pendingPermissions}
routeBottomAnchorRequest={routeBottomAnchorRequest}
isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory}
bottomOverlayHeight={bottomOverlayHeight}
bottomOverlayClearance={bottomOverlayClearance}
toast={toast}
pendingMessageSubmissions={pendingMessageSubmissions}
turnPresentation={turnPresentation}
@@ -1744,13 +1773,13 @@ const styles = StyleSheet.create((theme) => ({
zIndex: 50,
},
archivingTitle: {
fontSize: theme.fontSize.lg,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.foreground,
textAlign: "center",
},
archivingSubtitle: {
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
color: theme.colors.foregroundMuted,
textAlign: "center",
},
@@ -1777,14 +1806,14 @@ const styles = StyleSheet.create((theme) => ({
justifyContent: "center",
},
errorText: {
fontSize: theme.fontSize.lg,
fontSize: theme.fontSize.base,
color: theme.colors.foregroundMuted,
textAlign: "center",
},
statusText: {
marginTop: theme.spacing[2],
textAlign: "center",
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
color: theme.colors.foregroundMuted,
},
offlineTitle: {
@@ -1794,12 +1823,12 @@ const styles = StyleSheet.create((theme) => ({
textAlign: "center",
},
offlineDescription: {
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
color: theme.colors.foregroundMuted,
textAlign: "center",
},
offlineDetails: {
fontSize: theme.fontSize.xs,
fontSize: theme.fontSize.sm,
color: theme.colors.foregroundMuted,
textAlign: "center",
},