Two PRs today that have nothing to do with each other. One is a grab bag of developer quality-of-life fixes I kept putting off. The other is full-blown multilingual support for the landing pages. Both felt overdue.
A command center at /dev/
I added a new route at /dev/ that shows a grid of quick links to every service the project talks to: PostHog, Vercel, Xero, GitHub, Stripe, plus placeholders for six more I'm wiring up soon (SendGrid, Linear, Fly.io, Cloudflare, Stripe Connect, 1Password).
It's dev-mode only. The route registers when PORTAL_DEV_MODE=1, and middleware redirects any /dev/* request in production as a second safety layer. Simple dashboard cards — name, one-line description, button that opens the right tab.
This solves the "which bookmark folder was Xero in again" problem about fifteen times a day.
Secrets from macOS Keychain
Every secret used to live in .env, and I had to keep .env in sync between the Mac mini and the laptop. Easy to mess up.
Now honest-cam reads secrets in this order: environment variable first, .env file second, macOS Keychain third. For my two machines, Keychain means there's one encrypted, OS-managed source of truth, and .env becomes just for dev overrides. The Keychain helper silently falls through if the key isn't set, so the same code runs fine on Linux or in CI.
Proactive Xero token refresh
The bug that kept biting me: Xero refresh tokens expire 60 days after last use. If I don't run a Xero command for two months — easy during heads-down feature work — the next run hits a 401 and I have to re-authorize through the browser.
Fix: every honestcam xero-* command now refreshes the token at startup, no matter what. The refresh itself counts as "use" and resets the 60-day clock. One extra HTTP call per invocation, which is nothing at this scale. A little status line confirms it happened.
Vendor mapping fixes and other small stuff
Three new real-world vendor aliases for the Xero sync — Waste Management, Nobus Group II, and Neighborhood Compliance Services — each with multiple spelling variants that Bamboo House's bills uncovered. Also tightened hero spacing on mobile from the landing page redesign, retook OG screenshots at 1200x630 so social previews match the current layout, and updated a couple of docs.
Landing pages in Spanish, Portuguese, and Italian
Miami is a Spanish-and-Portuguese-speaking city. A lot of my target users — property managers, board members, owners — default to one of those languages before English. Italian is mostly opportunistic: there's a big Italian-speaking condo population in South Florida, and once the pipeline exists, adding a language is cheap.
The new honestcam i18n CLI has three subcommands: add-language scaffolds a new locale, translate runs a page through the Anthropic API, and sync re-translates any page whose English source changed. Under the hood each page is walked as an HTML tree. Text nodes get translated; tags, attributes, class names, JSON-LD — all left alone. That matters because the CSS naming convention and structured data fields like @type and addressCountry need to stay in English.
Sixteen total landing pages now (four English originals times four locales). Every page has a language switcher — EN | ES | IT | PT — that swaps you to the same page in the new locale, not the homepage. Plus hreflang tags so Google returns the right locale without the four versions competing against each other for ranking. Plus Vercel rewrites so /es, /pt, and /it work as clean root URLs.
Adding the next language is one command:
honestcam i18n add-language ht # Haitian CreoleIt scaffolds, translates, registers the locale in vercel.json and the switcher, and emits hreflang tags on every existing page. No manual file shuffling. Haitian Creole is the likely next one for this market.
PRs: