Keep focused agent timelines live and catch up instantly (#2196)

* fix(app): keep focused agent timelines live

Window focus was conflated with app visibility, so switching OS windows could remove the selected timeline subscription. Separate those signals, grace every visibility-driven removal, and cover retained history until authoritative catch-up completes.

* fix(app): keep timeline catch-up failures non-blocking

* fix(app): surface timeline catch-up failures

* fix(app): preserve file preview refocus

* fix(app): preserve optimistic catch-up flow
This commit is contained in:
Mohamed Boudra
2026-07-18 16:30:59 +02:00
committed by GitHub
parent fcaa84f0e4
commit 7917532716
26 changed files with 527 additions and 64 deletions
@@ -63,6 +63,7 @@ function createBaseInput(): AgentScreenMachineInput {
isArchivingCurrentAgent: false,
isHistorySyncing: false,
needsAuthoritativeSync: false,
visibilityCatchUpStatus: "ready",
hasHydratedHistoryBefore: false,
};
}
@@ -189,6 +190,43 @@ describe("deriveAgentScreenViewState", () => {
expect(sync.ui).toBe("silent");
});
it("covers already-hydrated history while a newly visible agent catches up", () => {
const memory = createBaseMemory({
hasRenderedReady: true,
lastReadyAgent: createAgent("agent-1"),
});
const input: AgentScreenMachineInput = {
...createBaseInput(),
agent: createAgent("agent-1"),
hasHydratedHistoryBefore: true,
visibilityCatchUpStatus: "pending",
};
const result = deriveAgentScreenViewState({ input, memory });
const ready = expectReadyState(result.state);
const sync = expectCatchingUpSync(ready);
expect(sync.ui).toBe("overlay");
});
it("keeps hydrated history readable after a visibility catch-up error", () => {
const memory = createBaseMemory({
hasRenderedReady: true,
lastReadyAgent: createAgent("agent-1"),
});
const input: AgentScreenMachineInput = {
...createBaseInput(),
agent: createAgent("agent-1"),
hasHydratedHistoryBefore: true,
visibilityCatchUpStatus: "error",
};
const result = deriveAgentScreenViewState({ input, memory });
const ready = expectReadyState(result.state);
expectSyncErrorSync(ready);
});
it("keeps sync errors non-blocking once the screen was ready", () => {
const memory = createBaseMemory({
hasRenderedReady: true,
@@ -328,7 +366,7 @@ describe("deriveAgentScreenViewState", () => {
expect(result.memory.lastReadyAgent).toBeNull();
});
it("still allows optimistic create flow to render before authoritative history arrives", () => {
it("keeps optimistic create non-blocking while timeline and authoritative history catch up", () => {
const memory = createBaseMemory();
const input: AgentScreenMachineInput = {
...createBaseInput(),
@@ -336,6 +374,7 @@ describe("deriveAgentScreenViewState", () => {
continuity: { kind: "optimistic-create", agent: createAgent("agent-1") },
needsAuthoritativeSync: true,
isHistorySyncing: true,
visibilityCatchUpStatus: "pending",
hasHydratedHistoryBefore: false,
};