Merge pull request #6399 from dotCooCoo/feat/api-reference

Commands: engine.apiReference returns the command API reference in Markdown
This commit is contained in:
BFlat
2026-11-10 13:28:44 +07:00
committed by GitHub
37 changed files with 635 additions and 16 deletions
+1 -1
View File
@@ -597,7 +597,7 @@ fn exercise_mcp_core(
assert!(r["result"]["completion"]["values"].as_array().is_some_and(|v| v.contains(&json!("svg"))), "{r}");
mcp_send(stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}}));
assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3));
assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 4));
mcp_send(stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}}));
assert!(mcp_recv(lines, notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4));
+3
View File
@@ -2,6 +2,7 @@
pub mod actionset;
mod addons;
mod apiref;
pub(crate) mod appearance;
mod assets;
mod attributes;
@@ -384,6 +385,8 @@ pub fn command_specs() -> &'static [CommandSpec] {
v.extend(actionset::specs());
v.extend(importmatches::specs());
v.extend(addons::specs());
// Last, so the reference it writes ends with its own section.
v.extend(apiref::specs());
v
})
}
+461
View File
@@ -0,0 +1,461 @@
//! `engine.apiReference`: the command API reference in Markdown, written when it is asked for from
//! the parameter schemas of the built-in commands ([`crate::schemas`]). Each command that has a
//! schema gets a section, in registry order, with its parameters in the schema's order; a command
//! without a schema, a plug-in's included, is left out. The text depends only on the registry, so
//! every call in one build returns the same bytes.
//!
//! Text from a schema is escaped before it goes into a heading, a list item or a table cell: the
//! Markdown punctuation characters get a backslash (CommonMark 0.31.2, backslash escapes,
//! <https://spec.commonmark.org/0.31.2/#backslash-escapes>), so a description shows as plain text and
//! cannot end a table cell or make a link, an image or HTML; line breaks, tabs and other control
//! characters become spaces.
use serde_json::{Value, json};
use vectorcraft_apischema::{Bound, MAX_DEPTH, MAX_ID, Omitted, Param, ParamSchema, Type, Writes};
use super::{CommandSpec, Result, Session, always, cmd, command_specs};
use crate::EngineError;
const ID: &str = "engine.apiReference";
/// The largest reference the command returns, in bytes of UTF-8.
pub const MAX_BYTES: usize = 1 << 20;
static REFERENCE: ParamSchema = ParamSchema::new(
&[],
"the command API reference in Markdown, written from the built-in commands' parameter schemas: a section for each command that has one, in registry order",
)
.returns("{markdown: string}")
.writes(Writes::Nothing)
.since(2);
pub fn specs() -> Vec<CommandSpec> {
vec![cmd!(
query "engine.apiReference",
"Command API Reference",
[],
None,
"{} the command API reference in Markdown, written from the built-in commands' parameter schemas: a section for each command that has one, in registry order → {markdown: string}",
always,
api_reference,
schema: &REFERENCE
)]
}
fn api_reference(_: &mut Session, _: &Value) -> Result<Value> {
Ok(json!({ "markdown": reference(command_specs())? }))
}
const HEADER: &str = "# VectorCraft command API reference\n\nThis reference lists only the commands that have a parameter schema, in registry order. `engine.commands` lists every command, with its params text and whether it can run now.\n";
/// The reference for the commands of `specs` that have a schema, or an error when it would be
/// larger than [`MAX_BYTES`]; a reference that is too large is never cut short.
pub(crate) fn reference(specs: &[CommandSpec]) -> Result<String> {
let mut out = String::from(HEADER);
let mut sections = 0;
for spec in specs {
let Some(schema) = spec.schema else { continue };
let text = section(spec, schema);
if out.len() + text.len() > MAX_BYTES {
return Err(EngineError::Other(format!("`{ID}`: the reference would be larger than {MAX_BYTES} bytes")));
}
out.push_str(&text);
sections += 1;
}
if sections == 0 {
out.push_str("\nNo commands have parameter schemas.\n");
}
Ok(out)
}
/// One command's section: its heading, a list of what it is, and its parameters.
fn section(spec: &CommandSpec, schema: &ParamSchema) -> String {
let menu = if spec.menu.is_empty() { "None".to_string() } else { spec.menu.iter().map(|m| escape(m)).collect::<Vec<_>>().join(" › ") };
let mut s = format!("\n## {}\n\n", escape(spec.id));
s.push_str(&format!("- Label: {}\n", escape(spec.label)));
s.push_str(&format!("- Menu: {menu}\n"));
s.push_str(&format!("- API level: {}\n", schema.since));
s.push_str(&format!("- Deprecated: {}\n", level_text(schema.deprecated)));
s.push_str(&format!("- Writes: {}\n", writes_text(schema.writes)));
s.push_str(&format!("- Cost: {}\n", schema.cost));
s.push_str(&format!("- Summary: {}\n", text_or_none(schema.summary)));
s.push_str(&format!("- Returns: {}\n\n", text_or_none(schema.returns)));
if schema.params.is_empty() {
s.push_str("No parameters.\n");
return s;
}
s.push_str("| Parameter | Type | Range | Unit | Default | Required | Since | Deprecated | Description |\n");
s.push_str("|---|---|---|---|---|---|---|---|---|\n");
rows("", schema.params, 0, &mut s);
s
}
/// A table row for each of `params`, and after each one the rows of the members of an object it
/// holds: `edits[].name` for an object in a list, `options.size` for an object.
fn rows(prefix: &str, params: &[Param], depth: usize, out: &mut String) {
for p in params {
let name = if prefix.is_empty() { p.name.to_string() } else { format!("{prefix}.{}", p.name) };
let (range, unit) = range_and_unit(&p.ty);
out.push_str(&format!(
"| {} | {} | {range} | {unit} | {} | {} | {} | {} | {} |\n",
escape(&name),
type_text(&p.ty, depth),
default_text(p.omitted),
if p.required { "Yes" } else { "No" },
p.since,
level_text(p.deprecated),
description(p),
));
if depth < MAX_DEPTH
&& let Some((members, path)) = members(&p.ty, name, depth)
{
rows(&path, members, depth + 1, out);
}
}
}
/// The parameters of the object `ty` holds, directly or as the items of lists, with the path of
/// its members' prefix (`name`, `name[]`, `name[][]`).
fn members(ty: &Type, mut path: String, depth: usize) -> Option<(&'static [Param], String)> {
let mut ty = ty;
for _ in depth..MAX_DEPTH {
match ty {
Type::Object(params) => return Some((params, path)),
Type::Array(item) => {
path.push_str("[]");
ty = item;
}
_ => return None,
}
}
None
}
fn type_text(ty: &Type, depth: usize) -> String {
match ty {
Type::Number { .. } => "number".into(),
Type::Integer { .. } => "integer".into(),
Type::Boolean => "boolean".into(),
Type::String => "string".into(),
Type::Enum(names) => format!("one of {}", names.iter().map(|n| format!("\"{}\"", escape(n))).collect::<Vec<_>>().join(", ")),
Type::Id => "object id (integer)".into(),
Type::Ids => "list of object ids".into(),
Type::Color => escape("color (a string, [r, g, b] or an object)"),
Type::Point => escape("point ([x, y], two numbers)"),
Type::Object(_) => "object".into(),
Type::Array(_) if depth >= MAX_DEPTH => "list".into(),
Type::Array(item) => format!("list of {}", type_text(item, depth + 1)),
}
}
/// The range and unit cells of a value of type `ty`.
fn range_and_unit(ty: &Type) -> (String, String) {
let ends = |low: Option<String>, high: Option<String>| {
let both: Vec<String> = low.into_iter().chain(high).collect();
if both.is_empty() { "None".to_string() } else { both.join(", ") }
};
match ty {
Type::Number { min, max, unit } => {
let low = min.map(|b| match b {
Bound::Inclusive(a) => format!("at least {a}"),
Bound::Exclusive(a) => format!("more than {a}"),
});
let high = max.map(|b| match b {
Bound::Inclusive(a) => format!("at most {a}"),
Bound::Exclusive(a) => format!("less than {a}"),
});
(ends(low, high), unit.map_or("None".to_string(), escape))
}
Type::Integer { min, max } => (ends(min.map(|a| format!("at least {a}")), max.map(|a| format!("at most {a}"))), "None".into()),
Type::Id => (format!("at least 0, at most {MAX_ID}"), "None".into()),
_ => ("None".into(), "None".into()),
}
}
fn default_text(omitted: Omitted) -> String {
match omitted {
Omitted::Nothing => "None".into(),
Omitted::Value(json) => format!("{} (JSON)", escape(json)),
Omitted::Note(note) => format!("{} (worked out by the command)", escape(note)),
}
}
/// The parameter's doc text, and what the schema says about how its value is checked.
fn description(p: &Param) -> String {
let mut parts = Vec::new();
if !p.doc.is_empty() {
parts.push(escape(p.doc));
}
if p.null_omitted {
parts.push("null reads as omitted".to_string());
}
if p.lenient {
parts.push("lenient (a value of another type or out of range is passed to the command, not refused)".to_string());
}
if parts.is_empty() { "None".to_string() } else { parts.join("; ") }
}
fn level_text(level: Option<u32>) -> String {
level.map_or("None".to_string(), |l| format!("API level {l}"))
}
fn writes_text(writes: Writes) -> &'static str {
match writes {
Writes::Nothing => "nothing",
Writes::Session => "session",
Writes::Preferences => "preferences",
Writes::Document => "document",
}
}
fn text_or_none(text: &str) -> String {
if text.is_empty() { "None".to_string() } else { escape(text) }
}
/// The characters [`escape`] puts a backslash before.
const ESCAPED: &[char] = &['\\', '|', '[', ']', '<', '>', '`', '*', '_', '!', '&', '#', '~', ':', '@'];
/// `text` as plain Markdown text on one line: each of [`ESCAPED`] with a backslash before it, the
/// `.` after `www` too (so no autolink starts), and each control character as a space.
pub(crate) fn escape(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for c in text.chars() {
if c.is_control() {
out.push(' ');
continue;
}
let after_www = out.len().checked_sub(3).and_then(|at| out.as_bytes().get(at..)).is_some_and(|b| b.eq_ignore_ascii_case(b"www"));
if ESCAPED.contains(&c) || (c == '.' && after_www) {
out.push('\\');
}
out.push(c);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn run(_: &mut Session, _: &Value) -> Result<Value> {
Ok(Value::Null)
}
fn spec(id: &'static str, menu: &'static [&'static str], schema: Option<&'static ParamSchema>) -> CommandSpec {
CommandSpec { id, label: "Label", menu, shortcut: None, params: "", enabled: always, run, journal: false, schema }
}
/// The cells of a table row as a GFM table reads them: split at each `|` that has no backslash
/// before it, with the outer pipes and the spaces around each cell taken off.
fn cells(row: &str) -> Vec<String> {
let inner = row.strip_prefix('|').and_then(|r| r.strip_suffix('|')).unwrap_or_else(|| panic!("not a table row: {row}"));
let (mut out, mut cell, mut chars) = (Vec::new(), String::new(), inner.chars());
while let Some(c) = chars.next() {
match c {
'\\' => {
cell.push(c);
cell.extend(chars.next());
}
'|' => out.push(std::mem::take(&mut cell).trim().to_string()),
_ => cell.push(c),
}
}
out.push(cell.trim().to_string());
out
}
/// What `text` shows as: each escaped character as itself. Panics on a Markdown punctuation
/// character without its backslash, or a backslash before a character that needs none.
fn literal(text: &str) -> String {
let (mut out, mut chars) = (String::new(), text.chars());
while let Some(c) = chars.next() {
assert!(!ESCAPED.contains(&c) || c == '\\', "`{c}` is not escaped in {text}");
if c == '\\' {
let next = chars.next().unwrap_or_else(|| panic!("a backslash ends {text}"));
assert!(ESCAPED.contains(&next) || next == '.', "`\\{next}` in {text}");
out.push(next);
} else {
out.push(c);
}
}
out
}
/// The shown cells of the row of parameter `name`.
fn row(markdown: &str, name: &str) -> Vec<String> {
let found = markdown.lines().filter(|l| l.starts_with('|')).find(|l| cells(l).first().map(|c| literal(c)).as_deref() == Some(name));
let row = found.unwrap_or_else(|| panic!("no row for {name} in\n{markdown}"));
let cells = cells(row);
assert_eq!(cells.len(), 9, "{row}");
cells.iter().map(|c| literal(c)).collect()
}
/// The text after `- {label}: ` in the section's list.
fn item(markdown: &str, label: &str) -> String {
let prefix = format!("- {label}: ");
let line = markdown.lines().find_map(|l| l.strip_prefix(prefix.as_str()));
literal(line.unwrap_or_else(|| panic!("no {label} in\n{markdown}")))
}
/// Two calls give the same bytes, with no document open, and the reference has one section
/// for each built-in command that has a schema, in registry order, ending with this command's.
#[test]
fn the_reference_lists_each_schema_once_in_registry_order_and_is_the_same_each_time() {
let mut s = Session::new();
let first = s.execute(ID, &json!({})).unwrap();
let second = s.execute(ID, &Value::Null).unwrap();
assert_eq!(first, second);
let markdown = first["markdown"].as_str().unwrap();
assert_eq!(markdown, reference(command_specs()).unwrap());
assert!(markdown.len() < MAX_BYTES / 10, "{} bytes", markdown.len());
let headings: Vec<String> = markdown.lines().filter_map(|l| l.strip_prefix("## ")).map(literal).collect();
let expected: Vec<&str> = command_specs().iter().filter(|c| c.schema.is_some()).map(|c| c.id).collect();
assert_eq!(headings, expected);
assert_eq!(headings.last().map(String::as_str), Some(ID));
assert!(expected.contains(&"view.saved.new") && !headings.iter().any(|h| h == "file.new"));
let new_view = &markdown[markdown.find("## view.saved.new").unwrap()..];
let names: Vec<String> = new_view
.lines()
.skip_while(|l| !l.starts_with("|---"))
.skip(1)
.take_while(|l| l.starts_with('|'))
.map(|l| literal(&cells(l)[0]))
.collect();
assert_eq!(names, ["name", "center", "zoom", "rotation"]);
let edit = &markdown[markdown.find("## view.saved.edit").unwrap()..];
assert_eq!(row(edit, "edits[].newName")[8], "null reads as omitted");
assert_eq!(row(edit, "newName")[8], "rename; null reads as omitted");
}
const INNER: &[Param] =
&[Param::new("size", Type::Integer { min: Some(1), max: Some(10) }), Param::new("mode", Type::Enum(&["a", "b"])).defaults_to("\"a\"")];
static FULL: ParamSchema = ParamSchema::new(
&[
Param::new("zoom", Type::Number { min: Some(Bound::Exclusive(0.0)), max: Some(Bound::Inclusive(64.0)), unit: Some("pt") })
.doc("1 = 100%"),
Param::new("name", Type::String).default_note("the first free name").null_omitted(),
Param::new("old", Type::Boolean).defaults_to("true").since(2).deprecated(3).lenient(),
Param::new("edits", Type::Array(&Type::Object(INNER))).optional(),
Param::new("ids", Type::Ids).optional(),
Param::new("at", Type::Point).optional(),
Param::new("color", Type::Color).optional(),
Param::new("id", Type::Id).optional(),
Param::new("ratio", Type::Number { min: Some(Bound::Inclusive(-1.5)), max: Some(Bound::Exclusive(2.0)), unit: None }).optional(),
],
"do the thing",
)
.returns("{done}")
.writes(Writes::Preferences)
.since(2)
.deprecated(4)
.cost(5);
/// Each field of a schema shows, nested members in their order under dotted names.
#[test]
fn every_field_of_a_schema_is_shown() {
let markdown = reference(&[spec("test.full", &["View", "Saved"], Some(&FULL))]).unwrap();
for (label, shown) in [
("Label", "Label"),
("Menu", "View › Saved"),
("API level", "2"),
("Deprecated", "API level 4"),
("Writes", "preferences"),
("Cost", "5"),
("Summary", "do the thing"),
("Returns", "{done}"),
] {
assert_eq!(item(&markdown, label), shown, "{label}");
}
let lenient = "lenient (a value of another type or out of range is passed to the command, not refused)";
let expected: [[&str; 9]; 11] = [
["zoom", "number", "more than 0, at most 64", "pt", "None", "Yes", "1", "None", "1 = 100%"],
["name", "string", "None", "None", "the first free name (worked out by the command)", "No", "1", "None", "null reads as omitted"],
["old", "boolean", "None", "None", "true (JSON)", "No", "2", "API level 3", lenient],
["edits", "list of object", "None", "None", "None", "No", "1", "None", "None"],
["edits[].size", "integer", "at least 1, at most 10", "None", "None", "Yes", "1", "None", "None"],
["edits[].mode", "one of \"a\", \"b\"", "None", "None", "\"a\" (JSON)", "No", "1", "None", "None"],
["ids", "list of object ids", "None", "None", "None", "No", "1", "None", "None"],
["at", "point ([x, y], two numbers)", "None", "None", "None", "No", "1", "None", "None"],
["color", "color (a string, [r, g, b] or an object)", "None", "None", "None", "No", "1", "None", "None"],
["id", "object id (integer)", "at least 0, at most 9007199254740991", "None", "None", "No", "1", "None", "None"],
["ratio", "number", "at least -1.5, less than 2", "None", "None", "No", "1", "None", "None"],
];
let names: Vec<String> =
markdown.lines().filter(|l| l.starts_with("| ") && !l.starts_with("| Parameter")).map(|l| literal(&cells(l)[0])).collect();
assert_eq!(names, expected.map(|r| r[0]));
for want in expected {
assert_eq!(row(&markdown, want[0]), want, "{}", want[0]);
}
}
const HOSTILE: &str = "a | b [link](https://evil.example) <script>x</script> `code` \\ *em* _u_ ![img](x) &amp; ~~s~~ # h www.example.com me@example.com\r\nnext\tline";
static ESCAPES: ParamSchema =
ParamSchema::new(&[Param::new("a|b", Type::String).doc(HOSTILE).defaults_to("\"<b>|</b>\"")], HOSTILE).returns(HOSTILE);
/// Markdown in a schema's text shows as plain text: a description stays in its own cell of a
/// nine-cell row, every punctuation character is escaped, and line breaks and tabs are spaces.
#[test]
fn markdown_in_schema_text_stays_literal_in_its_cell() {
let markdown = reference(&[spec("test.escapes", &["A|B", "[x](y)"], Some(&ESCAPES))]).unwrap();
let shown = HOSTILE.replace(['\r', '\n', '\t'], " ");
let cells = row(&markdown, "a|b");
assert_eq!(cells[4], "\"<b>|</b>\" (JSON)");
assert_eq!(cells[8], shown);
assert_eq!(item(&markdown, "Summary"), shown);
assert_eq!(item(&markdown, "Returns"), shown);
assert_eq!(item(&markdown, "Menu"), "A|B › [x](y)");
assert!(markdown.contains("www\\.example"), "{markdown}");
let table: Vec<&str> = markdown.lines().filter(|l| l.starts_with('|')).collect();
assert_eq!(table.len(), 3, "the header, the delimiter row and one row: {table:?}");
assert!(!markdown.contains('\r') && !markdown.contains('\t'));
}
/// A registry with no schema gives the header and a line that says so; a command without a
/// schema adds nothing.
#[test]
fn a_registry_without_schemas_gives_a_document_that_says_so() {
let empty = format!("{HEADER}\nNo commands have parameter schemas.\n");
assert_eq!(reference(&[]).unwrap(), empty);
assert_eq!(reference(&[spec("test.none", &["View"], None)]).unwrap(), empty);
let one = reference(&[spec("test.none", &[], None), spec("test.full", &[], Some(&FULL)), spec("test.other", &[], None)]).unwrap();
assert_eq!(one, reference(&[spec("test.full", &[], Some(&FULL))]).unwrap());
}
/// A reference of exactly [`MAX_BYTES`] is returned; one byte more is an error naming the
/// command, with no part of the reference.
#[test]
fn a_reference_past_the_limit_is_an_error() {
let with = |summary: String| -> Result<String> {
let schema: &'static ParamSchema = Box::leak(Box::new(ParamSchema::new(&[], Box::leak(summary.into_boxed_str()))));
reference(&[spec("test.big", &[], Some(schema))])
};
let base = with("x".into()).unwrap().len();
let fits = with("x".repeat(MAX_BYTES - base + 1)).unwrap();
assert_eq!(fits.len(), MAX_BYTES);
let e = with("x".repeat(MAX_BYTES - base + 2)).unwrap_err().to_string();
assert!(e.contains(ID) && e.contains("larger than 1048576 bytes"), "{e}");
assert!(e.len() < 200, "{e}");
}
/// An unknown key is refused with a parameter error naming it, and the command needs no
/// document.
#[test]
fn an_unknown_parameter_is_refused_naming_it() {
let mut s = Session::new();
let e = s.execute(ID, &json!({"prefix": "view."})).unwrap_err().to_string();
assert!(e.contains(ID) && e.contains("`prefix`"), "{e}");
assert!(s.execute(ID, &json!([])).is_err());
assert!(s.active().is_none());
assert!(s.execute(ID, &json!({})).is_ok());
}
#[test]
fn escape_marks_each_punctuation_character_and_flattens_control_characters() {
assert_eq!(escape("a|b"), "a\\|b");
assert_eq!(escape("WWW.x and w.x"), "WWW\\.x and w.x");
assert_eq!(escape("é\u{7}\u{85}z"), "é z");
assert_eq!(escape("1.5 > 1"), "1.5 \\> 1");
}
}
+7 -3
View File
@@ -26,10 +26,10 @@ use crate::cmd::{CommandSpec, bad, command_specs};
use crate::{EngineError, Result};
/// The API level of the commands' parameters.
pub const API_VERSION: u32 = 1;
pub const API_VERSION: u32 = 2;
/// [`schema_hash`] at each API level, oldest first; the last row is [`API_VERSION`]'s.
pub const SCHEMA_HASHES: &[(u32, u64)] = &[(1, 0x9cdb_1087_7074_855c)];
pub const SCHEMA_HASHES: &[(u32, u64)] = &[(1, 0x9cdb_1087_7074_855c), (2, 0x0e31_5d96_7bc9_2653)];
/// The hash of every built-in command's schema ([`vectorcraft_apischema::hash`]).
pub fn schema_hash() -> u64 {
@@ -296,8 +296,12 @@ mod tests {
("view.capsLock", json!([])),
("view.saved.new", json!(5)),
("view.transparencyGrid", json!("on")),
("engine.apiReference", json!({})),
("engine.apiReference", json!(null)),
("engine.apiReference", json!({"prefix": "view."})),
("engine.apiReference", json!([])),
];
assert_eq!(schemas().len(), 6);
assert_eq!(schemas().len(), 7);
for (id, schema) in schemas() {
let written = schema.to_json();
check_schema(&written, id).unwrap_or_else(|e| panic!("{e}\n{written:#}"));
+75 -1
View File
@@ -18,6 +18,20 @@ pub const DOC_JSON_URI: &str = "vectorcraft://document/json";
/// The live command catalog, including enabled state.
pub const COMMANDS_URI: &str = "vectorcraft://commands";
/// The command API reference in Markdown (`engine.apiReference`).
pub const REFERENCE_URI: &str = "vectorcraft://commands/reference";
/// The media type of the JSON resources.
pub const JSON: &str = "application/json";
/// The media type of the command API reference.
pub const MARKDOWN: &str = "text/markdown; charset=utf-8";
/// What `resources/read` returns for a URI: the text and its media type.
#[derive(Debug, PartialEq)]
pub struct Contents {
pub mime: &'static str,
pub text: String,
}
/// Why a resource could not be read. The distinction matters: the server turns it into `-32002`
/// (no such resource) or `-32602` (bad URI), and guessing from the message text would not hold.
@@ -83,9 +97,24 @@ pub fn list() -> Value {
{"uri": DOC_URI, "name": "document", "title": "Active document (summary)", "description": "Layer tree, artboards, selection and history of the active document (document.inspect)", "mimeType": "application/json"},
{"uri": DOC_JSON_URI, "name": "document-json", "title": "Active document (full model)", "description": "The complete document model as JSON (document.json)", "mimeType": "application/json"},
{"uri": COMMANDS_URI, "name": "commands", "title": "Command catalog", "description": "Command ids, labels, parameter descriptions and current enabled state", "mimeType": "application/json"},
{"uri": REFERENCE_URI, "name": "command-reference", "title": "Command API reference", "description": "Markdown reference of the commands that have a parameter schema: types, ranges, units, defaults, API levels, what each writes and returns (engine.apiReference)", "mimeType": MARKDOWN},
]})
}
/// `resources/read`: the text behind a resource URI, the command API reference as Markdown and
/// every other resource as indented JSON ([`read`]).
pub fn read_contents(b: &mut dyn Backend, uri: &str) -> Result<Contents, ReadError> {
if uri == REFERENCE_URI {
let mut reply = exec(b, "engine.apiReference").map_err(ReadError::Backend)?;
return match reply.get_mut("markdown").map(Value::take) {
Some(Value::String(text)) => Ok(Contents { mime: MARKDOWN, text }),
_ => Err(ReadError::Backend("engine.apiReference returned no `markdown` text".into())),
};
}
let v = read(b, uri)?;
Ok(Contents { mime: JSON, text: serde_json::to_string_pretty(&v).unwrap_or_default() })
}
/// `resources/templates/list`.
pub fn templates() -> Value {
let list: Vec<Value> = TEMPLATES
@@ -246,7 +275,7 @@ mod tests {
#[test]
fn lists_are_well_formed() {
let l = list();
assert_eq!(l["resources"].as_array().map(Vec::len), Some(3));
assert_eq!(l["resources"].as_array().map(Vec::len), Some(4));
let t = templates();
let items = t["resourceTemplates"].as_array().expect("templates");
assert_eq!(items.len(), TEMPLATES.len());
@@ -257,6 +286,51 @@ mod tests {
}
}
/// A backend that gives every call the same reply.
struct Fixed(Result<Value, String>);
impl Backend for Fixed {
fn call(&mut self, _: &str, _: Value) -> Result<Value, String> {
self.0.clone()
}
fn has_ui(&self) -> bool {
false
}
fn describe(&self) -> String {
"fixed".into()
}
}
/// The command reference is listed and read as Markdown, with the text `engine.apiReference`
/// returns, and with no document open; the other fixed resources still read as JSON.
#[test]
fn the_command_reference_reads_as_markdown() {
let listed = list();
let entry = listed["resources"].as_array().and_then(|r| r.iter().find(|r| r["uri"] == REFERENCE_URI)).expect("listed");
assert_eq!(entry["mimeType"], MARKDOWN);
assert_eq!(entry["name"], "command-reference");
let mut b = Box::new(crate::Headless::new());
let read = read_contents(b.as_mut(), REFERENCE_URI).unwrap();
assert_eq!(read.mime, MARKDOWN);
let direct = b.call("engine.execute", json!({"command": "engine.apiReference", "params": {}})).unwrap();
assert_eq!(read.text, direct["markdown"].as_str().unwrap());
assert!(read.text.starts_with("# VectorCraft command API reference\n"), "{}", read.text);
assert_eq!(read_contents(b.as_mut(), REFERENCE_URI).unwrap(), read);
let commands = read_contents(b.as_mut(), COMMANDS_URI).unwrap();
assert_eq!(commands.mime, JSON);
assert!(serde_json::from_str::<Value>(&commands.text).unwrap().is_array());
assert!(matches!(read_contents(b.as_mut(), "vectorcraft://commands/nope"), Err(ReadError::NotFound(_))));
}
/// A reply without the Markdown text, and an app that cannot be reached, are backend errors.
#[test]
fn a_bad_or_failed_reference_reply_is_a_backend_error() {
for reply in [Ok(json!({})), Ok(json!({"markdown": 5})), Ok(json!("text")), Err("the app is not running".to_string())] {
let mut b = Fixed(reply.clone());
assert!(matches!(read_contents(&mut b, REFERENCE_URI), Err(ReadError::Backend(_))), "{reply:?}");
}
}
#[test]
fn missing_and_unknown_uris_are_errors() {
let mut b = Box::new(crate::Headless::with_document());
+2 -2
View File
@@ -439,12 +439,12 @@ impl Server {
}
"resources/read" => {
let uri = params.get("uri").and_then(Value::as_str).ok_or((INVALID_PARAMS, "missing `uri`".to_string()))?;
let v = resources::read(self.backend.as_mut(), uri).map_err(|e| match e {
let read = resources::read_contents(self.backend.as_mut(), uri).map_err(|e| match e {
resources::ReadError::NotFound(uri) => (RESOURCE_NOT_FOUND, format!("resource not found: {uri}")),
resources::ReadError::Invalid(why) => (INVALID_PARAMS, why),
resources::ReadError::Backend(why) => (INTERNAL_ERROR, why),
})?;
Ok(json!({"contents": [{"uri": uri, "mimeType": "application/json", "text": serde_json::to_string_pretty(&v).unwrap_or_default()}]}))
Ok(json!({"contents": [{"uri": uri, "mimeType": read.mime, "text": read.text}]}))
}
"completion/complete" => Ok(prompts::complete(self.backend.as_mut(), params)),
"logging/setLevel" => {
+1 -1
View File
@@ -304,7 +304,7 @@ fn headless_end_to_end() {
// Resources.
let v = rpc(&mut s, 10, "resources/list", json!({}));
assert_eq!(v["result"]["resources"].as_array().unwrap().len(), 3);
assert_eq!(v["result"]["resources"].as_array().unwrap().len(), 4);
let v = rpc(&mut s, 11, "resources/read", json!({"uri": "vectorcraft://document"}));
let text = v["result"]["contents"][0]["text"].as_str().unwrap();
assert!(serde_json::from_str::<Value>(text).unwrap()["layers"].is_array());
+10 -2
View File
@@ -290,8 +290,16 @@ fn resources_are_json() {
let list = rpc(&mut s, 2, "resources/list", json!({}));
for r in list["result"]["resources"].as_array().unwrap() {
let v = rpc(&mut s, 3, "resources/read", json!({"uri": r["uri"]}));
let text = v["result"]["contents"][0]["text"].as_str().unwrap();
serde_json::from_str::<Value>(text).unwrap();
let content = &v["result"]["contents"][0];
assert_eq!(content["mimeType"], r["mimeType"], "{}", r["uri"]);
assert_eq!(content["uri"], r["uri"]);
let text = content["text"].as_str().unwrap();
if r["mimeType"] == "application/json" {
serde_json::from_str::<Value>(text).unwrap();
} else {
assert_eq!(r["mimeType"], "text/markdown; charset=utf-8", "{}", r["uri"]);
assert!(text.starts_with("# ") && text.ends_with('\n'), "{}", r["uri"]);
}
}
}
+1
View File
@@ -567,6 +567,7 @@ font-technology Variable Променливи
Coming soon — tracked in the parity plan Очаквайте скоро — следи се в плана за съвместимост
Command Click to Select Objects Behind Щракване с Command за избор на обекти отзад
Command Dialog Диалогов прозорец за команда
Command API Reference Справочник за API на командите
Community Общност
Compatibility: Съвместимост:
Compatible Gradient and Gradient Mesh Printing Съвместим печат на градиент и градиентна мрежа
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -965,6 +965,7 @@ lighting Light Svjetlo
Color Picker… Birač boja…
Coming soon — tracked in the parity plan Uskoro — prati se u planu usklađenosti
Command Dialog Dijalog naredbe
Command API Reference Referenca API-ja naredbi
Copy Selected Style Kopiraj odabrani stil
Corners… Uglovi…
Define Perspective Grid… Definiši mrežu perspektive…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -794,6 +794,7 @@ antiAlias Sharp Nítid
Command Ordre
Command Click to Select Objects Behind Clic amb Ordre per seleccionar els objectes del darrere
Command Dialog Diàleg d’ordres
Command API Reference Referència de l’API d’ordres
Comment not closed Comentari no tancat
Commenting, filling in form fields and signing Comentar, emplenar camps de formulari i signar
Commits Commits
@@ -5432,6 +5433,7 @@ digits Malayalam Malaiàlam
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out S’ha carregat “{name}”: s’ha omès {n} pas|S’ha carregat “{name}”: s’han omès {n} passos
@msg not an action set file no és un fitxer de conjunt d’accions
@msg the action set file is larger than 8 MiB el fitxer de conjunt d’accions supera els 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: la referència superaria els {MAX_BYTES} bytes
@msg the action set file holds more than 1,000 actions el fitxer de conjunt d’accions conté més de 1.000 accions
@msg the action set file needs a later version of VectorCraft el fitxer de conjunt d’accions necessita una versió més recent del VectorCraft
@msg an action in the action set file has more than 10,000 steps una acció del fitxer de conjunt d’accions té més de 10.000 passos
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -985,6 +985,7 @@ lighting Light Světlo
Color Picker… Výběr barvy…
Coming soon — tracked in the parity plan Připravujeme — sledováno v plánu parity
Command Dialog Dialogové okno příkazu
Command API Reference Reference API příkazů
Copy Selected Style Kopírovat vybraný styl
Corners… Rohy…
Define Perspective Grid… Definovat perspektivní mřížku…
@@ -4722,6 +4723,7 @@ imageTrace Outline Obrys
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Načteno „{name}“: vynechán {n} krok|Načteno „{name}“: vynechány {n} kroky|Načteno „{name}“: vynecháno {n} kroků
@msg not an action set file není to soubor sady akcí
@msg the action set file is larger than 8 MiB soubor sady akcí je větší než 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: reference by byla větší než {MAX_BYTES} bajtů
@msg the action set file holds more than 1,000 actions soubor sady akcí obsahuje více než 1 000 akcí
@msg the action set file needs a later version of VectorCraft soubor sady akcí vyžaduje novější verzi aplikace VectorCraft
@msg an action in the action set file has more than 10,000 steps akce v souboru sady akcí má více než 10 000 kroků
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -906,6 +906,7 @@ lighting Light Lys
Color Picker… Farvevælger…
Coming soon — tracked in the parity plan Kommer snart — følges i paritetsplanen
Command Dialog Kommandodialog
Command API Reference API-reference for kommandoer
Copy Selected Style Kopiér markeret typografi
Corners… Hjørner…
Define Perspective Grid… Definér perspektivgitter…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -560,6 +560,7 @@ font-technology Variable Variabel
Coming soon — tracked in the parity plan Demnächst verfügbar – im Paritätsplan vermerkt
Command Click to Select Objects Behind Cmd-Klick wählt dahinterliegende Objekte aus
Command Dialog Befehlsdialog
Command API Reference API-Referenz der Befehle
Community Community
Compatibility: Kompatibilität:
Compatible Gradient and Gradient Mesh Printing Kompatibles Drucken von Verläufen und Verlaufsgittern
@@ -4329,6 +4330,7 @@ theme Appearance Erscheinungsbild
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out „{name}“ geladen: {n} Schritt wurde ausgelassen|„{name}“ geladen: {n} Schritte wurden ausgelassen
@msg not an action set file keine Aktionssatzdatei
@msg the action set file is larger than 8 MiB die Aktionssatzdatei ist größer als 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: die Referenz wäre größer als {MAX_BYTES} Byte
@msg the action set file holds more than 1,000 actions die Aktionssatzdatei enthält mehr als 1.000 Aktionen
@msg the action set file needs a later version of VectorCraft die Aktionssatzdatei erfordert eine neuere Version von VectorCraft
@msg an action in the action set file has more than 10,000 steps eine Aktion in der Aktionssatzdatei hat mehr als 10.000 Schritte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -542,6 +542,7 @@ font-technology Variable Μεταβλητές
Coming soon — tracked in the parity plan Προσεχώς — περιλαμβάνεται στο σχέδιο αντιστοιχίας λειτουργιών
Command Click to Select Objects Behind Command-κλικ για επιλογή αντικειμένων από πίσω
Command Dialog Παράθυρο εντολών
Command API Reference Αναφορά API εντολών
Community Κοινότητα
Compatibility: Συμβατότητα:
Compatible Gradient and Gradient Mesh Printing Συμβατή εκτύπωση ντεγκραντέ και πλέγματος ντεγκραντέ
@@ -4367,6 +4368,7 @@ digits Malayalam Μαλαγιάλαμ
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Φορτώθηκε το «{name}»: παραλείφθηκε {n} βήμα|Φορτώθηκε το «{name}»: παραλείφθηκαν {n} βήματα
@msg not an action set file δεν είναι αρχείο συνόλου ενεργειών
@msg the action set file is larger than 8 MiB το αρχείο συνόλου ενεργειών είναι μεγαλύτερο από 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: η αναφορά θα ήταν μεγαλύτερη από {MAX_BYTES} byte
@msg the action set file holds more than 1,000 actions το αρχείο συνόλου ενεργειών περιέχει περισσότερες από 1.000 ενέργειες
@msg the action set file needs a later version of VectorCraft το αρχείο συνόλου ενεργειών απαιτεί νεότερη έκδοση του VectorCraft
@msg an action in the action set file has more than 10,000 steps μια ενέργεια στο αρχείο συνόλου ενεργειών έχει περισσότερα από 10.000 βήματα
Can't render this file because it is too large.
+2
View File
@@ -582,6 +582,7 @@ font-technology Variable Variables
Coming soon — tracked in the parity plan Próximamente: incluido en el plan de paridad
Command Click to Select Objects Behind Cmd+clic para seleccionar objetos de detrás
Command Dialog Cuadro de diálogo de comando
Command API Reference Referencia de la API de comandos
Community Comunidad
Compatibility: Compatibilidad:
Compatible Gradient and Gradient Mesh Printing Impresión compatible de degradados y mallas de degradado
@@ -4413,6 +4414,7 @@ axis Both Ambos
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Se cargó “{name}”: se omitió {n} paso|Se cargó “{name}”: se omitieron {n} pasos
@msg not an action set file no es un archivo de conjunto de acciones
@msg the action set file is larger than 8 MiB el archivo de conjunto de acciones supera los 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: la referencia superaría los {MAX_BYTES} bytes
@msg the action set file holds more than 1,000 actions el archivo de conjunto de acciones contiene más de 1000 acciones
@msg the action set file needs a later version of VectorCraft el archivo de conjunto de acciones necesita una versión más reciente de VectorCraft
@msg an action in the action set file has more than 10,000 steps una acción del archivo de conjunto de acciones tiene más de 10 000 pasos
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -964,6 +964,7 @@ lighting Light Valo
Color Picker… Värivalitsin…
Coming soon — tracked in the parity plan Tulossa pian — seurataan yhdenmukaisuussuunnitelmassa
Command Dialog Komentovalintaikkuna
Command API Reference Komentojen API-viite
Copy Selected Style Kopioi valittu tyyli
Corners… Kulmat…
Define Perspective Grid… Määritä perspektiiviruudukko…
@@ -4630,6 +4631,7 @@ imageTrace Outline Ääriviiva
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Ladattiin ”{name}”: jätettiin pois {n} vaihe|Ladattiin ”{name}”: jätettiin pois {n} vaihetta
@msg not an action set file ei ole toimintosarjatiedosto
@msg the action set file is larger than 8 MiB toimintosarjatiedosto on suurempi kuin 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: viite olisi suurempi kuin {MAX_BYTES} tavua
@msg the action set file holds more than 1,000 actions toimintosarjatiedostossa on yli 1 000 toimintoa
@msg the action set file needs a later version of VectorCraft toimintosarjatiedosto vaatii uudemman VectorCraft-version
@msg an action in the action set file has more than 10,000 steps toimintosarjatiedoston toiminnossa on yli 10 000 vaihetta
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -583,6 +583,7 @@ font-technology Variable Variables
Coming soon — tracked in the parity plan Bientôt disponible — prévu dans le plan de parité
Command Click to Select Objects Behind Cmd+clic pour sélectionner les objets situés derrière
Command Dialog Boîte de dialogue des commandes
Command API Reference Référence de l’API des commandes
Community Communauté
Compatibility: Compatibilité :
Compatible Gradient and Gradient Mesh Printing Impression compatible des dégradés et des filets de dégradé
@@ -4402,6 +4403,7 @@ axis Both Les deux
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out « {name} » chargé : {n} étape a été ignorée|« {name} » chargé : {n} étapes ont été ignorées
@msg not an action set file ce n’est pas un fichier de jeu de scripts
@msg the action set file is larger than 8 MiB le fichier de jeu de scripts dépasse 8 Mio
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}` : la référence dépasserait {MAX_BYTES} octets
@msg the action set file holds more than 1,000 actions le fichier de jeu de scripts contient plus de 1 000 scripts
@msg the action set file needs a later version of VectorCraft le fichier de jeu de scripts nécessite une version plus récente de VectorCraft
@msg an action in the action set file has more than 10,000 steps un script du fichier de jeu de scripts compte plus de 10 000 étapes
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -896,6 +896,7 @@ antiAlias Sharp शार्प
Command कमांड
Command Click to Select Objects Behind कमांड पीछे ऑब्जेक्ट्स का सिलेक्ट करने के लिए क्लिक करें
Command Dialog कमान डायलॉग
Command API Reference कमांड API संदर्भ
Comment not closed टिप्पणी बंद नहीं
Commenting, filling in form fields and signing टिप्पणी करना, फॉर्म फ़ील्ड में भरना और हस्ताक्षर करना
Commits कमिट्स
@@ -7385,6 +7386,7 @@ antiAlias Sharp शार्प
@msg the action has no steps कार्रवाई में कोई कदम नहीं है
@msg the action set file holds more than 1,000 actions एक्शन सेट फ़ाइल 1,000 से अधिक कार्यों को रखती है
@msg the action set file is larger than 8 MiB एक्शन सेट फ़ाइल 8 MiB से बड़ा है
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: संदर्भ {MAX_BYTES} बाइट से बड़ा होगा
@msg the action set file needs a later version of VectorCraft एक्शन सेट फ़ाइल को वेक्टर क्रेफ्ट के बाद के संस्करण की आवश्यकता होती है
@msg the action set ids are not a list कार्रवाई सेट ids एक सूची नहीं हैं
@msg the action sets changed; nothing was deleted कार्रवाई सेट बदल गया; कुछ भी नहीं हटा दिया गया था
Can't render this file because it is too large.
+2
View File
@@ -760,6 +760,7 @@ font-technology Variable Változó
Command Parancs
Command Click to Select Objects Behind Cmd+kattintás a mögöttes objektumok kijelöléséhez
Command Dialog Parancs párbeszédablaka
Command API Reference Parancsok API-referenciája
Commenting, filling in form fields and signing Megjegyzések, űrlapmezők kitöltése és aláírás
Commits Commitok
Community Közösség
@@ -5095,6 +5096,7 @@ imageTrace Outline Körvonal
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out „{name}” betöltve; kihagyott lépések: {n}
@msg not an action set file nem műveletkészlet-fájl
@msg the action set file is larger than 8 MiB a műveletkészlet-fájl nagyobb 8 MiB-nál
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: a referencia nagyobb lenne {MAX_BYTES} bájtnál
@msg the action set file holds more than 1,000 actions a műveletkészlet-fájl több mint 1000 műveletet tartalmaz
@msg the action set file needs a later version of VectorCraft a műveletkészlet-fájlhoz a VectorCraft újabb verziója kell
@msg an action in the action set file has more than 10,000 steps a műveletkészlet-fájl egyik műveletében több mint 10 000 lépés van
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -906,6 +906,7 @@ lighting Light Cahaya
Color Picker… Pemilih warna…
Coming soon — tracked in the parity plan Segera hadir — dilacak dalam rencana paritas
Command Dialog Dialog perintah
Command API Reference Referensi API perintah
Copy Selected Style Salin gaya terpilih
Corners… Sudut…
Define Perspective Grid… Definisikan kisi perspektif…
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
906
907
908
909
910
911
912
+2
View File
@@ -582,6 +582,7 @@ font-technology Variable Variabili
Coming soon — tracked in the parity plan Prossimamente — previsto nel piano di parità
Command Click to Select Objects Behind Cmd+clic per selezionare gli oggetti retrostanti
Command Dialog Finestra dei comandi
Command API Reference Riferimento API dei comandi
Community Comunità
Compatibility: Compatibilità:
Compatible Gradient and Gradient Mesh Printing Stampa compatibile di sfumature e trame sfumatura
@@ -4455,6 +4456,7 @@ axis Both Entrambi
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Caricato “{name}”: {n} passaggio è stato omesso|Caricato “{name}”: {n} passaggi sono stati omessi
@msg not an action set file non è un file di set di azioni
@msg the action set file is larger than 8 MiB il file del set di azioni supera 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: il riferimento supererebbe {MAX_BYTES} byte
@msg the action set file holds more than 1,000 actions il file del set di azioni contiene più di 1.000 azioni
@msg the action set file needs a later version of VectorCraft il file del set di azioni richiede una versione più recente di VectorCraft
@msg an action in the action set file has more than 10,000 steps un’azione nel file del set di azioni ha più di 10.000 passaggi
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1253,6 +1253,7 @@ antiAlias Sharp シャープ
Coming soon — tracked in the parity plan 近日対応予定
Command Click to Select Objects Behind Command+クリックで背面のオブジェクトを選択
Command Dialog コマンドダイアログ
Command API Reference コマンド API リファレンス
Community コミュニティ
Compatibility: 互換性:
Compatible Gradient and Gradient Mesh Printing 互換性のあるグラデーションとグラデーションメッシュのプリント
@@ -4801,6 +4802,7 @@ swatch Bright Violet ビビッドバイオレット
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 「{name}」を読み込みました。{n} 個のステップは省きました
@msg not an action set file アクションセットファイルではありません
@msg the action set file is larger than 8 MiB アクションセットファイルが 8 MiB を超えています
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: リファレンスが {MAX_BYTES} バイトを超えます
@msg the action set file holds more than 1,000 actions アクションセットファイルのアクションが 1,000 個を超えています
@msg the action set file needs a later version of VectorCraft このアクションセットファイルには新しいバージョンの VectorCraft が必要です
@msg an action in the action set file has more than 10,000 steps アクションセットファイルに 10,000 ステップを超えるアクションがあります
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -858,6 +858,7 @@ lighting Light სინათლე
Color Picker… ფერის ამრჩევი…
Coming soon — tracked in the parity plan მალე — აღრიცხულია თანხვედრის გეგმაში
Command Dialog ბრძანების დიალოგი
Command API Reference ბრძანებების API ცნობარი
Copy Selected Style მონიშნული სტილის კოპირება
Corners… კუთხეები…
Define Perspective Grid… პერსპექტივის ბადის განსაზღვრა…
@@ -4180,6 +4181,7 @@ font-technology Variable ცვლადი
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out ჩაიტვირთა „{name}“; გამოტოვებული ნაბიჯები: {n}
@msg not an action set file ეს არ არის მოქმედებების ნაკრების ფაილი
@msg the action set file is larger than 8 MiB მოქმედებების ნაკრების ფაილი 8 MiB-ზე დიდია
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: ცნობარი {MAX_BYTES} ბაიტზე დიდი იქნებოდა
@msg the action set file holds more than 1,000 actions მოქმედებების ნაკრების ფაილი 1000-ზე მეტ მოქმედებას შეიცავს
@msg the action set file needs a later version of VectorCraft მოქმედებების ნაკრების ფაილს VectorCraft-ის უფრო ახალი ვერსია სჭირდება
@msg an action in the action set file has more than 10,000 steps მოქმედებების ნაკრების ფაილში ერთ-ერთ მოქმედებას 10 000-ზე მეტი ნაბიჯი აქვს
Can't render this file because it is too large.
+2
View File
@@ -1243,6 +1243,7 @@ antiAlias Sharp 날카롭게
Coming soon — tracked in the parity plan 곧 제공 예정 — 기능 동등성 계획에서 관리 중
Command Click to Select Objects Behind Command-클릭으로 뒤쪽 개체 선택
Command Dialog 명령 대화상자
Command API Reference 명령 API 참조
Community 커뮤니티
Compatibility: 호환성:
Compatible Gradient and Gradient Mesh Printing 호환 그레이디언트 및 그레이디언트 메시 인쇄
@@ -4728,6 +4729,7 @@ theme Appearance 외관
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}”을(를) 불러왔습니다. 단계 {n}개를 제외했습니다
@msg not an action set file 액션 세트 파일이 아닙니다
@msg the action set file is larger than 8 MiB 액션 세트 파일이 8 MiB보다 큽니다
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: 참조가 {MAX_BYTES}바이트보다 커집니다
@msg the action set file holds more than 1,000 actions 액션 세트 파일의 액션이 1,000개를 넘습니다
@msg the action set file needs a later version of VectorCraft 이 액션 세트 파일을 열려면 최신 버전의 VectorCraft가 필요합니다
@msg an action in the action set file has more than 10,000 steps 액션 세트 파일에 단계가 10,000개를 넘는 액션이 있습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -920,6 +920,7 @@ lighting Light Licht
Color Picker… Kleurkiezer…
Coming soon — tracked in the parity plan Binnenkort beschikbaar — opgenomen in het pariteitsplan
Command Dialog Opdrachtdialoogvenster
Command API Reference API-naslag voor opdrachten
Copy Selected Style Geselecteerde stijl kopiëren
Corners… Hoeken…
Define Perspective Grid… Perspectiefraster definiëren…
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -921,6 +921,7 @@ lighting Light Światło
Color Picker… Próbnik kolorów…
Coming soon — tracked in the parity plan Już wkrótce — ujęte w planie zgodności
Command Dialog Okno polecenia
Command API Reference Dokumentacja API poleceń
Copy Selected Style Kopiuj zaznaczony styl
Corners… Narożniki…
Define Perspective Grid… Zdefiniuj siatkę perspektywy…
@@ -4545,6 +4546,7 @@ imageTrace Outline Kontur
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Wczytano „{name}”: pominięto {n} krok|Wczytano „{name}”: pominięto {n} kroki|Wczytano „{name}”: pominięto {n} kroków
@msg not an action set file to nie jest plik zestawu operacji
@msg the action set file is larger than 8 MiB plik zestawu operacji jest większy niż 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: dokumentacja byłaby większa niż {MAX_BYTES} bajtów
@msg the action set file holds more than 1,000 actions plik zestawu operacji zawiera ponad 1000 operacji
@msg the action set file needs a later version of VectorCraft plik zestawu operacji wymaga nowszej wersji programu VectorCraft
@msg an action in the action set file has more than 10,000 steps operacja w pliku zestawu operacji ma ponad 10 000 kroków
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -532,6 +532,7 @@ antiAlias Sharp Nítido
Coming soon — tracked in the parity plan Em breve — rastreado no plano de paridade
Command Click to Select Objects Behind Clique com Comando para Selecionar Objetos Atrás
Command Dialog Diálogo de Comandos
Command API Reference Referência da API de Comandos
Community Comunidade
Compatibility: Compatibilidade:
Compatible Gradient and Gradient Mesh Printing Impressão Compatível de Gradiente e Malha de Gradiente
@@ -3985,6 +3986,7 @@ axis Both Ambos
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}” carregado: {n} etapa foi omitida|“{name}” carregado: {n} etapas foram omitidas
@msg not an action set file não é um arquivo de conjunto de ações
@msg the action set file is larger than 8 MiB o arquivo de conjunto de ações tem mais de 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: a referência teria mais de {MAX_BYTES} bytes
@msg the action set file holds more than 1,000 actions o arquivo de conjunto de ações contém mais de 1.000 ações
@msg the action set file needs a later version of VectorCraft o arquivo de conjunto de ações requer uma versão mais recente do VectorCraft
@msg an action in the action set file has more than 10,000 steps uma ação do arquivo de conjunto de ações tem mais de 10.000 etapas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -580,6 +580,7 @@ font-technology Variable Вариативные
Coming soon — tracked in the parity plan Скоро — отслеживается в плане развития
Command Click to Select Objects Behind Cmd+щелчок для выбора объектов позади
Command Dialog Диалоговое окно команды
Command API Reference Справочник по API команд
Community Сообщество
Compatibility: Совместимость:
Compatible Gradient and Gradient Mesh Printing Совместимая печать градиентов и градиентных сеток
@@ -4449,6 +4450,7 @@ axis Both Оба
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Загружено «{name}»: пропущен {n} шаг|Загружено «{name}»: пропущено {n} шага|Загружено «{name}»: пропущено {n} шагов
@msg not an action set file это не файл набора действий
@msg the action set file is larger than 8 MiB файл набора действий больше 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: справочник был бы больше {MAX_BYTES} байт
@msg the action set file holds more than 1,000 actions файл набора действий содержит более 1000 действий
@msg the action set file needs a later version of VectorCraft для файла набора действий нужна более новая версия VectorCraft
@msg an action in the action set file has more than 10,000 steps в файле набора действий есть действие более чем из 10 000 шагов
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -569,6 +569,7 @@ font-technology Variable Variabla
Coming soon — tracked in the parity plan Kommer snart — följs upp i paritetsplanen
Command Click to Select Objects Behind Cmd-klicka för att markera objekt bakom
Command Dialog Kommandodialog
Command API Reference API-referens för kommandon
Community Gemenskap
Compatibility: Kompatibilitet:
Compatible Gradient and Gradient Mesh Printing Kompatibel utskrift av gradienter och gradientnät
@@ -4317,6 +4318,7 @@ axis Both Båda
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Läste in ”{name}”: {n} steg utelämnades|Läste in ”{name}”: {n} steg utelämnades
@msg not an action set file inte en fil med åtgärdsuppsättning
@msg the action set file is larger than 8 MiB filen med åtgärdsuppsättningen är större än 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: referensen skulle bli större än {MAX_BYTES} byte
@msg the action set file holds more than 1,000 actions filen med åtgärdsuppsättningen innehåller fler än 1 000 åtgärder
@msg the action set file needs a later version of VectorCraft filen med åtgärdsuppsättningen kräver en nyare version av VectorCraft
@msg an action in the action set file has more than 10,000 steps en åtgärd i filen med åtgärdsuppsättningen har fler än 10 000 steg
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -563,6 +563,7 @@ font-technology Variable Değişken
Coming soon — tracked in the parity plan Çok yakında — eşlik planında takip ediliyor
Command Click to Select Objects Behind Komut Arkasındaki Nesneleri Seçmek İçin Tıklayın
Command Dialog Komut İletişim Kutusu
Command API Reference Komut API Başvurusu
Community Topluluk
Compatibility: Uyumluluk:
Compatible Gradient and Gradient Mesh Printing Uyumlu Degrade ve Degrade Mesh Baskı
@@ -4177,6 +4178,7 @@ axis Both Her İkisi
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}” yüklendi: {n} adım atlandı
@msg not an action set file bir eylem seti dosyası değil
@msg the action set file is larger than 8 MiB eylem seti dosyası 8 MiB'den büyük
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: başvuru {MAX_BYTES} bayttan büyük olur
@msg the action set file holds more than 1,000 actions eylem seti dosyası 1.000'den fazla eylem içeriyor
@msg the action set file needs a later version of VectorCraft eylem seti dosyası VectorCraft'ın daha yeni bir sürümünü gerektiriyor
@msg an action in the action set file has more than 10,000 steps eylem seti dosyasındaki bir eylemde 10.000'den fazla adım var
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -576,6 +576,7 @@ font-technology Variable Варіативні
Coming soon — tracked in the parity plan Незабаром відстежується в плані розвитку
Command Click to Select Objects Behind Cmd+клацання для вибору об’єктів позаду
Command Dialog Діалогове вікно команди
Command API Reference Довідник API команд
Community Спільнота
Compatibility: Сумісність:
Compatible Gradient and Gradient Mesh Printing Сумісний друк градієнтів та градієнтних сіток
@@ -4439,6 +4440,7 @@ axis Both Обидві
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Завантажено «{name}»: пропущено {n} крок|Завантажено «{name}»: пропущено {n} кроки|Завантажено «{name}»: пропущено {n} кроків
@msg not an action set file це не файл набору дій
@msg the action set file is larger than 8 MiB файл набору дій більший за 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: довідник був би більшим за {MAX_BYTES} байтів
@msg the action set file holds more than 1,000 actions файл набору дій містить понад 1000 дій
@msg the action set file needs a later version of VectorCraft для файлу набору дій потрібна новіша версія VectorCraft
@msg an action in the action set file has more than 10,000 steps у файлі набору дій є дія з понад 10 000 кроків
Can't render this file because it contains an unexpected character in line 182 and column 5.
+2
View File
@@ -671,6 +671,7 @@ font-technology Variable Biến thiên
Coming soon — tracked in the parity plan Sắp ra mắt — đang theo dõi trong kế hoạch đối chiếu tính năng
Command Click to Select Objects Behind Command-bấm để chọn đối tượng phía sau
Command Dialog Hộp thoại lệnh
Command API Reference Tài liệu tham khảo API lệnh
Commits Commit
Community Cộng đồng
Company Công ty
@@ -4748,6 +4749,7 @@ theme Appearance Giao diện
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Đã tải “{name}”: bỏ qua {n} bước
@msg not an action set file không phải tệp bộ hành động
@msg the action set file is larger than 8 MiB tệp bộ hành động lớn hơn 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: tài liệu tham khảo sẽ lớn hơn {MAX_BYTES} byte
@msg the action set file holds more than 1,000 actions tệp bộ hành động chứa hơn 1.000 hành động
@msg the action set file needs a later version of VectorCraft tệp bộ hành động cần phiên bản VectorCraft mới hơn
@msg an action in the action set file has more than 10,000 steps một hành động trong tệp bộ hành động có hơn 10.000 bước
Can't render this file because it contains an unexpected character in line 204 and column 5.
+2
View File
@@ -528,6 +528,7 @@ antiAlias Sharp 锐利
Coming soon — tracked in the parity plan 即将推出 — 在对标计划中跟踪
Command Click to Select Objects Behind 按住 Command 单击选择后方对象
Command Dialog 命令对话框
Command API Reference 命令 API 参考
Community 社区
Compatibility: 兼容性:
Compatible Gradient and Gradient Mesh Printing 兼容渐变和渐变网格打印
@@ -4508,6 +4509,7 @@ imageTrace Outline 轮廓
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 已载入“{name}”:已略过 {n} 个步骤
@msg not an action set file 不是动作组文件
@msg the action set file is larger than 8 MiB 动作组文件大于 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`:参考将大于 {MAX_BYTES} 字节
@msg the action set file holds more than 1,000 actions 动作组文件包含超过 1,000 个动作
@msg the action set file needs a later version of VectorCraft 动作组文件需要更高版本的 VectorCraft
@msg an action in the action set file has more than 10,000 steps 动作组文件中有动作超过 10,000 个步骤
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -544,6 +544,7 @@ font-technology Variable 可變
Coming soon — tracked in the parity plan 即將推出 — 已列入功能對等計畫
Command Click to Select Objects Behind Cmd 加按一下可選取後方物件
Command Dialog 指令對話框
Command API Reference 指令 API 參考
Community 社群
Compatibility: 相容性:
Compatible Gradient and Gradient Mesh Printing 相容的漸層與漸層網格列印
@@ -2352,6 +2353,7 @@ file-filter Templates 範本
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 已載入「{name}」:已略過 {n} 個步驟
@msg not an action set file 不是動作組檔案
@msg the action set file is larger than 8 MiB 動作組檔案大於 8 MiB
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`:參考將大於 {MAX_BYTES} 位元組
@msg the action set file holds more than 1,000 actions 動作組檔案包含超過 1,000 個動作
@msg the action set file needs a later version of VectorCraft 動作組檔案需要較新版本的 VectorCraft
@msg an action in the action set file has more than 10,000 steps 動作組檔案中有動作超過 10,000 個步驟
Can't render this file because it contains an unexpected character in line 6 and column 47.
+25 -6
View File
@@ -210,8 +210,10 @@ echo '{"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"poster","
### Resource templates
`vectorcraft://document` (summary), `vectorcraft://document/json` (the whole model), and
`vectorcraft://commands` (the live command catalog with enabled state) are the fixed resources. The templates read one thing at a time, which matters on a real document: the full model
`vectorcraft://document` (summary), `vectorcraft://document/json` (the whole model),
`vectorcraft://commands` (the live command catalog with enabled state) and `vectorcraft://commands/reference` (the
command API reference in Markdown, `text/markdown; charset=utf-8`; see [Parameter schemas](#parameter-schemas)) are
the fixed resources. The templates read one thing at a time, which matters on a real document: the full model
is thousands of lines an agent pays for again on every change.
| `uriTemplate` | Reads |
@@ -748,6 +750,7 @@ Fixed resources and the four templates (same as [Resource templates](#resource-t
| `vectorcraft://document` | `document.inspect` summary (JSON) |
| `vectorcraft://document/json` | The complete document model (JSON) |
| `vectorcraft://commands` | Live command catalog with enabled state |
| `vectorcraft://commands/reference` | The command API reference (`engine.apiReference`), as Markdown (`text/markdown; charset=utf-8`) |
| `vectorcraft://object/{id}` | One layer or object (`document.node {id, summary: true}`) |
| `vectorcraft://command/{id}` | One command's metadata and enablement |
| `vectorcraft://effect/{id}` | One live effect with parameters and defaults |
@@ -1385,7 +1388,7 @@ vectorcraft-cli run --cmd file.new --params '{"width":600,"height":200}' --cmd
### Parameter schemas
Some commands have a machine-readable parameter schema: at present `view.capsLock`, `view.saved.new`,
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options`. The engine's
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference`. The engine's
`engine.commands` list gives such a command a `schema` field: an object schema in the shape of JSON Schema 2020-12
(https://json-schema.org/draft/2020-12). Its root has `$schema` (the 2020-12 dialect), `description` when the
command has a summary, and `deprecated: true` when the command is deprecated. The schema uses `type`, `properties`, `required` and
@@ -1427,9 +1430,25 @@ path to a value inside a list or an object, and the document and its undo histor
An omitted value with a `default` takes it (`rotation` 0 in `view.saved.new`); the journal keeps the params as they
were given. Saved steps played from a journal or an action keep a key the schema does not name, unless its value nests
more than 16 levels deep, and the values of the parameters it names are checked as in a new call. The schemas of built-in commands belong to API level 1
(`vectorcraft_engine::schemas::API_VERSION`); a change to any of them raises the level. Commands that plug-ins add have
no schema here.
more than 16 levels deep, and the values of the parameters it names are checked as in a new call. The schemas of built-in commands belong to API level 2
(`vectorcraft_engine::schemas::API_VERSION`); a change to any of them raises the level. Level 2 added
`engine.apiReference`. Commands that plug-ins add have no schema here.
`engine.apiReference {}` → `{markdown}` returns the command API reference, written from these schemas each time it
runs; no file of it is kept. It needs no open document and changes nothing. The reference has a `##` section for each
built-in command that has a schema, in registry order (the order `engine.commands` lists them), and leaves out every
command without one, plug-in commands included; `engine.commands` still lists those with their params text. A section
lists the command's label, menu path, API level, deprecation level, what it writes (`x-writes`), cost, summary and
return, then a table of its parameters in the schema's order with the columns Parameter, Type, Range, Unit, Default,
Required, Since, Deprecated and Description. A member of an object parameter is a row of its own after it, named with
dots and `[]` for a list (`edits[].name`). A range says whether each end is included ("more than 0", "at most 64"); a
default is either the JSON value the engine fills in, marked "(JSON)", or the note of a default the command works out;
the Description says when null reads as omitted and when the engine does not refuse a value of another type (a lenient
parameter). Text from a schema is escaped as Markdown text: each of `` \ | [ ] < > ` * _ ! & # ~ : @ `` and the `.`
after `www` get a backslash, and line breaks, tabs and other control characters become spaces, so a description cannot
end a table cell or make a link, an image or HTML. A reference larger than 1 MiB (1,048,576 bytes) is an error naming
`engine.apiReference`, and no part of it is returned. MCP serves the same text as the resource
`vectorcraft://commands/reference`.
## Checking scripts written for the reference app
+1
View File
@@ -674,6 +674,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **Architecture:** 19+ crates with enforced layering (`cargo xtask layers`). Every action is a command (~400 engine + ~50 UI). Undo is unlimited via structural sharing. `command.batch` runs several commands as one transaction.
- **Parameter schemas:** a command can carry a machine-readable parameter schema (the new `vectorcraft-apischema` crate): each parameter's type, range, unit, whether it is required, its default, whether null counts as left out, and the API levels it was added and deprecated at. `engine.commands` lists the schema in the shape of a JSON Schema 2020-12 object schema, a test keeps the command's `params` text equal to the text the schema writes, and a new call has the params checked against it before the command runs (batch steps before the first step), with an error naming the parameter and no change to the document; saved steps keep a key the schema does not name, and their values are checked. The View commands `view.capsLock`, `view.saved.new`, `view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options` have schemas. The schemas belong to API level 1 (`vectorcraft_engine::schemas::API_VERSION`), and a test fails when a schema changes without a new level. Tests: `crates/apischema/src/tests.rs` (with `conformance.rs`, a separate check of the JSON form against the JSON Schema 2020-12 rules), `schemas::tests` and `cmd::views::tests::each_view_command_refuses_a_bad_value_naming_the_parameter` in `vectorcraft-engine`.
- **Input schemas in the command listings:** `engine.commands` and the MCP tools `command_list` and `list_commands` give each command with a parameter schema an `inputSchema` next to `schema`: the same schema with the root `type` `"object"`, the form an MCP tool's input schema takes ([MCP schema reference](https://modelcontextprotocol.io/specification/2026-07-28/schema)). A command without a schema lists neither, and `brief` leaves out both schemas with the `params` text. Tests: `schemas::tests::engine_commands_list_an_input_schema_with_an_object_root` and `schemas::tests::an_input_schema_keeps_deprecation` in `vectorcraft-engine`, and `tests::conventions::command_listings_carry_input_schemas` in `vectorcraft-mcp`, which check every listed input schema against the JSON Schema 2020-12 rules.
- **Command API reference:** `engine.apiReference` returns a Markdown reference written from the parameter schemas when it is asked for, and MCP serves the same text as the resource `vectorcraft://commands/reference` (`text/markdown; charset=utf-8`). Each command that has a schema gets a section in registry order with its label, menu path, API levels, what it writes, summary and return, and a table of its parameters (type, range with its ends marked included or not, unit, default, required, levels and description; members of objects as dotted rows). Commands without a schema, plug-in commands included, are left out. Schema text is escaped so it stays plain text in its table cell, the same build always returns the same bytes, and a reference past 1 MiB is an error rather than a cut-short text. The command's own schema raised the API level to 2. This is a VectorCraft documentation feature; no score changes. Tests: `cmd::apiref::tests` and `schemas::tests` in `vectorcraft-engine`, `resources::tests` in `vectorcraft-mcp`.
- **Automation:**
- Variables: Update Data Set writes what the art holds into the active data set for the variables that data set holds, and keeps the data set's value for a text, image or graph variable whose objects are locked or inside a locked layer or group; `dataset.update {all: true}` also adds the other variables that the art reports a value for.
- Variables: `dataset.select`, `dataset.next` and `dataset.prev` rewrite type and apply the other values on hidden objects and in hidden layers and groups, which stay hidden unless a visibility value shows them, so a layer shown later holds the selected data set's text; objects that are locked or in a locked layer or group are skipped, except by a visibility value.