Problem
The mobile format grid does not update its active state while the user types or moves the caret. A tapped Bold button can stay unmarked. A button can stay marked after the caret leaves bold text.
The cause, traced on 14ab7f9c1, is below. Paths are under apps/webapp/src/components/chatroom/components/MessageComposer/.
- Nine of the ten format buttons compute
editor.isActive(type) only when they render (components/ui/Button.tsx:39). The Mention button reads its own store and is not affected.
- The editor runs with
shouldRerenderOnTransaction: false, so a re-render must come from somewhere else.
- The desktop row gets it from
useReRenderOnEditorTransaction (components/layouts/FormattingToolbar.tsx:36).
- The mobile grid,
ComposerFormatPanel, has no such subscription (components/layouts/ComposerFormatPanel.tsx:10-31).
- Its other triggers are the composer context and re-renders of its parent,
ComposerBar. None of them follows the selection. The context changes on reply, edit, comment, canSend, and a few flags (MessageComposer.tsx:248-280). By house rule it must stay that way (apps/webapp/src/components/chatroom/CLAUDE.md:73).
The desktop row has the reverse defect. FormattingToolbar subscribes before its early return. On desktop it re-renders after editor transactions, at most about once per 50 ms, while the row is hidden. On mobile it does the same while it always renders null.
The mobile grid lost the subscription in 3e08d6a71 (2026-07-01), when phones moved from FormattingToolbar to ComposerFormatPanel. The chatroom rules still say "FormattingToolbar must call useReRenderOnEditorTransaction(editor)" (apps/webapp/src/components/chatroom/CLAUDE.md:78).
Steps to reproduce
- On a phone, open a channel and focus the composer.
- Type
plain so the composer is not empty.
- Open Text formatting from the insert menu.
- Tap Bold. See the button stay unmarked.
- Type
bold. See the Bold button stay unmarked, though the new text is bold.
- Move the caret into
plain, then back into bold. See no change on the Bold button.
Acceptance criteria
Agent Brief
Category: bug
Summary: Move the transaction subscription into the component that renders the format buttons, and keep it only while that UI is open.
Current behavior:
The desktop row subscribes to editor transactions even when hidden, and even on mobile, where it renders nothing. The mobile grid never subscribes, so its active states are stale.
Desired behavior:
The format buttons re-render on editor transactions on both platforms, and only while the format UI is open. This replaces the chatroom rule that FormattingToolbar must call useReRenderOnEditorTransaction. That rule has been wrong for phones since the mobile grid moved out of FormattingToolbar. Update the rule in the same change.
Key interfaces:
useReRenderOnEditorTransaction(editor) — the throttled transaction subscription.
FormattingToolbar and ComposerFormatPanel — the desktop row and the mobile grid, both mounted by ComposerBar.
- The composer
ToolbarButton — reads editor.isActive() at render.
Out of scope
Notes
The evidence is a code trace on 14ab7f9c1. It was not checked on a device. Confirm steps 4 and 5 on iOS Safari and Android Chrome, before and after the fix.
Problem
The mobile format grid does not update its active state while the user types or moves the caret. A tapped Bold button can stay unmarked. A button can stay marked after the caret leaves bold text.
The cause, traced on
14ab7f9c1, is below. Paths are underapps/webapp/src/components/chatroom/components/MessageComposer/.editor.isActive(type)only when they render (components/ui/Button.tsx:39). The Mention button reads its own store and is not affected.shouldRerenderOnTransaction: false, so a re-render must come from somewhere else.useReRenderOnEditorTransaction(components/layouts/FormattingToolbar.tsx:36).ComposerFormatPanel, has no such subscription (components/layouts/ComposerFormatPanel.tsx:10-31).ComposerBar. None of them follows the selection. The context changes on reply, edit, comment,canSend, and a few flags (MessageComposer.tsx:248-280). By house rule it must stay that way (apps/webapp/src/components/chatroom/CLAUDE.md:73).The desktop row has the reverse defect.
FormattingToolbarsubscribes before its early return. On desktop it re-renders after editor transactions, at most about once per 50 ms, while the row is hidden. On mobile it does the same while it always rendersnull.The mobile grid lost the subscription in
3e08d6a71(2026-07-01), when phones moved fromFormattingToolbartoComposerFormatPanel. The chatroom rules still say "FormattingToolbarmust calluseReRenderOnEditorTransaction(editor)" (apps/webapp/src/components/chatroom/CLAUDE.md:78).Steps to reproduce
plainso the composer is not empty.bold. See the Bold button stay unmarked, though the new text is bold.plain, then back intobold. See no change on the Bold button.Acceptance criteria
Agent Brief
Category: bug
Summary: Move the transaction subscription into the component that renders the format buttons, and keep it only while that UI is open.
Current behavior:
The desktop row subscribes to editor transactions even when hidden, and even on mobile, where it renders nothing. The mobile grid never subscribes, so its active states are stale.
Desired behavior:
The format buttons re-render on editor transactions on both platforms, and only while the format UI is open. This replaces the chatroom rule that
FormattingToolbarmust calluseReRenderOnEditorTransaction. That rule has been wrong for phones since the mobile grid moved out ofFormattingToolbar. Update the rule in the same change.Key interfaces:
useReRenderOnEditorTransaction(editor)— the throttled transaction subscription.FormattingToolbarandComposerFormatPanel— the desktop row and the mobile grid, both mounted byComposerBar.ToolbarButton— readseditor.isActive()at render.Out of scope
Notes
The evidence is a code trace on
14ab7f9c1. It was not checked on a device. Confirm steps 4 and 5 on iOS Safari and Android Chrome, before and after the fix.