Skip to content

feat: add code block support to message composer#788

Merged
wpfleger96 merged 3 commits into
mainfrom
worktree-wpfleger+code-block-toolbar
May 29, 2026
Merged

feat: add code block support to message composer#788
wpfleger96 merged 3 commits into
mainfrom
worktree-wpfleger+code-block-toolbar

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Adds Code Block formatting support to the desktop and mobile message composers — both as a toolbar button and as a ``` input trigger that creates WYSIWYG code blocks inline.

The formatting toolbars already had inline code buttons, but creating a multi-line code block required manually typing fenced markdown syntax that only rendered after sending. On desktop, Tiptap's built-in CodeBlock input rule (``` + Space) existed but was blocked in two ways: submitOnEnter intercepted Enter before the rule could fire, and the rule's regex only matched at the start of a paragraph — not after a hard break mid-message. Separately, single-line code blocks without a language specifier rendered with both code-block and inline-code styling.

  • Adds SquareCode toolbar button on desktop (FormattingToolbar.tsx) and mobile (compose_bar.dart) positioned after inline Code, with toggleCodeBlock() and triple-backtick wrapping respectively
  • Fixes Shift+Enter inside code blocks on desktop — inserts a literal \n text character since hardBreak nodes aren't valid in ProseMirror code block schema
  • Extracts codeBlockExtensions.ts with: a fence detector in submitOnEnter that yields to the existing input rule (start of paragraph) or splits-and-converts directly (after hard break), plus a custom InputRule for the Space trigger after hard breaks
  • Fixes the CodeBlockAfterHardBreak InputRule regex to use \n instead of U+FFFC — Tiptap's InputRule engine represents hard breaks via renderText() which returns \n, not the object replacement character
  • Fixes single-line code block rendering in markdown.tsx — detects fenced blocks by checking for a trailing \n in the raw children string (react-markdown always appends one to fenced block content, never to inline code)

The formatting toolbars on desktop and mobile had buttons for inline
code but no way to insert code blocks without typing triple backticks
manually. Adds a Code Block button (SquareCode icon) to both platforms,
positioned after the inline Code button.

Also fixes Shift+Enter inside code blocks on desktop — hardBreak nodes
aren't valid inside ProseMirror code block nodes, so the smartShiftEnter
handler now inserts a literal \n text character instead of falling
through to the hardBreak extension.
submitOnEnter intercepted all Enter key presses, preventing Tiptap's
CodeBlock input rule from ever firing. The rule also only matched at the
start of a paragraph, so typing ``` after Shift+Enter (a hard break
mid-message) never triggered it.

Extracts code block extensions into codeBlockExtensions.ts: a fence
detector in submitOnEnter that yields to the existing input rule (start
of paragraph) or splits-and-converts directly (after hard break), plus
a custom InputRule for the Space trigger after hard breaks. Also removes
the non-functional ⌘⌥C shortcut from the Code Block toolbar tooltip.
@wpfleger96 wpfleger96 changed the title feat: add code block button to formatting toolbars feat: add code block support to message composer May 29, 2026
…rendering

The CodeBlockAfterHardBreak InputRule regex used U+FFFC to match hard
breaks, but Tiptap's InputRule engine represents them as \n via
renderText(). Single-line code blocks without a language specifier
fell through to inline-code styling because neither the language-class
check nor the newline-in-content check caught them.
@wesbillman

Copy link
Copy Markdown
Collaborator

@wpfleger96 when you have a sec, can you add some screenshots for this one to see what this change will look like?

@wpfleger96

wpfleger96 commented May 29, 2026

Copy link
Copy Markdown
Collaborator Author

New code block toolbar icon
image

triple backtick + [space|enter] now immediately creates a WYSIWYG code block
image
image

Old behavior: triple backtick-wrapped code blocks showed literal backtick characters in the chat compose window, and didn't get properly rendered in a code block until the message is actually sent
image
image

@wpfleger96

Copy link
Copy Markdown
Collaborator Author
image

@wpfleger96 wpfleger96 marked this pull request as ready for review May 29, 2026 17:50
@wpfleger96 wpfleger96 requested a review from a team as a code owner May 29, 2026 17:50
@wpfleger96

Copy link
Copy Markdown
Collaborator Author
image

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

@wesbillman this is good to go now!

@wpfleger96 wpfleger96 enabled auto-merge (squash) May 29, 2026 17:55
@wpfleger96 wpfleger96 merged commit 85861f3 into main May 29, 2026
15 checks passed
@wpfleger96 wpfleger96 deleted the worktree-wpfleger+code-block-toolbar branch May 29, 2026 18:38
tlongwell-block pushed a commit that referenced this pull request May 29, 2026
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
wpfleger96 added a commit that referenced this pull request Jun 1, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 1, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 1, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 2, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 2, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 2, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
wpfleger96 added a commit that referenced this pull request Jun 2, 2026
…ence

Desktop's code block support (PR #788) landed with a toolbar button but
no equivalent rendering or composer UX on mobile. The sent messages used
gpt_markdown's default CodeField with hard-coded Material styling that
didn't match the design system.

Adds _MessageCodeBlock (via gpt_markdown's codeBuilder callback) with
rounded border, muted bg, GeistMono font, horizontal scroll, optional
language label, and an always-visible copy-to-clipboard button. Also adds
_expandFenceIfNeeded to auto-expand ```lang + Enter into a full fenced
block template in the composer. A useRef isModifyingText guard prevents
the controller listener from re-entering _expandFenceIfNeeded while
applyCodeBlock or the expander itself is mutating the controller, which
was causing a StackOverflow crash.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants