Merge pull request #5480 from deepseek-harness/worktree/input-command-concatenation-bug-5ed14e
fix(web): stop claim token decoration from looping on boundary input
This commit is contained in:
commit
bb0296a0e5
2 files changed
+33
-2
No files matched your search
@@ -43,9 +43,14 @@ export function registerClaimDecoration(editor: LexicalEditor, activeToken: () =
|
||||
}
|
||||
if (text.length > token.length) {
|
||||
// Typing at the token boundary lands in the styled node; split the
|
||||
// overflow back out so only the token itself carries the color.
|
||||
const [tokenNode] = node.splitText(token.length)
|
||||
// overflow back out so only the token itself carries the color. The
|
||||
// split copies the token style onto the overflow; it is cleared in this
|
||||
// pass because Lexical's normalization merges same-style neighbours
|
||||
// before the overflow's own transform runs, which would rejoin the two
|
||||
// halves and loop until Lexical aborts the update.
|
||||
const [tokenNode, overflow] = node.splitText(token.length)
|
||||
if (tokenNode !== undefined && tokenNode.getStyle() !== TOKEN_STYLE) tokenNode.setStyle(TOKEN_STYLE)
|
||||
overflow?.setStyle('')
|
||||
return
|
||||
}
|
||||
if (node.getStyle() !== TOKEN_STYLE) node.setStyle(TOKEN_STYLE)
|
||||
|
||||
@@ -12,6 +12,7 @@ import './control-row-dom.ts'
|
||||
import type { GlobalStandardProps } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { act, cleanup, fireEvent, render } from '@testing-library/react'
|
||||
import { $getRoot, $isElementNode, $isTextNode } from 'lexical'
|
||||
import type { SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'
|
||||
import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||
import { InputTriggerService } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
||||
@@ -344,6 +345,31 @@ describe('scenario H: backspace breaks the token', () => {
|
||||
expect(b.shell.snapshot.phase).toBe('plain')
|
||||
})
|
||||
|
||||
it('keeps text typed at the end of the claimed token when the browser inserts it natively', async () => {
|
||||
const b = await bench()
|
||||
b.type('/goal')
|
||||
await vi.waitFor(() => { expect(b.controller.menu.getSnapshot().open).toBe(true) })
|
||||
fireEvent.keyDown(b.textarea, { key: ' ', keyCode: 32 })
|
||||
expect(b.shell.snapshot.phase).toBe('claimed')
|
||||
b.type('/goal 发布')
|
||||
// A caret at the token/argument boundary sits at the end of the styled
|
||||
// token node; native insertion writes there and Lexical syncs the DOM
|
||||
// text with setTextContent plus a caret select.
|
||||
act(() => {
|
||||
b.shell.editor.update(() => {
|
||||
const paragraph = $getRoot().getFirstChild()
|
||||
const token = $isElementNode(paragraph) ? paragraph.getFirstChild() : null
|
||||
if (!$isTextNode(token)) throw new Error('missing token node')
|
||||
token.setTextContent('/goal 立即')
|
||||
token.select(8, 8)
|
||||
}, { discrete: true })
|
||||
})
|
||||
expect(b.shell.snapshot.draft).toBe('/goal 立即发布')
|
||||
expect(b.shell.snapshot.phase).toBe('claimed')
|
||||
act(() => { b.shell.editor.update(() => {}, { discrete: true }) })
|
||||
expect(b.view.container.querySelector('[data-lexical-text][style*="business-primary"]')?.textContent).toBe('/goal ')
|
||||
})
|
||||
|
||||
it('claim releases automatically; the enter after that goes through adjudication again', async () => {
|
||||
const b = await bench()
|
||||
b.type('/goal')
|
||||
|
||||
Reference in new issue
Block a user