feat(frontend): markdown document editor — toolbar, code blocks, nested lists, rendering#4915
Conversation
…kdown editor Three improvements to the toolbar'd markdown editor used by the Instructions and Skill drawers. - Link button now opens a popover asking for the URL (seeded with the existing link when the caret is on one) with Add/Update + Remove, instead of blindly applying the literal "https://". - Table controls: a size-picker popover inserts a table (rows×cols hover grid); when the caret is inside a table the same control becomes a menu for insert/ delete row and column + delete table. The Lexical engine already registered TablePlugin + the markdown table transformers — this just exposes them. - The editor now accepts a dropped Markdown file (.md/.markdown/.mdx/.txt or any text/* file): it reads the file text and replaces the content, with a drop overlay. Intercepts only file drags in the capture phase, so Lexical's own internal text drag/drop is untouched.
Apply the "comfortable" prose direction to how Markdown documents (AGENTS.md / SKILL.md) render, in both the live editor and the read-only preview. The shared Lexical editor theme rendered document headings far too large (h1 24px / 600 weight), gave fenced code blocks no block styling, and left tables/blockquotes inconsistent. Rather than change that theme globally (the prompt and chat editors share it), scope the new prose styles to the document editors via a `md-prose` wrapper class: - editor-theme.css: a `.md-prose` block with a calmer heading scale (17/14/13, 500 weight), bordered/rounded code blocks, an accent-rule italic blockquote, and roomier line-height. Uses antd semantic tokens so it adapts to dark mode. - MarkdownEditor: tag its rendered containers with `md-prose` (covers both the edit and preview panes, which both render through this editor). - MarkdownPreview: bring its marked/DOMPurify MD_CLASS in line with the same scale, plus table styling it previously lacked.
Follow-up to live testing of the document editor. - Headings: replace the single H2 button with a block-type menu (Normal text, Heading 1-3, Quote, Code block) that reflects the caret's current block. The editor only ever offered one heading level before. "Code block" inserts a real fenced block (the standard @lexical/code CodeNode, already registered in rich mode) instead of the inline-code button, which had made multi-line "code" read as a stack of inline chips. - Headings now render with a clearer hierarchy (20/16/14, 600 weight) so they stand apart from body text. - Lists: the shared theme gave ol and ul different left margins and tall items, so the two indented inconsistently. Normalize both to one padding + tight rows under .md-prose. - Table size-picker: its grid cells used a fixed light hex that vanished in dark mode. Switch to antd semantic tokens so the cells are visible in both themes. - Code block wraps (pre-wrap) and keeps its bordered/rounded surface.
…ghting The "Code block" option turned a multi-line selection into one code node per line via $setBlocksType — which rendered as a stack of inline-code chips with no block background, not a single block. Mirror the Lexical playground: on a range selection, insert one code node and write the text back with insertRawText so line breaks become code lines (collapsed selection still uses $setBlocksType). Also wire up code highlighting, which the rich editor never enabled: - MarkdownEditor registers `registerCodeHighlighting` (the CodeNode/ CodeHighlightNode types were registered but the highlighter was never turned on, so blocks rendered as plain monospace). Token colors come from the existing `editor-token*` theme classes (light + dark). - The toolbar shows a searchable language picker (`getCodeLanguageOptions`) when the caret is inside a code block, writing `CodeNode.setLanguage` so Prism highlights the right grammar.
`.editor-inner:not(.code-editor) .editor-code` (0-3-0) was overriding the document code-block padding/fill from `.md-prose .editor-code` (0-2-0), so blocks rendered with 8px padding instead of the intended 12px/14px. Scope the rule to the rendered input (`.editor-input:not(.markdown-view)`) to reach 0-4-0 and win, without touching the markdown-source view. Verified live: a created code block now computes padding 12px 14px with the bordered/rounded surface.
Two changes from live testing of the document editor. - Drop the inline-code (`</>`) toolbar button. It applied per-line inline-code formatting, which read as a stack of chips and was the thing being mistaken for a code block. Code now means one thing: the block-type menu's "Code block". - Move the language picker out of the toolbar onto the code block itself. A new CodeBlockLanguageMenu renders a floating language Select pinned to the top-right of the code block the caret is in (fixed-position portal, recomputed on editor update + scroll/resize), driving CodeNode.setLanguage for Prism highlighting. The block gets 40px top padding to seat it. Verified live in the running app: toolbar no longer shows an inline-code button; selecting lines + "Code block" yields one styled block with the language picker seated on its top-right, aligned to the block and tracking scroll.
…polish it
The code block carried two language indicators that overlapped: the editor's
built-in uppercase `::after` label (globals.css, `attr(data-highlight-language)`)
and the new interactive picker. Hide the built-in label on document code blocks
(0-6-1 selector to beat globals' 0-5-1) so only the picker shows.
Polish the picker: borderless + compact (24px control, 11px text, sans), and
render the friendly language name ("JavaScript") for the selected value instead
of the raw id.
Verified live in the running app: two code blocks in one editor render
independently; the picker seats on the active block's top-right with no overlap;
the built-in `::after` label computes to `none`.
…e focused one Each code block reserves a 40px top strip for the language picker, but the picker only rendered on the block the caret was in — so other blocks showed an empty gap. Render a picker for every code block in the document (iterating the root's CodeNodes rather than the selection), clipped to the editor's scroll viewport so blocks scrolled out of view don't float a picker over the toolbar. Read-only panes get the picker too (disabled), so previews also fill the strip. Verified live: with two code blocks and the caret in a heading (neither focused), both blocks render their picker aligned to their own top-right.
…ions
Triggers showed a plain sentence ("No triggers yet. Add an app trigger or a
schedule…") while Tools/MCP/Skills use "No X yet — <add link>". Match it:
"No triggers yet — add a trigger", where the link opens the existing add menu
(App trigger / Scheduled trigger), so the "app trigger or a schedule" intent is
kept in the menu rather than the copy.
To guarantee parity, extract the shared `AddTextLink` (was local to
AgentTemplateControl) into its own module and use it in both. AddTriggerDropdown
gains an optional `trigger` so the empty-state link reuses the header's menu.
Verified live: the Triggers link is byte-identical in class to the Skills link
and opens the same add menu.
… playground
Nested numbered lists rendered every level as decimal (1., 1.), so depth was
invisible. Lexical already DOM-nests indented items (the wrapper li carries
`editor-nested-list-item`, which is marker-less), and the markdown round-trip
preserves nesting at a 4-space indent (LIST_INDENT_SIZE). So this is purely a
marker-styling gap: add depth-cycling `list-style-type` via descendant selectors
like the playground — ordered 1 → A → a → I → i, unordered • → ◦ → ▪ — scoped to
the rendered document view, plus the matching rules in MarkdownPreview.
Verified live: a nested ordered item computes upper-alpha ("A.") under the
decimal parent, with the wrapper li marker-less.
… font - On a cold open the drawer is still animating and the markdown still hydrating, so the first rect read is stale and the picker only appeared once a later event (e.g. a hover) nudged a recompute. Re-run recompute across the next frame + a few timeouts so it settles on the real position without interaction. - The picker portals to <body>, outside the antd theme container, so antd's inner `.ant-select-selection-item` fell back to a serif (Times). Set the app font (`var(--ant-font-family)`) on the wrapper and force the inner item / placeholder / search input to inherit it. Verified live: now renders Inter.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds Markdown editor prose styling, code block language pickers, Prism highlighting, file drag-and-drop, toolbar link/table actions, and shared trigger/button component extraction. ChangesMarkdown Editor Feature Additions
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
web/packages/agenta-ui/src/Editor/MarkdownToolbar.tsx (1)
88-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a Tailwind grid utility instead of inline style.
MAXis a fixed6, so this can begrid-cols-6and avoid CSS-in-JS for static layout.Proposed change
- className="grid w-fit gap-0.5" - style={{gridTemplateColumns: `repeat(${MAX}, 1fr)`}} + className="grid w-fit grid-cols-6 gap-0.5"As per coding guidelines,
web/**/*.{ts,tsx,css}should prefer Tailwind utility classes over inlinestyle={{...}}.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 72506590-4ec0-4897-b902-3c1529532d6f
📒 Files selected for processing (8)
web/oss/src/styles/editor-theme.cssweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AddTextLink.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/CodeBlockLanguageMenu.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/MarkdownEditor.tsxweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/TriggerManagementSection.tsxweb/packages/agenta-ui/src/Editor/MarkdownToolbar.tsxweb/packages/agenta-ui/src/MarkdownPreview.tsx
…rkers CR #4915: - The file-drop handler was memoized on `[dropEnabled]` with an eslint-disable, so it could call a stale `onChange`. That's reachable: SkillFormView passes a fresh inline `onChange={(v) => set("body", v)}` every render. Make `handleChange` a `useCallback([onChange])`, hoist the pure drop predicates to module scope, and add `handleChange` to the drop deps — the disable is gone and exhaustive-deps is clean. - MarkdownPreview only carried ordered nesting to lower-alpha while the editor CSS continues to upper-roman / lower-roman, so depth 4-5 lists rendered differently in the preview. Add the missing two levels.
|
@coderabbitai review |
✅ Action performedReview finished.
|
Context
The agent config's Instructions and Skill fields use a Lexical markdown editor. It was missing common authoring affordances and had several rendering/UX gaps: the link button applied a placeholder URL, there was no way to insert a table or a real code block, code blocks had no language or highlighting, nested numbered lists all rendered as
1., and the document prose used an oversized, inconsistent type scale. Separately, the Triggers config section's empty state didn't match the other sections.Changes
Focused improvements to the document editor, plus one config-section consistency fix. Each was verified live in the running app.
Toolbar
https://.</>button — "code" now always means a code block (the inline button kept being mistaken for one).Code blocks
CodeNode.setLanguage.Rendering
.md-prosewrapper so the shared prompt/chat editor theme is untouched.1 → A → a → I → i, unordered• → ◦ → ▪.Other
AddTextLinkand opening the same add menu.Tests / notes
@agenta/ui+@agenta/entity-uitsc clean; oss tsc at the pre-existing baseline (593); lint clean.upper-alpha, picker position/font, etc.).@lexical/markdownLIST_INDENT_SIZE).What to QA
A./a., not1...mdfile onto the editor: the content is replaced.