One file, two renderers
I wanted a chart card. Bar, line, pie, donut, the kind of thing every React app reaches for a library to draw. Recharts was the obvious pick, a few props and you have an SVG. Then I remembered what Export does: it writes the whole board into one .html file, a single document someone can double-click with no server and no install. Recharts lives inside the React tree. The exported file has no React tree. The chart would render perfectly in the app and show nothing at all in the one artifact Folium promises people — a whole board, one file.
That’s the constraint in its plainest form. The export in src/export/html.ts
is a second renderer: a plain-JS viewer with no framework and no build step,
and it gets a vote on every card type before that card ships. A React-only
library fails that vote instantly.
So the chart renderer in src/charts/renderChart.ts is hand-rolled: pure
functions that walk rows of numbers and emit <svg> markup directly. The
comment on it says the function “MUST stay self-contained — embedded into the
HTML export via .toString()”, and that is the actual mechanism. The decision
I’d defend hardest sits on one line of html.ts:
var renderChartSvg = ${renderChartSvg.toString()};
The app’s own function is stringified and dropped into the exported file as
live JavaScript. The app and the export don’t run similar code; they run the
same function, serialized twice, so they cannot drift apart. The whiteboard
ink outlines and the elbow line routing get the same treatment, pulled in from
src/model/inkOutline.ts and src/model/lineRoute.ts. The chart renderer has
its own test file, src/charts/renderChart.test.ts, checking negative values,
NaN, a single data point, label text that needs escaping, because once the
function is serialized into someone else’s file I can’t patch it after the
fact.
Parity is a standing bill
The viewer keeps needing the same polish the app gets. When boards gained
drag-to-pan, the export’s script had to learn it separately, since there is no
bundler to share a module through. The export CSS carries a comment that it
mirrors .app-canvas[data-board-bg] in src/styles/global.css, a note-to-self
that two stylesheets have to agree by hand. None of this is glamorous. It’s the
price of a board that can leave my machine as one file and still look like the
board I built, and I keep paying it because the alternative is an export that
quietly falls behind the app until the day someone opens a file and the chart
just isn’t there.