Quality Gate
All five checks must pass before any commit.
Run these commands before committing:
npm run lint # ESLint
npm run type-check # tsc --noEmit
npm run pretty:check # Prettier check
npm run knip # Dead code detection
npm test # Jest
If any check fails, fix the issues and verify again. Only claim what you actually ran.
Linting
Run ESLint
ESLint checks for:
Code style violations
Unused imports
Common programming errors
React/React Native best practices
Configuration
ESLint is configured with:
eslint-config-expo - Expo’s recommended config
eslint-config-prettier - Disables rules that conflict with Prettier
eslint-plugin-prettier - Runs Prettier as an ESLint rule
eslint-plugin-unused-imports - Detects and removes unused imports
The linter will auto-fix many issues. Check the output for remaining manual fixes.
Type Checking
Run TypeScript Compiler
This runs tsc --noEmit to check TypeScript types without generating output files.
Common Type Errors
Missing imports from coco-cashu-core
Always import types from coco-cashu-core, never redefine them. // ✅ Correct
import { Proof , MintKeys } from 'coco-cashu-core' ;
// ❌ Wrong - don't redefine coco types
type Proof = { amount : number ; secret : string };
Importing from @cashu/cashu-ts
Import from coco, not the underlying @cashu/cashu-ts library. // ✅ Correct
import { Proof } from 'coco-cashu-core' ;
// ❌ Wrong
import { Proof } from '@cashu/cashu-ts' ;
Check .cursor/rules/zustand-store-scoping.mdc for store type patterns and scoping rules.
Prettier formats:
JavaScript/TypeScript files (.js, .jsx, .ts, .tsx)
JSON files
Module formats (.mjs, .cjs)
Configuration
Prettier is configured with:
prettier-plugin-tailwindcss - Automatically sorts Tailwind classes
Most editors can auto-format on save. Configure your editor to run Prettier automatically.
Dead Code Detection
Run Knip
Knip finds:
Unused exports
Unused dependencies in package.json
Unreferenced files
Duplicate exports
What to do with Knip results
Review the output
Knip will list unused exports, files, and dependencies.
Verify before deleting
Some exports may be used by external tools or in ways Knip can’t detect. Verify before removing.
Remove dead code
Delete genuinely unused code. Keep the codebase lean.
Testing
Run Tests
This runs the Jest test suite.
Test Configuration
Tests use:
jest - Testing framework
jest-expo - Expo-specific Jest preset
@types/jest - TypeScript definitions for Jest
Writing Tests
Unit Test Example
Component Test Example
import { renderHook } from '@testing-library/react-hooks' ;
import { useProcessPaymentString } from '@/hooks/coco/useProcessPaymentString' ;
describe ( 'useProcessPaymentString' , () => {
it ( 'should parse a Cashu token' , () => {
const { result } = renderHook (() => useProcessPaymentString ());
const parsed = result . current . parse ( 'cashuAey...' );
expect ( parsed . type ). toBe ( 'token' );
expect ( parsed . token ). toBeDefined ();
});
});
Styling Standards
Use Semantic Tokens
Prefer semantic tokens over raw hex colors.
// ✅ Correct - semantic tokens
className = "bg-surface-secondary text-foreground"
// ❌ Wrong - raw hex colors
style = {{ backgroundColor : '#1a1a1a' , color : '#ffffff' }}
Runtime Color Values
Use useThemeColor for dynamic color access:
import { useThemeColor } from '@/hooks/useThemeColor' ;
const MyComponent = () => {
const dangerColor = useThemeColor ( 'danger' );
const [ primary , secondary ] = useThemeColor ([ 'primary' , 'secondary' ]);
return < View style ={{ borderColor : dangerColor }} />;
};
For multiple colors, use the array form to reduce hooks.
Prefer className Over style
// ✅ Correct - Tailwind className
< View className = "flex-1 bg-surface p-4" />
// ❌ Avoid - inline styles (use only when dynamic)
< View style = {{ flex : 1 , backgroundColor : '#fff' , padding : 16 }} />
Stable keyExtractor
Stable renderItem
// ✅ Correct - stable function reference
const keyExtractor = useCallback (( item : Transaction ) => item . id , []);
< FlatList
data = { transactions }
keyExtractor = { keyExtractor }
renderItem = { renderItem }
/>
Avoid Unnecessary Memoization
When to use useMemo
When to use useCallback
// ✅ Good - expensive computation
const sortedItems = useMemo (
() => items . sort (( a , b ) => b . timestamp - a . timestamp ),
[ items ]
);
// ❌ Unnecessary - simple value
const fullName = useMemo (() => ` ${ first } ${ last } ` , [ first , last ]);
Loading States
Use HeroUI Components
import { Spinner } from 'heroui-native' ;
import { Text } from '@/components/ui/Text' ;
// ✅ Correct - HeroUI Spinner
{ isLoading && < Spinner />}
// ✅ Correct - Text auto-skeleton (nullish children)
< Text >{ userName } </ Text > { /* Shows skeleton when userName is null/undefined */ }
Skeletons must not cause layout shift. Match final dimensions and preserve container structure.
Skeleton Best Practices
// ✅ Correct - auto-skeleton with nullish children
< Text className = "text-xl" > { balance } </ Text >
// ❌ Wrong - separate Skeleton wrapper causes layout shift
{ isLoading ? (
< Skeleton height = { 24 } width = { 100 } />
) : (
< Text className = "text-xl" > { balance } </ Text >
)}
No JSDoc that restates TypeScript types.
When to add JSDoc
Add JSDoc only for:
Intent (why this exists)
Constraints (limits, edge cases)
UX rules (user-facing behavior)
Async contracts (timing, dependencies)
Invariants (must-hold conditions)
Good JSDoc
Bad JSDoc - restates types
/**
* Processes a payment string (token, invoice, payment request).
* Returns null if the string is invalid or not a known payment type.
*
* Note: This hook debounces input to avoid excessive parsing.
*/
export function useProcessPaymentString () {
// ...
}
Quick Reference
Command Purpose npm run lintRun ESLint npm run type-checkRun TypeScript type checking npm run prettyFormat code with Prettier npm run pretty:checkCheck formatting without writing npm run knipFind unused exports/dependencies npm testRun Jest tests
All five must pass before committing: lint, type-check, pretty:check, knip, test.