fix(app): make composer track clearance deterministic
Layout measurement excluded pill margin on some renderers, collapsing the intended gap above visible tracks. Keep the two-pill rail on one line and derive its complete tail clearance from shared geometry so chat switching and layout callbacks cannot change the result.
This commit is contained in:
@@ -5,8 +5,7 @@ describe("resolveBottomOverlayTailInset", () => {
|
|||||||
it("adds only the space missing after existing footer clearance", () => {
|
it("adds only the space missing after existing footer clearance", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveBottomOverlayTailInset({
|
resolveBottomOverlayTailInset({
|
||||||
overlayHeight: 44,
|
requiredTailClearance: 56,
|
||||||
clearance: 12,
|
|
||||||
existingTailSpacing: 24,
|
existingTailSpacing: 24,
|
||||||
}),
|
}),
|
||||||
).toBe(32);
|
).toBe(32);
|
||||||
@@ -15,8 +14,7 @@ describe("resolveBottomOverlayTailInset", () => {
|
|||||||
it("reserves the full overlay and clearance when the tail has no spacing", () => {
|
it("reserves the full overlay and clearance when the tail has no spacing", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveBottomOverlayTailInset({
|
resolveBottomOverlayTailInset({
|
||||||
overlayHeight: 44,
|
requiredTailClearance: 56,
|
||||||
clearance: 12,
|
|
||||||
existingTailSpacing: 0,
|
existingTailSpacing: 0,
|
||||||
}),
|
}),
|
||||||
).toBe(56);
|
).toBe(56);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
export function resolveBottomOverlayTailInset({
|
export function resolveBottomOverlayTailInset({
|
||||||
overlayHeight,
|
requiredTailClearance,
|
||||||
clearance,
|
|
||||||
existingTailSpacing,
|
existingTailSpacing,
|
||||||
}: {
|
}: {
|
||||||
overlayHeight: number;
|
requiredTailClearance: number;
|
||||||
clearance: number;
|
|
||||||
existingTailSpacing: number;
|
existingTailSpacing: number;
|
||||||
}): number {
|
}): number {
|
||||||
return Math.max(0, overlayHeight + clearance - existingTailSpacing);
|
return Math.max(0, requiredTailClearance - existingTailSpacing);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,10 +256,8 @@ export interface AgentStreamViewProps {
|
|||||||
turnPresentation: TurnPresentation;
|
turnPresentation: TurnPresentation;
|
||||||
routeBottomAnchorRequest?: BottomAnchorRouteRequest | null;
|
routeBottomAnchorRequest?: BottomAnchorRouteRequest | null;
|
||||||
isAuthoritativeHistoryReady?: boolean;
|
isAuthoritativeHistoryReady?: boolean;
|
||||||
/** Measured transparent overlay at the bottom edge, rendered by the owning panel. */
|
/** Tail space required by a transparent overlay rendered at the bottom edge. */
|
||||||
bottomOverlayHeight?: number;
|
bottomOverlayTailClearance?: number;
|
||||||
/** Visible space kept between the final content and that overlay. */
|
|
||||||
bottomOverlayClearance?: number;
|
|
||||||
toast?: ToastApi | null;
|
toast?: ToastApi | null;
|
||||||
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
|
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
@@ -308,8 +306,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
|||||||
turnPresentation,
|
turnPresentation,
|
||||||
routeBottomAnchorRequest = null,
|
routeBottomAnchorRequest = null,
|
||||||
isAuthoritativeHistoryReady = true,
|
isAuthoritativeHistoryReady = true,
|
||||||
bottomOverlayHeight = 0,
|
bottomOverlayTailClearance = 0,
|
||||||
bottomOverlayClearance = 0,
|
|
||||||
toast,
|
toast,
|
||||||
onOpenWorkspaceFile,
|
onOpenWorkspaceFile,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
@@ -966,8 +963,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
|||||||
const existingTailSpacing =
|
const existingTailSpacing =
|
||||||
auxiliary.turnFooter && !auxiliary.pendingPermissions ? TURN_FOOTER_BOTTOM_SPACING : 0;
|
auxiliary.turnFooter && !auxiliary.pendingPermissions ? TURN_FOOTER_BOTTOM_SPACING : 0;
|
||||||
const bottomOverlayInset = resolveBottomOverlayTailInset({
|
const bottomOverlayInset = resolveBottomOverlayTailInset({
|
||||||
overlayHeight: bottomOverlayHeight,
|
requiredTailClearance: bottomOverlayTailClearance,
|
||||||
clearance: bottomOverlayClearance,
|
|
||||||
existingTailSpacing,
|
existingTailSpacing,
|
||||||
});
|
});
|
||||||
return renderLiveAuxiliaryNode({
|
return renderLiveAuxiliaryNode({
|
||||||
@@ -975,12 +971,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
|||||||
turnFooter: auxiliary.turnFooter,
|
turnFooter: auxiliary.turnFooter,
|
||||||
bottomOverlayInset,
|
bottomOverlayInset,
|
||||||
});
|
});
|
||||||
}, [
|
}, [auxiliary.pendingPermissions, auxiliary.turnFooter, bottomOverlayTailClearance]);
|
||||||
auxiliary.pendingPermissions,
|
|
||||||
auxiliary.turnFooter,
|
|
||||||
bottomOverlayClearance,
|
|
||||||
bottomOverlayHeight,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const renderers = useMemo<StreamSegmentRenderers>(
|
const renderers = useMemo<StreamSegmentRenderers>(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export function resolveComposerPillClearance(isCompact: boolean): number {
|
|||||||
return isCompact ? COMPOSER_PILL_CLEARANCE.compact : COMPOSER_PILL_CLEARANCE.wide;
|
return isCompact ? COMPOSER_PILL_CLEARANCE.compact : COMPOSER_PILL_CLEARANCE.wide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveComposerTrackTailClearance(isCompact: boolean): number {
|
||||||
|
const clearance = resolveComposerPillClearance(isCompact);
|
||||||
|
return clearance + COMPOSER_PILL_MIN_HEIGHT + clearance;
|
||||||
|
}
|
||||||
|
|
||||||
/** Shared visual contract for the compact pills immediately above the composer. */
|
/** Shared visual contract for the compact pills immediately above the composer. */
|
||||||
export const composerPillStyles = StyleSheet.create((theme) => ({
|
export const composerPillStyles = StyleSheet.create((theme) => ({
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
@@ -12,8 +12,18 @@ vi.mock("react-native", () => ({
|
|||||||
},
|
},
|
||||||
Pressable: "button",
|
Pressable: "button",
|
||||||
Text: "span",
|
Text: "span",
|
||||||
View: ({ children, style }: { children?: React.ReactNode; style?: unknown }) => (
|
View: ({
|
||||||
<div data-style={JSON.stringify(style)}>{children}</div>
|
children,
|
||||||
|
style,
|
||||||
|
onLayout,
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
style?: unknown;
|
||||||
|
onLayout?: unknown;
|
||||||
|
}) => (
|
||||||
|
<div data-style={JSON.stringify(style)} data-has-layout-handler={Boolean(onLayout)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -53,6 +63,7 @@ import {
|
|||||||
COMPOSER_PILL_CLEARANCE,
|
COMPOSER_PILL_CLEARANCE,
|
||||||
composerPillStyles,
|
composerPillStyles,
|
||||||
resolveComposerPillClearance,
|
resolveComposerPillClearance,
|
||||||
|
resolveComposerTrackTailClearance,
|
||||||
} from "./pill-styles";
|
} from "./pill-styles";
|
||||||
|
|
||||||
describe("ComposerTrackBar", () => {
|
describe("ComposerTrackBar", () => {
|
||||||
@@ -89,8 +100,16 @@ describe("ComposerTrackBar", () => {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
|
paddingBottom: { xs: 16, md: 10 },
|
||||||
});
|
});
|
||||||
expect(style).not.toHaveProperty("backgroundColor");
|
expect(style).not.toHaveProperty("backgroundColor");
|
||||||
|
expect(container.firstElementChild?.getAttribute("data-has-layout-handler")).toBe("false");
|
||||||
|
|
||||||
|
const trackStyle = JSON.parse(
|
||||||
|
container.firstElementChild?.firstElementChild?.getAttribute("data-style") ?? "{}",
|
||||||
|
);
|
||||||
|
expect(trackStyle).toMatchObject({ flexDirection: "row" });
|
||||||
|
expect(trackStyle).not.toHaveProperty("flexWrap");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches the composer's corner tangent without radius clamping", () => {
|
it("matches the composer's corner tangent without radius clamping", () => {
|
||||||
@@ -102,6 +121,8 @@ describe("ComposerTrackBar", () => {
|
|||||||
expect(composerPillStyles.label).toMatchObject({ fontSize: 12 });
|
expect(composerPillStyles.label).toMatchObject({ fontSize: 12 });
|
||||||
expect(resolveComposerPillClearance(true)).toBe(16);
|
expect(resolveComposerPillClearance(true)).toBe(16);
|
||||||
expect(resolveComposerPillClearance(false)).toBe(10);
|
expect(resolveComposerPillClearance(false)).toBe(10);
|
||||||
|
expect(resolveComposerTrackTailClearance(true)).toBe(64);
|
||||||
|
expect(resolveComposerTrackTailClearance(false)).toBe(52);
|
||||||
expect(COMPOSER_PILL_CLEARANCE).toEqual({ compact: 16, wide: 10 });
|
expect(COMPOSER_PILL_CLEARANCE).toEqual({ compact: 16, wide: 10 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useMemo, useState, type ReactElement, type ReactNode } from "react";
|
import { useCallback, useMemo, useState, type ReactElement, type ReactNode } from "react";
|
||||||
import { Pressable, Text, View, type LayoutChangeEvent } from "react-native";
|
import { Pressable, Text, View } from "react-native";
|
||||||
import { StyleSheet } from "react-native-unistyles";
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
import {
|
import {
|
||||||
MenuRoot,
|
MenuRoot,
|
||||||
@@ -19,25 +19,16 @@ import { COMPOSER_PILL_CLEARANCE, composerPillStyles } from "./pill-styles";
|
|||||||
*
|
*
|
||||||
* Everything in it is a pill: a count you can read without opening anything, and a panel behind
|
* Everything in it is a pill: a count you can read without opening anything, and a panel behind
|
||||||
* it for the detail. Trackers used to be stacked cards, so every one of them pushed the composer
|
* it for the detail. Trackers used to be stacked cards, so every one of them pushed the composer
|
||||||
* further down the pane; a row of pills costs one line no matter how many there are.
|
* further down the pane. The bar has at most one subagent pill and one task pill, so it remains
|
||||||
|
* one line and has deterministic geometry on every platform.
|
||||||
*
|
*
|
||||||
* The bar floats over the transcript with no background, so content remains visible underneath.
|
* The bar floats over the transcript with no background, so content remains visible underneath.
|
||||||
* Its host gives the scroll viewport a small bottom inset only when the bar exists; that keeps
|
* Its host gives the scroll viewport a small bottom inset only when the bar exists; that keeps
|
||||||
* the final footer clear without turning the overlay into a layout band.
|
* the final footer clear without turning the overlay into a layout band.
|
||||||
*/
|
*/
|
||||||
export function ComposerTrackBar({
|
export function ComposerTrackBar({ children }: { children: ReactNode }): ReactElement {
|
||||||
children,
|
|
||||||
onHeightChange,
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
onHeightChange?: (height: number) => void;
|
|
||||||
}): ReactElement {
|
|
||||||
const handleLayout = useCallback(
|
|
||||||
(event: LayoutChangeEvent) => onHeightChange?.(event.nativeEvent.layout.height),
|
|
||||||
[onHeightChange],
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.bar} pointerEvents="box-none" onLayout={handleLayout}>
|
<View style={styles.bar} pointerEvents="box-none">
|
||||||
<View style={styles.track} pointerEvents="box-none">
|
<View style={styles.track} pointerEvents="box-none">
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
@@ -126,7 +117,6 @@ function ComposerTrackPillTrigger({
|
|||||||
const pillStyle = useCallback(
|
const pillStyle = useCallback(
|
||||||
({ hovered, pressed, open: isOpen }: MenuTriggerState) => [
|
({ hovered, pressed, open: isOpen }: MenuTriggerState) => [
|
||||||
composerPillStyles.body,
|
composerPillStyles.body,
|
||||||
styles.pillSpacing,
|
|
||||||
(hovered || pressed || isOpen) && composerPillStyles.bodyActive,
|
(hovered || pressed || isOpen) && composerPillStyles.bodyActive,
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
@@ -246,21 +236,18 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
|
paddingBottom: {
|
||||||
|
xs: COMPOSER_PILL_CLEARANCE.compact,
|
||||||
|
md: COMPOSER_PILL_CLEARANCE.wide,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
track: {
|
track: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: MAX_CONTENT_WIDTH,
|
maxWidth: MAX_CONTENT_WIDTH,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
flexWrap: "wrap",
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
pillSpacing: {
|
|
||||||
marginBottom: {
|
|
||||||
xs: COMPOSER_PILL_CLEARANCE.compact,
|
|
||||||
md: COMPOSER_PILL_CLEARANCE.wide,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// The rail every panel row sits on: inset from the panel edge so the fill is a rounded block
|
// The rail every panel row sits on: inset from the panel edge so the fill is a rounded block
|
||||||
// inside it, and tall enough that revealing an action button cannot resize the row.
|
// inside it, and tall enough that revealing an action button cannot resize the row.
|
||||||
row: {
|
row: {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { FileDropZone } from "@/components/file-drop/file-drop-zone";
|
|||||||
import { useRetainedPanelActive } from "@/components/retained-panel";
|
import { useRetainedPanelActive } from "@/components/retained-panel";
|
||||||
import { SidebarCallout } from "@/components/sidebar-callout";
|
import { SidebarCallout } from "@/components/sidebar-callout";
|
||||||
import { Composer } from "@/composer";
|
import { Composer } from "@/composer";
|
||||||
import { COMPOSER_PILL_MIN_HEIGHT, resolveComposerPillClearance } from "@/composer/pill-styles";
|
import { resolveComposerTrackTailClearance } from "@/composer/pill-styles";
|
||||||
import { getActiveMessageSubmissions } from "@/composer/submission/model";
|
import { getActiveMessageSubmissions } from "@/composer/submission/model";
|
||||||
import { RewindComposerRestoreProvider } from "@/components/rewind/composer-restore";
|
import { RewindComposerRestoreProvider } from "@/components/rewind/composer-restore";
|
||||||
import { getProviderIcon } from "@/components/provider-icons";
|
import { getProviderIcon } from "@/components/provider-icons";
|
||||||
@@ -1213,7 +1213,7 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isCompactFormFactor = useIsCompactFormFactor();
|
const isCompactFormFactor = useIsCompactFormFactor();
|
||||||
const composerPillClearance = resolveComposerPillClearance(isCompactFormFactor);
|
const composerTrackTailClearance = resolveComposerTrackTailClearance(isCompactFormFactor);
|
||||||
const subagentRows = useSubagentsForParent({ serverId, parentAgentId: agentId });
|
const subagentRows = useSubagentsForParent({ serverId, parentAgentId: agentId });
|
||||||
const tasks = useSessionStore((state): TodoEntry[] | undefined =>
|
const tasks = useSessionStore((state): TodoEntry[] | undefined =>
|
||||||
state.sessions[serverId]?.agentTasks.get(agentId),
|
state.sessions[serverId]?.agentTasks.get(agentId),
|
||||||
@@ -1223,14 +1223,14 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
|||||||
parentAgentId: agentId,
|
parentAgentId: agentId,
|
||||||
rows: subagentRows,
|
rows: subagentRows,
|
||||||
});
|
});
|
||||||
const showAgentTracks = hasAgentTracks({
|
const hasActiveComposer = !agentState.archivedAt && !isArchivingCurrentAgent;
|
||||||
subagentRows,
|
const showAgentTracks =
|
||||||
tasks,
|
hasActiveComposer &&
|
||||||
archiveFinishedStatus: archiveFinishedSubagents.status,
|
hasAgentTracks({
|
||||||
});
|
subagentRows,
|
||||||
const [agentTracksHeight, setAgentTracksHeight] = useState(
|
tasks,
|
||||||
COMPOSER_PILL_MIN_HEIGHT + composerPillClearance,
|
archiveFinishedStatus: archiveFinishedSubagents.status,
|
||||||
);
|
});
|
||||||
const rawAgentInputDraft = useAgentInputDraft({
|
const rawAgentInputDraft = useAgentInputDraft({
|
||||||
draftKey: buildDraftStoreKey({
|
draftKey: buildDraftStoreKey({
|
||||||
serverId,
|
serverId,
|
||||||
@@ -1305,8 +1305,7 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
|||||||
agent={effectiveAgent}
|
agent={effectiveAgent}
|
||||||
routeBottomAnchorRequest={routeBottomAnchorRequest}
|
routeBottomAnchorRequest={routeBottomAnchorRequest}
|
||||||
hasAppliedAuthoritativeHistory={hasAppliedAuthoritativeHistory}
|
hasAppliedAuthoritativeHistory={hasAppliedAuthoritativeHistory}
|
||||||
bottomOverlayHeight={showAgentTracks ? agentTracksHeight : 0}
|
bottomOverlayTailClearance={showAgentTracks ? composerTrackTailClearance : 0}
|
||||||
bottomOverlayClearance={showAgentTracks ? composerPillClearance : 0}
|
|
||||||
toast={toastApi}
|
toast={toastApi}
|
||||||
onOpenWorkspaceFile={onOpenWorkspaceFile}
|
onOpenWorkspaceFile={onOpenWorkspaceFile}
|
||||||
/>
|
/>
|
||||||
@@ -1318,7 +1317,6 @@ const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
archiveFinishedStatus={archiveFinishedSubagents.status}
|
archiveFinishedStatus={archiveFinishedSubagents.status}
|
||||||
onArchiveFinished={archiveFinishedSubagents.archiveFinished}
|
onArchiveFinished={archiveFinishedSubagents.archiveFinished}
|
||||||
onHeightChange={setAgentTracksHeight}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</ReanimatedAnimated.View>
|
</ReanimatedAnimated.View>
|
||||||
@@ -1372,8 +1370,7 @@ const AgentStreamSection = memo(function AgentStreamSection({
|
|||||||
agent,
|
agent,
|
||||||
routeBottomAnchorRequest,
|
routeBottomAnchorRequest,
|
||||||
hasAppliedAuthoritativeHistory,
|
hasAppliedAuthoritativeHistory,
|
||||||
bottomOverlayHeight,
|
bottomOverlayTailClearance,
|
||||||
bottomOverlayClearance,
|
|
||||||
toast,
|
toast,
|
||||||
onOpenWorkspaceFile,
|
onOpenWorkspaceFile,
|
||||||
}: {
|
}: {
|
||||||
@@ -1383,8 +1380,7 @@ const AgentStreamSection = memo(function AgentStreamSection({
|
|||||||
agent: AgentScreenAgent;
|
agent: AgentScreenAgent;
|
||||||
routeBottomAnchorRequest: RouteBottomAnchorRequest;
|
routeBottomAnchorRequest: RouteBottomAnchorRequest;
|
||||||
hasAppliedAuthoritativeHistory: boolean;
|
hasAppliedAuthoritativeHistory: boolean;
|
||||||
bottomOverlayHeight: number;
|
bottomOverlayTailClearance: number;
|
||||||
bottomOverlayClearance: number;
|
|
||||||
toast: ReturnType<typeof useToastHost>["api"];
|
toast: ReturnType<typeof useToastHost>["api"];
|
||||||
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
|
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -1443,8 +1439,7 @@ const AgentStreamSection = memo(function AgentStreamSection({
|
|||||||
pendingPermissions={pendingPermissions}
|
pendingPermissions={pendingPermissions}
|
||||||
routeBottomAnchorRequest={routeBottomAnchorRequest}
|
routeBottomAnchorRequest={routeBottomAnchorRequest}
|
||||||
isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory}
|
isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory}
|
||||||
bottomOverlayHeight={bottomOverlayHeight}
|
bottomOverlayTailClearance={bottomOverlayTailClearance}
|
||||||
bottomOverlayClearance={bottomOverlayClearance}
|
|
||||||
toast={toast}
|
toast={toast}
|
||||||
pendingMessageSubmissions={pendingMessageSubmissions}
|
pendingMessageSubmissions={pendingMessageSubmissions}
|
||||||
turnPresentation={turnPresentation}
|
turnPresentation={turnPresentation}
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ export const AgentTracks = memo(function AgentTracks({
|
|||||||
tasks,
|
tasks,
|
||||||
archiveFinishedStatus,
|
archiveFinishedStatus,
|
||||||
onArchiveFinished,
|
onArchiveFinished,
|
||||||
onHeightChange,
|
|
||||||
}: {
|
}: {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
subagentRows: SubagentRow[];
|
subagentRows: SubagentRow[];
|
||||||
tasks: TodoEntry[] | undefined;
|
tasks: TodoEntry[] | undefined;
|
||||||
archiveFinishedStatus: ArchiveFinishedStatus;
|
archiveFinishedStatus: ArchiveFinishedStatus;
|
||||||
onArchiveFinished: () => void;
|
onArchiveFinished: () => void;
|
||||||
onHeightChange?: (height: number) => void;
|
|
||||||
}): ReactElement | null {
|
}): ReactElement | null {
|
||||||
const { openTab } = usePaneContext();
|
const { openTab } = usePaneContext();
|
||||||
const canDetachSubagents = useSessionStore(
|
const canDetachSubagents = useSessionStore(
|
||||||
@@ -59,7 +57,7 @@ export const AgentTracks = memo(function AgentTracks({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComposerTrackBar onHeightChange={onHeightChange}>
|
<ComposerTrackBar>
|
||||||
<SubagentsTrack
|
<SubagentsTrack
|
||||||
rows={subagentRows}
|
rows={subagentRows}
|
||||||
onOpenSubagent={handleOpenSubagent}
|
onOpenSubagent={handleOpenSubagent}
|
||||||
|
|||||||
Reference in New Issue
Block a user