().await.unwrap().cleared, 1);
+ assert!(client.flows(&FlowFilter::default()).await.unwrap().is_empty());
+ assert!(client.body(&flow.id, FlowBody::Response).await.is_err());
+
drop(server);
assert!(!dir.path().join("control.json").exists());
assert!(control::live_owner(dir.path()).is_none());
diff --git a/package-lock.json b/package-lock.json
index 4c774974e..0b4fef36f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -249,7 +249,6 @@
"@tanstack/react-virtual": "^3.14.17",
"@tauri-apps/api": "^2.12.3",
"@tauri-apps/plugin-os": "^2.4.0",
- "@yaakapp-internal/model-store": "^1.0.0",
"@yaakapp-internal/proxy-lib": "^1.0.0",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
diff --git a/packages/ui/src/components/tree/atoms.ts b/packages/ui/src/components/tree/atoms.ts
index d7070d113..fb9c4ec8a 100644
--- a/packages/ui/src/components/tree/atoms.ts
+++ b/packages/ui/src/components/tree/atoms.ts
@@ -2,16 +2,33 @@ import { atom } from "jotai";
import { atomFamily } from "jotai-family";
import { selectAtom } from "jotai/utils";
+/**
+ * An atom family for object params, looked up by a string key. `atomFamily` with a comparator
+ * scans every atom it has made on each lookup, which every rendered row does several times a
+ * render, so a large tree slowed down in proportion to the rows it had ever shown.
+ */
+function keyedFamily(key: (param: P) => string, create: (param: P) => A): (param: P) => A {
+ const atoms = new Map();
+ return (param) => {
+ const k = key(param);
+ let a = atoms.get(k);
+ if (a === undefined) {
+ a = create(param);
+ atoms.set(k, a);
+ }
+ return a;
+ };
+}
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const selectedIdsFamily = atomFamily((_treeId: string) => {
return atom([]);
});
-export const isSelectedFamily = atomFamily(
- ({ treeId, itemId }: { treeId: string; itemId: string }) => {
- return selectAtom(selectedIdsFamily(treeId), (ids) => ids.includes(itemId), Object.is);
- },
- (a, b) => a.treeId === b.treeId && a.itemId === b.itemId,
+export const isSelectedFamily = keyedFamily(
+ ({ treeId, itemId }: { treeId: string; itemId: string }) => `${treeId}\0${itemId}`,
+ ({ treeId, itemId }) =>
+ selectAtom(selectedIdsFamily(treeId), (ids) => ids.includes(itemId), Object.is),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -19,10 +36,9 @@ export const focusIdsFamily = atomFamily((_treeId: string) => {
return atom<{ lastId: string | null; anchorId: string | null }>({ lastId: null, anchorId: null });
});
-export const isLastFocusedFamily = atomFamily(
- ({ treeId, itemId }: { treeId: string; itemId: string }) =>
- selectAtom(focusIdsFamily(treeId), (v) => v.lastId === itemId, Object.is),
- (a, b) => a.treeId === b.treeId && a.itemId === b.itemId,
+export const isLastFocusedFamily = keyedFamily(
+ ({ treeId, itemId }: { treeId: string; itemId: string }) => `${treeId}\0${itemId}`,
+ ({ treeId, itemId }) => selectAtom(focusIdsFamily(treeId), (v) => v.lastId === itemId, Object.is),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -45,26 +61,28 @@ export const hoveredParentFamily = atomFamily((_treeId: string) => {
});
});
-export const isParentHoveredFamily = atomFamily(
+export const isParentHoveredFamily = keyedFamily(
({ treeId, parentId }: { treeId: string; parentId: string | null }) =>
+ `${treeId}\0${parentId ?? ""}`,
+ ({ treeId, parentId }) =>
selectAtom(hoveredParentFamily(treeId), (v) => v.parentId === parentId, Object.is),
- (a, b) => a.treeId === b.treeId && a.parentId === b.parentId,
);
-export const isAncestorHoveredFamily = atomFamily(
+export const isAncestorHoveredFamily = keyedFamily(
({ treeId, ancestorIds }: { treeId: string; ancestorIds: string[] }) =>
+ `${treeId}\0${ancestorIds.join(",")}`,
+ ({ treeId, ancestorIds }) =>
selectAtom(
hoveredParentFamily(treeId),
(v) => v.parentId && ancestorIds.includes(v.parentId),
Object.is,
),
- (a, b) => a.treeId === b.treeId && a.ancestorIds.join(",") === b.ancestorIds.join(","),
);
-export const isIndexHoveredFamily = atomFamily(
- ({ treeId, index }: { treeId: string; index: number }) =>
+export const isIndexHoveredFamily = keyedFamily(
+ ({ treeId, index }: { treeId: string; index: number }) => `${treeId}\0${index}`,
+ ({ treeId, index }) =>
selectAtom(hoveredParentFamily(treeId), (v) => v.index === index, Object.is),
- (a, b) => a.treeId === b.treeId && a.index === b.index,
);
export const hoveredParentDepthFamily = atomFamily((treeId: string) =>