Canonical source for all code conventions in this repository. CONTRIBUTING.md, CLAUDE.md, GEMINI.md, and
.github/copilot-instructions.mdreference this file. Full detail is in docs/development/CODING_STANDARDS.md.Project: Interact — Employee Engagement & Gamification Platform Last Updated: 2026-03-16
- Naming Conventions
- File Organization
- Import Ordering
- Component Patterns
- Error Handling
- Testing Conventions
- API / Data Fetching
- Database / Entity Conventions
- Git Conventions
- Comment Conventions
- Security Rules
| Artifact | Convention | Example |
|---|---|---|
| React component files | PascalCase | ActivityCard.jsx, UserProfile.jsx |
| Multi-word component files | PascalCase (no separator) | UserProfileCard.jsx |
| Custom hooks | use prefix + camelCase |
useActivityData.js, useMobile.js |
| Context files | PascalCase + Context suffix |
AuthContext.jsx |
| Page components | PascalCase | Dashboard.jsx, ActivityDetail.jsx |
| Utility/helper files | camelCase | imageUtils.js, formatDate.js |
| Constants | UPPER_SNAKE_CASE | API_BASE_URL, MAX_RETRY_COUNT |
| Frontend env vars | UPPER_SNAKE_CASE + VITE_ prefix |
VITE_BASE44_APP_ID |
| Backend env vars | UPPER_SNAKE_CASE (no prefix) | OPENAI_API_KEY |
| TypeScript interfaces | PascalCase | User, ActivityItem |
| TypeScript types | PascalCase | ButtonVariant, ThemeMode |
| Boolean variables | is/has/can/should prefix |
isLoading, hasError, canEdit |
| Event handlers | handle prefix |
handleSubmit, handleCardClick |
| Backend Deno functions | camelCase | awardBadgeForActivity.ts |
| Test files | Same name + .test.js/.test.jsx |
ActivityCard.test.jsx |
| CSS class names | Tailwind utilities (kebab-case for custom) | user-avatar, card-header |
src/
pages/ ← Route-level page components (PascalCase.jsx)
components/
ui/ ← Radix UI / shadcn primitives only
common/ ← Shared utility components (ErrorBoundary, PageHeader)
[feature]/ ← Feature-specific components (activities/, gamification/, etc.)
docs/ ← Auto-generated (DO NOT EDIT)
api/ ← API client setup only
lib/ ← Utilities, contexts, helpers
hooks/ ← Custom React hooks (use*.js/jsx)
utils/ ← Pure utility functions (no React)
test/ ← Test setup, fixtures, shared test utilities
functions/ ← Deno/TypeScript serverless functions (camelCase.ts)
docs/ ← Structured documentation (do not put random files here)
- Create
src/pages/NewPage.jsxusing an existing page as a template src/pages.config.jsis auto-generated by Base44 — do not edit it manually; the page is auto-registered when you create the file insrc/pages/- Add the route to
src/App.jsx - New shared UI primitives →
src/components/ui/ - New feature components →
src/components/[feature-name]/ - New backend logic →
functions/[actionName].ts - New hooks →
src/hooks/use[Name].js - New utilities →
src/utils/[name].js
- 3 levels deep maximum for component directories
- No index.js barrel files (import directly from component files)
Follow this order within each file (enforced by eslint-plugin-unused-imports):
// 1. React core
import { useState, useEffect, useCallback } from 'react';
// 2. Third-party libraries
import { useQuery } from '@tanstack/react-query';
import { motion } from 'framer-motion';
// 3. UI components (Radix / shadcn)
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
// 4. Feature components
import ActivityCard from '@/components/activities/ActivityCard';
// 5. Utilities and helpers
import { cn } from '@/lib/utils';
import { base44 } from '@/api/base44Client';
// 6. Icons
import { Check, X, Star } from 'lucide-react';// Order: imports → constants → hooks (ALL at top level) → handlers → JSX
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
export default function ComponentName({ prop1, prop2 = 'default', className }) {
// 1. State — all hooks at top level, never conditional
const [state, setState] = useState(null);
// 2. Effects
useEffect(() => {
// side effects
}, [dependencies]);
// 3. Event handlers
const handleAction = async () => {
try {
// action logic
toast.success('Success');
} catch (error) {
console.error('Action failed:', error);
toast.error('Something went wrong');
}
};
// 4. JSX
return (
<div className={cn('p-4', className)}>
{/* content */}
</div>
);
}- ✅ Call hooks at the top level of the component, before any conditionals or returns
- ❌ Never call hooks inside
if,for,while, or nested functions - ❌ Never call hooks after an early return
- ✅ Custom hooks must start with
use(e.g.,useActivityData) - Run
npm run lintto catchreact-hooks/rules-of-hooksviolations
- Only TailwindCSS utility classes — no inline styles, no separate CSS modules
- Use
cn()from@/lib/utilsfor conditional class composition:import { cn } from '@/lib/utils'; <div className={cn('base-class', condition && 'conditional-class', className)} />
- Mobile-first breakpoints:
sm:,md:,lg:,xl: - Radix UI components from
src/components/ui/for interactive primitives
const handleAction = async () => {
try {
const result = await someApiCall();
toast.success('Operation completed');
} catch (error) {
// 1. Log detailed error internally
console.error('Operation failed:', error);
// 2. Show generic message to user (never expose stack traces or internal paths)
toast.error('Something went wrong. Please try again.');
}
};const mutation = useMutation({
mutationFn: async (data) => base44.entities.Activity.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['activities'] });
toast.success('Activity created!');
},
onError: (error) => {
console.error('Create failed:', error);
toast.error('Failed to create activity');
},
});<ErrorBoundary>fromsrc/components/common/ErrorBoundary.jsxwraps the root app- Wrap heavy page routes with their own
<ErrorBoundary>for isolated failures
try {
// business logic
} catch (error) {
console.error('Function error:', error);
// Generic message in response, never expose internals
return Response.json({ error: 'Internal server error' }, { status: 500 });
}- Test files co-located with the file under test:
ComponentName.test.jsx - Integration/utility tests:
src/test/[name].test.js - Backend function tests:
functions/tests/[name].test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
describe('ComponentName', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders correctly with required props', () => {
// Arrange
const props = { name: 'Test Activity' };
// Act
render(<ComponentName {...props} />);
// Assert
expect(screen.getByText('Test Activity')).toBeInTheDocument();
});
it('calls handler when button clicked', async () => {
// ...
});
});Components using TanStack Query need a QueryClientProvider in tests:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const createWrapper = () => {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
render(<MyComponent />, { wrapper: createWrapper() });- Mock external API calls (
vi.spyOn(base44.entities.Activity, 'list')) - Use
vi.fn()for callback props - Use
createWrapper()utility fromsrc/test/utils.js - Do not mock React internal hooks
- New utilities and hooks: 80% minimum
- New components: test all user interactions
- Global target: 30% by Q2 2026
// Query
const { data, isLoading, error } = useQuery({
queryKey: ['activities'], // hierarchical key array
queryFn: () => base44.entities.Activity.list(),
staleTime: 5 * 60 * 1000, // 5 minutes (default)
});
// Mutation
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data) => base44.entities.Activity.create(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['activities'] }),
});['resource'] ← all items
['resource', id] ← single item
['resource', { filter, sort }] ← filtered collection
['resource', id, 'subresource'] ← nested resource
import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';
Deno.serve(async (req) => {
try {
const base44 = createClientFromRequest(req);
const user = await base44.auth.me();
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
const { param1 } = await req.json();
if (!param1) return Response.json({ error: 'Missing param1' }, { status: 400 });
const result = await base44.entities.SomeEntity.create({ ...data });
return Response.json({ success: true, data: result });
} catch (error) {
console.error('Function error:', error);
return Response.json({ error: 'Internal server error' }, { status: 500 });
}
});// List all
await base44.entities.Activity.list();
// Filtered
await base44.entities.Activity.filter({ status: 'active', created_by: user.email });
// Single
await base44.entities.Activity.get('entity-id');
// Create
await base44.entities.Activity.create({ name, type, created_by: user.email });
// Update
await base44.entities.Activity.update('entity-id', { status: 'completed' });
// Delete
await base44.entities.Activity.delete('entity-id');// Bypasses user permissions — use only in trusted backend functions
await base44.asServiceRole.entities.UserPoints.update(id, { total_points: newTotal });Always use ISO 8601 strings:
created_at: new Date().toISOString() // "2026-03-16T00:44:06.984Z"feat/short-description ← new feature
fix/short-description ← bug fix
chore/short-description ← maintenance, dependencies, tooling
docs/short-description ← documentation only
refactor/short-description ← code restructuring
security/short-description ← security fix
test/short-description ← tests only
<type>(<scope>): <description>
feat(gamification): add streak multiplier for consecutive logins
fix(auth): resolve token refresh race condition
chore(deps): upgrade react-router-dom to fix XSS vulnerability
docs(adr): add ADR-010 for notification service design
test(hooks): add tests for useActivityData hook
Types: feat, fix, chore, docs, refactor, security, test, perf
- Squash merge for feature branches into
main - No force pushes to
mainordevelop - Branch must be up to date with
mainbefore merging (CI enforced)
v1.0.0 ← semantic version (MAJOR.MINOR.PATCH)
v1.0.0-beta.1 ← pre-release
- Do: complex algorithm logic, non-obvious business rules, temporary workarounds
- Don't: obvious code, restating what the code does in plain English
// TODO: [Sentry] Implement Sentry integration for production error tracking
// FIXME: [AUTH-123] Token refresh does not handle concurrent requests
// HACK: Temporary workaround until Base44 adds batch operations — remove after migrationFormat: // TODO: [context] Description of what needs to be done
/**
* Optimizes a Cloudinary image URL with the given transformation options.
* @param {string} url - Original Cloudinary URL
* @param {{ width?: number, quality?: number }} options - Transform options
* @returns {string} Transformed URL
*/
export function optimizeImageUrl(url, { width = 800, quality = 80 } = {}) {- Never hardcode secrets — all via environment variables
- Never put secrets in
VITE_variables (they are browser-exposed) - Always call
base44.auth.me()at the start of every Deno function - Always validate inputs in backend functions (Zod or manual)
- Always use DOMPurify before
dangerouslySetInnerHTML:import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
- Strip sensitive fields before returning API responses
- Generic error messages to users; detailed logs to console only
- Run
npm auditbefore every release — fix all high/critical issues first
- Full coding standards: docs/development/CODING_STANDARDS.md
- Contributing workflow: CONTRIBUTING.md
- Security policy: SECURITY.md
- Agent context: CLAUDE.md, GEMINI.md
- AI agent rules: .github/copilot-instructions.md