fix(web): flush the intent draft into the list snapshot in the same tick

The hero composer renders the frontend Session Intent's retained prompt
from the sessions list snapshot, but updateIntent only reached that
snapshot through the intent watch's microtask-deferred markDirty. React
therefore rolled the controlled textarea back during the change tick,
which corrupted IME composition (Pinyin "nihao" committed fragments
like "nnini hni hani hao你好") and jumped the caret on plain typing.

SessionManager.updateIntent now calls notifyNow after
updatePendingPrompt, per the Notifier channel rule for direct echoes of
user gestures. The workspace-flow snapshot helper asserts the same-tick
echo instead of waiting for it, and a runtime unit test pins the
contract at the manager seam.
This commit is contained in:
Chinesezjc
2026-07-26 02:21:58 +08:00
parent 6f50208ea3
commit 50cf90bf6e
6 changed files with 106 additions and 9 deletions
@@ -162,7 +162,14 @@ export class SessionManager {
* @param text - exact controlled-input value for the active frontend Session.
*/
updateIntent(text: string): void {
this.getIntent()?.updatePendingPrompt(text)
const session = this.getIntent()
if (session === undefined) return
session.updatePendingPrompt(text)
// The intent watch defers via markDirty, but the hero composer reads this
// prompt from the LIST snapshot as a controlled value: it must flush in
// the same tick as onChange (see Notifier.notifyNow) or React rolls the
// textarea back and IME composition breaks.
this.notifier.notifyNow()
}
private discardIntent(): void {