# NumeraBooks 2.12.0 — deactivated customers, dates you can type, and the multi-user deadlock

> **Read this first.** You were running **2.10.0**. The 2.11.0 and 2.11.1 installers were built on
> 27–28 August but never run, so none of that work was on your machine — that is why the reconcile
> screen still showed reconciled transactions. This installer contains 2.11.0, 2.11.1 and 2.12.0
> together. Install it over what you have.

---

## "there is deactivated customers but still seeing in our software and no option to hide them"

Correct, and only in the Center. The **list view** has had a Show/Hide inactive button for a long
time. The **Customer Center** never did: it asked for every name including the deactivated ones and
merely dimmed them to half opacity. On your converted file that is hundreds of dead names in the
list you scroll every day — and the heading counted them, which is where "Customers (400)" came
from.

There is now a **View** selector above the search box:

| | |
|---|---|
| **Active only** | the default, as in QuickBooks' Customer Center |
| **Inactive only** | so a deactivated name can still be found and reactivated |
| **All** | the old behaviour |

The heading count follows the filter, and a line under the selector says how many are hidden, so
you are never looking at a filtered list without knowing it. The same applies to the Vendor Center.

**Two things it deliberately does *not* do.**

Deactivating a customer hides its **jobs** as well. A dead customer with three of its jobs still on
screen is how you end up reactivating the wrong record.

Hiding a name does **not** change any figure. A deactivated job can still carry an open balance, and
that balance is still owed — so the "including N jobs" rollup on the customer reads every job,
hidden or not. Changing the View selector changes what you see and nothing else.

---

## "not able to change date and it is accepting these years also (0072?)"

Both halves of that are one bug, and it is the reconcile date bug — RCN-501 — living in six more
screens.

Chromium decides an `<input type="date">` has "changed" the moment all three segments hold any value
at all, which happens on the **first** keystroke of the year. Typing `2026` into an empty year
completes the field at year **0002** and fires the change handler right there. Every one of these
screens then rebuilt the whole card — including the box your cursor was sitting in. The rest of what
you typed went nowhere, and what survived was whatever partial year was in the box when it was
destroyed: 0002, 0072, 0202. Hence both symptoms at once: the date could not be changed, and the
report cheerfully ran "as of 05/05/0072".

Affected: **A/R Aging, A/P Aging, Trial Balance, Balance Sheet, Balance Sheet Comparison, Payment
Reminders, Sales Tax by Agency**.

Fixed in two halves.

**The typing is allowed to finish.** The date box no longer acts on every keystroke. It waits until
you stop typing, or until you leave the field or press Enter — then it commits once, with the
finished date. Picking from the calendar still works the way it always did.

**An impossible year is refused.** Anything outside **1900–2199** is not accepted at all: the report
is not rebuilt, the box keeps what you typed, and it outlines in red and tells you why. It is not
silently changed to today's date — a report quietly answering a different question than the one on
screen is worse than one that says no.

On the A/R and A/P aging screens the controls were also moved out of the part of the screen that
gets rebuilt, the same way the reconcile screen was fixed. The other five keep the full redraw; that
is safe now that nothing commits mid-typing, and those screens have no automated coverage, so
restructuring them was not worth doing in the same release as the fix you are waiting on. It is
written down as the next tidy-up rather than quietly skipped.

---

## "Unexpected non-whitespace character after JSON at position 35"

The wall of red boxes when saving an edited account. Not an accounting error, and nothing to do with
the Chart of Accounts — it is the multi-user connection, and it is the same event as "it seems like
the application is heavy and slow... in multi user it lags when two users are posting".

Every multi-user request is performed by a small helper program that is supposed to write **one**
answer. When a request timed out it wrote two: the timeout reply, and then a second one caused by
its own act of closing the connection. NumeraBooks read the pair, could not make sense of it, and
put the parser's complaint on screen.

`{"code":0,"error":"server timeout"}` is **exactly 35 characters** long. That is the 35 in the
message — the same number every time, because it is the length of the first reply, not anything
about your data.

So what you were actually being told, in the worst possible words, was: **the server did not answer
in time.** You now get that sentence instead, once, and the helper can only ever write one answer.

## "it behaves as if it is just 1% of its speed"

Same root, and this is why the red boxes came in a flood rather than one at a time.

On a shared company, saving one edited account used to make NumeraBooks:

1. send the change to the server — starting a **separate copy of the program** to do it;
2. **download the entire company file** back, to pick up the result;
3. do both of those **again** for the sub-account setting, which is sent as a second change;
4. and again if the account type changed and an audit line was written.

Four to six program launches and two or three full downloads of the whole company — for one click,
with the screen frozen throughout. On a fast machine and an idle server you get away with it. On a
slower computer, or while somebody else is posting, each step can reach its 8-second limit, and
every one that did produced one of those red boxes.

Cleaning up duplicate accounts was the extreme version: **one full download per duplicate.** A
converted file with 200 duplicates downloaded the whole company 200 times.

### What is fixed now

**One action, one refresh.** An action that posts several changes now pulls the company back once,
at the end, instead of after every change. Saving an account: three round trips down to one refresh.
The duplicate cleanup: one, not two hundred.

**Audit lines cost nothing.** Writing "account type changed" to the activity log used to re-download
the entire company to see it. It cannot change any figure on screen, so it no longer does.

Nothing about *what* gets sent to the server changed — same operations, same order, same checks. The
refresh still happens if the action fails half-way, because a failed action can still have changed
the shared company, and the screen must not go on showing what was there before.

**The company file is no longer downloaded at all on a normal save.** This is the big one.

The server now sends back **what changed** instead of the whole company. NumeraBooks' engine already
writes down the exact database instruction behind every change — that is how the crash-recovery
journal works — so the server records those instructions while it performs your operation and returns
them. Your copy replays them and arrives at exactly the same books.

Measured, on a 30,000-transaction company:

| | before | now |
|---|---|---|
| Posting an invoice with its payment | **16.8 MB** downloaded | **2.4 KB** |
| Encoding cost before it even reaches the network | 116 ms | negligible |

That is roughly **seven thousand times less data per operation**, and it does not grow with the size
of your company — a 500 MB file sends the same couple of kilobytes.

It is exact, not approximate: the tests compare **all 43 tables, row by row**, after replaying a
posted invoice, a payment applied to it, and the audit line — and they must come out identical.

Your copy refuses to apply the change and falls back to the old full download whenever it is not
certain, which is the part that matters on a shared file:

- somebody else posted in between, so your copy is missing their work;
- the answer was a retry of a request whose reply got lost — replaying it could post the entry twice;
- the operation was too large to send as instructions (a bulk import), where the whole file is
  cheaper anyway;
- the highest entry numbers afterwards do not match the server's.

A copy that is quietly wrong is far worse than one that is slow, so every one of those cases takes
the slow path deliberately.

## The host computer was waiting for itself

This is the one that explains everything, and it is embarrassing in the way real bugs usually are.

Choosing **"Host multi-user on this computer"** starts the Database Server *inside NumeraBooks
itself*. NumeraBooks then points at its own server, because the host is also seat #1 — that part is
deliberate and fine.

But every multi-user save stops the program while it waits for the server to answer. It has to: the
screen must not carry on while a posting is in flight. And on the host, **the server it is waiting
for is inside the very program that has stopped.**

Nothing could answer. Every single operation on the host machine sat there until the request gave up
— **eight seconds each** — and then failed. The failure is the "position 35" box above. Two users
posting made it worse because the second one waited behind the first.

So on the hosting computer, NumeraBooks was not slow. It was timing out, all day, on every click.

The server now runs on its own, alongside the app rather than inside its waiting room, and answers
immediately. Nothing about setting up hosting changes — same folder, same port, same access code. If
for any reason it cannot start that way it falls back to the old arrangement rather than leaving you
without a server.

The test suite performs the deadlock on purpose, so nobody can put the server back where it was
without a test going red.

## Requests no longer launch a program each

With the downloads gone (above), the biggest remaining cost was that **every request started a fresh
copy of the application** to make it. NumeraBooks now keeps one small helper running for the session.

Measured, same request, same server:

| | |
|---|---|
| Launching a copy per request | **35.1 ms** |
| The helper that is already running | **0.8 ms** |

**44× faster per request** — and that measurement used plain Node. On Windows the thing being
launched was the 180 MB NumeraBooks binary itself, with antivirus inspecting every launch, so the
real saving on your machine is larger than this.

It is optional at every step: anything unexpected and it goes back to the old method, so the worst
case is exactly what you have today.

## Saving a customer or vendor, and a reconciliation

Both were posting more changes than they needed. Saving a vendor sent the 1099 details, the price
level and the reminder days as **three separate saves of the same row** — now one. Those, the parent
setting and the opening balance are all one action, so they refresh once between them rather than
after each. Same for saving a reconciliation.

## A ceiling on very large imports

Found while measuring, not reported: NumeraBooks refused to commit a single transaction touching
more than about 32,700 documents — it failed at the end, after the work, with "too many SQL
variables", and rolled the batch back. Nothing does that today because file imports post document by
document, but a conversion of a very large QuickBooks file is exactly the shape that would find it.
The limit is gone, and the balance check that runs at the end of every transaction still catches an
out-of-balance entry wherever it sits in the batch.

### What is still not fixed, honestly

**The screen is still frozen during a save.** The multi-user path is deliberately synchronous. A save
is now kilobytes over a connection that is already open, so the freeze should be imperceptible — but
it is still a freeze, and making it properly asynchronous is a change to the whole screen layer.

Batching is still not applied to the budget screen, the batch upload or the user screen. Each needs
its own reading; wrapping them mechanically is how a subtle bug gets in.

**There is now a git repository.** The code had no version control at all — 13,846 lines edited in
place with no way to diff or revert. That was the single largest risk to the product and it is why
this work waited for it. `dist` (14 GB of old installers), `node_modules`, exported customer qbXML
and, above all, the licence signing key are excluded from it.

---

## Under the hood

`src/nbdate.js` is new — one mechanism for every date field that drives a re-render, so the next
screen cannot go back to a bare `onchange`. Grep `NBDATE.attrs` to find all eight of them.

`src/srv-sync.js` now writes its reply once and cannot write twice (MU-701), and the reader in
`src/main.js` recovers the first reply even if something ever does write two again.

## Tests

**74 suites · 3,722 assertions · all green** (up from 67 / 3,562).

Two new suites, each carrying a **counter-proof** — a test that performs the old broken behaviour
and asserts that it breaks, so nobody removes the guard later:

- `tests/nbdate.test.js` — wired straight to `change`, a half-typed year *does* become the as-of
  date of the report.
- `tests/partycenter.test.js` — filtering deactivated names in SQL instead *does* detach a job from
  its customer and leave it looking like a top-level customer.
- `tests/srvsync.test.js` — two replies on one stream *do* produce "position 35", proving the number
  in the message is the length of the timeout reply. This suite starts a server that never answers
  and runs the real helper against it, rather than reading the code and believing it.
- `tests/opbatch.test.js` — unbatched, three operations really do cause three whole-company
  downloads. It also checks the source is actually wired this way, so the batching cannot be
  removed from a screen without a test going red.
- `tests/opdelta.test.js` — replaying a change onto a copy that missed somebody else's posting
  really does produce books that never existed on the server. That is the mistake the version check
  exists to prevent, and it is why the slow path is taken whenever anything is uncertain.
- `tests/hostloop.test.js` — a request to a server living inside the waiting program really does
  time out, every time. That is the host deadlock, performed on purpose.
- `tests/txscope.test.js` — one placeholder per document really does hit a hard limit, and an
  out-of-balance entry in a later batch is still caught.

## Still open

- **No version control.** 13,846 lines across two files, no git repo, every edit in place with no
  history and no way to revert. This remains the largest risk to the codebase.
- Splitting the remaining five date-driven reports into a fixed header and a redrawn body.
- **An asynchronous write path** so the screen does not freeze at all.
- Batching on the budget screen, the batch upload and the user screen.
- Batching on the remaining multi-step screens (customer/vendor, reconciliation, budgets, batch
  upload, users).
