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
+38 -8
View File
@@ -74,10 +74,12 @@ import { planTimelineTailFetch } from "@/timeline/timeline-sync-plan";
import {
CompletedTurnFooterRow,
TurnFooter,
TURN_FOOTER_BOTTOM_SPACING,
type AssistantTurnForkHandler,
type InFlightTurnForkHandler,
type TurnContentStrategy,
} from "./turn-footer";
import { resolveBottomOverlayTailInset } from "./bottom-overlay-inset";
import { layoutStream, type StreamLayoutItem } from "./layout";
import {
type BottomAnchorLocalRequest,
@@ -106,8 +108,9 @@ import { useRetainedPanelActive } from "@/components/retained-panel";
function renderLiveAuxiliaryNode(input: {
pendingPermissions: ReactNode;
turnFooter: ReactNode;
bottomOverlayInset: number;
}): ReactNode {
if (!input.pendingPermissions && !input.turnFooter) {
if (!input.pendingPermissions && !input.turnFooter && input.bottomOverlayInset === 0) {
return null;
}
return (
@@ -118,10 +121,18 @@ function renderLiveAuxiliaryNode(input: {
<View style={stylesheet.listHeaderContent}>{input.pendingPermissions}</View>
</View>
) : null}
{input.bottomOverlayInset > 0 ? (
<BottomOverlayInset height={input.bottomOverlayInset} />
) : null}
</>
);
}
function BottomOverlayInset({ height }: { height: number }) {
const style = useMemo(() => ({ height }), [height]);
return <View style={style} />;
}
function renderPendingPermissionsNode(input: {
pendingPermissions: PendingPermission[];
client: DaemonClient | null;
@@ -245,6 +256,10 @@ export interface AgentStreamViewProps {
turnPresentation: TurnPresentation;
routeBottomAnchorRequest?: BottomAnchorRouteRequest | null;
isAuthoritativeHistoryReady?: boolean;
/** Measured transparent overlay at the bottom edge, rendered by the owning panel. */
bottomOverlayHeight?: number;
/** Visible space kept between the final content and that overlay. */
bottomOverlayClearance?: number;
toast?: ToastApi | null;
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
readOnly?: boolean;
@@ -293,6 +308,8 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
turnPresentation,
routeBottomAnchorRequest = null,
isAuthoritativeHistoryReady = true,
bottomOverlayHeight = 0,
bottomOverlayClearance = 0,
toast,
onOpenWorkspaceFile,
readOnly = false,
@@ -946,11 +963,24 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
}),
);
const renderLiveAuxiliary = useCallback<StreamSegmentRenderers["renderLiveAuxiliary"]>(() => {
const existingTailSpacing =
auxiliary.turnFooter && !auxiliary.pendingPermissions ? TURN_FOOTER_BOTTOM_SPACING : 0;
const bottomOverlayInset = resolveBottomOverlayTailInset({
overlayHeight: bottomOverlayHeight,
clearance: bottomOverlayClearance,
existingTailSpacing,
});
return renderLiveAuxiliaryNode({
pendingPermissions: auxiliary.pendingPermissions,
turnFooter: auxiliary.turnFooter,
bottomOverlayInset,
});
}, [auxiliary.pendingPermissions, auxiliary.turnFooter]);
}, [
auxiliary.pendingPermissions,
auxiliary.turnFooter,
bottomOverlayClearance,
bottomOverlayHeight,
]);
const renderers = useMemo<StreamSegmentRenderers>(
() => ({
@@ -1508,7 +1538,7 @@ const stylesheet = StyleSheet.create((theme) => ({
},
syncingIndicatorText: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
},
invertedWrapper: {
transform: [{ scaleY: -1 }],
@@ -1516,7 +1546,7 @@ const stylesheet = StyleSheet.create((theme) => ({
},
emptyStateText: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
textAlign: "center",
},
scrollToBottomContainer: {
@@ -1556,7 +1586,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
color: theme.colors.foreground,
},
description: {
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
lineHeight: 20,
color: theme.colors.foregroundMuted,
},
@@ -1564,10 +1594,10 @@ const permissionStyles = StyleSheet.create((theme) => ({
gap: theme.spacing[2],
},
sectionTitle: {
fontSize: theme.fontSize.xs,
fontSize: theme.fontSize.sm,
},
question: {
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
marginTop: theme.spacing[1],
marginBottom: theme.spacing[1],
color: theme.colors.foregroundMuted,
@@ -1603,7 +1633,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
gap: theme.spacing[2],
},
optionText: {
fontSize: theme.fontSize.sm,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted,
},