Node-edit screens carried two avatar-ish controls: a left node icon that
doubled as Back (screenCore.reset()) and a right-side bespoke
DropdownMenu add-child menu. Both diverged from ClientScreen’s polished
Avatar + SpeechBubble + NodeMenu callout, and the node icon was
redundant with the Overview-tab chip.
NodeMenu’s per-item action was hardcoded to screenCore.executeCommand,
so the shared Avatar + SpeechBubble + NodeMenu callout could not be
reused for the editor’s createChildAndEdit flow. The editor therefore
forked its own bespoke add-child DropdownMenu instead of reusing the
callout, and an unrelated node icon was overloaded as Back. Once the
Overview-tab chip began showing the node icon, that header icon became
pure redundancy — two divergent, non-shared affordances where one
reusable component belonged.
Introduced EditorAvatarMenu — a self-contained reuse of the ClientScreen
avatar callout with no FTUE/installId()/viaAvatar coupling — overlaid
at TopStart of both NodeEditorContainer variants. NodeMenu gained an
injectable onSelect (default = executeCommand, preserving ClientScreen;
editor passes createChildAndEdit). NodeHeader reduced to the title;
Back is now solely the bottom Cancel button.
When a “second” UI affordance grows next to a polished shared one,
parameterize the shared component (here: NodeMenu.onSelect) and reuse it
rather than forking a one-off. Keep host-specific concerns (FTUE/startup)
out of the reusable slice so it can be dropped into a new host cleanly.
Pure decision logic (editorAddChildItems) is extracted from the
composable so it stays unit-testable under the no-flaky-tests rules.