Skip to content

sparkles:ui inspector — Feature Requirements (INS)

Status: partial · Date: 2026-08-11 · Scope: the generic inspector component (components/inspector.d) — an interactive tree of nodes over a subject, with a details pane for the selected node and a selection contract that lets the host highlight the selected node's extent in the subject — plus its adapters, starting with the toolkit's own widget tree.

Design & rationale

Every structure inspector shares one shape: Chromium's Elements panel, Visual Studio's Live Visual Tree, and neovim's :InspectTree are each a tree pane beside a subject, where moving through the tree highlights the corresponding extent in the subject, selecting a node answers questions about it, and the whole thing is a debugging aid that must never perturb what it inspects. The component states that shape once; everything subject-specific is an adapter:

  • The tree is the shared interactive component (VMD7) — cursor, disclosure, viewport, filter — over a TreeData whose node type carries the view's DbI capabilities (label, slot, badge, …). The inspector adds no second tree.
  • The selection contract is one value: the host reads the tree's selected node and asks its adapter what extent it covers — a layout rect for a widget tree, a byte range for a syntax tree. The component never learns what an extent is; the host interprets it (tint a rect, tint a source range).
  • The details pane is an adapter capability by presence: an adapter with details(node) gets a pane; one without simply has none (neovim parity — its inspector is tree-only, the details riding in each line).
  • The header carries a title and host-supplied toggle actions as data (label + active + hit id) — the picker, anonymous nodes, whatever the host binds — so the panel's chrome needs no per-host view code. The chips are buttons: the component answers which one an x lands on, from the same layout rule it paints them with.
  • The sync contract is host-owned and asymmetric, following DevTools: tree selection → subject extent highlight is always live (with scroll-follow only when the extent is fully off-screen — the detail that makes neovim's panel pleasant instead of jumpy), while subject position → tree selection is a mode the reader arms, the header's picker chip. A permanently bidirectional binding cannot express "I have chosen this node": the next mouse move over the subject would take the selection away again. So the picker ends on the click that chooses (INS9).

The north star is DevTools-grade genericity: a user should eventually be able to build an inspector over any tree-shaped third-party system (an HTML app, a scene graph, a remote process). What that adds is an asynchronous / remote node provider; v1 is deliberately synchronous and in-process, with the lazy-children seam (VMD5) as the named extension point — the API must not bake in "the whole tree is in memory" beyond that seam.

Deferred by decision: the inspect-from-context-menu entry (right-click / long-press a position in a subject → "Inspect node") waits on the anchored-overlay primitive (docs/specs/ui/popup.md, in research); until it lands, hosts bind inspect-at-position through their keymaps.

Requirements (INS)

IDRequirementStatusTraces to
INS1The inspector must be one component — header (title + host-supplied toggle actions as data) · the shared interactive tree (VMD7) · an optional details pane — rendered as a fixed-width, clip-guarded column an embedding pane can host.full (72765ef6)components/inspector.d inspectorView, InspectorAction
INS2The selection contract must be adapter-defined: the host reads the selected tree node and resolves its extent through the adapter; the component itself never names an extent type — that is what lets one component serve layout rects and byte ranges alike.full (72765ef6)WidgetInspect.extentOf (rect); a syntax adapter's byte range (hue TSI)
INS3The details pane must be an adapter capability by presence (details(node) → key/value rows), never a required interface; a tree-only adapter renders a tree-only panel.full (72765ef6)DetailRow; WidgetInspect.details
INS4A widget-tree adapter must ship with the toolkit, so any sparkles:ui application can inspect its own laid-out widget tree — kind + key labels, resolved-size badges, details (kind/rect/text/key/hit/slot) answered from the same frames the subject painted with.full (72765ef6) — the gallery's | panel is the first mount (756e4643)inspectWidgets, WidgetInspectNode; apps/ui-gallery/src/inspector.d
INS5A plain-text target must render the same flattened rows as guide-railed indented text (logging, goldens, non-interactive sinks); an adapter may define a richer serialization of its own (the tree-sitter inspector emits neovim-compatible query syntax).full (72765ef6)writeTreeText / treeText; hue TSI's S-expression emitter (planned)
INS6The sync contract: tree selection drives an extent highlight in the subject, scroll-following only when the extent is fully off-screen; subject position drives tree selection while the picker is armed (INS9).full (8dc7128c) — hue's tree-sitter inspector: extent tint + off-screen-only scroll-follow via the viewer's identity channel, hover-driven selection on both backends (dedup per pointer move)host wiring over INS1/INS2; hue foldAtCursor idiom
INS7Inspect-from-context-menu: with the inspector closed, a context action on a subject position opens it and reveals the node at that position.deferred — waits on the anchored-overlay primitive (popup.md, in research); keymap-bound inspect lands firstfuture popup.md menu; host keymaps
INS8The node-provider seam must admit asynchronous / remote subjects (DevTools over another process) without changing consumers; v1 is synchronous and in-process by decision, with lazy children (VMD5) as the extension point.researched — a design constraint on INS1INS3, not yet an implementationVMD5 lazy provider; future remote adapter
INS9The subject→tree direction must be a picker mode, not a permanent binding: a header chip arms it, hovering the subject then walks the tree live, and a click in the subject disarms it, pinning the node the reader chose. Opening the panel starts one-way (tree→subject); closing it ends all sync. Header chips must be clickable, hit-tested through the component's own header geometry rather than per-host guesses.full — hue's chip on both backends, from a UAT round on the two-way version (actionAt; the pane's s key is the same toggle)actionAt; hue InspectorPane.picking

Milestones

MilestoneScopeStatusRequirements
N0The component + the widget-tree adapter + the gallery panel mountfull (756e4643)INS1INS5
N1The sync contract, proven by hue's tree-sitter inspector (TSI / TVU2)fullINS6, INS9
N2Context-menu entry (after the anchored-overlay primitive)deferredINS7
N3Async/remote providerdeferredINS8

Module coverage

Source fileRequirements
libs/ui/src/sparkles/ui/components/inspector.dINS1INS5, INS9 (actionAt)
apps/ui-gallery/src/inspector.dthe first mount (INS4); UGL21

Relationship to existing specs

PieceRole
widgets.md WGT12/VMD1VMD7the tree component the inspector composes
hue tree-view.md TVU2the tree-sitter inspector — the component's second adapter
hue overlays.md TSIwhat that adapter must show (node type, field, extent, S-expression)
popup.md (in research)the anchored-overlay primitive behind INS7's context menu
ui-gallery UGL21the shell panel hosting the first mount

Overview · Widgets · Containers