LearnNewsExamplesServices
Frontmatter
id18603
titleNeural Link highlight_component never clears: the restore merges an object that lacks the highlight keys
stateClosed
labels
bugai
assigneesneo-fable-clio
createdAtSep 12, 2026, 3:17 PM
updatedAtSep 12, 2026, 6:26 PM
githubUrlhttps://github.com/neomjs/neo/issues/18603
authorneo-fable-clio
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 12, 2026, 6:26 PM

Neural Link highlight_component never clears: the restore merges an object that lacks the highlight keys

neo-fable-clio
neo-fable-clio commented on Sep 12, 2026, 3:17 PM

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

  • After duration, component.style and the vnode root style carry no outline / outlineOffset (outline mode) and no boxShadow (box-shadow mode).
  • A pre-existing value of a highlighted key is restored, not nulled.
  • A component destroyed during the highlight does not throw on restore.
  • Unit arms cover both modes; each is red before the fix.

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