- Node.js 18+
- npm
git clone <repo-url>
cd script-classification
npm install
cp .env.example .env # fill in values or leave blank to use dev auth
npm run devWith no Auth0 credentials, the app automatically uses dev auth mode. See auth.md for details.
- Create a branch from
main:git checkout -b feat/<short-description> - Make focused, atomic commits.
- Open a PR against
mainwhen the work is ready for review. - CI must pass before merging.
Use the conventional commit format:
<type>(<scope>): <short summary>
| Type | When to use |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Code change with no behaviour change |
style |
Formatting, whitespace |
chore |
Build, deps, tooling |
docs |
Documentation only |
Examples:
feat(workspace): add reviewer badge to classification panel
fix(auth): redirect pending users before loading dashboard
chore(deps): bump tanstack-query to 5.90
- Prefer
interfaceovertypefor object shapes. - Avoid
any. Useunknownand narrow with type guards. - Export types from the feature's
index.tsbarrel, not from deep paths. - Zod schemas live in
src/schema/. Derive TypeScript types from them withz.infer<>.
- One component per file, named with
kebab-case.tsx. - Props interface is defined in the same file, named
<ComponentName>Props. - Use
useCallbackfor event handlers passed as props to avoid re-renders. - Prefer composition over long prop lists. If a component grows past ~200 lines, split it.
interface BatchItemProps {
batch: Batch
onSelect: (id: string) => void
}
export function BatchItem({ batch, onSelect }: BatchItemProps) { ... }- Prefix with
use-, file name matches:use-script-styles.ts. - A hook that wraps a TanStack Query call lives in
features/<domain>/api/alongside the query file. - A hook that manages local UI state lives in
features/<domain>/hooks/.
Each endpoint gets its own file under features/<domain>/api/<resource>/. The file exports exactly one hook (query or mutation). Query key factories are in a co-located <resource>-keys.ts file.
// batch-keys.ts
export const batchKeys = {
all: ['batches'] as const,
lists: () => [...batchKeys.all, 'list'] as const,
report: (id: string) => [...batchKeys.all, 'report', id] as const,
}- Use Tailwind utility classes exclusively. No inline
styleprops. - Use
cn()fromsrc/lib/utils.tsto merge conditional classes. - Colours and spacing come from the Tailwind config / CSS variables — do not hardcode hex values.
- shadcn/ui components live in
src/components/ui/. Do not modify them to add feature-specific logic; wrap them instead.
- Server data → TanStack Query. Never put API responses in Zustand.
- Ephemeral UI state (open/closed, loading flags) → local
useState. - Persisted UI preferences (theme, language, font) →
useUIStore.
Use React Hook Form with a Zod schema resolver:
const form = useForm<BatchUploadForm>({
resolver: zodResolver(batchUploadSchema),
})Schemas live in src/schema/. Keep validation logic out of components.
- API errors bubble up through TanStack Query's
onErrorcallbacks. - Show user-facing feedback via
useUIStore().addToast(...). - Wrap page-level subtrees in
ErrorBoundary(fromsrc/components/common) for unexpected errors.
- Every user-visible string must come from i18next:
const { t } = useTranslation('<namespace>'). - Translation keys go in both
src/locales/en/andsrc/locales/bo/. - Namespaces:
common,auth,dashboard,workspace,admin.
npm run lintThe project uses ESLint with the react-hooks and react-refresh plugins. Fix all lint errors before pushing. There is no auto-formatter configured — maintain consistency with the surrounding code.
- Create
src/features/<name>/with subfolders:api/,components/,hooks/,index.ts. - Add translation keys to all locale files.
- Register routes in
src/routes/app-routes.tsxwith the appropriateProtectedRoutewrapper. - Export public API from
src/features/<name>/index.ts.