Skip to content

feat(frontend): markdown document editor — toolbar, code blocks, nested lists, rendering#4915

Merged
ardaerzin merged 12 commits into
big-agentsfrom
fe-feat/agent-markdown-editor
Jun 28, 2026
Merged

feat(frontend): markdown document editor — toolbar, code blocks, nested lists, rendering#4915
ardaerzin merged 12 commits into
big-agentsfrom
fe-feat/agent-markdown-editor

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

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

  • Block-type menu (Normal text, Heading 1–3, Quote, Code block) replacing the lone H2 button — the editor previously offered only one heading level.
  • Link button opens a popover that asks for the URL (pre-filled on an existing link) with Add/Update + Remove, instead of applying a literal https://.
  • Table controls: a size-picker popover inserts a table; inside a table the control becomes an insert/delete row & column menu.
  • Removed the inline-code </> button — "code" now always means a code block (the inline button kept being mistaken for one).

Code blocks

  • A multi-line selection becomes one real fenced block (was making one inline-code node per line, which read as a stack of chips). Prism highlighting is registered (it was never enabled).
  • A language picker is pinned to the top-right of every code block — on the block, not the toolbar, and on all blocks, not just the focused one — driving CodeNode.setLanguage.

Rendering

  • A "comfortable" prose scale (headings, code, quote, tables, lists), scoped via a .md-prose wrapper so the shared prompt/chat editor theme is untouched.
  • Per-depth markers for nested lists, matching the Lexical playground: ordered 1 → A → a → I → i, unordered • → ◦ → ▪.
  • Markdown-file drag/drop replaces the editor content.

Other

  • The Triggers config-section empty state now matches Tools/MCP/Skills: "No triggers yet — add a trigger", using the shared AddTextLink and opening the same add menu.

Tests / notes

  • @agenta/ui + @agenta/entity-ui tsc clean; oss tsc at the pre-existing baseline (593); lint clean.
  • Everything was verified in the running app via live DOM inspection and screenshots (block creation, nested markers computing upper-alpha, picker position/font, etc.).
  • Nested lists round-trip through markdown at a 4-space indent (@lexical/markdown LIST_INDENT_SIZE).

What to QA

  • Open an agent's Instructions drawer. Select a few lines → block-type menu → Code block: one styled block with a language picker on its top-right. Pick JavaScript and confirm the tokens highlight.
  • Select text → Link: enter a URL, Add; click the link again → Update / Remove.
  • Insert a table from the toolbar; with the caret inside it, use the row/column menu.
  • Type a numbered list and indent items (4-space markdown, or Tab): nested levels show A. / a., not 1..
  • Drop a .md file onto the editor: the content is replaced.
  • Triggers section on an agent with none: "No triggers yet — add a trigger" opens the App trigger / Scheduled trigger menu.
  • Regression: open a normal prompt or chat editor — its list/heading/code rendering is unchanged (the new prose styles are scoped to the document editors).

ardaerzin added 11 commits June 28, 2026 18:04
…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.
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jun 28, 2026
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 28, 2026 6:09pm

Request Review

@dosubot dosubot Bot added the Feature Request New feature or request label Jun 28, 2026
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6961c5e7-1507-4a60-99cc-8fe47597723c

📥 Commits

Reviewing files that changed from the base of the PR and between 43d8be0 and db5c498.

📒 Files selected for processing (2)
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/MarkdownEditor.tsx
  • web/packages/agenta-ui/src/MarkdownPreview.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/packages/agenta-ui/src/MarkdownPreview.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/MarkdownEditor.tsx

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Markdown editor improvements, including syntax highlighting, drag-and-drop Markdown file import, and visible code block language selection.
    • Expanded the toolbar with link editing and table insertion/editing controls.
    • Introduced a more prominent inline “add” action for trigger management.
  • Bug Fixes

    • Improved code block formatting behavior for multi-line selections.
    • Refined Markdown rendering and preview styling for better spacing, lists, links, quotes, and code blocks.

Walkthrough

Adds Markdown editor prose styling, code block language pickers, Prism highlighting, file drag-and-drop, toolbar link/table actions, and shared trigger/button component extraction.

Changes

Markdown Editor Feature Additions

Layer / File(s) Summary
AddTextLink shared component extraction
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AddTextLink.tsx, web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx, web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/TriggerManagementSection.tsx
AddTextLink is extracted to its own file, removed from AgentTemplateControl, and used in TriggerManagementSection for the empty-state affordance and custom AddTriggerDropdown trigger.
CodeBlockLanguageMenu portal component
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/CodeBlockLanguageMenu.tsx
New portal-based per-code-block language menu scans Lexical code nodes, measures visible positions, keeps placement synced, and updates node languages through editor.update().
MarkdownEditor highlighting and drop handling
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/MarkdownEditor.tsx
Adds code highlighting registration, Markdown/text file drag-and-drop replacement via handleChange, refactors the editor body layout, and mounts MarkdownViewSync, CodeHighlightSync, and CodeBlockLanguageMenu in the editor tree.
MarkdownToolbar link and table actions
web/packages/agenta-ui/src/Editor/MarkdownToolbar.tsx
Expands Lexical state tracking, adds code-block formatting behavior, and replaces link and table controls with popovers and dropdowns for link editing, table insertion, and table row/column operations.
Prose CSS and MarkdownPreview styling
web/oss/src/styles/editor-theme.css, web/packages/agenta-ui/src/MarkdownPreview.tsx
Adds .md-prose typography, list, quote, code, and link overrides, and replaces the Markdown preview class list with a larger comfortable prose styling set.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Agenta-AI/agenta#4883: Modifies TriggerManagementSection.tsx around the same trigger UI area that this PR changes with AddTextLink and the custom AddTriggerDropdown trigger prop.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main markdown editor improvements and matches the changeset.
Description check ✅ Passed The description is directly about the editor, triggers, and rendering changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fe-feat/agent-markdown-editor

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/packages/agenta-ui/src/Editor/MarkdownToolbar.tsx (1)

88-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a Tailwind grid utility instead of inline style.

MAX is a fixed 6, so this can be grid-cols-6 and 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 inline style={{...}}.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72506590-4ec0-4897-b902-3c1529532d6f

📥 Commits

Reviewing files that changed from the base of the PR and between 8ecc97d and 43d8be0.

📒 Files selected for processing (8)
  • web/oss/src/styles/editor-theme.css
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AddTextLink.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/CodeBlockLanguageMenu.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/MarkdownEditor.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/TriggerManagementSection.tsx
  • web/packages/agenta-ui/src/Editor/MarkdownToolbar.tsx
  • web/packages/agenta-ui/src/MarkdownPreview.tsx

Comment thread web/packages/agenta-ui/src/MarkdownPreview.tsx Outdated
…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.
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ardaerzin ardaerzin merged commit ebc4ec1 into big-agents Jun 28, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Request New feature or request Frontend size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant