M8: doc.list gives AI agents and plugins file names, not paths (#1117) (#1620)

This commit is contained in:
BFlat
2026-10-30 12:14:34 +07:00
committed by GitHub
parent 5247f73f59
commit b2885452fb
10 changed files with 86 additions and 15 deletions
+2 -1
View File
File diff suppressed because one or more lines are too long
+15 -7
View File
@@ -326,16 +326,24 @@ pub fn specs() -> Vec<CommandSpec> {
spec!(
"doc.list",
"List documents",
"{} → [{index, id, name, path, dirty, active}]: `id` names the file in a control or MCP call's `document`",
"{} → [{index, id, name, path?, dirty, active}]: `id` names the file in a control or MCP call's `document`; `path` only for the user's own scripts, not AI agents or plugins",
false,
|s, _| {
// Where the files are on disk is the user's: an agent gets their names (#1117).
let paths = s.agent.is_none();
Ok(Value::Array(
s.docs
.iter()
.enumerate()
.map(|(i, d)| json!({"index": i, "id": d.uid, "name": d.doc.name, "path": d.path, "dirty": d.is_dirty(), "active": i == s.active}))
.collect(),
))
s.docs
.iter()
.enumerate()
.map(|(i, d)| {
let mut e = json!({"index": i, "id": d.uid, "name": d.doc.name, "dirty": d.is_dirty(), "active": i == s.active});
if paths {
e["path"] = json!(d.path);
}
e
})
.collect(),
))
}
),
spec!("doc.home", "Home", "{}", false, |s, _| {
+48
View File
@@ -0,0 +1,48 @@
//! `doc.list` tells the user's own scripts where the open files are, but AI agents and plugins
//! get their names only (#1117).
use serde_json::json;
use crate::Session;
use crate::components::tests::ex;
use crate::plugins_tests::{OnRefusal, install, manifest, script};
const SECRET: &str = "/home/someone/private/clients/acme.figcraft";
fn two_files() -> Session {
let mut s = Session::new();
s.docs[0].path = Some(SECRET.into());
ex(&mut s, "file.new", json!({"name": "Front"}));
s
}
#[test]
fn the_users_own_calls_get_the_paths() {
let mut s = two_files();
let list = ex(&mut s, "doc.list", json!({}));
assert_eq!(list[0]["path"], json!(SECRET));
assert_eq!(list[1]["path"], json!(null), "a new file has none yet");
}
#[test]
fn an_agent_gets_names_and_ids_but_no_paths() {
let mut s = two_files();
let list = s.execute_as(Some("Claude"), "doc.list", &json!({})).unwrap();
assert_eq!(list.as_array().unwrap().len(), 2);
assert!(list[0]["id"].is_u64() && list[0]["name"].is_string());
assert!(list.as_array().unwrap().iter().all(|e| e.get("path").is_none()), "{list}");
// An agent with no name of its own is an agent too.
let list = s.execute_as(Some(""), "doc.list", &json!({})).unwrap();
assert!(!list.to_string().contains(SECRET));
}
#[test]
fn a_plugin_gets_names_but_no_paths() {
let mut s = two_files();
// The reply to its last call becomes its output.
let tail = "(call $set_output (i32.const 40000) (call $reply_read (i32.const 40000) (i32.const 20000))) (return (i32.const 0))";
install(&mut s, manifest("org.test.list", "read", &[]), &script(&[("doc.list", json!({}))], OnRefusal::Fail, tail));
let out = ex(&mut s, "plugin.run", json!({"id": "org.test.list"}))["output"].to_string();
assert!(out.contains("Front"), "{out}");
assert!(!out.contains(SECRET) && !out.contains("path"), "{out}");
}
+10 -1
View File
@@ -232,7 +232,16 @@ impl figcraft_plugin::Host for EngineHost<'_> {
}
Ok(v)
});
r.map(|v| v.to_string()).map_err(|e| e.to_string())
r.map(|mut v| {
// A plugin gets the open files' names, not where they are on disk (#1117).
if command == "doc.list" {
for e in v.as_array_mut().into_iter().flatten().filter_map(Value::as_object_mut) {
e.remove("path");
}
}
v.to_string()
})
.map_err(|e| e.to_string())
}
fn selection(&mut self) -> String {
+2
View File
@@ -277,6 +277,8 @@ mod devmode_behaviour_tests;
#[cfg(test)]
mod devmode_tail_tests;
#[cfg(test)]
mod doc_list_paths_tests;
#[cfg(test)]
mod doc_urls_tests;
#[cfg(test)]
mod double_click_tests;
+2 -1
View File
@@ -70,7 +70,8 @@ unknown id is an error.
request acts on that file without bringing its tab to the front; the tab in front comes back
afterwards unless the command opened or switched to another file itself. A file that isn't open is
an error, and so is a `document` naming a file behind the tab in front on any other method
(`ui.screenshot`, `ui.input`, `ui.key`… act on what the window shows).
(`ui.screenshot`, `ui.input`, `ui.key`… act on what the window shows). `doc.list` includes each file's
`path` only when the call names no `agent`: an AI agent's calls (MCP sets `agent`) get ids and names.
## Confining file access
+2 -1
View File
@@ -1,6 +1,6 @@
# Hardware and platform parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (a batch keeps one undo snapshot, not one per step (#1099); one depth cap bounds a whole render, pattern sources included, and render threads get 8 MiB stacks (#1082); in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); macOS: the Quit menu and ⌘Q ask about unsaved changes (#857); egui/eframe 0.37 dependency migration; hidden-window screenshot deadlines; zoom previews; hidden windows answer control requests; macOS file opening, the Windows start-up message, Linux packaging; copy and paste in the browser; Windows QA pass; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; pen pressure for the Pencil and brushes on Windows and from browser styluses; macOS: quitting ends the process once everything is kept, avoiding the Touch Bar teardown crash (#781); Linux and BSD clipboard: images under Wayland, reads time out; minimised windows under Wayland; window controls on Wayland without server decorations; canvas textures on device pixels; copy and paste in the browser; Windows QA pass; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; pen pressure for the Pencil and brushes on Windows and from browser styluses) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`doc.list` gives AI agents and plugins the open files' names, not their paths (#1117); a batch keeps one undo snapshot, not one per step (#1099); one depth cap bounds a whole render, pattern sources included, and render threads get 8 MiB stacks (#1082); in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); macOS: the Quit menu and ⌘Q ask about unsaved changes (#857); egui/eframe 0.37 dependency migration; hidden-window screenshot deadlines; zoom previews; hidden windows answer control requests; macOS file opening, the Windows start-up message, Linux packaging; copy and paste in the browser; Windows QA pass; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; pen pressure for the Pencil and brushes on Windows and from browser styluses; macOS: quitting ends the process once everything is kept, avoiding the Touch Bar teardown crash (#781); Linux and BSD clipboard: images under Wayland, reads time out; minimised windows under Wayland; window controls on Wayland without server decorations; canvas textures on device pixels; copy and paste in the browser; Windows QA pass; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; pen pressure for the Pencil and brushes on Windows and from browser styluses) · **Target:** Figma Design (desktop app 126.10.7)
How FigCraft uses hardware and which platforms it runs on, against Figma. Figma's desktop app is
Electron with a GPU helper process; its canvas renders on the GPU (WebGL, with WebGPU rolling out
@@ -51,6 +51,7 @@ Nothing has been timed against Figma on the same document (needs `.fig` import).
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | M8, #1117: AI agents and plugins no longer learn where the open files are on disk from `doc.list`. Numbers unchanged. |
| 2026-10-30 | minor | M8, #1099: long batches (scripts, plugins, MCP `batch`) no longer hold a document copy per step: one undo snapshot per batch (a 1,000-step batch: +165 MiB to +10 MiB peak). Numbers unchanged. |
| 2026-10-30 | minor | M8, #1082: canvas rendering of the deepest documents can't overflow a render thread: one depth cap bounds a render, pattern sources included, and render threads get 8 MiB stacks (a level takes about 6.5 KB). Numbers unchanged. |
| 2026-10-30 | minor | M8, #1123: the browser can't catch a panic, so an internal error stopped the page and lost its unsaved changes. Its panic hook now keeps the files with unsaved changes in the page's local storage (as many as fit, about 5 MB) and says so on the page; reloading opens them again as unsaved files. Numbers unchanged. |
+1 -1
View File
@@ -1333,7 +1333,7 @@ take an optional `document`: the file's `id` (in `run_command {command: "doc.lis
of the `document` outline `open_file` and `inspect_document` return) or the path of an open file.
The call acts on that file and the tab in front stays in front, unless the command itself opens or
switches to another file (`file.open`, `file.new`, `doc.switch`), as it would without `document`. A
file that isn't open is an error. `screenshot {window}` captures what the window shows, so it
file that isn't open is an error. `doc.list` gives an agent each open file's `id`, name and state, not`nwhere it is on disk (#1117). `screenshot {window}` captures what the window shows, so it
needs the file in front. Over the control channel, `engine.execute`, `document.inspect`,
`node.get` and `ui.render` take the same `document`; other methods refuse one naming a file behind
the tab in front. Headless sessions take it too.
+2 -2
View File
@@ -262,8 +262,8 @@ it can handle):
back), `codeConnect.import` with a `path` and `variable.import` with a file path instead of JSON,
`window.*` (new windows open files), `view.theme*`;
- live sessions and online files: `collab.*`;
- other documents and the version history: `doc.*` and `version.*` (except `doc.list` and
`version.list`);
- other documents and the version history: `doc.*` and `version.*` (except `doc.list`, which gives a
plugin the open files' names but not their paths, and `version.list`);
- the undo history and nesting: `edit.undo`, `edit.redo`, `edit.repeat`, `command.batch` (a run is
already one undo step), `plugin.*` / `plugins.*` (no plugin runs another) and `widget.*`;
- writing layer data whose key starts with `figcraft.widget` (a widget's own data: no plugin can
+2 -1
View File
File diff suppressed because one or more lines are too long