
Highlights#
- Built the renderer and authoring editor that remove the manual re-entry step in publishing Italian public-administration procedures: hundreds of real procedures render from expert-written Markdown into portal-ready accessible pages.
- Built a rule engine that rewrites documents by content, position and context on the parser’s token stream, without forking markdown-it or munging HTML strings.
- Designed a Markdown authoring editor on CodeMirror 6 whose autocomplete, linter, highlighter and component guide all read one catalog, so what it suggests is exactly what it accepts.
- Added git-style version history for reports: content-addressed snapshots chained by hash, precomputed diffs, and non-destructive restore.
How it works#
Experts don’t write markup. A procedure is one Markdown file: normal prose, front matter, and a handful of fenced blocks that map to the portal’s building blocks. The editor makes that grammar hard to get wrong while they type, the renderer turns the file into a complete portal page with accessibility attributes included, and every save is snapshotted so nothing an author writes can be lost.
Nothing can drift, at three layers: the editor’s four features all read the same catalog, every save snapshots the one Markdown file, and the same pure renderer feeds both the browser preview and the Node build — while the old manual re-entry step stays deleted.
The core render function is pure, with no filesystem access, so the same code runs at build time in Node and bundled in the browser. That is what makes the live preview honest: the pane the expert watches is produced by the same function that publishes the page.
Portfolio Case Study#
Problem. Procedures for an Italian public-administration catalog are documented by experts in a backoffice, in Markdown. The public portal shows them as structured, accessible pages built from the government design system’s components. Between those two sat a person: an editor re-entered every report into the platform’s forms so it would come out readable and WCAG-compliant. Every procedure existed twice, and the copies drifted.
Deleting the middle step raised two problems, not one. The Markdown file had to render as a native portal page, and non-technical experts had to be able to write that file without breaking it. md-italia answers the first; the authoring tooling built around it answers the second.
Approach. The renderer is a standalone library and CLI on purpose, not code inside the backoffice app. Getting the output to match the real portal needed its own test surface, fixtures pinning generated markup to the portal’s actual HTML, which would have been hard to keep honest inside a React codebase. The split has a cost: every change means rebuilding and repacking the package the app vendors.
It builds on markdown-it without forking it and without rewriting the generated HTML as strings. It reconstructs a document tree from the parser’s token stream, then applies rules that match nodes by what they contain, where they sit, and what surrounds them.
flowchart TD
MD["Markdown report
+ front matter"] --> P[markdown-it parse]
P --> DM["Document model
built from the token stream"]
DM --> RULES["Rule engine: match nodes by
content, position, context"]
RULES --> HTML[Bootstrap Italia HTML]
HTML --> SHELL["Page shell + portal assets"]
COMP["Components: item cards (voci), forms (modulistica),
extra-info, flowchart, …"] -.-> P
The portal components themselves were designed by a UX designer, and their HTML and CSS were written by a frontend colleague, Filippo; I built the system that turns an expert’s Markdown into exactly those components. What the expert writes is a closed grammar: front matter, five fenced components (item lists, forms with attachments, info boxes, the numbered how-to timeline and its steps) and seventeen icon tokens. It fits in a short bilingual manual and can be learned in ten minutes.
The editor exists to make that grammar safe. The first attempt was an off-the-shelf WYSIWYG, and it hit a wall: autocomplete, linting and highlighting for a custom ::: grammar weren’t feasible inside it. Its replacement is a CodeMirror 6 editor built for the grammar. Its four features (context-aware autocomplete, a live linter with quick-fixes, syntax highlighting, a component guide with insert-at-cursor) all read from one catalog module and one cursor-context detector, so a value the editor suggests is the same value it accepts, highlights and publishes. The pieces can’t drift apart.
Version history came from watching authors work: experts hesitated to edit freely without a safety net. Every save now appends an immutable snapshot carrying a SHA-256 content hash, a link to its parent version, the full text and a precomputed unified diff. Restoring is non-destructive; loading an old version and saving appends it as the new head instead of rewriting the chain. A lightweight, purpose-built Git for a single document.
What was hard. Portal parity, more than anything. The public site runs a Drupal theme, so matching it meant reverse-engineering the markup that theme expects, down to the component structure, and iterating under its real CSS until generated pages rendered like the originals. The rule engine had its own fight: when a rule replaces a node’s markup, every later node’s position shifts, so the engine tracks nodes by identity rather than trusting offsets.
Integration added smaller, sharper edges. The item-list component reads its container body line by line, and CRLF input silently dropped every card until line endings were normalized ahead of the parse. Reports saved through the old WYSIWYG arrived with every punctuation character backslash-escaped, breaking headings, lists and front matter at once; they get un-escaped before rendering. And the portal theme is one 784KB stylesheet that would fight any host app’s styles, so the live preview renders inside an iframe with the theme loaded in isolation.
Outcome. Hundreds of real procedures have been rendered through the system, and the pages have been demoed to the client and stakeholders; deployment to the live portal is the step in front of it. Publishing a procedure is now editing one Markdown file, and the re-entry work, with the drift that came with it, is gone. The same rendering serves the project’s AI drafting pipeline: a model’s Markdown output becomes a portal-ready page with no human markup step, and version history keeps the AI’s draft and every later human edit recoverable. The renderer is covered by a thorough test suite, including automated accessibility checks on the generated pages, and runs green in CI. The part I’d point at first is still the rule engine: real document-level matching, kept out of both the parser and the output strings.
Non-goals. It’s a publishing tool for the team’s own content, not a sanitizer; input is trusted. There’s no runtime on the public side, just static HTML and assets produced at build time. The renderer deliberately tracks the one design-system version the portal runs rather than abstracting over many. And the editor doesn’t hide Markdown behind a WYSIWYG surface: experts see the source, and the tooling’s job is to make that safe, not invisible.
Screenshots#

voci card — the portal’s item-card component — in the source become the portal’s real card components in the preview: icons, labels, titles and descriptions laid out as the public page shows them.
::: fence, the five components are offered as full scaffolds. Choosing one drops the whole block, colon nesting and closing fences included, with tab-stops to fill in.


Stack#
TypeScript, Node, markdown-it, CodeMirror 6, React, Bootstrap Italia, esbuild, Vitest, axe-core, GitHub Actions.
