feat(client): add markdown toolbar, list continuation, and @mention sβ¦#25
feat(client): add markdown toolbar, list continuation, and @mention sβ¦#25fzlzjerry wants to merge 4 commits into
Conversation
β¦uggestions Make issue/PR comment composers easier to use with formatting actions, Enter-to-continue lists, fenced code blocks, and GitHub-style @user autocomplete. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a shared markdown composer βshellβ to the Electron renderer, consolidating duplicated composer/editor UI into a single component that provides a formatting toolbar, live preview, and @mention suggestions. It also extends the underlying Monaco editor wrapper with markdown list continuation behavior and exposes editor helper methods needed by the new shell.
Changes:
- Introduces
ConversationMarkdownComposerShell(toolbar + editor + preview + mention suggestions) and refactors both the comment composer and markdown editor to use it. - Adds pure markdown formatting actions (bold/italic/heading/quote/code/link/lists) plus markdown list continuation logic, with unit tests.
- Extends
MonacoCodeEditorwith key interception, cursor listeners, and formatting helper APIs to support the shell.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/client/src/renderer/i18n/locales/en.json | Adds English strings for the markdown toolbar and mention suggestion UI. |
| packages/client/src/renderer/i18n/locales/zh.json | Adds Chinese strings for the markdown toolbar and mention suggestion UI. |
| packages/client/src/renderer/components/editor/monaco-code-editor.vue | Adds markdown list continuation on Enter, key interception hooks, and exposes editor helper APIs. |
| packages/client/src/renderer/components/conversation/mention-suggestion-menu.vue | New mention suggestions popover UI with local+remote candidate merging and loading/empty states. |
| packages/client/src/renderer/components/conversation/mention-query.ts | New mention detection + candidate merging helpers. |
| packages/client/src/renderer/components/conversation/mention-query.test.ts | Unit tests for mention detection and candidate merging. |
| packages/client/src/renderer/components/conversation/markdown-format-toolbar.vue | New formatting toolbar component emitting formatting actions. |
| packages/client/src/renderer/components/conversation/markdown-format-actions.ts | New pure formatting edit builders and list continuation decision logic. |
| packages/client/src/renderer/components/conversation/markdown-format-actions.test.ts | Unit tests for formatting edit builders and list continuation logic. |
| packages/client/src/renderer/components/conversation/conversation-markdown-editor.vue | Refactors markdown editor to use the shared composer shell. |
| packages/client/src/renderer/components/conversation/conversation-markdown-editor.test.ts | Updates test to assert the shared shell is hosted. |
| packages/client/src/renderer/components/conversation/conversation-markdown-composer-shell.vue | New shared shell component (toolbar + Monaco + preview + mention integration). |
| packages/client/src/renderer/components/conversation/conversation-markdown-composer-shell.test.ts | Adds test asserting toolbar integration in the shell source. |
| packages/client/src/renderer/components/conversation/conversation-comment-composer.vue | Refactors comment composer to use the shared composer shell. |
| packages/client/src/renderer/components/conversation/conversation-comment-composer.test.ts | Updates test to assert the shared shell is hosted. |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Dustella
left a comment
There was a problem hiding this comment.
Reviewed in depth β the core logic here is genuinely good. markdown-format-actions.ts and mention-query.ts are pure, well-tested functions (email exclusion, mid-word rejection, nested-indent renumbering, empty-item exit all covered), conversation-markdown-composer-shell.vue is a clean shared extraction that de-duplicates both composers (-43 lines each), and the toolbar is exemplary: all @oh-my-github/ui primitives, lucide icons, semantic tokens, select-none, matched en/zh i18n. Preview reuses the existing markdown renderer (no new injection surface), and remote user search is debounced with a monotonic sequence guard against out-of-order responses.
One clarification for anyone eyeing the +234 on monaco-code-editor.vue: that is not a new file or scope creep β it already exists on main and is used by both composers; this PR extends it with the imperative methods the feature needs (wrapSelection, getCursorContext, key interception). Correct place for that logic.
Two things to address before merge:
major β the @mention menu is clipped / unusable in the lower half of the composer.
mention-suggestion-menu.vue is position: absolute, and its nearest positioned ancestor is the relative h-48 editor column β but the composer root is overflow-hidden. The menu opens downward (top = cursorTop + lineHeight + 4, max-h-64 = 256px) inside a 192px (h-48) editor, so once the cursor is past ~the middle it overflows the overflow-hidden root and gets cut off (bottom edge, and the right edge in the md:grid-cols-2 layout). There's also no upward flip. Fix: teleport the menu to body and position in viewport coordinates (or render it outside the overflow-hidden root), and flip upward when there's no room below.
major β the mention dropdown is hand-rolled <button> rows.
packages/client/CLAUDE.md explicitly and repeatedly prohibits hand-built menus ("never hand-written <button> rows β¦ use DropdownMenu / ContextMenu"; the component picker maps a searchable pick β Combobox). Credit where due β it uses correct tokens (bg-popover, --ui-selected/--ui-hover, --shadow-dropdown), Avatar/Spinner, select-none and i18n throughout, so it's not a token violation, only a hand-rolled-control one. Fair caveat: a cursor-anchored typeahead is genuinely awkward to force into the stock DropdownMenu/Combobox (they own their own trigger + positioning), so this likely warrants a small shared "mention/typeahead popover" component β worth clearing the shape with the maintainer rather than either forcing the stock component or shipping bespoke per-page markup.
Minors / nits:
- minor β
monaco-code-editor.vue: Enter is bound unconditionally with nowhencontext. It correctly falls through to a plain newline for non-markdown/readonly (so other surfaces are unchanged), but awhen/context guard would avoid shadowing Monaco's accept-suggestion-on-Enter if the suggest widget is ever open in a markdown model. - minor β
mention-query.tsMENTION_TAILaccepts invalid logins (leading/trailing hyphen,@-foo). Harmless (UI gate only; the merge step filters), but could match GitHub's real login rules. - nit β
mentionActiveIndexisn't reset to 0 when the candidate list reorders on query change; the highlight can momentarily point at a different user. No crash β a stale out-of-range index falls through to a newline.
Everything else checks out β i18n keys match, filenames are kebab-case, the useAssignableUsersQuery / searchWorkspace({ mode: 'users' }) signatures and GitHubWorkspaceSearchItem shape are correct, and the pure-function tests are meaningful. Really nice feature; just needs the menu clipping fixed and a decision on the dropdown component.
β¦ aria - Let ArrowUp/ArrowDown fall through to the editor when the mention menu is open but has no candidates, so cursor navigation is never trapped; the cursor sync closes the menu once the caret leaves the query. - Scope role="listbox" to the options container so it only exists while it holds role="option" children; the searching/empty messages are now role="status" text instead of invalid listbox content. Addresses the Copilot review comments on ohmygit-hub#25. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
β¦ aria - Let ArrowUp/ArrowDown fall through to the editor when the mention menu is open but has no candidates, so cursor navigation is never trapped; the cursor sync closes the menu once the caret leaves the query. - Scope role="listbox" to the options container so it only exists while it holds role="option" children; the searching/empty messages are now role="status" text instead of invalid listbox content. Addresses the Copilot review comments on ohmygit-hub#25.
β¦n mention edges Addresses Dustella's review on ohmygit-hub#25: - Teleport the mention menu to body and position it in viewport coordinates so the composer's overflow-hidden shell and the h-48 editor column can no longer clip it; flip it above the caret when the space below runs out, clamp it horizontally, and re-anchor it when the editor scrolls under a stationary caret. - Guard the markdown list-continuation Enter command with !suggestWidgetVisible so it no longer shadows Monaco's accept-suggestion-on-Enter. - Tighten mention detection to logins GitHub can accept (no leading hyphen, no consecutive hyphens) while still tolerating a trailing hyphen mid-typing. - Reset the mention highlight to the top when the query changes so it cannot point at a reordered candidate.
627ef80 to
8300ff5
Compare
|
@Dustella Thanks for the deep review β everything is addressed in 8300ff5 except the component-shape question, which I'd like your (and the maintainers') call on. major β menu clipped in the lower half of the composer: fixed. The menu now major β hand-rolled minor β unconditional Enter binding: fixed β the list-continuation command is now registered with the minor β nit β stale All covered by new unit/source tests; |
This pull request introduces a new shared markdown composer shell component with a formatting toolbar, and refactors both the conversation comment composer and markdown editor to use this new shell. The changes also add robust markdown formatting actions, including list continuation logic, and comprehensive tests for these behaviors.
New shared markdown composer shell:
ConversationMarkdownComposerShellcomponent, which provides a markdown editor with a formatting toolbar, live preview, and mention suggestions, encapsulating common logic for both comment and editor use cases.conversation-comment-composer.vueandconversation-markdown-editor.vueto use the newConversationMarkdownComposerShellcomponent, removing duplicated editor and preview code. [1] [2] [3] [4]Markdown formatting actions:
markdown-format-actions.tswith pure functions to build markdown formatting edits (bold, italic, heading, quote, code, code block, link, lists) and logic for continuing/exiting markdown lists.markdown-format-actions.test.ts.Testing and verification:
Screenshot: