AgentDoc Patch Notes – April 2026
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.