Xircuit Blog

Product updates, developer logs, and news from behind the scenes at Xircuit.

A Home for the Dev-Log — and the Quiet Work Behind It cover image

A Home for the Dev-Log — and the Quiet Work Behind It


A Home for the Dev-Log — and the Quiet Work Behind It

A typewriter as a symbol for a blog of one's own Photo: Ian Livesey · StockSnap (CC0 1.0), via Openverse

Last time, I ended the weekly series — with a promise: the dev-log would continue as soon as there was a next big thing to tell. That thing is here now, and it has a nice double meaning. Because what I am writing about is the very place this text appears.

Until now the dev-log lived as a loose collection of Markdown files in the repository — written, but without a home of its own. This stretch gave it one: a standalone blog engine, cleanly integrated into the marketing website, bilingual at /de/blog and /en/blog. And because a move like that rarely stays free of surprises, this post tells both stories — how the blog came to be, right down into the SDK that carries it, and what happened in the quieter week afterwards, when a growing test suite brought three real production bugs to light.

A blog that carries itself

The obvious solution for a company blog is to somehow wedge it into the existing website — a few Markdown files, a renderer, done. I deliberately chose not to. The blog now runs on a standalone, hosted blog engine, and the Xircuit website embeds it through that engine's .NET SDK. It sounds like more work, and in the short term it is. But it cleanly separates two things that have nothing to do with each other: writing and managing posts on one side, serving the marketing pages on the other. The blog gets categories, tags, drafts, scheduled publishing, RSS and a media library without my maintaining a single line of it — and the website stays lean.

From the outside, none of this shows, and that is exactly the point. Posts appear at /de/blog and /en/blog, in the same layout as the rest of the site, with canonical URLs, sitemap entries, RSS and structured data for search engines. A request to the bare /blog redirects permanently to the matching language. The blog feels like part of the website — but it is a system of its own that only shows through in exactly the right places.

The devil is in the routing

Making two systems feel like one is work — and the friction, as expected, sat where website and blog touch. The first was language. The marketing website derives its culture from a /{culture} prefix in the path; the SDK's blog area, by contrast, brought its own notion and routed on a {lang} segment. The result: /de/blog did show the German posts, but with an English frame — navigation, footer, everything around it in the wrong language. The fix was a second culture provider that listens specifically to the blog's {lang} segment and derives the culture from it, alongside the existing one for the rest of the site. Two providers sharing responsibility, rather than one trying to guess both.

The second point of friction was images. Posts reference their images with root-relative paths — /media/blob/…, the way they live in the blog engine's media library. On the website those paths went nowhere, because there is simply nothing under /media there; the images live on the blog host. A small piece of middleware now forwards such /media requests to the blog host, so the images appear in the post even though they physically live elsewhere. The instructive part was an exception to that rule: the SDK's bundled stylesheet lives under /_content — the standard path for a Razor Class Library's static files. Without an explicit exception, the same forwarding would have sent that stylesheet to the blog host too, where it is not, and the page would have stayed unstyled. One-line exceptions like that are invisible until they are missing.

From a Bootstrap accident to semantic markup

The first version of the embedded blog looked wrong, and in a revealing way. The SDK shipped its markup in a Bootstrap style — classes and a structure that assume one particular CSS library. But the Xircuit website is built with Tailwind. Two design worlds that know nothing of each other: the result was a blog that worked but felt like a foreign body. My first instinct was to counteract it — a stylesheet of my own, scoped to .pn-blog, rebuilding the Bootstrap structure and matching it to the site's look. That worked, but it was essentially a reimplementation of someone else's assumptions: around 360 lines of CSS just to fight markup I did not want.

The clean solution was not more CSS, but the SDK itself. It gained a new option — a markup style that emits framework-free, semantic HTML: plain pn-* classes with no Bootstrap assumptions, plus a bundled stylesheet themable through CSS variables. With that option the blog could be switched to semantic mode and the brand tinted through a handful of --pn-* variables — and the 360 lines of reimplementation shrank to barely 60. That is the kind of change I like best: not more code that skirts a problem, but less code, because the problem disappears at its root.

A bridge in the fog as a symbol for connecting two systems Photo: Aaron Burden · StockSnap (CC0 1.0), via Openverse

Dogfooding all the way into the SDK

The attentive reader will have noticed: twice, the right fix was not to tweak the website but to extend the SDK — once for the configurable language route, once for the semantic markup style. That is no accident; it is a stance. The blog engine is itself a product from the same house, and Xircuit is one of its first serious users. The rough edges I hit here were, by the same token, the SDK's rough edges — and the cleanest repair was almost always the one made upstream, where the missing option comes from, rather than papering over it downstream.

Concretely, two SDK releases came out of this stretch on NuGet: one that brought the configurable language routing along with comprehensive SEO output, and a second with the semantic markup style. Xircuit consumes both. This back-and-forth — a real use case exposes a gap in the tool, the tool gets better, the use case benefits immediately — is the actual argument for dogfooding. And it had an unexpected guardian: a cross-platform bug in URL generation, invisible on my Windows machine but which would have produced relative instead of absolute URLs on Linux, showed up only in the Linux CI run — before anything was published. That is exactly why you build a pipeline that runs on several operating systems: so that the obvious does not become the deceptive.

The quiet work afterwards

With the blog live, things went outwardly quiet again — and that was precisely the ground for the second half of this stretch. I pushed the test coverage further into the last unobserved paths, continuing a pattern that had already proven itself: a shared scenario library that runs the same scenario once against the live platform and once against the local development environment. A scenario — say: switch the active organization, create a service, edit it and delete it again, or create a support ticket and reply to it — is described exactly once and executed in both worlds. That rules out a whole class of bug: the quiet drift between „how we test locally“ and „how it behaves for real“.

On top of that came a visible piece of administration: membership-lifecycle actions, right in the member lists. An administrator can suspend a member, reactivate them, remove them or change their role — across all industries, from gym to club, each of these actions backed by an audit entry and covered by component tests. Plus uniform delete buttons with a confirmation prompt in the configuration views. None of it is spectacular. But these are the everyday moves that make a platform livable — and covering them automatically meant actually running them once from start to finish.

Three real bugs the net caught

The reward for this effort came, as so often, in the form of red results that were justified. The first bug was the nastiest. In one path of the web app, a timeout guard from the resilience layer fired — the mechanism that aborts a hanging dependency after a deadline instead of waiting forever. So far, correct. Only, the handling ran in an async void context: a fire-and-forget flow whose thrown exception no one catches anymore. The orderly timeout thus turned into an unobserved exception, and it tore down not just the one request but the entire web host. A safeguard meant to keep the app alive brought it down in this constellation — the most insidious kind of bug, because it grows out of an in-itself-correct protection. The fix now catches the timeout where it arises and leaves the host standing.

The second bug was quieter, but similarly instructive. When the development environment starts up, test users from the fixtures were reconciled against Auth0, the identity provider — and that reconciliation wrote back on every start, even when nothing had changed. Many such write-backs became a storm: enough writes against the Auth0 Management API to stall the development API. The fix stops the superfluous write-backs for these fixture users — they are only touched now when there is genuinely something to change.

The third bug came, of all places, out of the scenario that had just served as a clean example: creating a support ticket and replying to it. The wizard an administrator uses to open a ticket on a member's behalf silently filed it under the wrong category — the platform level instead of the organization. The consequence was as inconspicuous as it was annoying: the ticket was created, but never landed in the queue of the gym team that should have handled it. No error, no red screen — just a request that ran into the void. Only because the scenario's local counterpart played the whole path from creation to delivery did the gap surface; the fix now files the ticket under the correct, organization-scoped category.

All three bugs share the same moral: they did not show up in production, where they would have been expensive, but during the deliberate exercising of the paths where they lay in wait. A test net proves its worth not by glowing green, but by tearing at the right spot.

What's next?

The dev-log now has a home, and the test coverage reaches a little further into the corners where bugs like to hide. Both are the kind of foundational work you cannot show off and yet feel every day. What comes next I do not quite know yet — but the dev-log now has its own place to tell it, as soon as the time comes.


Comments

No comments yet. Be the first to share your thoughts!

Comments are closed for this post.