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:
Mohamed Boudra
2026-08-17 23:23:51 +02:00
parent a6a533bc6a
commit 1cc6712e51
8 changed files with 62 additions and 69 deletions
@@ -5,8 +5,7 @@ describe("resolveBottomOverlayTailInset", () => {
it("adds only the space missing after existing footer clearance", () => {
expect(
resolveBottomOverlayTailInset({
overlayHeight: 44,
clearance: 12,
requiredTailClearance: 56,
existingTailSpacing: 24,
}),
).toBe(32);
@@ -15,8 +14,7 @@ describe("resolveBottomOverlayTailInset", () => {
it("reserves the full overlay and clearance when the tail has no spacing", () => {
expect(
resolveBottomOverlayTailInset({
overlayHeight: 44,
clearance: 12,
requiredTailClearance: 56,
existingTailSpacing: 0,
}),
).toBe(56);
@@ -1,11 +1,9 @@
export function resolveBottomOverlayTailInset({
overlayHeight,
clearance,
requiredTailClearance,
existingTailSpacing,
}: {
overlayHeight: number;
clearance: number;
requiredTailClearance: number;
existingTailSpacing: number;
}): number {
return Math.max(0, overlayHeight + clearance - existingTailSpacing);
return Math.max(0, requiredTailClearance - existingTailSpacing);
}
+5 -14
View File
@@ -256,10 +256,8 @@ 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;
/** Tail space required by a transparent overlay rendered at the bottom edge. */
bottomOverlayTailClearance?: number;
toast?: ToastApi | null;
onOpenWorkspaceFile?: (request: WorkspaceFileOpenRequest) => void;
readOnly?: boolean;
@@ -308,8 +306,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
turnPresentation,
routeBottomAnchorRequest = null,
isAuthoritativeHistoryReady = true,
bottomOverlayHeight = 0,
bottomOverlayClearance = 0,
bottomOverlayTailClearance = 0,
toast,
onOpenWorkspaceFile,
readOnly = false,
@@ -966,8 +963,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
const existingTailSpacing =
auxiliary.turnFooter && !auxiliary.pendingPermissions ? TURN_FOOTER_BOTTOM_SPACING : 0;
const bottomOverlayInset = resolveBottomOverlayTailInset({
overlayHeight: bottomOverlayHeight,
clearance: bottomOverlayClearance,
requiredTailClearance: bottomOverlayTailClearance,
existingTailSpacing,
});
return renderLiveAuxiliaryNode({
@@ -975,12 +971,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
turnFooter: auxiliary.turnFooter,
bottomOverlayInset,
});
}, [
auxiliary.pendingPermissions,
auxiliary.turnFooter,
bottomOverlayClearance,
bottomOverlayHeight,
]);
}, [auxiliary.pendingPermissions, auxiliary.turnFooter, bottomOverlayTailClearance]);
const renderers = useMemo<StreamSegmentRenderers>(
() => ({