Problem
The rule for the composer link dialog says every path must keep the soft keyboard as it was: "open stays open, closed stays closed" (extensions/CLAUDE.md:68). It names the preview tap, edit and add open, Cancel, and dismiss. The code trace shows two paths that break it. The refocus paths may break too; see Notes.
On 14ab7f9c1. Paths are under apps/webapp/src/components/chatroom/components/MessageComposer/.
Add and edit open. The editor dialog focuses its URL field on every open (components/ComposerLinkDialog/ComposerLinkEditorDialog.tsx:39-49). It only delays the focus when the dialog came from a preview while the keyboard was closed. Add open focuses the field during the same tap, so a closed keyboard opens. Edit from a preview focuses after 50 ms. A device check must show whether that opens the keyboard.
Cancel from an edit that came from a preview. cancel() returns to the preview phase (stores/composerLinkDialogStore.ts:181-184). That unmounts the focused URL field and focuses nothing. On a phone, removing the focused field closes the keyboard. So an open keyboard does not stay open. Back from that edit runs the same return to the preview in handleComposerLinkDialogPopState (stores/composerLinkDialogStore.ts:66-74).
Decide first. The first path may be intended, because a user who adds a link will type a URL. If so, change the rule to say that add and edit open focus the URL field. Say which on this issue before the fix is merged.
Steps to reproduce
- On a phone, open a channel and type a message that contains a link.
- With the keyboard open, tap the link, then choose Edit.
- Choose Cancel.
- See the keyboard close.
Acceptance criteria
Agent Brief
Category: bug
Summary: Make the composer link dialog keep the keyboard state on every path, or change the rule where focus is intended.
Current behavior:
Add open focuses the URL field during the same tap, which opens a closed keyboard. Edit from a preview focuses it after a short delay. Cancel from an edit that came from a preview unmounts the focused field, which closes an open keyboard.
Desired behavior:
Each dialog path keeps the keyboard state it had when the dialog opened, except where the rule is changed on purpose. The rule and the code agree.
Key interfaces:
- The composer link dialog store —
openCreate, openEdit, openEditFromPreview, cancel, and keyboardWasOpenAtOpen.
ComposerLinkEditorDialog — the URL field focus on open.
ComposerLinkModalShell — the focusin trap that moves outside focus to the card's first control. The Cancel fix must let focus stay in the composer while the preview shows.
handleComposerLinkDialogPopState — back from an edit that came from a preview returns to the preview, the same as Cancel.
- The keyboard-state rule for the composer link dialog in the extensions agent doc.
Out of scope
- The pad hyperlink popovers.
- The dialog's look and motion.
Notes
When the dialog closes, AnimatePresence keeps the shell mounted during its exit tween (components/ComposerLinkDialog/ComposerLinkDialogHost.tsx:48). On close, the store's refocus calls editor.commands.focus() on the composer. On iOS and Android, TipTap calls view.dom.focus() at once, then view.focus() in the next frame. Both calls run while the shell's focusin trap is still active (components/ComposerLinkDialog/ComposerLinkModalShell.tsx:45-50). Check where focus lands after the exit tween.
The preview shell's trap also moves composer focus to the first card button (components/ComposerLinkDialog/ComposerLinkModalShell.tsx:45-50). A change in the store alone cannot keep the keyboard open on Cancel.
The evidence is a code trace on 14ab7f9c1, confirmed by two verifiers. Keyboard behavior needs a check on iOS Safari and Android Chrome.
Problem
The rule for the composer link dialog says every path must keep the soft keyboard as it was: "open stays open, closed stays closed" (
extensions/CLAUDE.md:68). It names the preview tap, edit and add open, Cancel, and dismiss. The code trace shows two paths that break it. The refocus paths may break too; see Notes.On
14ab7f9c1. Paths are underapps/webapp/src/components/chatroom/components/MessageComposer/.Add and edit open. The editor dialog focuses its URL field on every open (
components/ComposerLinkDialog/ComposerLinkEditorDialog.tsx:39-49). It only delays the focus when the dialog came from a preview while the keyboard was closed. Add open focuses the field during the same tap, so a closed keyboard opens. Edit from a preview focuses after 50 ms. A device check must show whether that opens the keyboard.Cancel from an edit that came from a preview.
cancel()returns to the preview phase (stores/composerLinkDialogStore.ts:181-184). That unmounts the focused URL field and focuses nothing. On a phone, removing the focused field closes the keyboard. So an open keyboard does not stay open. Back from that edit runs the same return to the preview inhandleComposerLinkDialogPopState(stores/composerLinkDialogStore.ts:66-74).Decide first. The first path may be intended, because a user who adds a link will type a URL. If so, change the rule to say that add and edit open focus the URL field. Say which on this issue before the fix is merged.
Steps to reproduce
Acceptance criteria
extensions/CLAUDE.mdstates whether add and edit open focus the URL field.Agent Brief
Category: bug
Summary: Make the composer link dialog keep the keyboard state on every path, or change the rule where focus is intended.
Current behavior:
Add open focuses the URL field during the same tap, which opens a closed keyboard. Edit from a preview focuses it after a short delay. Cancel from an edit that came from a preview unmounts the focused field, which closes an open keyboard.
Desired behavior:
Each dialog path keeps the keyboard state it had when the dialog opened, except where the rule is changed on purpose. The rule and the code agree.
Key interfaces:
openCreate,openEdit,openEditFromPreview,cancel, andkeyboardWasOpenAtOpen.ComposerLinkEditorDialog— the URL field focus on open.ComposerLinkModalShell— thefocusintrap that moves outside focus to the card's first control. The Cancel fix must let focus stay in the composer while the preview shows.handleComposerLinkDialogPopState— back from an edit that came from a preview returns to the preview, the same as Cancel.Out of scope
Notes
When the dialog closes,
AnimatePresencekeeps the shell mounted during its exit tween (components/ComposerLinkDialog/ComposerLinkDialogHost.tsx:48). On close, the store'srefocuscallseditor.commands.focus()on the composer. On iOS and Android, TipTap callsview.dom.focus()at once, thenview.focus()in the next frame. Both calls run while the shell'sfocusintrap is still active (components/ComposerLinkDialog/ComposerLinkModalShell.tsx:45-50). Check where focus lands after the exit tween.The preview shell's trap also moves composer focus to the first card button (
components/ComposerLinkDialog/ComposerLinkModalShell.tsx:45-50). A change in the store alone cannot keep the keyboard open on Cancel.The evidence is a code trace on
14ab7f9c1, confirmed by two verifiers. Keyboard behavior needs a check on iOS Safari and Android Chrome.