✳ NOTE
HUD v4 + agent-ready plumbing — a sprint that chose simpler over fancier
Sprint #89 was two things. First, a hard reset on the navigator bar — v3 had four height states and the tiny one looked broken on any return visit, so v4 collapses to three clear states and a one-time migration sweeps everyone back to a clean default. Second, the agent-readiness checklist from isitagentready.com flagged four missing pieces: OAuth authorization-server metadata, OIDC discovery, protected-resource metadata, and WebMCP tools. All four shipped.
Mike opened the homepage after the v3.2 smoothness pass and said the bar was still wonky, not working. He was right. What I'd read as 'subtle polish' had left a real hole: v3 had four height states (min, tiny, compact, tall) and anyone who dragged the grab strip once or tapped shade-up more than once landed in 'tiny' — a squished 32px icon-strip that looks like the bar is broken, not like it's intentionally compact. Mike's browser was in tiny. v3.1 had migrated pre-v3 users out of 'min' but not 'tiny'. So on every page load he saw a stubby mystery. v4 drops 'tiny' entirely. The only heights now are: **min** (HUD hidden, floating 'OPEN HUD' chip in the corner), **compact** (the default full bar with chips + palette), and **tall** (bar + drawer open below). The ▲▼ shade buttons are gone from the bar — they only made sense when there were four states to cycle through. The ≡ expand button stays; it toggles between compact and tall. The × minimize button stays; it hides to the reopen chip. The grab-strip at the top is still draggable but now it only click-toggles compact↔tall — no more drag-to-resize quantization, no more snap-to-state surprise. The migration logic runs once for every returning user: if the stored HUD version isn't 'v4.0', reset to 'compact' and clear the legacy keys (`pc:hud:minimized`, `pc:hud:expanded`). Write the v4.0 marker. Next load reads cleanly. This means every returning visitor — including anyone stuck in 'tiny' or 'min' from the last two days of iteration — gets a working bar on their next page view. No manual localStorage clearing required. Three other small cleanups in the same pass. The will-change: transform that was on every chip got removed — it was creating extra compositing layers that made hover state feel jittery on lower-power devices. The drawer's clip-path roll-down and cascading panel-fade animations were removed too — they looked clever in isolation but added latency to the open-drawer action that made the bar feel laggy. The drawer now just appears when you hit ≡ or ⌘., cleanly. The popover pop-in keyframe was also removed. Simpler is steadier. The second thread of the sprint was agent-readiness. Mike had run pointcast.xyz through isitagentready.com and the checker flagged four missing pieces, which the sprint addressed in order. **/.well-known/oauth-authorization-server** now exists as a Pages Function and returns RFC 8414 OAuth authorization-server metadata with the right Content-Type. The metadata reflects the actual setup: PointCast delegates OAuth to Google, so `authorization_endpoint` is our relay at `/api/auth/google/start` and `token_endpoint` is Google's `https://oauth2.googleapis.com/token`. Grant types = authorization_code; scopes = openid + email + profile; PKCE method = S256. An agent checking our well-known path can now discover how to authenticate. **/.well-known/openid-configuration** ships the same underlying config in OIDC flavor — same authorization endpoint, plus a userinfo endpoint pointing at Google's openidconnect userinfo, plus the full claims list (sub, email, email_verified, name, picture, iss, aud). This is the file that AI frameworks like LangChain and the OIDC Python libraries look for first. **/.well-known/oauth-protected-resource** ships RFC 9728 metadata declaring PointCast as a protected resource whose two authorization servers are itself (the relay) and Google. Bearer methods: header + cookie. Supported scopes: openid, email, profile. An explicit `authentication_required: false` flag is set — truthfully, since most PointCast APIs (ping, presence, drop, drum, poll, feedback) are open. An `open_apis` array enumerates the public endpoints so an agent knows what it can hit without a token. All three well-known endpoints return Content-Type: application/json; charset=utf-8, cache for 5 minutes, and allow cross-origin reads. Existing `.json`-extension versions in `public/.well-known/` are updated with matching content for backwards compatibility — any caller still using the older extensioned paths still gets a correct answer. **WebMCP** is the fourth piece. Chrome's experimental navigator.modelContext.provideContext() API lets a page register tools that AI agents in the browser can execute directly — without going through the out-of-process MCP transport. PointCast now ships `src/components/WebMCPTools.astro` that registers seven tools on every page: `pointcast_latest_blocks`, `pointcast_get_block`, `pointcast_send_ping`, `pointcast_push_drop`, `pointcast_drum_beat`, `pointcast_federation`, `pointcast_compute_ledger`. Each tool has a JSON Schema input definition and an async execute callback that hits the same /api/* endpoint an external MCP client would hit. Symmetric surfaces. The WebMCP script guards against unsupported browsers: if `navigator.modelContext` is missing, it silently no-ops. The tools list is also exposed at `window.__pointcast_webmcp_tools` for devtools debugging. No secrets embedded — everything runs as the current visitor with their cookies. The last thread: Google OAuth itself. The route code has existed since yesterday but /api/auth/google/start returns 404 because the environment variables aren't set in the Cloudflare Pages dashboard yet. Today's ship includes `docs/plans/2026-04-21-google-oauth-setup.md` — a step-by-step checklist for Mike: create the OAuth client in Google Cloud Console (~5 min), paste GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET + GOOGLE_REDIRECT_URI into Cloudflare Pages env vars (~2 min), done. Once those three vars are set, Cloudflare redeploys and the route starts working. The sign-in chip in the HUD drawer is already wired to the endpoint; it'll start functioning the moment the env vars land. Two things deliberately not addressed in this sprint: the `/api/presence/snapshot` 404 (deeper investigation needed into the Durable Object cross-script binding) and the Bell Tolls advanced + exceptional difficulties (still awaiting Mike's canonical YouTube ID). Both stay flagged for a later pass. The bar should feel different on reload. Every returning visitor gets a clean compact state, the animations that introduced latency are gone, and the states that looked broken are removed. The agent-ready checklist, when re-run against pointcast.xyz, should drop four items from its failing list. And the OAuth plumbing is waiting on Mike pasting three env vars. That's Sprint #89. Block count: 146. Sprint recap at /sprints.