PluginManager::broadcast_notification was fanning out host notifications to every loaded plugin. Only V4 plugin processes can receive host->plugin notifications; V2/V3 plugins correctly rejected them with notify_plugin requires V4 protocol.
Add PluginProcess::is_v4() and skip non-V4 plugins in broadcast_notification so V4 document snapshot notifications only go to V4-capable plugins. Fixes repeated console errors for Example, Land Survey and V2 Compat.
Saving to a path that already holds a drawing fails on Windows with
"could not verify <path> before saving: ... (os error 33)". Save As over any
existing drawing is affected; saving to a new path is not.
EditLease::acquire() opens the destination and takes the platform drawing
lock on it, then the save fingerprints the same path to check nothing changed
underneath. LockFileEx is mandatory and scoped to the handle that took it, so
that second open is refused against our own lock, lands in the non-NotFound
arm and aborts the save. flock on Unix is advisory and the second open
succeeds, so neither CI nor a Linux desktop sees it.
Read through the handle the lease already holds instead. A duplicate shares
the lock ownership, so the pre-replace re-check on the save worker can use
one too rather than re-opening the path.
The same collision reached the headless open op, which read a path this
session may hold leased: automation's save_then_open_round_trips fails on
main for that reason, one line after the save it no longer reaches.
A unitless block has no source length to convert from, so falling back to
MEASUREMENT gave it an inches or millimetres identity it never had and
scaled the reference on insert. The properties panel reported unit_factor
1.0 for the same insert.
Skip the conversion when either the block or the drawing is unitless.
Fixes#706.
Each ribbon panel was rendered five ways every frame so CollapsePanels could
roll back to any density at layout time: full, compact, button, tight and a
full-size flyout that duplicated the full rendering.
The layout pass ran four full measure-layouts per panel even in forced modes,
and the degradation decision was an untested u8 inline in Ribbon::view.
Now the choice is a pure, unit-tested decide_levels() whose levels are a typed
Level enum (Full/Compact/Collapsed/Tight). Panel carries its four renderings
as elements: [Element; 4] indexed by Level, with a slot(i, level) helper for
the tree stride. Forced modes (Full/Compact/Collapsed) measure nothing at all
and derive the row height from the placed panel. The open flyout draws the
panel's own `full` rendering inside a self-drawn box (identical weakest-bg +
1px neutral-border to the container it replaced), so no duplicate
render_group is built per panel per frame. Level, Panel, Widths, CollapsePanels
and decide_levels are pub(crate); CollapseMode stays pub (re-exported).
Tests (collapse.rs tests module, added RED first):
- forced_modes_pin_every_panel
- auto_huge_width_keeps_everything_full
- auto_degrades_from_the_right_one_panel_at_a_time
- auto_stops_degrading_once_the_row_fits
- auto_escalates_a_single_panel_past_collapsed_to_tight
- auto_cascade_drops_every_collapsed_panel_to_tight
- auto_runs_the_compact_phase_across_the_row_first
- no_panels_stays_empty
- single_panel_walks_the_whole_ladder
- zero_widths_never_overflow
- endless_row_degrades_everything_to_tight
- unbounded_row_keeps_everything_full
- nan_budget_degrades_both_phases_but_no_tight_cascade
- exact_fit_at_full_stays_full
- matches_the_pre_refactor_algorithm: byte-equivalence sweep against the
verbatim pre-refactor u8 algorithm (280 width/budget combos incl. the
degenerate 0/0/inf/NaN inputs), proving the decision logic is unchanged.
Measured with the committed #[ignore]d benchmark
(`cargo test --lib --release -- --ignored --nocapture bench_ribbon_view_construction`)
run as one instrument across pre-change / this change: Ribbon::view()
construction ~676 -> ~425 us/frame (~37% of view construction saved); forced
modes run 1 layout call per panel per frame instead of 5.
Verification: cargo build clean; cargo test --lib = 382 passed (367 baseline
+ 15 new ribbon tests) / 2 known pre-existing failures unchanged
(save_then_open_round_trips, tilted_ucs_places_planar_entities_with_the_plane_normal).
Strict confinement uses a private runtime directory while the Wayland socket remains in the host directory. Resolve the accessible socket path, provide bundled XKB data, and fall back to X11 only when needed.
Snapcraft strips the sole usr directory from tar sources, so extract the artifact directly and retain its desktop-file path.\n\nAdd a snap-only manual target to avoid rebuilding other platforms while validating packaging fixes.
The ribbon kept its own \show_block_palette\ copy that the app had to
manually sync at every mutation point (the BLOCKPALETTE command and the
dock Close handler). Drop the copy and thread the app's authoritative
\show_block_palette\ into \Ribbon::view\ at render time, so the button
highlight can't drift from the panel's true visibility — those two
handlers are now mutate-points only and the ribbon view is the single
place that reads the state back.
The \\\x01attr\\x01…\ sentinel-prefixed string was used to tell geometry
fields (\&'static str\ names) apart from block attribute rows (runtime
tags) in \ctive_field\ and \dit_buf\. Replace it with a \FieldKey\
enum (\Geom(&'static str)\ / \Attr(String)\), so the two namespaces can't
collide by construction: \dit_buf\ is now \HashMap<FieldKey, String>\,
\ctive_field\ is \Option<FieldKey>\, and the sentinel decoding in
\ctive_key_id\ is replaced by \FieldKey::widget_id\.
While touching the key plumbing, replace \prop_field_key_for_id\'s
per-event scan of every section x prop (with a \String\ format per
candidate) with an O(1) precomputed \HashMap<iced::widget::Id, FieldKey>\
(\ield_key_by_id\), built once in \
efresh_properties\ alongside the
sections — and rebuilt after \make_sections_read_only\ so rows demoted
to read-only stop mapping. \PropSyncActive\ now does a single map lookup
per event.
The global left-click listener fires PropPointerPressed on every mouse
press anywhere in the app, and the handler used to launch a full
widget-tree focus sweep on each one - even when the Properties panel
was closed. Gate the sweep on dock_panel_visible(Properties): when the
panel is hidden the handler now returns Task::none, turning an O(whole
UI) traversal per click into an O(1) boolean check in the common case.
The other sweep call sites already gate via sync_active_field_if_any;
this was the only ungated one.
Tests:
- hidden_properties_panel_skips_the_focus_sweep - a click with the
panel closed returns a Task::none (units == 0), i.e. no sweep.
- visible_properties_panel_runs_the_focus_sweep - a click with the
panel open still fires the sweep (units > 0), preserving the
select-whole-value behavior.