Prevent Android chats from freezing or going blank (#1989)

* fix(app): stabilize streamed chat rendering

Use deterministic Markdown AST keys and preserve unique row identities when an assistant message resumes after a tool. Keep native gesture and plan-card children correctly keyed to avoid unsupported Fabric reconciliation paths.

* fix(app): stabilize retained native panels

Keep retained workspace roots in a stable native order and make active
panels present in the selection commit. Hidden panels now stop expensive
subscriptions and animations without losing mounted state.

* fix(app): harden retained panel activity
This commit is contained in:
Mohamed Boudra
2026-07-11 15:39:14 +08:00
committed by GitHub
parent 860fcb2e35
commit 28a27b02d3
40 changed files with 1244 additions and 543 deletions
@@ -92,8 +92,22 @@ describe("workspace agent activity index", () => {
expect(index).toEqual(
new Map([
["workspace-a", { status: "needs_input", enteredAt: new Date("2026-06-01T10:01:00.000Z") }],
["workspace-b", { status: "attention", enteredAt: new Date("2026-06-01T10:02:00.000Z") }],
[
"workspace-a",
{
agentId: "permission",
status: "needs_input",
enteredAt: new Date("2026-06-01T10:01:00.000Z"),
},
],
[
"workspace-b",
{
agentId: "attention",
status: "attention",
enteredAt: new Date("2026-06-01T10:02:00.000Z"),
},
],
]),
);
});
@@ -135,8 +149,82 @@ describe("workspace agent activity index", () => {
);
expect(index.get("workspace-a")).toEqual({
agentId: "root",
status: "running",
enteredAt: new Date("2026-06-01T10:00:00.000Z"),
});
});
it("preserves the activity index while the same agent remains in the same status", () => {
const previous = buildWorkspaceAgentActivityIndex(
new Map([
[
"root",
agent({
id: "root",
workspaceId: "workspace-a",
status: "running",
updatedAt: "2026-06-01T10:00:00.000Z",
}),
],
]),
);
const next = buildWorkspaceAgentActivityIndex(
new Map([
[
"root",
agent({
id: "root",
workspaceId: "workspace-a",
status: "running",
updatedAt: "2026-06-01T10:05:00.000Z",
}),
],
]),
previous,
);
expect(next).toBe(previous);
expect(next.get("workspace-a")?.enteredAt).toEqual(new Date("2026-06-01T10:00:00.000Z"));
});
it("records a new entry time when an agent changes status", () => {
const previous = buildWorkspaceAgentActivityIndex(
new Map([
[
"root",
agent({
id: "root",
workspaceId: "workspace-a",
status: "running",
updatedAt: "2026-06-01T10:00:00.000Z",
}),
],
]),
);
const next = buildWorkspaceAgentActivityIndex(
new Map([
[
"root",
agent({
id: "root",
workspaceId: "workspace-a",
status: "idle",
updatedAt: "2026-06-01T10:05:00.000Z",
pendingPermissionCount: 1,
}),
],
]),
previous,
);
expect(next).not.toBe(previous);
expect(next.get("workspace-a")).toEqual({
agentId: "root",
status: "needs_input",
enteredAt: new Date("2026-06-01T10:05:00.000Z"),
});
});
});