feat(rich-editor): rich markdown field + @ mentions for skill & deploy modals#5215
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Adds an Fixes false “unsaved changes” on open by optional File viewer editor improvements: divider/image keyboard selection and Backspace behavior, suggestion-aware arrow keys, upload progress toasts, safer non-image drops, linked-badge markdown serialization, and CSS for mentions and range-selected leaf nodes. Docs/icons: new Settings shell: optional Docs chip on the panel header and section action slot. Reviewed by Cursor Bugbot for commit bda9400. Configure here. |
|
@greptile review |
|
@cursor review |
Greptile SummaryThis PR adds a controlled
Confidence Score: 5/5Safe to merge; the changes are well-isolated, thoroughly tested, and the abort/cleanup paths for SSE streams are correctly wired. The new No files require special attention; Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant Editor as TipTap Editor
participant Mention as Mention Extension
participant Hook as useEditorMentions
participant Store as MentionStore
participant RQ as React Query (useMarkdownMentions)
participant API as Workspace APIs
User->>Editor: "types "@""
Editor->>Mention: Suggestion.allow() check
Mention-->>Editor: true (after whitespace / start-of-block)
Mention->>Store: render() → onOpen callback
Store->>Hook: setActive(true)
Hook->>RQ: enable queries (files, folders, skills, workflows…)
RQ->>API: fetch workspace entities
API-->>RQ: data
RQ-->>Hook: MentionItem[]
Hook->>Store: store.set(items)
Store-->>Editor: useSyncExternalStore notify → MentionList re-renders
User->>Editor: selects item from MentionList
Mention->>Editor: insertContent([mention node, text " "])
Editor->>Editor: renderMarkdown → [label](sim:kind/id)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant Editor as TipTap Editor
participant Mention as Mention Extension
participant Hook as useEditorMentions
participant Store as MentionStore
participant RQ as React Query (useMarkdownMentions)
participant API as Workspace APIs
User->>Editor: "types "@""
Editor->>Mention: Suggestion.allow() check
Mention-->>Editor: true (after whitespace / start-of-block)
Mention->>Store: render() → onOpen callback
Store->>Hook: setActive(true)
Hook->>RQ: enable queries (files, folders, skills, workflows…)
RQ->>API: fetch workspace entities
API-->>RQ: data
RQ-->>Hook: MentionItem[]
Hook->>Store: store.set(items)
Store-->>Editor: useSyncExternalStore notify → MentionList re-renders
User->>Editor: selects item from MentionList
Mention->>Editor: insertContent([mention node, text " "])
Editor->>Editor: renderMarkdown → [label](sim:kind/id)
Reviews (29): Last reviewed commit: "fix(rich-editor): raw-fallback paste hoo..." | Re-trigger Greptile |
Greptile SummaryThis PR adds a controlled
Confidence Score: 4/5The PR is safe to merge. The core mechanics — duplicate plugin key prevention, sim: link round-trip, dirty-on-open baseline, streaming mirror — are all covered by new tests. The two findings are both about edge-case consistency rather than broken paths. Two inconsistencies worth addressing before shipping:
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Host as Host Component
participant UEM as useEditorMentions
participant UMM as useMarkdownMentions
participant Store as MentionStore
participant Ext as Mention Extension
participant List as MentionList
Host->>UEM: mount (workspaceId)
UEM->>Ext: "storage.enabled = true"
UEM->>Ext: "storage.onOpen = () => setActive(true)"
Note over UMM: queries disabled (active=false)
Host->>Ext: "user types @"
Ext->>Ext: allow() → true
Ext->>UEM: onOpen() → setActive(true)
UEM->>UMM: "enabled=true → React Query fires"
UMM-->>UEM: items[] (async)
UEM->>Store: store.set(items)
Store-->>List: useSyncExternalStore notify
List-->>Host: menu populated reactively
Host->>List: user selects item
List->>Ext: command(item)
Ext->>Ext: insertContent [label](sim:kind/id)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Host as Host Component
participant UEM as useEditorMentions
participant UMM as useMarkdownMentions
participant Store as MentionStore
participant Ext as Mention Extension
participant List as MentionList
Host->>UEM: mount (workspaceId)
UEM->>Ext: "storage.enabled = true"
UEM->>Ext: "storage.onOpen = () => setActive(true)"
Note over UMM: queries disabled (active=false)
Host->>Ext: "user types @"
Ext->>Ext: allow() → true
Ext->>UEM: onOpen() → setActive(true)
UEM->>UMM: "enabled=true → React Query fires"
UMM-->>UEM: items[] (async)
UEM->>Store: store.set(items)
Store-->>List: useSyncExternalStore notify
List-->>Host: menu populated reactively
Host->>List: user selects item
List->>Ext: command(item)
Ext->>Ext: insertContent [label](sim:kind/id)
Reviews (2): Last reviewed commit: "feat(rich-editor): rich markdown field +..." | Re-trigger Greptile |
|
@greptile review |
|
@cursor review |
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b393ea9. Configure here.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit bb68df9. Configure here.
|
@greptile review |
bb68df9 to
c46ef1b
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c46ef1b. Configure here.
c46ef1b to
69c271f
Compare
|
@greptile review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b40bbd4. Configure here.
…cursor - Backspace at the start of an empty block whose previous sibling is a divider/image removes the blank line (instead of deleting the leaf) and selects the divider above; a non-empty block selects the leaf so a second Backspace deletes it (highlight-before-delete). - Select-all (and any range selection) now visibly highlights dividers/images, which the native text highlight skips because leaves carry no text — via a decoration that paints a selection band. - The gap cursor between two adjacent leaves no longer draws its stray caret (matching Linear); the position stays functional. Leading/trailing gap cursors keep their caret. - Unit tests for the backspace + select-all behavior.
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 31d1c92. Configure here.
…nd-trip, a11y - Split mention-node into the schema-only `MarkdownMention` (mention-node.ts, no React/registry) and the live `MentionChip` node view (mention-chip.tsx); move the live factory to editor-extensions.ts and inject node views via DI. The headless round-trip path (markdown-parse/normalize-content/round-trip-safety) no longer pulls the 269-block registry — it now bundles for the browser with zero node-builtin deps. - A sized + linked image serializes as `[](href)` (dropping the unrepresentable size) instead of `[<img>](href)`, which the tokenizer can't reparse — the link is preserved, no silent data loss. Also escape the href title symmetrically. - Wire the suggestion menus as an ARIA combobox: while open, the editor gets aria-haspopup/expanded/controls and an aria-activedescendant tracking the active option, so screen readers announce it; cleared on close. Empty state is a role=status live region.
|
@greptile review |
|
@cursor review |
… cap The per-group MAX_PER_GROUP limit is meant to keep the unfiltered menu from flooding; applying it while a query is active hid matches past the eighth in a category, so search couldn't reach them. Cap only when there's no query. Adds a regression test (12 matches shown when searching).
|
@greptile review |
|
@cursor review |
- mentionIcon never returns undefined: an empty/unrecognized kind (schema default '', or a future kind on a sim: link) falls back to a generic icon instead of crashing the chip's render. Adds tests. - Add a mention input rule so typing `[label](sim:kind/id)` becomes a chip on the closing paren — matching the paste/load path (the tokenizer), which previously left typed syntax as literal text. A plain InputRule (full-range replace) is used; nodeInputRule would keep the surrounding brackets.
|
@greptile review |
|
@cursor review |
…n list - RawMarkdownField now honors onPasteText (e.g. skill SKILL.md destructuring), so a full-document paste is intercepted in the raw fallback too, not only the WYSIWYG path. - Bound the @-mention list while filtering (MAX_WHEN_FILTERED) so lifting the per-group cap for search can't render thousands of rows in the non-virtualized menu on a broad query; search still reaches deep matches well before the bound. Adds a test. - Tighten an extensions.ts doc comment (the headless path omits the registry + node-view construction, not React itself).
|
@greptile review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit bda9400. Configure here.
Summary
RichMarkdownField(sibling of the file-viewer editor) and use it for the skill Content field and the deploy version description — same WYSIWYG markdown experience, with placeholder/typography matched to the chip fields@-mention menu (TipTap suggestion) that inserts portable[label](sim:kind/id)markdown links for files, folders, tables, knowledge bases, workflows, skills, and integrations; wired into both the field and the file viewer via a shareduseEditorMentionshook, with the popup lifecycle + menu chrome shared with the/slash commandv3 · name) so a named version keeps a short, never-truncated referenceSKILL.md), and reorder to GitHub → UploadType of Change
Testing
bun run type-check,biome, andcheck:api-validationclean; 351 file-viewer/skills/deploy unit tests pass (incl. new round-trip, dirty-on-open, and sim-link regression tests)Checklist