runlocally

runlocally

Engineering notes

How the runlocally tools are built: the technologies each tool uses, what they are, and how they fit together. One technical article per tool.

Inspect Characters August 22, 2026

How Inspect Characters is built

Reading a string one grapheme at a time instead of one index at a time, and why a character that renders as nothing — a zero-width space, a byte-order mark — is exactly the case this tool exists to catch.

Count Text August 21, 2026

How Count Text is built

Why a single emoji can report four different lengths at once, and the Intl.Segmenter API that counts what a person actually sees instead of what UTF-16 happens to store.

Convert Case August 20, 2026

How Convert Case is built

Why splitting an identifier into words is the entire problem, and the two rules — consecutive capitals, digit boundaries — that decide whether HTTPResponse and oauth2Token tokenize correctly.

Decode JWT August 19, 2026

How Decode JWT is built

Splitting a compact JWT into its three Base64URL parts with nothing but native decoding, and why the UI has to say out loud that the signature was never checked.

Extract RAR/7z August 18, 2026

How Extract RAR/7z is built

The technology behind Extract RAR/7z: the TAR/RAR/7z container formats, libarchive's pluggable format-reader/compression-filter architecture compiled to WebAssembly via libarchive.js, Comlink's worker RPC, and how a password-protected archive is detected and reported rather than half-opened.

Merge Excel Workbooks August 17, 2026

How Merge Excel Workbooks is built

Copying a worksheet between two ExcelJS workbook instances cell by cell rather than file by file, and the case-insensitive naming collision Excel enforces that has to be resolved before two files can share a 'Summary' tab.

Images to XLSX August 16, 2026

How Images to XLSX is built

A one-word setting inside a library's write call that decides whether an embedded picture resizes when you widen its column or stays stuck at its original size forever — found by generating a real file and reading its raw XML, not by reading the library's own type declarations.

CSV to XLSX August 15, 2026

How CSV to XLSX is built

The worksheet-naming rules Excel enforces that a plain filename never has to follow — a 31-character cap, a short list of banned characters, and one specific reserved name Excel refuses outright — and detecting Shift_JIS without ever being told a file's encoding.

Compare Excel Workbooks August 14, 2026

How Compare Excel Workbooks is built

Why comparing two spreadsheets cell-by-cell is a much bigger problem than it looks once a single row gets inserted, and the deliberate MVP scope cut — index-based comparison instead of row re-alignment — that keeps the feature honest about what it actually checks.

XLSX to JSON August 13, 2026

How XLSX to JSON is built

Turning a spreadsheet's date cells into a format JSON can actually represent, the JSONL variant that lets a huge sheet be processed one line at a time downstream, and a case where a library's own type declarations didn't match what it exports at runtime.

XLSX to Markdown Table August 12, 2026

How XLSX to Markdown Table is built

Why a spreadsheet's widest row has to decide the Markdown table's column count before the first line is written, and the two characters — a pipe and a newline — that have to be escaped out of a cell before a table format that can't represent either one.

XLS to XLSX Converter August 11, 2026

How XLS to XLSX Converter is built

Reading a 1990s binary spreadsheet format that predates OOXML entirely, why its Japanese code-page table only loads when a legacy file actually needs it, and a real mismatch between what a library's own type declarations promise and what its runtime module actually exports.

Excel Workbook Viewer August 10, 2026

How Excel Workbook Viewer is built

Checking a file's binary signature before ever handing it to a spreadsheet parser, so an unrelated file produces a clear error instead of a confusing one three layers deep — and why the tool can't preview a workbook's embedded pictures.

XLSX to CSV August 9, 2026

How XLSX to CSV is built

Loading a spreadsheet parser as a lazy chunk instead of a page dependency, the BOM that keeps round-tripped CSV readable in Excel, and a WebKit-on-macOS filesystem quirk that normalizes Japanese filenames out from under a download test.

Remove Excel Sheet Protection August 8, 2026

How Remove Excel Sheet Protection is built

Why sheetProtection and workbookProtection are locks, not encryption — removing the XML element is enough, no password recovery involved — and how a CFB magic-byte check tells a locked sheet apart from a file that needs a password just to open.

Strip XLSX Metadata August 7, 2026

How Strip XLSX Metadata is built

Where personal information actually hides inside an .xlsx package beyond the obvious docProps/core.xml — comment authors, threaded-comment person records, and a custom-properties part that has to be unregistered from two other XML files, not just deleted.

Extract Images from Excel August 6, 2026

How Extract Images from Excel is built

Why an .xlsx file is just a ZIP archive in a trench coat, how xl/media/ holds every picture a workbook contains regardless of which sheet it's pasted into, and the path-traversal check needed before trusting names read out of someone else's archive.

QR Code Generator August 5, 2026

How QR Code Generator is built

The QR error-correction/capacity model behind this tool's use of the qrcode npm library, its split canvas-preview and SVG-download paths, and a capacity estimator kept deliberately separate from the encoder that actually decides whether an input fits.

Password Generator August 4, 2026

How Password Generator is built

The randomness and character-selection mechanics behind the password generator: why crypto.getRandomValues replaces Math.random, the rejection-sampling scheme that removes modulo bias when mapping random bytes onto a character set, the algorithm that guarantees every enabled character class appears at least once, and the length × log2(charset size) entropy figure the tool reports instead of a strength meter.

File Hash August 3, 2026

How File Hash is built

How SHA-256 and SHA-1 are computed via the browser's native WebCrypto SubtleCrypto digest API against a file's full ArrayBuffer, how MD5 is computed instead through spark-md5's chunked API in bounded file.slice() reads so that path alone stays memory-bounded on large files, and how a pasted hash is matched to an algorithm purely by its trimmed hex length.

Encode Base64 August 2, 2026

How Encode Base64 is built

Text and file Base64 conversion built on TextEncoder/TextDecoder and a chunked byte-to-binary-string routine instead of plain btoa/atob, so it doesn't corrupt multi-byte UTF-8 text or arbitrary binary data.

Convert Timestamp August 1, 2026

How Convert Timestamp is built

The conversion engine behind Convert Timestamp: a digit-count heuristic that tells Unix seconds from milliseconds, why it shows Local time and UTC side by side instead of a timezone selector, and the native-Date quirks the code relies on and works around — no date library involved.

Convert Color July 31, 2026

How Convert Color is built

The hand-rolled sRGB↔HSL conversion math behind Convert Color, and the echo-then-derive update pattern that keeps its HEX, RGB and HSL text fields in sync without an update loop or blanking invalid input.

Format SQL July 30, 2026

How Format SQL is built

A thin wrapper around sql-formatter's tokenizer — five exposed dialects out of the seventeen-plus the library supports, and what happens when a query can't be tokenized at all.

Decode Certificate July 29, 2026

How Decode Certificate is built

How X.509 certificates and PKCS#10 CSRs get parsed from PEM or DER with @peculiar/x509, entirely client-side, including the certificate-chain and private-key-skipping logic that shapes the decode path.

HAR Viewer July 28, 2026

How HAR Viewer is built

The technology behind HAR Viewer: the HAR 1.2 JSON format DevTools exports, a shape-validating parser with no third-party HAR library, a CSS-only timing waterfall sized against the whole capture span, and hand-authored heuristics that flag headers, query parameters, and cookies shaped like tokens or session cookies.

Identify File Type July 27, 2026

How Identify File Type is built

Binary-signature ('magic bytes') detection with magic-bytes.js, why its two prefix-only pseudo-signatures had to be filtered out and routed through a separate text sniffer instead, and how a 4-byte ZIP signature that matches a dozen container formats is surfaced instead of guessed at.

Edit ASCII Diagram July 26, 2026

How Edit ASCII Diagram is built

A display-column-aware grid model for Unicode box-drawing diagrams — built to fix a real width bug in existing tools that breaks on Japanese text and emoji — plus an order-based edge-alignment detector that recovers the boxes from ragged, real-world pasted input where a naive positional approach provably cannot.

Compare Text July 25, 2026

How Compare Text is built

Line-level text diffing with jsdiff's diffLines, a one-flag fix for a jsdiff quirk that turns a trailing-newline mismatch into an oversized diff, and keeping additions/removals readable without relying on color alone.

Format JSON July 24, 2026

How Format JSON is built

Pretty-printing, minifying, and validating JSON with nothing but native JSON.parse/stringify, and an empirical survey of how differently Chromium, Firefox, and WebKit report a parse error's location — because none of them agree.

Strip Tracking Params July 23, 2026

How Strip Tracking Params is built

Removing UTM/fbclid/gclid and other tracking parameters with nothing but the URL and URLSearchParams APIs, a hand-authored parameter list kept clear of a GPL-licensed data file, and a real disagreement between browsers over what counts as a valid URL.

Edit Flowchart July 22, 2026

How Edit Flowchart is built

A hand-written, line-based lossless parser and printer for Mermaid flowchart syntax — built because Mermaid's own parser can't round-trip to text and its syntax has no coordinates to drag a node to — with an opaque pass-through design and a byte-identical round-trip guarantee proven by a test corpus.

Draw Flowchart July 21, 2026

How Draw Flowchart is built

Live-rendering Mermaid flowchart syntax to SVG with a scope guard that only recognizes flowcharts, PNG export via canvas rasterization, and a real cross-browser bug where a deprecated Mermaid setting silently left the canvas tainted.

Edit Markdown Table July 20, 2026

How Edit Markdown Table is built

A hand-written GFM table parser and serializer with no dependency, a subtle escaping rule proven correct by hand-trace rather than assumed, and why the grid uses real semantics instead of full ARIA-APG roving-tabindex.

Markdown Viewer July 19, 2026

How Markdown Viewer is built

The technology behind Markdown Viewer: a two-layer XSS defense with markdown-it (html:false + a link-scheme allowlist) and DOMPurify, an encoding-detection routine ported straight from CSV Viewer, and why ZIP support was deliberately left out.

Rename Images in Sequence July 17, 2026

How Rename Images is built

A tap-to-order single grid (like the multi-select photo picker on your phone), a small pure-function template engine ({n} / {n:03}) that maps position to filename, and why this tool needs no Web Worker at all.

Trim Audio July 16, 2026

How Audio Trim is built

The decode/waveform/encode pipeline behind Audio Trim: Web Audio decoding, peak-downsampling a waveform to a fixed column count, canvas rendering, and range-based sample slicing into the same lamejs MP3 encoder its sibling tool uses.

Winmail Viewer July 15, 2026

How Winmail Viewer is built

The technology behind Winmail Viewer: the MS-OXTNEF format behind winmail.dat, why Outlook produces it at all, hand-parsing its flat TLV attribute stream with DataView, and reconstructing attachments from attAttachRenddata/attAttachTitle/attAttachData groups with no third-party TNEF library.

Eml Viewer July 14, 2026

How Eml Viewer is built

Parsing RFC 822/MIME email with postal-mime, and the two independent layers — DOMPurify sanitization and a sandboxed iframe — that make rendering someone else's HTML email safe to do at all.

Extract PDF Text July 13, 2026

How PDF Extract Text is built

Reconstructing readable text from pdf.js's positioned text runs, and the font-metrics gotcha that breaks word-spacing if you skip it.

PDF to Image July 12, 2026

How PDF to Image is built

The technology behind PDF to Image: rasterizing PDF pages with pdf.js (getDocument → page.render onto a canvas), the Canvas 2D API, resolution as a DPI-to-viewport-scale conversion, pdf.js's own dedicated worker plus its lazily-fetched font/cmap/WASM assets, and zipping multi-page output with zip.js.

Hex Viewer July 11, 2026

How Hex Viewer is built

Rendering a file with millions of rows as a hex dump without hanging the tab: windowed/virtualized list rendering, the File API, TextDecoder for multi-encoding text, and two real bugs — a mislabeled Latin-1 decoder and a flexbox layout that silently defeated virtualization.

Split ZIP July 10, 2026

How Split ZIP is built

The technology behind Split ZIP: first-fit-decreasing bin-packing of a ZIP's entries by uncompressed size plus structural overhead, re-packed with @zip.js/zip.js into N independent, individually-openable .zip parts rather than a fragile spanned .z01/.z02 archive.

Merge ZIP July 9, 2026

How Merge ZIP is built

The technology behind Merge ZIP: streaming several archives through one @zip.js/zip.js ZipWriter with N ZipReaders, and resolving same-path name collisions explicitly — rename to keep both, or skip to keep the first — so nothing is ever silently overwritten.

Remove from ZIP July 8, 2026

How Remove from ZIP is built

The technology behind Remove from ZIP: listing an archive's central directory with @zip.js/zip.js and re-packing only the kept entries through a fresh ZipWriter, streaming each entry's data while preserving folder paths, timestamps, and the UTF-8 filename flag.

Recover ZIP July 7, 2026

How Recover ZIP is built

The technology behind Recover ZIP: verifying entries through the central directory with @zip.js/zip.js getData({ checkSignature: true }), and a fallback that scans raw bytes for local file headers, inflates DEFLATE with the browser-native DecompressionStream, and validates the result with a hand-written CRC-32.

Unlock ZIP July 6, 2026

How Unlock ZIP is built

The technology behind Unlock ZIP: decrypting a password-protected archive you can already open with @zip.js/zip.js ZipReader({ password }) and re-packing it through a passwordless ZipWriter, plus how a wrong password is told apart from a not-encrypted archive.

Encrypt ZIP July 5, 2026

How Encrypt ZIP is built

The technology behind Encrypt ZIP: building a password-protected archive with @zip.js/zip.js ZipWriter and encryptionStrength 3 (WinZip AES-256), what WinZip AES encryption in a ZIP actually is versus legacy ZipCrypto, and which extractors can open the result.

Unzip July 4, 2026

How Unzip is built

The technology behind Unzip: reading a ZIP's central directory with @zip.js/zip.js ZipReader.getEntries(), extracting each entry on demand with getData + BlobWriter, and how encrypted and non-UTF-8 entries are surfaced rather than silently mishandled.

CSV Viewer July 3, 2026

How CSV Viewer is built

The technology behind CSV Viewer: reading a file as bytes and detecting its encoding with a strict-then-fallback TextDecoder, parsing delimited text with papaparse (RFC 4180 quoting and delimiter guessing), and drawing tens of thousands of rows with a windowed table that only keeps the on-screen rows in the DOM.

Remove Silence July 2, 2026

How Remove Silence is built

The technology behind Remove Silence: decoding audio with the Web Audio API, detecting silence with an amplitude threshold over the PCM samples, and re-encoding the kept audio to MP3 with a pure-JavaScript LAME encoder.

Merge PDF July 1, 2026

How Merge PDF is built

The technology behind Merge PDF: combining several PDFs in the browser with pdf-lib (copyPages across documents → addPage → save), controlling the page order, and why merged output isn't deduplicated.

Split PDF June 30, 2026

How Split PDF is built

The technology behind Split PDF: extracting page ranges in the browser with pdf-lib (PDFDocument.load → copyPages → addPage → save), and parsing a 1-based page range into the set of pages to keep.

Fix ZIP Filenames June 29, 2026

How Fix ZIP Filenames is built

The technology behind Fix ZIP Filenames: re-decoding garbled ZIP entry names from their raw Shift_JIS/CP932 bytes with the browser's TextDecoder, then re-writing the archive with the UTF-8 filename flag set, using @zip.js/zip.js.

ZIP Viewer June 28, 2026

How ZIP Viewer is built

The technology behind ZIP Viewer: listing a .zip without extracting it by reading only the central directory with @zip.js/zip.js (ZipReader.getEntries), and surfacing the UTF-8 filename flag — no decompression, no Web Worker.

Create ZIP June 27, 2026

How Create ZIP is built

The technology behind Create ZIP: building a .zip in the browser with @zip.js/zip.js (ZipWriter + BlobWriter, deflate), setting the UTF-8 filename flag so non-ASCII names survive on Windows, and the Web Workers zip.js uses for compression.

WebP to JPG June 26, 2026

How WebP to JPG is built

The technology behind the WebP to JPG converter: decoding WebP natively with an <img> element, encoding to JPG or PNG on a 2D canvas (with alpha matting and resizing), and the static Astro/Preact PWA shell — no WASM and no Web Worker.

HEIC to JPG June 25, 2026

How HEIC to JPG is built

The technology behind the HEIC to JPG converter: the HEIF/HEVC format, decoding it with libheif compiled to WebAssembly in a Web Worker, the OffscreenCanvas encode path with a Safari fallback, the Service Worker that makes it an offline PWA, and the Cloudflare Pages routing and caching details.