Editor Polish, Round One
The editor has been functional for a while now. You can create nodes, wire them up, inspect values, search — it works. But “works” isn’t the bar. The bar is that it feels like an instrument. This round of polish is about closing the gap between “functional” and “fluid”.
Node draw order
Before this change, node rendering order was determined by HashMap iteration — effectively random. Click a node and it might render behind its neighbours. Overlapping nodes were a visual mess.
Now CanvasState tracks a draw_order: Vec<NodeId>. New nodes are appended to the end (render on top). Clicking a node calls bring_to_front(), which moves it to the end of the list. The renderer iterates draw_order instead of the HashMap, so z-ordering is deterministic and intuitive.
pub fn bring_to_front(&mut self, id: NodeId) {
self.draw_order.retain(|&n| n != id);
self.draw_order.push(id);
}
Simple, but it makes the canvas feel correct in a way you only notice when it’s missing.
Smarter tooltips
Pin tooltips used to be a single line of text in a fixed-style popup: radius: Number. Functional, but not helpful when you’re learning what a pin does.
Now tooltips have two layers:
- Header — pin name and type, rendered at 11px in the standard tooltip color
- Description — the pin’s documentation (if any), rendered at 10px in a dimmer color
The tooltip auto-sizes up to 280px max width. If a pin has no description, you get the clean single-line tooltip you had before. If it does, you get context without leaving the canvas.
The colours and sizes all come from theme constants now — TOOLTIP_TEXT, TOOLTIP_HELP, TOOLTIP_HEADER_FONT, TOOLTIP_HELP_FONT, TOOLTIP_MAX_WIDTH — so they’re tunable in one place.
The inspector knows when to hide
The inspector panel used to render even when nothing was selected — an empty right panel eating 220px of canvas space for no reason. Now it only appears when you have a node selected.
When it does appear, the layout has been refined too. Node documentation (summary and description) now renders below the pin controls instead of above them. The logic: when you select a node, the first thing you want is the pins — what you can connect and tweak. The description is reference material, not the headline.

Search opens at your cursor
Previously, pressing Space or Tab to open node search placed the search popup at a hardcoded screen center ([400.0, 300.0]). If you’d panned the canvas, the new node would appear in a totally different place from where you were working.
Now the editor tracks last_cursor_canvas — the cursor’s position in canvas space, updated on every mouse move. When you press Space, the search opens at the cursor position. Create a node from search and it appears right where you’re looking.
"Tab" | "Space" => {
if !self.search.open {
self.command_palette.close();
self.search.open_at(self.canvas.last_cursor_canvas);
}
}
This is one of those changes that’s almost invisible — you just stop being annoyed by something you couldn’t quite articulate.
Theme constants everywhere
This round also eliminated every hardcoded magic number in the inspector and tooltip rendering. Sizes like panel width, pin dot radius, spacing between pins, tooltip offsets — all moved to theme::sizes and theme::colors constants.
pub const INSPECTOR_WIDTH: f32 = 220.0;
pub const INSPECTOR_MARGIN: f32 = 8.0;
pub const INSPECTOR_PIN_DOT: f32 = 6.0;
pub const INSPECTOR_SPACING: f32 = 8.0;
pub const INSPECTOR_PIN_SPACING: f32 = 2.0;
pub const TOOLTIP_OFFSET_X: f32 = 12.0;
pub const TOOLTIP_OFFSET_Y: f32 = -10.0;
No behaviour change, but now the entire editor visual language is defined in one file. When we get to theming or DPI scaling, there’s exactly one place to update.
The test count
16 new tests across these changes:
- 6 tests for draw order and
bring_to_front()behaviour - 4 tests for search-at-cursor with pan and zoom
- 4 tests for
input_doc()/output_doc()builder methods - 2 tests for tooltip description propagation
Every UI behaviour that has logic behind it gets a test. The renderer is visual, but the state management that drives it is pure functions on structs — and that’s all testable without a window.