Every node editor was a single vertical scroll where type-specific fields,
metadata, and the shared source-wiring widget were stacked together. The
TargetingWiringEditor was appended mid/bottom of ~24 editors with no
explanation of what sources or verbs (EXECUTE/RESET) mean, so users could
not grok what a node does, how it observes sources, or what it does when
fired.
Not a bug — a design gap. Wiring UI was duplicated inline across every
editor (each with its own meta.copy(sources = it) callbacks), so there
was no single place to add explanation or restructure layout.
NodeEditorContainer now hosts a consistent 3-tab layout (Overview /
Settings / Sources) with the header and Save/Cancel kept outside the tabs.
The Overview/Settings split is done in one place, not per-editor: the
router’s type when was extracted into a single NodeFace(node, viewMode)
composable, and the Overview tab renders each type’s existing ROW face
(its at-a-glance live state) while the Settings tab renders the existing
EDIT/VIEW editor form (metadata editing). Reusing the per-type
representations that already existed avoided bespoke field re-categorization
across 24 editors and the regression risk that carried.
The Sources tab is fully centralized in a new SourcesTab composable: it
renders a fixed sources/verbs preamble plus per-type prose pulled from the
existing KrillFeature JSON fields (llmPurpose, llmBehavior,
description — no cross-repo schema change), then the shared
TargetingWiringEditor for TargetingNodeMetaData nodes (or a
“does-not-observe-sources” notice otherwise). Wiring writes go through one
new generic helper, NodeMetaData.withWiring(...), mirroring the existing
withParentSourceDefault per-subtype copy dispatch. The inline
TargetingWiringEditor call was deleted from all 24 editors; a tab-less
NodeEditorContainer(viewMode, content) overload was kept for node-less
setup flows (Add Server / Connect Peer).
Centralizing the wiring write path created a latent clobber risk: an
editor’s local-meta LaunchedEffect persistence vs. the Sources tab’s
edited() write. This is structurally prevented because the tab container
composes only the selected tab, so the Overview editor and the Sources tab
never coexist in composition — switching tabs re-seeds each editor’s
remember from live state. Regression guards: NodeMetaDataWithWiringTest
(round-trips all three wiring fields per family, preserves unrelated
fields) and SourcesExplanationTest (pure buildSourcesExplanation
mapping incl. the non-targeting branch). withWiring and
withParentSourceDefault must stay in lockstep — both carry an explicit
comment saying any new TargetingNodeMetaData subtype must be added to
both when arms or its wiring silently becomes uneditable.