Code
Outside of work I build self-hosted tools, personal websites, and whatever else catches my interest. Side projects are where I experiment with new technology and stay sharp since at work I stick to stable, mature frameworks. There is plenty more to see on my GitHub account.
Taproot
Dotfiles, containers, and the configs that make a machine mine.
{
"sha": "5e870d0",
"message": "graft an ai container onto the workspace\n\none dockerfile, one container: a llama.cpp cuda server built from source on\nubuntu 24.04 for ampere alone, holding qwen3.5-9b at 128k context, with the pi\ncoding agent and its web-access extension alongside it. entered the same way as\nwebdev, docker exec -it tmux and then pi.\n\nqwen3.5-9b because it is hybrid gated-deltanet: only 8 of its 32 layers grow a\nkv cache, so 128k costs about a gigabyte rather than nine and the whole thing\nstays resident on an 8gb card. anything that spills to system ram loses most of\nits speed, which is the constraint every other choice here follows from.\n\nbuilding llama.cpp ourselves rather than layering on the upstream image costs a\nquarter hour and buys an image a tenth the size, control of the cuda version,\nand immunity to the sonameless shared objects that ldconfig quietly forgets.\n\nquiet by default: no telemetry, no update pings, no version checks, weights\nserved from cache, search pinned to duckduckgo because the stock provider chain\nends at a third party, and nothing listening anywhere but loopback inside the\ncontainer.",
"date": "2026-08-18T02:35:20Z",
"author": "Isaac Bythewood"
}GitHubdarkfurrow.com
A living almanac of seasons, soil, and the quiet knowledge that used to be common.
{
"sha": "0878cc9",
"message": "fix december countdown, retire service worker, honest nav links\n\n- days_until_next_season skips same-name seasons when wrapping into next\n year, so December no longer counts down the days until winter mid-winter\n- delete sw.js and unregister existing installs: it cached unhashed asset\n paths that no longer exist and pinned returning visitors on stale pages\n- reload content on day rollover, not just season rollover (sun, moon, and\n the day-seeded picks all change at midnight)\n- season nav gets real hrefs (works without JS) and aria-current=\"page\"\n- winter ends 2/29 so leap day belongs to a season explicitly\n- format_hm carries 60 minutes into the hour (\"5h 60m\" -> \"6h 0m\")\n- 500 responses log the detail and serve a generic body",
"date": "2026-07-21T23:01:58Z",
"author": "Isaac Bythewood"
}GitHubRepos
A minimal self-hosted git browser. Renders bare repos as a website with commits, diffs, syntax-highlighted blobs, and atom feeds, plus clone over HTTPS.
{
"sha": "7634889",
"message": "harden raw serving and diff/highlight limits, fix syntax detection\n\n- serve html/svg/xml blobs on /raw as text/plain with nosniff: rendered\n inline they would run scripts on this origin (stored XSS)\n- tree listings read blob sizes via find_header instead of loading every\n blob's full contents\n- cap parsed diff output at 2MB (a vendored-tree commit could render\n hundreds of MB) and skip syntax highlighting past 1MB with a \"view raw\"\n fallback; cap READMEs at 1MB\n- syntax detection no longer calls find_syntax_for_file (it opened the\n request-supplied basename on the server fs); match extension, whole\n name, then first-line sniff from the blob\n- percent-encode rev and path segments in tree/blob hrefs (new\n urlencode_path filter keeps separators)\n- atom 404s serve a generic body instead of the error string\n- kill_on_drop on git http-backend so dropped clone streams don't orphan\n upload-pack",
"date": "2026-07-21T23:08:13Z",
"author": "Isaac Bythewood"
}GitHubAnalytics
A self-hostable analytics service with a straightforward API to track events from any source.
{
"sha": "b9ed9c7",
"message": "harden collector and auth, fix historical graph anchor and report filenames\n\n- fail startup when ANALYTICS_PASSWORD is unset instead of defaulting to admin\n- constant-time password compare with a flat 500ms delay on failed logins\n- escape / in typst_md so collector text cannot start Typst line comments\n- anchor the events graph to the requested end date, not today\n- ASCII-sanitize report Content-Disposition filenames (non-ASCII panicked)\n- cap collector bodies at 16KB, event names at 200 chars, hot fields at 2048\n- read client IP from the proxy-appended last X-Forwarded-For entry\n- validate and atomically rename GeoIP downloads so a corrupt file cannot\n wedge enrichment for 30 days\n- collector.js: keepalive so page_leave survives unload, cap click text",
"date": "2026-07-21T00:31:52Z",
"author": "Isaac Bythewood"
}GitHubStatus
A self-hosted uptime monitor and status page builder, with Lighthouse audits and PDF reports baked in.
{
"sha": "a6e6b21",
"message": "harden auth, crawler, and report endpoints\n\n- fail startup when STATUS_PASSWORD is unset instead of defaulting to admin\n- constant-time password compare with a flat 500ms delay on failed logins\n- require auth for ?report=pdf|md so public pages are not a free\n CPU-bound PDF-generation endpoint\n- ASCII-sanitize report Content-Disposition filenames (non-ASCII panicked)\n- kill_on_drop on the lighthouse subprocess so timeouts don't orphan chromium\n- crawler: respect the deadline during external-link probes and cap the\n probe list at 500 links\n- crawler: stream HTML bodies with a 5MB cap instead of buffering unbounded\n- record real elapsed time on probe failure, not a flat 10s timeout\n- require https:// on property create (checker is HTTP/2-over-TLS only)\n- toggle_public returns an honest 500 on db error instead of success:true",
"date": "2026-07-21T22:28:21Z",
"author": "Isaac Bythewood"
}GitHubFinance
A self-hosted market watcher for stocks, ETFs, indexes, and futures, with live charts, key stats, fundamentals, and SEC filings.
{
"sha": "8a927c5",
"message": "close guard pacing race, prune dead watchlists, tidy small findings\n\n- serialize EndpointGuard::acquire per endpoint with an async mutex: the\n read-decide-sleep-commit sequence was racy, letting concurrent tasks\n violate pacing or double-claim the half-open probe slot\n- robots.txt: Disallow /api/ and /stream\n- prune watchlist sids whose newest row is >13 months old (the fin_sid\n cookie lives 12 months and is never refreshed, so they can never return)\n- country_bucket returns String instead of leaking the long-tail codes\n- drop unused zip dependency",
"date": "2026-07-21T22:52:14Z",
"author": "Isaac Bythewood"
}GitHubblog.bythewood.me
A self-hostable markdown blog for developers, with code blocks, syntax highlighting, live search, great SEO, and a clean customizable UI.
{
"sha": "7681299",
"message": "cache PDFs, sanitize search rendering, tighten headers and errors\n\n- cache rendered PDF bytes per slug (posts are immutable per process, so\n every hit paid the CPU-bound Typst compile; now 6ms warm vs 1.2s cold)\n- 500 responses log the detail and serve a generic body instead of leaking\n template/IO error strings\n- add `inline;` disposition-type to PDF and Markdown Content-Disposition\n- search.js: escape regex metacharacters in the query and build result rows\n from text nodes instead of innerHTML (also preserves match case)\n- template `debug` flag follows cfg!(debug_assertions) instead of false\n- /content/images/ caches 1 day instead of 1 year (unhashed filenames)\n- frontmatter close delimiter must start a line (find \"\\n---\"), so a ---\n inside a value cannot truncate the metadata block",
"date": "2026-07-21T22:58:19Z",
"author": "Isaac Bythewood"
}GitHubTimelite
A simple time tracker that keeps everything local in your browser. No accounts, no sync, no server-side state.
{
"sha": "1e9cf55",
"message": "harden persistence and hotkeys, DST-safe days, next 16.2.11\n\n- persist() helper with .catch around every localForage.setItem: a quota\n or private-mode failure was an invisible unhandled rejection\n- LOCALDATA_READY merges over initialState (stored state can predate new\n fields), coerces entry start/end and timer to Dates, defaults tags to []\n- import assigns fresh ids for duplicates within the imported file, not\n just collisions with the existing log; delete dead mergeEntries\n- hotkeys ignore ArrowUp/Down inside inputs (they were stealing the caret)\n- DST-safe day stepping in log (setDate) and summary (setDate + round)\n- unsafe-eval only in dev CSP\n- bun --bun in package scripts, pin Dockerfile to oven/bun:1-alpine\n- bump next to ^16.2.11",
"date": "2026-07-21T23:15:42Z",
"author": "Isaac Bythewood"
}GitHubisaacbythewood.com
The personal website you are looking at right now.
{
"sha": "0d765b7",
"message": "security headers, next 16.2.11, fix canvas and loader bugs\n\n- add security headers in next.config.js (nosniff, frame options,\n referrer policy, permissions policy, CSP allowing the analytics\n collector in script-src/connect-src; unsafe-eval only in dev)\n- bump next to ^16.2.11\n- _document.js collector snippet: assign collectorScript to window\n explicitly instead of via implicit global\n- synthwave.js: close the rgba() in the gradient stop\n- retrostars.js: refs instead of state (state re-rendered the component\n on every mousemove and twinkle tick; the canvas loop reads refs)\n- index.js: release the loader on hero image error, not just load\n- add prop-types as a real dependency (was riding on a transitive)",
"date": "2026-07-21T23:12:30Z",
"author": "Isaac Bythewood"
}GitHub