fix(naming): use direct FigCraft feature descriptions (#2131)

Drops the "Figma-class" self-description from the MCP instructions and the "Accessibility at Figma" aside from pref.uiScaleUp help. Help text only; ids and preference keys unchanged. Adds a display-name guard over mcp/src and engine/src/cmd string literals.
This commit is contained in:
FredApps authored and GitHub committed 2026-11-09 06:22:48 +01:00
1 parent 970cdfe664
commit 72cfe7be30
5 files changed
+195 -4

No files matched your search

+1 -2
View File
@@ -1,5 +1,4 @@
//! Arc handles: the on-canvas handles of a selected ellipse's arc (as public documentation of
//! Figma-class tools describes them).
//! Arc handles: on-canvas controls for a selected ellipse's arc.
//!
//! - **Sweep** starts on the ellipse's right edge, on its centre line. Dragging it around the
//! ellipse opens it into an arc: the sweep is a signed share of a full turn, positive dragging up
+1 -1
View File
@@ -431,7 +431,7 @@ pub fn specs() -> Vec<CommandSpec> {
spec!(
"pref.uiScaleUp",
"Larger interface",
"{} the next of the Preferences sizes above the current one (the interface size keys of Accessibility at Figma); at the largest it stays → {scale}",
"{} the next of the Preferences sizes above the current one; at the largest it stays → {scale}",
false,
|s, _| ui_scale_step(s, 1)
),
+1 -1
View File
@@ -28,7 +28,7 @@ pub const RESOURCE_NOT_FOUND: i64 = -32002;
const MAX_LINE: usize = 32 * 1024 * 1024;
/// The `instructions` string sent on initialize.
pub const INSTRUCTIONS: &str = "FigCraft is an interface design tool (Figma-class): pages hold frames, \
pub const INSTRUCTIONS: &str = "FigCraft is an interface design tool: pages hold frames, \
groups, shapes, text, components and instances, with auto layout, styles, variables and prototyping. \
Coordinates are page units (1 unit = 1 px at 100% zoom), x right and y DOWN. create_node and set_properties \
take x/y RELATIVE TO THE PARENT (the page when there is no parent, so page coordinates); inspect_document \
+187
View File
@@ -0,0 +1,187 @@
//! Guard the production display surfaces without banning compatibility identifiers or notices.
use std::path::{Path, PathBuf};
const BANNED: &[&str] = &["Figma-class", "Accessibility at Figma"];
const ALLOWED: &[&str] = &[];
const ROOTS: &[&str] = &["src", "../engine/src/cmd"];
fn is_word(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
fn banned_display(text: &str) -> bool {
if ALLOWED.contains(&text) {
return false;
}
BANNED.iter().any(|banned| {
text.match_indices(banned)
.any(|(at, _)| !text[..at].chars().next_back().is_some_and(is_word) && !text[at + banned.len()..].chars().next().is_some_and(is_word))
})
}
// Read ordinary and raw Rust string literals, ignoring comments. No compiler dependency is
// needed, so this guard can also run by itself when a full application build is unavailable.
#[allow(clippy::cognitive_complexity)] // This small test-only lexer keeps raw-string/comment state together.
fn literals(source: &str) -> Vec<String> {
let chars: Vec<char> = source.chars().collect();
let mut out = Vec::new();
let mut at = 0;
let mut test_module = false;
let mut test_depth = 0_usize;
while at < chars.len() {
if chars.get(at..at + 12) == Some(&['#', '[', 'c', 'f', 'g', '(', 't', 'e', 's', 't', ')', ']']) {
at += 12;
let rest: String = chars[at..].iter().collect();
if rest.trim_start().starts_with("mod ") {
test_module = true;
}
} else if chars.get(at..at + 2) == Some(&['/', '/']) {
while at < chars.len() && chars[at] != '\n' {
at += 1;
}
} else if chars.get(at..at + 2) == Some(&['/', '*']) {
at += 2;
let mut depth = 1;
while at < chars.len() && depth > 0 {
if chars.get(at..at + 2) == Some(&['/', '*']) {
depth += 1;
at += 2;
} else if chars.get(at..at + 2) == Some(&['*', '/']) {
depth -= 1;
at += 2;
} else {
at += 1;
}
}
} else if chars[at] == 'r' && matches!(chars.get(at + 1), Some('"' | '#')) {
let start = at;
at += 1;
while chars.get(at) == Some(&'#') {
at += 1;
}
let hashes = at - start - 1;
if chars.get(at) != Some(&'"') {
continue;
}
at += 1;
let begin = at;
while at < chars.len() {
if chars[at] == '"' && (1..=hashes).all(|n| chars.get(at + n) == Some(&'#')) {
break;
}
at += 1;
}
if !test_module {
out.push(chars[begin..at].iter().collect());
}
at += 1 + hashes;
} else if chars[at] == '"' {
at += 1;
let mut text = String::new();
while at < chars.len() {
let c = chars[at];
at += 1;
if c == '"' {
break;
}
if c == '\\' && at < chars.len() {
text.push(chars[at]);
at += 1;
} else {
text.push(c);
}
}
if !test_module {
out.push(text);
}
} else if chars[at] == '\'' && chars.get(at + 2) == Some(&'\'') {
at += 3; // A character literal such as '"', not the start of a string.
} else {
if test_module {
match chars[at] {
'{' => test_depth += 1,
'}' => {
test_depth = test_depth.saturating_sub(1);
if test_depth == 0 {
test_module = false;
}
}
';' if test_depth == 0 => test_module = false,
_ => {}
}
}
at += 1;
}
}
out
}
fn check_tree(path: &Path, failures: &mut Vec<String>) -> std::io::Result<()> {
if path.is_dir() {
for entry in std::fs::read_dir(path)? {
let path = entry?.path();
if path.file_name().is_some_and(|name| name == "tests") {
continue;
}
check_tree(&path, failures)?;
}
} else if path.extension().is_some_and(|ext| ext == "rs") {
let name = path.file_name().ok_or_else(|| std::io::Error::other("source filename missing"))?.to_string_lossy();
if name == "tests.rs" || name.ends_with("_tests.rs") {
return Ok(());
}
let source = std::fs::read_to_string(path)?;
// Skip only inline test modules, not later production following test-only imports.
for text in literals(&source) {
if banned_display(&text) {
failures.push(format!("{}: {text:?}", path.display()));
}
}
}
Ok(())
}
#[test]
fn production_display_names_stay_neutral() -> std::io::Result<()> {
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut failures = Vec::new();
for root in ROOTS {
let root = base.join(root);
assert!(root.exists(), "missing display source: {}", root.display());
check_tree(&root, &mut failures)?;
}
assert!(failures.is_empty(), "Banned display names:\n{}", failures.join("\n"));
Ok(())
}
#[test]
fn guard_distinguishes_labels_from_identifiers_and_comments() {
for name in BANNED {
assert!(banned_display(name), "{name}");
}
for name in ALLOWED {
assert!(!banned_display(name), "{name}");
}
assert!(!banned_display("filter.cameraRaw"));
assert!(!banned_display("tool.guidedUpright"));
assert!(!banned_display("insert.smartArt"));
assert_eq!(literals("// \"ignored\"\n/* \"ignored\" */ \"label\""), ["label"]);
assert_eq!(literals("r#\"raw label\"#"), ["raw label"]);
}
#[test]
fn guard_scans_production_after_test_only_items() {
let source = r###"#[cfg(test)] mod tests { let _ = "ignored"; }
#[cfg(test)] use fixtures::Helper;
fn production() { let _ = r#"live label"#; }
"###;
assert_eq!(literals(source), ["live label"]);
assert_eq!(literals("#[cfg(test)] mod tests; fn live() { let _ = \"live label\"; }"), ["live label"]);
}
#[test]
fn source_read_failures_are_reported() {
// A file cannot be traversed as a directory. A failed scan must fail, not silently pass.
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/display_names.rs/not-a-directory.rs");
assert!(check_tree(&path, &mut Vec::new()).is_err());
}
+5
View File
@@ -0,0 +1,5 @@
# Feature naming
FigCraft describes itself as an **interface design tool**, without the comparative Figma-class tag. Interface-size command help describes the size steps directly. Command IDs, file-format interoperability references, behavioral documentation and attribution remain unchanged.
Regression tests prevent the retired comparative display phrases from returning to MCP instructions or command help. This copy change makes no legal claim about the comparative terms and does not change behavior.