Inspector (inspector)

An inspector places a properties panel on the window’s trailing edge, beside the content, the Keynote/Pages shape: selection on the left, its editable attributes on the right. Like every Day surface it is a projection of an app-owned signal, not an imperative controller:

let show = Signal::global(false);
inspector(show, editor(), || form((section((/* property rows */,)),)))
// show:  show.set(true);      — from a toolbar toggle, a menu item, a button
// hide:  show.set(false);
  • visible is any Binding<bool>. Every affordance that shows or hides the panel writes the same signal, so a toolbar toggle, a menu item, and a button in the content can coexist and stay in step; bind a toolbar_toggle to it and relabel a menu item from it.
  • content is the window content the panel sits beside. panel is a builder, because the panel can be re-homed: a compact window presents it inside a fullscreen sheet instead of a side pane, and each home builds it fresh in its own scope. The piece wraps the panel in a scroll on every path.
  • .width(pt) sets the pane’s preferred width (default 280). Where the native pane has a user-draggable divider this is the initial width, not a limit. The composed pane follows the width it splits: at 1000 points or more (a tablet in landscape, even beside a layers pane) it is at least 320, and it never takes more than two fifths of the split, so a narrow window keeps its content.
  • .sheet_done(label) names the compact sheet’s dismiss button (default ; day carries no “Done” of its own, so pass a localized one).
  • .edge(PaneEdge::Leading) puts the pane on the leading side of the content instead, for a utility pane like a layer panel (docs/tree.md) rather than a properties inspector. Default PaneEdge::Trailing. On AppKit the leading pane is a plain pinned split item (not the system inspector item, whose treatment is trailing-specific); GTK makes the panel the GtkPaned’s start child; Qt puts the splitter’s panel pane first (the panes are positional; day-qt maps roles onto positions at realize). The composed form mounts the pane before the content, and, unlike the trailing pane, it stays a side pane at every width rather than re-homing into the compact sheet, because a layer panel beside a narrow canvas works better than a fullscreen modal, which on iOS also detaches the presenting view from the window. Two inspectors nest; Day Sketch wraps its trailing-inspector editor in a leading layers pane.

Wide and compact

On a window wider than WidthClass::Compact the panel is a side pane. On a compact window there is no room for one, so while visible is true the panel presents as a fullscreen sheet instead, an unrouted cover, so it claims no route segments and a restored session never reopens a modal. The sheet carries its own dismiss button; the system back gesture dismisses it too, writing false straight back into the app’s signal. Resizing across the breakpoint re-homes the panel automatically in both directions, because the sheet’s open state is derived from visible and the window’s size class rather than stored anywhere.

A hidden composed pane reserves no width. While its panel is shown as a compact sheet, the underlying content also keeps the full width. This follows the visibility binding and the size class of the owning window, including when the window is resized.

The native desktop panes do not re-home on a narrow window; a desktop window dragged narrow squeezes its split, the same rule the nav sidebar follows.

Per-backend presentation

backendCap::Inspectorpane
macos-appkitNativeNSSplitViewItem inspector item (inspectorWithViewController:), full-height under the titlebar. The panel paints an opaque window-background backdrop over the item’s vibrancy material; the vibrant variants of the system fills only composite correctly in allowsVibrancy views, so grouped cards on the raw material came out near-black in dark mode (and materials capture black offscreen). Width pinned; visibility is Day’s alone (the item cannot be user-collapsed).
linux-gtk / macos-gtkNativeA GtkPaned with the panel as its end child (start for PaneEdge::Leading), each pane a filling DayCell so Day’s frames never become pane minimums; the divider is draggable down to 160pt and the panel keeps its dragged width across a hide and a reveal; visibility flips at once, no transition (the screenshot rule). Since 2026-09 — the AdwOverlaySplitView it replaced pinned the width by the GNOME idiom (docs/navigation.md records the decision).
linux-qt / macos-qtNativeThe nav QSplitter mirrored: content pane stretches, panel pane trailing at its preferred width, divider draggable. It is not a QDockWidget; DayWindow is a plain QWidget with hand-managed chrome, and dock areas need QMainWindow (the same trade the toolbar records in the shim).
windows-xamlNativeSplitView with PanePlacement=Right, DisplayMode=Inline: the pane sits beside the content, never over it.
ios-uikit, android-mdc, harmony-arkui, web-dom, mockUnsupportedThe piece composes the pane from plain containers (same panel, same signal, a divider but no drag) and presents the compact sheet described above.

On the native tier the INSPECTOR node’s two INSPECTOR_PANE children have native-owned frames: the split sizes each pane and reports it via Event::FrameChanged, and Day lays the pane’s content out inside the reported size, the nav-page contract (docs/navigation.md). A backend has to report on the split’s own layout passes, not only when content is inserted or the divider is dragged: at insert time the split usually has whatever geometry its constructor left it, and content laid out against those numbers stays that size until something else happens to resize the host. InspectorPatch::Visible shows and hides the pane without a rebuild; a native affordance hiding it (none of the current four has one) reports back as Event::InspectorChanged, which must never re-fire for a Day-driven patch (the from-native echo rule).

Extending the panel

The panel is ordinary Day content: form/section/labeled rows bound to the app’s model. Keep each property row’s binding small and selection-aware: read the common value of the selection, write through the model’s own commit path so an edit is one undo unit. Day-Sketch’s src/inspector.rs is the reference: a property table (label + get + set), each rendered as a labeled(text_field(...)) row, with a shared Binding<String> implementation that shows a multi placeholder when the selection disagrees and fans a typed value out to every selected node in one undo turn.