Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

How Do I…?

Quick-reference task router for AI agents and new developers. Each entry lists the exact files to touch (in order) and links to deeper docs.

Routes & Navigation

Add a new route

  1. Create your page component in src/features/[feature]/components/pages/
  2. Add a route entry in the appropriate domain file under src/routes/config/:
    • Grant-related: src/routes/config/grantDomainRoutes.tsx
    • Operations (finance, docs, journals, users): src/routes/config/operationsRoutes.tsx
    • Platform/admin: src/routes/config/platformLegacyRoutes.tsx
    • Public/core: src/routes/config/publicCoreRoutes.tsx
  3. Use createLazyRoute() from src/routes/routeLazy.tsx for code splitting:
    {
      path: 'my-page',
      lazy: createLazyRoute(
        () => import('@/features/myFeature/components/pages/MyPage'),
        (mod) => mod.MyPage ?? mod.default,
      ),
    }
    
  4. Optionally wrap with withPageGuard() for permission-gated routes
See: Frontend Architecture

Add a sidebar menu item

  1. Edit src/components/layout/navConfig.ts
  2. Add an entry to the appropriate section with path, label, icon (from lucide-react), and optional permission
See: Menu Structure

Features

Create a new feature module

  1. Create folder: src/features/[name]/
  2. Create subfolders: components/, hooks/, services/, types/, constants/
  3. Create barrel export: src/features/[name]/index.ts
  4. Create context: src/features/[name]/[Name]Context.tsx
  5. Create service: src/features/[name]/services/[Name]Service.ts (extend BaseService)
  6. Register modals: src/features/[name]/modals.registry.ts
  7. Add route (see “Add a new route” above)
  8. Add nav item (see “Add a sidebar menu item” above)
Canonical example: src/features/expenses/ See: Feature Checklist

Add a new service

  1. Create src/features/[feature]/services/[Name]Service.ts
  2. Extend BaseService<T> from src/core/BaseService.ts
  3. Set protected collectionName and protected serviceName
  4. Return ServiceResult<T> from all operations
  5. Use this.withErrorBoundary() for error handling
  6. Use this.validateInput() for input validation
  7. Use this.logSuccess() for audit logging
Reference: src/features/expenses/services/ExpenseService.ts See: Service Patterns

Add EventBus events

  1. Define event type in @grantmaster/shared/events (SystemEventType enum)
  2. Add payload to EventPayloadMap
  3. Emit from service (never from components):
    eventBus.emit(SystemEventType.EXPENSE_APPROVED, {
      entityId: expense.id,
      severity: EventSeverity.INFO,
      // ...payload
    });
    
  4. Consume in services that need to react
Reference: src/features/expenses/services/ExpenseWorkflowService.ts See: Base Service and EventBus

UI

Create a new page

import { PageLayout } from '@/components/ui/PageLayout';
import { PageHeader } from '@/components/ui/PageHeader';

export function MyPage() {
  return (
    <PageLayout>
      <PageHeader title="My Page" description="Description here" />
      {/* page content */}
    </PageLayout>
  );
}
For tabbed pages, add PageTabs — render content externally, not via the tab content prop:
import { PageTabs } from '@/components/ui/PageTabs';

const tabs = [
  { id: 'overview', label: 'Overview' },
  { id: 'details', label: 'Details' },
];

<PageTabs tabs={tabs} activeTab={activeTab} onTabChange={setActiveTab} />
{activeTab === 'overview' && <OverviewContent />}
{activeTab === 'details' && <DetailsContent />}
See: Component Cookbook

Add a modal

  1. Create modal component in src/features/[feature]/components/
  2. Register in src/features/[feature]/modals.registry.ts:
    import type { ModalDef } from '@/components/modals/modal.types';
    import { Permission } from '@/shared/auth/contracts';
    
    export const myModals: ModalDef[] = [
      {
        id: 'my-modal',
        component: () => import('./components/MyModal'),
        select: (mod) => mod.MyModal ?? mod.default,
        size: 'lg',
        permissions: [Permission.SOME_PERMISSION],
        variant: 'modal',
        scope: 'feature',
      },
    ];
    
See: Modal System, Modals Inventory

Add a secondary sidebar

import { SecondarySidebar } from '@/components/ui/SecondarySidebar';

<div className="flex gap-6">
  <SecondarySidebar
    items={[
      { id: 'general', label: 'General', icon: Settings },
      { id: 'advanced', label: 'Advanced', icon: Wrench },
    ]}
    activeItem={activeItem}
    onItemChange={setActiveItem}
    searchable={items.length >= 10}
  />
  <div className="flex-1">{/* content */}</div>
</div>
See: Surface System

Add a data table

  1. Define columns using @tanstack/react-table ColumnDef<T>[]
  2. Use react-window for virtual scrolling on large datasets
  3. Add filter/sort state with React hooks
See: Data Table Guide

Forms

Create a validated form

Use useZodForm from @/hooks/useZodForm (NOT useValidatedForm — deprecated).
import { useZodForm } from '@/hooks/useZodForm';
import { mySchema } from '@/schemas/my.schema';

const { form, handleSubmit, isSubmitting, FormFieldController } = useZodForm({
  schema: mySchema,
  defaultValues: { name: '', amount: 0 },
  onSubmit: async (data) => {
    await myService.create(data);
  },
});
See: Forms and Validation

Testing

Write a unit test

  1. Create test file colocated with source: src/features/[feature]/services/MyService.test.ts
  2. Use Vitest:
    import { describe, it, expect } from 'vitest';
    
    describe('MyService', () => {
      it('should create an entity', async () => {
        // arrange, act, assert
      });
    });
    
  3. Run: npm run test (watch mode) or npx vitest run path/to/test.ts
See: Testing Strategy

Write an E2E test

  1. Create test in tests/e2e/ organized by role
  2. Use Playwright:
    import { test, expect } from '@playwright/test';
    
    test('should display dashboard', async ({ page }) => {
      await page.goto('/dashboard');
      await expect(page.getByRole('heading')).toContainText('Dashboard');
    });
    
  3. Run: npm run test:e2e or npm run test:e2e:ui
See: E2E Framework

Data

Add a Firestore collection

  1. Add typed collection reference in src/core/firestoreCollections.ts (re-exports from firestoreCollections.legacy.ts)
  2. Define the document type in src/features/[feature]/types/
  3. Create a Zod schema for validation in src/schemas/
Gotcha: Collection names may not match the domain concept — see Common Gotchas See: Data Architecture

Add a new type

  1. Create src/features/[feature]/types/[name].types.ts
  2. Export from barrel: src/features/[feature]/index.ts
  3. For shared types used across features: packages/shared/src/ or src/types/
See: Type System