Working notes
Devblog
One post for each thing that clicked while I built it: a self-hosted homelab, the automation and search systems that run it, my photo app Loupe, and the smaller tools in between. It runs from cloud fundamentals up to the systems I keep alive at home.
Start here
Aug 18
The agent platform: 21 tools, one fleet, four models
How a job gets dispatched, which model runs it, and what it has to prove before I believe the result.
May 10
My cloud capstone: pricing, deploying, and auto-scaling on AWS
The capstone that made cloud real: price it, build it, load-test it, then move its data to a second cloud.
Jul 4
The night my control-plane machine died and I failed over
I took down my own homelab, then proved the failover plan actually works.
Jun 22
Folding two repos into one, then scrubbing my git history
Merged Loupe's two repos into one, then found a home address, GPS coordinates and my network layout still sitting in the history and rewrote it out before the first push.
Jul 11
Loupe's ML pipeline runs entirely on your own hardware
Every model that looks at your photos runs on my own GPU, nothing phones home.
Jul 5
Handing a job to whichever machine should run it
Built a way to hand work to the right machine and walk away, instead of running everything by hand.
Topics105▾Stack map →
Self-hosting62Reliability46Homelab23Troubleshooting23Python22Data quality21AI20Automation19Linux18SQLite17Backups14Knowledge base12Monitoring11Agents9RAG9Security9Web UI9Debugging8Guardrails8Image processing8Networking8Orchestration8Incident response7Loupe7Product strategy7Semantic search7Storage7Branding6Cloudflare6CSS6Design systems6Machine learning6SSH6Computer vision5Concurrency5Git5Hardware5LLM5Typography5Browser extension4Distributed systems4Failover4Metadata pipeline4Performance4Raspberry Pi4Access control3Breadcrumbs3Dashboards3Demo3FFmpeg3GPU3Perceptual hashing3Search3systemd3Tailscale3cloudflare-pages2Dependency management2eleventy2Face recognition2iCloud2JavaScript2Knowledge graph2Local models2MCP2Model routing2Open source2Personal finance2rclone2Retrieval2Testing2Web scraping2AI agents1Apple Photos1Auto-scaling1AWS1Chrome1Cloud1Content-addressed storage1Cost1Cost estimation1CUDA1Data export1Data privacy1deploy1Environment configuration1ExifTool1Gemini1git filter-repo1High availability1iOS1Manifest V31Maps1Multi-cloud1Node1Pipelines1Product design1Push notifications1PWA1React1Repo consolidation1Responsive design1Safari1Schema migrations1SMB1TypeScript1
August 19 - making the clean version of a repository the only version that can be pushed
Six of my repositories had gone public straight from their working trees, carrying internal names, addresses, paths and real personal data in test fixtures. Rewriting files does not purge history, so every repo was deleted and recreated from a sanitized single-commit snapshot, and the publishing pipeline was folded into the nightly pusher with a fail-closed gate that is deliberately allowed to tolerate two things.
GitSecurityAutomationReliabilitySelf-hostingOpen source
August 18 - the agent platform, described in one place for once
I have been building an agent platform on my own hardware one post at a time. This is the whole thing in one place: how a job gets dispatched, which model runs it, and what it has to prove before I believe it.
AIAgentsMCPLLMRAGModel routingGuardrailsOrchestrationSelf-hosting
August 17 - twenty hours down, and the watchdog told me why
My control-plane machine vanished for twenty hours. The most useful evidence turned out to be a safety mechanism that did not fire.
Incident responseReliabilityHardwareLinuxTroubleshootingMonitoringHomelabRaspberry Pi
August 15 - seventy-seven percent of the "edited" photos were not edited
A phone screenshot of an error turned into a day of storage work: an audit of 4,921 pulled photo edits found 3,777 byte-identical to their originals, 108 GB reclaimed, a 3.4 TB library moved with an instant rename, and five separate ways a copy or a delete can lie to you.
LoupeStorageData qualityBackupsTroubleshootingLinuxSelf-hosting
August 14 - ten readiness gates instead of one number
A single readiness percentage is a number you can fool yourself with. Waypoint replaced it with ten independent gates that each pass or fail on their own evidence.
PythonTypeScriptReactData qualitySelf-hostingSQLiteRetrievalProduct strategy
August 11 - the "are you sure" flag that turned out to be the only lock on the door
While reading my dashboard's code as a precedent for something else, a note that said 'stale comments' turned into a live path where anyone on my home network could launch an AI worker with tool auto-approval. Fixed at two layers, then the fix locked me out, which was its own lesson.
SecurityAccess controlAIAgentsNetworkingIncident responseSelf-hosting
August 10 - the submission that failed twice and saved everything both times
A live practice-exam submission returned a 502 and the retry said 'already submitted', leaving a permanent error in front of a button that could never succeed. The proxy had a five-second default nobody had questioned, the submit path took 29 seconds, and the first optimization made it slower. The fixes preserved output exactly or were reverted.
ReliabilityTroubleshootingPerformancePythonIncident responseSelf-hosting
August 9 - treating the listing database as a cache, not the record
Listing sites edit and pull their own pages. Homestead keeps every capture as immutable evidence and rebuilds the searchable database from it, so a delisted house keeps its record.
Self-hostingData qualitySQLitePythonBrowser extensionContent-addressed storageBackupsWeb scraping
August 9 - four different reasons my dashboard was wrong, and only one of them was a real problem
The fleet dashboard kept raising conformance warnings. Genuine state edges were being amplified by four independent defects in how it observed and reported. Then two screenshots from my phone showed the first fix was incomplete.
MonitoringDashboardsReliabilityTroubleshootingSSHSelf-hosting
August 9 - a test suite that proved it could fail, and the search that had been dead for two days
Loupe got its first real test suite. Writing it found two things: one of the new tests could not fail, and the search feature had been silently broken since a move two days earlier while the service reported healthy the whole time.
LoupePythonTestingReliabilityTroubleshootingSemantic searchSelf-hosting
August 9 - a green unit that produced no backup for three days
The timer that snapshots Loupe's databases reported success while the newest snapshot in the destination was three days old. Two scheduled runs had failed and a manual test run had overwritten the status. Then a real restore drill, which checked four properties and first failed on my own script's assumption.
LoupeBackupsReliabilityTroubleshootingSQLiteSelf-hosting
August 8 - the price field that looked like history and wasn't
Breadcrumbs grew a price-history service over 17 months of my own grocery orders. Two facts about the data shaped the whole design, a passing test suite hid a false alert that only a replay against real orders exposed, and raising the threshold provably could not fix the promo problem.
BreadcrumbsData qualityPersonal financeSQLiteReliabilitySelf-hosting
August 8 - deciding which of my own records the search index is allowed to believe
Two passes over the memory layer my AI tools search. First, an audit of how eight knowledge bases preserve working transcripts, which ended with part of the index quarantined and a refusal to backfill history. Then importing 708 conversations from a cloud account into that same layer without publishing things to myself that should stay private.
AIRAGKnowledge baseData qualitySecurityReliabilitySelf-hosting
August 7 - two failures with the same shape, six hours apart
A multi-day video conversion job hit two unrelated failures in one day, and both had the same shape: one broken precondition outside any file, turned by the script into thousands of fast, confident per-file failures. A circuit breaker, a pinned driver, and a watcher that looks at rates instead of names.
HomelabReliabilityStorageLinuxTroubleshootingAutomationSelf-hosting
August 7 - the machine that looked broken was not the broken machine
My storage box was showing offline, so I went looking at the storage box. The fault was a network port on a different machine that happened to be the storage box's only route out.
NetworkingIncident responseTroubleshootingLinuxReliabilityHardwareMonitoringHomelab
August 7 - the sync that would have copied a live login onto every machine I own
Porting a working file-sync pattern from one project to another would have replicated a 188 MB authenticated browser session and a secrets file across the whole machine mesh, because the sync tool does not read the ignore file that version control does. Three options, one structural fix, verified by hashes and an absence check.
BreadcrumbsSecurityBrowser extensionAutomationReliabilitySelf-hosting
August 7 - the prompt budget that could only get worse
Every mode of my study assistant returned a server error before the model was ever called. The context packet's fixed part had grown past the safety limit on its own, and one unbounded field guaranteed it would keep growing. The fix went into the shared layer three different callers depend on, with caps chosen by measurement and a degrade path before any error.
AILLMRAGMCPReliabilityTroubleshootingPythonSelf-hosting
August 6 - the migration that stopped being a migration and became a retirement
What started as moving two small apps to the control-plane machine became the complete retirement of a third machine as an application host. Dependency mapping first, an explicit 'park that one' that evidence later overturned, a symlink instead of rewriting a hundred thousand paths, 51 GB reclaimed, and two live credentials caught before they replicated.
HomelabReliabilityLinuxStorageSecuritySelf-hostingAutomation
August 6 - checking whether the job ran, not whether the timer is armed
A sweep after the watchdog repair found the fleet's automation registry listed 13 timers on a machine running 16, two of the missing ones backups. It also nearly 'discovered' a hole that did not exist, because a grep over unit files cannot see configuration that arrives through drop-in directories.
MonitoringReliabilityLinuxAutomationSelf-hosting
August 5 - the alarm that would have caught the outage had never actually started
The service whose only job is to tell me when a device stops checking in was dead. The previous handoff said it had died the day before. It had never worked once: its sandbox was built before its script ran, and it was the one unit in its batch that nobody wired to the failure notifier.
MonitoringReliabilityLinuxTroubleshootingSelf-hostingAutomation
August 5 - teaching a knowledge base to admit what it does not cover
A small domain knowledge base built from an official document and three books, with a coverage gate that reports its own holes instead of hiding them. The useful part was the second pass: of eight apparent gaps, three turned out to be my own bad phrasing and five survived as real, and telling those apart is the whole job.
AIRAGKnowledge baseData qualityRetrievalSelf-hosting
August 4 - I raised an alarm about my own backups, then measured it away
A session meant to make a backup claim checkable first read the config wrong and nearly 'repaired' a healthy setup, then proved one restore, then opened an alarm about unbounded growth. The follow-up the same day measured the alarm and found two of its load-bearing facts wrong. Ruling: accept.
BackupsReliabilityStorageHomelabTroubleshootingSelf-hosting
August 3 - restoring a backup I had never actually restored
I had encrypted backups of my private records and had never once put one back. Proving it worked also revealed that the older archive contained nothing at all.
ReliabilityBackupsSecuritySelf-hostingAutomationTroubleshootingLinux
August 1 - the redaction marker that broke the filter meant to throw the payload away
The redaction fix from the day before had quietly made my search index 21% bigger. The cause was one line in the wrong order, the fix was two changes that were useless alone, and the proof was a measurement that matched the prediction to the character.
AIRAGData qualityKnowledge baseTroubleshootingReliabilitySelf-hosting
August 1 - my agents' instructions were a dozen copies with no owner
The instruction text my AI tools read lived in a dozen places across the fleet: files on each machine, symlinks, importers, and text boxes in someone else's web UI, with no record of which was current. Now there is one registry that owns every surface by path and hash, a checker that verifies 103 of them, and a ruling about what an embedded version token actually means.
AIAgentsOrchestrationReliabilityKnowledge baseSelf-hosting
August 1 - I said the sync would survive losing the middle machine, so I cut it and watched
My notes sync between three machines went through one of them, which made that machine a single point of failure for the other two. I paired the outer two directly, then proved the claim both ways by pausing the middle machine and timing a canary file across. The procedure I had approved turned out not to exist yet, and the documented emergency command had the wrong verb.
HomelabReliabilityFailoverDistributed systemsBackupsSelf-hostingTroubleshooting
July 31 - the secret scrubber that scrubbed everything except the secrets
Three live API keys had made it into the searchable index of my own working history, straight through a redaction step that ran on every byte. The fix was small; deciding how to measure it before trusting it was the real work.
AIRAGSecurityGuardrailsKnowledge baseReliabilitySelf-hosting
July 29 - stopping a cleanup agent mid-run, and putting everything back
A routine tidy-up of my project notes was handed to an automated worker. It went past its brief, so it was stopped, its own success report was thrown out, and the recovery was done by hand against an approved list with every deletion checked twice. The good suggestions it made were kept.
AIAgentsGuardrailsKnowledge baseReliabilityIncident responseSelf-hosting
July 29 - a freshness guard proven against the real defect, and three ways in with everything else a 403
Two passes in a row had shipped the wrong artifact one layer apart: a service running a process older than the source, then a deployed bundle older than the feature commit. The fix was a guard validated by running it against the actual stale artifact, a release workflow with automatic rollback, and, the same day, an API that only three bounded classes of caller can reach.
ReliabilitySecurityAccess controlAutomationTestingSelf-hosting
July 28 - letting whichever machine is free do the work
Jobs were queuing behind a busy machine for no better reason than habit about which machine owned that kind of work, so I separated who runs a job from where the job lands.
AutomationOrchestrationDistributed systemsSSHHomelabReliabilityAISelf-hosting
July 28 - getting a local model to follow an exact plan
The free model on my own hardware kept failing halfway through jobs. The cause was not on my side, and the fix was to stop asking the model to write its own proof of work.
AILLMGuardrailsAgentsReliabilityTroubleshootingPythonSelf-hosting
July 28 - I designed the failover three times before picking the free one
The standby for my agent server was designed as a warm standby, formally ratified, amended five times, and thrown out the same day for an always-on mesh with edge steering. That died too, on cost and on a Pi with 905 MB of RAM. What shipped is a dormant cold standby with its own free tunnel, a structural split-brain fence, and a health endpoint that tells the truth about what it cannot do.
HomelabFailoverReliabilityRaspberry PiCloudflareSelf-hostingDistributed systems
July 28 - a fleet monitor that is not allowed to fix anything
A standing read-only collector that checks contract parity, machine-to-machine reachability, required services and timers, and required paths across the fleet. Three design choices carry it: it keeps last-known-good evidence, it separates drift from probe failure, and it never repairs anything.
MonitoringReliabilityAutomationGuardrailsSelf-hostingSSH
July 27 - two background workers, one status column, and a system that never converged
Prospect's two AI background workers were quietly overwriting each other's status, so every service restart redid work that was already done. The handoff called it a one-row cosmetic cleanup. Reading the two workers against each other showed something worse, and the fix cost no GPU time.
AIOrchestrationSQLiteData qualityReliabilityTroubleshootingSelf-hosting
July 27 - the migration command that defaulted to production
Prospect's migration runner picks the live database when you forget to tell it otherwise. I found that out by running it without the environment variable, against production.
SQLiteSchema migrationsData qualityIncident responseSelf-hostingNodeBrowser extensionDebugging
July 27 - searching what I actually did, not just what I wrote down
My notes could answer what I decided but not what I actually did, so I built a second searchable layer over the working history itself, and a rule for when it is allowed to be used.
AIRAGSemantic searchKnowledge baseSearchPythonAutomationSelf-hosting
July 27 - letting the fleet choose which AI model runs each job
I stopped picking which AI model runs each job by hand and built a router that scores the options on what each one has left, reserves the winner, and writes down why it chose.
AIModel routingAutomationOrchestrationPythonReliabilitySelf-hosting
July 24 - three backups, zero bytes, and a mount that timed itself out
Three database backups written to network storage two weeks earlier were all zero bytes. The cause was an idle timeout on the mount tearing it down mid-write. The obvious retry then failed instantly for a second, unrelated reason, and the worker stopped and asked instead of improvising.
BackupsStorageSQLiteReliabilityTroubleshootingHomelabSelf-hosting
July 24 - finding a quarter of a terabyte of duplicates without deleting anything
Hashing every size-collision candidate on my NAS found fifty-two thousand duplicate groups and about 254 GB reclaimable. The interesting part was everything that nearly made the number wrong.
StorageHardwareLinuxAutomationReliabilityTroubleshootingSelf-hosting
July 22 - auditing whether every machine can really reach every other
I assumed every machine could reach every other. Checking every direction properly found a quarter of the paths broken, and one repair job reported success while having quietly undone itself.
HomelabSSHLinuxNetworkingReliabilityAutomationTroubleshootingSelf-hosting
July 21 - four ways a worker reported success without doing the work
The harness that runs my local model was reporting success unconditionally, no matter what happened. Hardening it went in four phases, each one proving the defect on the unmodified code before patching, and ended with a single terminal contract that maps every way a run can end to an honest status and exit code.
AIAgentsGuardrailsReliabilityPythonTroubleshootingSelf-hosting
July 20 - the file I was re-reading on every single turn, and a handoff I treated as a claim
Two things about working with AI agents that only show up after you have run them for a while: the cost of what you read at the start of a conversation is paid again on every later turn, and a detailed handoff from another agent is a claim until something the author could not have shaped confirms it.
AIAgentsLLMKnowledge baseOrchestrationReliabilitySelf-hosting
July 20 - picking a local model with six tests instead of a hunch
The free model on my GPU box was unreliable in a way I could not name. Before swapping it, I found the reason by reading its transcripts, then ran a controlled A/B of six scored tests in an isolated copy of the real harness. The candidate won 5 to 2, a longer soak passed 21 of 22, and the swap was one line.
AILLMLocal modelsAgentsGuardrailsReliabilitySelf-hosting
July 20 - my persistent logs were persistent, and also volatile
The control-plane machine had a config file explicitly setting persistent log storage, and its logs were still vanishing. A vendor file with a higher number won by sort order alone, roughly nine days of history had already rotated away, and half the reported symptom turned out to be my own wrong command.
LinuxReliabilityRaspberry PiTroubleshootingMonitoringSelf-hosting
July 19 - two rankings that cannot be compared, and a duplicate detector allowed to be wrong
Prospect got meaning-based search fused with its keyword search, and a semantic check for reposted listings. Both had to be designed so they could fail without taking anything down, and the duplicate threshold was calibrated on a copy before it touched the real database.
AIRAGSemantic searchSQLiteGuardrailsData qualitySelf-hosting
July 15 - one conversation, one section: how I keep months of AI-assisted work from losing the thread
Working with AI assistants for months across several projects, the failure I kept hitting was not bad answers, it was lost context: every conversation restarted from zero or dragged a bloated history. The fix was a method: each conversation does exactly one bounded section, and continuity lives in three small documents, not in the chat.
AIAgentsKnowledge baseOrchestrationReliabilitySelf-hosting
July 14 - the aesthetic score was measuring sharpness
I set out to train a small local model to reproduce Apple's photo-quality score so Loupe could rank photos for anyone. It matched Apple almost perfectly. Then a cheap probe showed what the score actually measures, and it was not beauty.
LoupeMachine learningAIComputer visionData qualitySelf-hosting
July 13 - searching sixty thousand photos by meaning, with the recipe pinned on both sides
Loupe can now search photos by describing them, with every model running on my own hardware. The interesting work was not the model. It was proving that the index machine and the query machine compute exactly the same numbers, and finding out the plan's premise was false.
LoupeSemantic searchMachine learningComputer visionSelf-hostingData qualityReliability
July 13 - putting my notes under version control without breaking the sync that copies them everywhere
My entire knowledge base had no undo, and adding one turned out to be a real distributed-systems puzzle because the same files live on four machines at once.
HomelabSelf-hostingGitReliabilityAutomationLinux
July 11 - Loupe's machine-learning pipeline runs entirely on your own hardware
For an app that looks at your private photos, the machine learning cannot phone home, so every model in Loupe runs on your own machine, on a single consumer graphics card.
LoupeMachine learningComputer visionGPUSelf-hostingSemantic search
July 11 - a successful ping that my monitor scored as a failure
One machine showed permanently critical on the fleet dashboard while every other check against it was green. The probe was judging health on a ping command's exit code, and a ping that succeeds through a relay returns a real answer, a real latency, and a non-zero exit code.
MonitoringDashboardsNetworkingTroubleshootingTailscaleSelf-hosting
July 10 - how Loupe decides what to keep: labels nominate, scores order, people protect
Culling a hundred-thousand-photo library should never come down to one mysterious score, so I split the decision into three plain roles that each answer a different question.
LoupeMachine learningProduct designComputer visionSelf-hostingPython
July 9 - the dashboard learned to reach my phone: an installable app with tiered alerts
Turned the fleet dashboard into an installable phone app with alerts tiered by urgency, a critical channel that survives the app itself failing, and a dead-man's switch that can announce its own death.
HomelabSelf-hostingPWAiOSPush notificationsReliabilityMonitoring
July 9 - a compute box hitting 115°C, and the safety net that let it run hot but safe
My busiest compute machine was cooking itself under real load, so instead of just turning the work off, I built layers of protection that let it keep running without frying.
HomelabReliabilityHardwareMonitoringLinuxSelf-hosting
July 8 - searching my notes by meaning, not by keyword
I taught my notes to answer questions by meaning, so I can ask what I decided about X and why and land on the right note even without remembering a single word from it.
HomelabSearchSemantic searchRAGKnowledge baseSelf-hosting
July 7 - a knowledge base that rebuilds and reorganizes itself every night
I write a lot of scattered notes across a lot of projects, and instead of ever organizing them by hand, I built a pipeline that rebuilds and reorganizes the whole thing every night while I sleep.
HomelabAutomationKnowledge basePipelinesReliabilitySelf-hosting
July 6 - running my own models so the routine work costs nothing
A lot of the small jobs I was paying a hosted model to do did not need a frontier model at all, so I started running open-weight models on my own GPU box and let the routine work run for free.
HomelabGPULocal modelsCostAutomationSelf-hosting
July 5 - handing a job to whichever machine should run it, then walking away
I stopped doing my machines' work at the keyboard and built a way to hand a task to whichever machine should run it, let it run on its own, and pick up the result later.
HomelabAutomationDistributed systemsSSHOrchestrationSelf-hosting
July 4 - the night my control-plane machine died and I failed the homelab over to a standby
I caused an outage by running a heavy job on the one machine that holds everything together, then spent the night proving my failover plan actually works.
HomelabReliabilityIncident responseFailoverMonitoringSelf-hosting
July 3 - a dashboard that became a second brain: fleet health, my notes, and search over my own work
Pulled fleet health and my entire notes vault onto one gated dashboard, then added semantic search over my own work by borrowing an embedding model already running elsewhere on the network.
HomelabSelf-hostingDashboardsSearchSemantic searchPythonKnowledge baseAccess control
July 2 - reads that fell from 96 to 12 MB/s on a link that swore it was fine
Every cheap health check told me my storage link was perfectly healthy while it was actually running at a tenth of its speed, and the real work was proving which piece was lying.
HomelabNetworkingStorageTroubleshootingReliabilitySelf-hosting
July 2 - every machine can now reach every other, with no passwords and no key juggling
Turned the fleet into a full mesh where every machine can reach every other one directly, passwordless, without hand-managed keys, laying the groundwork for running work anywhere in the fleet.
HomelabSelf-hostingTailscaleSSHNetworkingAutomationLinux
July 1 - a spare radio, a 0.06-second failover, and two interfaces that renamed themselves
Adding one wireless card to the GPU box renumbered the PCI bus and renamed two interfaces, not one, which took down the isolated storage network's gateway through a chain nothing had advertised. The rebuild made the two radios a primary and hot spare, with a measured failover gap and an idempotent service.
HomelabNetworkingHardwareLinuxReliabilityFailoverSelf-hosting
June 30 - moving the front door off the busy machine so a reboot cannot take the site down
Moved the public tunnel entrance off the busy compute box onto its own dedicated Raspberry Pi with its own uplink, so a reboot of any other machine can no longer take the whole public site down.
HomelabSelf-hostingNetworkingCloudflareTailscaleReliabilityRaspberry Pi
June 27 - a Swiss redesign, a GPU box, and a wiki over all my notes
Reskinned davidgomez.cc into a Swiss/International typographic system, stood up a GPU box and proved face-embedding runs on it, and brought up a regenerable wiki over every one of my notes, run by hand for now.
Design systemsTypographyCSSEleventyMachine learningGPUCUDAPythonLinuxKnowledge graph
June 26 - the demo stops faking it: every view wired, real face crops, live in production
The marketing demo stopped being a screenshot tour and became the actual Loupe app running over a hand-authored fixture, every view wired in owner mode, real detected face crops, a library full of believable junk frames, and a production deploy.
DemoWeb UICloudflare PagesJavaScriptPythonImage processingComputer visionProduct strategy
June 25 - faking a whole photo library, teaching the demo to act like the app, and mapping it as a graph
Built a reusable image generator to manufacture a synthetic photo library for the public cull demo, wired the static demo to behave like the real app across several views, and ran a code-mapping tool over the app to chart its architecture and data flow.
DemoGeminiImage processingMachine learningKnowledge graphPythonData qualityWeb UI
June 25 - breadcrumbs: exporting my own grocery orders, one logged-in page at a time
breadcrumbs is a free, open-source browser extension I built to read my own logged-in grocery order pages and export a clean, itemized file, every item by name, with the price, so my budget knows what the basket actually held. Two store integrations work, the Chrome version is verified end to end, and a Safari port is in progress. This is what it is, why I built it, and what's still open.
BreadcrumbsBrowser extensionOpen sourceJavaScriptManifest V3Web scrapingChromeSafariPersonal financeData export
June 24 - a demo that ships without a backend, and a render pull that lied about succeeding
Built a static, backend-free demo of the Loupe cull UI, untangled an edited-render pull that reported success while writing nothing, fixed an importer that choked on Live Photos, and reorganized all my project notes into one place.
DemoiCloudApple PhotosImage processingData qualityDebuggingBackups
June 23 - Loupe's first offsite backup, lazy render lookups, and getting my notes out of iCloud's way
The whole project finally lives somewhere other than my one machine: I pushed a private backup of the repo. Then a small but careful change to how the app resolves edited renders so it stops checking thousands of network files on every restart, plus a power-outage readiness check and a move of my project notes vault out of the folders iCloud sweeps.
GitBackupsImage processingWeb UIPythoniCloud
June 22 - three ways to ship the wrong thing to Cloudflare Pages
Adding a devblog to my site took three tries to deploy: a nested zip, a misplaced git init, and a commit Cloudflare refused to stop rebuilding. All three were the same mistake wearing different hats.
cloudflare-pageseleventydeploygit
June 22 - folding two repos into one, then scrubbing my git history clean before the first push
I merged Loupe's pipeline repo into the app repo as a folder with its history intact, untangled a code-vs-data path coupling that would have quietly broken everything, and then (right before the first push to a private GitHub repo) ran a full-history audit that came back dirty. It found a home address, GPS coordinates, and a dump of my home network's internal layout buried in old commits. The rest of the day was rewriting history to purge all of it, normalizing my commit identity, and collapsing everything to a single clean branch.
Gitgit filter-repoRepo consolidationPythonEnvironment configurationData privacySecurityAI agentsBackups
June 21 - glass on the surface, plumbing underneath
A wide day: shipped a dormant glass redesign and a fully responsive header, chased a stats box that kept clipping, traced six photo-pairing mis-binds down to a filename suffix, sorted out the faithful way to pull edited photos, relocated my notes vault, and ran a security review I'm keeping vague on purpose.
Design systemsCSSResponsive designTypographyWeb UIBrandingPythonSQLiteMetadata pipelineData qualityImage processingBackupsDebuggingProduct strategy
June 20 - a portable enrichment import, a console that runs the pipeline, and a brand pinned down
A long, many-threaded day. I built the full path that lets a second person's Apple-photo metadata become my app's enrichment database, turned the setup console into something that can actually start the pipeline stages itself, split the frontend out of the monolith, unified the brand across app and marketing site, stood up real email, and fixed a couple of bugs that had been silently breaking the photo pipeline for a while.
PythonSQLiteMetadata pipelineImage processingConcurrencysystemdCloudflareBrandingDesign systemsTypographyCSSWeb UIProduct strategyFace recognitionDebuggingData quality
June 19 - the day Loupe got its name, its face, and its front door
A long, many-threaded day: renamed both repos to loupe, shipped the Map and Cutting Room pages, built the in-app People view on top of face embeddings, redrew the brand mark down to the favicon, and stood up the onboarding front door.
PythonSQLiteFace recognitionMapsBrandingDesign systemsTypographyCSSWeb UIsystemdCloudflareDebuggingData qualityPerformance
June 18 - the motif gets deeper the closer you zoom
Redesigned the month view's day cards into a contact-sheet film motif, polished the marketing site's desktop nav, survived a concurrent-edit collision that wiped my work off disk, and finally figured out what machine I've actually been building on.
Web UIDesign systemsCSSBrandingConcurrencyDebuggingPythonLinuxCloudflareProduct strategy
June 17 - making Loupe leave my box: portability, a brand, and a public site
The day Loupe stopped being a thing that only runs on my machine. I reframed it as a self-hostable product, made the pipeline portable through environment variables, scrubbed both repos clean for publishing, finalized a brand and header, fixed the photobook's repetition problem, and registered loupeculling.com for a marketing site.
Product strategyPythonDependency managementSQLiteBackupsBrandingDesign systemsTypographyCSSWeb UIPerceptual hashingData quality
June 16 - a kernel reboot, two apps becoming one, and an enrichment layer pulled from Apple
The day the project got a name and a spine: I came back to a box that had silently rebooted, merged two sibling photo-review apps into one managed service called Loupe, added a cloud backup and AI-written period summaries, and built an enrichment layer that joins Apple's own labels, people, and scores back onto my library by UUID.
SQLitesystemdCloudflareFFmpegImage processingPerformanceConcurrencyMetadata pipelinercloneBackupsBrandingData qualityDebuggingProduct strategy
June 15 - the 462 GB move that took 0.09 seconds, and two review tools
A long day: turning a 462 GB copy into an instant rename by understanding the filesystem, catching two backup processes about to trample each other, and building two browser tools to actually review ~91,000 photos without ever deleting one by accident.
SMBrcloneBackupsSQLiteWeb UIFFmpegConcurrency
June 14 - a dependency cascade, and matching files back to a list
An aggressive package cleanup quietly broke my storage box's admin UI through a dependency I didn't know mattered. Then a filename-normalizer bug taught me to trust byte sizes over names when matching files back to a database.
LinuxDebuggingDependency managementSQLiteData quality
June 12 - culling rules that find nothing, and a bucket that wasn't what it looked like
With the metadata database built, I started defining rules to surface delete-candidates. Two of the rules found nothing, one was secretly broken, and the biggest 'junk' bucket turned out to be footage I'd never delete. The lesson of the day: rules lie, pixels don't.
SQLiteFFmpegPerceptual hashingImage processingData quality
June 9 - building a photo-metadata pipeline with no sudo and a flaky network
Day one of the photo project: build an ingest tool that reads ~91,000 photos and videos into a SQLite database so I can cull systematically. The build went fine. The environment fought me, and the performance lessons were not where I expected.
SQLiteMetadata pipelineExifToolPerceptual hashingConcurrencyPerformancePython
May 10 - my cloud capstone: pricing, deploying, and auto-scaling a web app on AWS
My cloud course capstone made me do the whole job, price an architecture before building it, stand it up on AWS, make it survive load, and move its data to a second cloud.
AWSCloudHigh availabilityAuto-scalingCost estimationMulti-cloud