Explorar o código

fix(web): repair merge tails — todo-row selector and composer handler clone

The #921 selector change and the duplication gate both first ran against
this branch after the merge made it MERGEABLE: assembly-surfaces still
queried the retired data-sample="todo-row" hook (the composed TodoRow
carries ToolRow's data-tool attribute instead), and the redesigned
QuestionComposer duplicated the custom-draft onChange/onKeyDown pair
across its inline input and optionless textarea. The spec now anchors on
data-tool="todo_write", and the composer shares one draftCustom /
continueFromCustom handler pair (Enter continues, Shift+Enter stays a
newline, IME composition stays inert).
imccyu hai 2 meses
pai
achega
3c8cd4eca3

+ 2 - 2
packages/client/ui-conversation/tests/assembly-surfaces.spec.tsx

@@ -97,7 +97,7 @@ describe('todo_write assembly (product registrations, no outlet twins)', () => {
     const view = runtime.renderRoot()
     const view = runtime.renderRoot()
 
 
     // Keyed toolview registration took the row (summary derived from args).
     // Keyed toolview registration took the row (summary derived from args).
-    const row = view.container.querySelector('[data-sample="todo-row"]')
+    const row = view.container.querySelector('[data-tool="todo_write"]')
     expect(row).not.toBeNull()
     expect(row).not.toBeNull()
     expect(row!.textContent).toContain('1/3 已完成 · 实现 fixture 样本')
     expect(row!.textContent).toContain('1/3 已完成 · 实现 fixture 样本')
 
 
@@ -117,7 +117,7 @@ describe('todo_write assembly (product registrations, no outlet twins)', () => {
     await waitFor(() => {
     await waitFor(() => {
       expect(view.container.querySelector('[data-testid="todo-panel"]')).toBeNull()
       expect(view.container.querySelector('[data-testid="todo-panel"]')).toBeNull()
     })
     })
-    expect(view.container.querySelector('[data-sample="todo-row"]')).not.toBeNull()
+    expect(view.container.querySelector('[data-tool="todo_write"]')).not.toBeNull()
     await runtime.dispose()
     await runtime.dispose()
   })
   })
 })
 })

+ 22 - 25
packages/client/ui-question/src/client/QuestionComposer.tsx

@@ -1,4 +1,4 @@
-import { useMemo, useState, type KeyboardEvent } from 'react'
+import { useMemo, useState, type ChangeEvent, type KeyboardEvent } from 'react'
 import clsx from 'clsx'
 import clsx from 'clsx'
 import {
 import {
   Button, IconCheckOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
   Button, IconCheckOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
@@ -149,6 +149,23 @@ function QuestionFlow({ pending, t, useLocale }: {
     submitDrafts(drafts)
     submitDrafts(drafts)
   }
   }
 
 
+  // Shared by the inline custom input and the optionless textarea: typing a
+  // custom draft clears any selection, and Enter continues the flow
+  // (Shift+Enter stays a newline in the textarea; on the single-line input it
+  // is inert either way).
+  const draftCustom = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
+    const value = event.target.value
+    updateDraft(current => ({
+      ...current, selected: [], custom: value, skipped: false,
+    }))
+  }
+
+  const continueFromCustom = (event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
+    if (event.key !== 'Enter' || event.shiftKey || isComposing(event)) return
+    event.preventDefault()
+    continueFlow()
+  }
+
   const skipQuestion = (): void => {
   const skipQuestion = (): void => {
     const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index
     const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index
       ? { selected: [], custom: '', skipped: true }
       ? { selected: [], custom: '', skipped: true }
@@ -247,18 +264,8 @@ function QuestionFlow({ pending, t, useLocale }: {
                     value={draft.custom}
                     value={draft.custom}
                     disabled={busy !== null}
                     disabled={busy !== null}
                     placeholder={t('custom.placeholder')}
                     placeholder={t('custom.placeholder')}
-                    onChange={(event) => {
-                      const value = event.target.value
-                      updateDraft(current => ({
-                        ...current, selected: [], custom: value, skipped: false,
-                      }))
-                    }}
-                    onKeyDown={(event) => {
-                      if (event.key === 'Enter' && !isComposing(event)) {
-                        event.preventDefault()
-                        continueFlow()
-                      }
-                    }}
+                    onChange={draftCustom}
+                    onKeyDown={continueFromCustom}
                   />
                   />
                 </div>
                 </div>
               )
               )
@@ -270,18 +277,8 @@ function QuestionFlow({ pending, t, useLocale }: {
                   disabled={busy !== null}
                   disabled={busy !== null}
                   rows={2}
                   rows={2}
                   placeholder={t('custom.placeholder')}
                   placeholder={t('custom.placeholder')}
-                  onChange={(event) => {
-                    const value = event.target.value
-                    updateDraft(current => ({
-                      ...current, selected: [], custom: value, skipped: false,
-                    }))
-                  }}
-                  onKeyDown={(event) => {
-                    if (event.key === 'Enter' && !event.shiftKey && !isComposing(event)) {
-                      event.preventDefault()
-                      continueFlow()
-                    }
-                  }}
+                  onChange={draftCustom}
+                  onKeyDown={continueFromCustom}
                 />
                 />
               )}
               )}
           </div>
           </div>