aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-16 10:41:17 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-16 10:41:17 +0200
commit4802be13580de1529b7824a2337d132d64849801 (patch)
tree6dd77bcc00a0d8e494cf687918c86b9fd8e77051 /packages/meshbay-cli/src
parentd74d371bd7c0ed25f60754e475a78975bf2dc4ee (diff)
downloadmeshbay-4802be13580de1529b7824a2337d132d64849801.tar.gz
fix(cli): apply index updates in place instead of fetching the whole index
The interactive mode fetched and re-parsed the full index on every index_delta, on the loop receiving the download: 67 times in 80 s while a node enriched a library. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/src')
-rw-r--r--packages/meshbay-cli/src/meshbay_cli/client/tree.py33
-rw-r--r--packages/meshbay-cli/src/meshbay_cli/tui/screens.py30
2 files changed, 62 insertions, 1 deletions
diff --git a/packages/meshbay-cli/src/meshbay_cli/client/tree.py b/packages/meshbay-cli/src/meshbay_cli/client/tree.py
index 6e537ef..dd3acc8 100644
--- a/packages/meshbay-cli/src/meshbay_cli/client/tree.py
+++ b/packages/meshbay-cli/src/meshbay_cli/client/tree.py
@@ -57,6 +57,39 @@ class Tree:
dirs |= set(roots)
return cls(roots, dirs, files)
+ def apply_delta(self, delta: dict) -> bool:
+ """
+ Bring the tree up to an `index_delta`, in place. True when the delta
+ changed what the tree holds — a file added or removed, a root's state —
+ rather than only the fields of files already there.
+
+ A delta carries no `dirs`, so an empty folder made elsewhere is not in
+ it: a structural change is where the caller asks for the whole index
+ again, once. The common delta is enrichment filling in a file's fields,
+ about one a second while a library is being enriched, and asking for
+ the whole index on each of those took the download's own event loop:
+ 67 full indexes in the 80 s of one download.
+ """
+ updates = {e["id"]: e for e in delta.get("updates", [])}
+ gone = set(delta.get("deletions", []))
+ for parent, entries in self.files.items():
+ if gone or any(e["id"] in updates for e in entries):
+ self.files[parent] = [updates.get(e["id"], e) for e in entries
+ if e["id"] not in gone]
+ known = {e["id"] for entries in self.files.values() for e in entries}
+ added = [e for e in delta.get("additions", []) if e["id"] not in known]
+ for e in added:
+ self.files.setdefault(e["path"], []).append(e)
+ parts = e["path"].split("/")
+ for i in range(1, len(parts) + 1):
+ self.dirs.add("/".join(parts[:i]))
+ roots = {r["name"]: r for r in delta.get("roots", [])}
+ moved_roots = bool(roots) and roots != self.roots
+ if moved_roots:
+ self.roots = roots
+ self.dirs |= set(roots)
+ return bool(gone or added or moved_roots)
+
def root_available(self, path: str) -> bool:
root = self.roots.get(path.split("/", 1)[0])
return bool(root.get("available", True)) if root else True
diff --git a/packages/meshbay-cli/src/meshbay_cli/tui/screens.py b/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
index 39b5df3..b6fd347 100644
--- a/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
+++ b/packages/meshbay-cli/src/meshbay_cli/tui/screens.py
@@ -41,6 +41,9 @@ from .panes import LocalPane, RemotePane
from .widgets import Choice, TransferStrip
PER_GROUP = 2
+# A burst of index updates that added or removed files is answered by one full
+# index, this long after the last of them.
+REFRESH_AFTER_DELTA_S = 2.0
class SignInScreen(Screen):
@@ -118,6 +121,7 @@ class MainScreen(Screen):
self._trees: dict[str, Tree] = {}
self._gates: dict[str, asyncio.Semaphore] = {}
self._watching: set[str] = set()
+ self._refreshes: dict[str, asyncio.TimerHandle] = {}
def compose(self) -> ComposeResult:
with Horizontal():
@@ -150,12 +154,36 @@ class MainScreen(Screen):
self._trees[group["id"]] = Tree.from_index(await session.index())
if group["id"] not in self._watching:
self._watching.add(group["id"])
- session.ch.on_push("index_delta", lambda _msg, g=group: self._changed(g))
+ session.ch.on_push(
+ "index_delta", lambda msg, g=group, s=session: self._delta(g, s, msg))
return self._trees[group["id"]]
def forget_tree(self, group: dict) -> None:
self._trees.pop(group["id"], None)
+ def _delta(self, group: dict, session, msg: dict) -> None:
+ """A pushed index update, applied to the tree in place. Only one that
+ adds or removes something is drawn, and asks for the whole index — once
+ for a burst, not once per message."""
+ tree = self._trees.get(group["id"])
+ if tree is None:
+ return # the next look asks for the whole index anyway
+ try:
+ structural = tree.apply_delta(session.open_delta(msg))
+ except CliError:
+ structural = True
+ if not structural:
+ return
+ pending = self._refreshes.pop(group["id"], None)
+ if pending is not None:
+ pending.cancel()
+ self._refreshes[group["id"]] = asyncio.get_running_loop().call_later(
+ REFRESH_AFTER_DELTA_S, self._refreshed, group)
+
+ def _refreshed(self, group: dict) -> None:
+ self._refreshes.pop(group["id"], None)
+ self._changed(group)
+
def _changed(self, group: dict) -> None:
"""Something changed in the group: the index is asked for again, and the
pane redrawn if it is showing that group."""