Description
ChatComposerTrigger declares:
/**
* Parse serialized tokens back into rendered tokens.
* Used when loading a previous message for editing.
*/
deserialize?: (value: string) => ChatComposerToken | null;
Nothing calls it. Code search for deserialize in this repo returns two files, and in packages/core/src/Chat/ChatComposerInput.tsx the only occurrence is the declaration itself (line 139 on main). useTriggerMenu.tsx never references it.
The effect that syncs the controlled value does this:
// ChatComposerInput.tsx:420 (main)
editable.textContent = controlledValue;
That assignment removes every [data-astryx-token] span, and cleanupPortals() then drops the matching portals. A token is a DOM span plus a React portal, and the value string carries neither, so there is nothing left to rebuild from. Any genuine external value update turns every inline chip into its raw serialized text.
The paths that hit this are ordinary ones: session switching, draft restore, prompt-history recall, and the "loading a previous message for editing" case named in the prop's own doc comment.
It also defeats a feature that is on by default. pasteAsToken collapses a paste over 200 characters into a { value: <the whole text>, label: "N lines, M chars" } badge. After an external value write that badge is gone and the full pasted text sits in the input, which is the flooding the feature was added to stop.
Expected: after an external value update, serialized tokens render as tokens again. That is what deserialize is for. The paste token needs an equivalent, since it belongs to no trigger.
Actual: they become plain text and stay that way. Retyping is the only way back.
Reproduction
Default configuration, no custom trigger:
function Demo() {
const [drafts, setDrafts] = useState({ a: '', b: '' });
const [active, setActive] = useState<'a' | 'b'>('a');
return (
<>
<ChatComposerInput
value={drafts[active]}
onChange={(v) => setDrafts((d) => ({ ...d, [active]: v }))}
/>
<button onClick={() => setActive('b')}>draft B</button>
<button onClick={() => setActive('a')}>draft A</button>
</>
);
}
- Focus the input and paste about 500 characters. It collapses to a
500 chars badge.
- Click "draft B", then "draft A".
- The badge is gone and all 500 characters are in the input.
The same three steps with a triggers entry whose onSelect returns a ChatComposerToken lose the chip the same way, with or without deserialize supplied.
Astryx Version
@astryxdesign/core@0.2.0, also checked against current main.
Environment
Electron 43.1.1, macOS 15. Nothing here is engine-specific; the loss happens in the textContent assignment.
Related
#2473 (ChatComposerInput internal-authoritative state model) is aimed at this exact line and this exact tension. Its API sketch is text-only (setValue / insertText / getText), so as written it would move the string write without bringing tokens back. Token round-tripping belongs inside that state model rather than in a patch after it. Filing this separately so the constraint is recorded either way.
Description
ChatComposerTriggerdeclares:Nothing calls it. Code search for
deserializein this repo returns two files, and inpackages/core/src/Chat/ChatComposerInput.tsxthe only occurrence is the declaration itself (line 139 onmain).useTriggerMenu.tsxnever references it.The effect that syncs the controlled value does this:
That assignment removes every
[data-astryx-token]span, andcleanupPortals()then drops the matching portals. A token is a DOM span plus a React portal, and thevaluestring carries neither, so there is nothing left to rebuild from. Any genuine externalvalueupdate turns every inline chip into its raw serialized text.The paths that hit this are ordinary ones: session switching, draft restore, prompt-history recall, and the "loading a previous message for editing" case named in the prop's own doc comment.
It also defeats a feature that is on by default.
pasteAsTokencollapses a paste over 200 characters into a{ value: <the whole text>, label: "N lines, M chars" }badge. After an externalvaluewrite that badge is gone and the full pasted text sits in the input, which is the flooding the feature was added to stop.Expected: after an external
valueupdate, serialized tokens render as tokens again. That is whatdeserializeis for. The paste token needs an equivalent, since it belongs to no trigger.Actual: they become plain text and stay that way. Retyping is the only way back.
Reproduction
Default configuration, no custom trigger:
500 charsbadge.The same three steps with a
triggersentry whoseonSelectreturns aChatComposerTokenlose the chip the same way, with or withoutdeserializesupplied.Astryx Version
@astryxdesign/core@0.2.0, also checked against currentmain.Environment
Electron 43.1.1, macOS 15. Nothing here is engine-specific; the loss happens in the
textContentassignment.Related
#2473 (
ChatComposerInputinternal-authoritative state model) is aimed at this exact line and this exact tension. Its API sketch is text-only (setValue/insertText/getText), so as written it would move the string write without bringing tokens back. Token round-tripping belongs inside that state model rather than in a patch after it. Filing this separately so the constraint is recorded either way.