# v2.0 Research: Stack Additions

**Date:** 2026-05-15
**Scope:** Native per-tablo chat, etapes, Google/Apple sign-in, individual planning, tablo events.

## Existing Stack

- Go 1.26 module in `backend/`
- chi router, gorilla/csrf, server-managed sessions
- templ + HTMX + Tailwind
- Postgres via pgx + sqlc + goose migrations
- River worker already installed for background jobs
- Single container / VPS-oriented deploy

## Recommended Additions

### Social sign-in

- Add `golang.org/x/oauth2` for OAuth2 authorization-code exchange and state handling helpers.
- Add a small internal OIDC/JWT verifier package rather than a managed auth platform.
- Store external identities in Postgres with provider, provider subject, verified email, and linked user ID.
- Keep existing session issuance path: after provider verification, create or find a local `users` row, then create a local `sessions` row and cookie.

### Google sign-in

- Use Google's OpenID Connect server flow.
- Required provider setup: Google Cloud OAuth client ID/secret and registered redirect URI.
- Required runtime config: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URL`.
- Verify ID token claims before linking:
  - issuer is Google
  - audience equals configured client ID
  - subject is stable provider identity
  - email is present and verified

Sources:
- Google OIDC docs: https://developers.google.com/identity/openid-connect/openid-connect
- Google OAuth web-server docs: https://developers.google.com/identity/protocols/oauth2/web-server

### Apple sign-in

- Use Apple's web OAuth/REST flow.
- Required provider setup: Apple Services ID, Team ID, Key ID, private key, redirect URI.
- Required runtime config: `APPLE_CLIENT_ID`, `APPLE_TEAM_ID`, `APPLE_KEY_ID`, `APPLE_PRIVATE_KEY`, `APPLE_REDIRECT_URL`.
- Generate Apple's `client_secret` as an ES256 JWT server-side; cache it until near expiration.
- Exchange authorization code at `POST https://appleid.apple.com/auth/token`.
- Verify Apple's ID token with Apple's public keys and persist provider subject. Treat Apple email/name as first-login-only data.

Sources:
- Apple token validation: https://developer.apple.com/documentation/SigninwithAppleRESTAPI/Generate-and-validate-tokens
- Apple authorization request: https://developer.apple.com/documentation/signinwithapplerestapi/request-an-authorization-to-the-sign-in-with-apple-server.
- Apple client secret creation: https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret

### Real-time chat

Two viable options:

- SSE receive + normal POST send:
  - Best fit for HTMX/server-rendered UI.
  - Browser opens a per-tablo event stream; messages are posted with normal forms/HTMX.
  - Easier auth/CSRF story because writes remain HTTP POSTs.
  - One-way stream is enough for "new messages appear in real time".

- WebSocket:
  - Best if the same connection must both send and receive messages.
  - Requires explicit message protocol, origin checks, backpressure decisions, and CSRF-equivalent protection on upgrade.

Recommendation for v2: implement SSE for receive and HTMX POST for send. It satisfies "real time" without bringing in a managed chat vendor or forcing a larger client-side app model. Revisit WebSockets only if typing indicators, presence, or very high-frequency bidirectional events become requirements.

Sources:
- MDN SSE guide: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
- MDN WebSocket API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

## Avoid

- No Stream Chat, Ably, Pusher, Firebase, Supabase Realtime, or managed chat/realtime services.
- No Clerk/Auth0/Lucia. Google and Apple should only verify identity; Xtablo still owns account rows and sessions.
- No recursive task hierarchy. Etapes are a one-level grouping table, not nested tasks.
