From 367364e9f88d2a263e07962d2e5762334f43537a Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 15 May 2026 20:11:23 +0200 Subject: [PATCH] docs: research milestone v2.0 collaboration planning social sign-in --- .planning/research/ARCHITECTURE.md | 69 ++++++++++++++++++++++++++ .planning/research/FEATURES.md | 58 ++++++++++++++++++++++ .planning/research/PITFALLS.md | 39 +++++++++++++++ .planning/research/STACK.md | 77 ++++++++++++++++++++++++++++++ .planning/research/SUMMARY.md | 36 ++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 .planning/research/ARCHITECTURE.md create mode 100644 .planning/research/FEATURES.md create mode 100644 .planning/research/PITFALLS.md create mode 100644 .planning/research/STACK.md create mode 100644 .planning/research/SUMMARY.md diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md new file mode 100644 index 0000000..1d946a4 --- /dev/null +++ b/.planning/research/ARCHITECTURE.md @@ -0,0 +1,69 @@ +# v2.0 Research: Architecture Notes + +**Date:** 2026-05-15 + +## Integration Points + +### Auth + +Add a provider layer beside `backend/internal/auth`: + +- `internal/auth/oauth.go` or `internal/identity/` +- `user_identities` table: + - `id` + - `user_id` + - `provider` (`google`, `apple`) + - `provider_subject` + - `email` + - `email_verified` + - `created_at` + - `updated_at` + - unique `(provider, provider_subject)` + +Keep `users` and `sessions` authoritative. Social sign-in should finish by using the same session store used by email/password login. + +### Chat + +Add: + +- `tablo_messages` table: + - `id` + - `tablo_id` + - `author_user_id` + - `body` + - `created_at` + - `edited_at` + - `deleted_at` +- `GET /tablos/{id}/messages` renders current message tab/history. +- `POST /tablos/{id}/messages` creates a message and returns a message fragment. +- `GET /tablos/{id}/messages/stream` holds an SSE connection and streams new message fragments or JSON payloads. + +For a single-host v2, an in-process hub keyed by `tablo_id` is acceptable. The database remains the source of truth; the hub is only a delivery accelerator. If multi-instance deployment arrives later, replace hub fanout with Postgres LISTEN/NOTIFY or Redis. + +### Etapes + +Add: + +- `etapes` table with `id`, `tablo_id`, `title`, `description`, `position`, timestamps. +- `tasks.etape_id nullable references etapes(id) on delete set null`. +- Keep existing `tasks.status` and `tasks.position`; etape grouping is orthogonal to kanban status. + +This matches the constraint: only tasks may reference an etape, and etapes do not reference each other. + +### Events / Planning + +Add: + +- `tablo_events` table with `id`, `tablo_id`, `created_by_user_id`, `title`, `description`, `location`, `starts_at`, `ends_at`, timestamps. +- `GET /planning` renders current user's event list/calendar-like agenda. +- `GET /tablos/{id}/events` renders a tablo events tab. +- Event ownership initially follows tablo ownership. If shared tablos arrive later, planning aggregation should include events from tablos the user can access. + +## Phase Order + +1. Social sign-in first, because it touches the existing auth/session foundation and should be isolated from collaboration schema changes. +2. Etapes next, because they extend existing tasks and can be tested without real-time concerns. +3. Events/planning next, because they add a new core domain with straightforward CRUD. +4. Chat last or near-last, because real-time delivery needs extra browser/manual verification and careful deployment behavior. + +For roadmap ergonomics, chat can also be split into persistence first and real-time second. diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md new file mode 100644 index 0000000..fd48b11 --- /dev/null +++ b/.planning/research/FEATURES.md @@ -0,0 +1,58 @@ +# v2.0 Research: Feature Expectations + +**Date:** 2026-05-15 + +## Authentication + +Table stakes: +- User can choose Google or Apple from the login/signup page. +- OAuth callbacks validate state/nonce and provider identity before issuing a local Xtablo session. +- Existing email/password accounts still work. +- If a provider identity has a verified email matching an existing account, the flow links to that account after explicit or safe same-session handling. +- User can later see linked providers in account settings. + +Differentiators for later: +- Provider unlinking. +- Multiple emails per user. +- Account merge conflict UI. + +## Chat / Messaging + +Table stakes: +- Each tablo has one discussion stream. +- Authenticated tablo owner can post a text message. +- Messages persist in Postgres with author, body, created timestamp, edited/deleted markers. +- Other open browsers on that tablo receive new messages without manual refresh. +- Reloading the page shows message history. + +Differentiators for later: +- Mentions, reactions, typing indicators, file previews, threads, read receipts, moderation. + +## Etapes + +Table stakes: +- User can create an etape inside a tablo. +- User can edit/delete/reorder etapes. +- A task can belong to zero or one etape. +- An etape cannot belong to another etape. +- The tasks UI can filter or group by etape while preserving the existing kanban status model. + +Differentiators for later: +- Etape progress rollups, due dates, dependencies, templates. + +## Events and Individual Planning + +Table stakes: +- User can create a scheduled event attached to a tablo. +- Event has title, start time, optional end time, optional description/location. +- User can edit/delete events. +- Tablo detail page shows its events. +- Individual planning page shows the authenticated user's scheduled events across tablos. +- Events are authorized through tablo ownership/membership rules. + +Differentiators for later: +- Recurrence, reminders, ICS import/export, external calendar sync, multi-person assignments. + +## Scope Boundary + +This milestone should deliver working behavior with plain UI. Visual polish is intentionally deferred because the user will provide a beautiful UI later. diff --git a/.planning/research/PITFALLS.md b/.planning/research/PITFALLS.md new file mode 100644 index 0000000..5a4fd28 --- /dev/null +++ b/.planning/research/PITFALLS.md @@ -0,0 +1,39 @@ +# v2.0 Research: Pitfalls + +**Date:** 2026-05-15 + +## Authentication + +- Do not trust OAuth callback parameters without validating state and nonce. +- Do not use provider access tokens as Xtablo sessions. They are not the local session authority. +- Do not link accounts by unverified email. +- Apple may only provide some profile fields on first authorization; persist what is needed immediately. +- Apple client secret generation is easy to get wrong: use ES256, correct `kid`, `iss`, `aud`, `sub`, and expiration. +- Provider subject, not email, is the stable external identity key. + +## Chat + +- Do not treat the in-process real-time hub as durable storage. Every message must commit to Postgres first. +- SSE connections need keep-alive comments/events to avoid idle timeouts. +- Browser/domain connection limits matter for SSE over HTTP/1.1; keep one stream per tablo view and document the limitation. +- Message POSTs must keep CSRF protection. SSE stream endpoints should be GET-only and authenticated. +- Avoid storing unbounded message bodies; enforce length limits. +- Avoid HTML injection by rendering message bodies through templ escaping, not safe HTML. + +## Etapes + +- Do not model etapes as recursive tasks unless the product explicitly needs nested hierarchy later. +- Adding `tasks.etape_id` should not break existing kanban ordering. +- Deleting an etape should not delete its tasks unless the user explicitly confirms that behavior; default to unassigning tasks. + +## Events / Planning + +- Store timestamps as `timestamptz`. +- Require `ends_at` to be null or after `starts_at`. +- Decide the first timezone behavior explicitly. For v2, storing absolute times and displaying in the user's browser/local preference is enough. +- Planning should not leak events from tablos the user cannot access. + +## Deployment + +- WebSockets may require reverse-proxy upgrade config. SSE usually works through normal HTTP but still needs buffering disabled if the proxy buffers streams. +- If using Caddy, verify streaming behavior during human UAT. diff --git a/.planning/research/STACK.md b/.planning/research/STACK.md new file mode 100644 index 0000000..54602c1 --- /dev/null +++ b/.planning/research/STACK.md @@ -0,0 +1,77 @@ +# 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. diff --git a/.planning/research/SUMMARY.md b/.planning/research/SUMMARY.md new file mode 100644 index 0000000..bbf70a9 --- /dev/null +++ b/.planning/research/SUMMARY.md @@ -0,0 +1,36 @@ +# v2.0 Research Summary + +**Date:** 2026-05-15 + +## Stack Additions + +- Add `golang.org/x/oauth2` and a small internal provider verification layer for Google/Apple. +- Keep server-managed sessions as the only Xtablo session mechanism. +- Use Postgres tables for social identities, messages, etapes, and events. +- Implement real-time chat as SSE receive + HTMX POST send for v2. This satisfies real-time updates with less client complexity than WebSockets. + +## Feature Table Stakes + +- Google and Apple sign-in both validate provider identity, then create/link a local user and issue the existing session cookie. +- Each tablo has a persistent message stream with authenticated posting and real-time updates. +- Etapes group tasks one level deep: task -> optional etape; etapes have no parent. +- Events belong to tablos and appear both in tablo context and in each user's planning page. + +## Watch Outs + +- OAuth state/nonce and ID token validation are non-negotiable. +- Provider subject is the durable identity key; email alone is insufficient. +- Apple profile data can be first-login-only. +- Real-time delivery must be separate from message durability. +- SSE/proxy buffering and keep-alives need manual verification. +- Event authorization must follow tablo access. + +## Sources + +- Google OpenID Connect: https://developers.google.com/identity/openid-connect/openid-connect +- Google OAuth 2.0 web server flow: https://developers.google.com/identity/protocols/oauth2/web-server +- 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 +- MDN Server-sent events: 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