Two very different problems today. First: Florida law says condos over 150 units need a website with their official records on it. Bamboo House is over that line, so honest-cam needs to ship one. Second: honest-cam itself has no public homepage — if I send someone a link there's nothing to see. Both got fixed tonight.
The association website
Florida statute §718.111(12)(g) requires certain document categories to be available online. Some publicly, some behind a login. So the association website has two zones:
- Public pages at
/website/*— homepage, about, contact, meeting notices. No login required, which is exactly what the statute asks for on things like meeting notices. - Auth-gated records at
/website/records— 17 statutory document categories (contracts, financial reports, budgets, insurance certs, director certifications) behind the magic-link login the owner portal already uses.
The fun part is how 940 documents got sorted into the right buckets. A new website-populate CLI reads the OCR sidecars from earlier pipeline stages and symlinks every document into one of four categories. Symlinks, not copies — the canonical file stays in the ingest pipeline and the website just points at it. Re-running the command is idempotent and classification comes straight from the OCR data, no hand-labeling.
I also wrote a competitor reference doc covering 20+ providers — CONDUU, CondoSites, BuildingLink, and others. Not because I want to copy them. I want to be clear about what honest-cam does that they don't (native Xero integration, OCR-driven doc organization, real compliance tracking) and what they offer that I haven't bothered with yet (branded domains, fancy public pages).
Dev mode auto-login
Testing the website meant requesting a magic link, copying it out of stdout, pasting it into the browser, logging in, and clicking through. Over and over. New env var: PORTAL_DEV_MODE=1. When it's set, login skips the magic-link dance and auto-creates a session for the first unit owner on file.
It only works on localhost — the middleware rejects anything that isn't 127.0.0.1 or ::1. Four tests specifically cover that guard because I want the "oh god this shipped to prod" scenario to be impossible, not just unlikely.
Xero PDF attachments
This one's been bugging me since PR #4. When honest-cam syncs a bill or invoice to Xero, it now attaches the source PDF to the transaction. So when a board member opens Xero and clicks on a $6,000 waste management invoice, they see the actual invoice — not just a line item that says "$6,000, Waste Management."
That closes the loop between the OCR pipeline and the ledger. Every Xero transaction is traceable to its source document in one click.
A backfill command, honestcam xero-attach <slug>, walks all historical synced transactions and attaches PDFs retroactively. Idempotent — Xero's API returns the existing attachment ID on duplicates, so running it twice is harmless.
A small bonus: scripts/xero-reauth.sh handles the full OAuth dance from the command line when Xero refresh tokens expire (every 60 days of non-use). Scheduled weekly so I never have to think about it.
Landing pages
honest-cam has been all backend until now. The portals are real, but they're password-gated — no public URL that explains what the thing is. This PR fixes that with four static HTML pages under packages/landing/:
index.html— top-level pitch.managers.html— for property managers.owners.html— for unit owners and board members.investors.html— for the fundraising crowd.
One shared CSS/JS bundle (387 lines of CSS, 100 lines of JS) so every page gets the same fonts, nav, and look without duplicating anything. Deployed via vercel.json routing. Plus a docs/feature-analytics.md plan for what to instrument — PostHog for behavior, Vercel Analytics for Web Vitals, event tracking on CTA clicks.
The pages are functional but cosmetically bare. The real visual design, SEO, and copy rewrite come in later PRs. This is the scaffold.
Verification
- 21 website route tests — public pages render, auth gating works, file downloads stream, path traversal is blocked.
- 4 dev-mode login tests — auto-redirect, session cookie, env-var guard, non-localhost rejection.
- 23 populate tests — category mapping, symlink creation, idempotency, dedup, error handling.
- Full suite: 575+ tests passing, ruff clean.
PRs: