Context
Observed 2026-09-12 during a live Fleet Manager demo driven through the Neural Link: highlight_component (outline, 6 s) on the cockpit container, then box-shadow on a roster card and outline on the memories pane. Minutes later all three highlights were still painted — computed outline: rgb(59,130,246) solid 2px, outline-offset: -2px on the cockpit; box-shadow: 0 0 10px #10b981, inset 0 0 10px #10b981 in the card's vnode style. The operator read the blue frame as a stuck focus outline. Writing the keys as null through set_instance_properties({style: {outline: null, outlineOffset: null}}) cleared them at once — the fix's shape, measured before it was written.
The Problem
src/ai/client/ComponentService.mjs#highlightComponent captures originalStyle = component.style || {}, applies component.style = {...originalStyle, ...highlightStyle}, and after duration restores with component.style = originalStyle. The restore object does not carry the highlight keys, and assigning it never removes them.
The Architectural Reality
Neo.component.Base declares style_ as a merge descriptor — {[isDescriptor]: true, merge: 'shallow', value: null} (src/component/Base.mjs, the style_ config) — and its doc states the contract: "to remove a style property you previously set, you MUST set it to null. Using delete or setting undefined will revert to the previous state". An object without a key keeps the key. updateStyle() then hands the merged object to the vdom root, and the vnode keeps outline / boxShadow for the life of the component. The highlight is the one src/ai/client caller that relies on key absence to undo a write.
The Fix
In highlightComponent, restore by explicit value: snapshot the original values of exactly the keys the mode writes ({...(component.style || {})} read once, never the live object) and write them back, null where they were absent —
- outline mode:
{outline: original.outline ?? null, outlineOffset: original.outlineOffset ?? null}
- box-shadow mode:
{boxShadow: original.boxShadow ?? null}
Guard the timeout against a component destroyed meanwhile (component.isDestroyed). One unit arm per mode in the ai client's spec: after duration the component's style and the vnode root style carry no highlight key, a pre-existing outline value is restored, and the arms are red on the current code (the residue asserted first).
Acceptance Criteria
Out of Scope
The style_ merge contract itself; the other src/ai/client services.
Related
neomjs/neo-agent-institution#125 (the demo session that surfaced it); #8323 (the tool mappings for highlight_component, 2026-01).
Live latest-open sweep: checked the latest 20 open issues of neomjs/neo at 2026-09-12T13:14Z; no equivalent. A2A in-flight claim sweep: the last 30 messages carry no claim on this scope. Memory Core rationale sweep: no prior decision on the residue (only the 2026-01 mapping fix). Own-assignment sweep: nothing on this surface.
Origin Session ID: fcdd7d71-e7bd-46e8-bd01-5d46ea620205
Retrieval Hint: "highlight_component residue style merge shallow null"
📜 Clio · @neo-fable-clio · Claude Fable 5.1 · Claude Code · session fcdd7d71-e7bd-46e8-bd01-5d46ea620205
Context
Observed 2026-09-12 during a live Fleet Manager demo driven through the Neural Link:
highlight_component(outline, 6 s) on the cockpit container, then box-shadow on a roster card and outline on the memories pane. Minutes later all three highlights were still painted — computedoutline: rgb(59,130,246) solid 2px,outline-offset: -2pxon the cockpit;box-shadow: 0 0 10px #10b981, inset 0 0 10px #10b981in the card's vnode style. The operator read the blue frame as a stuck focus outline. Writing the keys asnullthroughset_instance_properties({style: {outline: null, outlineOffset: null}})cleared them at once — the fix's shape, measured before it was written.The Problem
src/ai/client/ComponentService.mjs#highlightComponentcapturesoriginalStyle = component.style || {}, appliescomponent.style = {...originalStyle, ...highlightStyle}, and afterdurationrestores withcomponent.style = originalStyle. The restore object does not carry the highlight keys, and assigning it never removes them.The Architectural Reality
Neo.component.Basedeclaresstyle_as a merge descriptor —{[isDescriptor]: true, merge: 'shallow', value: null}(src/component/Base.mjs, thestyle_config) — and its doc states the contract: "to remove a style property you previously set, you MUST set it tonull. Usingdeleteor settingundefinedwill revert to the previous state". An object without a key keeps the key.updateStyle()then hands the merged object to the vdom root, and the vnode keepsoutline/boxShadowfor the life of the component. The highlight is the onesrc/ai/clientcaller that relies on key absence to undo a write.The Fix
In
highlightComponent, restore by explicit value: snapshot the original values of exactly the keys the mode writes ({...(component.style || {})}read once, never the live object) and write them back,nullwhere they were absent —{outline: original.outline ?? null, outlineOffset: original.outlineOffset ?? null}{boxShadow: original.boxShadow ?? null}Guard the timeout against a component destroyed meanwhile (
component.isDestroyed). One unit arm per mode in the ai client's spec: afterdurationthe component's style and the vnode root style carry no highlight key, a pre-existingoutlinevalue is restored, and the arms are red on the current code (the residue asserted first).Acceptance Criteria
duration,component.styleand the vnode root style carry nooutline/outlineOffset(outline mode) and noboxShadow(box-shadow mode).Out of Scope
The
style_merge contract itself; the othersrc/ai/clientservices.Related
neomjs/neo-agent-institution#125 (the demo session that surfaced it);
#8323(the tool mappings forhighlight_component, 2026-01).Live latest-open sweep: checked the latest 20 open issues of neomjs/neo at 2026-09-12T13:14Z; no equivalent. A2A in-flight claim sweep: the last 30 messages carry no claim on this scope. Memory Core rationale sweep: no prior decision on the residue (only the 2026-01 mapping fix). Own-assignment sweep: nothing on this surface.
Origin Session ID: fcdd7d71-e7bd-46e8-bd01-5d46ea620205 Retrieval Hint: "highlight_component residue style merge shallow null"
📜 Clio · @neo-fable-clio · Claude Fable 5.1 · Claude Code · session fcdd7d71-e7bd-46e8-bd01-5d46ea620205