@topspinj/flora
v0.1.2
Published
Fault-tolerant, Mermaid-compatible diagram library for AI applications — interactive SVGs from imperfect input
Maintainers
Readme
Broken input, working diagram
LLMs write Mermaid constantly — and get it slightly wrong constantly. Feed this to a strict parser and you get a blank screen:
flowchart TD
A[User request] --> B{Cache hit?}
B -->|yes| C[Return cached]
B -->|no| D[(Postgres]
style D fill:#f9f
D --> E[Query and render]Strict parsers (Mermaid): Parse error on line 4 ... Expecting 'SQE', got 'PS'. Nothing renders. Your UI shows an error box or nothing at all.
Flora: the four valid lines render as an interactive diagram. The unclosed D[(Postgres] is skipped whole and reported as a structured diagnostic — never reinterpreted as garbage nodes — and the style directive is acknowledged and deliberately ignored:
[
{ "line": 4, "col": 16, "message": "Unterminated () (missing closing ))", "severity": "error" },
{ "line": 5, "col": 3, "message": "'style' ignored: styling directive — Flora handles styling through themes", "severity": "info" }
]Try it live in the playground — diagrams are encoded in the URL, so they're shareable with a link.
Quickstart
JavaScript
npm install @topspinj/floraimport { render } from "@topspinj/flora";
const { warnings } = render(
`flowchart LR
A[Start] --> B{Decision}
B -->|Yes| C[Do thing]
B -->|No| D[Other thing]`,
document.getElementById("diagram")
);Or with no build step — the CDN bundle registers a <flora-diagram> custom element:
<script src="https://unpkg.com/@topspinj/flora"></script>
<flora-diagram theme="default">
flowchart TD
A[Dashboard] --> B[API]
B --> C[(Database)]
</flora-diagram>Diagrams are interactive by default: scroll to zoom, drag to pan, click a node to highlight its upstream/downstream lineage.
Python / Jupyter
pip install florajsfrom florajs import Diagram
d = Diagram("""
flowchart TD
a[Start] --> b{Decide}
b -->|yes| c([Done])
b -->|no| a
""", theme="sketch")
d # displays interactively in Jupyter
d.to_svg_file("decision.svg") # headless export — embedded V8, no browserThere's also a programmatic Flowchart builder — see the Python docs.
Fault tolerance is the contract
Flora's rule is never blank, never silently wrong. Every line of input lands in one of three tiers:
- Supported — graph structure: nodes and shapes, edges and labels, chaining, subgraphs, direction, comments. Renders faithfully.
- Gracefully ignored — Mermaid presentation/behavior directives (
classDef,class,style,linkStyle,click,%%{init}%%). Recognized, skipped, reported asinfodiagnostics. Flora handles styling through themes and clicks throughonNodeClick. - Rejected loudly — anything the parser can't understand is skipped whole and reported as an
errordiagnostic ({ line, col, message, severity }). It is never guessed into extra nodes. If nothing parses,render()shows an error card, not an empty SVG.
Prefer failing? Pass strict: true to throw a FloraParseError (diagnostics on .warnings) instead of rendering best-effort. The rehype plugin is strict by default so broken diagrams fail your build.
API
All functions accept the same options (below) and return warnings alongside their result.
| Function | Returns |
|---|---|
| render(input, element, options?) | renders into a DOM element |
| toSVGElement(input, options?) | detached SVGSVGElement |
| toSVGString(input, options?) | SVG markup string (no DOM needed) |
| toPNG(input, options?) | Promise<Blob> |
| toAST(input, options?) | parsed AST, no rendering |
| toLayout(input, options?) | computed node/edge positions |
Options
{
theme: "default" | "tufte" | "digital" | "sketch" | { /* overrides */
background: "#ffffff",
nodeColors: { fill: "#f0f4ff", stroke: "#4f6df5", text: "#1e293b" },
edgeColors: { stroke: "#94a3b8", label: "#64748b" },
fontFamily: "Inter, sans-serif",
fontSize: 14,
nodeRadius: 8,
shadow: true,
},
interactive: true, // zoom, pan, hover, click-to-highlight lineage
strict: false, // throw FloraParseError instead of best-effort
onNodeClick: (nodeId) => {},
onNodeHover: (nodeId) => {},
onHighlight: (nodeId, upstream, downstream) => {},
}Node shapes
| Shape | Syntax | | Shape | Syntax |
|---|---|---|---|---|
| Rectangle | A[text] | | Stadium | A([text]) |
| Rounded | A(text) | | Cylinder | A[(text)] |
| Diamond | A{text} | | Queue | A[[text]] |
Subgraphs (subgraph Name ... end) render as collapsible groups and nest.
React
import { Flora } from "@topspinj/flora/react";
<Flora input={source} theme="tufte" onNodeClick={(id) => select(id)} />Rehype (Markdown pipelines)
import rehypeFlora from "@topspinj/flora/rehype";
// turns ```flora / ```mermaid code fences into rendered diagrams; strict by defaultUse with Claude Code
Flora ships a Claude Code plugin — a skill that writes correct Flora syntax, visualizes dbt lineage from a manifest.json, and returns playground share links:
/plugin marketplace add topspinj/florajs
/plugin install flora@florajsThen: /flora draw the auth flow for my app. The skill uses the open Agent Skills format, so it works with other agents too.
Feedback & contributing
Flora is young and shaped by the people using it. Bugs (a playground link is the perfect repro), feature requests, and general feedback all go through GitHub issues. PRs welcome — the open issues are a good place to start.
License
MIT
