refactor: add comprehensive coding standards and tooling - #1
MylesMCook wants to merge 4 commits into
Conversation
MylesMCook
commented
Jan 8, 2026
- Add ESLint with strict TypeScript and React rules
- Add Prettier for consistent code formatting
- Set up Vitest testing framework with 40 tests for date-parser
- Enable ESLint and TypeScript checking during builds
- Clean up package.json (rename to tempus-total, remove unused deps)
- Add comprehensive CONTRIBUTING.md with coding guidelines
- Update README with complete documentation
- Update aiRules.md to reflect current project structure
- Add .env.example for environment configuration
- Clean up date-parser.ts (remove console.log, fix unused vars)
- Add .gitignore and .prettierignore files
- Add ESLint with strict TypeScript and React rules - Add Prettier for consistent code formatting - Set up Vitest testing framework with 40 tests for date-parser - Enable ESLint and TypeScript checking during builds - Clean up package.json (rename to tempus-total, remove unused deps) - Add comprehensive CONTRIBUTING.md with coding guidelines - Update README with complete documentation - Update aiRules.md to reflect current project structure - Add .env.example for environment configuration - Clean up date-parser.ts (remove console.log, fix unused vars) - Add .gitignore and .prettierignore files
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR implements comprehensive coding standards and development tooling for the TempusTotal project. The changes establish a professional development workflow with linting, formatting, testing, and thorough documentation.
Key Changes:
- Added ESLint, Prettier, and Vitest for code quality and testing
- Enabled strict TypeScript and ESLint checking during builds
- Cleaned up package.json (renamed project, removed unused dependencies, organized deps)
- Added comprehensive documentation (CONTRIBUTING.md with 666 lines of guidelines)
- Created 40 unit tests for the date-parser with proper test setup
- Cleaned up date-parser.ts (removed console.log, fixed unused variables)
Reviewed changes
Copilot reviewed 13 out of 15 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.mts | Configures Vitest testing framework with coverage thresholds (70%/60%) and jsdom environment |
| package.json | Renames to tempus-total, adds test/lint/format scripts, reorganizes dependencies, updates versions |
| next.config.mjs | Enables ESLint and TypeScript checking during builds, adds performance optimizations |
| eslint.config.mjs | Comprehensive ESLint configuration with TypeScript, React, accessibility, and import rules |
| app/lib/date-parser.ts | Removes console.log statements, fixes unused variables, improves error handling with proper typing |
| aiRules.md | Updates project structure documentation and development guidelines to reflect current state |
| tests/setup.ts | Test environment setup with mocks for DOM APIs, localStorage, and clipboard |
| tests/date-parser.test.ts | 40 comprehensive tests covering date parsing edge cases and functionality |
| README.md | Complete rewrite with API documentation, examples, tech stack, and configuration details |
| CONTRIBUTING.md | Extensive 666-line guide covering architecture, code style, TypeScript, React, API, testing, and git workflow |
| .prettierrc | Prettier configuration with Tailwind CSS plugin support |
| .prettierignore | Prettier ignore patterns for build outputs and dependencies |
| .gitignore | Updated ignore patterns with better organization |
| .env.example | Environment variable template for analytics and API configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Blob: "readonly", | ||
| File: "readonly", |
There was a problem hiding this comment.
The 'Blob' and 'File' globals are duplicated - they appear both at lines 58-59 and again at lines 191-192. Remove the duplicate declarations to avoid confusion.
| Blob: "readonly", | |
| File: "readonly", |
| DeviceOrientationEvent: "readonly", | ||
| Gamepad: "readonly", | ||
| GamepadEvent: "readonly", | ||
| PointerEvent: "readonly", |
There was a problem hiding this comment.
The 'PointerEvent' global is duplicated - it appears at line 73 and again at line 230. Remove one of the duplicate declarations.
| PointerEvent: "readonly", |
| "htmlWhitespaceSensitivity": "css", | ||
| "embeddedLanguageFormatting": "auto", | ||
| "plugins": ["prettier-plugin-tailwindcss"], | ||
| "tailwindConfig": "./tailwind.config.js", |
There was a problem hiding this comment.
The Prettier configuration references './tailwind.config.js' but the actual Tailwind config file might be named 'tailwind.config.ts' or 'tailwind.config.mjs' (common in Next.js projects). Verify that the file path matches the actual Tailwind configuration file name in the project, otherwise the Tailwind CSS class sorting plugin will not work correctly.
| "lru-cache": "latest", | ||
| "date-fns-tz": "latest", | ||
| "nanoid": "latest" | ||
| "zod": "^3.24.1" |
There was a problem hiding this comment.
Several dependencies have been removed that were listed with 'latest' version (@remix-run/react, @sveltejs/kit, svelte, vue, vue-router, crypto). While cleaning up unused dependencies is good, verify that none of these packages were actually being used in the codebase. The 'crypto' package in particular might have been used for utilities, though Node.js provides a built-in crypto module.
| "@vitejs/plugin-react": "^4.3.4", | ||
| "@vitest/coverage-v8": "^2.1.8", | ||
| "@vitest/ui": "^2.1.8", | ||
| "autoprefixer": "^10.4.20", |
There was a problem hiding this comment.
The autoprefixer dependency has been moved from dependencies to devDependencies. However, autoprefixer is typically needed during the build process by PostCSS/Tailwind CSS. While it may work in this setup (since Next.js handles the build), verify that production builds still work correctly after this change, especially if the app is deployed in environments where devDependencies are not installed.
| globals: { | ||
| React: "readonly", | ||
| JSX: "readonly", | ||
| NodeJS: "readonly", | ||
| console: "readonly", | ||
| process: "readonly", | ||
| Buffer: "readonly", | ||
| setTimeout: "readonly", | ||
| clearTimeout: "readonly", | ||
| setInterval: "readonly", | ||
| clearInterval: "readonly", | ||
| fetch: "readonly", | ||
| URL: "readonly", | ||
| URLSearchParams: "readonly", | ||
| Request: "readonly", | ||
| Response: "readonly", | ||
| Headers: "readonly", | ||
| FormData: "readonly", | ||
| Blob: "readonly", | ||
| File: "readonly", | ||
| AbortController: "readonly", | ||
| AbortSignal: "readonly", | ||
| Event: "readonly", | ||
| EventTarget: "readonly", | ||
| CustomEvent: "readonly", | ||
| MessageEvent: "readonly", | ||
| ErrorEvent: "readonly", | ||
| ProgressEvent: "readonly", | ||
| ClipboardEvent: "readonly", | ||
| DragEvent: "readonly", | ||
| FocusEvent: "readonly", | ||
| KeyboardEvent: "readonly", | ||
| MouseEvent: "readonly", | ||
| PointerEvent: "readonly", | ||
| TouchEvent: "readonly", | ||
| WheelEvent: "readonly", | ||
| AnimationEvent: "readonly", | ||
| TransitionEvent: "readonly", | ||
| UIEvent: "readonly", | ||
| InputEvent: "readonly", | ||
| CompositionEvent: "readonly", | ||
| document: "readonly", | ||
| window: "readonly", | ||
| navigator: "readonly", | ||
| localStorage: "readonly", | ||
| sessionStorage: "readonly", | ||
| location: "readonly", | ||
| history: "readonly", | ||
| performance: "readonly", | ||
| requestAnimationFrame: "readonly", | ||
| cancelAnimationFrame: "readonly", | ||
| requestIdleCallback: "readonly", | ||
| cancelIdleCallback: "readonly", | ||
| IntersectionObserver: "readonly", | ||
| MutationObserver: "readonly", | ||
| ResizeObserver: "readonly", | ||
| PerformanceObserver: "readonly", | ||
| MediaQueryList: "readonly", | ||
| matchMedia: "readonly", | ||
| getComputedStyle: "readonly", | ||
| HTMLElement: "readonly", | ||
| HTMLInputElement: "readonly", | ||
| HTMLButtonElement: "readonly", | ||
| HTMLDivElement: "readonly", | ||
| HTMLSpanElement: "readonly", | ||
| HTMLAnchorElement: "readonly", | ||
| HTMLFormElement: "readonly", | ||
| HTMLImageElement: "readonly", | ||
| HTMLVideoElement: "readonly", | ||
| HTMLAudioElement: "readonly", | ||
| HTMLCanvasElement: "readonly", | ||
| HTMLTextAreaElement: "readonly", | ||
| HTMLSelectElement: "readonly", | ||
| HTMLOptionElement: "readonly", | ||
| HTMLTableElement: "readonly", | ||
| HTMLTableRowElement: "readonly", | ||
| HTMLTableCellElement: "readonly", | ||
| Element: "readonly", | ||
| Node: "readonly", | ||
| NodeList: "readonly", | ||
| DocumentFragment: "readonly", | ||
| Text: "readonly", | ||
| Comment: "readonly", | ||
| DOMParser: "readonly", | ||
| XMLSerializer: "readonly", | ||
| SVGElement: "readonly", | ||
| Map: "readonly", | ||
| Set: "readonly", | ||
| WeakMap: "readonly", | ||
| WeakSet: "readonly", | ||
| Promise: "readonly", | ||
| Proxy: "readonly", | ||
| Reflect: "readonly", | ||
| Symbol: "readonly", | ||
| BigInt: "readonly", | ||
| ArrayBuffer: "readonly", | ||
| SharedArrayBuffer: "readonly", | ||
| DataView: "readonly", | ||
| Int8Array: "readonly", | ||
| Uint8Array: "readonly", | ||
| Uint8ClampedArray: "readonly", | ||
| Int16Array: "readonly", | ||
| Uint16Array: "readonly", | ||
| Int32Array: "readonly", | ||
| Uint32Array: "readonly", | ||
| Float32Array: "readonly", | ||
| Float64Array: "readonly", | ||
| BigInt64Array: "readonly", | ||
| BigUint64Array: "readonly", | ||
| JSON: "readonly", | ||
| Math: "readonly", | ||
| Date: "readonly", | ||
| Intl: "readonly", | ||
| Error: "readonly", | ||
| TypeError: "readonly", | ||
| RangeError: "readonly", | ||
| SyntaxError: "readonly", | ||
| ReferenceError: "readonly", | ||
| EvalError: "readonly", | ||
| URIError: "readonly", | ||
| AggregateError: "readonly", | ||
| Object: "readonly", | ||
| Array: "readonly", | ||
| String: "readonly", | ||
| Number: "readonly", | ||
| Boolean: "readonly", | ||
| Function: "readonly", | ||
| RegExp: "readonly", | ||
| encodeURIComponent: "readonly", | ||
| decodeURIComponent: "readonly", | ||
| encodeURI: "readonly", | ||
| decodeURI: "readonly", | ||
| atob: "readonly", | ||
| btoa: "readonly", | ||
| structuredClone: "readonly", | ||
| queueMicrotask: "readonly", | ||
| reportError: "readonly", | ||
| isNaN: "readonly", | ||
| isFinite: "readonly", | ||
| parseFloat: "readonly", | ||
| parseInt: "readonly", | ||
| globalThis: "readonly", | ||
| crypto: "readonly", | ||
| Crypto: "readonly", | ||
| CryptoKey: "readonly", | ||
| SubtleCrypto: "readonly", | ||
| TextEncoder: "readonly", | ||
| TextDecoder: "readonly", | ||
| ReadableStream: "readonly", | ||
| WritableStream: "readonly", | ||
| TransformStream: "readonly", | ||
| Blob: "readonly", | ||
| File: "readonly", | ||
| FileReader: "readonly", | ||
| FileList: "readonly", | ||
| Image: "readonly", | ||
| Audio: "readonly", | ||
| Video: "readonly", | ||
| WebSocket: "readonly", | ||
| Worker: "readonly", | ||
| SharedWorker: "readonly", | ||
| ServiceWorker: "readonly", | ||
| BroadcastChannel: "readonly", | ||
| MessageChannel: "readonly", | ||
| MessagePort: "readonly", | ||
| Notification: "readonly", | ||
| indexedDB: "readonly", | ||
| IDBDatabase: "readonly", | ||
| IDBObjectStore: "readonly", | ||
| IDBIndex: "readonly", | ||
| IDBCursor: "readonly", | ||
| IDBTransaction: "readonly", | ||
| IDBRequest: "readonly", | ||
| IDBKeyRange: "readonly", | ||
| caches: "readonly", | ||
| Cache: "readonly", | ||
| CacheStorage: "readonly", | ||
| Storage: "readonly", | ||
| StorageEvent: "readonly", | ||
| CloseEvent: "readonly", | ||
| BeforeUnloadEvent: "readonly", | ||
| PageTransitionEvent: "readonly", | ||
| PopStateEvent: "readonly", | ||
| HashChangeEvent: "readonly", | ||
| SecurityPolicyViolationEvent: "readonly", | ||
| PromiseRejectionEvent: "readonly", | ||
| DeviceMotionEvent: "readonly", | ||
| DeviceOrientationEvent: "readonly", | ||
| Gamepad: "readonly", | ||
| GamepadEvent: "readonly", | ||
| PointerEvent: "readonly", | ||
| SpeechRecognition: "readonly", | ||
| SpeechSynthesis: "readonly", | ||
| ScreenOrientation: "readonly", | ||
| Screen: "readonly", | ||
| Geolocation: "readonly", | ||
| GeolocationPosition: "readonly", | ||
| GeolocationCoordinates: "readonly", | ||
| GeolocationPositionError: "readonly", | ||
| Clipboard: "readonly", | ||
| ClipboardItem: "readonly", | ||
| Permissions: "readonly", | ||
| PermissionStatus: "readonly", | ||
| }, |
There was a problem hiding this comment.
The globals configuration is extremely verbose with 243 individual global declarations. This approach is error-prone and difficult to maintain. Consider using a standard globals preset from the 'globals' npm package (e.g., globals.browser, globals.node, globals.es2021) which provides these common globals automatically and is less verbose.
| "import/resolver": { | ||
| typescript: { | ||
| alwaysTryTypes: true, | ||
| project: "./tsconfig.json", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The import/resolver configuration references 'typescript' but the required 'eslint-import-resolver-typescript' package is not listed in the devDependencies. This will cause the import plugin to fail when trying to resolve TypeScript imports. Add 'eslint-import-resolver-typescript' to devDependencies.
| Response: "readonly", | ||
| Headers: "readonly", | ||
| FormData: "readonly", | ||
| Blob: "readonly", |
There was a problem hiding this comment.
This property is duplicated in a later property.
| Blob: "readonly", |
| Headers: "readonly", | ||
| FormData: "readonly", | ||
| Blob: "readonly", | ||
| File: "readonly", |
There was a problem hiding this comment.
This property is duplicated in a later property.
| File: "readonly", |
| FocusEvent: "readonly", | ||
| KeyboardEvent: "readonly", | ||
| MouseEvent: "readonly", | ||
| PointerEvent: "readonly", |
There was a problem hiding this comment.
This property is duplicated in a later property.
| PointerEvent: "readonly", |
- Fix rate-limit.ts for Next.js 15 async headers() API - Fix date-parser.ts type inference for Token[] and direction type - Fix date-picker.tsx Date arithmetic operations - Fix api/parse/route.ts async rate limiting and proper typing - Fix analytics.ts and security-monitor.ts type issues - Fix self-closing component warnings in layout.tsx, loading.tsx - Clean up unused imports and variables in page.tsx - Simplify ESLint config to avoid import resolver issues - Update use-toast.ts type declarations All 40 tests pass, TypeScript compiles without errors.
Replace Geist/Inter Google Fonts with system font stack to avoid build failures when Google Fonts API is unreachable. The app now uses the default system UI fonts which load instantly and work offline. - Remove next/font/google imports from layout.tsx - Update tailwind.config.js to use system font stack - Build now completes successfully
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| duration: 0, | ||
| success: false, | ||
| expression: "SECURITY_ALERT", | ||
| expression: `SECURITY_ALERT:${clientIp}`, |
There was a problem hiding this comment.
The metrics.addMetric call is passing additional fields (clientIp, eventType) that are not defined in the APIMetrics interface. According to the monitoring.ts interface, APIMetrics only accepts: timestamp, duration, success, expression, and statusCode. The clientIp is now being embedded in the expression field as a workaround, but the eventType field is being removed entirely. This could cause issues if the monitoring system expects this data for security events.
| expression: `SECURITY_ALERT:${clientIp}`, | |
| expression: `SECURITY_ALERT:suspicious-activity:${clientIp}`, |
| it("should throw ParseError when expression exceeds max length", () => { | ||
| expect(() => parseExpression("a".repeat(201))).toThrow(ParseError); |
There was a problem hiding this comment.
The example shows testing a ParseError being thrown, but the actual date-parser.ts implementation returns null for invalid input rather than throwing errors. This inconsistency between the documentation example and the actual implementation could confuse contributors.
| it("should throw ParseError when expression exceeds max length", () => { | |
| expect(() => parseExpression("a".repeat(201))).toThrow(ParseError); | |
| it("should return null when expression exceeds max length", () => { | |
| expect(parseExpression("a".repeat(201))).toBeNull(); |
| // This ensures the API works smoothly during development and for UI components | ||
| if (!isSameOrigin) { | ||
| try { | ||
| // More generous limit per IP - 200 requests per minute for external users |
There was a problem hiding this comment.
The rate limiter check is set to 60 requests per minute, but the comment says "200 requests per minute for external users". This mismatch between the comment and the actual limit value could lead to confusion. Update the comment to match the actual limit of 60 requests per minute.
| // More generous limit per IP - 200 requests per minute for external users | |
| // Rate limit per IP - 60 requests per minute for external users |