A notepad calculator you run yourself, in the browser.
Type in plain language on the left; answers appear in a column on the right, updating as you type. Units, currencies, percentages, dates, variables and running totals all work the way you'd write them on paper.
groceries $86.40 #home $86.40
train fare $12.80 #travel $12.80
electricity $94.10 #home $94.10
sum $193.30
sum #home $180.50
Nothing above is a special mode. A line the engine doesn't recognize is left as plain text with no answer, so notes, headings and sums mix freely in one sheet.
git clone https://github.com/philoking/Sumline.git sumline
cd sumline
docker compose up -d --buildOpen http://localhost:8422. Sheets live in the sumline-data volume and survive restarts and
rebuilds. Date math resolves in your browser's timezone, so today is your today wherever
the instance is hosted.
That is the whole installation. Everything below is why it works that way.
Press ? in the app for the full syntax reference — every example in it is a passing test, so it
cannot claim behavior the engine lacks. A taste of what that covers (dates relative to a Saturday
in August 2026):
| You type | You get |
|---|---|
12 * 34 |
408 |
20% of 250 |
50 |
10 km in miles |
6.2137119224 miles |
100 USD in EUR |
€80.00 |
5 hours 30 minutes in minutes |
330 minutes |
next friday |
Fri 21 Aug 2026 |
today + 3 weeks |
Sat 5 Sep 2026 |
3 workdays from today |
Wed 19 Aug 2026 |
Plus variables (day rate = 550), references to earlier lines (prev, line 3), tags that
subtotal across a sheet (#home), per-line formatting written into the line (1/3 to 2 dp), and
a running total in the corner that cycles between total, average, count and median.
Soulver is the app that worked this idea out, and it is a good one. Sumline exists because a notepad calculator that lives on one Mac is a document, and some of us wanted a service — one instance on the network, every browser reaching the same sheets, no syncing and no per-device license. That single decision is where nearly every difference below comes from.
| Sumline | Soulver | |
|---|---|---|
| Runs on | Any browser, against a container you host | Native apps for Mac, iPhone and iPad |
| Where sheets live | On the server, in one SQLite volume | In a .sheetbook file on the device |
| Between devices | Every browser reaches one instance, so there is nothing to sync | iCloud sync across your own Apple devices |
| More than one person | What it is built for | One person, across their own devices |
| The engine | math.js, evaluated in the browser | Soulver's own |
Because it is a server rather than a document, it gains things a single-user app has no reason
to have: spaces (separate sets of sheets, settings and global variables, for two people or
for one person keeping Work and Personal apart), live updates over an event stream, an
editing lock with a conflict panel that shows which lines differ rather than picking a
winner, share links where every slug a sheet has ever held keeps working, and an
HTTP API and CLI so a launcher and a sheet agree about what day rate * 3 means.
And it loses things a native app can do: no conditionals or branching, no live weather or stock prices, nothing that needs an API key, and nothing native — no Alfred, no Services, no iOS. The engine accepts the phrasings Soulver documents; inventing more was a decision, not an oversight, because every guess is a rule to maintain and rules collide.
Everything in a sheet is text you typed, its formatting included: 1/3 to 2 dp and
100,000 in full are written into the line rather than stored invisibly against it.
This is the constraint the rest of the design answers to. Hidden per-line state would not survive a copy, an export, a search, or a line being moved — and plain text is what makes diffing two versions of a sheet possible, which is what the conflict panel needs to exist at all. It is also why some features are absent: marking a line as a "time point" through a menu would be state living outside the text.
Exchange rates and public holidays are the only two network dependencies. Both were picked for needing no API key, and both fall back to bundled data, so the container works with no internet at all. That rules out live weather and stock prices, which is a trade made on purpose.
| Variable | Default | Meaning |
|---|---|---|
PORT |
8080 |
Port the server listens on inside the container |
HOST |
0.0.0.0 |
Bind address |
DATA_DIR |
/data |
Directory holding sumline.db |
STATIC_ROOT |
/app/web/dist |
Built UI to serve |
TZ |
UTC |
The container's clock: log timestamps, and which years of public holidays get fetched. It does not decide how sheets do date math, which follows the reader's browser. |
HOLIDAY_COUNTRY |
US |
ISO country code for public holidays in workday math |
SPACES |
one space, "Me" | Seeds the spaces on an instance that has none. Ignored once it has any. |
SUMLINE_PASSWORD |
unset | One shared password for the whole instance. Unset or blank means no authentication. |
By default there is no authentication: anyone who can reach the port can read and edit every sheet. For a trusted LAN that is the right default, and it stays the default.
Setting SUMLINE_PASSWORD turns on one shared password — a door, not a login. There are still no
accounts; a space says which sheets you are looking at, not whether you may. Ten wrong answers
from one address buys a five-minute wait. The cookie is deliberately not marked Secure, because
a self-hosted instance is usually plain HTTP and marking it would make signing in impossible
there — so on plain HTTP the password crosses the network in the clear. For more than that, put
it behind a reverse proxy that terminates TLS.
Found a hole? SECURITY.md says how to report it.
The Quick start above is the deployment: docker compose up -d --build on whatever machine
should host it.
docker-compose.prod.yml is the same stack without a build: stanza, for a host that builds the
image as its own step. Nothing deploys automatically — deploy.sh is the whole
update, run on the host when you decide it should happen:
cd ~/sumline && ./deploy.shIt pulls, runs the suite inside the image, builds, brings the stack up, waits for /api/health,
and then asks the deployed engine an actual question and checks the answer — a container that
starts is not the same as an app that serves. Any gate that fails stops the deploy and prints the
container's logs.
That test build is the only thing standing between a red commit and the host, since no pipeline is checking.
docker-compose.prod.yml pins the compose project name to sumline so the sheets volume keeps
its name. Renaming the project or the volume key would start the app on an empty database.
Requires Node 22.5 or newer.
npm install
npm run dev # API on :8080, UI on :5173 with hot reload
npm test
npm run build| Workspace | What it is |
|---|---|
| engine/ | The calculation engine. Pure TypeScript, no DOM or Node APIs, covered by a golden table of input → answer cases. |
| web/ | React + CodeMirror 6. Evaluation runs in the browser, so answers never wait on the network. |
| server/ | Fastify. Sheets in SQLite through Node's built-in node:sqlite, exchange rates, holidays, settings, and the sumline CLI. |
CONTRIBUTING.md covers what a change has to do to land — chiefly that a
documented example is a test, since examples.ts is the single source
for both the in-app reference and the suite that pins it.
The idea is Soulver's. Soulver created and refined the notepad
calculator, and most of what makes this pleasant to use was worked out there first: unit
assimilation, the last-currency-wins rule, sum closing a section, prev and line N, per-line
formatting written into the line, and a good deal of the natural phrasing the engine accepts.
Soulver's own documentation was the specification this was
built against, and it is cited throughout the source where a rule came from there.
Sumline is an independent implementation, not a port: no Soulver code was used, and it is not affiliated with or endorsed by Soulver's makers. If you want the polished native original, with many features this deliberately leaves out, buy Soulver.
MIT. See LICENSE.