mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 05:46:46 -06:00
This commit is contained in:
+2
-1
File diff suppressed because one or more lines are too long
@@ -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, _| {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user