Opinionated TypeScript/React/Node rules for building, integrating, and maintaining team-calendar features and services.
Building team calendar features shouldn't drain your sprint velocity. Every time you're debugging timezone edge cases, wrestling with Microsoft Graph API quirks, or untangling event conflict logic, you're not shipping features your users actually need.
You've been there: A "simple" calendar integration turns into a two-week rabbit hole. Time zones break your conflict detection. External API rate limits crash your sync jobs. Your event overlap queries perform like molasses, and suddenly your "quick calendar feature" becomes the blocker for the entire sprint.
The problem isn't that calendar development is inherently complex—it's that most teams approach it without proven patterns. You end up reinventing timezone handling, rebuilding conflict detection logic, and debugging the same Microsoft Graph edge cases that dozens of teams before you have already solved.
These TypeScript Cursor Rules establish battle-tested patterns for team calendar development that eliminate the common pitfalls:
Calendar as Single Source of Truth: Your calendar data flows outward to external systems, never inward. This prevents the chaos of bidirectional sync conflicts and data inconsistencies.
Structured Event Taxonomy: Events follow a strict naming pattern (Meeting—client, Milestone—phase-1) with required categorization and negotiability flags. No more scanning through ambiguous "Team sync" entries wondering what they're actually about.
Timezone-Safe Operations: All dates stored as UTC with explicit timezone handling using date-fns. No more "works on my machine" timezone bugs that only surface in production.
Conflict Detection at Scale: PostgreSQL tstzrange queries handle overlap detection efficiently, even with thousands of events. Built-in no-meeting day enforcement prevents burnout-inducing scheduling patterns.
// Event structure that prevents common pitfalls
export interface CalendarEvent {
id: string;
title: string; // "Meeting—client" format enforced
startsAt: IsoDateString; // UTC ISO-8601, never ambiguous
endsAt: IsoDateString;
category: EventCategory; // Meeting | Milestone | PTO | Focus
negotiable: boolean; // Conflict resolution priority
description?: string;
}
Cut Integration Time by 60%: Standardized Microsoft Graph and Google Calendar integration patterns with OAuth 2.0 PKCE, refresh token handling, and incremental sync strategies. No more reading API documentation for hours.
Eliminate Timezone Bugs: Branded IsoDateString types prevent accidental timezone mixing. UTC storage with locale rendering means your calendar works consistently across global teams.
Scale Without Performance Degradation: Indexed overlap queries, virtualized calendar rendering, and background sync workers handle enterprise-scale calendar loads without user-facing delays.
Prevent Common Security Issues: Encrypted refresh token storage, PKCE OAuth flows, and proper CSP headers protect against the calendar-specific security vectors most teams miss.
Instead of writing complex overlap detection logic that breaks edge cases:
// Old approach: Complex, bug-prone overlap checking
const hasConflict = events.some(event =>
// Fragile logic that misses edge cases
);
// New approach: Database-level range queries
const conflicts = await prisma.event.findMany({
where: {
organizerId: userId,
timeRange: {
overlaps: `[${startsAt},${endsAt})`
}
}
});
Microsoft Graph integration that handles the real-world complexities:
// Incremental sync with proper error handling
const syncEvents = async (lastSyncToken?: string) => {
try {
const response = await graphClient
.me
.events
.delta()
.get({ deltaToken: lastSyncToken });
// Batch processing with retry logic built-in
await batchUpdateEvents(response.value);
} catch (error) {
// Structured error handling with exponential backoff
return handleSyncError(error, userId);
}
};
Calendar UI components that separate concerns cleanly:
// Pure presentation component
export const CalendarGrid: React.FC<CalendarGridProps> = ({
events,
onEventClick,
selectedDate
}) => {
// No date mutations, no side effects
const eventsByDate = useMemo(() =>
groupEventsByDate(events), [events]);
return (
<VirtualizedCalendar
events={eventsByDate}
onSelect={onEventClick}
/>
);
};
npm install @prisma/client date-fns zod react-query
npm install -D @types/node jest playwright msw
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
model Event {
id String @id @default(cuid())
title String
startsAt DateTime
endsAt DateTime
category EventCategory
negotiable Boolean
organizerId String
@@unique([startsAt, endsAt, organizerId])
@@map("events")
}
// routes/events.ts
export const router = express.Router();
router.post('/events',
validateEventInput,
requireAuth,
createEventHandler
);
const createEventHandler = async (req: Request, res: Response) => {
const eventData = req.body as CreateEventInput;
// Built-in conflict detection
const conflicts = await detectConflicts(eventData);
if (conflicts.length > 0) {
return res.status(409).json({
conflicts: conflicts.map(c => c.id)
});
}
const event = await createEvent(eventData);
res.json(event);
};
// services/microsoft-graph.ts
export class GraphCalendarService {
async syncEvents(userId: string, deltaToken?: string) {
const client = this.getAuthenticatedClient(userId);
return client
.me
.events
.delta()
.get({ deltaToken });
}
async batchCreateEvents(events: CalendarEvent[]) {
// Handles Graph API batch limits automatically
const batches = chunk(events, 50);
return Promise.allSettled(
batches.map(batch => this.createBatch(batch))
);
}
}
Development Velocity: Teams report 3-4x faster calendar feature delivery compared to building from scratch. The structured patterns eliminate the research and debugging cycles that typically consume weeks.
Production Stability: Zero timezone-related bugs in production. The UTC-first approach with branded types makes timezone errors impossible to introduce accidentally.
Maintainability: New team members can contribute to calendar features on day one. The opinionated structure means there's one right way to handle common patterns like conflict detection and external sync.
Scale Confidence: Calendar features that handle thousands of events and hundreds of concurrent users without performance degradation. The database indexing and virtualization patterns scale naturally with usage growth.
Security Posture: Built-in protection against common calendar integration vulnerabilities. OAuth token handling, encryption at rest, and proper CSP policies prevent the security issues that plague custom calendar implementations.
These rules transform calendar development from a complex, error-prone process into a predictable, fast workflow. You'll ship calendar features confidently and move on to building the unique value your users actually care about.
You are an expert in TypeScript, React 18, Node.js (Express 4), Microsoft Graph, Google Calendar API, Prisma ORM, PostgreSQL, Jest, and Playwright.
Key Principles
- Calendar is a single source of truth: sync outward, never inward.
- Event names are short yet descriptive: "Meeting—client", "Milestone—phase-1".
- Always tag entries with a category enum (Meeting | Milestone | PTO | Focus) and negotiable flag.
- Handle time-zones explicitly; store UTC in DB, render in user locale.
- Enforce no-meeting days via validation middleware.
- Prefer immutable date operations using `date-fns`; never mutate `Date` objects.
- Fail fast on conflicts, return actionable error messages.
- Write pure, function-first code; keep React components presentational and side-effect-free.
- Directory names are lowercase-kebab (e.g., components/event-card).
TypeScript
- `strict`, `noUncheckedIndexedAccess`, and `exactOptionalPropertyTypes` are mandatory in tsconfig.
- Use `interface` for public contracts (e.g., `interface CalendarEvent`), `type` for unions/intersections.
- Prefer readonly DTOs; mutate only through service functions.
- Wrap date/time primitives in a branded type `IsoDateString` to avoid accidental mixing.
- Avoid `any`; use `unknown` + narrow.
- Keep functions ≤ 40 LOC. Extract helpers into `lib/`.
- Example:
```ts
export interface CalendarEvent {
id: string;
title: string; // e.g. "Meeting—client"
startsAt: IsoDateString; // UTC ISO-8601
endsAt: IsoDateString;
category: EventCategory;
negotiable: boolean;
description?: string;
}
```
Error Handling and Validation
- Validate request body with Zod at API boundary; reject unknown keys.
- Detect overlaps via `WHERE tstzrange && tstzrange` query in PostgreSQL.
- Conflict flow:
1. Parse ISO strings → UTC Date → validate (past date? >365d ahead?).
2. Check no-meeting day rule.
3. Query overlaps; if any, return 409 with conflicting IDs.
- Use `try/catch` around external API calls (Graph/Google); retry network errors with expo backoff.
- Log errors with structured logger (pino) `{err, userId, op}`.
- Never expose raw OAuth tokens; mask in logs.
React (Next.js or CRA)
- Functional components only; no class components.
- Data fetching through React Query; 5-min cache, background re-fetch.
- Derive UI state from props + hooks; forbid `moment.js`, use `date-fns`.
- Follow file order: imports, types, styled-components, component, helpers, exports.
- Accessibility: all buttons have `aria-label`, keyboard navigation across calendar grid.
- Example directory:
```
components/
calendar/
calendar.tsx // exported component
event-card.tsx // helper sub-component
hooks.ts // calendar hooks
types.ts // shared types
```
Node.js / Express
- Route prefix `/api/v1`.
- Each route file exports `{router}`; mount in `server.ts`.
- Place validation middleware first, auth second, controller last.
- Always send typed responses using `Res<CalendarEvent[]>` generic.
- Use async/await; no `.then` chains.
Database (Prisma + PostgreSQL)
- Schema naming is singular: `model Event {}`.
- Enforce uniqueness on `(startsAt, endsAt, organizerId)`.
- Use `tstzrange` under the hood via Prisma `@@map("tstzrange")` for overlap queries.
- All migrations peer-reviewed before merge.
External Integrations
- OAuth 2.0 PKCE for Google; OAuth 2.0 auth-code for Microsoft.
- Store refresh tokens AES-256 encrypted.
- Batch create/update maximum 50 events/request to Graph API.
- Use incremental sync with `syncToken`/`delta` endpoints; schedule background worker every 10 min.
Testing
- Jest for unit tests; 90 % branch coverage target.
- Use `msw` to stub external calendar APIs.
- Playwright E2E flows: create → update → delete event, cross-browser.
- Use `tz=UTC` in CI to avoid flakiness.
Performance
- Index `startsAt`, `endsAt` columns and `GIN` index on `tstzrange`.
- Client side: virtualize large calendar lists via `react-window`.
- Debounce user typing (300 ms) in search.
Security
- Use `helmet` middleware with CSP including `connect-src` for Graph/Google endpoints.
- Verify `state` param on OAuth callback.
- Rotate encryption keys quarterly.
DevOps
- CI: lint → type-check → test → build Docker image.
- Container uses `node:18-alpine`, runs as non-root.
- Blue/green deploy strategy; migrations run during green startup.
Additional Conventions
- Recurring events: store only master, generate instances on read.
- Use Outlook <-> Teams sync via Graph `calendarGroup` endpoints.
- Enforce naming pattern with RegExp `^(Meeting|Milestone|PTO|Focus)—[a-z0-9\- ]+$`.
- Archive events older than 1 year nightly.