mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-07 11:03:15 +00:00
M5: proto.settings refuses an unknown device and answers the settings (#2027)
`proto.settings` took any string as the device ("Pixel Nonexistent", "iphone 16") and saved it in
the file: the presenter then drew no device frame while its picker showed the name as if it were
one. An unknown colour was already refused with the device's colours, but a typo in the device
went unnoticed, and an agent couldn't read the settings back (`proto.settings {}` answered
`{ok: true}`, and neither `inspect_document` nor `get_node` shows a page's prototype settings).
An unknown name, or a value that isn't a string, is now refused with every name the picker offers
("None", "Custom size" and the devices), and nothing changes. A known name is taken in any case and
with spaces around it, and stored as the picker spells it (`known_device`). Every call answers the
page's settings as they are after it (`settings_json`): device, background, rotation, the colour
shown, the screen as presented (null without a device), the custom size and responsive, beside the
`ok: true` it answered before; `proto.settings {}` reads them.
Test: `proto_present_tests::unknown_devices_are_refused_and_settings_read_back`. Docs: mcp.md,
prototyping-parity.md, ROADMAP.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2LxvJ6MuFmiLA6Zqm
This commit is contained in:
1 parent
8eb0b87646
commit
7f62fc1416
5 files changed
+82
-8
No files matched your search
+2
-1
@@ -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-11-02 · **Change:** minor (glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per component and file (gap 10); skipping the Open link notice by command is saved with the preferences at once (gap 15); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); `.fig` connector labels without a saved snapshot are kept, centred on their line (gap 1); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and instance checks, on every move (#1571); damaged font tracking table offsets are checked instead of wrapping on 32-bit targets, and font indexing makes its existing offset bounds explicit (#1125); BLine truncated
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** patch (`proto.settings` refuses an unknown device with the list of devices instead of saving it, takes a known one in any case, and answers the page's settings, so `proto.settings {}` reads them (#2027); glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per component and file (gap 10); skipping the Open link notice by command is saved with the preferences at once (gap 15); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); `.fig` connector labels without a saved snapshot are kept, centred on their line (gap 1); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and Line truncated
|
||||
|
||||
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
|
||||
@@ -358,6 +358,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | patch | M5, #2027: `proto.settings` refuses an unknown device name with the list of devices (it saved any string, and the presenter showed no device), takes known names in any case, and answers the page's prototype settings, so `proto.settings {}` reads them back over MCP. Test: `proto_present_tests::unknown_devices_are_refused_and_settings_read_back`. Numbers unchanged |
|
||||
| 2026-11-02 | patch | M8, #1885: a glass whose shape is too big to keep whole at the zoom (past `MAX_SHAPE_PX` with its core's margin) bends along its edge normals instead of working out a core, so its fields never cover more pixels than a kept shape's, and the core's feature transform holds 16 bytes a pixel. The edge normals are box-blurred twice again and the bend directions once, as documented. Test: `glass::tests::a_shape_too_big_to_keep_bends_along_its_normals`. Numbers unchanged |
|
||||
| 2026-11-02 | minor | M3, #1885: glass bends the backdrop towards its core, the shape shrunk by `depth` and the bevel, as the reference tool's: round a sharp corner the bend turns smoothly instead of meeting along the diagonal, and a part thinner than twice that (a capsule, a letter's stroke) bends across itself towards its own middle (pixels about as deep as any within 2 px). Rectangles, frames, components, instances and sections whose corners aren't smoothed work their glass out in closed form, with nothing to rasterise, transform or keep: a zoom frame draws in 40 ms instead of 54 and a resize frame in 46 instead of 67 (`glass_render_timings`, release, 2×). Other shapes keep their fields, with the bend directions for the last two depths asked for beside them, so dragging the Depth slider works out only those again. Tests: `glass::tests::a_sharp_corner_bends_round_towards_the_core`, `glass::tests::a_thin_part_bends_across_itself`, `glass::tests::neighbouring_strokes_bend_towards_their_own_middles`, `glass::tests::a_rounded_rectangle_in_closed_form_matches_its_distance_transform`, `glass_cut_tests::what_the_shape_depends_on_works_it_out_again_and_nothing_else_does`. Numbers unchanged |
|
||||
| 2026-11-02 | minor | M3, #1885: glass's light is a line on its 1-unit bevel added to the backdrop, as the reference tool's: fully where the edge faces the light, none below 0.35 of facing it and 0.6 as much on the opposite side, with a faint gradient across the curve, 5% darker facing the light and lighter opposite (it was a glow mixed towards white, as deep as `splay` made it). `splay` turns the bend from along the edge's normal towards the way away from the layer's centre, as the reference tool's does, and no longer widens the light: files with a splay above 0 look different. The direction to the centre is worked out in the shape's own frame, so cut renders match. Tests: `glass::tests::the_light_lies_on_the_bevel_and_the_gradient_across_the_curve`, `backdrop_tests::splay_turns_the_bend_towards_the_centre`, `glass_cut_tests::the_light_is_a_line_on_the_bevel`. Numbers unchanged |
|
||||
|
||||
@@ -112,7 +112,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
spec!(
|
||||
"proto.settings",
|
||||
"Prototype settings",
|
||||
"{device? (a device name, \"Custom size\" or \"None\"), background?, rotation? portrait|landscape (or 0|90; phones and tablets turn), color? (one of the device's colours by name), width?, height? (the custom size's screen, 1–10000 points; picks \"Custom size\"), responsive? bool (frames resize to the screen and their constraints and auto layout respond)}",
|
||||
"{device? (a device name, \"Custom size\" or \"None\"; any case; another name is refused with the list), background?, rotation? portrait|landscape (or 0|90; phones and tablets turn), color? (one of the device's colours by name), width?, height? (the custom size's screen, 1–10000 points; picks \"Custom size\"), responsive? bool (frames resize to the screen and their constraints and auto layout respond)} → the page's settings after the call (none given: reads them)",
|
||||
true,
|
||||
settings
|
||||
),
|
||||
@@ -173,12 +173,51 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
]
|
||||
}
|
||||
|
||||
/// `proto.settings`: everything is checked before anything changes.
|
||||
/// The device setting `name` stands for, as the device picker names it: "None", "Custom size" or a
|
||||
/// model of [`PrototypeSettings::DEVICES`], whatever its case and the spaces around it.
|
||||
fn known_device(name: &str) -> Option<&'static str> {
|
||||
let name = name.trim();
|
||||
["None", PrototypeSettings::CUSTOM_DEVICE]
|
||||
.into_iter()
|
||||
.chain(PrototypeSettings::DEVICES.iter().map(|d| d.name))
|
||||
.find(|d| d.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
/// What `proto.settings` answers: the page's prototype settings (the device as the picker names it,
|
||||
/// the colour shown, the screen as presented, `null` without a device).
|
||||
fn settings_json(ps: &PrototypeSettings) -> Value {
|
||||
let screen = ps.device().map(|d| json!({"width": d.width, "height": d.height}));
|
||||
json!({
|
||||
"ok": true,
|
||||
"device": ps.device,
|
||||
"background": format!("#{}", ps.background.to_hex()),
|
||||
"rotation": ps.rotation,
|
||||
"color": ps.device_color().map(|c| c.name),
|
||||
"screen": screen,
|
||||
"customSize": {"width": ps.custom_size[0], "height": ps.custom_size[1]},
|
||||
"responsive": ps.responsive,
|
||||
})
|
||||
}
|
||||
|
||||
/// `proto.settings`: everything is checked before anything changes; an unknown device is refused
|
||||
/// with the devices to pick from, as an unknown colour is. Answers the settings as they are after
|
||||
/// the call, so a call without parameters reads them.
|
||||
fn settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "proto.settings";
|
||||
let page = s.page()?;
|
||||
let bg = color(p, "background");
|
||||
let dev = string(p, "device");
|
||||
let dev = match p.get("device") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(v) => {
|
||||
let name = v.as_str().unwrap_or_default();
|
||||
let found = known_device(name).ok_or_else(|| {
|
||||
let names: Vec<&str> =
|
||||
["None", PrototypeSettings::CUSTOM_DEVICE].into_iter().chain(PrototypeSettings::DEVICES.iter().map(|d| d.name)).collect();
|
||||
bad(cmd, format!("unknown device {}: device is one of {}", figcraft_format::clip(&v.to_string(), 60), names.join("|")))
|
||||
})?;
|
||||
Some(found.to_string())
|
||||
}
|
||||
};
|
||||
let rot = match p.get("rotation") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(v) => Some(rotation_of(v).ok_or_else(|| bad(cmd, "rotation: portrait|landscape"))?),
|
||||
@@ -231,12 +270,13 @@ fn settings(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None if !colors.iter().any(|x| x.name == next.color) => next.color.clear(),
|
||||
None => {}
|
||||
}
|
||||
let answer = settings_json(&next);
|
||||
if next != *prototype
|
||||
&& let NodeKind::Page { prototype, .. } = &mut s.doc_mut()?.get_mut(page)?.kind
|
||||
{
|
||||
*prototype = next;
|
||||
}
|
||||
ok()
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
/// Resize top-level `frame` to a screen of `(w, h)` as a responsive prototype does: its children
|
||||
|
||||
@@ -97,3 +97,31 @@ fn explicit_variable_modes_go_back_to_auto() {
|
||||
assert!(s.execute("variable.setMode", &json!({"ids": [f], "collection": "Tokens", "mode": "Dusk"})).is_err());
|
||||
assert!(s.execute("variable.setMode", &json!({"ids": [f], "collection": "Tokens"})).is_err(), "a mode, or null for Auto");
|
||||
}
|
||||
|
||||
/// #2027: an unknown device is refused with the devices to pick from, as an unknown colour is, and
|
||||
/// nothing changes; a known one is taken whatever its case and stored as the picker names it; the
|
||||
/// settings read back from `proto.settings` without parameters.
|
||||
#[test]
|
||||
fn unknown_devices_are_refused_and_settings_read_back() {
|
||||
let mut s = Session::new();
|
||||
let before = settings(&s);
|
||||
for junk in [json!("Pixel Nonexistent"), json!(""), json!(42), json!(["iPhone 16"])] {
|
||||
let err = s.execute("proto.settings", &json!({"device": junk, "responsive": true})).unwrap_err().to_string();
|
||||
assert!(err.contains("unknown device") && err.contains("iPhone 17|") && err.contains("Custom size"), "{junk}: {err}");
|
||||
assert_eq!(settings(&s), before, "{junk}: nothing changes");
|
||||
}
|
||||
let set = s.execute("proto.settings", &json!({"device": " iphone 16 ", "color": "black"})).unwrap();
|
||||
assert_eq!(settings(&s).device, "iPhone 16", "stored as the picker names it");
|
||||
assert_eq!((&set["device"], &set["color"]), (&json!("iPhone 16"), &json!("Black")));
|
||||
let read = s.execute("proto.settings", &json!({})).unwrap();
|
||||
assert_eq!(read, set, "a call without parameters reads the settings");
|
||||
assert_eq!((&read["ok"], &read["rotation"], &read["responsive"]), (&json!(true), &json!("portrait"), &json!(false)));
|
||||
assert_eq!(read["screen"], json!({"width": 393.0, "height": 852.0}));
|
||||
assert!(read["background"].as_str().is_some_and(|c| c.starts_with('#')), "{read}");
|
||||
// "None" and "Custom size" in any case; no device has no screen or colour.
|
||||
s.execute("proto.settings", &json!({"device": "custom SIZE", "width": 800, "height": 600})).unwrap();
|
||||
assert_eq!(settings(&s).device, PrototypeSettings::CUSTOM_DEVICE);
|
||||
let none = s.execute("proto.settings", &json!({"device": "none"})).unwrap();
|
||||
assert_eq!((&none["device"], &none["screen"], &none["color"]), (&json!("None"), &json!(null), &json!(null)));
|
||||
assert_eq!(none["customSize"], json!({"width": 800.0, "height": 600.0}));
|
||||
}
|
||||
+6
-2
@@ -847,11 +847,15 @@ removes the whole conditional). Every one is an undo step and applies to all `id
|
||||
with the same interactions). Flows `proto.flow.add` / `rename` / `remove`,
|
||||
and the device, its rotation and the background `proto.settings {device?, background?,
|
||||
rotation?, color?, width?, height?, responsive?}` (`None`, `iPhone 16`, `iPhone 16 Pro Max`,
|
||||
`Android Compact`, `iPad mini`, `MacBook Air`, `Desktop` or `Custom size`; `rotation` `portrait` or
|
||||
`Android Compact`, `iPad mini`, `MacBook Air`, `Desktop` or `Custom size`, in any case; another
|
||||
name is an error that lists the devices, and nothing changes; `rotation` `portrait` or
|
||||
`landscape`, which turns phones and tablets; `color` one of the device's colours by name, e.g.
|
||||
`Black`, `White`, `Pink`, `Teal`, `Blue` for the iPhone 16 (an error lists them), `""` for the
|
||||
default; `width` / `height` set the custom size's screen, 1–10000 points, and pick `Custom size`,
|
||||
which is a bare screen without a body). On a device the presented frame is sized to the device's
|
||||
which is a bare screen without a body). Every call answers the page's settings as they are after
|
||||
it, so `proto.settings {}` reads them: `{device, background, rotation, color (the colour shown, null
|
||||
without a body), screen ({width, height} as presented, null without a device), customSize,
|
||||
responsive}`. On a device the presented frame is sized to the device's
|
||||
screen: a frame bigger than the screen scrolls through its whole size whatever its `overflow`, content
|
||||
past the frame's own edges scrolls only as its `overflow` allows (else it's clipped), and `fixed`
|
||||
layers stay on screen (pinned to the edges their constraints name). With `responsive: true` (saved
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Prototyping parity
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (Hide UI in the presenter's Options menu, and Cmd/Ctrl+\ as in the editor: the bar above the prototype and the navigation bar under it go away, a toast names the key that brings them back, `proto.presentOptions {hideUi}` (#1820); the Controller menu names each button as PlayStation and Switch controllers do; "Open in new tab" on Open link: off, the web build's own tab goes to the link; a manual overlay's position can be dragged on the canvas (#405); Open link notices now cover mail links, and skipping them covers only links the viewer fired (#1096); overlay positions relative to the layer that opens them; the Triggers row no longer lists delays on mouse triggers as missing; top-level frames bigger than the device's screen scroll in the player; Change to, set variable and set variable mode lay out what they change in the player, as the canvas does; presentation tabs, one per page of a file; a Present tab with device and scale controls (#537); core editor leftovers: delays on mouse triggers and manual overlay offsets; controller buttons on key/gamepad triggers, widgets that react in prototypes, `proto.evaluate` and the mode lookup rules pinned) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-06 · **Change:** patch (`proto.settings` refuses an unknown device with the list of devices instead of saving it, takes a known one in any case, and answers the page's settings, so `proto.settings {}` reads them (#2027); Hide UI in the presenter's Options menu, and Cmd/Ctrl+\ as in the editor: the bar above the prototype and the navigation bar under it go away, a toast names the key that brings them back, `proto.presentOptions {hideUi}` (#1820); the Controller menu names each button as PlayStation and Switch controllers do; "Open in new tab" on Open link: off, the web build's own tab goes to the link; a manual overlay's position can be dragged on the canvas (#405); Open link notices now cover mail links, and skipping them covers only links the viewer fired (#1096); overlay positions relative to the layer that opens them; the Triggers row no longer lists delays on mouse triggers as missing; top-level frames bigger than the device's screen scroll in the player; Change to, set variable and set variable mode lay out what they change in the player, as the canvas does; presentation tabs, one per page of a file; a Present tab with device and scale controls (#537); core editor leftovers: delays on mouse triggers and manual overlay offsets; controller buttons on key/gamepad triggers, widgets that react in prototypes, `proto.evaluate` and the mode lookup rules pinned) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
**Breadth ~92%, ready ~74% (estimated).** Every Figma trigger except media, every action except
|
||||
media control, every transition and easing exist; key/gamepad triggers take controller buttons,
|
||||
@@ -107,6 +107,7 @@ tool; opt-in), **silent** (the documentation doesn't say; the case pins the curr
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | patch | M5, #2027: `proto.settings` took any string as the device (`"Pixel Nonexistent"`, `"iphone 16"`) and saved it in the file; the presenter then drew no device while its picker showed the name as if it were one, and the settings couldn't be read back over MCP (`proto.settings {}` answered `{ok: true}`). An unknown name (or a non-string) is now refused with every name the picker offers (`None`, `Custom size` and the devices), as an unknown colour is refused with the device's colours, and nothing changes; a known name is taken in any case and stored as the picker spells it. Every call answers the page's settings after it: device, background, rotation, the colour shown, the screen as presented, the custom size and responsive. Test: `proto_present_tests::unknown_devices_are_refused_and_settings_read_back` |
|
||||
| 2026-10-31 | minor | Gap 15: the Controller menu names buttons as PlayStation and Switch controllers do; that item leaves the missing list. Estimate unchanged. Test: `present::tail_tests`. |
|
||||
| 2026-10-30 | minor | Opening a link in place: Open link has "Open in new tab", on by default; off, the web build's own tab goes to the link after the notice (or at once with it skipped); the desktop app has no tab of its own and opens the browser either way. Saved in files only when off (`openInNewTab`) and read from REST files. |
|
||||
| 2026-10-30 | patch | A manual overlay can be placed on the canvas (#405): while the interaction editor has an Open overlay action with Manual position open, the overlay frame shows dimmed, outlined, where it opens from the top-left of the layer that opens it (right below that layer without an offset), and dragging it moves it there. The X and Y fields follow while dragging, and the drag is one undo step (`crates/ui-egui/src/canvas/proto_canvas/overlay_ghost.rs`). Numbers unchanged |
|
||||
|
||||
Reference in new issue
Block a user