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:
@@ -4,7 +4,7 @@ import type { WorkspaceStructureProject } from "@/projects/workspace-structure";
|
||||
import {
|
||||
appendMissingOrderKeys,
|
||||
applyStoredOrdering,
|
||||
buildSidebarStatusWorkspacePlacements,
|
||||
buildSidebarWorkspaceEntries,
|
||||
buildSidebarWorkspacePlacementModel,
|
||||
buildSidebarProjectsFromStructure,
|
||||
computeSidebarOrderUpdates,
|
||||
@@ -226,7 +226,7 @@ describe("shared sidebar workspace model", () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
const statusRows = buildSidebarStatusWorkspacePlacements({
|
||||
const workspaceEntries = buildSidebarWorkspaceEntries({
|
||||
placements: model.workspaces,
|
||||
sessions: [
|
||||
{
|
||||
@@ -290,14 +290,66 @@ describe("shared sidebar workspace model", () => {
|
||||
],
|
||||
}),
|
||||
]);
|
||||
expect(statusRows.map((entry) => [entry.workspaceKey, entry.statusBucket, entry.name])).toEqual(
|
||||
[
|
||||
["host-a:main", "done", "main"],
|
||||
["host-b:feature", "running", "feature/status-flow"],
|
||||
],
|
||||
);
|
||||
expect(
|
||||
Array.from(workspaceEntries.values()).map((entry) => [
|
||||
entry.workspaceKey,
|
||||
entry.statusBucket,
|
||||
entry.name,
|
||||
]),
|
||||
).toEqual([
|
||||
["host-a:main", "done", "main"],
|
||||
["host-b:feature", "running", "feature/status-flow"],
|
||||
]);
|
||||
expect(model.projectNamesByKey).toEqual(new Map([["getpaseo/paseo", "getpaseo/paseo"]]));
|
||||
});
|
||||
|
||||
it("preserves unchanged row identities when another workspace updates", () => {
|
||||
const model = buildSidebarWorkspacePlacementModel({
|
||||
projects: [project({ projectKey: "project", workspaceKeys: ["srv:one", "srv:two"] })],
|
||||
});
|
||||
const one = workspace({
|
||||
id: "one",
|
||||
name: "one",
|
||||
projectId: "project",
|
||||
projectDisplayName: "project",
|
||||
});
|
||||
const two = workspace({
|
||||
id: "two",
|
||||
name: "two",
|
||||
projectId: "project",
|
||||
projectDisplayName: "project",
|
||||
});
|
||||
const previousEntries = buildSidebarWorkspaceEntries({
|
||||
placements: model.workspaces,
|
||||
sessions: [
|
||||
{
|
||||
serverId: "srv",
|
||||
workspaceAgentActivity: new Map(),
|
||||
workspaces: new Map([
|
||||
["one", one],
|
||||
["two", two],
|
||||
]),
|
||||
},
|
||||
],
|
||||
});
|
||||
const nextEntries = buildSidebarWorkspaceEntries({
|
||||
placements: model.workspaces,
|
||||
sessions: [
|
||||
{
|
||||
serverId: "srv",
|
||||
workspaceAgentActivity: new Map(),
|
||||
workspaces: new Map([
|
||||
["one", one],
|
||||
["two", { ...two, status: "running" }],
|
||||
]),
|
||||
},
|
||||
],
|
||||
previousEntries,
|
||||
});
|
||||
|
||||
expect(nextEntries.get("srv:one")).toBe(previousEntries.get("srv:one"));
|
||||
expect(nextEntries.get("srv:two")).not.toBe(previousEntries.get("srv:two"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldShowSidebarHostLabels", () => {
|
||||
|
||||
Reference in New Issue
Block a user