DEV Community

Krish Verma
Krish Verma

Posted on

Ankita v2.5.2: startup that actually starts — a DOM probe, a strict CSP, and the blank window

Yesterday I shipped Ankita v2.5.1 and, about forty-six minutes later, v2.5.2 — a hotfix for a bug I couldn't leave standing: the desktop app launched to a blank window. Not a crash, not an error dialog. Just nothing, in both development and packaged builds. Your companion refusing to wake up is about as broken as it gets.

Here's what happened, what actually fixed it, and the test gap that let it through in the first place.

The symptom: a window with no app in it

The blank window was the tell. In Electron, a renderer that dies before React mounts doesn't throw something helpful in your face — the page loads, the CSP does its job, and the app simply never appears. It looked like everything was fine except the one thing that mattered.

The cause: a DOM probe hiding in a shared module

The release notes say it plainly: shared browser progress metadata imported a host-only DOM probe that compiles a function at module initialization. The renderer's Content Security Policy correctly refused it before React mounted.

Let me unpack that, because the shape of this bug is the interesting part:

  1. A module responsible for browser progress metadata needed a form-field limit — a plain constant, nothing DOM-specific about it.
  2. But it imported that constant from a module that also contained a host-only DOM probe — code that probes the DOM and, at module initialization time, compiles a function to do so.
  3. Importing the constant pulled the whole module in. The probe ran its function-compiling step during module init.
  4. The renderer's CSP — which correctly refuses unsafe-eval — blocked the compilation.
  5. React never mounted. Blank window.

This is the classic shared-module boundary failure. The constant was innocent; the module it lived in wasn't. Nobody wrote a line of bad code on purpose — someone put a pure value in the wrong neighborhood, and a later import carried the whole neighborhood along.

The fix: move the value, keep the security

The fix was a move, not a rewrite: the form-field limit now lives in the pure operation-policy module, and the original ref module re-exports it for compatibility so nothing downstream breaks.

Two deliberate non-changes matter here:

  • Browser batching is preserved. The metadata pipeline that needed the limit works exactly as before — only the source of the constant moved.
  • The CSP still refuses unsafe-eval. The temptation with CSP blocks is to loosen the policy. We didn't. The security posture is unchanged; the code moved to respect it instead.

That's the part I'm proudest of, honestly. A hotfix shipped under pressure is exactly when it's easiest to "temporarily" weaken a security boundary and forget to put it back.

The test gap: we were testing components, not boots

The old startup tests exercised an isolated component without CSP. A component test can't catch a module-init failure that only happens under the real policy — by construction, the thing that broke wasn't in the frame.

The new regressions mount both complete renderer entry points with their shipped HTML and CSP — the actual boot path, not a cut-down version of it. The focused startup/progress/browser recovery suite passes 33/33, including real Playwright and connected-Chrome form batches and stale-ref recovery.

The wider numbers: the full serial local suite reports 1,121 passed, zero failures (one pre-existing POSIX permission skip on Windows). TypeScript and Vite builds pass. The packaged ASAR was mounted in native Electron with the actual packaged preload and offline engine fixtures — zero renderer exceptions, CSP unchanged, executable reports 2.5.2.

What this hotfix deliberately doesn't do

Full honesty, since that's the point of this series: v2.5.2 does not address the connected-Chrome Windows CI navigation/cart failures recorded for 2.5.1. Publication used the locally checked Windows package, and no exact-commit remote CI pass is claimed. The full before/after traces, packaging checks, and remaining verification limits are in docs/desktop-startup-findings.md in the repo — go read them if you want the unvarnished version.

The question I'd actually like argued with

Here's the design tension I can't fully resolve: module-level side effects are the whole reason this bug existed (a function compiled at import time, invisible to every caller), and the standard advice is "don't do side effects at module scope." But in practice, real codebases — mine included — keep re-growing them, because a DOM probe that initializes once at import is convenient right up until it compiles a function your CSP forbids.

Do you police this with lint rules, architecture tests, or just discipline? And where do you draw the line on boot tests — full renderer entries with shipped HTML, or is that over-testing a path that rarely breaks?

Ankita is my open-source desktop AI companion — local-first, your models, MIT licensed. The repo is here, and the v2.5.2 release notes have the full verification detail. If you've fought your own CSP-vs-convenience battle, I'd genuinely like to hear how you drew the line.

Top comments (0)