feat(chat-ui): rich text in the agent prompt composer
What & why
The agent prompt composer (PromptInput) was plain text. It now supports bullets, numbered lists, quotes, code blocks, bold and italic, built from the same Lexical pieces as the channel composer, with no format toolbar.
- Typing shortcuts:
-/*/1./>/|/```/`code`. Bold and italic come only from ⌘B / ⌘I, so prompt text like*args,__init__and~/srcstays literal. - Keys: Shift+Enter continues a list; Tab / Shift+Tab nest. Enter adds a line inside a code block, and ⌘Enter still sends.
- Hotkeys: ⌘B / ⌘I no longer also toggle the sidebar and the terminal rich input. Underline is blocked, so ⌘U still attaches a file.
- Mentions:
@and/menus don't open inside code. - Paste: formatted text from a web page keeps its headings, links, lists, quotes and code blocks. A pasted table becomes markdown table rows (
| a | b |), since the composer has no table block. Plain text, such as code from an editor or markdown from a terminal, stays as typed. ⌘⇧V always pastes plain text, and 5,000+ characters still become apasted-N.txtattachment. - Copy: copying from the composer puts markdown in the clipboard's plain text, so copied bullets keep their
-markers in other apps. A copy inside one code block stays plain code. Pasting back into the composer still restores the real list. - Escape: keeps focus as before.
RichTextPluginwould otherwise blur the editor.
Drafts: the composer now saves drafts as its own Lexical state, so a draft reloads exactly as you left it (formatting, chips, nesting). ComposerDraftSegment gains an editor segment holding that state plus the markdown the agent would get. Drafts built outside the composer stay text (markdown: failed sends, /compact, setup and plugin prompts) or chip (linked issue and PR seeds). linkedContextSeed now returns chips only, so no draft mixes chips into markdown. draftChips reads chips from every segment kind, for linked context.
What the agent receives: markdown. Plain text goes out as typed, since Lexical's export would backslash-escape *_~. Formatted text keeps its edge spaces outside the markers (*x* y). Chips go out as their serialized text. Empty bullets at the start or end are dropped. ↑ history and failed-send restore parse that markdown back. Typed &#N; entities are protected, because Lexical's import decodes them and throws on out-of-range ones.
Your own messages: your bubble in the transcript now renders markdown, like the agent's replies. Chips still draw inside lists and formatting, typed line breaks stay, and copying a selection still gives back typed URLs. Typed text that only looks like markdown stays as typed: the composer writes formatting only as *, ** and ~~, so _, __ and single ~ show literally, and an unclosed * is not auto-closed (new complete option on ChatMarkdown). Older messages also render as markdown now.
Removed: the "Show in text field" action on the large-paste card. The paste still goes as pasted-N.txt.
Not in this PR:
- A pasted table shows as markdown rows in the composer, not as a drawn table. The agent gets the same markdown table, and your sent bubble draws it as a table.
- Editing a sent message still opens a plain text box showing its markdown.
- The new-workspace composer still has no slash commands. It never had any, and this PR doesn't change that.
Known gaps:
- Typing
1.inside a bullet doesn't switch that item to a numbered list (Lexical limitation).
How I tested it
- Unit tests:
- a composer draft reloads exactly, including mid-word italic and chips
- markdown text and chip seeds load correctly
- literal-looking text and typed entities go out as typed
- edge spaces and whitespace-only formatting keep markers balanced
- empty bullets are dropped
- history recall of markdown, including entries that read back differently
- In headless Chrome: I loaded the real
PromptInputthrough a temporary Storybook story (not committed) and drove it with CDP key events, with Chrome reporting a Mac platform:- 39 common cases pass, including:
- lists, nesting and leaving a list
- undoing a shortcut
- code blocks
- formatting a selection
- emoji and CJK
- plain and large paste
- copy and paste inside the composer, plus a control case showing the plain-text part alone flattens
- a full ↑↑↑ / ↓↓↓ history walk back to the draft
- Escape keeps focus
- ⌘Enter steer
- draft survives a remount (six shapes)
- 40 typed bullets
/commands- stand-in hotkey listeners for ⌘B, ⌘I and ⌘U
- 120 random key sequences (two seeds), each checking that the draft text and the editor's rendered HTML are unchanged after a remount, that nothing is sent by accident, and that the console stays clean. All pass.
- These runs found and fixed:
- backslash-escaped prompt text
- a lone
-being sent - Escape blurring the editor
- formatting lost on draft reload, which led to the switch to Lexical-state drafts
- 39 common cases pass, including:
- Paste, copy and the bubble, in headless Chrome: six paste samples (a web page section, VS Code code, a table, markdown from a terminal, markdown-looking text, a 120-item list) were pasted into the real
PromptInputthrough a temporary Storybook story, checking the editor's blocks and the markdown sent. Copy-out and copy-then-paste inside the composer were checked the same way. The sent bubble was screenshotted in Storybook with formatted, table and markdown-looking messages. - Not run in the full desktop app: there was no signed-in app in the cloud workspace. The hotkey checks used listeners bound the same way as
useHotkey, not the real sidebar and attachment hotkeys.
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintandbun run typecheckpass. Latest commits: chat-ui (71) and desktop chat, composer and new-workspace suites (1,355) pass; desktop and chat-ui type-check, Biome andsherifare clean. Biome is clean onpackages/chat-uiandpackages/i18n; typecheck passes forchat-uianddesktop;check:i18nandcheck:pluginspass; the chat-ui (66), i18n and desktop chat, composer and new-workspace suites (811) pass. - "Allow edits from maintainers" is checked on fork PRs (n/a)
- I have signed the CLA
Summary by CodeRabbit
- New Features
- The message composer supports rich-text formatting, including bold, italic, lists, quotes, and code blocks, with Markdown-style shortcuts while typing.
- Shift+Enter adds a new paragraph; Tab indents or outdents selected list items.
- Recalled prompts preserve list formatting, and restored draft content is appended to existing content.
- Improvements
- Pressing Enter submits a prompt unless text composition is in progress; Enter inside code blocks preserves code formatting.
- Pasted text from other sources is normalized; large pastes become attachments, while smaller pastes are inserted into the composer.
- Mention and command suggestions do not activate while typing inside code blocks.
- Drafts preserve formatting and chips when restored or combined.
- Removed
- The “Show in text field” action is no longer available on pasted-text attachment cards.