The shopping list acquired a server
An afternoon of themes, real keys, live updates, and asking whether anyone timed it
Security engineer. Purple team. Outdoors Man. Father of two ♂
Luleå, Sweden
The previous harness ran out of tokens. Clip already existed. The next session read the repository, found three bugs, and suggested where to start. I picked themes. They looked cool.
The picker set the standard
Seven palettes, live previews, a choice saved per user. Once Appearance looked good, the rest of Settings needed to catch up. Credential cards, clearer labels, better mobile layouts. The invite output became just a link and a copy button. That was what I needed on the phone.
Multiple lists worked before their placement felt right. One menu in the upper-right corner for Lists and Settings. Sign out belongs in Settings. Then make the list title clickable: tap the name, choose another list. Later, add + New list to that dropdown. Even 2 l mjölk became Mjölk · 2 l. A shopping app is mostly a few words and where you put them.
This was not one prompt producing a finished interface. It was looking at each result and saying which part felt wrong.
The same key, a different requirement
The first passkey fix prevented one credential from belonging to two users. I registered the same YubiKey for two household members anyway. It worked because the key created separate credentials.
The intended rule was several keys per user, but one user per key. Registration was changed to check active Clip credentials, with virtual-key tests for rejection and reassignment after revocation. WebAuthn does not expose a physical key serial number, so this is credential-based detection, not hardware inventory. Erasing credentials changes what can be detected; old registrations cannot retrospectively be tied to the same physical device.
The first fix passed its interpretation of the requirement. Trying the actual key corrected that interpretation.
The phone is part of the test
Docker Compose put Nginx in front of Clip for local HTTPS. The iPhone downloaded the local CA and installed its profile. Safari still objected. I suspected name resolution; the endpoint and local discovery checks looked right. The missing step was enabling full trust for the root certificate in iOS. Installed and trusted are separate states.
So are built and deployed. Clip embeds its UI assets in the binary, and Docker runs the built image. One attempted refresh left the old build serving. The session retried and checked the live stack. A message saying “done” is less useful than the phone showing the change.
WebSockets were not the bug fix
The review had found lost drafts during live updates, partial recipe imports after failure, and authenticated content surviving in the offline cache. I asked whether WebSockets would prevent the first two. No: a WebSocket handler can still reload the page, and a connection does not make database writes atomic.
We fixed input preservation and wrapped imported items and events in one SQLite transaction first. Then a separate branch introduced WebSockets, reconnect catch-up, and a Live/Reconnecting/Offline indicator.
I went to the sauna while the work ran. I also interrupted the wrong terminal with Ctrl+C. The session continued from the completed fixes and tested two browsers, restart recovery, session expiry, household isolation, and the real HTTPS proxy.
The cache problem still needed its own change. Only public assets would be cached; old caches would be removed; offline navigation would show a neutral reconnect page. By that point the session reported 43 passing Rust tests, plus browser checks for logout, offline navigation, back-navigation, and account switching.
Did you time it?
Fifteen temporary lists, 226 randomly selected Swedish items. I asked about bottlenecks. The agent had not measured the run, so it could not give a measured answer. We built a repeatable benchmark instead.
The benchmark used a separate database and HTTPS stack with the deployed release image. Fifteen lists, a fixed seed, 215 items, five repetitions. Creation, import, and verification were timed separately. These were laptop loopback results, not iPhone-over-Wi-Fi results.
Warm median import time was 117 ms with batches and fresh HTTPS connections, 61 ms with connection reuse, and 1,022 ms with individual requests over a reused connection. Two concurrent batch clients took about 55 ms. Five did not improve that. Batching mattered more than adding clients.
The interesting slowdown came with seven visible items and a growing event history. Median page response went from 1.7 ms at 100 events, to 25.8 ms at 10,000, to 163 ms at 50,000. Rendering loaded and deserialized the entire history just to find the latest event ID. An indexed helper already existed.
Every measured check or uncheck also fetched two list snapshots in the acting browser and one in the other browser. The local action and the WebSocket notification both asked for a refresh. Faster notification had exposed duplicate work.
One attractive result was thrown out: simulated freeze/resume was fast because Chromium never actually froze. The report excluded that timing. Real iPhone lock/unlock testing remained open. The findings became issues, and the temporary lists were deleted. Finding an optimization is not implementing it.
A server in the other terminal
Meanwhile another session read Akre, the infrastructure repository, and began preparing a local model backend. Clip needed an API, not a chat interface. The plan became Debian and Ollama, initially eight vCPUs, 16 GiB of RAM, and 100 GiB of disk. The target had older Xeons and no compute GPU. Start small and measure.
The hosting plan connected a web frontend, an application container on a DMZ machine awaiting migration from VMware to Proxmox, and a private inference service. The model server could be prepared independently. At the log snapshot, capacity and address checks were done and the installer image was checksum-verified; a working Ollama API was not yet confirmed.
Back in the app session, owner-only AI settings were being started: provider, server URL, model, optional API key, and a connection test. Native Ollama first, with an OpenAI-compatible option. Several sessions could move work forward at once, but they did not automatically know what the others had done.
Sort the groceries before inventing the aisle
The first model feature will be AI sort: press a button and group items into fruit and vegetables, dairy, meat, frozen, and similar categories. Preserve the quantities and checked states. Keep ordinary shopping usable when inference fails.
The larger request attaches a specific store branch to a list. Store-specific skills retrieve product and location data; the model helps match items. A plausible shelf number is not a shelf number. It needs a source. With no store selected, there should be no shelf UI, placeholders, or store lookups. Generic category sorting is separate and can still work.
There was also a request for shading on the app icon. Database transactions, certificate trust, a model server, and making the Home Screen look nicer. All in the same afternoon.
Leave the next session evidence
At this stopping point, live sync and the three reviewed bug fixes were deployed. The benchmark had identified the next costs. The inference server and AI settings were underway; sorting and store lookup had not shipped.
The difficult part was tracking the state of each claim: requested, implemented, tested, merged, built, or actually running on the phone. Reading the sessions made the picture coherent again. Use the result, correct the interpretation, measure the behavior, and leave enough evidence for whoever picks it up next.
Check the milk, then check the claim.