M7: open inputs for parameterized menu actions

This commit is contained in:
Yarila
2026-10-16 23:36:23 +05:00
parent d1d8c3f652
commit 8c90ef962a
9 changed files with 205 additions and 9 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (bounded canvas pan overscan; community fixes from #478 up and a control-channel fix (#589); bounded canvas pan overscan; Typography spacing icons support scrubbing (#373); font scans skip named pipes; system theme on first run; recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (menu and Actions parameter dialogs; bounded canvas pan overscan; community fixes from #478 up and a control-channel fix (#589); bounded canvas pan overscan; Typography spacing icons support scrubbing (#373); font scans skip named pipes; system theme on first run; recent files as cards with thumbnails on the home screen; community fixes: lost images reported on loading (#207), SVG import budgets (#190), Leptos code (#223), full screen (#227), server snapshots readable again (#188), owner-only preferences (#200), ruzstd 0.9.1 (#182), layer cycle repair (#205), batch rollback, Direct3D 12 on Windows, plugin metering (#192); vector edit mode regions: hover hatch, selection and drag; SVG/PDF exports refuse over-depth documents (#208); bounded MCP/control framing, connections and queues (#201); plugin log limits (#191); live-session revert guard (#198); attached MCP no longer replays after a lost reply (#194); repeat command recursion fix; the Layout panel's constrain-proportions link scales the other side, #220; core editor leftovers: component playground, auto gap handles, variables inside styles, inner shadows and background blur on groups, command-made text readable on dark pages, a one-step frame/group convert, delays on mouse triggers and manual overlay offsets, wrapped-line alignment; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; starter templates, a Templates tab and Getting started tips on the home screen; your online files; everyday editing partials: number fields, outline options, Hide when publishing, cursors, panel widths, dark pages; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7)
FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow,
native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through
@@ -313,6 +313,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-16 | minor | Affected menu and Actions commands that need values now open the relevant input or panel instead of showing a parameter error; Preferences offers language in a dropdown. Headline numbers unchanged. |
| 2026-10-16 | minor | Community fixes from #478 up (batches #591, #598, #602; 69 PRs) and a control-channel fix: a line that isn't JSON now ends a `--control` connection, so a web page can't drive the app through a browser request (#589). Highlights: page copies keep navigation, flows, prototype settings and guides; Find and Replace keeps line endings and case-insensitive matches; text hyperlinks survive import and export; recent colours record only what was picked; MCP screenshots respect `maxSize`; Swedish catalog (UI strings), Czech plurals; bounded canvas overscan while panning; Korean fallback at the run's weight; many vector-edit, guide, page and .fig round-trip fixes. Numbers unchanged |
| 2026-10-15 | minor | Drop files anywhere to open them (M7): dragging files over the window shows a "Drop to open" (home) or "Drop to import" (canvas) overlay. On the home screen each supported file opens as a document of its own and other files are turned away with a toast; on the canvas the files import at the drop point. Both wait behind a small card that is on screen before a large file blocks the window. A canvas import stays with the document and page that received the drop even if the tab or page changes meanwhile, and is cancelled with a message if that document or page is gone. Numbers unchanged. |
| 2026-10-15 | minor | M7: system font discovery skips entries observed as non-regular files before opening them, avoiding the reproduced named-pipe stall. Regular-file symlinks remain supported; directory depth, font limits and priorities are unchanged. Numbers unchanged. |
+2 -1
View File
@@ -829,6 +829,7 @@ name = "English"
"No model credits were built into this copy." = "No model credits were built into this copy."
"No number variables fit here." = "No number variables fit here."
"No number variables yet. Create them in Local variables." = "No number variables yet. Create them in Local variables."
"No other pages" = "No other pages"
"No paints" = "No paints"
"No plugins on {server}." = "No plugins on {server}."
"No plugins yet. Import a development plugin from its figcraft-plugin.json." = "No plugins yet. Import a development plugin from its figcraft-plugin.json."
@@ -1603,4 +1604,4 @@ name = "English"
"⌘ arrows" = "⌘ arrows"
"⌘ click" = "⌘ click"
"⌘ scroll" = "⌘ scroll"
"⌥ drag" = "⌥ drag"
"⌥ drag" = "⌥ drag"
+1
View File
@@ -830,6 +830,7 @@ status = "draft — needs native review"
"No model credits were built into this copy." = "Esta copia no incluye créditos de modelos."
"No number variables fit here." = "Ninguna variable numérica encaja aquí."
"No number variables yet. Create them in Local variables." = "Aún no hay variables numéricas. Créalas en Variables locales."
"No other pages" = "No hay otras páginas"
"No paints" = "Sin pinturas"
"No plugins on {server}." = "No hay plugins en {server}."
"No plugins yet. Import a development plugin from its figcraft-plugin.json." = "Aún no hay plugins. Importa un plugin de desarrollo desde su figcraft-plugin.json."
+1
View File
@@ -819,6 +819,7 @@ name = "Русский"
"No model credits were built into this copy." = "В этой сборке нет сведений об использованных моделях."
"No number variables fit here." = "Нет подходящих числовых переменных."
"No number variables yet. Create them in Local variables." = "Числовых переменных пока нет. Создайте их в разделе «Локальные переменные»."
"No other pages" = "Других страниц нет"
"No paints" = "Заливок нет"
"No plugins on {server}." = "На {server} нет плагинов."
"No plugins yet. Import a development plugin from its figcraft-plugin.json." = "Плагинов пока нет. Импортируйте плагин для разработки из его figcraft-plugin.json."
+86 -6
View File
@@ -71,6 +71,15 @@ pub enum AboutTab {
Models,
}
/// Values collected by a menu or Actions command before dispatching it to the engine.
#[derive(Clone, Debug, PartialEq)]
pub enum MenuInput {
RenameFile(String),
Link(String),
Offset(f64),
MoveToPage,
}
impl AboutTab {
pub const ALL: [AboutTab; 3] = [AboutTab::About, AboutTab::Contributors, AboutTab::Models];
@@ -137,6 +146,7 @@ fn about_body(ui: &mut egui::Ui, t: &Tokens) {
pub fn keys(app: &mut FigApp, ctx: &Context) {
let u = &mut app.ui;
let open = u.color_profile.is_some()
|| u.menu_input.is_some()
|| u.find.is_some()
|| u.batch_rename.is_some()
|| u.confirm_close
@@ -147,7 +157,7 @@ pub fn keys(app: &mut FigApp, ctx: &Context) {
if !open || !ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
return;
}
if u.color_profile.take().is_some() || u.find.take().is_some() || u.batch_rename.take().is_some() {
if u.menu_input.take().is_some() || u.color_profile.take().is_some() || u.find.take().is_some() || u.batch_rename.take().is_some() {
return;
}
for flag in [&mut u.confirm_close, &mut u.export_preview, &mut u.shortcuts_panel, &mut u.preferences, &mut u.about] {
@@ -265,12 +275,17 @@ pub fn show(app: &mut FigApp, ctx: &Context) {
ui.label(tr("Language"));
// Each language by its own name; System follows the OS (the browser on the web).
let langs = i18n::languages().iter().filter(|l| crate::cjk::can_show(l.code)).map(|l| (l.code, l.name));
for (code, name) in [("system", tr("System"))].into_iter().chain(langs) {
if ui.selectable_label(app.session.prefs.language.eq_ignore_ascii_case(code), name).clicked() {
app.run("pref.language", json!({"language": code}));
ui.ctx().request_repaint();
let choices: Vec<_> = [("system", tr("System"))].into_iter().chain(langs).collect();
let selected =
choices.iter().find(|(code, _)| app.session.prefs.language.eq_ignore_ascii_case(code)).map_or(tr("System"), |(_, name)| *name);
egui::ComboBox::from_id_salt("pref_language").selected_text(selected).width(220.0).show_ui(ui, |ui| {
for (code, name) in choices {
if ui.selectable_label(app.session.prefs.language.eq_ignore_ascii_case(code), name).clicked() {
app.run("pref.language", json!({"language": code}));
ui.ctx().request_repaint();
}
}
}
});
});
if let Some(lang) = i18n::current().filter(|l| l.draft) {
let note = trf("{language}: draft translation, waiting for a native speaker's review.", &[("language", &lang.name)]);
@@ -321,6 +336,7 @@ pub fn show(app: &mut FigApp, ctx: &Context) {
app.ui.find = if open { Some((find, replace)) } else { None };
}
batch_rename_dialog(app, ctx);
menu_input_dialog(app, ctx);
crate::variables_panel::show(app, ctx);
crate::dev_compare::show(app, ctx);
crate::branch_ui::show(app, ctx);
@@ -335,6 +351,70 @@ pub fn show(app: &mut FigApp, ctx: &Context) {
crate::windows::confirm_close(app, ctx);
}
/// Menus and Actions cannot pass a value themselves. Gather it here, then run the regular
/// command so the same validation and undo behaviour apply as for programmatic callers.
fn menu_input_dialog(app: &mut FigApp, ctx: &Context) {
let Some(mut input) = app.ui.menu_input.take() else { return };
let title = match &input {
MenuInput::RenameFile(_) => i18n::command("file.rename", "Rename file"),
MenuInput::Link(_) => i18n::command("text.link", "Create link"),
MenuInput::Offset(_) => i18n::command("vector.offset", "Offset path"),
MenuInput::MoveToPage => i18n::command("object.moveToPage", "Move to page"),
};
let mut open = true;
let mut action: Option<(&str, serde_json::Value)> = None;
window(&title, ctx).open(&mut open).show(ctx, |ui| {
ui.set_width(320.0);
match &mut input {
MenuInput::RenameFile(name) => {
let field = ui.add(egui::TextEdit::singleline(name).desired_width(300.0));
if (ui.button(tr("Rename")).clicked() || field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)))
&& !name.trim().is_empty()
{
action = Some(("file.rename", json!({"name": name.trim()})));
}
}
MenuInput::Link(url) => {
let field = ui.add(egui::TextEdit::singleline(url).hint_text("https://").desired_width(300.0));
if (ui.button(tr("Done")).clicked() || field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) && !url.trim().is_empty()
{
action = Some(("text.link", json!({"url": url.trim()})));
}
}
MenuInput::Offset(offset) => {
ui.add(egui::DragValue::new(offset).speed(1.0).range(-100_000.0..=100_000.0));
if ui.button(tr("Offset")).clicked() {
action = Some(("vector.offset", json!({"offset": offset})));
}
}
MenuInput::MoveToPage => {
let mut choices = 0;
if let Ok(ds) = app.session.ds() {
for &page in &ds.doc.pages {
if page == ds.page || ds.doc.is_divider(page) {
continue;
}
if let Some(node) = ds.doc.node(page)
&& ui.button(&node.name).clicked()
{
action = Some(("object.moveToPage", json!({"page": page})));
}
choices += 1;
}
}
if choices == 0 {
ui.label(tr("No other pages"));
}
}
}
});
if let Some((id, params)) = action {
app.run(id, params);
} else if open {
app.ui.menu_input = Some(input);
}
}
/// The Rename layers dialog's fields.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct BatchRenameUi {
+5
View File
@@ -132,6 +132,8 @@ mod live_tests;
#[cfg(test)]
mod live_text_tests;
#[cfg(test)]
mod menu_input_tests;
#[cfg(test)]
mod online_tests;
#[cfg(test)]
mod open_issues_tests;
@@ -410,6 +412,8 @@ pub struct UiState {
/// The tab the About window shows.
pub about_tab: dialogs::AboutTab,
pub preferences: bool,
/// A menu command that needs a value before the engine can run it.
pub menu_input: Option<dialogs::MenuInput>,
pub shortcuts_panel: bool,
/// The Keyboard shortcuts panel's tab and search text.
pub shortcuts_tab: figcraft_engine::keymap::Group,
@@ -537,6 +541,7 @@ impl Default for UiState {
about: false,
about_tab: dialogs::AboutTab::About,
preferences: false,
menu_input: None,
shortcuts_panel: false,
shortcuts_tab: figcraft_engine::keymap::Group::Essential,
shortcuts_query: String::new(),
+74
View File
@@ -0,0 +1,74 @@
//! Menu and Actions entries that need an input must collect it in the UI, not send `{}` to the
//! engine (which would show a parameter error or silently use a default).
use figcraft_engine::Session;
use serde_json::json;
use crate::dialogs::MenuInput;
use crate::layers_drag_tests::Ui;
use crate::menus;
#[test]
fn preference_actions_open_preferences_without_changing_values() {
let mut ui = Ui::new(Session::new());
ui.app.session.prefs.language = "en".into();
ui.app.session.prefs.publish_server = "wss://example.test/".into();
let before = ui.app.session.prefs.clone();
for id in ["pref.language", "pref.userName", "pref.publishServer", "pref.nudge"] {
ui.app.ui.preferences = false;
menus::invoke(&mut ui.app, id);
assert!(ui.app.ui.preferences, "{id} did not open Preferences");
assert!(ui.app.ui.last_error.is_none(), "{id} showed an error");
assert_eq!(ui.app.session.prefs.language, before.language);
assert_eq!(ui.app.session.prefs.publish_server, before.publish_server);
assert_eq!(ui.app.session.prefs.nudge_small, before.nudge_small);
assert_eq!(ui.app.session.prefs.nudge_big, before.nudge_big);
}
// Programmatic callers still need the actual argument.
assert!(ui.app.session.execute("pref.language", &json!({})).is_err());
}
#[test]
fn menu_entries_collect_required_arguments_before_running() {
let mut ui = Ui::new(Session::new());
let name = ui.app.session.doc().unwrap().name.clone();
for (id, expected) in [
("file.rename", MenuInput::RenameFile(name)),
("text.link", MenuInput::Link(String::new())),
("vector.offset", MenuInput::Offset(0.0)),
("object.moveToPage", MenuInput::MoveToPage),
] {
ui.app.ui.menu_input = None;
menus::invoke(&mut ui.app, id);
assert_eq!(ui.app.ui.menu_input, Some(expected), "{id}");
assert!(ui.app.ui.last_error.is_none(), "{id} showed an error");
}
ui.frame(vec![]);
assert!(ui.app.ui.last_error.is_none());
}
#[test]
fn palette_entries_with_existing_pickers_show_them() {
let mut ui = Ui::new(Session::new());
menus::invoke(&mut ui.app, "tool.set");
assert_eq!(ui.app.ui.tool_flyout, Some("move"));
ui.app.ui.left_collapsed = true;
menus::invoke(&mut ui.app, "page.select");
assert!(!ui.app.ui.left_collapsed);
assert!(ui.app.ui.pages_open);
menus::invoke(&mut ui.app, "file.openRecent");
assert!(ui.app.ui.home);
assert_eq!(ui.app.ui.home_view.section, crate::home::Section::Recents);
assert!(ui.app.ui.last_error.is_none());
}
#[test]
fn batch_rename_opens_its_existing_dialog() {
let mut session = Session::new();
let id = session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 20, "height": 20})).unwrap()["id"].clone();
session.execute("select.set", &json!({"ids": [id]})).unwrap();
let mut ui = Ui::new(session);
menus::invoke(&mut ui.app, "object.batchRename");
assert_eq!(ui.app.ui.batch_rename.as_ref().map(|d| d.ids.len()), Some(1));
assert!(ui.app.ui.last_error.is_none());
}
+29
View File
@@ -81,6 +81,10 @@ pub fn invoke(app: &mut FigApp, id: &str) {
"file.placeImage" => app.import_dialog(true),
"file.save" => app.save(),
"file.saveAs" => app.save_as_dialog(),
"file.openRecent" => {
app.ui.home = true;
app.ui.home_view.section = crate::home::Section::Recents;
}
"file.saveVersion" => crate::versions::start_save(app),
"branch.create" => crate::versions::start_branch(app),
"file.export" | "file.exportSvg" => app.ui.export_preview = true,
@@ -128,6 +132,31 @@ pub fn invoke(app: &mut FigApp, id: &str) {
app.services.clipboard_set_text(&format!("figcraft://node/{}", ids.first().map(|i| i.0).unwrap_or(0)));
}
"edit.findReplace" => app.ui.find = Some((String::new(), String::new())),
// The Actions palette invokes commands without parameters. These values are edited in
// Preferences; sending an empty object would either fail or silently reset a preference.
"pref.language" | "pref.userName" | "pref.publishServer" | "pref.nudge" => {
app.run("app.preferences", json!({}));
}
"file.rename" => {
if let Ok(doc) = app.session.doc() {
app.ui.menu_input = Some(crate::dialogs::MenuInput::RenameFile(doc.name.clone()));
}
}
"text.link" => app.ui.menu_input = Some(crate::dialogs::MenuInput::Link(String::new())),
"vector.offset" => app.ui.menu_input = Some(crate::dialogs::MenuInput::Offset(0.0)),
"object.moveToPage" => app.ui.menu_input = Some(crate::dialogs::MenuInput::MoveToPage),
"object.batchRename" => {
let ids = app.session.selection();
if !ids.is_empty() {
crate::dialogs::open_batch_rename(app, &ids);
}
}
"tool.set" => app.ui.tool_flyout = Some("move"),
"page.select" => {
app.ui.left_tab = crate::LeftTab::File;
app.ui.left_collapsed = false;
app.ui.pages_open = true;
}
// From a menu the link is pasted into the Join dialog (`collab.join {link}` joins directly).
"collab.join" => crate::live::open_join(app, ""),
// So is the server address for publishing (it carries the server key).
+5 -1
View File
@@ -1,6 +1,6 @@
# Where FigCraft falls short of Figma
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (bounded canvas pan overscan; reviewed Russian catalog and integer plurals; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **Target:** Figma Design (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-16 · **Change:** minor (menu and Actions parameter dialogs; bounded canvas pan overscan; reviewed Russian catalog and integer plurals; `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; sharing with a join code; text box sizes measured against documents re-saved by other software; live sessions per document; .fig export checked by opening written files in other software; experimental .fig export; keyboard shortcut parity suite and bindings; Simplified Chinese draft catalog; Latin American Spanish overlay; prototyping tail: gamepad triggers, widgets in prototypes, mode lookups; plugin directory on figcraft-server; Japanese and Korean draft catalogs and CJK UI fonts; plugin widgets and the plugin panel tail; French, German and Brazilian Portuguese draft catalogs; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and edit tail; dev resources links; extended collections tail; branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **Target:** Figma Design (desktop app 126.10.7)
Every known shortfall against Figma, one entry each, ranked by how much it stops a Figma user from
doing real work in FigCraft. Agents: pick work from the top unless the owner says otherwise. The
@@ -138,6 +138,9 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
cursor for every tool and modifier, panel widths, dark pages (`field_tests`, `cursor_tests`,
`cursors_tests`, `outline_options_tests`, `hide_publishing_tests`, `panel_width_tests`,
`theme_pages_tests`).
- **Landed (2026-10-16):** affected menu and Actions entries that need values now open their existing
controls or collect the values before dispatch. The language choice in Preferences uses a
dropdown that fits in the dialog (`menu_input_tests.rs`).
- **Evidence:** ROADMAP item 7 ("Left: tool by tool, panel by panel against the incumbent").
- **Impact:** Figma users notice the feel within minutes; it decides whether they stay.
- **Estimate:** 40–60 h. Detail in [`ui-parity.md`](ui-parity.md).
@@ -400,6 +403,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
| Date | Change | Summary |
|---|---|---|
| 2026-10-16 | minor | Gap 4: affected menu and Actions entries with required values open an input or the relevant panel before running the command; the language selector fits inside Preferences. Estimates and ranking unchanged. |
| 2026-10-15 | minor | Gap 7: bounded canvas overscan for smoother panning. Estimates and ranking unchanged. |
| 2026-10-14 | minor | Russian catalog (M9), reviewed and approved by VanTan: all 2,165 current entries, Русский in Preferences and regional ru tags. Russian integer plurals use one/few/many; contributor display name VanTan added. Existing estimates unchanged. |
| 2026-10-13 | minor | Gap 1: the experimental `.fig` export writes instance sizes (resized, filling an auto layout, scaled), variables with modes and bindings, shared styles and variable-width strokes, and the import reads them back. Checked by opening written files in other software: resized and scaled instances keep their size and lay their layers out for it, variables with their modes and colour bindings resolve there (a frame set to another mode shows that mode's colours, also inside instances), colour, text, effect and grid styles are listed, and variable-width strokes that are the same on both sides of the path draw with their width; number, string and boolean bindings and bindings on effects, layout guides and gradient stops come across as the values they give but did not show as bound there (their entries now also name the layer field they set, not yet checked there), and strokes wider on one side of the path are written as filled outlines, no longer editable strokes there (the stroke itself is kept as plugin data and our own import puts it back; not yet checked there). Hours and the ranking unchanged |