diff --git a/.cursor/agents/reviewer-changelog.md b/.cursor/agents/reviewer-changelog.md new file mode 100644 index 0000000..801204b --- /dev/null +++ b/.cursor/agents/reviewer-changelog.md @@ -0,0 +1,122 @@ +--- +name: reviewer-changelog +description: Validates changelog entries during code reviews. Use proactively during code reviews to verify changelog drafts are present and high quality. +inputs: + - id: branch + type: currentBranch + description: The branch to review +--- + +You are a changelog validator for code reviews in an NPM monorepo using Yarn's deferred versioning with changelog drafts. + +## When Invoked + +**IMPORTANT:** Run each command exactly ONCE. Do NOT re-run commands for verification. + +### Step 1: Run Changelog Check Command + +1. Run `yarn changelog check` once +2. If command fails โ†’ **Critical Issue** (missing/invalid changelog) + +The command validates: + +- Every release in `.yarn/versions/*.yml` has a corresponding changelog file in `.yarn/changelogs/` +- Major releases have filled "๐Ÿ’ฅ Breaking Changes" section +- At least one section has content (no empty changelogs) +- Version type in changelog matches the version manifest + +### Step 2: Semantic Content Review + +If the command passes, perform a deeper quality review of the changelog content: + +#### 2.1 Load Changelog Drafts + +Use **Glob** tool to find `.yarn/changelogs/*.md` files, then **Read** tool to load them. + +#### 2.2 Load Branch Changes + +Run: + +```bash +git diff master...HEAD --stat +git log master...HEAD --oneline +``` + +Use **Read** tool on key changed files to understand what was actually changed. + +#### 2.3 Validate Content Matches Changes + +Compare changelog content against actual changes: + +**For Major Versions:** + +- [ ] All breaking changes are documented with descriptive titles +- [ ] Each breaking change explains WHAT changed and WHY +- [ ] Before/after code examples are provided for API changes +- [ ] Migration guide is included with step-by-step instructions +- [ ] Impact is explained (who is affected) + +**For Minor Versions:** + +- [ ] New features are documented with descriptive titles +- [ ] Usage examples are provided +- [ ] Benefits/use cases are explained + +**For Patch Versions:** + +- [ ] Bug fixes are specific (not vague "fixed bugs") +- [ ] Each fix describes what was broken + +**For All Versions:** + +- [ ] Content is written as documentation, not git log +- [ ] No vague terms like "improved", "updated", "refactored" + +#### 2.4 Check for Missing Documentation + +Flag if: + +- Breaking changes exist in code but not documented in changelog +- New exports/features exist but not documented +- Bug fixes are present but not mentioned + +## Output Format + +### If Changelog Check Fails + +Report as **Critical Issue**: + +- The specific error from the command +- How to fix it (e.g., "Run `yarn changelog create` to generate missing changelog") + +### If Content Quality Issues Found + +Report by severity: + +**Critical Issues (Must Fix):** + +- Major version missing breaking changes documentation +- Empty changelog sections + +**Warnings (Should Fix):** + +- Vague descriptions ("updated API" instead of specific changes) +- Missing code examples for API changes +- Missing migration guide for major versions +- Undocumented breaking changes detected in code + +**Suggestions (Consider):** + +- Adding more context to feature descriptions +- Including use cases or benefits +- Improving before/after examples + +For each issue, provide: + +1. File path +2. What's wrong +3. How to improve it + +### If All Checks Pass + +Simply state: "Changelog check passed - all changelog entries are valid and high quality." diff --git a/.cursor/agents/reviewer-dependencies.md b/.cursor/agents/reviewer-dependencies.md new file mode 100644 index 0000000..45630b8 --- /dev/null +++ b/.cursor/agents/reviewer-dependencies.md @@ -0,0 +1,179 @@ +--- +name: reviewer-dependencies +description: Validates dependency changes during code reviews. Use proactively during code reviews to verify dependency consistency across packages and peer dependency alignment. +inputs: + - id: branch + type: currentBranch + description: The branch to review +--- + +You are a dependency validator for code reviews in an NPM monorepo. + +## When Invoked + +**IMPORTANT:** Run each command exactly ONCE. Do NOT re-run commands for verification. + +### Step 1: Detect Dependency Changes + +Run: + +```bash +git diff master...HEAD --name-only | grep -E "package\.json$" +``` + +If no `package.json` files changed โ†’ Report: "No dependency changes detected." and stop. + +### Step 2: Analyze Changed Dependencies + +For each changed `package.json`, run: + +```bash +git diff master...HEAD -- +``` + +Parse the diff to identify: + +- **Added dependencies**: New entries in `dependencies`, `devDependencies`, or `peerDependencies` +- **Removed dependencies**: Deleted entries +- **Updated dependencies**: Changed version numbers +- **Moved dependencies**: Dependencies moved between types (e.g., from `devDependencies` to `peerDependencies`) + +### Step 3: Validate Consistency Across Packages + +#### 3.1 Load All Package.json Files + +Use **Glob** tool to find all `package.json` files and root `package.json`, then **Read** tool to load them. + +#### 3.2 Check Version Consistency + +For each non-workspace dependency that appears in multiple packages, verify the version is consistent: + +**Check across all dependency types:** + +- `dependencies` +- `devDependencies` +- `peerDependencies` +- Root `package.json` (both `devDependencies` and `peerDependencies`) + +**Flag inconsistencies:** + +| Scenario | Severity | Example | +| ------------------------------------------------------------------------------ | ------------ | -------------------------------------------------------------------------------------------- | +| Same dependency, different versions in different packages | **Critical** | `react: ^18.0.0` in package A, `react: ^19.0.0` in package B | +| Same dependency, different versions in different dep types within same package | **Critical** | `devDependencies: react ^19.2.4` but `peerDependencies: react ^18.0.0` (version not covered) | + +**Exceptions (do NOT flag):** + +- Workspace dependencies (`workspace:^`, `workspace:*`) - these are internal +- Peer dependency ranges that intentionally support multiple major versions (e.g., `^18.0.0 || ^19.0.0`) + +#### 3.3 Check Peer Dependency Alignment + +For each package with `peerDependencies`: + +1. **Dev dependency covers peer range**: If a peer dependency is also in `devDependencies`, verify the dev version satisfies the peer range + + ``` + โœ… Good: + devDependencies: { "react": "^19.2.4" } + peerDependencies: { "react": "^18.0.0 || ^19.0.0" } // 19.2.4 satisfies ^19.0.0 + + โŒ Critical: + devDependencies: { "react": "^19.2.4" } + peerDependencies: { "react": "^18.0.0" } // 19.2.4 does NOT satisfy ^18.0.0 + ``` + +2. **Peer dependencies consistent across packages**: Same peer dependency should have compatible ranges across all packages + + ``` + โœ… Good: + Package A peerDeps: { "react": "^18.0.0 || ^19.0.0" } + Package B peerDeps: { "react": "^18.0.0 || ^19.0.0" } + + โŒ Critical: + Package A peerDeps: { "react": "^19.0.0" } + Package B peerDeps: { "react": "^18.0.0" } // Incompatible ranges + ``` + +3. **Root peer dependencies align with packages**: Root `package.json` peer dependencies should match or be superset of package peer dependencies + +#### 3.4 Check Workspace Dependency Consistency + +For internal workspace dependencies (`@furystack/*`): + +- Verify consistent reference style: prefer `workspace:^` over `workspace:*` or bare `*` +- Flag if same workspace dependency uses different reference styles across packages + +## Output Format + +### Summary Section + +Start with a brief summary: + +``` +## Dependency Review Summary + +- **Packages with dependency changes:** [list] +- **Total dependencies added:** X +- **Total dependencies updated:** X +- **Total dependencies removed:** X +``` + +### Critical Issues (Must Fix) + +**All dependency issues are Critical.** Dependencies affect the entire monorepo and downstream consumers - inconsistencies can cause runtime failures, version conflicts, and broken builds. + +Report as **Critical Issue**: + +- Version mismatch for same dependency across packages +- Dev dependency version doesn't satisfy peer dependency range +- Inconsistent peer dependency ranges across packages +- Inconsistent workspace dependency reference style (`workspace:^` vs `*`) + +### If No Issues Found + +Simply state: "Dependency check passed - all dependencies are consistent across packages." + +## Examples + +### Critical Issue Example + +``` +## Critical Issues + +### Version Mismatch: @mui/material + +The dependency `@mui/material` has inconsistent versions: + +| Package | Type | Version | +|---------|------|---------| +| common | devDependencies | ^7.3.7 | +| frontend | devDependencies | ^7.2.0 | +| service | devDependencies | ^7.3.7 | + +**Fix:** Update all packages to use the same version (recommend: `^7.3.7`) +``` + +### Critical Issue Example: Peer Dependency Not Covered + +``` +## Critical Issues + +### Peer Dependency Not Covered by Dev Dependency + +In `common`: + +- `devDependencies`: `"react": "^19.2.4"` +- `peerDependencies`: `"react": "^18.0.0"` + +The installed dev version (19.2.4) does not satisfy the peer range (^18.0.0). + +**Fix:** Update peer dependency to `"^18.0.0 || ^19.0.0"` to cover the dev version. +``` + +## Notes + +- This reviewer focuses on **consistency validation** +- All issues are **Critical** - dependency inconsistencies affect the entire monorepo +- Workspace dependencies (`workspace:^`) are expected to vary and are not flagged for version mismatches +- Peer dependency ranges supporting multiple major versions (e.g., `^6.0.0 || ^7.0.0`) are valid and expected diff --git a/.cursor/agents/reviewer-eslint.md b/.cursor/agents/reviewer-eslint.md new file mode 100644 index 0000000..3cc928a --- /dev/null +++ b/.cursor/agents/reviewer-eslint.md @@ -0,0 +1,29 @@ +--- +name: reviewer-eslint +description: Runs ESLint checks during code reviews. Use proactively during code reviews to verify code quality and linting rules. +--- + +You are an ESLint checker for code reviews. + +## When Invoked + +**IMPORTANT:** Run `yarn lint` exactly ONCE. Do NOT re-run the command for any reason (verification, double-checking, etc.). Base your entire report on the single execution. + +1. Run `yarn lint` once and capture the output +2. Analyze the exit code and output from that single run +3. Report findings immediately - do not re-run + +## Output Format + +### If Errors Found (non-zero exit code) + +Report each error as a **Critical Issue** with: + +- File path and line number +- The rule that was violated +- The error message +- Brief suggestion on how to fix it (if obvious) + +### If No Errors (exit code 0) + +Simply state: "ESLint check passed - no linting errors found." diff --git a/.cursor/agents/reviewer-prettier.md b/.cursor/agents/reviewer-prettier.md new file mode 100644 index 0000000..cb2bade --- /dev/null +++ b/.cursor/agents/reviewer-prettier.md @@ -0,0 +1,27 @@ +--- +name: reviewer-prettier +description: Runs Prettier formatting checks during code reviews. Use proactively during code reviews to verify code formatting. +--- + +You are a Prettier formatting checker for code reviews. + +## When Invoked + +**IMPORTANT:** Run `yarn prettier:check` exactly ONCE. Do NOT re-run the command for any reason (verification, double-checking, etc.). Base your entire report on the single execution. + +1. Run `yarn prettier:check` once to check for formatting issues +2. Analyze the exit code and output from that single run +3. Report findings immediately - do not re-run + +## Output Format + +### If Errors Found + +Report each unformatted file as a **Critical Issue** with: + +- File path +- Instruction to run `yarn prettier` to fix formatting + +### If No Errors + +Simply state: "Prettier check passed - all files are properly formatted." diff --git a/.cursor/agents/reviewer-tests.md b/.cursor/agents/reviewer-tests.md new file mode 100644 index 0000000..8f951b2 --- /dev/null +++ b/.cursor/agents/reviewer-tests.md @@ -0,0 +1,112 @@ +--- +name: reviewer-tests +description: Runs unit tests and assesses test coverage during code reviews. Use proactively during code reviews to verify all tests pass and new code has adequate coverage. +--- + +You are a unit test and coverage reviewer for code reviews. + +## When Invoked + +**IMPORTANT:** Run `yarn test` exactly ONCE. Do NOT re-run the command for any reason (verification, double-checking, etc.). Base your entire report on the single execution. + +### Step 1: Run Unit Tests + +1. Run `yarn test` once to execute all unit tests +2. Analyze the exit code and output from that single run +3. If any tests fail, report them as **Critical Issues** + +### Step 2: Assess Test Coverage for Changed Code + +If tests pass, analyze whether new/changed code has adequate test coverage: + +#### 2.1 Identify Changed Source Files + +```bash +git diff master...HEAD --name-only -- "*.ts" "*.tsx" | grep -v "\.spec\." +``` + +#### 2.2 Check for Corresponding Test Files + +For each changed source file, verify a test file exists: + +- `frontend/src/foo.ts` โ†’ `frontend/src/foo.spec.ts` +- `service/src/bar.tsx` โ†’ `service/src/bar.spec.tsx` + +#### 2.3 Analyze Test Quality + +Use **Read** tool on test files to assess: + +- **Coverage of new functions/exports** - Are new public functions tested? +- **Meaningful assertions** - Tests should verify behavior, not just run without errors +- **Edge cases** - Error handling, boundary conditions, empty states +- **Mocking appropriateness** - External dependencies properly mocked + +#### 2.4 Check for False Positive Tests + +Identify tests that could pass without actually verifying correct behavior: + +- **Missing `expect.assertions()`** - Tests with assertions only inside `catch` blocks or conditional branches MUST use `expect.assertions(n)` to ensure assertions run. Without it, if the code unexpectedly resolves/doesn't throw, the test passes silently with zero assertions. + + ```typescript + // โŒ False positive risk - passes if function doesn't throw + it('should throw on error', async () => { + try { + await functionThatShouldThrow() + } catch (error) { + expect(error).toBeInstanceOf(Error) + } + }) + + // โœ… Correct - fails if assertions don't run + it('should throw on error', async () => { + expect.assertions(1) + try { + await functionThatShouldThrow() + } catch (error) { + expect(error).toBeInstanceOf(Error) + } + }) + ``` + +- **Empty catch blocks** - Tests that catch errors but don't assert on them may hide failures +- **Assertions in callbacks** - Assertions inside `.forEach()`, `.map()`, or event handlers that may not execute +- **Async assertions without await** - Promises that resolve after the test completes +- **Mocks that always return success** - Tests where mocks don't reflect realistic failure scenarios + +## Output Format + +### If Tests Fail + +Report each failing test as a **Critical Issue** with: + +- Test file path +- Test name/description that failed +- The error message or assertion that failed +- Brief suggestion on what might be wrong (if obvious from the error) + +### If Coverage Issues Found + +Report by severity: + +**Critical (Must Fix):** + +- False positive tests - tests that would pass even if the code is broken (e.g., assertions in catch blocks without `expect.assertions()`) +- New exported functions/components without any tests +- Changed logic without updated tests +- Assertions inside loops/callbacks without `expect.assertions()` + +**High Priority (Should Fix):** + +- Missing edge case coverage +- Tests that only check happy path +- Shallow assertions (e.g., only checking component renders) + +For each issue, provide: + +1. Source file and function/component name +2. What's missing +3. Suggested test case to add + +### If All Checks Pass + +Simply state: "Unit tests passed - all tests are green and coverage is adequate." diff --git a/.cursor/agents/reviewer-typescript.md b/.cursor/agents/reviewer-typescript.md new file mode 100644 index 0000000..84b63d9 --- /dev/null +++ b/.cursor/agents/reviewer-typescript.md @@ -0,0 +1,28 @@ +--- +name: reviewer-typescript +description: Runs TypeScript type checking during code reviews. Use proactively during code reviews to verify type safety. +--- + +You are a TypeScript type checker for code reviews. + +## When Invoked + +**IMPORTANT:** Run `yarn build` exactly ONCE. Do NOT re-run the command for any reason (verification, double-checking, etc.). Base your entire report on the single execution. + +1. Run `yarn build` once to check for TypeScript errors +2. Analyze the exit code and output from that single run +3. Report findings immediately - do not re-run + +## Output Format + +### If Errors Found + +Report each error as a **Critical Issue** with: + +- File path and line number +- The error message +- Brief suggestion on how to fix it (if obvious) + +### If No Errors + +Simply state: "TypeScript check passed - no type errors found." diff --git a/.cursor/agents/reviewer-versioning.md b/.cursor/agents/reviewer-versioning.md new file mode 100644 index 0000000..0f3f073 --- /dev/null +++ b/.cursor/agents/reviewer-versioning.md @@ -0,0 +1,104 @@ +--- +name: reviewer-versioning +description: Validates version bumps during code reviews. Use proactively during code reviews to verify version bumps are present and appropriate for the changes. +inputs: + - id: branch + type: currentBranch + description: The branch to review +--- + +You are a versioning validator for code reviews in an NPM monorepo using Yarn's deferred versioning. + +## When Invoked + +**IMPORTANT:** Run each command exactly ONCE. Do NOT re-run commands for verification. + +### Step 1: Run Version Check Command + +1. Run `yarn version check` once +2. If command fails โ†’ **Critical Issue** (missing version bump for changed packages) + +### Step 2: Validate Version Bump Type vs Changes + +If the command passes, analyze whether the declared version bump types match the actual changes: + +#### 2.1 Read Version Manifests + +Use **Glob** tool to find `.yarn/versions/*.yml` files, then **Read** tool to load them. + +Each manifest contains: + +```yaml +releases: + 'package-name': patch | minor | major +``` + +Identify: + +- Which packages have version bumps staged +- What type of bump (patch/minor/major) is declared for each + +#### 2.2 Analyze Branch Changes + +Run: + +```bash +git diff master...HEAD --name-only +``` + +Then use **Read** tool on changed files to understand the nature of changes: + +- **Breaking changes indicators:** + - Removed or renamed exports + - Changed function signatures (parameters added/removed/reordered) + - Changed return types + - Removed public API methods + - Changed required props in components + +- **New feature indicators:** + - New exported functions, classes, or components + - New optional parameters or props + - New configuration options + +- **Bug fix indicators:** + - Internal logic changes without API changes + - Error handling improvements + - Performance optimizations without API changes + +#### 2.3 Validate Version Type Matches Changes + +For each package with a version bump: + +| Declared Bump | Required Changes | Issue if Mismatch | +| ------------- | ------------------------------- | ------------------------------------------------------------------------------------ | +| **major** | Breaking changes must exist | Warning: "Package X has `major` bump but no breaking changes detected" | +| **minor** | New features/exports must exist | Warning: "Package Y has `minor` bump but no new exports detected - consider `patch`" | +| **patch** | Only fixes, no new public API | Warning: "Package Z has `patch` bump but adds new export `foo` - consider `minor`" | + +#### 2.4 Check for Missing Bumps + +Compare packages with changes against packages with version bumps: + +- If a package has source file changes but no version bump โ†’ Warning: "Package W was modified but has no version bump staged" +- Exclude non-source changes (tests, docs, config) from this check + +## Output Format + +### If Version Check Fails + +Report as **Critical Issue**: + +- List packages that need version bumps +- Provide command to fix: `yarn bumpVersions` (interactive) or `yarn version patch/minor/major` for specific package + +### If Version Type Mismatches Found + +Report as **Warnings**: + +- Package name and declared version type +- What was expected based on changes +- Specific files/exports that indicate the mismatch + +### If All Checks Pass + +Simply state: "Version check passed - all package versions are properly staged." diff --git a/.cursor/rules/CODE_STYLE.mdc b/.cursor/rules/CODE_STYLE.mdc index 1c5c3c7..0c60597 100644 --- a/.cursor/rules/CODE_STYLE.mdc +++ b/.cursor/rules/CODE_STYLE.mdc @@ -1,11 +1,9 @@ --- name: Code Style -description: Formatting, naming conventions, import ordering, and file organization for the Boilerplate app +description: Formatting, naming conventions, import ordering, and file organization globs: - '**/*.ts' - '**/*.tsx' - - '**/*.js' - - '**/*.jsx' alwaysApply: true --- @@ -20,10 +18,10 @@ This project uses Prettier for code formatting. **Always use the project's Prett **Configuration file:** `prettier.config.js` ```bash -# Format code +# โœ… Good - format code yarn prettier -# Check formatting +# โœ… Good - check formatting yarn prettier:check ``` @@ -34,18 +32,18 @@ This project uses ESLint for code quality. **Always use the project's ESLint con **Configuration file:** `eslint.config.js` ```bash -# Lint code +# โœ… Good - lint code yarn lint ``` ### Automated Formatting -Code is automatically formatted on commit via Husky and lint-staged: +Code is automatically formatted on commit via Husky: ```json { "lint-staged": { - "*.{ts,tsx}": ["eslint --ext .tsx,.ts --cache --fix", "prettier --write", "git add"] + "*.{ts,tsx}": ["eslint --fix", "prettier --write", "git add"] } } ``` @@ -54,212 +52,619 @@ Code is automatically formatted on commit via Husky and lint-staged: ### Files and Directories -#### Source Files +#### Component Files -- **kebab-case** for all files -- `.ts` for TypeScript, `.tsx` for JSX components -- One main export per file +- **Shades Components:** PascalCase, `.tsx` extension +- **One component per file** +- File name matches component name ``` โœ… Good: -frontend/src/components/theme-switch/index.tsx -frontend/src/services/session.ts -service/src/shutdown-handler.ts +frontend/src/components/user-profile.tsx +frontend/src/pages/dashboard.tsx +frontend/src/components/routes/settings-routes.tsx โŒ Avoid: -frontend/src/components/ThemeSwitch.tsx -frontend/src/services/Session.ts -service/src/ShutdownHandler.ts +frontend/src/components/UserProfile.tsx (should be kebab-case for files) +frontend/src/components/user_profile.tsx +frontend/src/components/components.tsx (multiple components) +``` + +#### Non-Component Files + +- **Utilities:** kebab-case, `.ts` extension +- **Services:** kebab-case with `-service` suffix, `.ts` extension +- **Types:** kebab-case, `.ts` extension +- **Constants:** kebab-case or UPPER_SNAKE_CASE, `.ts` extension +- **API Clients:** kebab-case with `-api-client` suffix, `.ts` extension + +``` +โœ… Good: +service/src/utils/format-currency.ts +frontend/src/services/session-service.ts +common/src/models/user.ts +common/src/constants/api-endpoints.ts +frontend/src/services/api-clients/media-api-client.ts + +โŒ Avoid: +service/src/utils/FormatCurrency.ts +frontend/src/services/SessionService.ts +common/src/models/User.ts ``` #### Test Files -- **Same name as source** with `.spec.ts` suffix -- Co-located with source file when possible +- **Unit Tests:** `*.spec.ts` or `*.spec.tsx` +- **E2E Tests:** `*.e2e.spec.ts` +- Co-located with the file being tested ``` โœ… Good: -service/src/config.ts -service/src/config.spec.ts +frontend/src/services/session-service.ts +frontend/src/services/session-service.spec.ts +e2e/login.e2e.spec.ts โŒ Avoid: -service/test/config.spec.ts (not co-located) -service/src/config.test.ts (use .spec.ts) +frontend/src/services/session-service-test.ts +test/session-service.spec.ts (not co-located) +``` + +#### Directory Names + +- **Component directories:** kebab-case +- **Service directories:** kebab-case +- **Module directories:** kebab-case + +``` +โœ… Good: +frontend/src/components/user-profile/ +frontend/src/pages/media-library/ +service/src/app-models/auth/ +common/src/apis/ + +โŒ Avoid: +frontend/src/components/UserProfile/ +frontend/src/components/user_profile/ +``` + +### Variables and Functions + +#### Variables + +- **camelCase** for variables +- **UPPER_SNAKE_CASE** for constants +- Descriptive names + +```typescript +// โœ… Good +const userName = 'John Doe' +const isAuthenticated = true +const userList = [] +const MAX_RETRY_COUNT = 3 +const API_BASE_URL = 'https://api.example.com' + +// โŒ Avoid +const user_name = 'John Doe' +const User = 'John Doe' +const x = true +const maxretrycount = 3 ``` -### Components and Classes +#### Functions -- **PascalCase** for components, classes, and types +- **camelCase** for functions +- Verb-based names - Descriptive and clear purpose ```typescript // โœ… Good -export const ThemeSwitch = createComponent(...) -export class SessionService {} -export type BoilerplateApi = {} +function getUserProfile() {} +function calculateTotal() {} +function validateEmail() {} +function handleSubmit() {} +function fetchUserData() {} // โŒ Avoid -export const themeSwitch = createComponent(...) // Wrong case -export class sessionService {} // Wrong case +function user() {} +function calc() {} +function validate() {} +function submit() {} +function getData() {} ``` -### Functions and Variables +#### Event Handlers -- **camelCase** for functions and variables -- **UPPER_SNAKE_CASE** for constants +- **Prefix with `handle`** for internal handlers +- **Prefix with `on`** for prop callbacks + +```typescript +// โœ… Good - Shades component with event handlers +const MyComponent = Shade({ + shadowDomName: 'my-component', + render: ({ props, injector }) => { + const handleButtonClick = () => { + // Internal logic + props.onSave?.(); + }; + + const handleFormSubmit = (ev: Event) => { + ev.preventDefault(); + // Submit logic + }; + + return ( +
+ + +
+ ); + }, +}); +``` + +#### Boolean Variables + +- Prefix with `is`, `has`, `should`, or `can` ```typescript // โœ… Good -export function getCurrentUser() {} -export const isAuthenticated = true; -export const DEFAULT_PORT = 9090; +const isAuthenticated = true +const hasPermission = false +const shouldRender = true +const canEdit = false +const isLoading = true +const hasError = false // โŒ Avoid -export function GetCurrentUser() {} -export const IsAuthenticated = true; +const authenticated = true +const permission = false +const render = true +const edit = false ``` -## Project Structure +### Shades Components + +#### Component Names + +- **PascalCase** for components +- Descriptive and specific -### Workspace Organization +```typescript +// โœ… Good +export const UserProfile = Shade({ + /* ... */ +}) +export const MediaLibrary = Shade({ + /* ... */ +}) +export const NavigationSidebar = Shade({ + /* ... */ +}) +// โŒ Avoid +export const userProfile = Shade({ + /* ... */ +}) +export const Component = Shade({ + /* ... */ +}) // Too generic +export const UP = Shade({ + /* ... */ +}) // Unclear abbreviation ``` -boilerplate/ -โ”œโ”€โ”€ common/ # Shared types and API definitions -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ index.ts # Main exports -โ”‚ โ”‚ โ”œโ”€โ”€ boilerplate-api.ts # API type definitions -โ”‚ โ”‚ โ””โ”€โ”€ models/ # Entity models -โ”‚ โ””โ”€โ”€ schemas/ # JSON schemas for validation -โ”œโ”€โ”€ frontend/ # Shades-based frontend -โ”‚ โ””โ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ index.tsx # App entry point -โ”‚ โ”œโ”€โ”€ components/ # Reusable components -โ”‚ โ”œโ”€โ”€ pages/ # Page components -โ”‚ โ””โ”€โ”€ services/ # Frontend services -โ”œโ”€โ”€ service/ # REST backend service -โ”‚ โ””โ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ service.ts # Service entry point -โ”‚ โ”œโ”€โ”€ config.ts # Configuration -โ”‚ โ””โ”€โ”€ seed.ts # Database seeding -โ””โ”€โ”€ e2e/ # End-to-end tests - โ””โ”€โ”€ page.spec.ts + +#### Component Props Types + +- **PascalCase** with `Props` suffix +- Use `type` instead of `interface` + +```typescript +// โœ… Good +type UserProfileProps = { + userId: string; + onEdit?: () => void; + onDelete?: () => void; +}; + +export const UserProfile = Shade({ + shadowDomName: 'user-profile', + render: ({ props }) => { + // Component implementation + }, +}); + +// โŒ Avoid +interface UserProfileProps { } // Use type instead +type UserProfileProperties { } // Wrong suffix +type Props { } // Not specific enough ``` -### Component File Structure +### Services and Injectables + +- **PascalCase** for class names +- **Suffix with `Service`** for services +- Use `@Injectable` decorator + +```typescript +// โœ… Good +@Injectable({ lifetime: 'singleton' }) +export class SessionService { + // Service implementation +} -Components should be organized in directories when they have assets: +@Injectable({ lifetime: 'singleton' }) +export class MediaApiClient { + // API client implementation +} +// โŒ Avoid +export class session {} // Wrong case +export class Session {} // Missing suffix +export class SessionServiceClass {} // Redundant suffix ``` -components/ -โ”œโ”€โ”€ theme-switch/ -โ”‚ โ””โ”€โ”€ index.tsx # Component with no assets -โ”œโ”€โ”€ github-logo/ -โ”‚ โ”œโ”€โ”€ index.tsx # Component -โ”‚ โ”œโ”€โ”€ gh-dark.png # Dark theme asset -โ”‚ โ””โ”€โ”€ gh-light.png # Light theme asset -โ””โ”€โ”€ header.tsx # Simple component (no directory needed) + +### TypeScript Types and Interfaces + +- **PascalCase** for types and interfaces +- **Prefer `type` over `interface`** +- Descriptive names + +```typescript +// โœ… Good +type User = { + id: string + name: string + email: string +} + +type UserWithProfile = User & { + avatar?: string + bio?: string +} + +type RequestState = + | { status: 'idle' } + | { status: 'loading' } + | { status: 'success'; data: T } + | { status: 'error'; error: string } + +// โŒ Avoid +interface User {} // Use type instead (per project guidelines) +type user = {} // Wrong case +type UserType = {} // Redundant suffix +type T = {} // Not descriptive ``` -## Import Ordering +### Enums and Constants + +- **PascalCase** for enum names +- **UPPER_SNAKE_CASE** for enum values +- **UPPER_SNAKE_CASE** for constants + +```typescript +// โœ… Good +enum UserRole { + ADMIN = 'ADMIN', + USER = 'USER', + GUEST = 'GUEST', +} + +const MAX_RETRY_COUNT = 3 +const API_BASE_URL = 'https://api.example.com' +const DEFAULT_PAGE_SIZE = 10 + +// โŒ Avoid +enum userRole {} // Wrong case +enum UserRole { + admin = 'admin', // Wrong case for values +} +const maxRetryCount = 3 // Should be UPPER_SNAKE_CASE +``` + +## File Organization Patterns + +### Component Directory Structure + +``` +frontend/src/components/user-profile/ +โ”œโ”€โ”€ user-profile.tsx # Main component +โ”œโ”€โ”€ user-profile.spec.tsx # Unit tests +โ””โ”€โ”€ components/ # Sub-components (if any) + โ”œโ”€โ”€ user-avatar.tsx + โ””โ”€โ”€ user-stats.tsx +``` + +### Project Structure + +``` +stack-craft/ +โ”œโ”€โ”€ common/ # Shared types and APIs +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ apis/ # API type definitions +โ”‚ โ”‚ โ”œโ”€โ”€ models/ # Shared models +โ”‚ โ”‚ โ””โ”€โ”€ schemas/ # Generated schemas +โ”œโ”€โ”€ frontend/ # Frontend application +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # Reusable components +โ”‚ โ”‚ โ”œโ”€โ”€ pages/ # Page components +โ”‚ โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”‚ โ””โ”€โ”€ styles/ # Global styles +โ”œโ”€โ”€ service/ # Backend service +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ app-models/ # Domain models +โ”‚ โ”‚ โ”œโ”€โ”€ actions/ # REST actions +โ”‚ โ”‚ โ””โ”€โ”€ utils/ # Utility functions +โ””โ”€โ”€ e2e/ # End-to-end tests +``` + +## Import Ordering and Grouping ### Import Order -1. **Node built-ins** -2. **External dependencies** (npm packages) -3. **FuryStack packages** (`@furystack/*`) -4. **Workspace packages** (`common`) +Group imports in the following order: + +1. **FuryStack framework imports** +2. **Third-party libraries** +3. **Common package imports** (from `common`) +4. **Absolute imports from the project** (frontend/service) 5. **Relative imports** 6. **Type imports** (if separated) ```typescript // โœ… Good - organized imports -import { EventEmitter } from 'events'; +import { Injectable, Injected } from '@furystack/inject' +import { ObservableValue } from '@furystack/utils' +import { Shade } from '@furystack/shades' + +import type { FindOptions } from '@furystack/core' +import { Cache } from '@furystack/cache' -import { createComponent, Shade } from '@furystack/shades'; -import { Injector } from '@furystack/inject'; +import type { User, Series } from 'common' -import type { BoilerplateApi } from 'common'; +import { SessionService } from '../services/session-service.js' +import { formatDate } from '../utils/format-date.js' -import { SessionService } from '../services/session.js'; -import type { UserProps } from './types.js'; +import { UserAvatar } from './components/user-avatar.js' +import { UserStats } from './components/user-stats.js' // โŒ Avoid - random ordering -import { SessionService } from '../services/session.js'; -import { EventEmitter } from 'events'; -import { Injector } from '@furystack/inject'; +import { UserStats } from './components/user-stats.js' +import { ObservableValue } from '@furystack/utils' +import type { User } from 'common' +import { SessionService } from '../services/session-service.js' ``` -### Import Grouping +### Import Grouping with Blank Lines Separate import groups with blank lines: ```typescript // โœ… Good - separated groups -import { Injector } from '@furystack/inject'; -import { getLogger, useLogging } from '@furystack/logging'; +import { Injectable } from '@furystack/inject' +import { Shade } from '@furystack/shades' + +import { Cache } from '@furystack/cache' + +import type { User } from 'common' + +import { SessionService } from '../services/session-service.js' + +import { UserAvatar } from './components/user-avatar.js' +``` + +### Named Imports + +Use named imports and group them logically: + +```typescript +// โœ… Good - grouped FuryStack imports +import { Injectable, Injected } from '@furystack/inject' +import { ObservableValue, usingAsync } from '@furystack/utils' + +// โœ… Good - alphabetically sorted when many imports +import { Cache } from '@furystack/cache' +import { getCurrentUser } from '@furystack/core' +import { getLogger } from '@furystack/logging' +import { JsonResult, RequestError } from '@furystack/rest-service' + +// โŒ Avoid - separate imports for same package +import { Injectable } from '@furystack/inject' +import { Injected } from '@furystack/inject' +``` + +## Code Organization Within Files + +### Component File Structure + +Organize Shades components with this structure: + +```typescript +// 1. Imports +import { Shade } from '@furystack/shades'; +import { ObservableValue } from '@furystack/utils'; import type { User } from 'common'; -import { SessionService } from './services/session.js'; +// 2. Types +type UserProfileProps = { + userId: string; + onEdit?: () => void; +}; + +// 3. Constants (if any) +const MAX_BIO_LENGTH = 500; + +// 4. Helper functions (if any) +const formatDate = (date: Date): string => { + // ... +}; + +// 5. Main component +export const UserProfile = Shade({ + shadowDomName: 'user-profile', + render: ({ props, injector, useObservable, useDisposable }) => { + // Services + const userService = injector.getInstance(UserService); + + // Observable subscriptions + const [user] = useObservable('user', userService.getUserById(props.userId)); + + // Local state + const isEditing = useDisposable('isEditing', () => new ObservableValue(false)); + const error = useDisposable('error', () => new ObservableValue('')); + + // Handlers + const handleEditClick = () => { + isEditing.setValue(true); + props.onEdit?.(); + }; + + // Render + return ( +
+ {/* Component content */} +
+ ); + }, +}); ``` -## Code Organization +### Service File Structure -### Service Class Structure +Organize services with this structure: ```typescript -// โœ… Good - organized service +// 1. Imports +import { Injectable, Injected } from '@furystack/inject' +import { ObservableValue } from '@furystack/utils' +import { Cache } from '@furystack/cache' + +// 2. Types +type UserData = { + id: string + name: string +} + +// 3. Service class @Injectable({ lifetime: 'singleton' }) -export class SessionService { - // 1. Injected dependencies +export class UserService { + // Injected dependencies @Injected(ApiClient) - private declare apiClient: ApiClient; + declare private apiClient: ApiClient + + // Cache instances + public userCache = new Cache({ + capacity: 100, + load: async (id: string) => { + const { result } = await this.apiClient.call({ + method: 'GET', + action: '/users/:id', + url: { id }, + query: {}, + }) + return result + }, + }) + + // Observable state + public currentUser = new ObservableValue(null) + + // Public methods + public getUser = this.userCache.get.bind(this.userCache) + public getUserAsObservable = this.userCache.getObservable.bind(this.userCache) +} +``` - // 2. Public properties - public currentUser = new ObservableValue(null); +## JSX/TSX Code Style - // 3. Constructor (if needed) - constructor() { - this.init(); - } +### JSX Formatting - // 4. Public methods - public async login(credentials: Credentials): Promise { - // Implementation - } +```tsx +// โœ… Good - multi-line for readability + - // 5. Private methods - private async init(): Promise { - // Implementation - } +// โœ… Good - single line for simple cases + - // 6. Disposal - public [Symbol.dispose](): void { - // Cleanup - } +// โŒ Avoid - hard to read + +``` + +### Conditional Rendering + +```tsx +// โœ… Good - logical AND for simple cases +{ + isLoading &&
Loading...
+} + +// โœ… Good - ternary for if-else +{ + isLoading ?
Loading...
:
Content
+} + +// โœ… Good - early return for complex cases +if (isLoading) return
Loading...
+if (error) return
Error: {error}
+return
Content
+ +// โŒ Avoid - nested ternaries +{ + isLoading ?
Loading...
: error ?
Error
:
Content
} ``` -### Component File Structure +## Comments and Documentation + +### JSDoc for Functions ```typescript -// 1. Imports -import { createComponent, Shade } from '@furystack/shades'; -import { Injected } from '@furystack/inject'; +/** + * Formats a currency value with the specified currency code + * @param value - The numeric value to format + * @param currency - The ISO currency code (e.g., 'USD', 'EUR') + * @returns Formatted currency string + */ +export const formatCurrency = (value: number, currency: string): string => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency, + }).format(value) +} +``` -// 2. Types -type MyComponentProps = { - title: string; -}; +### Component Documentation -// 3. Component -export const MyComponent = Shade({ - shadowDomName: 'my-component', +```typescript +/** + * UserProfile component displays user information and allows editing + */ +export const UserProfile = Shade({ + shadowDomName: 'user-profile', render: ({ props }) => { - return
{props.title}
; + // Component implementation }, -}); +}) +``` + +### Inline Comments + +Use inline comments sparingly and only for complex logic: + +```typescript +// โœ… Good - explaining complex logic +// Calculate exponential backoff: 1s, 2s, 4s, 8s... +const delay = Math.min(1000 * 2 ** attempt, 30000) + +// โŒ Avoid - stating the obvious +// Set count to 0 +const count = 0 ``` ## Summary @@ -267,19 +672,20 @@ export const MyComponent = Shade({ **Key Principles:** 1. **Follow project's Prettier and ESLint configs** -2. **Use consistent naming:** - - PascalCase: Components, classes, types - - camelCase: Functions, variables +2. **Use consistent naming conventions:** + - PascalCase: Components, services, types + - camelCase: Variables, functions - kebab-case: File names - UPPER_SNAKE_CASE: Constants -3. **Organize imports** by category with blank lines -4. **Co-locate tests** with source files -5. **Structure components** in directories when they have assets +3. **Organize files logically** with direct imports +4. **Order imports** by category with blank lines (FuryStack โ†’ Third-party โ†’ Common โ†’ Local) +5. **Structure components** consistently (services โ†’ observables โ†’ handlers โ†’ render) +6. **Be explicit** with TypeScript where it adds clarity +7. **Format JSX** for readability +8. **Document** complex logic and public APIs **Tools:** - Prettier: `yarn prettier` - ESLint: `yarn lint` - Type Check: `yarn build` -- Unit Tests: `yarn test:unit` -- E2E Tests: `yarn test:e2e` diff --git a/.cursor/rules/TYPESCRIPT_GUIDELINES.mdc b/.cursor/rules/TYPESCRIPT_GUIDELINES.mdc index 7713c8d..d64bd6f 100644 --- a/.cursor/rules/TYPESCRIPT_GUIDELINES.mdc +++ b/.cursor/rules/TYPESCRIPT_GUIDELINES.mdc @@ -1,6 +1,6 @@ --- name: TypeScript Guidelines -description: Type safety, strict typing, NEVER use any, and type patterns for the Boilerplate app +description: Type safety, strict typing, NEVER use any, generics, and type guards globs: - '**/*.ts' - '**/*.tsx' @@ -9,257 +9,631 @@ alwaysApply: true # TypeScript Guidelines -## Type Safety +## Type Definitions -### NEVER use `any` +### Component Props -There are no acceptable uses of `any` in this codebase: +- **Prefer `type` over `interface`** for type definitions +- Let TypeScript infer return types for simple components +- Include types in the same file as the component they belong to +- Only create separate type files for shared types used across multiple components ```typescript -// โœ… Good - use unknown for truly unknown types -export function processData(data: unknown): string { - if (typeof data === 'string') { - return data; - } - return JSON.stringify(data); -} +// โœ… Good - let TypeScript infer return type +type ButtonProps = { + variant?: 'contained' | 'outlined' | 'text'; + color?: 'primary' | 'secondary' | 'error'; + disabled?: boolean; + onclick?: () => void; +}; -// โœ… Good - use generics for flexible types -export function identity(value: T): T { - return value; -} +export const Button = Shade({ + shadowDomName: 'app-button', + render: ({ props }) => { + const variant = props.variant ?? 'contained'; + const color = props.color ?? 'primary'; -// โŒ FORBIDDEN - using any -export function processData(data: any): string { - return data.toString(); + return ( + + ); + }, +}); + +// โœ… Good - explicit return type for complex conditional logic +export const StatusBadge = Shade<{ status: string }>({ + shadowDomName: 'status-badge', + render: ({ props }): JSX.Element | null => { + switch (props.status) { + case 'active': + return
Active
; + case 'inactive': + return
Inactive
; + case 'pending': + return
Pending
; + default: + return null; // Easier to specify return type than cast in each case + } + }, +}); + +// โŒ Avoid - using interface (prefer type) +interface ButtonProps { + variant?: string; } ``` -### Explicit Types for Public APIs +### Type Organization -All exported functions and public methods should have explicit types: +- Include component-specific types in the same file as the component +- Create separate type files only for shared types used across multiple components +- Use descriptive names for types +- Group related types together ```typescript -// โœ… Good - explicit types -export async function getCurrentUser(): Promise { - // Implementation +// โœ… Good - types in source file +type UserProfileProps = { + user: User + onEdit?: (user: User) => void + onDelete?: (userId: string) => void } -export class SessionService { - public async login(credentials: LoginCredentials): Promise { - // Implementation - } +export const UserProfile = Shade({ + shadowDomName: 'user-profile', + render: ({ props }) => { + // Component implementation + }, +}) + +// โœ… Good - shared types in separate file +// common/src/models/user.ts +export type User = { + id: string + name: string + email: string + role: 'admin' | 'user' | 'guest' +} + +export type UserProfile = User & { + avatar?: string + preferences: UserPreferences } -// โŒ Avoid - implicit return types for public APIs -export async function getCurrentUser() { - // Return type is inferred - not ideal for public APIs +export type UserPreferences = { + theme: 'light' | 'dark' + language: string + notifications: boolean } ``` -## Type Definitions +## Generic Components -### Prefer `type` over `interface` +### Generic Type Patterns + +- Use generics for flexible component types +- Provide proper TypeScript constraints +- Use descriptive generic parameter names ```typescript // โœ… Good -type UserProps = { - name: string; - email: string; +type ListProps = { + items: T[]; + renderItem: (item: T, index: number) => JSX.Element; + keyExtractor: (item: T) => string; }; -type ApiResponse = { - data: T; - status: number; -}; +export const List = (props: ListProps) => Shade({ + shadowDomName: 'generic-list', + render: () => { + return ( +
    + {props.items.map((item, index) => ( +
  • + {props.renderItem(item, index)} +
  • + ))} +
+ ); + }, +}); -// โŒ Avoid -interface UserProps { - name: string; - email: string; -} +// โŒ Avoid - not generic when it should be +type ListProps = { + items: any[]; + renderItem: (item: any) => JSX.Element; +}; ``` -### Component Props Types +### Generic Constraints -Define explicit prop types for all components: +- Use constraints to limit generic types +- Provide meaningful constraints for type safety +- Use `extends` keyword for type constraints ```typescript -// โœ… Good - explicit props type -type ButtonProps = { - label: string; - onClick: () => void; - disabled?: boolean; +// โœ… Good - with constraints +type DataItem = { + id: string; + name: string; }; -export const Button = Shade({ - shadowDomName: 'app-button', - render: ({ props }) => { +type DataListProps = { + items: T[]; + onItemClick: (item: T) => void; +}; + +export const DataList = (props: DataListProps) => Shade({ + shadowDomName: 'data-list', + render: () => { return ( - +
+ {props.items.map(item => ( +
props.onItemClick(item)}> + {item.name} +
+ ))} +
); }, }); + +// โŒ Avoid - no constraints when needed +type DataListProps = { + items: T[]; + onItemClick: (item: T) => void; +}; ``` -## API Type Safety +## Type Safety -### Using the BoilerplateApi Type +### Strict Type Checking -The `common` package defines the API contract. Always import and use it: +- Enable strict TypeScript configuration +- Use proper type annotations +- **NEVER use `any` type** - There are no acceptable uses of `any` ```typescript -// โœ… Good - typed API client -import type { BoilerplateApi } from 'common'; +// โœ… Good - strict typing +const handleSubmit = (event: Event) => { + event.preventDefault() + const target = event.target as HTMLFormElement + const formData = new FormData(target) + const email = formData.get('email') as string + const password = formData.get('password') as string + + onSubmit({ email, password }) +} -const client = new RestClient({ - baseUrl: '/api', -}); +// โœ… Good - use unknown instead of any +const processData = (data: unknown) => { + if (typeof data === 'string') { + return data.toUpperCase() + } + throw new Error('Invalid data type') +} -// Type-safe API calls -const user = await client.call({ method: 'GET', action: '/currentUser' }); +// โœ… Good - use generics instead of any +const processItem = (item: T): T => { + return item +} + +// โŒ FORBIDDEN - using any +const handleSubmit = (event: any) => { + // NEVER DO THIS +} + +// โŒ FORBIDDEN - using any +const data: any = fetchData() + +// โŒ FORBIDDEN - any in function parameters +const processData = (data: any) => { + // NEVER DO THIS +} ``` -### REST Service Endpoints +### Alternatives to `any` -Use the typed API definition when creating endpoints: +When you think you need `any`, use these instead: + +1. **`unknown`** - For truly unknown types that need runtime checking +2. **Generics** - For flexible, type-safe functions +3. **Union types** - For known possible types +4. **Type guards** - For runtime type checking +5. **Utility types** - For transforming existing types ```typescript -// โœ… Good - typed REST service -import type { BoilerplateApi } from 'common'; - -useRestService({ - injector, - api: { - GET: { - '/currentUser': GetCurrentUser, - '/isAuthenticated': IsAuthenticated, - }, - POST: { - '/login': LoginAction, - '/logout': LogoutAction, - }, - }, -}); +// โœ… Good - unknown with type guard +const processValue = (value: unknown) => { + if (typeof value === 'string') { + return value.toUpperCase() + } + if (typeof value === 'number') { + return value * 2 + } + throw new Error('Unsupported type') +} + +// โœ… Good - generics +const identity = (value: T): T => { + return value +} + +// โœ… Good - union types +const processInput = (input: string | number | boolean) => { + if (typeof input === 'string') return input.toUpperCase() + if (typeof input === 'number') return input * 2 + return !input +} + +// โœ… Good - Record for object with unknown keys +const processConfig = (config: Record) => { + // Process configuration +} +``` + +### Union Types and Discriminated Unions + +- Use union types for multiple possible values +- Use discriminated unions for complex state +- Provide proper type guards + +```typescript +// โœ… Good - discriminated union +type CacheState = + | { status: 'idle' } + | { status: 'loading' } + | { status: 'loaded'; value: T } + | { status: 'error'; error: string } + +@Injectable({ lifetime: 'singleton' }) +export class DataService { + private dataState = new ObservableValue>({ status: 'idle' }) + + public async loadData() { + this.dataState.setValue({ status: 'loading' }) + try { + const data = await this.fetchData() + this.dataState.setValue({ status: 'loaded', value: data }) + } catch (error) { + this.dataState.setValue({ + status: 'error', + error: (error as Error).message, + }) + } + } +} + +// Usage with type narrowing +const state = dataState.getValue() + +if (state.status === 'loaded') { + // TypeScript knows state.value is available + console.log(state.value) +} + +// โŒ Avoid - no discriminator +type CacheState = { + loading: boolean + error?: string + data?: T +} +``` + +## Type Utilities + +### Utility Types + +- Use TypeScript utility types for common patterns +- Create custom utility types when needed +- Leverage built-in utility types + +```typescript +// โœ… Good - using utility types +type User = { + id: string + name: string + email: string + password: string + role: string +} + +// Partial for optional fields +type PartialUser = Partial + +// Pick for specific fields +type UserCredentials = Pick + +// Omit for excluding fields +type PublicUser = Omit + +// Record for object types +type UserMap = Record + +// Required for making all fields required +type RequiredUser = Required> + +// Readonly for immutable types +type ImmutableUser = Readonly +``` + +### Custom Utility Types + +- Create reusable utility types +- Use descriptive names for custom types +- Document complex utility types + +```typescript +// โœ… Good - custom utility types + +/** + * Makes all properties of T deeply partial + */ +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] +} + +/** + * Extracts the return type of an async function + */ +type AsyncReturnType Promise> = T extends ( + ...args: unknown[] +) => Promise + ? R + : never + +/** + * Makes specific properties required + */ +type RequireKeys = T & Required> + +// Usage +type UserWithRequiredEmail = RequireKeys, 'email'> ``` ## Type Guards -### Provide Type Guards for Runtime Checks +### Type Guard Functions + +- Create type guards for runtime type checking +- Use type guards to narrow types +- Provide meaningful type guard functions ```typescript -// โœ… Good - type guard -export function isUser(value: unknown): value is User { - return ( - typeof value === 'object' && - value !== null && - 'username' in value && - typeof (value as User).username === 'string' - ); +// โœ… Good - type guards +const isUser = (value: unknown): value is User => { + return typeof value === 'object' && value !== null && 'id' in value && 'name' in value && 'email' in value +} + +const isError = (value: unknown): value is Error => { + return value instanceof Error +} + +const isString = (value: unknown): value is string => { + return typeof value === 'string' +} + +const isNonNullable = (value: T | null | undefined): value is T => { + return value !== null && value !== undefined } // Usage -if (isUser(data)) { - console.log(data.username); // TypeScript knows data is User +const processData = (data: unknown) => { + if (isUser(data)) { + // TypeScript knows data is User here + console.log(data.name) + } } -``` -## Observable Types +// Filter with type guard +const validUsers = users.filter(isNonNullable) -### Type Observable Values +// โŒ Avoid - no type guard +const processData = (data: any) => { + console.log(data.name) // No type safety +} +``` -Always provide explicit types for ObservableValue: +### Array Type Guards ```typescript -// โœ… Good - explicit observable type -public currentUser = new ObservableValue(null); -public isLoading = new ObservableValue(false); -public errors = new ObservableValue([]); +// โœ… Good - array type guards +const isStringArray = (value: unknown): value is string[] => { + return Array.isArray(value) && value.every((item) => typeof item === 'string') +} -// โŒ Avoid - relying on inference for complex types -public currentUser = new ObservableValue(null); // Type is ObservableValue +const isUserArray = (value: unknown): value is User[] => { + return Array.isArray(value) && value.every(isUser) +} ``` -## Generic Patterns +## TypeScript Best Practices + +### Type Inference -### Use Descriptive Generic Names +- **Let TypeScript infer types when possible** - TypeScript's inference is powerful +- Use explicit types strategically for: + - Public APIs where you want to enforce a contract + - Complex conditional logic (switch/case) where explicit types are clearer than casting + - When inference would be ambiguous or incorrect + - When it significantly improves error messages +- Avoid unnecessary type annotations ```typescript -// โœ… Good - descriptive generic names -type ApiResponse = { - data: TData; - status: number; +// โœ… Good - let TypeScript infer +const users = ['Alice', 'Bob', 'Charlie']; // Inferred as string[] +const count = users.length; // Inferred as number +const hasUsers = users.length > 0; // Inferred as boolean + +// โœ… Good - infer component return types when straightforward +export const SimpleComponent = Shade<{ title: string }>({ + shadowDomName: 'simple-component', + render: ({ props }) => { + return
{props.title}
; // Return type inferred + }, +}); + +// โœ… Good - explicit for public APIs +export const createUser = (userData: CreateUserData): Promise => { + // Explicit return type enforces contract }; -type CacheEntry = { - value: TValue; - timestamp: number; +// โœ… Good - explicit for complex conditional logic +export const getStatusDisplay = (status: Status): JSX.Element | null => { + // Easier to specify return type than cast in each branch + switch (status) { + case 'active': + return
Active
; + case 'inactive': + return
Inactive
; + case 'pending': + return
Pending
; + default: + return null; + } }; -// โŒ Avoid - unclear generic names -type ApiResponse = { - data: T; - status: number; +// โœ… Good - explicit when inference would be wrong or ambiguous +const processUsers = (users: User[]): Map => { + return new Map(users.map(user => [user.id, user])); }; + +// โŒ Avoid - unnecessary annotations +const count: number = 5; +const name: string = 'John'; +const isActive: boolean = true; ``` -### Constrain Generics When Appropriate +### Type Assertions + +- Use type assertions sparingly +- Prefer type guards over type assertions +- Use `as const` for literal types ```typescript -// โœ… Good - constrained generic -export function getProperty( - obj: TObject, - key: TKey -): TObject[TKey] { - return obj[key]; +// โœ… Good - as const for literal types +const colors = ['red', 'green', 'blue'] as const +type Color = (typeof colors)[number] // 'red' | 'green' | 'blue' + +const config = { + apiUrl: 'https://api.example.com', + timeout: 5000, +} as const + +// โœ… Good - type assertion when necessary +const element = document.getElementById('my-element') as HTMLInputElement + +// โœ… Good - with null check +const element = document.getElementById('my-element') +if (element) { + const input = element as HTMLInputElement + input.value = 'test' } + +// โŒ Avoid - unnecessary assertion +const value = 'hello' as string + +// โŒ Avoid - dangerous assertion +const value = unknownValue as User // Prefer type guard ``` -## Strict Null Checks +### Non-Null Assertion Operator -### Handle Null and Undefined Explicitly +- Avoid using `!` operator when possible +- Prefer optional chaining and nullish coalescing +- Use type guards for null checks ```typescript -// โœ… Good - explicit null handling -export async function getUser(id: string): Promise { - const user = await fetchUser(id); - if (!user) { - return null; - } - return user; +// โœ… Good - optional chaining +const userName = user?.name ?? 'Anonymous' + +// โœ… Good - type guard +if (user) { + console.log(user.name) } -// โœ… Good - use optional chaining -const userName = user?.name ?? 'Unknown'; +// โš ๏ธ Use sparingly - non-null assertion +const element = document.getElementById('my-element')! + +// โŒ Avoid - dangerous non-null assertion +const user = maybeUser! // Can cause runtime error +``` + +## Injectable Services and DI + +### Service Type Safety -// โŒ Avoid - ignoring potential null -const userName = user.name; // Error if user is null +- Use proper typing for injected dependencies +- Leverage TypeScript's type system with dependency injection + +```typescript +// โœ… Good - properly typed service +@Injectable({ lifetime: 'singleton' }) +export class UserService { + @Injected(ApiClient) + declare private apiClient: ApiClient + + @Injected(LoggerService) + declare private logger: LoggerService + + public async getUser(id: string): Promise { + try { + const { result } = await this.apiClient.call({ + method: 'GET', + action: '/users/:id', + url: { id }, + query: {}, + }) + return result + } catch (error) { + this.logger.error({ message: 'Failed to get user', data: { id, error } }) + throw error + } + } +} + +// โŒ Avoid - untyped dependencies +@Injectable({ lifetime: 'singleton' }) +export class UserService { + private apiClient: any // Don't do this +} ``` ## Summary **Key Principles:** -1. **NEVER use `any`** - Use `unknown`, generics, or proper types -2. **Explicit types for exports** - Document the contract -3. **Prefer `type` over `interface`** -4. **Type component props** - Every Shade component should have typed props -5. **Use the API type** - Import `BoilerplateApi` from `common` -6. **Type observables** - Always provide explicit types for ObservableValue -7. **Handle nulls** - Use strict null checks and optional chaining +1. **NEVER use `any`** - Use `unknown`, generics, or union types instead +2. **Prefer `type` over `interface`** for type definitions +3. **Let TypeScript infer** when possible, be explicit when needed +4. **Use type guards** for runtime type checking +5. **Leverage utility types** for common patterns +6. **Use discriminated unions** for complex state +7. **Type all function parameters** and public APIs +8. **Use generics** for flexible, reusable code +9. **Prefer `unknown`** over `any` for truly unknown types +10. **Enable strict mode** in tsconfig.json **Type Safety Checklist:** -- [ ] No `any` types anywhere -- [ ] All exported functions have explicit return types -- [ ] Component props are typed -- [ ] API calls use the `BoilerplateApi` type -- [ ] Observable values have explicit types -- [ ] Null/undefined handled explicitly +- [ ] No `any` types anywhere in the code +- [ ] All function parameters are typed +- [ ] Public APIs have explicit return types +- [ ] Type guards are used for runtime checking +- [ ] Generic types have proper constraints +- [ ] Discriminated unions for complex state +- [ ] Utility types used where appropriate +- [ ] No dangerous type assertions **Tools:** -- Type checking: `yarn build` +- Type checking: `yarn build` or `tsc --noEmit` - Strict mode: Enabled in `tsconfig.json` diff --git a/.cursor/skills/fill-changelog/SKILL.md b/.cursor/skills/fill-changelog/SKILL.md new file mode 100644 index 0000000..1a7cd8b --- /dev/null +++ b/.cursor/skills/fill-changelog/SKILL.md @@ -0,0 +1,200 @@ +--- +name: fill-changelog +description: Fill changelog entries for the current branch. Use when the user asks to fill changelog, write changelog entries, update changelog, or prepare changelog for a PR. +--- + +# fill-changelog + +Automate filling changelog entries based on the changes in the current branch. + +## Prerequisites + +Version bumps MUST be configured before running this skill. If `yarn version check` fails, version files need to be adjusted first using `yarn version patch/minor/major` or `yarn bumpVersions` (interactive). + +## Workflow + +### Step 1: Validate Version Configuration + +Run `yarn version check` to validate version manifests exist: + +```bash +yarn version check +``` + +**If fails:** Stop and report error. The user must run `yarn version patch`, `yarn version minor`, `yarn version major`, or `yarn bumpVersions` first to configure version bumps. + +### Step 2: Create/Recreate Changelog Drafts + +Run with force flag to ensure all drafts are created or recreated: + +```bash +yarn changelog create -f +``` + +This creates files in `.yarn/changelogs/` with the pattern `{package-name}.{manifest-id}.md`. + +### Step 3: Analyze Branch Changes + +Gather information about changes: + +```bash +git diff master...HEAD --stat +git log master...HEAD --oneline +``` + +Read the changed files to understand what was actually modified. + +### Step 4: Read Changelog Drafts + +Use Glob to find `.yarn/changelogs/*.md` files, then Read to load their content. + +### Step 5: Fill Changelog Entries + +Map changes to the appropriate sections and fill the changelog drafts. + +## Section Mapping + +| Section | When to Use | +| ------------------- | ---------------------------------------------- | +| โœจ Features | New functionality, new files, new capabilities | +| ๐Ÿ› Bug Fixes | Corrections to existing behavior | +| ๐Ÿ“š Documentation | README, comments, documentation files | +| โšก Performance | Optimizations | +| โ™ป๏ธ Refactoring | Code restructuring without behavior change | +| ๐Ÿงช Tests | Test additions/modifications | +| ๐Ÿ“ฆ Build | Build system, dependencies configuration | +| ๐Ÿ‘ท CI | CI/CD configuration changes | +| โฌ†๏ธ Dependencies | Dependency updates | +| ๐Ÿ”ง Chores | Other maintenance tasks | +| ๐Ÿ’ฅ Breaking Changes | Major version only (REQUIRED) | +| ๐Ÿ—‘๏ธ Deprecated | Minor/Major versions only | + +## Quality Guidelines + +### Writing Style: Documentation, NOT Git Log + +Write for package consumers, not as git history. + +**Avoid vague terms:** + +- "improved", "updated", "refactored", "fixed bugs", "changed internal implementation" + +**Use specific, actionable language:** + +- "Added pagination support to `getUserList()` with page and limit parameters" +- "Fixed date picker not respecting user timezone in profile settings" + +### Version-Specific Requirements + +**Major Versions:** + +- Document ALL breaking changes with descriptive titles +- Explain WHAT changed and WHY +- Include before/after code examples using โŒ/โœ… markers +- Provide migration guide with step-by-step instructions +- Explain impact (who is affected) + +**Minor Versions:** + +- Document new features with descriptive titles +- Provide usage examples +- Explain benefits/use cases + +**Patch Versions:** + +- Be specific about bug fixes (not vague "fixed bugs") +- Describe what was broken + +### List Items Need Descriptions + +When listing multiple items (features, agents, tools, etc.), each item should include a brief description of what it does and why it matters: + +```markdown +// โŒ Bad - just names without context + +- Added `reviewer-changelog` +- Added `reviewer-eslint` +- Added `reviewer-prettier` + +// โœ… Good - each item explains its purpose + +- Added `reviewer-changelog` - Validates changelog entries have high-quality, descriptive content +- Added `reviewer-eslint` - Runs ESLint checks to catch linting violations automatically +- Added `reviewer-prettier` - Validates code formatting matches project standards +``` + +Even for simple list items, the reader should understand **what** the item does without needing to look elsewhere. + +## Entry Format Examples + +### Simple List (straightforward changes) + +```markdown +## โœจ Features + +- Added `exportToCSV()` function for data export +- Implemented multi-select filtering in list views +``` + +### Detailed Entry (complex changes) + +```markdown +## โœจ Features + +### New Data Export Feature + +Export data in multiple formats for analysis and reporting. + +**Usage:** + +\`\`\`typescript +import { exportData } from 'common'; + +const result = await exportData(items, { format: 'csv' }); +\`\`\` +``` + +### Breaking Changes (major versions) + +```markdown +## ๐Ÿ’ฅ Breaking Changes + +### API Methods Now Use Object Parameters + +Methods now accept object parameters instead of positional arguments. + +**Examples:** + +\`\`\`typescript +// โŒ Before +await injector.getInstance(MyService, arg1, arg2); + +// โœ… After +await injector.getInstance(MyService, { arg1, arg2 }); +\`\`\` + +**Impact:** All callers of the affected methods need to be updated. + +**Migration:** Search for method calls with `grep -r "getInstance"` and update to object syntax. +``` + +## Validation + +After filling entries, run: + +```bash +yarn changelog check +``` + +This validates: + +- Every release has a changelog file +- Major releases have filled "๐Ÿ’ฅ Breaking Changes" section +- At least one section has content +- Version type matches the manifest + +## Reference + +For detailed guidelines, see: + +- `.cursor/agents/reviewer-changelog.md` (will review the filled entries) diff --git a/.cursor/skills/review-changes/SKILL.md b/.cursor/skills/review-changes/SKILL.md new file mode 100644 index 0000000..262060b --- /dev/null +++ b/.cursor/skills/review-changes/SKILL.md @@ -0,0 +1,194 @@ +--- +name: review-changes +description: Review all changes on the current branch. Use when the user asks for a code review or PR review. +disable-model-invocation: false +inputs: + - id: branch + type: currentBranch + description: The branch to review +--- + +# review-changes + +Review all changes on the current branch compared to the upstream branch. + +## Context + +{{branch}} + +## Determining the Base Branch + +**IMPORTANT:** This repository uses `master` as the integration branch. + +1. **Use `master` as the default base branch** for all comparisons: + + ```bash + git diff origin/master...HEAD + ``` + +2. **Verify by checking GitHub workflows** if unsure - look at `.github/workflows/*.yml` for `pull_request.branches` targets + +3. **Use the correct git diff syntax:** + - `origin/master...HEAD` - shows changes in current branch since it diverged from master + - Run `git log --oneline origin/master..HEAD` to see commits being reviewed + +## Pre-flight: Detect Change Types + +Before launching subagents, analyze what files changed: + +```bash +git diff origin/master...HEAD --name-only +``` + +**Conservative skip rules (only skip when 100% safe):** + +| Reviewer | Run | Notes | +| ----------------------- | ----------- | ------------------------------------------- | +| `reviewer-prettier` | โœ… Always | Formats `.md`, `.json`, `.ts`, `.tsx`, etc. | +| `reviewer-typescript` | Conditional | Skip ONLY if NO `.ts`/`.tsx` files changed | +| `reviewer-eslint` | Conditional | Skip ONLY if NO `.ts`/`.tsx` files changed | +| `reviewer-tests` | Conditional | Skip ONLY if NO `.ts`/`.tsx` files changed | +| `reviewer-dependencies` | Conditional | Run if ANY `package.json` changed | + +**When in doubt, run the check.** Fast failures are better than missed issues. + +## Execution Strategy + +**IMPORTANT:** Launch ALL applicable subagents in a SINGLE parallel batch. +Do NOT wait for one group to finish before starting another. + +In one tool call batch, launch all applicable reviewers: + +- `reviewer-prettier` (always) +- `reviewer-typescript` (if `.ts`/`.tsx` files changed) +- `reviewer-eslint` (if `.ts`/`.tsx` files changed) +- `reviewer-tests` (if `.ts`/`.tsx` files changed) +- `reviewer-dependencies` (if any `package.json` changed) + +## Analysis Required + +Check for: + +**Code Quality & Bugs:** + +- Potential bugs, edge cases, or runtime errors +- Code smells, anti-patterns, or violations of repository rules (check `.cursor/rules/*`) +- Newly added TODO/FIXME comments that should be addressed +- Suspicious or unclear code changes +- Business logic changes that need scrutiny + +**Standards & Compliance:** + +- Delegate to `reviewer-typescript` subagent to check for TypeScript errors +- Delegate to `reviewer-eslint` subagent to check for linting violations +- Delegate to `reviewer-prettier` subagent to check for formatting issues +- Breaking changes to public API surface +- Type definition changes + +**Shades Styling Patterns:** + +- Flag `useState()` used only for CSS-representable states (hover, focus, active) +- Recommend using `css` property with pseudo-selectors instead: + + ```typescript + // โŒ Anti-pattern to flag + const [isHovered, setIsHovered] = useState('hover', false) +
setIsHovered(true)} style={{ opacity: isHovered ? 1 : 0.7 }} /> + + // โœ… Recommend instead + css: { opacity: '0.7', '&:hover': { opacity: '1' } } + ``` + +- Static `style` props in Shade definitions should use `css` instead + +**Testing & Coverage:** + +- Delegate to `reviewer-tests` subagent to run unit tests and assess coverage +- Missing test coverage for new/changed public APIs +- Test quality and edge case coverage + +**Performance & Security:** + +- Performance concerns (memory leaks, missing disposal) +- Security vulnerabilities or data exposure + +**Dependencies:** + +- Delegate to `reviewer-dependencies` subagent to validate dependency consistency + +**Documentation:** + +- Missing or outdated documentation for public APIs +- README updates for new features + +## Subagent Output Rules + +**IMPORTANT:** Only include subagent results in the final output if they found errors or issues. + +- If `reviewer-typescript` passes โ†’ Do NOT mention it in the output +- If `reviewer-eslint` passes โ†’ Do NOT mention it in the output +- If `reviewer-prettier` passes โ†’ Do NOT mention it in the output +- If `reviewer-tests` passes โ†’ Do NOT mention it in the output +- If `reviewer-dependencies` passes โ†’ Do NOT mention it in the output + +Only report subagent findings when they detect actual problems. + +## Output Format + +**1. Summary:** Brief overview of changes (2-3 sentences max) + +**2. Issues by Priority:** + +- ๐Ÿ’€ **Critical:** Must fix before merge +- ๐Ÿ”ฅ **High:** Should fix before merge +- ๐Ÿค” **Medium:** Consider addressing +- ๐Ÿ’š **Low:** Nice to have + +For each issue, be specific: package, file, line, problem, suggested fix. + +**3. Test Coverage:** Assess coverage quality. Warn if public APIs lack tests. + +**4. Breaking Changes:** List any breaking changes and suggest migration guide if needed. + +**5. Changelog:** Generate a short, consistent changelog as a copyable markdown code block. + +Format: + +- Use present tense, imperative mood (e.g., "Add", "Fix", "Update", "Remove") +- One line per logical change (max 5-7 lines) +- Group by type if multiple changes: Features, Fixes, Refactors +- No verbose descriptions - keep each line under 80 characters + +Example: + +``` +- Add user profile validation +- Fix observable memory leak in cache +- Update dependency injection patterns +``` + +**6. Pull Request Description:** Generate as a copyable markdown code block with: + +- Relevant emoji per header +- Brief description of what the PR does +- **Remaining Tasks** checklist generated from found issues (if any), grouped by priority: + +```markdown +## ๐Ÿ“‹ Remaining Tasks + +### ๐Ÿ’€ Critical + +- [ ] Fix TypeScript error in `frontend/src/foo.ts:42` + +### ๐Ÿ”ฅ High + +- [ ] Add missing test for `handleSubmit` function + +### ๐Ÿค” Medium + +- [ ] Address memory leak in observable subscription +``` + +Omit empty priority sections. If no issues found, omit the entire Remaining Tasks section. + +**Style:** Be critical, specific, and concise. Focus on code quality and API surface area. If unsure, ask for clarification. diff --git a/.yarn/versions/5e7c1747.yml b/.yarn/versions/5e7c1747.yml new file mode 100644 index 0000000..e27b4cb --- /dev/null +++ b/.yarn/versions/5e7c1747.yml @@ -0,0 +1,2 @@ +releases: + furystack-boilerplate-app: patch