The through-line of this quarter was moving from building B&B websites to running a B&B’s entire digital operation. Three months ago the work was still mostly “make the site look good and convert.” By the end of June it was an automated stack: sites that rank and convert, a weekly report the owner can act on, and — new this quarter — Google Ads that a pipeline audits and adjusts on its own.
All the clients here are B&B operators in Hengchun. By quarter’s end the portfolio was nine B&Bs across five codebases, with three operators managing five properties under the ads engine.
Two new sites, and a real content-ownership problem
Early in the quarter I finished and shipped a two-property brand’s site and then stood up two more sites from scratch. The first was a single-property inn; the last was an umbrella brand covering two guesthouses. Each followed the same Astro bilingual pattern — zh served at root, en parallel, responsive AVIF/WebP image pipeline, Lighthouse-driven performance passes.
The interesting problem wasn’t the pages. It was: who edits them after launch. For the single-property inn I built a self-hosted Decap CMS with GitHub OAuth so the operator could publish promo banners, seasonal messaging, room content, and pricing without a developer or a git commit. The line between operator self-service and developer work sits at structure: new page templates, new feature types, and layout changes still need me — but content, the high-frequency stuff, costs nothing to change now.
The CMS took far longer than the site did. Most of the time went into fighting Decap’s built-in UI — a teal header strip that Chrome kept re-tinting, list widgets that wouldn’t show item names, image previews that showed deleted files. Bending a third-party admin into something a non-technical operator would actually trust is unglamorous work, and I under-budgeted it badly.
For the two-property brand I later took a different route: a purpose-built Astro SSR admin app with Google OAuth, an email allowlist, and CRUD for promo banners and cards backed by GitHub storage. Same goal — operator owns the content — reached two ways, because the constraints differed. That’s the honest version of “reusable”: the pattern reused, the implementation didn’t.
The analytics agent grew a spine
The biggest arc of the quarter was the analysis agent. It started as a three-stage pipeline (fetch → analyze → render) producing a weekly operations report from GA4, per-property business context, and later Google Search Console and Google Ads data. Then it grew four more layers I didn’t fully anticipate needing:
- An anti-fabrication verifier that rejects any number the LLM can’t cite back to source data. Getting this right was a running battle — every week the verifier would false-positive on some legitimate string (a search term with digits in it, a Chinese year reference, a PSI score written “55/100”) and I’d tighten the strip rules. By June it moved to a fail-open model: if verification exhausts its retries, the report goes only to me for human review, never to the client. Zero fabrication risk at the cost of occasional manual passes.
- A
site-snapshottool that parses each site’s own components to inventory CTAs and pricing, so the report reasons about the actual live site, not a stale description. - An
operator-events.yamllog. This one matters. The agent’s business-context document is static — it only knows what it was last told, which can’t keep pace with how often the operator actually changes things. That gap caused misattribution: the AI would credit a metric swing to the wrong cause. The fix was a machine-readable, continuously-appended event log. When I take a recommendation from the weekly report into a deeper session with Claude Code inside the client repo and we co-design an intervention, that decision gets written to the log. The next analysis run reads it — so the AI evaluating outcomes already knows what changed and why.
That closed loop — the report surfaces a problem, a human-plus-AI session deepens it into a plan, the plan is recorded in structured form, and the next run consumes that record — is the real product. I’m the lead; two AI systems play different roles (one analyst, one strategy partner); the handoff between them is structured, not verbal.
Killing the click proxy
For months the conversion metric was a proxy: on-site CTA button taps — phone, WeChat, book-now clicks captured by GA4 — standing in for real bookings. That wasn’t a mistake to be fixed. It was the only viable option given a real constraint: the operators record bookings on paper, and there was no low-friction way to get real booking data into a digital system.
So I built the low-friction path. An A5 paper form designed for a non-technical operator to log every phone booking, in their own vocabulary, with age as an estimate and a “not sure” option. Then a booking-tracker integration that feeds those real bookings into the pipeline, splitting into received (the primary signal) and staying views, and a monthly-occupancy analysis per property. Late in June the fetch layer switched from the click proxy to real booking data. The proxy served its purpose; the tool replaced the gap it was filling.
Whether this holds isn’t a question of operator discipline — it’s a question of friction. My job is to keep lowering the threshold until logging every booking is the path of least resistance. That’s still an open bet.
Ads became a product of its own
The part I didn’t see coming at quarter’s start: the Google Ads work turned into a full sub-system. A manual ads health-check Action grew into dozens of calibration rules — spotting off-domain sitelinks, disapproved ads, geo-targeting that reads presence vs. interest correctly, quality-score education written in plain language for a reader who isn’t a PPC specialist. Then a keyword-optimize pipeline that suggests additions and negatives, routes them through a GitHub review UI, and executes on approval. Then a five-layer keyword-cleanup that detects weak-signal, redundant, and zero-conversion keywords. A hard constraint threaded through every layer: for operators running two properties on two separate ad accounts, the accounts stay completely separated — no cross-contamination, which turned out to be a recurring source of bugs when snapshot data leaked between sites.
By late June I onboarded a fourth site (ads-only, no website work) and a third property brand, wired up conversion-action setup, PMax campaign scaffolding with LLM-generated assets, and a portfolio-level report across all clients. A Google Trends snapshot job now feeds shared Hengchun-market search-trend data into every site’s weekly report.
This is the framing that matters for the business side: none of this would have happened without AI — not because a team was replaced, but because the work was previously unaffordable for this category of client. Engineering, data analysis, ad optimization, and weekly reporting each used to demand dedicated headcount. Together, far out of a small B&B’s reach. AI made a previously impossible service economically viable for the first time. The weight OTAs carry comes from their advertising budgets, years of built trust, and full-time marketing teams — against that, a lone site with no analytics and no ad strategy is invisible. The commission rates OTAs charge are higher than most people realize, and inescapable. Closing that gap needs the whole stack, which is what this quarter built.
Where this heads next quarter
Three threads are open. The real-booking data loop needs to prove it holds in daily use — the metric only means something if the logging habit survives. The ads sub-system needs the same hardening the weekly report already got: fewer false alarms, more caught real problems. And the portfolio is now large enough that the cross-site management report is becoming the primary lens — less “how is this one site doing” and more “where across all clients is the next highest-value hour.” The infrastructure for that shipped in the last week of June; next quarter is where it earns its place.