diff --git a/.gitignore b/.gitignore index 6b369e476..866dd82a6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ packages/**/yarn.lock build/ buildcache/ -docs/ logs/ node_modules/ playwright-report/ @@ -49,4 +48,5 @@ packages/backend/.env packages/backend/.prod.env +tmp/ !.env.local.example diff --git a/AGENTS.md b/AGENTS.md index 6ef8405cc..a43ac7403 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ Primary instructions for AI agents and developers in the Compass monorepo. - [Compass Calendar Development Instructions](#compass-calendar-development-instructions) - [Quick Start](#quick-start) - [Table of Contents](#table-of-contents) - - [Key Guidelines](#key-guidelines) + - [Principles](#principles) - [Module Aliases](#module-aliases) - [Working Effectively](#working-effectively) - [Initial Setup](#initial-setup) @@ -26,11 +26,8 @@ Primary instructions for AI agents and developers in the Compass monorepo. - [Validation](#validation) - [Project Structure](#project-structure) - [Packages Overview](#packages-overview) + - [Documentation](#documentation) - [Key Files \& Directories](#key-files--directories) - - [Environment Requirements](#environment-requirements) - - [Required for Full Development](#required-for-full-development) - - [Optional Services](#optional-services) - - [Critical Environment Variables (backend/.env)](#critical-environment-variables-backendenv) - [Common Tasks \& Timing](#common-tasks--timing) - [Repository Operations](#repository-operations) - [Development Workflow](#development-workflow) @@ -47,14 +44,11 @@ Primary instructions for AI agents and developers in the Compass monorepo. - [Semantic Branch Naming](#semantic-branch-naming) - [Semantic Commit Messages](#semantic-commit-messages) - [Pre-commit Validation](#pre-commit-validation) - - [Gotchas](#gotchas) -## Key Guidelines +## Principles -1. When working in `packages/web`, follow React best practices and idiomatic patterns -2. When working in `packages/backend` or `packages/core`, follow Node.js best practices and idiomatic patterns -3. Always use module aliased paths for imports when importing Compass modules. -4. Prefer the simplest solution to a problem over the more complex solutions. +1. **Simplicity**. Prefer the simplest solution to a problem over the more complex solutions. +2. **Avoid Dependencies**. Prefer built-ins and stable, minimal dependencies. This project will be around for decades, so we want to avoid dependencies that will become obsolete. ## Module Aliases @@ -104,17 +98,16 @@ Run `yarn test:core`, `yarn test:web`, and `yarn test:backend` after making chan - **Web tests**: `yarn test:web` - **Backend tests**: `yarn test:backend` - **Scripts tests**: `yarn test:scripts` -- **Full test suite**: `yarn test` (may fail if MongoDB binary download is blocked) +- **Full test suite**: `yarn test` ### Building - **Web Build**: `yarn cli build web --environment staging --clientId "test-client-id"` - **Node Build**: `yarn cli build nodePckgs --environment staging` -- Both builds require valid environment configuration ### Linting -- `yarn prettier . --write` - Takes ~15 seconds. +- `yarn prettier . --write` ## Validation @@ -133,6 +126,10 @@ This is a Typescript project with a monorepo structure. - `@compass/core` - Shared utilities, types, and business logic - `@compass/scripts` - CLI tools for building, database operations, user management +### Documentation + +Additional documentation lives in the `docs/` directory (e.g. API docs, workflow examples). Update these documents as you make changes when relevant. + ### Key Files & Directories ```text @@ -157,38 +154,6 @@ packages/core/src/ └── mappers/ # Data transformation logic ``` -## Environment Requirements - -### Required for Full Development - -- Node.js (version specified in package.json engines) -- Yarn package manager (lockfile format) -- Google Cloud Project with OAuth 2.0 credentials -- Supertokens account for user session management -- MongoDB database (cloud or local) - -### Optional Services - -- Kit.com account for email integration -- NGrok account for local tunneling - -### Critical Environment Variables (backend/.env) - -```bash -# Required for backend to start -BASEURL=http://localhost:3000/api -GOOGLE_CLIENT_ID=YOUR_GOOGLE_OAUTH_CLIENT_ID -GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_OAUTH_SECRET -SUPERTOKENS_URI=YOUR_SUPERTOKENS_INSTANCE_URL -SUPERTOKENS_KEY=YOUR_SUPERTOKENS_API_KEY -MONGO_URI=YOUR_MONGODB_CONNECTION_STRING - -# Required for web development -PORT=3000 -NODE_ENV=development -TZ=Etc/UTC -``` - ## Common Tasks & Timing ### Repository Operations @@ -224,6 +189,9 @@ TZ=Etc/UTC - **Test failures**: Run `yarn test:core`, `yarn test:web`, and `yarn test:backend` individually to narrow the scope of the failure - **Backend won't start**: Missing environment variables in `packages/backend/.env`, use web-only development (`yarn dev:web`) +- Environment: Copy from `packages/backend/.env.local.example` to `packages/backend/.env` (there is no `.env.example`). +- Webpack dev server warns about a missing `.env.local` file; this is harmless—it falls back to `process.env`. +- Husky pre-push hook runs `yarn prettier . --write`, which can modify files. Ensure working tree is clean or committed before pushing. ### Network Limitations @@ -249,8 +217,6 @@ TZ=Etc/UTC - `yarn test:backend` - Run backend package tests only - `yarn test:scripts` - Run scripts package tests only -Always prioritize frontend development with `yarn dev:web` when backend services are unavailable. - ## Naming Conventions - Use `is` prefix for boolean variables. For example, `isLoading`, `isError`, `isSuccess` @@ -333,9 +299,3 @@ The repository includes Husky hooks that will: - Run `yarn prettier . --write` on pre-push (ensures consistent formatting) **ALWAYS** ensure your commits pass these checks before pushing. - -## Gotchas - -- Environment: Copy from `packages/backend/.env.local.example` to `packages/backend/.env` (there is no `.env.example`). -- Webpack dev server warns about a missing `.env.local` file; this is harmless—it falls back to `process.env`. -- Husky pre-push hook runs `yarn prettier . --write`, which can modify files. Ensure working tree is clean or committed before pushing. diff --git a/AI-AGENT-GUIDE.md b/AI-AGENT-GUIDE.md new file mode 100644 index 000000000..3bd91d7e0 --- /dev/null +++ b/AI-AGENT-GUIDE.md @@ -0,0 +1,434 @@ +# AI Agent Guide for Compass Calendar + +**A comprehensive guide for AI coding agents working on the Compass Calendar project** + +## 🚀 Quick Start (60 seconds) + +```bash +# 1. Install dependencies (~3.5 minutes) +yarn install --frozen-lockfile --network-timeout 300000 + +# 2. Setup environment +cp packages/backend/.env.local.example packages/backend/.env + +# 3. Start development (frontend only) +yarn dev:web +# Open http://localhost:9080 + +# 4. Run tests +yarn test:core && yarn test:web + +# 5. Generate documentation +yarn docs:generate +``` + +## 📚 Essential Documentation + +### Primary Guides + +- **[README.md](./README.md)** - Project overview and "For AI Agents" section +- **[CONTRIBUTING.md](./CONTRIBUTING.md)** - Contribution guidelines with AI agent-specific sections +- **[AGENTS.md](./AGENTS.md)** - Detailed development instructions and conventions +- **[ai-tools/README.md](./ai-tools/README.md)** - AI tooling documentation + +### Architecture Reference + +- **Monorepo Structure**: 4 packages (web, backend, core, scripts) +- **Type System**: TypeScript + Zod schemas for validation +- **State Management**: Redux (frontend) +- **API**: Express REST API with Supertokens authentication +- **Database**: MongoDB + +## 🛠️ AI Tools Available + +### 1. API Documentation Generator + +```bash +yarn docs:generate +``` + +**Output**: `ai-tools/api-documentation.md` +**Purpose**: Auto-extracts all backend API endpoints with authentication requirements + +### 2. Type Reference Extractor + +```bash +yarn ts-node ai-tools/extract-types.ts +``` + +**Output**: `ai-tools/type-reference.md` +**Purpose**: Documents all TypeScript types, interfaces, and Zod schemas + +### 3. Code Health Auditor + +```bash +yarn audit:code-health +``` + +**Output**: Console report +**Purpose**: Analyzes codebase for issues, complexity metrics, and improvement areas + +### 4. Full AI Index + +```bash +yarn ai:index +``` + +**Purpose**: Runs documentation generators (API docs + type reference) + +### 5. Type Checker + +```bash +yarn type-check +``` + +**Purpose**: Full TypeScript type validation across all packages + +## 📖 Key Concepts + +### Module Aliases (ALWAYS USE THESE) + +```typescript +// ✅ Correct - Use aliases +import { foo } from '@compass/core' +import { bar } from '@web/common/utils' +import { baz } from '@core/types' + +// ❌ Wrong - No relative paths +import { foo } from '../../../core/src' +``` + +**Available Aliases**: + +- `@compass/backend` → `packages/backend/src` +- `@compass/core` → `packages/core/src` +- `@compass/scripts` → `packages/scripts/src` +- `@web/*` → `packages/web/src/*` +- `@core/*` → `packages/core/src/*` + +### Validation Pattern (ALWAYS USE ZOD) + +```typescript +import { z } from "zod"; + +// 1. Define schema +export const UserSchema = z.object({ + id: z.string(), + email: z.string().email(), +}); + +// 2. Export inferred type +export type User = z.infer; + +// 3. Use for validation +const user = UserSchema.parse(data); +``` + +### Testing Pattern (USE TESTING LIBRARY) + +```typescript +// ✅ Correct - Semantic queries and user interactions +const button = screen.getByRole('button', { name: /save/i }); +await user.click(button); + +// ❌ Wrong - Implementation details +const button = container.querySelector('.save-btn'); +``` + +## 🏗️ Architecture Overview + +### Backend (`@compass/backend`) + +``` +packages/backend/src/ +├── auth/ # Google OAuth integration +├── calendar/ # Calendar list and selection +├── event/ # Event CRUD operations +├── sync/ # Google Calendar sync logic +├── user/ # User profile and metadata +├── priority/ # Task priority management +├── waitlist/ # Waitlist management +└── common/ # Shared utilities, middleware +``` + +**Key Files**: + +- `*routes.config.ts` - Route definitions +- `controllers/*.controller.ts` - Request handlers +- `services/*.service.ts` - Business logic +- `dao/*.dao.ts` - Database operations + +### Frontend (`@compass/web`) + +``` +packages/web/src/ +├── views/ # React components by feature +│ ├── Calendar/ # Calendar view (day/week) +│ ├── Forms/ # Event forms +│ ├── Now/ # Focus mode +│ └── Root.tsx # Router configuration +├── store/ # Redux state management +│ ├── calendar/ # Calendar state +│ ├── draft/ # Draft event state +│ ├── schema/ # Schema state +│ ├── settings/ # User settings +│ ├── sidebar/ # Sidebar state +│ ├── task/ # Task state +│ └── view/ # View state +├── common/ # Shared utilities +└── hooks/ # Custom React hooks +``` + +**Key Patterns**: + +- Use Tailwind semantic colors: `bg-bg-primary` not `bg-blue-300` +- No barrel files (`index.ts`) - use named exports +- Redux for global state, local state for component-specific + +### Core (`@compass/core`) + +``` +packages/core/src/ +├── types/ # TypeScript type definitions +├── constants/ # Shared constants +├── util/ # Utility functions +│ ├── date/ # Date/time utilities (dayjs) +│ └── event/ # Event utilities +├── mappers/ # Data transformation +└── validators/ # Validation schemas +``` + +**Key Files**: + +- `types/**/*.types.ts` - Type definitions with Zod schemas +- `util/date/` - Date handling with dayjs and custom plugins +- `mappers/` - Transform between Compass and Google Calendar formats + +### Scripts (`@compass/scripts`) + +``` +packages/scripts/src/ +├── commands/ # CLI commands (build, seed, delete) +├── common/ # Shared CLI utilities +└── cli.ts # Command-line interface entry point +``` + +## 🔐 Authentication & Authorization + +### Supertokens Session Management + +- **Session validation**: `verifySession()` middleware +- **Google OAuth**: `requireGoogleConnectionSession` middleware +- **Dev-only**: `authMiddleware.verifyIsDev` middleware + +### API Endpoint Patterns + +```typescript +// Public endpoint (no auth) +this.app.route("/api/waitlist").post(controller.method); + +// Authenticated endpoint +this.app.route("/api/user/profile").all(verifySession()).get(controller.method); + +// Requires Google Calendar connection +this.app + .route("/api/event") + .all(verifySession()) + .post(requireGoogleConnectionSession, controller.create); +``` + +## 🧪 Testing Strategy + +### Test Commands + +```bash +yarn test:core # Core package tests (~2 seconds, 134 tests) +yarn test:web # Web package tests (~15 seconds) +yarn test:backend # Backend tests (~15 seconds) +yarn test:scripts # Scripts tests +yarn test # Full suite (avoid in restricted networks) +yarn test:e2e # Playwright E2E tests +``` + +### Writing Tests + +1. **Frontend**: Use Testing Library, semantic queries, user-event +2. **Backend**: Use Jest, mock external services +3. **Core**: Pure function tests, edge cases +4. **E2E**: Playwright for critical user flows + +## 📝 Git Workflow + +### Branch Naming + +```bash +feature/add-calendar-sync +bug/fix-auth-timeout +docs/update-api-docs +refactor/simplify-event-logic +``` + +### Commit Messages (Conventional Commits) + +```bash +feat(web): add calendar event creation modal +fix(backend): resolve authentication timeout +docs(readme): update AI agent instructions +refactor(core): simplify date utility functions +test(web): add tests for login flow +``` + +## 🎯 Common Tasks + +### Adding a New API Endpoint + +1. Define types in `packages/core/src/types/` +2. Create Zod schema for validation +3. Add route in `packages/backend/src/*/routes.config.ts` +4. Implement controller in `controllers/*.controller.ts` +5. Add service logic in `services/*.service.ts` +6. Add DAO if database access needed +7. Add JSDoc comments with `@auth`, `@body`, `@returns`, `@throws` +8. Write tests +9. Run `yarn docs:generate` to update API docs + +### Adding a New React Component + +1. Create component in `packages/web/src/views/[feature]/` +2. Use semantic Tailwind colors from `@theme` directive +3. Add TypeScript types (no `any`) +4. Write tests using Testing Library +5. Use Redux for global state, props for local +6. Follow naming conventions (`is` prefix for booleans) + +### Modifying Date Logic + +1. Use dayjs from `@core/util/date/dayjs` +2. Prefer custom plugin methods over manual manipulation +3. Always handle timezones explicitly +4. Add JSDoc with examples +5. Write comprehensive tests for edge cases + +## ⚡ Performance Tips + +- Use `yarn ai:index` to build documentation once, reference it +- Run targeted tests (`yarn test:core`) instead of full suite +- Use `yarn type-check` before committing +- Frontend works standalone - no backend needed for UI work + +## 🔧 Troubleshooting + +### Common Issues + +**Tests failing?** + +```bash +# Run packages individually +yarn test:core +yarn test:web +yarn test:backend +``` + +**Backend won't start?** + +- Missing env variables in `packages/backend/.env` +- Use web-only mode: `yarn dev:web` + +**Type errors?** + +```bash +yarn type-check +``` + +**Code style issues?** + +```bash +yarn prettier . --write +``` + +### Network Limitations + +- MongoDB binary downloads may fail in restricted networks +- Use individual test commands instead of full suite +- Frontend tests work without backend + +## 📊 Code Quality Standards + +### Pre-Commit Checklist + +1. ✅ Code follows module alias conventions +2. ✅ All new code has tests +3. ✅ Types are defined with Zod schemas +4. ✅ JSDoc comments added for public APIs +5. ✅ `yarn prettier . --write` passes +6. ✅ `yarn type-check` passes +7. ✅ Relevant tests pass + +### Code Review Standards + +1. Changes are surgical and minimal +2. No introduction of `any` types +3. Error handling is comprehensive +4. Documentation is updated +5. No unrelated changes included + +## 🌟 Best Practices + +### DO ✅ + +- Use module aliases for imports +- Use Zod for all validation +- Write tests using semantic queries +- Add JSDoc to public APIs +- Follow conventional commit format +- Keep changes focused and minimal +- Run `yarn audit:code-health` before PRs + +### DON'T ❌ + +- Use relative imports +- Use `any` types (use `unknown` instead) +- Use `data-*` attributes in tests +- Use raw Tailwind colors (use semantic) +- Create barrel files (`index.ts`) +- Use `console.log` (use logger) +- Modify unrelated code + +## 🔗 Resources + +### External References + +- **OpenAI Harness Engineering**: [https://openai.com/index/harness-engineering/](https://openai.com/index/harness-engineering/) +- **Loop Methodology**: [https://ghuntley.com/loop/](https://ghuntley.com/loop/) +- **Testing Library**: [https://testing-library.com/docs/react-testing-library/intro/](https://testing-library.com/docs/react-testing-library/intro/) +- **Zod Documentation**: [https://zod.dev/](https://zod.dev/) + +### Internal Documentation + +- **AI Workflow Examples**: [ai-tools/workflow-examples.md](./ai-tools/workflow-examples.md) +- **Generated API Docs**: [ai-tools/api-documentation.md](./ai-tools/api-documentation.md) +- **Type Reference**: [ai-tools/type-reference.md](./ai-tools/type-reference.md) + +## 🆘 Getting Help + +1. **Check existing documentation** - README, AGENTS.md, CONTRIBUTING.md +2. **Run AI tools** - `yarn ai:index` for up-to-date docs +3. **Review workflow examples** - See `ai-tools/workflow-examples.md` +4. **Check GitHub issues** - Look for similar problems +5. **Create an issue** - Provide context and steps to reproduce + +## 📈 Success Metrics + +An AI agent is successful when: + +- ✅ Changes are minimal and surgical +- ✅ All tests pass (core, web, backend) +- ✅ Type checking passes +- ✅ Code follows conventions +- ✅ Documentation is updated +- ✅ No regressions introduced +- ✅ Code is readable and maintainable + +--- + +**Remember**: The goal is to make safe, incremental improvements while maintaining high code quality and following established patterns. When in doubt, check the documentation, run the AI tools, and keep changes small and focused. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c8c76a8b..5ea733da3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,3 +2,261 @@ The Compass Contribution Guide is available on the official doc site: [https://docs.compasscalendar.com/docs/how-to-contribute/contribute](https://docs.compasscalendar.com/docs/how-to-contribute/contribute) + +## For AI Agents + +This section provides specific guidelines for AI coding agents working on Compass. + +### Getting Started + +1. **Read the Documentation First** + - Start with [AGENTS.md](./AGENTS.md) for complete development instructions + - Review [README.md](./README.md) for project overview + - Check existing issues and PRs for context + +2. **Environment Setup** + + ```bash + yarn install --frozen-lockfile --network-timeout 300000 + cp packages/backend/.env.local.example packages/backend/.env + yarn dev:web # Verify frontend works + ``` + +3. **Understand the Codebase** + - Use `yarn ai:index` to generate semantic search index + - Explore `ai-tools/` directory for helpful scripts + - Review architecture in [AGENTS.md](./AGENTS.md) + +### Coding Standards for AI Agents + +#### 1. Module Imports + +**ALWAYS** use module aliases instead of relative paths: + +```typescript +// ✅ Correct +import { foo } from '@compass/core' +import { bar } from '@web/common/utils' +import { baz } from '@core/types' + +// ❌ Wrong +import { foo } from '../../../core/src' +import { bar } from '../../common/utils' +``` + +#### 2. Type Safety and Validation + +**ALWAYS** use Zod for validation: + +```typescript +import { z } from "zod"; + +// Define schema +export const UserSchema = z.object({ + id: z.string(), + email: z.string().email(), + name: z.string().min(1), +}); + +// Export type from schema +export type User = z.infer; + +// Use for validation +export const validateUser = (data: unknown): User => UserSchema.parse(data); +``` + +#### 3. Testing Requirements + +Write tests following user behavior patterns: + +```typescript +// ✅ Correct - Using semantic queries +const button = screen.getByRole('button', { name: /save/i }); +await user.click(button); + +// ❌ Wrong - Using implementation details +const button = container.querySelector('.save-button'); +button.click(); +``` + +#### 4. API Route Documentation + +Document all backend routes with JSDoc: + +```typescript +/** + * Get user profile information + * @route GET /api/user/profile + * @auth Required - Supertokens session + * @returns {UserProfile} User profile data + * @throws {401} Unauthorized - Invalid or missing session + * @throws {404} Not Found - User not found + */ +.get(userController.getProfile); +``` + +#### 5. Error Handling + +Always include proper error handling: + +```typescript +try { + const result = await apiCall(); + return { success: true, data: result }; +} catch (error) { + logger.error('Operation failed', { error, context }); + throw new ApiError('User-friendly message', 500); +} +``` + +### Git Workflow for AI Agents + +#### Branch Naming + +Follow semantic branch naming: + +```bash +# Format: type/action[-issue-number] +feature/add-calendar-sync +bug/fix-auth-timeout +docs/update-api-docs +refactor/simplify-event-handler +``` + +#### Commit Messages + +Use conventional commit format: + +```bash +# Format: type(scope): description +feat(web): add calendar event creation modal +fix(backend): resolve authentication timeout issue +docs(readme): update installation instructions +refactor(core): simplify date utility functions +test(web): add unit tests for login component +``` + +**Commit Types:** + +- `feat` - New features +- `fix` - Bug fixes +- `docs` - Documentation changes +- `style` - Code style changes (formatting, semicolons) +- `refactor` - Code refactoring +- `test` - Adding or updating tests +- `chore` - Maintenance tasks, dependency updates + +**Scope Examples:** + +- `web` - Frontend/React changes +- `backend` - Server/API changes +- `core` - Shared utilities/types +- `scripts` - CLI tools and build scripts +- `config` - Configuration files + +### Code Review Process + +#### Before Submitting PR + +1. **Run Type Checking** + + ```bash + yarn type-check + ``` + +2. **Run Tests** + + ```bash + yarn test:core + yarn test:web + yarn test:backend + ``` + +3. **Check Code Health** + + ```bash + yarn audit:code-health + ``` + +4. **Format Code** + + ```bash + yarn prettier . --write + ``` + +5. **Generate Documentation** (if you added/modified APIs) + ```bash + yarn docs:generate + ``` + +#### PR Description Template + +```markdown +## Description + +Brief description of changes + +## Type of Change + +- [ ] Bug fix (non-breaking change fixing an issue) +- [ ] New feature (non-breaking change adding functionality) +- [ ] Breaking change (fix or feature causing existing functionality to change) +- [ ] Documentation update + +## Testing + +- [ ] Unit tests pass (`yarn test:core`, `yarn test:web`, `yarn test:backend`) +- [ ] E2E tests pass (if applicable) +- [ ] Manual testing completed + +## Checklist + +- [ ] Code follows project style guidelines +- [ ] Self-review completed +- [ ] Comments added for complex logic +- [ ] Documentation updated +- [ ] No new warnings introduced +- [ ] Tests added/updated as needed +``` + +### Common Pitfalls to Avoid + +1. **Don't use barrel files (`index.ts`)** - Use named exports directly +2. **Don't use raw Tailwind colors** - Use semantic theme colors (`bg-bg-primary` not `bg-blue-300`) +3. **Don't test login without backend** - Frontend tests should work standalone +4. **Don't modify unrelated code** - Keep changes surgical and focused +5. **Don't skip type checking** - Always run `yarn type-check` before submitting +6. **Don't ignore linter warnings** - Fix all warnings in your code +7. **Don't use relative imports** - Always use module aliases + +### AI Tools Reference + +The `ai-tools/` directory contains helper scripts: + +- **`generate-api-docs.ts`** - Extract and document all API endpoints +- **`extract-types.ts`** - Generate type documentation +- **`code-health-audit.ts`** - Analyze code quality metrics +- **`semantic-index.ts`** - Build search index for code navigation +- **`test-harness.ts`** - Template for automated testing workflows + +Run any tool with: + +```bash +yarn ts-node ai-tools/.ts +``` + +### Resources for AI Agents + +- **OpenAI Harness Engineering**: [https://openai.com/index/harness-engineering/](https://openai.com/index/harness-engineering/) +- **Loop Methodology**: [https://ghuntley.com/loop/](https://ghuntley.com/loop/) +- **Testing Library**: Best practices for user-centric testing +- **Zod Documentation**: Type-safe validation patterns + +### Questions? + +For issues or questions: + +1. Check existing GitHub issues +2. Review [AGENTS.md](./AGENTS.md) and docs +3. Join GitHub Discussions +4. Create a new issue with detailed context diff --git a/README.md b/README.md index 8a46d41bd..adf7a327f 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Join thousands of engineers who are staying organized and productive with Compas https://github.com/user-attachments/assets/ba7b91b9-1984-49f2-afc6-7fcda1100b31 ---- - ## Features ### The Unique Stuff @@ -43,31 +41,47 @@ https://github.com/user-attachments/assets/ba7b91b9-1984-49f2-afc6-7fcda1100b31 ### Current Limitations - Only supports primary Google Calendar (no sub-calendars) -- No sharing, locations, reminders, or mobile app (yet!) +- No sharing, locations, reminders, or mobile app We're actively working on improvements – check out our [roadmap](https://github.com/orgs/SwitchbackTech/projects/4). +## Tech Stack + +- **Frontend**: React, Redux, Tailwind CSS, TypeScript, Webpack +- **Backend**: Node.js, Express, TypeScript, MongoDB +- **Integrations**: Google Calendar API, Google OAuth2, Socket.io +- **Testing**: Jest, React Testing Library +- **Other**: Yarn workspaces for monorepo management + ## Getting Started ### Try It Online -Head over to [app.compasscalendar.com](https://app.compasscalendar.com?utm_source=github&utm_medium=referral&utm_campaign=readme) - -No signup required — start planning instantly! +Head over to [app.compasscalendar.com](https://app.compasscalendar.com?utm_source=github&utm_medium=referral&utm_campaign=readme). No signup required ### Run Locally -Want to poke around or run it self-hosted? +Want to poke around or self-host? [Read the technical docs](https://docs.compasscalendar.com?utm_source=github&utm_medium=referral&utm_campaign=readme): All the info you'd need to get started, including guides on how to install, test, build, deploy, and contribute. -## Tech Stack +### Development Workflow -- **Frontend**: React, Redux, Tailwind CSS, TypeScript, Webpack -- **Backend**: Node.js, Express, TypeScript, MongoDB -- **Integrations**: Google Calendar API, Google OAuth2, Socket.io -- **Testing**: Jest, React Testing Library -- **Other**: Yarn workspaces for monorepo management +```bash +# Quick start +yarn install --frozen-lockfile --network-timeout 300000 +cp packages/backend/.env.local.example packages/backend/.env +yarn dev:web # Frontend on http://localhost:9080 +yarn dev:backend # Backend on http://localhost:3000 + +# Testing +yarn test:core && yarn test:web && yarn test:backend +yarn test:e2e + +# Type checking and linting +yarn type-check +yarn prettier . --write +``` ## Contributing @@ -77,7 +91,7 @@ Issues: Check open issues or create a new one. Pull Requests: Fork the repo, make your changes, and submit a PR. Follow our [Contribution Guidelines](https://docs.compasscalendar.com/docs/contribute). Discussions: Join the conversation on GitHub Discussions. -First-time contributors? Look for issues labeled "good first issue"! +First-time contributors? Look for issues labeled `good first issue.` ## Community & Resources diff --git a/docs/agent-onboarding.md b/docs/agent-onboarding.md new file mode 100644 index 000000000..c17d640e7 --- /dev/null +++ b/docs/agent-onboarding.md @@ -0,0 +1,83 @@ +# Agent Onboarding + +This is the fastest accurate path into the Compass codebase for AI agents. + +## Start Here + +Read these in order: + +1. `AGENTS.md` for repo rules, test commands, and naming conventions. +2. `docs/repo-architecture.md` for package boundaries and startup paths. +3. `docs/feature-file-map.md` for "where do I edit?" lookups. +4. `docs/common-change-recipes.md` for safe implementation patterns. + +## Current Ground Truth + +- The monorepo has four active packages: `packages/web`, `packages/backend`, `packages/core`, and `packages/scripts`. +- The frontend can run standalone with `yarn dev:web`. +- The backend requires valid env configuration and external services. +- Shared domain types and validation live primarily in `packages/core/src`. +- Event behavior spans all three runtime packages: `core`, `web`, and `backend`. + +## First Commands To Run + +```bash +rg --files docs packages +sed -n '1,220p' package.json +sed -n '1,220p' packages/web/src/index.tsx +sed -n '1,220p' packages/backend/src/app.ts +sed -n '1,260p' packages/core/src/types/event.types.ts +``` + +## High-Value File Anchors + +- Web boot: `packages/web/src/index.tsx` +- Web root view: `packages/web/src/views/Root.tsx` +- Web router: `packages/web/src/routers/index.tsx` +- Redux store: `packages/web/src/store/index.ts` +- Event sagas: `packages/web/src/ducks/events/sagas/event.sagas.ts` +- Local storage adapter: `packages/web/src/common/storage/adapter/indexeddb.adapter.ts` +- Backend app startup: `packages/backend/src/app.ts` +- Express wiring: `packages/backend/src/servers/express/express.server.ts` +- Event routes: `packages/backend/src/event/event.routes.config.ts` +- Event controller: `packages/backend/src/event/controllers/event.controller.ts` +- Event service: `packages/backend/src/event/services/event.service.ts` +- Sync service: `packages/backend/src/sync/services/sync.service.ts` +- Websocket server: `packages/backend/src/servers/websocket/websocket.server.ts` +- Shared event types: `packages/core/src/types/event.types.ts` +- Shared websocket constants: `packages/core/src/constants/websocket.constants.ts` + +## Working Rules That Matter In Practice + +- Use module aliases instead of deep relative imports. +- Prefer adding or updating Zod schemas next to shared types in `core`. +- For web changes, test from user behavior, not implementation details. +- For backend routes, follow `routes.config -> controller -> service -> query` flow. +- For event changes, inspect both sync directions before editing only one side. +- Be careful with authenticated vs unauthenticated behavior; Compass supports both local-only and remote-backed flows. + +## Known Friction Points + +- Web event state is split between Redux slices/sagas and an Elf event store. +- Event recurrence rules and someday behavior have several transition paths. +- IndexedDB initialization and migrations happen before the app fully boots. +- Once a user has authenticated, event repository selection intentionally prefers remote access even if the current session is gone. + +## If You Are Touching... + +- Auth or session behavior: read `docs/frontend-runtime-flow.md` and `docs/env-and-dev-modes.md`. +- Backend endpoints: read `docs/backend-request-flow.md`. +- Google sync or websocket behavior: read `docs/google-sync-and-websocket-flow.md`. +- Local persistence: read `docs/offline-storage-and-migrations.md`. +- Event or task shape: read `docs/event-and-task-domain-model.md`. +- Shared schemas: read `docs/types-and-validation.md`. + +## Validation Defaults + +Run the smallest relevant checks first: + +- Core-only changes: `yarn test:core` +- Web-only changes: `yarn test:web` +- Backend-only changes: `yarn test:backend` +- Cross-package type changes: `yarn test:core && yarn test:web && yarn test:backend` +- Before handoff: `yarn type-check` diff --git a/docs/api-documentation.md b/docs/api-documentation.md new file mode 100644 index 000000000..a75893846 --- /dev/null +++ b/docs/api-documentation.md @@ -0,0 +1,156 @@ +# Compass API Documentation + +## Table of Contents + +- [Calendar Routes](#calendar-routes) +- [Auth Routes](#auth-routes) +- [User Routes](#user-routes) +- [Common Routes](#common-routes) +- [Priority Routes](#priority-routes) +- [Sync Routes](#sync-routes) +- [Event Routes](#event-routes) + +--- + +## Calendar Routes + +**Source**: `packages/backend/src/calendar/calendar.routes.config.ts` + +### /api/calendars + +### /api/calendars/select + +--- + +## Auth Routes + +**Source**: `packages/backend/src/auth/auth.routes.config.ts` + +### /api/auth/session + +--- + +## User Routes + +**Source**: `packages/backend/src/user/user.routes.config.ts` + +### /api/user/profile + +### /api/user/metadata + +--- + +## Common Routes + +**Source**: `packages/backend/src/common/common.routes.config.ts` + +_Review the source file for route definitions_ + +--- + +## Priority Routes + +**Source**: `packages/backend/src/priority/priority.routes.config.ts` + +### /api/priority + +### /api/priority/:id + +--- + +## Sync Routes + +**Source**: `packages/backend/src/sync/sync.routes.config.ts` + +### /api${GCAL_NOTIFICATION_ENDPOINT} + +### /api/sync/maintain-all + +### /api/sync/import-gcal + +### /api/event-change-demo + +### ${SYNC_DEBUG}/import-incremental/:userId + +### ${SYNC_DEBUG}/maintain/:userId + +### ${SYNC_DEBUG}/refresh/:userId + +### ${SYNC_DEBUG}/start + +### ${SYNC_DEBUG}/stop + +### ${SYNC_DEBUG}/stop-all/:userId + +--- + +## Event Routes + +**Source**: `packages/backend/src/event/event.routes.config.ts` + +### /api/event + +### /api/event/deleteMany + +### /api/event/reorder + +### /api/event/delete-all/:userId + +### /api/event/:id + +--- + +## Authentication + +Most endpoints require authentication via Supertokens session management. + +**Authentication Flow**: + +1. Client initiates OAuth flow via `/api/auth` +2. User authenticates with Google +3. Session cookie is set +4. Subsequent requests include session cookie +5. Backend validates session with `verifySession()` middleware + +## Common Patterns + +### Route Configuration + +Routes are defined in `*routes.config.ts` files using Express router: + +```typescript +this.app + .route(`/api/endpoint`) + .all(verifySession()) // Authentication middleware + .get(controller.method) + .post(controller.create) + .put(controller.update) + .delete(controller.delete); +``` + +### Middleware + +- `verifySession()` - Requires valid Supertokens session +- `requireGoogleConnectionSession` - Requires Google OAuth connection +- `verifyIsDev` - Development environment only + +## Error Responses + +Standard error response format: + +```json +{ + "error": "Error message", + "statusCode": 400, + "details": "Additional context (optional)" +} +``` + +## Key Endpoints + +- `/api/auth/*` - Authentication and OAuth flows +- `/api/user/*` - User profile and metadata +- `/api/event/*` - Calendar event CRUD operations +- `/api/calendars/*` - Calendar list and selection +- `/api/sync/*` - Google Calendar synchronization +- `/api/priority/*` - Task priority management diff --git a/docs/backend-request-flow.md b/docs/backend-request-flow.md new file mode 100644 index 000000000..4d5f82879 --- /dev/null +++ b/docs/backend-request-flow.md @@ -0,0 +1,114 @@ +# Backend Request Flow + +Compass backend code follows a consistent route -> controller -> service pattern with middleware-heavy request setup. + +## Startup And Route Registration + +Primary files: + +- `packages/backend/src/app.ts` +- `packages/backend/src/servers/express/express.server.ts` + +Express startup does this: + +1. initialize SuperTokens +2. install request middleware +3. install CORS and SuperTokens middleware +4. install helmet, logging, and JSON parsing +5. register route config classes +6. install the SuperTokens error handler after routes + +## Route Config Pattern + +Each feature owns a `*routes.config.ts` file that extends `CommonRoutesConfig`. + +Base class: + +- `packages/backend/src/common/common.routes.config.ts` + +Typical ownership split: + +- routes config: express path and middleware composition +- controller: request parsing and response orchestration +- service: business logic +- query/repo layer: database-specific access + +## Middleware Order Matters + +The backend relies on middleware ordering for correct behavior: + +- request/response promise helpers are installed before routes +- session middleware runs before route handlers that require auth +- SuperTokens error handling runs after routes + +If a new route behaves strangely, verify it is registered inside `initExpressServer()` and uses the expected middleware stack. + +## Response Pattern + +File: + +- `packages/backend/src/common/middleware/promise.middleware.ts` + +The backend decorates `res` with `res.promise(...)` so controllers can pass: + +- a Promise +- a sync function +- a resolved value + +Errors funnel through shared Express error handling. + +## Event Request Example + +Files: + +- `packages/backend/src/event/event.routes.config.ts` +- `packages/backend/src/event/controllers/event.controller.ts` +- `packages/backend/src/event/services/event.service.ts` + +For `POST /api/event`: + +1. route requires `verifySession()` +2. route also requires `requireGoogleConnectionSession` for create +3. controller adds the authenticated user id +4. controller normalizes single vs array payloads +5. controller forwards the change set to `CompassSyncProcessor` +6. processor persists and syncs changes, then notifies clients + +## Common Auth Guards + +Frequently used middleware: + +- `verifySession()`: authenticated Compass session required +- `requireGoogleConnectionSession`: active Google connection required +- `authMiddleware.verifyIsDev`: development-only route +- `authMiddleware.verifyIsFromCompass`: trusted internal caller +- `authMiddleware.verifyIsFromGoogle`: trusted Google notification source + +## Validation Placement + +Validation is spread across a few layers: + +- env validation at startup through Zod +- shared data schemas in `packages/core/src/types` +- query validation in feature utilities where needed +- controller/service parsing for request-specific inputs + +When adding a public contract, prefer creating or extending a shared schema in `core` first. + +## How To Add A Backend Endpoint + +1. Add or reuse a shared schema/type in `core` if the contract is cross-package. +2. Register the route in the relevant `*routes.config.ts`. +3. Keep the controller thin: extract params, user id, and response orchestration only. +4. Put business logic in a service. +5. Add or update tests at the controller/service level. +6. If the endpoint affects realtime UI, check whether a websocket notification is also needed. + +## Where Bugs Usually Hide + +- middleware ordering +- session requirement mismatches +- user id injection in controllers +- recurring event scope handling +- Mongo/ObjectId conversion around event ids +- returning instance events without rehydrating recurrence rules diff --git a/docs/cli-and-maintenance-commands.md b/docs/cli-and-maintenance-commands.md new file mode 100644 index 000000000..903cebef0 --- /dev/null +++ b/docs/cli-and-maintenance-commands.md @@ -0,0 +1,103 @@ +# CLI And Maintenance Commands + +Compass ships a repo CLI for builds and database maintenance. + +## Entry Point + +Primary file: + +- `packages/scripts/src/cli.ts` + +Root command: + +```bash +yarn cli +``` + +## Supported Commands + +### Build + +Examples: + +```bash +yarn cli build web --environment staging --clientId "test-client-id" +yarn cli build nodePckgs --environment staging +``` + +Implementation: + +- `packages/scripts/src/commands/build.ts` + +Use for: + +- production-style web builds +- compiled node package output + +### Delete + +Example: + +```bash +yarn cli delete --user --force +``` + +Implementation: + +- `packages/scripts/src/commands/delete.ts` + +Use with care; this is user-data deletion logic. + +Implementation: + +- `packages/scripts/src/commands/invite.ts` + +### Migrate + +Example shape: + +```bash +yarn cli migrate +``` + +Implementation: + +- `packages/scripts/src/commands/migrate.ts` + +This command wraps Umzug and uses Mongo-backed migration storage. + +### Seed + +Example shape: + +```bash +yarn cli seed +``` + +Use for database seeder flows built on the same migration framework. + +## Migration Internals + +The migration command: + +- starts Mongo +- builds an Umzug CLI dynamically +- loads migrations from `packages/scripts/src/migrations` or seeders from `packages/scripts/src/seeders` +- stores execution state in Mongo collections + +There is also a separate web-local migration system under `packages/web/src/common/storage/migrations`; do not confuse the two. + +## Safety Guidance + +- Prefer reading a command implementation before running it. +- Treat delete flows as destructive unless proven otherwise. +- For migration work, inspect existing migration naming and ordering first. +- For build work, confirm whether you need `web` or `nodePckgs`. + +## Quick Reference + +- General help: `yarn cli --help` +- Build package outputs: `yarn cli build ...` +- Database migration framework: `yarn cli migrate ...` +- Seeder framework: `yarn cli seed ...` +- Waitlist/user maintenance: `yarn cli invite`, `yarn cli delete ...` diff --git a/docs/common-change-recipes.md b/docs/common-change-recipes.md new file mode 100644 index 000000000..8acfda05e --- /dev/null +++ b/docs/common-change-recipes.md @@ -0,0 +1,88 @@ +# Common Change Recipes + +These are the safest implementation paths for common Compass changes. + +## Add A Backend Endpoint + +1. Define or extend a shared schema/type in `packages/core/src/types` if the contract is shared. +2. Add the route to the relevant `packages/backend/src/*/*.routes.config.ts`. +3. Keep the controller thin in `controllers/*.controller.ts`. +4. Put business logic in `services/*.service.ts`. +5. Add controller or service tests. +6. If the endpoint affects realtime UI, decide whether a websocket event is required. + +## Add A New Event Field + +1. Update the event schema/types in `packages/core/src/types/event.types.ts`. +2. Update any mapper or utility code in `packages/core/src/mappers` or `packages/core/src/util/event`. +3. Update backend persistence or parser logic if the field is stored or transformed. +4. Update web editors, selectors, and rendering. +5. Add tests in `core`, `web`, and `backend` as needed. + +Rule: never treat event shape as web-only unless the field is strictly presentational. + +## Change Recurring Event Behavior + +1. Read `packages/core/src/types/event.types.ts`. +2. Read `packages/backend/src/sync/services/sync/compass.sync.processor.ts`. +3. Read `packages/backend/src/event/classes/compass.event.parser.ts`. +4. Update the relevant transition path. +5. Add focused tests for the exact recurrence transition you changed. + +Do not edit recurring behavior from one layer only. + +## Add A Websocket Event + +1. Add the event name to `packages/core/src/constants/websocket.constants.ts`. +2. Update shared websocket types in `packages/core/src/types/websocket.types.ts` if needed. +3. Emit from `packages/backend/src/servers/websocket/websocket.server.ts`. +4. Consume it in a web socket hook under `packages/web/src/socket/hooks`. +5. Add tests on both emitter and listener sides. + +## Add Or Change Local Storage Data + +1. Update `packages/web/src/common/storage/adapter/storage.adapter.ts` if the public adapter contract changes. +2. Update `packages/web/src/common/storage/adapter/indexeddb.adapter.ts`. +3. Add a migration if existing user data could become invalid. +4. Add adapter and migration tests. + +## Change Repository Selection Or Offline Behavior + +1. Start in `packages/web/src/common/repositories/event/event.repository.util.ts`. +2. Verify auth-state implications in `packages/web/src/auth/session/SessionProvider.tsx` and auth-state helpers. +3. Test both never-authenticated and previously-authenticated behavior. + +## Change Day View Task Behavior + +1. Start in `packages/web/src/views/Day/hooks/tasks`. +2. Inspect supporting UI in `packages/web/src/views/Day/components/TaskList`. +3. If persistence changed, update storage adapter code and tests. + +## Add A Migration Or Seeder + +For database migrations: + +1. inspect `packages/scripts/src/commands/migrate.ts` +2. add migration under `packages/scripts/src/migrations` +3. run the relevant scripts tests + +For web local-data migrations: + +1. inspect `packages/web/src/common/storage/migrations/migrations.ts` +2. add the migration to the correct registry +3. add migration tests + +## Change Environment Handling + +1. Update the relevant env schema: + - backend: `packages/backend/src/common/constants/env.constants.ts` + - web: `packages/web/src/common/constants/env.constants.ts` +2. Confirm startup behavior still works in the intended dev mode. +3. Document any new required variables. + +## Add A New CLI Command + +1. Register the command in `packages/scripts/src/cli.ts`. +2. Implement behavior in `packages/scripts/src/commands`. +3. Reuse shared CLI utilities from `packages/scripts/src/common`. +4. Add integration tests in `packages/scripts/src/__tests__`. diff --git a/docs/env-and-dev-modes.md b/docs/env-and-dev-modes.md new file mode 100644 index 000000000..651f87c35 --- /dev/null +++ b/docs/env-and-dev-modes.md @@ -0,0 +1,126 @@ +# Env And Dev Modes + +Compass has multiple workable development modes. Pick the lightest mode that supports the feature you are changing. + +## Frontend-Only Mode + +Command: + +```bash +yarn dev:web +``` + +Use this for: + +- most layout and interaction work +- local task behavior +- many event UI changes +- router and view work + +You do not need the backend for basic frontend rendering. + +## Backend Mode + +Command: + +```bash +yarn dev:backend +``` + +Use this for: + +- authenticated API work +- Google OAuth/session behavior +- Mongo-backed event behavior +- sync and websocket work + +This requires valid env config. + +## Backend Environment Contract + +Source: + +- `packages/backend/src/common/constants/env.constants.ts` + +The backend validates required env at startup with Zod. + +Important variables: + +- `NODE_ENV` +- `TZ` +- `BASEURL` +- `PORT` +- `MONGO_URI` +- `GOOGLE_CLIENT_ID` +- `GOOGLE_CLIENT_SECRET` +- `SUPERTOKENS_URI` +- `SUPERTOKENS_KEY` +- `TOKEN_GCAL_NOTIFICATION` +- `TOKEN_COMPASS_SYNC` + +Optional but behavior-changing: + +- `NGROK_AUTHTOKEN` +- `NGROK_DOMAIN` +- emailer-related variables + +## Web Environment Contract + +Source: + +- `packages/web/src/common/constants/env.constants.ts` + +Important variables: + +- `API_BASEURL` +- `GOOGLE_CLIENT_ID` +- `NODE_ENV` +- `POSTHOG_KEY` +- `POSTHOG_HOST` + +`BACKEND_BASEURL` is derived from `API_BASEURL`. + +## Practical Mode Matrix + +### Safe without backend + +- route changes +- component rendering +- keyboard and pointer interactions +- local storage behavior +- many task workflows + +### Requires backend + +- user profile loading +- authenticated event APIs +- Google connection flows +- backend validation behavior +- Mongo persistence +- websocket server behavior + +### Requires Google-related setup + +- real OAuth +- real Google Calendar import/sync +- notification watch flows + +## Auth And Anonymous Behavior + +Compass supports both: + +- never-authenticated users using local storage +- authenticated or previously-authenticated users using remote repositories + +When testing changes around event loading, explicitly decide which user state you are modeling. + +## Ngrok Notes + +Ngrok is optional for general local development but relevant for Google notification/watch flows. The backend env schema requires both ngrok auth token and static domain together if ngrok is enabled. + +## Common Failure Modes + +- backend exits immediately because required env is missing +- web points at the wrong API base URL +- session exists but user profile fetch fails +- sync endpoints work but notification/watch setup fails due to incomplete Google/ngrok setup diff --git a/docs/event-and-task-domain-model.md b/docs/event-and-task-domain-model.md new file mode 100644 index 000000000..ba9cb5fb3 --- /dev/null +++ b/docs/event-and-task-domain-model.md @@ -0,0 +1,143 @@ +# Event And Task Domain Model + +The event domain is the most cross-cutting part of Compass. Read this before changing event shape, recurrence logic, sync behavior, or local persistence. + +## Core Event Schema + +Primary source: + +- `packages/core/src/types/event.types.ts` + +Important event fields: + +- `_id`: Compass event id +- `startDate`, `endDate`: ISO datetime or date strings +- `isAllDay`: display semantics +- `isSomeday`: local Compass someday bucket semantics +- `origin`: where the event came from +- `priority`: shared priority enum +- `gEventId`: Google event id when synced +- `gRecurringEventId`: Google recurring parent id when relevant +- `recurrence.rule`: RRULE array for recurring bases +- `recurrence.eventId`: base Compass event id for recurring instances + +## Display Categories + +`Categories_Event` maps events to visible buckets: + +- `allday` +- `timed` +- `sidebarWeek` +- `sidebarMonth` + +These are UI-facing categories, not storage categories. + +## Recurrence Categories + +`Categories_Recurrence` models structural state: + +- `STANDALONE` +- `RECURRENCE_BASE` +- `RECURRENCE_INSTANCE` +- `STANDALONE_SOMEDAY` +- `RECURRENCE_BASE_SOMEDAY` +- `RECURRENCE_INSTANCE_SOMEDAY` + +Many sync and parser decisions key off transitions between these states. + +## Update Scopes + +Recurring edits use `RecurringEventUpdateScope`: + +- `This Event` +- `This and Following Events` +- `All Events` + +If you change recurring edit behavior, check: + +- `packages/core/src/types/event.types.ts` +- `packages/backend/src/event/controllers/event.controller.ts` +- `packages/backend/src/sync/services/sync/compass.sync.processor.ts` + +## Backend Event Shape Semantics + +The backend treats recurring events as: + +- one base event containing recurrence rules +- zero or more generated instances referencing the base via `recurrence.eventId` + +When reading instances back, the backend rehydrates the instance with the base event's recurrence rule before returning it. + +Primary code: + +- `packages/backend/src/event/services/event.service.ts` +- `packages/backend/src/event/classes/compass.event.parser.ts` +- `packages/backend/src/event/classes/compass.event.generator.ts` + +## Someday Semantics + +`isSomeday` is not just a UI flag. + +It affects: + +- query behavior +- sync transitions +- websocket notification type +- provider selection when mapping events + +For someday events, Compass often behaves as the provider of record instead of Google. + +## Optimistic IDs + +The web can create optimistic ids using a prefix, and the backend strips that prefix before persisting: + +- web optimistic flow: `packages/web/src/ducks/events/sagas/event.sagas.ts` +- backend normalization: `packages/backend/src/event/controllers/event.controller.ts` + +Do not assume every incoming `_id` is already a durable Mongo id. + +## Task Model + +Primary source: + +- `packages/web/src/common/types/task.types.ts` + +Task fields: + +- `_id` +- `title` +- `status` (`todo` or `completed`) +- `order` +- `createdAt` +- `description` +- `user` + +Tasks are currently local-storage centric and are stored with a `dateKey` in the adapter layer, not in the public `Task` shape. + +## Storage-Specific Task Shape + +The IndexedDB adapter wraps tasks as `StoredTask`: + +- public task data +- plus `dateKey` + +Source: + +- `packages/web/src/common/storage/adapter/storage.adapter.ts` + +## Invariants To Preserve + +- Every persisted event must have a stable Compass `_id`. +- Instances reference a base event via `recurrence.eventId`. +- Base recurring events carry the `recurrence.rule`. +- `isSomeday` changes downstream sync and notification behavior. +- Tasks should normalize through `normalizeTask` / `normalizeTasks` before persistence. +- Local storage schemas can evolve, but migrations must preserve existing user data. + +## Before Changing The Domain + +Check all three layers: + +1. `core` type/schema definition +2. `backend` persistence and sync behavior +3. `web` editing, rendering, selectors, storage, and tests diff --git a/docs/feature-file-map.md b/docs/feature-file-map.md new file mode 100644 index 000000000..a23ed046b --- /dev/null +++ b/docs/feature-file-map.md @@ -0,0 +1,88 @@ +# Feature File Map + +Use this document to find the first files to inspect for common Compass changes. + +## App Boot And Routing + +- Frontend bootstrap: `packages/web/src/index.tsx` +- App provider tree: `packages/web/src/components/App/App.tsx` +- Root authenticated shell: `packages/web/src/views/Root.tsx` +- Router config: `packages/web/src/routers/index.tsx` +- Router loaders: `packages/web/src/routers/loaders.ts` + +## Authentication And Session + +- Session initialization and SuperTokens wiring: `packages/web/src/auth/session/SessionProvider.tsx` +- User profile bootstrap: `packages/web/src/auth/context/UserProvider.tsx` +- Auth schemas: `packages/web/src/auth/schemas/auth.schemas.ts` +- Backend auth routes: `packages/backend/src/auth/auth.routes.config.ts` +- Backend auth controllers/services: `packages/backend/src/auth/controllers`, `packages/backend/src/auth/services` + +## Events + +- Shared event schema/types: `packages/core/src/types/event.types.ts` +- Event helpers and recurrence utilities: `packages/core/src/util/event` +- Web event sagas: `packages/web/src/ducks/events/sagas` +- Web event slices/selectors: `packages/web/src/ducks/events/slices`, `packages/web/src/ducks/events/selectors` +- Elf event entity store: `packages/web/src/store/events.ts` +- Event API/repositories: `packages/web/src/ducks/events/event.api.ts`, `packages/web/src/common/repositories/event` +- Backend event routes: `packages/backend/src/event/event.routes.config.ts` +- Backend event controller/service: `packages/backend/src/event/controllers/event.controller.ts`, `packages/backend/src/event/services/event.service.ts` + +## Tasks + +- Shared task type used by local storage: `packages/web/src/common/types/task.types.ts` +- Day task hooks: `packages/web/src/views/Day/hooks/tasks` +- Task UI components: `packages/web/src/views/Day/components/TaskList` +- Local storage for tasks: `packages/web/src/common/storage/adapter` + +## Day / Week / Now Views + +- Day view route and content: `packages/web/src/views/Day/view` +- Day view hooks: `packages/web/src/views/Day/hooks` +- Week/calendar view: `packages/web/src/views/Calendar` +- Now view: `packages/web/src/views/Now` + +## Offline Storage + +- Adapter singleton and readiness: `packages/web/src/common/storage/adapter/adapter.ts` +- IndexedDB implementation: `packages/web/src/common/storage/adapter/indexeddb.adapter.ts` +- Legacy schema migration: `packages/web/src/common/storage/adapter/legacy-primary-key.migration.ts` +- Data/external migrations: `packages/web/src/common/storage/migrations` + +## Sync And Websockets + +- Web socket client: `packages/web/src/socket/client/socket.client.ts` +- Web socket hooks: `packages/web/src/socket/hooks` +- Web socket provider: `packages/web/src/socket/provider/SocketProvider.tsx` +- Shared websocket event names: `packages/core/src/constants/websocket.constants.ts` +- Backend websocket server: `packages/backend/src/servers/websocket/websocket.server.ts` +- Backend sync routes/services: `packages/backend/src/sync/sync.routes.config.ts`, `packages/backend/src/sync/services` + +## Users / Metadata / Waitlist + +- User queries/services: `packages/backend/src/user` +- Priority feature: `packages/backend/src/priority` +- Waitlist controllers/service/types: `packages/backend/src/waitlist` + +## Environment And Infra + +- Backend env parsing: `packages/backend/src/common/constants/env.constants.ts` +- Web env parsing: `packages/web/src/common/constants/env.constants.ts` +- Express middleware order: `packages/backend/src/servers/express/express.server.ts` + +## CLI / Maintenance + +- CLI entrypoint: `packages/scripts/src/cli.ts` +- Build command: `packages/scripts/src/commands/build.ts` +- Delete command: `packages/scripts/src/commands/delete.ts` +- Migration command: `packages/scripts/src/commands/migrate.ts` +- Seeders/migrations: `packages/scripts/src/migrations`, `packages/scripts/src/seeders` + +## Test Anchors + +- Root Jest project config: `jest.config.js` +- Core test setup: `packages/core/src/__tests__` +- Web test setup: `packages/web/src/__tests__` +- Backend test setup: `packages/backend/src/__tests__` +- E2E tests: `e2e` diff --git a/docs/frontend-runtime-flow.md b/docs/frontend-runtime-flow.md new file mode 100644 index 000000000..c199f83ef --- /dev/null +++ b/docs/frontend-runtime-flow.md @@ -0,0 +1,153 @@ +# Frontend Runtime Flow + +This document describes how the web app boots and where runtime responsibilities live. + +## Boot Sequence + +Primary entrypoint: + +- `packages/web/src/index.tsx` + +Boot order: + +1. initialize local storage through `initializeDatabaseWithErrorHandling()` +2. start redux-saga with `sagaMiddleware.run(sagas)` +3. initialize session tracking with `sessionInit()` +4. render `` +5. show a toast if local database initialization failed + +This order matters because storage should be ready before sagas and repositories perform local operations. + +## App Provider Tree + +`packages/web/src/components/App/App.tsx` renders: + +- keyboard and movement event setup hooks +- optional providers +- required providers +- router provider + +The route tree lazily loads feature views. + +## Router Flow + +Files: + +- `packages/web/src/routers/index.tsx` +- `packages/web/src/routers/loaders.ts` + +Important behavior: + +- the root route loads `RootView` +- the day route redirects to today's date when needed +- `loadAuthenticated()` checks whether a session exists +- route loaders use shared date parsing from `core` + +## Root View Responsibilities + +`packages/web/src/views/Root.tsx`: + +- blocks mobile with `MobileGate` +- wraps authenticated layout with `UserProvider` +- wires socket listeners through `SocketProvider` + +This is the shell for the main desktop app experience. + +## Session Runtime + +File: + +- `packages/web/src/auth/session/SessionProvider.tsx` + +Responsibilities: + +- initialize SuperTokens recipes +- track auth state in a `BehaviorSubject` +- mark users as having authenticated +- connect or disconnect sockets on session changes +- expose a React context for auth status + +Important detail: + +Once a user has ever authenticated, the app records that fact in local auth-state storage so repository selection can prefer remote data later. + +## User Bootstrap + +File: + +- `packages/web/src/auth/context/UserProvider.tsx` + +Responsibilities: + +- fetch the user profile only for users who have authenticated before +- avoid blocking unauthenticated users +- show a session-expired toast on auth failures +- identify the user in PostHog when enabled + +## State Systems + +The web app uses multiple state layers: + +- Redux Toolkit reducers and slices for app and async state +- redux-saga for network/storage side effects +- Elf for event entity store, active event, and draft state +- IndexedDB for local persistence + +Read these together for event work: + +- `packages/web/src/store/index.ts` +- `packages/web/src/store/sagas.ts` +- `packages/web/src/ducks/events/sagas` +- `packages/web/src/store/events.ts` + +## Repository Selection + +File: + +- `packages/web/src/common/repositories/event/event.repository.util.ts` + +Repository choice: + +- never-authenticated users use local IndexedDB repositories +- authenticated or previously-authenticated users use remote repositories + +This is deliberate and prevents events from "disappearing" after login when local data is empty. + +## Storage Initialization + +Files: + +- `packages/web/src/common/storage/adapter/adapter.ts` +- `packages/web/src/common/storage/migrations/migrations.ts` + +Startup storage flow: + +1. create or reuse the storage adapter singleton +2. open IndexedDB and run internal schema migrations +3. run data migrations +4. run external import migrations + +Database init failure is non-fatal; the app falls back to remote-only behavior when possible. + +## Websocket Runtime + +Files: + +- `packages/web/src/socket/provider/SocketProvider.tsx` +- `packages/web/src/socket/hooks/useSocketConnection.ts` +- `packages/web/src/socket/hooks/useEventSync.ts` +- `packages/web/src/socket/hooks/useGcalSync.ts` + +Responsibilities: + +- connect/disconnect the socket based on auth state +- refetch events when background event changes arrive +- react to Google import progress and Google revocation events +- request user metadata via socket when appropriate + +## What To Read Before Editing + +- Auth/session issue: read session provider, user provider, router loaders. +- Event refresh issue: read socket hooks, sync slice, event sagas. +- Offline issue: read storage adapter and migration runner. +- Rendering issue in day/week/now: start at the route view, then its hooks. diff --git a/docs/google-sync-and-websocket-flow.md b/docs/google-sync-and-websocket-flow.md new file mode 100644 index 000000000..d87845032 --- /dev/null +++ b/docs/google-sync-and-websocket-flow.md @@ -0,0 +1,114 @@ +# Google Sync And Websocket Flow + +Compass sync is bidirectional: + +- Compass-originated event changes can propagate to Google and then notify web clients. +- Google-originated changes can flow back into Compass and then notify web clients. + +## Shared Event Names + +Source: + +- `packages/core/src/constants/websocket.constants.ts` + +Important server-to-client events: + +- `EVENT_CHANGED` +- `SOMEDAY_EVENT_CHANGED` +- `USER_METADATA` +- `IMPORT_GCAL_START` +- `IMPORT_GCAL_END` +- `GOOGLE_REVOKED` + +## Outbound Flow: User Changes An Event In Compass + +High-level path: + +1. UI dispatches an event action. +2. A saga performs optimistic updates. +3. The selected repository writes locally or remotely. +4. Remote event writes hit backend event routes. +5. `EventController` packages the change as a `CompassEvent`. +6. `CompassSyncProcessor.processEvents()` parses the event transition and applies persistence/sync logic. +7. After commit, the backend emits websocket notifications based on whether the change affected normal or someday events. + +Primary files: + +- `packages/web/src/ducks/events/sagas/event.sagas.ts` +- `packages/web/src/common/repositories/event` +- `packages/backend/src/event/controllers/event.controller.ts` +- `packages/backend/src/sync/services/sync/compass.sync.processor.ts` + +## Inbound Flow: Google Notifies Compass About Changes + +High-level path: + +1. Google posts to the notification endpoint in sync routes. +2. Backend verifies the request origin. +3. `SyncService.handleGcalNotification()` locates the watch and sync record. +4. The service builds a Google Calendar client for the user. +5. `GCalNotificationHandler` fetches incremental changes using the stored sync token. +6. `GcalSyncProcessor` applies those changes to Compass data. +7. The websocket server emits `EVENT_CHANGED` so clients refetch. + +Primary files: + +- `packages/backend/src/sync/sync.routes.config.ts` +- `packages/backend/src/sync/services/sync.service.ts` +- `packages/backend/src/sync/services/notify/handler/gcal.notification.handler.ts` +- `packages/backend/src/sync/services/sync/gcal.sync.processor.ts` + +## Watch And Sync Records + +Two backend data concepts matter: + +- watch records track Google push channels +- sync records track sync tokens and per-resource state + +If notification handling fails because a watch or sync record is stale, the backend attempts cleanup before failing hard. + +## Websocket Server Responsibilities + +Source: + +- `packages/backend/src/servers/websocket/websocket.server.ts` + +The websocket server: + +- binds the authenticated session to each socket +- tracks sockets by session and by user +- emits events to one session or all active sessions for a user +- responds to `FETCH_USER_METADATA` by returning `USER_METADATA` + +## Web Client Responsibilities + +Files: + +- `packages/web/src/socket/hooks/useSocketConnection.ts` +- `packages/web/src/socket/hooks/useEventSync.ts` +- `packages/web/src/socket/hooks/useGcalSync.ts` + +The client: + +- connects only when user/session state warrants it +- refetches events after background changes +- tracks Google import status +- handles Google revocation +- requests metadata used to decide whether import should start + +## Import Flow + +Google import progress is also realtime: + +1. backend starts import +2. websocket emits `IMPORT_GCAL_START` +3. client marks import pending +4. backend completes import and emits `IMPORT_GCAL_END` +5. client stores import results and triggers a refetch + +## Rules Of Thumb For Changes + +- New realtime behavior usually needs changes in `core`, `backend`, and `web`. +- If you add a new websocket event, update both shared event types/constants and both emit/listen sides. +- If you change sync token behavior, inspect both notification handling and import logic. +- If the UI is stale after edits, confirm a socket event is emitted and that the right sync slice action is dispatched on the client. diff --git a/docs/offline-storage-and-migrations.md b/docs/offline-storage-and-migrations.md new file mode 100644 index 000000000..9ce15151f --- /dev/null +++ b/docs/offline-storage-and-migrations.md @@ -0,0 +1,143 @@ +# Offline Storage And Migrations + +Compass supports a meaningful local-first path for unauthenticated users and resilient fallback behavior for authenticated users. + +## Storage Entry Points + +Primary files: + +- `packages/web/src/common/storage/adapter/adapter.ts` +- `packages/web/src/common/storage/adapter/storage.adapter.ts` +- `packages/web/src/common/storage/adapter/indexeddb.adapter.ts` + +The adapter layer exists so application code is not tightly bound to Dexie. + +## Adapter Lifecycle + +`initializeStorage()` does this: + +1. lazily create the storage adapter singleton +2. open the underlying IndexedDB database +3. run adapter-level schema upgrades +4. run app-level data migrations +5. run app-level external import migrations + +The initialization call is idempotent and memoized by `initPromise`. + +## IndexedDB Schema + +Current database name: + +- `compass-local` + +Current table groups: + +- `events` +- `tasks` +- `_migrations` + +The IndexedDB adapter keeps: + +- events keyed by `_id` +- tasks keyed by `_id` and associated to a `dateKey` +- migration completion records in `_migrations` + +## Legacy Primary-Key Migration + +File: + +- `packages/web/src/common/storage/adapter/legacy-primary-key.migration.ts` + +There is explicit support for an older task schema that used `id` instead of `_id`. + +Recovery strategy: + +1. detect the Dexie upgrade error +2. read legacy records through a legacy Dexie schema +3. delete the old database +4. reopen using the current schema +5. reinsert events and tasks + +## Data Migrations + +File: + +- `packages/web/src/common/storage/migrations/migrations.ts` + +Data migrations: + +- transform data already inside Compass storage +- are tracked in the `_migrations` table +- fail startup if they fail + +Current example: + +- task `id` -> `_id` migration + +## External Migrations + +External migrations: + +- import data from outside the adapter +- are tracked in localStorage, not IndexedDB +- are non-blocking on failure + +Current examples: + +- localStorage task import +- demo data seeding + +## Failure Model + +Database initialization errors are surfaced to the user but do not hard-stop app boot. + +Files: + +- `packages/web/src/common/utils/app-init.util.ts` +- `packages/web/src/index.tsx` + +Expected behavior: + +- app still renders +- toast explains offline storage is unavailable +- authenticated users can continue in remote-only mode + +## Event And Task Persistence + +Event operations: + +- query overlapping date ranges +- put one or many events +- delete by event id + +Task operations: + +- get tasks for a `dateKey` +- replace all tasks for a date +- upsert a single task +- move a task between dates + +All task writes should pass through normalization helpers from `packages/web/src/common/types/task.types.ts`. + +## When To Add A Migration + +Add a migration when you change: + +- IndexedDB schema shape +- local task/event field names or required defaults +- import behavior from legacy local sources + +Choose the right mechanism: + +- adapter schema/version change for storage structure +- data migration for data already in storage +- external migration for imports from localStorage or other sources + +## Safe Editing Checklist + +Before changing storage behavior: + +1. update adapter code +2. add or adjust migration if existing user data could break +3. add tests for fresh database and migrated database paths +4. confirm startup still degrades gracefully when storage fails diff --git a/docs/repo-architecture.md b/docs/repo-architecture.md new file mode 100644 index 000000000..074f44860 --- /dev/null +++ b/docs/repo-architecture.md @@ -0,0 +1,143 @@ +# Repo Architecture + +Compass is a TypeScript monorepo with four packages and one shared event domain. + +## Package Map + +### `packages/web` + +The React frontend. It owns: + +- app startup and routing +- auth/session-aware UI +- event and task interactions +- local offline storage +- websocket listeners + +Key entrypoints: + +- `packages/web/src/index.tsx` +- `packages/web/src/components/App/App.tsx` +- `packages/web/src/routers/index.tsx` +- `packages/web/src/views/Root.tsx` + +### `packages/backend` + +The Express + MongoDB backend. It owns: + +- route registration +- Supertokens session enforcement +- event CRUD and recurrence processing +- Google Calendar sync +- websocket fanout + +Key entrypoints: + +- `packages/backend/src/app.ts` +- `packages/backend/src/servers/express/express.server.ts` +- `packages/backend/src/servers/websocket/websocket.server.ts` + +### `packages/core` + +The shared domain layer. It owns: + +- Zod schemas and TypeScript types +- shared constants +- date/event utilities +- mapping logic between Compass and provider formats + +High-value files: + +- `packages/core/src/types/event.types.ts` +- `packages/core/src/types/type.utils.ts` +- `packages/core/src/constants/core.constants.ts` +- `packages/core/src/constants/websocket.constants.ts` + +### `packages/scripts` + +The CLI and database maintenance package. It owns: + +- build commands +- delete flows +- database migrations and seeders +- waitlist invite tasks + +Entry point: + +- `packages/scripts/src/cli.ts` + +## Runtime Boundaries + +### Web -> Core + +The web package imports shared event/task/date concepts from `core` and should not redefine them locally unless the data is UI-specific. + +### Backend -> Core + +The backend uses `core` for shared validation, event categories, recurrence scopes, constants, and websocket event names. + +### Web <-> Backend + +The web talks to the backend through: + +- HTTP APIs +- websocket events +- shared domain types from `core` + +## Startup Paths + +### Frontend boot + +`packages/web/src/index.tsx` does this in order: + +1. initialize storage +2. start saga middleware +3. initialize session tracking +4. render `` + +`` then installs provider trees and the router. + +### Backend boot + +`packages/backend/src/app.ts` does this in order: + +1. create Express app +2. create HTTP server +3. initialize websocket server on the HTTP server +4. start Mongo +5. listen on the configured port +6. optionally connect ngrok + +## Main Architectural Patterns + +### Backend route pattern + +`routes.config.ts` -> controller -> service -> query/mongo + +This is the standard pattern for new HTTP behavior. + +### Web state pattern + +Web state is not single-system: + +- Redux Toolkit slices hold app state and async state +- redux-saga handles side effects +- Elf stores are used for event entity management and draft/active state +- IndexedDB stores offline events/tasks + +Treat this as an intentional mixed architecture, not an inconsistency to "fix" casually. + +### Shared schema pattern + +The repo prefers: + +1. define schema with Zod +2. export inferred TypeScript type +3. consume the same contract in web and backend + +## Where Cross-Cutting Changes Usually Land + +- New event field: `core` schema, backend parsing/persistence, web editors/selectors/tests +- New backend endpoint: backend route/controller/service plus maybe shared type in `core` +- New websocket event: `core` constants/types, backend server emitter, web socket hook consumer +- New local persistence behavior: web storage adapter, migration runner, tests diff --git a/docs/testing-playbook.md b/docs/testing-playbook.md new file mode 100644 index 000000000..b1c1e1558 --- /dev/null +++ b/docs/testing-playbook.md @@ -0,0 +1,145 @@ +# Testing Playbook + +Use the smallest test surface that can fail for the change you are making, then widen only if the change crosses boundaries. + +## Main Commands + +From repo root: + +```bash +yarn test:core +yarn test:web +yarn test:backend +yarn test:scripts +yarn type-check +``` + +Avoid defaulting to `yarn test` unless you really need the full suite. + +## Jest Project Layout + +Source: + +- `jest.config.js` + +Projects: + +- `core` +- `web` +- `backend` +- `scripts` + +Each project has its own setup files and module alias mapping. + +## What To Run By Change Type + +### Shared type or schema change + +Run: + +```bash +yarn test:core && yarn test:web && yarn test:backend +yarn type-check +``` + +### Web-only UI or behavior change + +Run: + +```bash +yarn test:web +``` + +Add `yarn test:core` if the change touched shared utilities. + +### Backend route or service change + +Run: + +```bash +yarn test:backend +``` + +Add `yarn test:core` if a shared type or mapper changed. + +### CLI, migration, or seeder change + +Run: + +```bash +yarn test:scripts +``` + +## Web Test Style + +Preferred style: + +- React Testing Library +- semantic queries by role/name/text +- `user-event` for real interactions + +Avoid: + +- CSS selectors +- implementation-detail assertions +- unnecessary module-wide mocks + +Useful anchors: + +- `packages/web/src/__tests__` +- `packages/web/src/views/**/*.test.tsx` +- `packages/web/src/socket/**/*.test.ts` + +## Backend Test Style + +Preferred style: + +- controller/service behavior tests +- realistic request flows when possible +- mock only external services, not internal business logic + +Useful anchors: + +- `packages/backend/src/__tests__` +- `packages/backend/src/event/services/*.test.ts` +- `packages/backend/src/sync/**/*.test.ts` + +## Core Test Style + +Preferred style: + +- pure function coverage +- edge cases and schema validation +- date and recurrence invariants + +Useful anchors: + +- `packages/core/src/util/**/*.test.ts` +- `packages/core/src/types/*.test.ts` +- `packages/core/src/validators/*.test.ts` + +## E2E Notes + +E2E tests live in `e2e`. + +Use them for: + +- critical user flows +- integration between auth, UI, and persistence +- regressions that unit tests cannot model cleanly + +## Testing Realtime And Sync Changes + +For websocket or sync work: + +- test backend emitters/handlers where possible +- test web socket hooks for listener registration and dispatch behavior +- test event sagas if refetch or optimistic behavior changed + +## Common Gaps To Watch + +- optimistic event ids +- recurring event scope handling +- local-only versus authenticated repository behavior +- storage migration paths +- date parsing around all-day events and UTC formatting diff --git a/docs/types-and-validation.md b/docs/types-and-validation.md new file mode 100644 index 000000000..152aa95ff --- /dev/null +++ b/docs/types-and-validation.md @@ -0,0 +1,94 @@ +# Types And Validation + +Compass uses shared Zod-backed contracts to keep web and backend behavior aligned. + +## Preferred Pattern + +The repo generally prefers: + +1. define a Zod schema +2. export the inferred TypeScript type +3. reuse that contract across packages + +This keeps runtime validation and compile-time typing close together. + +## Main Places To Look + +### Shared contracts + +- `packages/core/src/types` +- `packages/core/src/validators` +- `packages/core/src/types/type.utils.ts` + +### Backend request/env validation + +- `packages/backend/src/common/constants/env.constants.ts` +- `packages/backend/src/common/validators` +- feature-specific query validators such as `packages/backend/src/sync/util/sync.queries.ts` + +### Web form/client validation + +- `packages/web/src/auth/schemas` +- `packages/web/src/common/validators` +- feature-specific form hooks such as `packages/web/src/components/AuthModal/hooks/useZodForm.ts` + +## Shared Event Contract + +The most important shared contract is the event schema in: + +- `packages/core/src/types/event.types.ts` + +Do not duplicate event field definitions inside `web` or `backend` unless the extra type is intentionally layer-specific. + +## ObjectId And String Helpers + +The repo has reusable schema helpers in: + +- `packages/core/src/types/type.utils.ts` + +Examples include: + +- object id parsing +- date string parsing +- timezone validation +- hex color validation + +Use these helpers before inventing new ad hoc validators. + +## Zod Versions In The Repo + +The codebase currently uses both: + +- `zod` +- `zod/v4` +- `zod/v4-mini` + +Be consistent with nearby code when editing, especially inside `core` types. + +## When To Put A Type In `core` + +Put it in `core` when: + +- both web and backend use it +- it defines a persisted or API-visible contract +- it encodes domain invariants + +Keep it local to `web` or `backend` when: + +- it is purely presentation or implementation detail +- it only exists to support one runtime layer + +## Recommended Workflow For Contract Changes + +1. update the shared schema in `core` +2. update inferred types and related helpers +3. update backend parsers/controllers/services +4. update web forms, selectors, and renderers +5. run cross-package tests and `yarn type-check` + +## Common Pitfalls + +- changing a TypeScript interface without updating the Zod schema +- changing web-only types while forgetting the backend contract +- adding a field to storage or API responses without updating normalization +- mixing local-only and shared event/task types unintentionally diff --git a/docs/workflow-examples.md b/docs/workflow-examples.md new file mode 100644 index 000000000..fc27cddd4 --- /dev/null +++ b/docs/workflow-examples.md @@ -0,0 +1,445 @@ +# AI Workflow Examples + +This document demonstrates Harness and Loop-style development workflows for AI agents working on Compass. + +## Table of Contents + +- [Harness Engineering Workflows](#harness-engineering-workflows) +- [Loop Methodology Examples](#loop-methodology-examples) +- [Automated Testing Workflows](#automated-testing-workflows) +- [Code Review Automation](#code-review-automation) + +--- + +## Harness Engineering Workflows + +Based on OpenAI's Harness Engineering methodology: [https://openai.com/index/harness-engineering/](https://openai.com/index/harness-engineering/) + +### Example 1: Adding a New API Endpoint + +**Goal**: Add a new endpoint to get user statistics + +**Harness Approach**: + +1. **Understand the Pattern** + + ```bash + # Study existing endpoint structure + yarn ts-node ai-tools/generate-api-docs.ts + # Review output to understand route patterns + ``` + +2. **Define the Interface First** + + ```typescript + // packages/core/src/types/user/user.stats.types.ts + import { z } from "zod"; + + export const UserStatsSchema = z.object({ + userId: z.string(), + totalEvents: z.number(), + completedTasks: z.number(), + streak: z.number(), + lastActive: z.string().datetime(), + }); + + export type UserStats = z.infer; + ``` + +3. **Create Test First** + + ```typescript + // packages/backend/src/user/__tests__/user.stats.test.ts + describe("User Statistics", () => { + it("should return user statistics", async () => { + const stats = await userService.getStats(testUserId); + expect(stats).toMatchObject({ + userId: expect.any(String), + totalEvents: expect.any(Number), + completedTasks: expect.any(Number), + }); + }); + }); + ``` + +4. **Implement the Route** + + ```typescript + // packages/backend/src/user/user.routes.config.ts + this.app + .route("/api/user/stats") + .all(verifySession()) + .get(userController.getStats); + ``` + +5. **Verify and Document** + + ```bash + # Run tests + yarn test:backend + + # Regenerate docs + yarn ts-node ai-tools/generate-api-docs.ts + + # Verify documentation includes new endpoint + cat ai-tools/api-documentation.md | grep "/api/user/stats" + ``` + +### Example 2: Refactoring with Safety Harness + +**Goal**: Extract complex date logic into utility function + +**Harness Approach**: + +1. **Add Tests for Current Behavior** + + ```typescript + // packages/core/src/util/date/__tests__/date.utils.test.ts + describe("Date Utilities", () => { + describe("existing behavior", () => { + it("should handle timezone conversions", () => { + // Test current implementation + }); + }); + }); + ``` + +2. **Run Tests to Establish Baseline** + + ```bash + yarn test:core + # All tests should pass + ``` + +3. **Refactor in Small Steps** + + ```typescript + // Extract utility function + export function convertToUserTimezone(date: Date, timezone: string): Date { + // Refactored logic here + } + ``` + +4. **Test After Each Step** + + ```bash + yarn test:core + # Ensure no regressions + ``` + +5. **Update All Call Sites** + + ```bash + # Find all usages + grep -r "oldImplementation" packages/ + + # Replace incrementally, testing after each change + ``` + +--- + +## Loop Methodology Examples + +Based on Geoffrey Huntley's Loop: [https://ghuntley.com/loop/](https://ghuntley.com/loop/) + +### Example 1: Feature Development Loop + +**Goal**: Add calendar event filtering by category + +**Loop Cycle**: + +``` +┌─────────────────────────────────────┐ +│ 1. UNDERSTAND │ +│ - Review existing filter logic │ +│ - Check Redux store structure │ +│ - Identify impact areas │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ 2. PLAN │ +│ - Define filter interface │ +│ - Map state changes needed │ +│ - Identify components to update │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ 3. IMPLEMENT (Small Increment) │ +│ - Add filter type to Redux │ +│ - Test: yarn test:web │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ 4. VERIFY │ +│ - Run tests │ +│ - Manual verification │ +│ - Code health audit │ +└──────────────┬──────────────────────┘ + │ + ├─── If issues ──────┐ + │ │ + │ ▼ +┌──────────────▼──────────────────────┐ +│ 5. NEXT LOOP OR DONE │ +│ - Add filter UI │ +│ - Repeat cycle │ +└─────────────────────────────────────┘ +``` + +**Implementation**: + +```typescript +// Loop 1: Add type definition +// packages/core/src/types/filter.types.ts +export const EventFilterSchema = z.object({ + category: z.array(z.string()).optional(), + startDate: z.string().datetime().optional(), + endDate: z.string().datetime().optional(), +}); + +// Test and commit +// git add . && git commit -m "feat(core): add event filter types" + +// Loop 2: Add Redux state +// packages/web/src/store/filter/filter.slice.ts +const filterSlice = createSlice({ + name: "filter", + initialState: { category: [] /* ... */ }, + // ...reducers +}); + +// Test and commit +// yarn test:web +// git add . && git commit -m "feat(web): add filter state to Redux" + +// Loop 3: Add UI component +// packages/web/src/views/Calendar/FilterPanel.tsx +// Test and commit + +// Loop 4: Wire up to backend +// Test and commit + +// Each loop: small, testable, verifiable increment +``` + +### Example 2: Bug Fix Loop + +**Goal**: Fix date display bug in calendar view + +**Loop Cycle**: + +```typescript +// Loop 1: REPRODUCE THE BUG +describe('Calendar Date Display', () => { + it('should display dates in user timezone', () => { + // Write failing test that reproduces the bug + const event = createTestEvent({ date: '2024-01-01T12:00:00Z' }); + render(); + expect(screen.getByText(/Jan 1, 2024/i)).toBeInTheDocument(); + // This should fail, confirming the bug + }); +}); + +// yarn test:web +// Confirm test fails as expected + +// Loop 2: FIX THE BUG (Minimal change) +// packages/web/src/views/Calendar/DateDisplay.tsx +export function formatEventDate(date: string): string { + return dayjs(date).tz(userTimezone).format('MMM D, YYYY'); + // Changed from: dayjs(date).format(...) +} + +// Loop 3: VERIFY FIX +// yarn test:web +// Test should now pass + +// Loop 4: CHECK FOR REGRESSIONS +// yarn test:core && yarn test:backend +// yarn audit:code-health + +// Loop 5: MANUAL VERIFICATION +// yarn dev:web +// Navigate to calendar and verify fix visually + +// Loop 6: COMMIT +// git add . && git commit -m "fix(web): correct date display timezone handling" +``` + +--- + +## Automated Testing Workflows + +### Workflow 1: Test-Driven Development + +```typescript +// Step 1: Write test (RED) +// packages/web/src/hooks/__tests__/useEventForm.test.ts +describe("useEventForm", () => { + it("should validate event title is not empty", () => { + const { result } = renderHook(() => useEventForm()); + + act(() => { + result.current.setTitle(""); + }); + + expect(result.current.errors.title).toBe("Title is required"); + }); +}); + +// Step 2: Run test (should fail) +// yarn test:web + +// Step 3: Implement (GREEN) +// packages/web/src/hooks/useEventForm.ts +export function useEventForm() { + const [errors, setErrors] = useState({}); + + const validateTitle = (title: string) => { + if (!title.trim()) { + setErrors((prev) => ({ ...prev, title: "Title is required" })); + return false; + } + return true; + }; + + // ... +} + +// Step 4: Run test (should pass) +// yarn test:web + +// Step 5: Refactor if needed +// Keep tests passing while improving code +``` + +### Workflow 2: Regression Test Automation + +```typescript +// Create test harness for known bugs +// ai-tools/test-harness/regression-suite.ts +import { runTest } from "./test-utils"; + +const regressionTests = [ + { + id: "BUG-123", + description: "Date picker should handle leap years", + test: async () => { + const result = await testDatePicker({ date: "2024-02-29" }); + expect(result.isValid).toBe(true); + }, + }, + { + id: "BUG-456", + description: "Drag-drop should not duplicate events", + test: async () => { + const result = await testDragDrop(); + expect(result.eventCount).toBe(1); + }, + }, +]; + +// Run all regression tests +export async function runRegressionSuite() { + for (const test of regressionTests) { + console.log(`Testing ${test.id}: ${test.description}`); + await test.test(); + } +} +``` + +--- + +## Code Review Automation + +### Workflow 1: Pre-Commit Checks + +```bash +#!/bin/bash +# .git/hooks/pre-commit (already handled by Husky) + +# Type check +echo "Running type check..." +yarn type-check || exit 1 + +# Lint +echo "Running prettier..." +yarn prettier . --write + +# Tests (quick smoke test) +echo "Running tests..." +yarn test:core || exit 1 + +echo "✅ Pre-commit checks passed" +``` + +### Workflow 2: AI-Assisted Review + +```typescript +// ai-tools/review-assistant.ts +// Checks code before PR submission + +interface ReviewChecklist { + hasTests: boolean; + typesafe: boolean; + usesAliases: boolean; + documented: boolean; + complexity: "low" | "medium" | "high"; +} + +export async function reviewCode(files: string[]): Promise { + const results: ReviewChecklist = { + hasTests: true, + typesafe: true, + usesAliases: true, + documented: true, + complexity: "low", + }; + + for (const file of files) { + // Check for test file + const testExists = fs.existsSync(file.replace(/\.tsx?$/, ".test.ts")); + results.hasTests = results.hasTests && testExists; + + // Check for relative imports + const content = fs.readFileSync(file, "utf-8"); + if (content.includes('from "../')) { + results.usesAliases = false; + } + + // Check complexity + const complexity = analyzeComplexity(content); + if (complexity > 20) { + results.complexity = "high"; + } + } + + return results; +} +``` + +--- + +## Summary + +These workflows demonstrate: + +1. **Harness Engineering**: Building safety nets through tests and tooling +2. **Loop Methodology**: Small, incremental changes with continuous verification +3. **Automation**: Scripts and tools to speed up repetitive tasks +4. **Safety**: Multiple verification steps before committing + +### Key Principles + +- **Small increments**: Each loop should be < 1 hour of work +- **Continuous testing**: Test after every change +- **Automated checks**: Use tooling to catch issues early +- **Documentation**: Keep docs in sync with code +- **Verification**: Multiple layers of safety + +### Next Steps + +1. Study these examples +2. Apply patterns to your work +3. Create your own workflow variants +4. Share improvements back to the team diff --git a/eslint.config.mjs b/eslint.config.mjs index 879917024..1d1f3d0a6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,4 @@ +import eslintConfigPrettier from "eslint-config-prettier"; import pluginJest from "eslint-plugin-jest"; import jestDom from "eslint-plugin-jest-dom"; import jsxA11y from "eslint-plugin-jsx-a11y"; @@ -18,11 +19,15 @@ export default [ pluginJs.configs.recommended, pluginReact.configs.flat.recommended, pluginReact.configs.flat["jsx-runtime"], - ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, { files: ["**/*.{ts,tsx}"], languageOptions: { globals: { ...globals.browser, ...globals.node }, + parserOptions: { + projectService: true, + tsconfigRootDir: __dirname, + }, }, }, { @@ -47,6 +52,10 @@ export default [ rules: { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", + "@typescript-eslint/consistent-type-imports": [ + "warn", + { prefer: "type-imports", fixStyle: "inline-type-imports" }, + ], }, }, { @@ -74,6 +83,13 @@ export default [ "jest/valid-expect": "error", }, }, + // Warn on console.log in packages/web to avoid leaking secure info + { + files: ["packages/web/**/*.{ts,tsx}"], + rules: { + "no-console": ["warn", { allow: ["warn", "error"] }], + }, + }, // these plugins adjust other parts of this config, // so keep them down here { @@ -84,4 +100,5 @@ export default [ ...prettierEslint.configs.recommended.rules, }, }, + eslintConfigPrettier, ]; diff --git a/package.json b/package.json index bce547bcc..6eda4d35b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "test:backend": "yarn test backend", "test:core": "yarn test core", "test:web": "yarn test web", - "test:scripts": "yarn test scripts" + "test:scripts": "yarn test scripts", + "type-check": "tsc --noEmit" }, "dependencies": { "@compass/backend": "*", diff --git a/packages/backend/src/event/event.routes.config.ts b/packages/backend/src/event/event.routes.config.ts index 6e6a35725..819337fca 100644 --- a/packages/backend/src/event/event.routes.config.ts +++ b/packages/backend/src/event/event.routes.config.ts @@ -5,33 +5,104 @@ import { CommonRoutesConfig } from "@backend/common/common.routes.config"; import { requireGoogleConnectionSession } from "@backend/common/middleware/google.required.middleware"; import eventController from "./controllers/event.controller"; +/** + * Event Routes Configuration + * + * Handles calendar event CRUD operations and synchronization with Google Calendar. + * Most operations require Google Calendar connection for bi-directional sync. + */ export class EventRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, "EventRoutes"); } configureRoutes(): express.Application { + /** + * GET /api/event + * Retrieves all events for the authenticated user + * + * POST /api/event + * Creates a new calendar event + * + * @auth Required - Supertokens session + * @auth Required for POST - Google Calendar connection + * @body {Object} event - Event data (title, start, end, etc.) + * @returns {Array|Object} All events (GET) or created event (POST) + * @throws {401} Unauthorized - Invalid session + * @throws {403} Forbidden - No Google Calendar connection (POST) + * @throws {400} Bad Request - Invalid event data (POST) + */ this.app .route(`/api/event`) .all(verifySession()) .get(eventController.readAll) .post(requireGoogleConnectionSession, eventController.create); + /** + * DELETE /api/event/deleteMany + * Deletes multiple events in a batch operation + * + * @auth Required - Supertokens session + * @body {Array} ids - Array of event IDs to delete + * @returns {Object} Deletion result + * @throws {401} Unauthorized - Invalid session + * @throws {400} Bad Request - Invalid IDs array + */ this.app .route(`/api/event/deleteMany`) .all(verifySession()) .delete(eventController.deleteMany); + /** + * PUT /api/event/reorder + * Reorders events (updates order field for drag-and-drop) + * + * @auth Required - Supertokens session + * @body {Array} events - Array of events with updated order + * @returns {Object} Reorder result + * @throws {401} Unauthorized - Invalid session + * @throws {400} Bad Request - Invalid events array + */ this.app .route(`/api/event/reorder`) .all(verifySession()) .put(eventController.reorder); + /** + * DELETE /api/event/delete-all/:userId + * Deletes all events for a specific user (Development only) + * + * @auth Required - Supertokens session + Dev environment + * @param {string} userId - User ID whose events to delete + * @returns {Object} Deletion result + * @throws {401} Unauthorized - Invalid session + * @throws {403} Forbidden - Not in development environment + */ this.app .route(`/api/event/delete-all/:userId`) .all([verifySession(), authMiddleware.verifyIsDev]) .delete(eventController.deleteAllByUser); + /** + * GET /api/event/:id + * Retrieves a specific event by ID + * + * PUT /api/event/:id + * Updates an existing event + * + * DELETE /api/event/:id + * Deletes a specific event + * + * @auth Required - Supertokens session + * @auth Required for PUT/DELETE - Google Calendar connection + * @param {string} id - Event ID + * @body {Object} event - Updated event data (PUT only) + * @returns {Object} Event data (GET) or operation result (PUT/DELETE) + * @throws {401} Unauthorized - Invalid session + * @throws {403} Forbidden - No Google Calendar connection (PUT/DELETE) + * @throws {404} Not Found - Event not found + * @throws {400} Bad Request - Invalid event data (PUT) + */ this.app .route(`/api/event/:id`) .all(verifySession()) diff --git a/packages/backend/src/user/user.routes.config.ts b/packages/backend/src/user/user.routes.config.ts index f20c704ad..542dac487 100644 --- a/packages/backend/src/user/user.routes.config.ts +++ b/packages/backend/src/user/user.routes.config.ts @@ -3,17 +3,45 @@ import { verifySession } from "supertokens-node/recipe/session/framework/express import { CommonRoutesConfig } from "@backend/common/common.routes.config"; import userController from "./controllers/user.controller"; +/** + * User Routes Configuration + * + * Handles user profile and metadata management endpoints. + * All routes require authentication via Supertokens session. + */ export class UserRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, "UserRoutes"); } configureRoutes(): express.Application { + /** + * GET /api/user/profile + * Retrieves the current user's profile information + * + * @auth Required - Supertokens session + * @returns {Object} User profile data including email, name, and settings + * @throws {401} Unauthorized - Invalid or missing session + * @throws {404} Not Found - User not found + */ this.app .route(`/api/user/profile`) .all(verifySession()) .get(userController.getProfile); + /** + * GET /api/user/metadata + * Retrieves user metadata (preferences, settings, etc.) + * + * POST /api/user/metadata + * Updates user metadata + * + * @auth Required - Supertokens session + * @body {Object} metadata - User metadata to update + * @returns {Object} Updated metadata + * @throws {401} Unauthorized - Invalid or missing session + * @throws {400} Bad Request - Invalid metadata format + */ this.app .route(`/api/user/metadata`) .all(verifySession()) diff --git a/packages/core/src/util/date/date.util.ts b/packages/core/src/util/date/date.util.ts index 40962fb3e..9396dd7f7 100644 --- a/packages/core/src/util/date/date.util.ts +++ b/packages/core/src/util/date/date.util.ts @@ -1,6 +1,10 @@ import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants"; import dayjs, { Dayjs } from "@core/util/date/dayjs"; +/** + * RFC date format definitions for date parsing and formatting + * Used across the application for consistent date handling + */ export const FORMAT = { RFC5545: { key: "RFC5545", @@ -18,7 +22,7 @@ export const FORMAT = { type Key_Format = (typeof FORMAT)[keyof typeof FORMAT]["key"]; /** - * Convert a recurrence rule with UNTIL value to date + * Convert a recurrence rule with UNTIL value to date * @param rrule The full recurrence rule with UNTIL value (e.g. "RRULE:FREQ=DAILY;UNTIL=20260108T005808Z") * @returns The UNTIL value, parsed as iso8601 (e.g. '2026-01-08T00:58:08.000Z') */ @@ -29,6 +33,15 @@ export const convertRruleWithUntilToDate = (rrule: string) => { return origDateFromUntil; }; +/** + * Get current week and month date ranges + * @returns Object containing week and month date ranges in YYYY-MM-DD format + * @example + * { + * week: { startDate: '2024-01-01', endDate: '2024-01-07' }, + * month: { startDate: '2024-01-01', endDate: '2024-01-31' } + * } + */ export const getCurrentRangeDates = () => { const now = dayjs(); @@ -50,6 +63,12 @@ export const getCurrentRangeDates = () => { }; }; +/** + * Check if two date strings are in the same month + * @param start Start date string (ISO or other parseable format) + * @param end End date string (ISO or other parseable format) + * @returns true if both dates are in the same month + */ export const isSameMonth = (start: string, end: string) => { const _start = dayjs(start); const _end = dayjs(end);