Tooling & Lab
nh-deck
Local-first CLI that turns one Markdown file into a presentable slide deck
3 min read · 639 words
nh-deck is a TypeScript/Node command-line tool that turns a single Markdown file into a slide deck, for one author who wants to write, present and export slides without an account or a network. It is a solo project: 53 of its 58 commits are the owner's, the other five are Dependabot's.
- Six subcommands —
init,list-themes,render,pdf,pngandbuild, built with Commander (buildexists in the repo at v1.10.0 but not in the npm 1.7.0 release). - Local-first HTML — KaTeX fonts are inlined as base64 and Mermaid SVGs lose their Google Fonts import, so the page needs no CDN.
- Live preview —
render --watchserves on 127.0.0.1 and reloads over Server-Sent Events. - Presentation mode —
?presentadds fragments, a grid overview, and a second-window presenter view with notes and a timer. - PDF and PNG export — landscape 16:9 pages with heading-derived bookmarks, or one PNG per slide, through a browser already installed.
- Static build —
buildwrites the same HTML to<name>-site/index.html, which opens fromfile://.
563 Vitest tests passed on the last CI run on main (2026-09-17) across Linux, macOS and Windows; the repo is tagged v1.10.0 but npm serves only 1.7.0, and the project has 0 stars and 39 npm downloads in the 30 days to 2026-10-18.
Architecture
A flat src/ of 21 TypeScript files (6,742 lines) compiles with plain tsc to dist/index.js, with no bundler, on Node 22 or newer. src/render.ts holds the pure, synchronous generateHtml(), which lexes the Markdown once and renders each hr-delimited group into its own <section>. Frontmatter uses a narrow hand-written key: value parser instead of a YAML dependency, falling back to whole-file-as-body so a deck opening with a bare --- is not misread.
The local-asset modules are small: katexAssets.ts inlines KaTeX fonts, mermaidRenderer.ts renders through beautiful-mermaid, and codeHighlight.ts registers 12 highlight.js grammars. server.ts is a plain node:http server on loopback, and watch mode uses Server-Sent Events. The presentation client is driven only by location.hash and location.search, which is why build output works off file://.
The repo is tagged v1.10.0 (2026-09-16), but npm has one version, 1.7.0, published the same day. 1.8.0 through 1.10.0 were never pushed and no workflow has a publish step. All twelve releases landed in five days, and the last non-Dependabot commit to main is dated 2026-09-17. CI covers three operating systems on Node 22 and latest, a two-Chromium pixel diff (its 2% tolerance has only ever seen 0.000%, so it is unproven as a detector) and an axe-core scan; main requires 8 status checks but no PR review.
How It Works
- Write
deck.md: a---after a blank line is a slide boundary, markers such as<!-- note -->and<!-- fragment -->apply per slide, and optional frontmatter setstheme:,dir:andtransition:. - Resolve options —
--themebeats frontmattertheme:, and--css <path>beats both with a full stylesheet replacement; an unknown theme warns and falls back tolightinstead of failing. - Generate HTML —
generateHtml()lexes once and splits athrtokens;mermaidfences go tobeautiful-mermaid, math to KaTeX (fonts inlined only when math is present), other fences to highlight.js. - Serve —
startServer()binds 127.0.0.1 on an OS-assigned port and opens the browser unless--no-open; with--watch, a file change re-renders and reloads the page. - Present —
?presentswitches to one slide at a time with keyboard, click and swipe navigation, fragment reveal, anooverview and appresenter window. - Export —
pdfandpngpick the first installed Chrome, Chromium, Edge or Brave, or exit non-zero with a message; puppeteer-core prints 16:9 pages with a heading-derived outline or crops one PNG per slide, andbuildwrites the same HTML todeck-site/index.html. - Gate — every PR runs typecheck, Biome and Vitest on six OS and Node combinations, the pixel diff and an axe-core scan; the repo still has 10 open code-scanning alerts (9 OpenSSF Scorecard findings and 1 CodeQL hit in a test file).