A sync log for crews that lose signal.
Chalkline is the server half of an offline-first field app: tablets push batches of edits whenever they can reach the network, pull everything after a cursor when they come back, and two crews editing the same punch item in a basement both keep their work. Rails 8 on Postgres, deliberately small enough to read in one sitting.
Idempotent pushEvery op carries a client id. Retry a batch a hundred times; it lands once.
Dense cursorsSequence numbers are allocated under a per-project advisory lock, so a pull from
since never skips a change committed late.Per-field LWWTwo crews editing different fields both win. Same field, later client clock wins, and the loser is still in the log.
Versioned API
/api/v1 is frozen by a contract test. /api/v2 adds clocks and a cursor object without touching v1.Projects
- Riverside Clinic, Phase 2 head_seq 42 ยท 3 devices
The API, in three calls
# authenticate as a device (token identifies project + device)
curl -H "Authorization: Bearer $TOKEN" https://chalkline.levelbrook.com/api/v1/items
# push a batch; op_id and item_uuid are client-generated
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
https://chalkline.levelbrook.com/api/v1/sync/push -d '{"ops":[{"op_id":"<uuid>","item_uuid":"<uuid>","kind":"punch",
"op":"upsert","fields":{"title":"Cracked tile, unit 204","status":"open"},"client_ts":1789600000000}]}'
# pull everything after your cursor, in order, dense
curl -H "Authorization: Bearer $TOKEN" "https://chalkline.levelbrook.com/api/v1/sync/pull?since=0&limit=500"
Push returns applied (what actually won per field), duplicate (op_ids already seen), rejected, and head_seq. Pull returns changes, next_since, more, head_seq. Device tokens for the demo project are on its page.