AgentDoc Patch Notes – April 2026

Changelog · 6 min read

A short, dense round of fixes landed in AgentDoc (also known as agent doc, agentdocs, docedit) over the last sprint. The theme this time is renderer correctness: edge cases where the agent's tool output was technically valid but the visual layer either dropped information or, worse, fabricated styles that the agent had not actually requested. Five patches, all targeted, no API changes.

1. Nested lists: relative depth preserved during normalization

c62928c · fix(renderer): preserve relative depth in nested-list normalization

When the agent emitted nested ordered/unordered lists with mixed indentation, the renderer's normalization pass was collapsing depths to the minimum level present in the markdown – which meant a list of the form 1.1.1.1.1.1. would render as three siblings rather than a tree. The fix changes the normalizer from absolute floor to relative offset: depth differences within the block are preserved, only the global indentation is normalized.

Why agents care: many models will emit insert_text calls with content that has been reflowed at the LLM layer (e.g. a model that learned indentation as 2-space when the document uses 4-space). The renderer must respect structure, not raw whitespace.

2. Decoration classes now compose instead of replacing

ddfb64a · fix(formatting): decoration-* classes compose instead of replacing

Previously, calling apply_decoration(target, "decoration-underline") on a span that already carried decoration-strikethrough would clobber the existing class. This is the wrong semantic: decorations are orthogonal (you can underline and strike through the same token), and our docstring promised exactly that. The bug came from a single className = assignment in the decoration setter, which we replaced with proper classList bookkeeping that adds without removing siblings of the same family.

3. apply_* tools now have proper TOGGLE semantics

8201f0a · fix(content_ops): implement TOGGLE on apply_* tools (matches docstrings)

The MCP tool surface advertised toggle behavior on apply_bold, apply_italic, apply_underline, etc. – meaning a second call against the same selection should remove the formatting, not double-apply it. The implementation, however, was always set-on. The fix introduces a state check on the resolved span and branches: if the formatting is already present and fully covers the selection, it is removed; if absent or partial, it is applied uniformly.

This matters more than it sounds. Voice flows lean heavily on "actually, undo that" as a redo signal; the agent translating that into a second apply_bold call now does the right thing without needing to call a separate remove_format tool. We removed two LLM round-trips from a common path.

4. Inline-code escaping inside [text]{.class} attributes

1e32d70 · fix(renderer): rewrite [text]{.class} swallowed inside inline-code

The renderer's pre-pass for our extended attribute syntax was running before the markdown-to-HTML stage, which meant strings like `[text]{.foo}` appearing inside backtick-delimited inline code (i.e. literal documentation of the syntax) were being rewritten as styled spans rather than displayed as code. We now skip the rewrite when the offset falls inside a code span. Documentation pages – including this one – render correctly again.

5. decoration-* classes apply to any element

a05b44c · fix(frontend): style decoration-* classes on any element, not just span

The CSS rules for .decoration-underline, .decoration-strikethrough etc. were namespaced as span.decoration-*. This worked for the common case but failed when the agent applied a decoration to a heading, list item, or table cell. The fix removes the element-name qualifier, so decorations are now orthogonal to element type – consistent with the toggle-semantics fix above and with the docs.

What's next

The next batch of work is queued around BYOK (bring-your-own Gemini API key) flows, which requires teaching the frontend modal and the quota-status pill to coexist with the new byok_modal.js and quota_status.js modules. After that we'll do a focused pass on the chat router, which has been collecting branches.

If you want to see the engineering rationale behind the tool-design decisions referenced above, the tool granularity write-up covers the framework we use to decide whether a behavior should be a tool, a tool argument, or pushed into the LLM's reasoning layer.

← Tool Granularity in LLM Agents Next: Rebuilding PDF + DOCX Export →