DEV Community

Curtis Zhang
Curtis Zhang

Posted on Fully Autonomous

Why MindMapAny Generates Markdown Before Building a Mind Map

A mind map editor needs structured data: node IDs, parent IDs, sibling order and source references. That does not mean the language model needs to generate the editor's entire data structure.

MindMapAny uses an intermediate format: an indented Markdown outline. Application code turns that outline into nodes. The interesting part is the boundary between those two steps—especially what happens when the model produces something slightly wrong.

This article was drafted with AI assistance using the project's implementation as source material.

Give the model a smaller output contract

Here is an illustrative outline in the format the generation prompt requests:

# Battery Research
- Test conditions
  - Temperature: Cells were tested at room temperature. ^c3
  - Load: Each cell used the same discharge profile. ^c4
- Limitations
  - Sample size: The experiment used twelve cells. ^c8
Enter fullscreen mode Exit fullscreen mode

The text describes the content, indentation expresses the hierarchy, and each leaf ends with a source chunk reference. The application assigns IDs and sibling order after parsing.

That keeps mechanical bookkeeping out of the generation task. The model chooses topics and groups facts. Code decides how those choices become a tree the editor can store.

The resulting representation still uses structured objects. Markdown is an intermediate language, not the persistence format.

The parser makes the recovery decisions

The parser scans the outline and maintains a stack of potential parents. When it reaches a node at the same or a shallower level, it removes deeper entries until it finds the appropriate parent.

This excerpt shows the parent-selection step from the implementation:

while (stack.length > 1 && level <= stack[stack.length - 1].level) {
  stack.pop();
}
const parent = stack[stack.length - 1];
Enter fullscreen mode Exit fullscreen mode

Here, level has already been derived from indentation. The root starts at level -1, which gives top-level topics a parent without requiring a separate special case for each one.

The parser also has explicit policies for imperfect output. It skips unrecognized lines, drops duplicate sibling titles after lowercasing them, enforces a depth budget, and limits the number of parsed non-root nodes. It returns warnings for several of these cases.

Those choices deserve review. Dropping a malformed line may preserve the surrounding outline, but it can also discard information. A parsed tree should not automatically be presented as a complete account of the document.

For applications where missing a point matters, a useful next step is to turn selected warnings into visible incomplete-output states or regeneration triggers. That is an application policy, not something Markdown supplies for free.

One extra space can change the tree

The prompt asks for two spaces per level. The parser nevertheless allows some variation.

Before creating nodes, it collects the indentation widths used by bullet lines. It sorts those widths and groups neighboring values when the difference is at most one space. Tabs count as two spaces.

Consider this illustrative input:

- Test conditions
  - Temperature
   - Load
Enter fullscreen mode Exit fullscreen mode

The two child lines use two and three spaces. This parser assigns them the same level. Without that normalization, an implementation that treats every indentation increase as a new level could make “Load” a child of “Temperature.”

There is a cost: genuinely distinct adjacent indentation widths can be merged. The grouping is also transitive across neighboring widths. If several widths differ by one space, they can collapse into the same level.

That is a tolerance decision tailored to this outline format. It is not a general-purpose Markdown parsing rule. A strict parser might reject the input instead; that would be a reasonable choice for a different product.

A chunk reference identifies a location, not a truth

The generation prompt asks for references such as ^c3. The parser looks that identifier up in an application-supplied chunk index:

const resolved = opts.chunkIndex?.get(ref[1]);
if (resolved) source = resolved;
else if (opts.chunkIndex) {
  warnings.push(`unknown chunk ref ^${ref[1]}`);
}
Enter fullscreen mode Exit fullscreen mode

The application restores the source location from that lookup. It does not ask the model to invent the final page number.

An unknown reference generates a warning when an index is available. The node can still exist without a resolved source. The implementation also does not reject every leaf that lacks a reference, even though the prompt asks for one.

More fundamentally, a valid chunk ID does not prove that the chunk supports the claim. A model can cite an existing passage and summarize it incorrectly. Reference validation and factual verification are separate checks.

This distinction matters when writing interface copy. “Open the cited source” describes a capability. “Verified fact” would require additional evidence.

Markdown does not make streaming automatic

A line-oriented format suggests a convenient streaming approach: accumulate text until a newline arrives, parse completed lines, and hold the unfinished tail in a buffer.

But this parser first examines indentation across the supplied outline. Its final hierarchy can therefore depend on lines that have not arrived yet. The implementation discussed here parses a supplied string; it should not be described as proof of a stable incremental renderer.

A streaming implementation using this design would need an additional decision: enforce fixed indentation, accept provisional hierarchy and reconcile later, or delay final parent assignment until generation finishes.

JSON can also be streamed with suitable tooling. The relevant question is which partial results the application is prepared to interpret and revise.

Structured output is still a reasonable alternative

Schema-constrained generation can provide structured model responses. OpenAI's documentation describes schema adherence and supported recursive structures, so a tree is not inherently a reason to avoid that approach. Structured Outputs overview.

The choice here is about where complexity lives. An outline gives the application control over indentation recovery and node construction. A schema gives the model a more explicit object contract. Both still need application checks for useful grouping, missing content and unsupported claims.

MindMapAny itself uses a JSON object for a separate hierarchy-planning prompt. That task assigns existing node IDs to groups rather than regenerating all the document's content. Different stages can use different output contracts.

Questions this design raises

Does successful parsing mean the map is complete?

No. The parser can skip lines or drop nodes because of limits. Completion needs its own checks, including the generation result and any parsing warnings.

Can this parser accept arbitrary Markdown?

No. It recognizes a deliberately limited outline format. Its treatment of headings, indentation and fenced content belongs to that format.

Is Markdown cheaper or more accurate than JSON?

This article provides no benchmark establishing either claim. Compare formats using representative documents, the same quality criteria and the actual models used by the application.

Choose the boundary you can inspect

The useful part of this Markdown outline pipeline is that its recovery behavior is visible in code. It makes concrete decisions about malformed indentation, duplicate siblings, depth limits and unknown references.

For another AI application, start by writing down those failure decisions. Then choose an intermediate format that makes them manageable. A valid object is only the beginning of a usable result.

Implementation discussed: MindMapAny, an AI mind mapping application. The examples above are illustrative, not benchmark results.

Top comments (0)