world viz: repo focus mode — click a file/dir/run to enter the tree
MVP of the repo-detail sub-view. Click any file/dir/run/repo node
in Live and the viz culls to just that node's subtree — you're
now watching agents crawl the repo instead of the whole workspace.
Esc (or the pill button that appears) exits.
Backend
- routes/world.rs: file-op tool events now emit a `file:<path>`
world.touch IN ADDITION to the existing `tool:<name>` touch.
The path is pulled from the tool input's path / target / file
/ filename / url keys (same lookup summarize_input uses, but we
keep the full string so the client can build a real hierarchy).
Non-file tools are unchanged — they still hit tool:<name> nodes
as before.
Engine
- onTouch synthesizes a directory hierarchy when the id starts
with `file:`. Each intermediate path segment gets a `dir:<acc>`
node (label = the segment), parented at the previous dir; the
file itself parents at the innermost dir. Ensures the layout
spring-simulates as a tree naturally, no separate render mode
needed.
- New pawn fireColor for file: touches: coral #ff8a7a. Reads as
"file work" vs #5ec8d8 (tool convergence) vs #5fd08a (run
activity).
WorldCanvas
- Render pass now takes a `visibleNodes: Set<string> | null`.
When the selected id starts with file: / dir: / run: / repo:,
we BFS descendants via parentId and hide every non-descendant
node. Node meshes, glow sprites, hierarchy edges, and labels
all gate on the set. Pawns stay visible (agents still dart to
the focused files).
- ESC handler on window: clears the selection by calling
onSelect("") when a repo-focus id is set.
- Small "REPO FOCUS · <path>" pill lands at top-center with an
Esc button so the exit is discoverable at a glance without
learning the shortcut.
- Dashboard.onWorldSelect now treats empty string as "clear
focus" (setWorldSel(null)) so the same callback handles ESC.
Not yet: the always-on repo:<topic_id> node emitted at run
start when a research topic has a repo bound. Today the focus
works off run:<id> nodes; a repo:<id> anchor would let users
click without waiting for a first file touch. Also skipped:
per-file heat map / call-count visualization tied to touch
weight over time. Both are natural follow-ups on this bones.
This commit is contained in:
@@ -114,6 +114,33 @@ fn normalize_run_event(
|
||||
out.push(("node.activity", json!({ "nodeId": node_id, "label": tool, "kind": "service", "heat": if file_op { 1.0 } else { 0.9 } })));
|
||||
// the agent converges on the tool it's using (the Gource beam)
|
||||
out.push(("world.touch", json!({ "agentId": agent_id, "nodeId": node_id, "kind": "service", "weight": weight })));
|
||||
|
||||
// File-op tools ALSO emit a `file:<path>` touch so the repo
|
||||
// detail view can build the tree from real events. The path
|
||||
// comes from the input's `path` / `target` / `file` / `url`
|
||||
// keys — same lookup summarize_input does but we keep the
|
||||
// full string so the client can build the dir hierarchy.
|
||||
if file_op {
|
||||
if let Some(input) = payload.get("input") {
|
||||
for k in ["path", "target", "file", "filename", "url"] {
|
||||
if let Some(p) = input.get(k).and_then(|v| v.as_str()) {
|
||||
let cleaned = p.trim().trim_start_matches("./");
|
||||
if !cleaned.is_empty() {
|
||||
let file_node = format!("file:{cleaned}");
|
||||
out.push((
|
||||
"node.activity",
|
||||
json!({ "nodeId": file_node, "label": cleaned, "kind": "service", "heat": 1.0 }),
|
||||
));
|
||||
out.push((
|
||||
"world.touch",
|
||||
json!({ "agentId": agent_id, "nodeId": file_node, "kind": "service", "weight": 1.0 }),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"approval_required" => {
|
||||
let action = payload
|
||||
|
||||
Reference in New Issue
Block a user