Let terminal text show its escape characters→
Build a plain-text diagnostic renderer that quotes untrusted fields, preserves the source data, and tests the bytes it sends to the terminal.
Field notes · No. 026
Senior DevOps Engineer building in public. Shipped features across Local Fitness, Ghostwriter, and more — the tradeoffs behind them, and what broke.
Build a plain-text diagnostic renderer that quotes untrusted fields, preserves the source data, and tests the bytes it sends to the terminal.
Preserve a validated report through failed refreshes, while recording the latest attempt separately so readers can see when retained data is stale.
Compare a transcription of the rendered graph with an evidence-backed graph, catching reversed, missing, and invented relationships separately from typography.
Store the selected reference, exact copy, prompt, and output identity together so a later image edit starts from a known candidate instead of a half-remembered chat.
Every post my ghostwriter sourced from 'trending topics' flopped, and the research behind that lane turned out to be one hardcoded curl. Here's how to build a real multi-surface trending sweep with nothing but the Python standard library, including the part most sweeps skip: refusing to call an empty result a quiet day.
A style rule an LLM applies to itself is a suggestion. This release moved every ban into a deterministic gate, calibrated so the author's own published posts define what a false positive is.
Every post my LinkedIn skill published stalled under 300 impressions, and the only way to prove it was a log a human fills in by hand. How to build a publish log, an outcome CLI with numeric fields, and the listing flag that keeps the data complete.
A publish approval is only consent if the full artifact is visible at the moment of the decision. The one-line fix, and the CI invariant that keeps the rule from being edited away later.
A voice system built from accumulated bans produces drafts that violate no rule and still read like incident reports. Why negative style specs bottom out at neutral, and how to write the positive register contract that gets past it.
A generated card read its version, date and install steps off the published release and got all of them right. The one figure a person copied from a design doc was stale by half.
Three releases of my LinkedIn ghostwriter went to interaction cost: one dialog instead of a chain of questions, silent setup instead of narrated bookkeeping, and generated visuals grounded in real command output after an invented one cost nine review rounds.
Every card my LinkedIn ghostwriter generated had the same skeleton, because a template plus an LLM equals the same output forever. The fix was a component vocabulary, a card-history file, and a rule that each new card must differ from the last three on at least two axes.
How to measure a rendered HTML page for overflow and truncation before a human ever sees it, and the CSS trick that lets one layout adapt itself to however much content lands in it.
Ghostwriter's voice profile and credentials lived inside its own repo, so a dev checkout and a marketplace-installed copy each needed their own setup. Moving personal config to the home directory collapses that back to one source of truth.
Ghostwriter moved from a manual symlink install to a real Claude Code plugin, which meant learning the marketplace.json/plugin.json schema and one directory-nesting rule that Claude Desktop enforces and the CLI quietly didn't.
A rendered card and its stylesheet are two files that only agree by convention, nothing in HTML or CSS enforces it. Here's how to write a fast, deterministic test that catches the drift before a render silently ships unstyled.
Ghostwriter's post images moved from a dark look to a light one by changing a single CSS class. Here's how to build a token-driven theme switch, render it to a deterministic PNG, and catch the one missing token that makes text disappear.
An agent that writes a confident claim will just as confidently skip checking it. Version 0.7.0 adds a code gate that blocks publishing until every outside-world claim traces to real, live sources, and it has to survive garbage input and a harness that lies about testing it.
Claude Code's own docs say its session transcripts are an internal format that can change on any release, yet discovering what you shipped recently has no sanctioned API. Here is how to read the logs anyway, defensively enough to survive the warning.
Ghostwriter's new matrix card type shipped as one new file, with no registry code touched. Here's how a naming convention can stand in for a plugin registry, and the test that keeps it from shipping half-wired or quietly duplicated.
Ghostwriter's image cards got pulled onto one shared base and grew a portrait carousel with a progress bar. The mechanism behind it, CSS custom properties feeding a headless-browser render pipeline, is a pattern you can reuse for any HTML-to-image or HTML-to-PDF generator.
ghostwriter v0.3.0 added carousel support: a headless-browser render step that turns HTML slides into a single PDF, and a publisher path that uploads it through a document API LinkedIn renders as a swipeable carousel. Here's the render-and-ship pipeline, built generic enough for any chunked document upload.
Compute bar lengths from one zero baseline, render a self-contained card, and check the browser geometry before exporting the PNG.
The ghostwriter skill's first versioned release made a markdown instruction file gradeable by CI and pushed its automation scripts to 100% line coverage. The two techniques worth stealing: a pass/fail scorer over frontmatter and body text, and monkeypatching a dependency your code doesn't even import until the function runs.
Ghostwriter can now attach a locally rendered diagram or card to a post, and the two techniques worth taking are waiting on a real completion signal instead of network idleness, and keeping personal styling in a gitignored file a stranger's clone never has to see.