Notes

The most intense Saturday: editor, ingestion, and a DNS that went down

Twenty commits: own dashboard in admin, AI buttons, ingestion queue with

3 min read

The most intense Saturday of the project

Twenty commits (each saved point in the code, with its message). Phase 5 almost done, the start of phase 6, and a DNS that went down in the middle of a build (the process that compiles the whole site). It goes in order. If you've gotten lost, one of the previous chapters of the diary is The migrator already talks to the production WordPress.

An own dashboard

Custom /admin view: five cards (monthly published, pending, LaIA plans, subscribers, LLM consumption) and three direct shortcuts. Making it display required fixing the build twice: Payload's importMap —the new content manager— generates itself and isn't committed; without it, custom views don't register. And payload-types.ts is in gitignore (the list of files that Git ignores), so the server build was failing with new collections until generation was added to the process.

On top of that, a bug in Payload 3.89: passing the locale object as clientProp to a client view breaks RSC serialization (the format with which React passes data from server to browser) with a Functions cannot be passed directly to Client Components. Solution: a wrapper without use client that receives the view and mounts the dashboard without props.

AI buttons and gated ingestion

Contextual AI buttons for title, copdet (catchy description), metaDescription and alt on four collections, with live preview on the same.

The ingestion queue (T5.6): what the agent generates no longer publishes itself. It arrives with estatFlux=revisio, publishing is admin-only, and discarding requires a reason. On the agent endpoint, four corrections of the day: lookup by the HMAC index of the API key (a fingerprint of the key, not the full key), not by the encrypted key; structured fields don't accept null (a $push on null breaks); review images go to a child table, because Postgres (the database) doesn't store arrays inside jsonb; and a cast because the drafts type requires a flag we don't use.

Ghost number 418

The hydration error —the server renders one thing and the browser expected another— came back: an <a> inside an <a> on the home, and the admin's lang. And edit views were returning 500: the dependency tree had an old version of a Payload package (3.45 installed, 3.89 required) and AiAssist's deep imports were breaking it. Out phantom version, views alive.

DNS dies mid-build

Mid-build, the machine's DNS went down and builds were hanging waiting for Google fonts. DNS is the address book that translates names into addresses; without it, nothing. Two patches: pre-resolve domains in /etc/hosts, and public DNS (8.8.8.8, 1.1.1.1) on the app service. After half an hour, everything reverted: when Tailscale's MagicDNS (the server's private network) comes back, the patch is noise. A recovery script remains, and a lesson learned: if payload migrate finds batch = -1 markers, it asks for interactive confirmation and halts the deploy (publishing to the server). The deploy cleans up these rows before migrating.

And at night, advertising

First phase 6: zone engine with weighted rotation, own impression and click tracking (/ads/imp, /ads/click), and statistics panel with page /publicitat. The detail of the day: the active campaigns filter was comparing against CURRENT_DATE, which in Postgres is UTC (world time). An active campaign at 00:30 local didn't show up. Now it compares against (now() AT TIME ZONE 'Europe/Madrid')::date.

What's left

Phase 5 nearly closed. Phase 6, lit up. And the server, with DNS alive again.