I wanted a blog. The articles designed it.

Quick answer

I built a blog engine around the articles I wanted to write. By the fourth working day, the first article was live. The blocks and charts came from what the articles needed to show. Agents edit the content, and the pages already looked damn good. This page uses those same parts.

Updated
Reading time
14 min

Shepit is a dictation app. Building it gave me plenty I wanted to share as an engineer: technical challenges, details behind the features, and discoveries along the way. There were speech engines that lost parts of recordings, pauses we could trim, and noise we could see beneath a voice. I needed a blog that could put that work in front of a reader.

A page for the work

Articles had a practical job, too: people search for ways to dictate on a Mac, and an answer to a real question could bring them to Shepit. Holding a key, speaking and releasing it takes a few words to explain. Showing what happens to the recording takes more than prose.

I wanted an award-level page, with the same care for type, colour and layout as our product site. The site of Rebbix, our company, gave me another reference: strong chapter backgrounds and images crossing between them. The charts needed a place in that design. A grey wall of text would not do.

What comes with a ready-made blog

WordPress or Ghost would give me a publishing system to reshape. A headless content system such as Sanity would keep the content but leave the pages to us. Astro, a static site generator, would leave out the admin and database; the editing and publishing workflow would still be ours to build.

Building custom software with agents had become practical for me. For a fully styled blog with custom blocks, charts and generated covers, I would once have expected at least a month with a team of engineers and designers. Now I suspected that adapting a large publishing engine would take more work than building the parts I needed, while tying me to its design conventions, data formats, security updates and unused features. Agent editing in WordPress.com or Sanity did not remove that adaptation work.

I wanted the blog integrated directly into our site, with our design, blocks shaped for these stories, charts in one style, and tools for agents to edit it all. When an article needed a new component, we could extend the engine ourselves.

Visitors looking for WordPress

Shepit was a new product on a site only a few months old. Yet every day for a month, someone asked shepit.app for /wp-admin/install.php?step=1, looking for an unfinished WordPress installation. Shepit has never run WordPress.

I had noticed these scripts while considering the blog. The count below comes from a later check of 30 days of logs.

Across the site, admin and API, 7 September to 7 October 2026. Rounded from 19,298 requests; four real ad visits excluded.

WordPress was not even the main target: most requests hunted for secret files and Git settings left on a server by mistake. One client tried 254 different secret-file paths in 78 seconds. These requests met missing pages, a sign-in gate or an API refusal; some received the admin login page. They show what the scripts were looking for, not a hole they found.

What the scripts were looking for

Requests to addresses used in vulnerability scans, by target, 7 September to 7 October 2026

02.5k5k7.5k10k12.5kSecrets and config files12.3k · .env, .gitWordPress4.3k · never installed herePHP scripts2.3kDebug consoles and server status297Joomla, Drupal, Magento84Ghost1
02.5k5k7.5k10k12.5kSecrets and config files12.3k · .env, .gitWordPress4.3k · never installed herePHP scripts2.3kDebug consoles and server status297Joomla, Drupal, Magento84Ghost1
02.5k5k7.5k10k12.5kSecrets and config files12.3k1WordPress4.3k · never installed herePHP scripts2.3kDebug consoles and server status297Joomla, Drupal, Magento84Ghost1
  1. 1Secrets and config files: .env, .git

Counted on all 19,302 filter matches, including four real visits from an ad link whose address happened to match; the 19,298 total above leaves them out.

The counts by target
TargetRequestsDistinct clients
Secrets and config files12,320708
WordPress4,295521
PHP scripts2,305214
Debug consoles and server status297105
Joomla, Drupal, Magento8453
Ghost11

If a critical vulnerability in a publishing engine gives these scripts a way in before we patch it, we're fucked. They are already knocking every day. I did not want to add an engine, its plugins and their security updates to that workload as the product grew. So I chose a small engine integrated into our own site. Its code would still be ours to maintain.

The editor nobody here would open

My editor was already the conversation with an agent. I was not going to sit in a CMS editing paragraphs, so the blog's interface became a Model Context Protocol (MCP) server: tools an agent can call to read and change articles. Editors sign in with Google, and the server checks their editing role on every call.

An article is a list of blocks. Through the same tools, the agent can read the writing guide and the help for each block, with its fields, limits and layout options. Answers stay compact: what changed, the new revision and a preview link, rather than the whole article returned after each edit.

The blog also checks edits before saving them. I tested this by having the agent add a percentage from our server logs without explaining what was counted or over what period. The blog refused to save it. When the agent added the dates and request counts, the edit went through. A required explanation cannot quietly disappear just because an agent forgot it.

Each edit saves a complete version of the article. Publishing chooses which version readers see; a rollback chooses an older one. No article revision is ever deleted.

Ask the articles what they need

I sent agents to research possible articles and prepare briefs, including how each would present its evidence. Fourteen briefs came back. Between them, they requested 83 drawn visuals.

The requests included:

  • Timelines with holes where speech disappeared.
  • Before-and-after values joined on one scale.
  • Scatter plots with arrows showing dictionary effects.
  • Waveforms with pauses falling away like cut film.

Not one of the fourteen briefs asked for a pie chart.

Tables and side notes led the requests

Element requestedBriefs, out of 14
Tables12
Side notes11
Diagrams9
Key numbers with their conditions8
Actual input and output text8
Line and curve charts8
Step-by-step instructions7
Before-and-after charts7
Sound on a time axis6

Requests from 14 article briefs, 29 September 2026. Each brief counts once per row and can request several elements.

We turned the requests into a ranked list of content blocks and graphical elements. Then we took the first article, on choosing a speech engine, and built the high-priority elements it needed while we wrote it. Where a chart did not exist yet, the draft held its exact data until the component shipped. Once the article was live, we kept building from the list.

The article was specifying the engine while we wrote it.

Borrow the drama, keep the reading

I asked agents to study roughly twenty contemporary blogs and our company site, Rebbix. The reading lines in the long-form examples clustered around 62 to 76 characters; Rebbix ran about 85. I wanted its dramatic chapter backgrounds in a format that would also be comfortable through a long technical article.

The first two design directions separated those instincts. A had a long dark header and an image crossing into the next background. B put the article into a lighter editorial frame, with a clearer header structure. Neither became the whole design.

A: chapter backgrounds
Early mockup A for the speech-engine article: a long dark header with a large title, and a screenshot card crossing from the dark ground into the light one below.
B: editorial frame
Early mockup B for the same article: a light editorial header with the title in black, a pink display line and the quick answer beside the article details.
Two design directions for the same article, 29 September 2026. Neither shipped as drawn.

We took the chapter backgrounds and bridge from A, then brought B's header structure onto the dark ground. The text kept its narrower reading column while figures could spread out.

The result was a fixed frame around flexible blocks: a header, a body we could compose, and an ending with a purpose. The microphone at the top of this page is that crossing image: one object standing in two backgrounds, so you notice the change without a line or a box.

Six charts, three renderers

The chart requests looked like a job for a charting library. Apache ECharts and Vega-Lite are powerful tools; I wanted to know how much work it would take to make our charts fit our page.

We took six real examples from the briefs and drew each three ways: with ECharts, with Vega-Lite, and with an SVG recipe an agent wrote for the test. The data, colours and type rules stayed the same, at both a reading-column width and a phone width.

On the phone, ECharts squeezed the long-recording plot into a sliver. Vega-Lite left almost no plot beside an axis label reading 1,500.000000. Our recipe moved the labels above the rows and kept room for the data.

The same long-recording chart at phone width: our recipe keeps readable bars and labels, ECharts squeezes the plot to a sliver, and Vega-Lite leaves almost no plot beside an axis label reading 1,500.000000.
The long-recording chart, drawn three ways at 358 px phone width. Same data and theme; renderer test, 29 September 2026.

ECharts needed two to four chart-specific adjustments per example. Vega-Lite needed workarounds too, and some layouts still broke our type rules. Our recipe kept all six charts in theme at both widths on the first pass.

As with the blog itself, building a renderer for our needs looked like less work and cost than adapting a general one. The agent supplies the data and names; our renderer owns the colours, type, sizes and layout, inside a common chart frame. When an article later needed sound on a time axis, we added it to that renderer. The server sends each vector chart with its caption and data table already in the page, so readers and search crawlers can reach the evidence.

The timeline that lived 19 hours

Timeline was the first chart kind we shipped, built to show speech missing from the middle of a recording. About nineteen hours later, it was gone as a separate kind. Its drawing had worked; we had found a structure that could cover much more.

The missing middle, and the refusal that did announce itself

Three gpt-4o-mini tests from the speech-engine article; recording length in minutes

speech returned as textspeech missing from the textrefused0510152025 min900 s recordingHTTP 2001,200 s recordingHTTP 2001,500 s recordingHTTP 400
speech returned as textspeech missing from the textrefused0510152025 min900 s recordingHTTP 2001,200 s recordingHTTP 2001,500 s recordingHTTP 400
speech returned as textspeech missing from the textrefused0510152025 min900 s recordingHTTP 2001,200 s recordingHTTP 2001,500 s recordingHTTP 400

The first two returned HTTP 200 despite the gaps. The third refused the file outright. The speech-engine tests

The lengths, gaps and replies
RecordingMissing intervalAnswer
900 s200-660 sHTTP 200
1,200 s420-1,140 sHTTP 200
1,500 sfile refusedHTTP 400

Across the agents' requests, the same layout kept appearing. Categories ran down the left, grouped when needed. One shared scale ran across. Each row could hold a bar, a before-and-after pair, a range or spans with a gap. Seconds, dollars and percent changed the unit, not the structure.

About half of the chart requests in the briefs fitted that layout, and timeline's job moved into it with them.

On each row it can draw:

  • A bar, with a lighter tail for a second figure.
  • Spans in states, with gaps where something is missing.
  • A refusal, a dot with its range, or a before-and-after pair.
  • Events, plus zones and reference lines across every row.

Bars, spans, dots, pairs and events on one axis

Invented values to demonstrate the renderer

a spana gaprefusedan eventa dotto the high endto the low endbefore → aftera second figure0246810a reference linea zoneBarsA bar6A bar with a tail4 to 7SpansSpans with a gapA refusalPointsA dot and its range5Before and after2.5 → 6.5EventsTwo eventsstartstop
a spana gaprefusedan eventa dotto the high endto the low endbefore → aftera second figure0246810a reference linea zoneBarsA bar6A bar with a tail4 to 7SpansSpans with a gapA refusalPointsA dot and its range5Before and after2.5 → 6.5EventsTwo eventsstartstop
a spana gaprefusedan eventa dotto the high endto the low endbefore → aftera second figure0246810a reference linea zoneBarsA bar6A bar with a tail4 to 7SpansSpans with a gapA refusalPointsA dot and its range5Before and after2.5 → 6.5EventsTwo eventsstartstop

The zone from 4 to 6 and the reference line at 9 run across every row. These are illustrative values, not measurements.

The illustrative values
RowMarkValues
A barbar6
A bar with a tailbar and tail4 to 7
Spans with a gapspans0-3, gap 3-5.5, 5.5-8.5
A refusalrefusalto 7
A dot and its rangedot and whiskers5, from 3.5 to 7.5
Before and afterpair2.5 → 6.5
Two eventsevents2 and 8

The catalogue mockup had seventeen chart names. The engine ended up with three families: rows on one axis, series against two axes, and sound on a time axis. An article still asks for the picture it needs; the engine has fewer ways to draw it.

A price question needed two axes

The same article had another question: which speech engine cost less for a particular dictation? Price per audio hour could not answer it alone. Soniox billed for the terms sent with each request, so the answer depended on both recording length and dictionary size.

The brief proposed two sliders and a live calculator. I chose a chart: the price as the term list grows, with two lines for Soniox at different request lengths and GPT Transcribe's rate for comparison.

Where Soniox crosses GPT Transcribe's rate as the dictionary grows

Per hour of audio at 12 s and 25 s requests; September 2026 billing

per hour of audio27 terms65 terms00.25$0.5Soniox, 12 s requestsSoniox, 25 s requestsGPT TranscribeElevenLabs010203040506070terms in the dictionary
Soniox, 12 s requestsSoniox, 25 s requestsGPT TranscribeElevenLabsper hour of audio27 terms65 terms00.25$0.5010203040506070terms in the dictionary
Soniox, 12 s requestsSoniox, 25 s requestsGPT TranscribeElevenLabsper hour of audio27 terms65 terms00.25$0.5010203040506070terms in the dictionary

Soniox's lines are modelled from usage logs; the others are flat measured rates. The speech-engine comparison

The cost of an hour of audio, in dollars
Engine0 terms10 terms25 terms50 terms100 termsSoniox dearer past, 12 s / 25 s
Soniox, 12 s0.1220.1920.2660.3880.633-
Soniox, 25 s0.1130.1470.1830.2410.359-
GPT Transcribe0.2770.2770.2770.2770.27727 / 65 terms
ElevenLabs, with a list0.2630.2630.2630.2630.26324 / 59
ElevenLabs, no list0.2200.2200.2200.2200.22016 / 41

Follow the two pink lines. The shorter requests cross the fixed rate sooner: sending the same term list more often costs more. The picture shows why a single price per hour was not enough.

XY became our second chart family: different marks sharing the same two axes.

It can combine:

  • Lines and the ranges around them.
  • Separate points, with arrows from before to after.
  • Columns, grouped or stacked, including values below zero.

Lines, points, columns and change on one chart

Invented values to demonstrate the renderer

Series oneSeries twoChangeRangeLineSample set 1Sample set 2Before → AfterIllustrative value0510123Jun 1, 2026Jun 4Jun 6Jun 8Jun 10Jun 12Jun 14Jun 16Jun 18Jun 20Jun 22Jun 24Jun 26Jun 28Jun 30Illustrative day
  1. 1Columns stacked from two parts
  2. 2One thing, before and after
  3. 3A line with its range around it
Series oneSeries twoChangeRangeLineSample set 1Sample set 2Before → AfterIllustrative value0510123Jun 1, 2026Jun 5Jun 8Jun 11Jun 14Jun 17Jun 20Jun 23Jun 26Jun 30Illustrative day
  1. 1Columns stacked from two parts
  2. 2One thing, before and after
  3. 3A line with its range around it
Series oneSeries twoChangeRangeLineSample set 1Sample set 2Before → AfterIllustrative value0510123Jun 1, 2026Jun 9Jun 15Jun 22Jun 30Illustrative day
  1. 1Columns stacked from two parts
  2. 2One thing, before and after
  3. 3A line with its range around it

These are illustrative values, not measurements.

The illustrative values
MarkDays, JuneValues
Line1-305.4 rising to 8.5
Range1-30the line ± 1.2
Points, set 130 points, 1-304.8-9.9
Points, set 230 points, 1-304.5-8.8
Stacked columns3, 8, 132.5 + 1.5, 2 + 1, 3 + 1.2
Before → after18 → 221 → 3.8

This family took about two hours of planning and another three to four hours of agent work, as I remember it. We could compose these combinations in our own style, without designing each as a separate chart type.

Make sound visible

The silence-trim brief asked for removed regions falling away like cut film. The third chart family, sound, could show the original recording and a shorter copy at the same scale. On dark, its voice bars are gold, like Shepit's on-screen recording indicator.

One dictation before and after silence trimming, on one scale

A real 24 s recording; pink regions removed, with the shorter copy below

speechnot speechremovedbefore: 24.0 s06121824 safter: 19.3 s, 20% shorter06121819.3 s
speechnot speechremovedbefore: 24.0 s06121824 safter: 19.3 s, 20% shorter061219.3 s
speechnot speechremovedbefore: 24.0 s06121824 safter: 19.3 s, 20% shorter061219.3 s

The silence-trim article

What was removed
Removed interval, sRemoved, s
0.00-0.380.38
1.24-1.340.10
8.54-10.542.00
19.96-21.621.66
23.44-24.000.56
Total4.70

This is a real 24-second dictation of mine. The pink bands are what the trimmer removes. Under the axis, the same recording appears without those regions and stops at 19.3 seconds. The film shows where the seconds came from.

I had cut the spectrogram as overkill when the catalogue held too many candidates to judge. But the noisy-room brief had a precise question: where did the room's rumble live beneath the voice? I brought it back as a lane under the waveform, on the same time axis. Here is a seven-second window from a different recording, before and after a filter.

Where the room's rumble lives, before and after a 100 Hz high-pass

Seven seconds of a real dictation: time across, frequency up, brightness the energy

the recordingas the microphone heard it502001k4 kHzwhere the app listens1after a 100 Hz high-pass502001k4 kHz100 Hz high-pass29303132333435 s
  1. 1No speech here: the stripe stays
the recordingas the microphone heard it502001k4 kHzwhere the app listens1after a 100 Hz high-pass502001k4 kHz100 Hz high-pass29303132333435 s
  1. 1No speech here: the stripe stays
the recordingas the microphone heard it502001k4 kHz12after a 100 Hz high-pass502001k4 kHz329303132333435 s
  1. 1No speech here: the stripe stays
  2. 2where the app listens
  3. 3100 Hz high-pass
Measurements over the full recording, about 60 s
MeasureAs recordedAfter the 100 Hz high-pass
Loudest band53 Hz, -19.88 dBFS85 Hz, -29.25 dBFS
Gate floor in the voice band, int16 RMS50.1847.34, about 0.5 dB lower

These two examples use measurements from my recordings: the blog engine receives the numbers, not the audio, and the agent chooses the clip and the window to show.

Three ways to make a cover

From the article

An article already has things worth pulling out: a chart, a highlight, a key number. Our engine can turn those accents into covers. Here are the options it made for this article, alongside its category default.

From a chart
A cover drawn from section 08's missing-middle rows chart.
From a highlight
A light cover drawn from the article's main highlighted sentence, with a pink rule.
From a count
A light cover drawn from the 19,300 scanner-request highlight.
Category default
The dark signal pattern used as the Deep dive default.
Four options for this article: three from its blocks, one category default.

Defaults with variety

Fable proposed six designs, one for each category. The engine varies them using an article's address. Even when we choose no custom cover, the article list has colour and variety.

Deep dive
The Deep dive default: a dark signal plate of gray bars with a few gold ones.
Features
The Features default: a dotted microphone with a gold crescent on soft tiffany.
Comparisons
The Comparisons default: rows of open, ringed and filled dots on gray.
Guides
The Guides default: a three-step progress line on light, the middle step current.
Overview
The Overview default: a grid of rounded squares on yellow, a few marked.
Notes
The Notes default: three rounded lines of different lengths on green.
Six default designs, rendered using this article's address.

The microphone design below is one pattern, drawn for four different article addresses.

Silence trimming
One large microphone with a gold head on soft tiffany.
Superwhisper comparison
A field of small outlined microphones with one filled in gold, on soft tiffany.
Speech engines
A microphone drawn in dots with a gold dotted fill, on soft tiffany.
This article
A dotted microphone whose head scatters into a gold crescent, on soft tiffany.
One Features pattern, four article addresses.

Drawn for one article

For a cover beyond those options, I can ask an agent to work with Fable: take this accent from the article and make something with it. The first article's cover, in the next section, came from that route.

The chosen cover appears on the blog card, the author's page and the share image.

By the fourth working day

The first speech-engine article went live on 4 October. It had real blocks and rows charts, on a page I could edit through agents, in our own design.

A dark scoreboard of six speech-to-text engines, showing results and which engines were kept or cut.
The first article built on the new engine, live on 4 October. Its current cover was drawn by Fable from the article's scoreboard.Source: shepit.app

From the first decisions to a live article

Calendar milestones, including weekend breaks; the chart families kept growing after first publication

corelater additionsSep 29, 2026Sep 30Oct 1Oct 2Oct 3Oct 4Oct 5Oct 6first article liveAgent editingPages, covers and searchBlocks and chartsxy, sound
corelater additionsSep 29, 2026Oct 1Oct 2Oct 3Oct 4Oct 6first article liveAgent editingPages, covers and searchBlocks and chartsxy, sound
corelater additionsSep 29, 2026Oct 2Oct 4Oct 6first article liveAgent editingPages, covers and searchBlocks and chartsxy, sound

Tracks run from each area's first to last release milestone, including breaks; dates do not measure hours worked.

The release milestones
MilestoneKyiv timeKind
Roles and sign-in29 Sep 18:13code merge
Content store and editing tools30 Sep 05:22code merge
Plain public pages30 Sep 10:52code merge
Editorial workflow1 Oct 18:10code merge
Timeline chart1 Oct 21:00code merge
Rows replaces timeline2 Oct 16:18code merge
List, author pages, covers4 Oct 01:28code merge
First article live4 Oct 12:32publication
Search and feeds4 Oct 16:38code merge
xy chart4 Oct 17:43code merge
Price chart replaces its placeholder4 Oct 20:10article draft
Price chart live in the article5 Oct 17:04publication
Sound, with the spectrum lane5 Oct 23:50code merge

Work continued that evening: the two-axis chart replaced its price placeholder. That chart went live with the next day's revision, and the sound family joined the engine that day too.

The speech-engine, silence-trim and long-dictation articles are live. The engine has article pages, the blog list, author pages and cover tools. New components go straight into the agents' editing tools.

Only now do I see that we built more than a blog. With the same blocks and a little more work on the engine, we can build any custom landing page on our site. Our marketing team could build those pages themselves: working with their own agent on the messages and angles that bring people to Shepit, they would publish through the same MCP server, with no coding skills and no access to the repository.

This article asked for a grid

This article needed one more block. I wanted readers to compare its cover options, the category defaults and variations of one pattern side by side. The covers belonged in grids, where their differences would be visible at a glance.

So we added an image grid to the blog engine. Planning it with an agent took about half an hour. Fable's visualisations were good on the first pass; I had no changes. Opus implemented it in about two hours.

In two to three hours, we had a new block the agents could use in any article through our editing tools. Owning the blog made the addition possible; working with agents made it this quick.

This page is built from the engine's parts, and it added one of its own. I needed a blog, and building exactly the one I wanted was faster and simpler than adapting someone else's. It came out fucking awesome.

Personal software, built on demand for exactly what you need. The future is already here.

Written by

Oleksij Rak

Co-founder of Rebbix, maker of Shepit

Engineer for 30 years. Started Shepit to fix his own dictation and builds it hands-on, from the speech pipeline to the tools behind this blog.

Published Updated