blog · Artikel auf Englisch

Does the architecture fit? An architecture review of Snotra

A code review asks whether the code is right. An architecture review asks whether the structure fits what the app has to achieve. How I did it for Snotra: an interview for the quality goals first, then 29 scenarios checked against the code, the decisions written down, and 38 issues.

Over the last weeks Snotra went through a code review in eighteen blocks: 118 findings, block by block, all asking the same question — is this code correct and clean? What that review never asked was whether the blocks themselves were cut in the right places. It took the architecture as given, because it used the architecture as its grid.

So the next step was the question one level up: does the architecture Snotra has fit what Snotra has to achieve? That question has a catch. You can’t answer it from the code alone, because “fit” needs something to fit to. Without written quality goals, every judgement about an architecture is a matter of taste.

Two phases, strictly in this order

The issue behind the review split it into two phases, and phase 2 was not allowed to start before phase 1 was agreed.

  1. Elicit the quality requirements. A structured interview, not a document drafted in advance and handed to me for approval. arc42 chapters 1, 2 and 10 served as orientation: context and constraints, quality goals, quality scenarios. The result is quality-requirements.md, written on October 15 and agreed the day after.
  2. Evaluate the architecture against them. ATAM-light: each scenario was mapped to the architectural approaches meant to serve it — layering and ports, the composition root, IPC contracts, the process model, the permission system, storage, self-update, the renderer — and rated supported, partly supported, at risk or not built, each time with the evidence in the code. The result is architecture-review.md.

A full ATAM brings the stakeholders together in workshops. For a solo project that is oversized: the only person at the table is me, so the interview replaces the workshop. The evaluation itself ran in four strands in parallel — security and privacy; robust operation; the separable core and the documentation; usability, parity and performance — each reading the source, the docs, the manual and the CI history. The claims the high findings rest on were checked again by hand.

The report also names its limits, and I think that belongs in a review: nothing was executed, one census of error handling is a heuristic, and not a single performance number in it is measured. The last one became a finding of its own.

What the interview asked

The interview started with the people the system is for. Four groups came out of it: public users, who download the app and have to get along without me; me, keeping a large code base changeable alone; the AI agents that write much of the code and have to find their way through the documentation; and future consumers of the core — a headless mode, a CLI, a mobile app. Then came the questions whose answers shape an architecture:

QuestionAnswer
Who is Snotra built for, first of all?Public users
Which platforms rank equally?All three: macOS, Windows, Linux
Which providers must work?Every provider the app offers
How serious is separating back end and front end?A concrete goal: headless, CLI, mobile
A feature can’t reach every platform in time — what then?Ship it where it works, show the gap, track it in an issue
Update check: security or privacy?Automatic by default, but switchable
Faster in main or the renderer, or cleaner through the core?Always through the core
May a feature send data to a service the user did not set up?Only after consent
Is performance a quality goal?Yes, with budgets
Remote control: how far may it reach?Localhost only
Which failure is the worst?Silent misbehaviour
What is Snotra not?A multi-user server, or an IDE

The most important answer was the ranking, because it decides what wins when two goals collide:

  1. Agent security — the agent can’t do anything the user did not allow.
  2. Usability and accessibility — a public user gets along without help, WCAG 2.1 AA in light and dark.
  3. Privacy — data stays on the machine and with the services the user set up.
  4. Robust operation — updates, broken files and failed runs never cost data, and nothing goes wrong silently.
  5. Separable core — the application core runs without Electron.

Below these five, and still binding: safe changeability, portability and parity, a bilingual interface, and performance. Each goal was then made checkable in scenarios of the form stimulus → response → measure — 29 of them. One example, SEC-3: a safety mechanism can’t be established, say the OS keyring is missing. The app falls back to the stricter state and says so. The measure: no path that fails open, every degraded state visible. A scenario like that can be checked against the code; “the app should be secure” cannot.

The architecture at a glance

What was evaluated, roughly: an Electron app with a sandboxed renderer, a main process that holds every secret and decides every access to the outside, and inside main, layers whose dependencies point inwards to a core without Electron. The tags mark where the review found the largest risks.

Snotra Agent at a glance On the user’s machine: the renderer with the app window, the HTML preview and the manual window on the left; the main process in the middle with, from top to bottom, IPC handlers, the composition root, services, adapters, tools and providers, the application core and the shared modules; on the right the execution sandbox and the local store. Outside the machine: LLM providers, OpenAI services, the web, MCP servers and GitHub Releases, reached only from the main process. Tags name the findings AR-06, AR-31, AR-08, AR-07, AR-29, AR-15, AR-10, AR-17 and AR-12. Snotra Agent at a glance Containers on the user’s machine, the layers inside the main process, and where the review saw the largest risks The user’s machine — no server of Snotra’s own Renderer app window, Chromium Chat, file tree, file views, settings Still holds the state of the conversation AR-06 Preload · window.electronAPI HTML preview own session, sandboxed Manual window own preload and channels IPC Main process Node.js · holds every secret IPC handlers AR-31 Composition root AR-08 Services, adapters, tools, providers Most of the logic lives here AR-07 Application core chat engine, permissions, ports AR-29 Shared contracts, runtime, i18n Execution sandbox shell and Python Seatbelt on macOS, bubblewrap on Linux Windows: none AR-15 Local store JSON files in userData secrets via the OS keyring (safeStorage) History: one blob AR-10 HTTPS · stdio Outside — reached only from the main process LLM providers cloud or local OpenAI services images, voice input The web search, URL fetch MCP servers set up by the user no sandbox AR-17 GitHub Releases update check no switch AR-12 AR-nn finding of the review, one issue each depends on / talks to Created 2026-10-17 · Claude Opus 5.5 · Reasoning effort: high
Snotra Agent as of the review (v1.23.0): containers on the user’s machine, the layers inside the main process, and where the largest risks sit. Simplified; the full picture is in architecture.md.

What came out

Of the 29 scenarios, two are supported, fifteen partly supported, ten at risk, and two are not built yet. Per goal:

GoalVerdictIn short
1 · Agent securitypartly supportedA mature design, with two paths that fail open
2 · Usabilitypartly supportedAccessibility checked by hand on macOS only
3 · Privacypartly supportedNo telemetry; the update check couldn’t be switched off
4 · Robust operationat riskNo safety net against silent misbehaviour
5 · Separable coreat riskThe core holds about 5.3k of 34k lines of backend logic
Safe changeabilitypartly supportedA real gate on three platforms, but too many red runs
Portability and parityat riskGaps between providers and on Windows
Bilingual interfacepartly supportedA third language would need code changes
Performanceat riskNothing was measured

The finding that hurt most is goal 4. The interview had named silent misbehaviour the worst failure — and there was no safety net for it: no global error handlers, no log file, and a chat that could not be saved was lost on quit without a word. The other surprise was goal 5. The import guard holds, so the core really knows nothing of Electron. But it is small, and the renderer, not the core, owns the conversation. A CLI built today would have to take most of the main process along.

The decisions

An architecture review is also a good moment to write down decisions that so far only existed in the code or in my head. Three kinds came out of this one.

Rules for conflicts, from the interview. These override the ranking where they apply:

  • Security beats usability. A softer setting may be offered per workspace, but it is never the default.
  • Privacy has a floor. No data goes to a service the user did not set up before they agreed — with the service named and the consent revocable.
  • Parity: ship, and show the gap. A feature that can’t reach every platform or provider in time ships where it works; the gap is visible in the UI, named in the manual and tracked in an issue.
  • Logic always goes through the core, even when building it in main or the renderer would be faster. The ranking decides what the product does; this rule decides where the logic lives.
  • Remote control stays on the machine. Headless and mobile front ends reach the core over localhost only.
  • Prompt-injection detection is an extra layer, never the protection. Isolation and approvals protect; a detector may only warn.

The founding decisions, questioned for the first time. The code review had treated them as given. Here they were open:

  • Electron stays. It costs memory and a self-built updater, but it serves parity across platforms and offline operation, and it does not block the separable core: only four files in the main process import it. The rule to record: Electron is the shell, the core runs on plain Node.
  • Plain JavaScript stays — no build step — but the core, the ports and the contracts get type checking through tsc --checkJs, because drifting contracts showed up in five blocks of the code review.
  • A renderer framework is still open. About twenty findings of the code review — redraw, focus, races — are the cost of going without one. But a framework wouldn’t fix who owns the conversation, so that move comes first, and the framework question gets its own issue afterwards.

The order of the work. Severity first, then the rank of the goal, cheap fixes first where the risk is equal: the two small high findings, then a safety net against silent misbehaviour, then measuring before optimising, then the separable core in a fixed sequence — checked ports, an Electron-free composition, confirmations behind a port, the conversation into the core, the rest of the logic after it.

38 issues

As with the code review, every finding became an issue of its own, AR-01 to AR-38, each linked to the review issue and with its own definition of done: 10 high, 25 medium, 3 low. Two further findings were already covered by existing issues and were not filed again. Fixing them is a separate step, prioritised like any other work — the review only says what and in which order.

That separate step started right away. A day after the report, 13 of the 38 are closed, among them both of the small high fixes, the safety net against silent misbehaviour, a running turn that is now saved after every finished step instead of only at the end, a switch for the update check, and a harness that measures the performance budgets instead of guessing them. Twenty-five are open, the separable core among them, which is the longest stretch of the road.

The review also says when to look again: before the first second front end is started, once the performance numbers are measured — and otherwise in six months, or as soon as a quality goal changes.

If you want to look closer

Everything is in the repository: the quality model with the full interview record, the review with every scenario, trade-off and sensitivity point, and the issue that links all 38 findings. If you maintain a project of your own, I’d be curious whether you write your quality goals down before you judge your architecture, or only afterwards — the Discussions are open for it.