Under the hood

The FocusNexus engine

A technical tour of how FocusNexus works: the motivation, the design goals it led to, and the key choices those goals forced — a single-file local-first application, your data as plain JSON in your own Google Drive, and team collaboration with no server at all.

Motivation

FocusNexus began as a working scientist's answer to a familiar failure mode: the week is a mix of long-horizon goals (grants, papers, a lab's trajectory) and a relentless stream of small obligations, and the tools on offer either fragment that mix across several apps or centralize it on someone else's server, behind someone else's account, under someone else's terms. Every mainstream organizer asks for the same trade — convenience in exchange for custody of your working memory.

The founding question was whether that trade is actually necessary. A modern browser is a capable runtime; Google Drive (which most academics already have) is a durable, user-owned file store with a permission model narrow enough to be trustworthy; and cryptography in the browser is now standard. Could a serious organizer — daily digest, prioritization views, time blocking, recurring work, even small-team collaboration — be built so that no server exists anywhere in the design, and the user's data never leaves accounts the user owns? FocusNexus is the running answer, refined in daily use, and doubles as a modest experiment in how far the serverless idea can be pushed.

Design goals → design choices

Six goals govern the design. Each one forced a concrete choice, and most of the architecture falls out of taking them seriously at the same time.

G1Nothing to run, nothing to subscribe to, no one to trust

No server, no account, no database, no analytics. Choice: the entire application is one static HTML file served from GitHub Pages. There is no backend because there is nothing for a backend to do.

G2The user's data belongs to the user, in inspectable form

Data must live where the user can see, copy, and delete it — as readable files, not opaque rows. Choice: everything is plain JSON in a FocusNexus folder in the user's own Drive, under Google's narrowest OAuth scope (drive.file): the app can touch only files it created itself and is structurally blind to the rest of the Drive.

G3Failure should be boring

Networks drop, laptops sleep, and mistakes happen; none of it should cost data. Choice: local-first operation (every edit lands in the browser first, Drive follows), an offline mode that keeps you editing on the device and, when the connection returns, uploads on its own if nothing else changed — or asks which version to keep, never guessing — and one snapshot file per calendar day — the 30 most recent kept — with a one-click revert to yesterday.

G4One artifact

An app you can audit in a single read, save locally, and reopen in a decade. Choice: a single self-contained HTML file — no build step, no bundler, React loaded as two pinned script tags. The price is paid in discipline instead: releases are made by scripted, checksum-verified surgical edits, and every release must clear a regression gate that now runs eighty test suites.

G5Collaboration without infrastructure

Small teams should share a board without anyone standing up a service. Choice: a team is nothing but a shared Drive folder plus cryptography — each member writes only their own file there, every member's app merges all the files deterministically, and access is controlled by an invite string and a passphrase rather than by a server checking identities.

G6Identity should be human

People are email addresses, not storage accounts. Choice: the userID (the email shown on cards and to teammates) is deliberately separate from the googleID (the Google account whose Drive holds the data). The app pins the storage account it first syncs with and refuses to mix data across Google accounts — a guard against the classic multiple-sign-in accident. Moving to another Google account is therefore explicit: the account dialog switches accounts, and a forwarding note left in the old account (a movedTo field in its newest file) makes every other device pause and offer the switch instead of syncing to the old account.

The shape of the system

At runtime, FocusNexus is a React application holding one document in memory: the state document described below. Every view — the Daily Digest, the Eisenhower matrix, the time-blocking board, the projected-workload chart, the calendar — is a pure function of that document; there are no per-view databases to drift out of sync. Every edit updates the document, which is written to the browser's local storage immediately and to Drive on a debounced schedule. Reads on startup go the other way: local storage boots the app instantly, and Drive reconciles behind it.

PERSONAL — EVERYTHING IN YOUR OWN ACCOUNTS Your browser FocusNexus app one state document, in memory localStorage working copy · instant boot every edit Your Google Drive FocusNexus/ folder FOCUSNEXUS-20260830-….json FOCUSNEXUS-20260829-….json ⋮ one snapshot per day · 30 kept plain JSON — open it any time debounced save drive.file: app-created files only load · one-click revert TEAM — A SHARED FOLDER, NOT A SERVER You a@lab.org merges all files locally Teammate b@lab.org merges all files locally Team hub folder in the hub account's Drive …TEAM-…-a@lab.org.json only YOU ever write this file …TEAM-…-b@lab.org.json only b@lab.org writes this one access = invite string + passphrase: the folder credential travels encrypted (PBKDF2 ×600k → AES-256-GCM) writes own file only writes own file only reads ALL files → merge reads ALL files → merge Solid = writes · dashed = reads. No component in this picture is a server.
Figure 1 — where the data lives and moves. Personal lane: edits land in the browser first; Drive receives a debounced save into the day's snapshot file, and about a month of daily snapshots makes revert trivial. Team lane: each member writes only their own file into the shared hub folder — so there is no write contention and no locking — and every member reads all the files and merges them locally into the identical shared view.

Why a single file, really

The single-file constraint looks like an aesthetic and is actually load-bearing. It makes the app auditable (the entire program is one view-source away, matching the privacy claims to something checkable); it makes it portable (a saved copy is a working app — the header's icon even hides itself gracefully when the icon assets aren't beside it); and it removes the build pipeline as a failure surface and a dependency treadmill. The engineering cost is real — you cannot casually refactor a 450 KB file — and it is paid with process: every release is produced by a small Python script of exact-count, checksum-asserted edits against the previous version, and must pass the full regression gate (currently 80 automated suites, from pure-function merge tests to two-client end-to-end simulations over a faked Drive) with zero failures before it ships.

The data objects

Four object shapes carry the whole system. The first is the heart; the others are wrappers and envelopes around it.

The state document

roles[]            roles — the hats you wear (role menu ▾)
 └ sections[]       areas within a role
    └ items[]       goal | project | task
                     (a subtask is a task whose
                     parent is a task — parentId)
sibling maps, keyed by item id:
  status · deadlines · notes · links
  whens · recurs · costs
personal-only: focus[] · timeBlocks
  focusSessions · settings · scratch note

One document, many views: Digest, Eisenhower, time-blocking, workload and calendar are all pure functions of this object. Item facts live in flat maps beside the tree, so moving an item never rewrites its metadata.

A day-file (personal durability)

FOCUSNEXUS-YYYYMMDD-HHMMSS.json
= the state document, verbatim
one file per calendar day
same-day saves overwrite in place
30 most recent kept · older pruned

Yesterday's file is the backup. The app's revert button loads it; anything older is a normal file in your Drive, openable by hand.

A team member file

= a state document, plus:
  author      who wrote this file
  _ts         write stamp (ms)
  tstamps     per-item edit stamps (ms)
  tombstones  per-item deletion stamps
  people      roster the author has seen
  meta        team name · owner
+ team maps: assignee · assigner

Millisecond per-item stamps exist because "newest wins" needs finer granularity than the personal day-level dates. Tombstones make deletion a fact that can win a merge, not an absence that loses one.

The invite string

FNTEAM1:base64{
  n  team name
  f  hub folder id
  b  credential blob —
     PBKDF2-SHA256 ×600,000
     → AES-256-GCM
  c  OAuth client id }

The invite is safe to send over ordinary channels: without the passphrase (told separately) the blob is ciphertext. The passphrase is never stored anywhere — the decrypted credential lives only in memory, and locking the team clears it.

Teams: collaboration by merge, not by server

The usual answer to "several people, one board" is a server that owns the truth and referees concurrent edits. FocusNexus refuses the server, so the truth has to be a function instead: every member's app computes the shared view by merging all member files with a deterministic, order-independent procedure. If two clients hold the same set of files, they render the identical board — there is nothing else to consult.

The merge is per-item, newest-wins. Every edit stamps the touched item with a millisecond timestamp in the author's file; the merged view takes, for each item independently, the version with the newest stamp (ties broken deterministically by file write-stamp, then author, so document order never matters). A deletion writes a tombstone stamp, and the tombstone kills the item exactly when it is at least as new as the item's last edit — which means a genuinely newer edit resurrects it, and a stale file carried forward by an absent teammate can never overrule either outcome. The procedure is idempotent and permutation-stable, and a dedicated unit suite proves both properties on every release.

…-a@lab.org.json t1 “Draft intro” · stamp 10:04 t2 “Order reagents” · stamp 09:12 …-b@lab.org.json t1 “Draft introduction” · stamp 10:31 t2 tombstone · stamp 09:40 merge pure · per item Every member sees: t1 “Draft introduction” 10:31 > 10:04 — newest wins t2 — gone tombstone 09:40 ≥ edit 09:12 Deterministic: any client, any file order, same board. A newer edit than the tombstone would resurrect t2.
Figure 2 — the merge, on one disagreement. Per-item newest-wins with deterministic tie-breaks; deletions are tombstone stamps that win only against older edits. Because each member writes only their own file, the hub needs no locks and no coordinator — the merge is the coordination.

Everything else about teams follows from the merge. Assignments (By:/For: on every card) are just more per-item facts riding the winning item. A member who edits offline simply has a stale file until they reconnect — and stale files, by construction, cannot clobber newer work. And because the hub is a plain folder, "rotating" a compromised or expired credential swaps an encrypted blob and issues a fresh invite while the folder and every file in it stay put.

The trust model, stated plainly

Honest limits matter more in a serverless design, because there is no operator to compensate for them. The cryptography does three jobs: the invite's credential blob is unreadable without the passphrase (PBKDF2-SHA256 at 600,000 iterations feeding AES-256-GCM); the decrypted credential exists only in memory and is discarded on lock; and a wrong-account credential is rejected before anything is saved, because under drive.file only the account that created the hub folder can even see it.

What the design deliberately does not do: there are no per-item permissions inside a team — the trust boundary is team membership itself, which is the honest boundary for a small lab group sharing one board. Item timestamps come from members' own clocks, so "newest wins" inherits ordinary clock accuracy; the deterministic tie-breaks keep the outcome consistent everywhere, and at human editing timescales skew is a non-issue, but this is a small-team design, not a distributed database. And once someone can read the board, nothing technical prevents them copying it — as with any shared document, revocation (a credential rotation) protects the future, not the past.

A useful way to summarize the whole engine: FocusNexus replaces a server with three things — a browser that computes everything, a Drive folder that remembers everything, and a merge function that agrees with itself everywhere. The first two belong to you; the third is mathematics.

How it was built

Recognizing the possibilities inherent in new AI coding engines, and my long running dissatisfaction with other methods to address planning and time management challenges, I started developing FocusNexus in feb/march 2026. The current version represents approximately 200 iterative updates to the code base. I use FocusNexus constantly. The application is written and maintained in Claude cowork sessions: an AI coding agent does the implementation work while I direct it, setting the goals, judging behavior against daily use, and deciding what ships to github. The division of labor has stayed constant across every release: design intent, feature decisions, and acceptance criteria, are human (JOD); the mechanics of implementing, testing, documenting, as well as security analyses of each change are delegated to Claude.

The single-file constraint shapes the process as much as the product. A ~450 KB HTML file is hostile to hand edits, so no release is edited by hand: each version is produced by a short Python build script that applies an exact list of transformations to the previous version, with every transformation required to match the file an exact number of times — an unexpected count aborts the build instead of corrupting it. The scripts live in the repository beside the app, so each release is reproducible from the one before it.

Nothing ships on inspection alone. A regression gate — unit tests plus end-to-end suites exercised against the built file, eighty suites as of v1.25 — must pass on the exact bytes being released. And every release carries a handoff document recording what changed, why, and what was learned, so the next working session — human or AI — starts from the design's actual state rather than re-deriving it.

Version control is deliberately ordinary: a local git repository on the author's machine, mirrored to GitHub, one tag per release. The same repository is the deployment mechanism — a GitHub Actions workflow publishes the app and these pages to GitHub Pages on every push, and a verification script closes the loop by confirming that the live site matches the repository before a release is called done. Like the app itself, the process runs on nothing but files, folders and history — no build server, no extra infrastructure, nothing to maintain but the work.

A deeper design document — the full schema, the merge algorithm's edge cases, and the reasoning behind individual trade-offs — is planned; this page is the map, not the survey. For the practical tour, start with Getting started; for what the app does day to day, the homepage; for data handling in plain language, the privacy policy.