[{"data":1,"prerenderedAt":98},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fnotes:en":8,"api:\u002Fapi\u002Fprojects\u002Ffinance-tracker:en":32},{"email":4,"lang":5,"name":6,"role":7},"hello@ewsirdev.example","en","Ewsirdev","Web developer: websites, landing pages and Go services",[9,16,22,27],{"slug":10,"lang":5,"title":11,"date":12,"summary":13,"project":14,"minutes":15},"double-booking","Double booking: a transaction plus a partial UNIQUE index","2026-10-20","How booking-slots protects against two clients booking the same time at once, and how that protection is tested.","booking-slots",1,{"slug":17,"lang":5,"title":18,"date":12,"summary":19,"project":20,"minutes":21},"lease-and-fencing","Lease and fencing: how a queue neither loses a job nor lets a zombie finish it twice","A worker can crash, stall, or wake up after its job was taken by someone else. How the lease and the lease token in job-queue deal with that on SQLite.","job-queue",2,{"slug":23,"lang":5,"title":24,"date":12,"summary":25,"project":26,"minutes":21},"p95-nearest-rank","p95 in an uptime monitor: the nearest-rank method and what it does not hide","How uptime-monitor computes p50\u002Fp95 latency and uptime, why the nearest-rank method was chosen, and where those numbers can mislead.","uptime-monitor",{"slug":28,"lang":5,"title":29,"date":12,"summary":30,"project":31,"minutes":21},"websocket-from-scratch","WebSocket on the standard library: what really happens under new WebSocket()","Why realtime-rooms has its own RFC 6455 layer, which protocol violations it checks, and why a library is still the better choice for production.","realtime-rooms",{"slug":33,"title":34,"type":35,"stack":36,"description":52,"highlights":53,"run":58,"hue":59,"og":60,"shots":61,"case":69,"case_lang":5,"prev":92,"next":95},"finance-tracker","Ledger: personal finance tracker","backend",[37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"Go","SQLite","modernc.org\u002Fsqlite (no CGO)","REST API","Nuxt 4","Vue 3","TypeScript","Pinia","SVG charts","vitest","ESLint","Docker","GitHub Actions","PBKDF2","CSV import\u002Fexport","Personal finance tracking: accounts in different currencies with manual exchange rates, income, expenses and transfers, budgets, payments on a cron-like schedule, CSV import with a preview, reports. Money is stored only in integer minor units, users' data is isolated, and the charts are drawn in SVG with no library. Go + SQLite with a Nuxt 4 front end.",[54,55,56,57],"Money only in integer minor units","Multi-currency with manual exchange rates","CSV import with a preview and a dry run","Hand-made SVG charts, no library","cd finance-tracker && set SEED_DEMO=1&& go run .",140,"\u002Fog\u002Fen\u002Ffinance-tracker.jpg",{"desktop":62,"desktop_sm":63,"mobile":64,"dw":65,"dh":66,"mw":67,"mh":68},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",1280,800,390,844,{"about":70,"features":73,"run":83,"notes":87},[71,72],"Ledger is a self-hosted personal finance tracker: accounts in different currencies, income, expenses and transfers, budgets with warnings, scheduled payments, CSV import from a bank and reports. One Go binary with SQLite inside and a Nuxt 4 front end embedded.","The point is the money: everywhere, in the database, the API and the browser, it is stored as integer minor units (cents), and currency conversion goes through big.Int with rounding. The charts are drawn in SVG with no library. In demo mode all data in the interface is marked as made up.",[74,75,76,77,78,79,80,81,82],"Accounts in different currencies and manual exchange rates (stored as an integer x10^6); totals and reports are computed in the base currency","Income, expenses and transfers between accounts, including across currencies; tags, search and filters","Money without floats: integer minor units, conversion through big.Int with round-half-away-from-zero, and tests for amount parsing and rounding","Budgets: a monthly limit per category, a warning threshold, and the statuses \"within\", \"close\" and \"exceeded\"","Payments on a cron-like rule of five fields (@monthly, *\u002F10, ranges, weekdays); missed dates are caught up","CSV import: delimiter detection, a preview, column mapping, Windows-1251 encoding, a dry run with no writes, duplicate skipping and a report of bad rows; CSV export protected against formula injection, and a full JSON export","Reports: net worth over time, spending and income by category, month over month and trends (a least-squares slope, in integers)","User isolation: every request is bound to the user from the session, and tests check that other users' objects cannot be read or changed","Hand-made SVG charts (line, bars, horizontal bars, sparkline) with role=\"img\" and a text table for screen readers; light and dark themes, keyboard shortcuts",[84,85,86],"git clone \u003Crepository on request> finance-tracker","cd finance-tracker","SEED_DEMO=1 go run .   # http:\u002F\u002Flocalhost:8102, a demo user with made-up data for 12 months",[88,89,90,91],"Exchange rates are manual and current only: no rate history is stored, so the past is recomputed at today's rates and the exchange-rate difference is not visible. The base currency and an account's currency cannot be changed.","A budget is the same every month, with no carry-over. Scheduled payments are created when someone opens the app or presses a button: there is no background scheduler.","There is no password change or recovery and no e-mail confirmation. SQLite and a single process: fine for personal or family use, not for high load. style-src still allows unsafe-inline (Vue styles), and SSR is off, so this is an SPA shell.","The -race tests run only in CI (there is no gcc locally), and the Docker image has not been built.",{"slug":93,"title":94},"auth-service","Keyring: an authentication service",{"slug":96,"title":97},"pixel-wall","Pixel Wall: a shared canvas",1792697892726]