Fix bottom anchoring and Claude wake routing

This commit is contained in:
Mohamed Boudra
2026-03-08 12:54:08 +07:00
parent 0c44e7db80
commit b339c5e61d
27 changed files with 3801 additions and 326 deletions
@@ -0,0 +1,37 @@
import type { BottomAnchorRouteRequest } from "@/components/use-bottom-anchor-controller";
export type RouteBottomAnchorIntent = {
routeKey: string;
reason: BottomAnchorRouteRequest["reason"];
};
export function deriveRouteBottomAnchorIntent(input: {
cachedIntent: RouteBottomAnchorIntent | null;
routeKey: string | null;
hasAppliedAuthoritativeHistoryAtEntry: boolean;
}): RouteBottomAnchorIntent | null {
if (!input.routeKey) {
return null;
}
if (input.cachedIntent?.routeKey === input.routeKey) {
return input.cachedIntent;
}
return {
routeKey: input.routeKey,
reason: input.hasAppliedAuthoritativeHistoryAtEntry ? "resume" : "initial-entry",
};
}
export function deriveRouteBottomAnchorRequest(input: {
intent: RouteBottomAnchorIntent | null;
effectiveAgentId: string | null;
}): BottomAnchorRouteRequest | null {
if (!input.intent || !input.effectiveAgentId) {
return null;
}
return {
reason: input.intent.reason,
agentId: input.effectiveAgentId,
requestKey: `${input.intent.routeKey}:${input.intent.reason}`,
};
}
@@ -0,0 +1,70 @@
import { describe, expect, it } from "vitest";
import {
deriveRouteBottomAnchorIntent,
deriveRouteBottomAnchorRequest,
} from "./agent-ready-screen-bottom-anchor";
describe("agent-ready-screen bottom anchor intent", () => {
it("latches initial-entry on first route entry before authoritative history is applied", () => {
const intentAtEntry = deriveRouteBottomAnchorIntent({
cachedIntent: null,
routeKey: "server-1:agent-1",
hasAppliedAuthoritativeHistoryAtEntry: false,
});
const intentAfterHistoryApplies = deriveRouteBottomAnchorIntent({
cachedIntent: intentAtEntry,
routeKey: "server-1:agent-1",
hasAppliedAuthoritativeHistoryAtEntry: true,
});
expect(intentAfterHistoryApplies).toEqual({
routeKey: "server-1:agent-1",
reason: "initial-entry",
});
expect(
deriveRouteBottomAnchorRequest({
intent: intentAfterHistoryApplies,
effectiveAgentId: "agent-1",
})
).toEqual({
agentId: "agent-1",
reason: "initial-entry",
requestKey: "server-1:agent-1:initial-entry",
});
});
it("creates resume requests when revisiting an already-hydrated route", () => {
const intent = deriveRouteBottomAnchorIntent({
cachedIntent: null,
routeKey: "server-1:agent-2",
hasAppliedAuthoritativeHistoryAtEntry: true,
});
expect(
deriveRouteBottomAnchorRequest({
intent,
effectiveAgentId: "agent-2",
})
).toEqual({
agentId: "agent-2",
reason: "resume",
requestKey: "server-1:agent-2:resume",
});
});
it("does not create a request until the effective agent exists", () => {
const intent = deriveRouteBottomAnchorIntent({
cachedIntent: null,
routeKey: "server-1:agent-3",
hasAppliedAuthoritativeHistoryAtEntry: false,
});
expect(
deriveRouteBottomAnchorRequest({
intent,
effectiveAgentId: null,
})
).toBeNull();
});
});
@@ -59,6 +59,10 @@ import { shouldClearAgentAttention } from "@/utils/agent-attention";
import type { DaemonClient } from "@server/client/daemon-client";
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
import type { ExplorerCheckoutContext } from "@/stores/panel-store";
import {
deriveRouteBottomAnchorIntent,
deriveRouteBottomAnchorRequest,
} from "./agent-ready-screen-bottom-anchor";
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
const IS_DEV = Boolean((globalThis as { __DEV__?: boolean }).__DEV__);
@@ -390,12 +394,19 @@ function AgentScreenContent({
const historySyncGeneration = useSessionStore(
(state) => state.sessions[serverId]?.historySyncGeneration ?? 0
);
const hasAppliedAuthoritativeHistory = useSessionStore((state) =>
resolvedAgentId
? state.sessions[serverId]?.agentAuthoritativeHistoryApplied?.get(
resolvedAgentId
) === true
: false
);
const agentHistorySyncGeneration = useSessionStore((state) =>
resolvedAgentId
? state.sessions[serverId]?.agentHistorySyncGeneration?.get(resolvedAgentId) ?? -1
: -1
);
const hasHydratedHistoryBefore = agentHistorySyncGeneration >= 0;
const hasHydratedHistoryBefore = hasAppliedAuthoritativeHistory;
// Select raw pending permissions - filter with useMemo to avoid new Map on every render
const allPendingPermissions = useSessionStore(
@@ -432,6 +443,10 @@ function AgentScreenContent({
});
const reconnectToastArmedRef = useRef(false);
const initAttemptTokenRef = useRef(0);
const routeBottomAnchorRequestRef = useRef<{
routeKey: string;
reason: "initial-entry" | "resume";
} | null>(null);
const attentionClientRef = useRef(client);
const attentionConnectedRef = useRef(isConnected);
const setFocusedAgentId = useCallback(
@@ -658,6 +673,20 @@ function AgentScreenContent({
});
const effectiveAgent = viewState.tag === "ready" ? viewState.agent : null;
const routeEntryKey = resolvedAgentId ? `${serverId}:${resolvedAgentId}` : null;
routeBottomAnchorRequestRef.current = deriveRouteBottomAnchorIntent({
cachedIntent: routeBottomAnchorRequestRef.current,
routeKey: routeEntryKey,
hasAppliedAuthoritativeHistoryAtEntry: hasAppliedAuthoritativeHistory,
});
const routeBottomAnchorRequest = useMemo(
() =>
deriveRouteBottomAnchorRequest({
intent: routeBottomAnchorRequestRef.current,
effectiveAgentId: effectiveAgent?.id ?? null,
}),
[effectiveAgent?.id]
);
useEffect(() => {
if (!isPendingCreateForRoute || !pendingCreate) {
return;
@@ -914,6 +943,8 @@ function AgentScreenContent({
shouldUseOptimisticStream ? mergedStreamItems : streamItems
}
pendingPermissions={pendingPermissions}
routeBottomAnchorRequest={routeBottomAnchorRequest}
isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory}
/>
</ReanimatedAnimated.View>
</View>
@@ -926,7 +957,12 @@ function AgentScreenContent({
autoFocus
isSubmitLoading={showPendingCreateSubmitLoading}
onAddImages={handleAddImagesCallback}
onMessageSent={() => streamViewRef.current?.scrollToBottom()}
onComposerHeightChange={() =>
streamViewRef.current?.prepareForViewportChange()
}
onMessageSent={() =>
streamViewRef.current?.scrollToBottom("message-sent")
}
/>
)}