> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Calendar

> Shared Calendar product surface across apps/web and apps/calendar.

## Overview

`apps/calendar` and the Calendar experience inside `apps/web` are paired
Calendar hosts. They must stay 1:1 for product features, data behavior,
mutations, permissions, and user-facing workflow updates.

* Calendar UI and logic should live in shared packages, primarily
  `@tuturuuu/ui/calendar-app/*`, with app route files acting as thin auth and
  workspace-context wrappers.
* `apps/calendar` owns its standalone workspace shell, local `/login`, and
  local `/verify-token` completion route.
* `apps/web /{wsId}/calendar` renders the same shared Calendar product surface
  inside the normal platform dashboard shell.
* Dashboard navigation in `apps/web` should link to the local
  `/{wsId}/calendar` route, not to the standalone Calendar origin.

## Auth Model

Calendar does not create local Supabase Auth sessions. It uses central
`apps/web` login plus a Calendar app-session cookie:

1. `/login` in `apps/calendar` normalizes a safe `next` path.
2. If both the Calendar app-session cookie and Web-issued app-session cookie are
   already present, `/login` redirects to that local `next` path immediately.
3. Otherwise `/login` redirects to `apps/web /login` with a return URL pointing
   back to Calendar `/verify-token?nextUrl=...`.
4. `/verify-token` posts the handoff token to the Calendar-local verifier, which
   validates through central Web and sets host-local app-session cookies.

Calendar proxy responses should clear stale `sb-*-auth-token` cookies. A valid
Calendar session is represented by Tuturuuu app-session cookies, not by a local
Supabase browser session.

## API Ownership

Protected Calendar data APIs remain in `apps/web`. `apps/calendar` rewrites
fallback `/api/*` requests to the central Web app, and routes consumed by
Calendar must opt into app-session auth explicitly when used by the standalone
host.

* Calendar-only routes use `targetApp: 'calendar'`.
* Task and board helper routes that Calendar legitimately calls for quick task
  creation, task sidebar scheduling, and time tracker task creation use
  `targetApp: ['calendar', 'tasks']`.
* Do not accept generic satellite app-session tokens for Calendar APIs.
* Keep workspace membership checks and Calendar/task permissions on the central
  route before admin-backed reads or writes.

## OAuth Token Safety

Calendar OAuth credentials live in `calendar_auth_tokens`. RLS restricts
selects to `user_id = auth.uid()`, but admin/service-role reads bypass RLS.

Calendar server pages must therefore:

* Check `manage_calendar` before loading calendar integration state.
* Scope token reads to both `ws_id` and the authenticated `user_id`.
* Never pass `access_token` or `refresh_token` into client components. SSR
  props should use the shared
  `fetchUserWorkspaceCalendarGoogleTokenForClient()` helper from
  `@tuturuuu/utils/calendar-auth-token`, which projects only connection
  metadata (`id`, account email/name, provider, active state, expiry).
* Keep token refresh and Google API calls on central `apps/web` API routes.

When adding a new Calendar host route, reuse the helper instead of
`select('*')` on `calendar_auth_tokens`.

When adding new Calendar UI that needs protected data, update the shared
Calendar component or helper first, then wire both host wrappers in the same
change. Prefer a central `apps/web` API route and a `packages/internal-api`
helper instead of host-local product API forks or direct client Supabase reads.

## Calendar OAuth And Connections

Google Calendar OAuth is Web-owned even when the user starts from the
standalone Calendar app. The `/api/v1/calendar/auth` route in `apps/web` must
build its Google callback URL from a browser-safe Web origin and ignore wildcard
listener addresses such as `0.0.0.0` or `::`. If no safe configured Web origin
is available, the fallback origin is `https://tuturuuu.com`.

After Google returns to the callback, the flow always sends the user to the
central Web Calendar page at
`https://tuturuuu.com/{wsId}/calendar?provider=google&connected=true`. Valid
local development callback URLs such as
`http://localhost:7803/api/v1/calendar/auth/callback` may still be configured
through `GOOGLE_REDIRECT_URI`, but wildcard listener URLs must never be emitted
as Google `redirect_uri` values or browser redirects.

Normal Google Calendar connect and reconnect URLs request
`access_type=offline` and `include_granted_scopes=true`, but they must not force
`prompt=consent` by default. Google may return a refresh token only during the
first authorization, so the callback must preserve an existing stored
`refresh_token` when a reconnect returns only a new access token. If neither the
callback nor the existing token row has a refresh token, the flow must fail
before saving an active Google connection because provider sync cannot refresh
offline.

Standalone `apps/calendar` also exposes the shared Calendar connections manager
inside its settings dialog under Calendar -> Integrations. It should fetch the
initial connection state through `@tuturuuu/internal-api/calendar` and render the
shared Calendar connections UI inside `CalendarSyncProvider` rather than
forking host-local connection controls.

## CI And Deployment

Calendar deploys through dedicated GitHub Actions workflows, not Vercel-owned
GitHub auto-builds:

* Preview deployments use `.github/workflows/vercel-preview-calendar.yaml`.
* Production deployments use `.github/workflows/vercel-production-calendar.yaml`.
* The Vercel project id is provided through the environment-scoped
  `VERCEL_CALENDAR_PROJECT_ID` secret.
* `apps/calendar/vercel.json` must keep `git.deploymentEnabled` and
  `github.enabled` set to `false` so GitHub Actions builds with `vercel build`
  and deploys prebuilt artifacts with `vercel deploy --prebuilt`.

Keep Calendar cron definitions in `apps/calendar/vercel.json`; disabling Vercel
GitHub builds must not remove the app-owned cron schedules.

## Provider Sync

Calendar provider sync is scheduled by `apps/web` cron, not Trigger.dev. The
shared source of truth is `apps/web/cron.config.json`; `calendar-provider-sync`
must stay at `*/15 * * * *` and `apps/web/vercel.json` must be regenerated from
that file with `node scripts/sync-web-crons.js`.

The cron wrapper at `/api/cron/calendar/provider-sync` must keep calling
`/api/v1/workspaces/:wsId/calendar/sync` through `INTERNAL_WEB_API_ORIGIN` when
available. The workspace sync route owns Google and Microsoft fan-out, running
locks, cron cooldown behavior, and `calendar_sync_dashboard` audit rows. Do not
add provider-specific fetch logic or Trigger.dev scheduled calendar jobs outside
that route family.

## Two-Way Sync Controls

Calendar sync has two layers of user control:

* Workspace-user preferences in
  `private.calendar_user_workspace_preferences` enable or disable inbound
  imports, outbound Tuturuuu-to-provider mirroring, the default outbound
  provider calendar, and the conflict policy.
* `calendar_connections` controls each external calendar's import, outbound
  write, and provider-delete behavior.

Outbound mirroring is opt-in. When enabled, native Tuturuuu event creates and
edits mirror to the selected writable Google or Microsoft connection, and the
local event stores provider identity so future updates and deletes propagate
externally. Manual or cron sync with `direction: "outbound"` or `"both"` may
also catch up local-only or previously failed Tuturuuu events in the active sync
window.

Inbound provider deletes are controlled per connection through
`sync_delete_enabled`. User-initiated deletion of a synced external event still
deletes the provider event directly because it is explicit CRUD, not passive
provider import cleanup.

After adding or changing two-way sync columns, prepare migrations in
`apps/database`; do not run production Supabase push commands from agent
sessions. Apply locally with `bun sb:up` when feasible, then regenerate database
types only after the local schema reflects the migration.
