How it's made

One person, one repository, four products.

The IDE you design in, the runtime underneath it, the player that is the executable of every application you build, the server and the client that pair with it — and this website, the manual and the example projects. All of it out of one folder, written with an AI and decided by a person. This page is the inside story: how it fits together, how that arrangement is kept honest, and two afternoons where the numbers moved.

92,000lines of Swift, in 264 files
27,000of them are tests
4products from one build
1person

Everything is in one place

There is no separate team for the server, none for the website, none for the docs. The repository holds one Xcode project and one Swift package, and everything else is a small tool beside them — the manual is generated, the example projects are generated, the website's pictures are generated, even the application icon is drawn by a program.

That is a constraint, not a boast. One person cannot maintain two of anything. So there is one list component and the data browser draws it too; one printing engine and the quick print from a list goes through it; one shell behind find, sort, import and export. Every place where a second version of something could have appeared, it did not — because there would have been nobody to keep the two in step.

PartWhat it is
RuntimeSQLite, schema, migration, the JavaScript bridge, forms, reports, transfer, the editors
IDEthe designer, the debugger, the build pipeline
Playera small host — the binary of every application you build
Server + Clientthe same payload, one holding the data
Website16 pages, checked by a script
Manualgenerated into the site and into the IDE
Examplesgenerated, never hand-edited

There is no compiler in the build

This is the decision the whole shape rests on. A finished application is our precompiled player — one binary, built and signed by us once — with your project beside it as an encrypted payload. Building copies, encrypts and signs.

Seconds, not minutes

A test build of a real project finishes in about two seconds. The two minutes a release build takes are almost entirely Apple's notarization, not ours.

A fix reaches everything

The runtime is linked by the IDE and by the player. A bug fixed there is fixed in the designer, in Run, in every application rebuilt afterwards, and in the server — at the same moment.

One rule decides where code goes

Anything a running application needs lives in the runtime, never in the IDE. That is why the find window, the import window and the report editor exist inside the applications you ship, not just in the tool.

Written with an AI, decided by a person

Most of this code was written by an AI, working from decisions a person made and under review by that person. That is a plain fact about how Neuridion is built, and it is written here rather than left to be guessed at.

It is also the honest answer to the obvious question — how does one person build an IDE, a database runtime, a client/server layer, a website and a manual? This is how. The interesting half is not that an AI writes code; it is what has to be true for that to be safe.

Every claim is checked by a command

The project's own notes say how many tests there are, how many warnings the build has, how many German strings are allowed to remain in an English product. A script compares each number against reality and fails when they differ. A number nobody verifies drifts — three of them did, on one day, before this existed.

A new test has to be proven to bite

After a fix, the fix is deliberately broken again to watch the new test go red, then put back. Twice a test written after a bug would have passed with the bug still in place. Once — this week — a test looked right and proved nothing, because the two names it compared happened to sort correctly anyway.

Nothing is deviated from quietly

Decisions are written down with their date and their reason. If something turns out to be impossible, the work stops and the person is told — instead of a slightly different thing being built and never mentioned.

Measure, do not guess

When a diagnosis is a guess, the answer is a spike: the smallest possible program that settles it. Both stories below started that way, and one of them overturned what everybody assumed the problem was.

What that does not mean. It does not mean nobody reads this code — every line is reviewed, and the decisions in it are a person's. It does not mean fewer tests; it means more, because tests are how a machine's work is held to account. And it does not mean the mistakes below were avoided: they are all real, they were all found here, and they are why the guard rails exist in the shape they do.

A story

The attachment nobody was looking at — and how a database became two files

Neuridion stores a scanned invoice in the record itself: no media folder beside the data, nothing to lose when somebody moves a directory. The question is what that costs when a list shows two hundred rows and nobody wants to see a single scan.

The answer, for a long time, was everything. Reading records compiled to SELECT table.*, so every row arrived with its documents attached, whether or not anybody looked. The laziness everyone believed in was one floor too high: it sat at the bridge — a script was carefully handed an object without the bytes — while the bytes had already been read from disk, decrypted, and put in memory to build that object.

A spike settled it: three hundred rows, a five-megabyte scan on each, a database of 1.5 gigabytes, read once the old way and once with file columns left out of the query.

Reading 300 recordsBeforeAfterFaster by
Plain database0.376 s0.001 s376×
Encrypted (SQLCipher)1.725 s0.002 s862×
1 ms 10 ms 100 ms 1 s plain encrypted Logarithmic — each dashed line is ten times the one before it. 0.376 s 0.001 s 1.725 s 0.002 s
The bars are on a logarithmic scale, because on a linear one the two fast bars would be invisible — which is rather the point. And the comparison is conservative: the old figure does not include building the records that the new figure does.

Why the database is now two files

Making the read lazy raised the next question: where do those bytes live? Leaving them in the row means every page SQLite touches drags megabytes past the disk head. So the documents moved into a sibling databaseCustomers.files.sqlite beside Customers.sqlite — attached to the same connection with the same key.

One transaction, still

Attached to the same connection, a write that touches a record and its document is one atomic transaction. Two files, one promise.

Encryption comes free

The sibling uses the same key. There is no second thing to configure and no way to end up with an encrypted database and plaintext scans.

The list never opens it

The record's own column keeps the envelope — name, kind, dates, size — so a list draws “Contract.pdf · 2.4 MB” without touching the second file at all.

No file fields? No second file

A project without documents has exactly one database, as it always did.

The idea that was designed and then thrown away. Storing each document under the hash of its content would have deduplicated identical files — and made changing a document a birth: a new row, an old row that is now rubbish, and therefore a garbage collector, and therefore a reference table to know what is still in use. One-to-one ownership instead: replacing a document is an update, deleting one is immediate, and there is nothing to collect. The price is no deduplication, and it is deliberate.
And the trap it set. SQLCipher's export function defaults its source to the main database. Written the short way, a backup would have copied every record and silently left every document behind — verification passes, and you find out on the day you need the backup. Four tests now stand in front of that one line.

Another story

We tortured the server until it stopped answering

A MacBook Air as the server, a MacBook Pro flooding it over Wi-Fi with real paired clients — lists, counts, sums, grouped aggregates and writes, in a tight loop with no think-time at all. Far harsher than any office; the point was to find the edge.

Concurrent clientsRequests / secondTypical answer
15712 ms
1037018 ms
2553934 ms
5066562 ms
100712114 ms
200600262 ms
4002711.1 s
800 600 400 200 1 10 25 50 100 200 400 Concurrent clients — the spacing is the staircase of the run, not a linear axis. 712 / s
Read the middle of the curve, not the end. Somewhere around fifty clients the server answers well inside a tenth of a second; the plateau near 700 requests a second is headroom, not a promise — how far it really goes depends on what each client asks for.

Then it stopped accepting anybody

After a hard flood the server went deaf. New clients hung at Connecting…, the status window barely refreshed, and only a restart helped. The obvious suspect was the single serial lane that keeps every write and every table event in one honest order — saturated with backlog, surely.

It was not. The status window told on the real culprit: 189 connections still listed, minutes after the last client had gone, trickling about two requests a second between them. They were dead — clients killed, Macs asleep, a FIN lost over Wi-Fi — and nothing ever noticed, because the listener ran on plain TCP with keepalive switched off. The receive path only ever sees a clean goodbye; the sweep deliberately left quiet-but-established connections alone. So the sockets sat there holding file descriptors until there were none left to accept with.

Keepalive, and a dead peer is gone in 25 seconds

Ten seconds of quiet, then three probes five seconds apart. A living idle client answers them in its own TCP stack and stays; a dead one is reclaimed. The wedge disappeared with it.

The refusals were a second bug, not the same one

52 connections turned away at 240 clients looked like the same story. It was accept starvation: handshake cryptography and database serving shared the one lane, so every new client queued behind everybody else's queries.

A queue per connection

Receiving, handshake, sealing and sending now happen on each connection's own queue. Only the answer itself — the SQLite writer and the order of table events — stays on the central lane, and accepting has a queue of its own.

Sealing is a chain that must not be broken

The encrypted channel counts a nonce per sealed frame, so answers and pushes of one connection have to be sealed in a single serialised chain. The per-connection queue is exactly that chain — a hard constraint, written down where it can be read.

And the bug that the test found while it was being written. Pushes were being sent to every connection that had a channel — including one still in its handshake, before the client was listening for sealed frames. The nonce counters drifted apart and the client cut a perfectly healthy connection. Pushes now wait until a connection has said hello. The test that found it drives 32 desks at once, write-heavy, and it bites.

What the whole exercise bought: zero failed requests and zero refused connections through 400 clients hammering with no pause; the edge, where requests finally begin to time out, sits around eight hundred such clients. And the behaviour that matters more than any number — it slows down, it does not fall over, and it heals itself without a restart.

And at the end

The tests, and the commands that keep the story honest

1,160tests in 197 suites, every release
177test files
0warnings tolerated in the IDE
1command runs the lot
Every feature arrives with its own tests

Not as a rule somebody remembers — as the way the work is done. Sound arrived with ten tests, the update check with thirteen, computed fields with six suites. The tests are what makes the next change safe, which is the only thing that matters to somebody already using the version before it.

A fix is not finished until its test fails without it

After a bug is fixed, the fix is deliberately broken again to watch the new test go red. Twice a test written after a fix would have passed with the bug still in place — which is a test that proves nothing and reassures everybody.

Two drawings of one object are counted, not eyeballed

The designer canvas and the running window are separate renderers. A test counts the settings each one reads: anything the window honours and the canvas ignores fails the build. Four rounds of reading the two files side by side had missed what counting found in seconds.

Every script of every example is checked

One command runs all ~85 scripts of the shipped projects through the real engine: does it parse, does every table and field it names exist, can the debugger rewrite it. The first time it ran it found fifty-five complaints — every one of them a bug in the checker, under correct code.

The interface is tested by driving it

Some faults exist only while the program is running — a keystroke that never reaches its handler, a sheet that swallows every click. Those are found by a test that opens the application and uses it, not by reading the source.

The pictures on this site are taken by the machine

They regenerate with one command, so a picture cannot quietly go on showing a version that no longer exists — which is exactly what had happened to the set before them. A second command checks that every picture a page names is actually there: it found a chapter of the manual pointing at a file nobody copied, in the shipped product, with nothing failing over it.

The lesson that cost the most time

A test that measures the machine is worse than no test. Four of them wobbled under load — and three had one cause, which was not the clock: the thing they waited for is delivered on the main queue, which an application services constantly and a test process services only by luck. They passed on an idle Mac and failed while something else was compiling. The cure was an injectable delivery queue and waiting for a state instead of a duration; the proof is the suite run three times with four processes deliberately burning CPU.

What this does not claim. Tests do not make software correct; they make it hold still. The honest promise is narrower and more useful: what worked in the version you have will still work in the next one, and where something does break there is a failing test with your name on it rather than a shrug. That is the whole reason to accept an update from a one-person product at all.