Agent Skill
2/7/2026

frontend-development-guidelines

Guard rails for React/MobX reactivity, session management, CQRS queries, and WCAG 2.1 AA accessibility in A4C-AppSuite.

A
analytics4change
0GitHub Stars
2Views
npx skills add Analytics4Change/A4C-AppSuite

SKILL.md

Namefrontend-development-guidelines
DescriptionGuard rails for React/MobX reactivity, session management, CQRS queries, and WCAG 2.1 AA accessibility in A4C-AppSuite.

name: Frontend Development Guidelines description: Guard rails for React/MobX reactivity, session management, CQRS queries, and WCAG 2.1 AA accessibility in A4C-AppSuite. version: 3.0.0 category: frontend tags: [react, mobx, accessibility, wcag, cqrs, logging, viewmodel]

Frontend Guard Rails

Critical rules that prevent bugs in the React/TypeScript frontend. For full guidance, component patterns, and the dropdown decision tree, see frontend/CLAUDE.md and search documentation/AGENT-INDEX.md with keywords: react, mobx, accessibility, wcag, component, dropdown, modal, forgot-password, password-reset, logging, viewmodel, auth, session, cqrs.


1. MobX: Never Spread Observable Arrays

Spreading breaks the observable chain — components silently stop re-rendering.

// ❌ WRONG — loses reactivity, component won't update
<CategorySelection selectedClasses={[...vm.selectedTherapeuticClasses]} />

// ✅ CORRECT — pass observable directly
<CategorySelection selectedClasses={vm.selectedTherapeuticClasses} />

// ✅ For copies: use .slice() or toJS()
const copy = store.items.slice();

2. MobX: Always observer() + runInAction()

Every component reading observables MUST be wrapped with observer(). Async state updates after await MUST use runInAction().

// ✅ Component wrapped with observer
export const MyComponent = observer(() => {
  const store = useMyStore();
  return <div>{store.items.map(item => <Item key={item.id} {...item} />)}</div>;
});

// ✅ Async updates wrapped in runInAction
async fetchData() {
  const response = await api.get('/data');
  runInAction(() => {
    this.data = response.data;
    this.loading = false;
  });
}

3. Never Cache Sessions Manually

Supabase manages session state automatically. Manual caching causes silent failures — empty data lists with zero results and no errors.

Do NOT use getCurrentSession() in new code — it is a legacy cache pattern in supabase.service.ts. Do not copy it.

// ❌ WRONG — manual cache returns NULL, all queries silently fail
const session = supabaseService.getCurrentSession();

// ✅ CORRECT — retrieve session from Supabase every time
const { data: { session } } = await client.auth.getSession();
const claims = JSON.parse(atob(session.access_token.split('.')[1]));
// Use claims.org_id for RLS-compatible queries

4. CQRS: api. Schema RPC Only

NEVER use direct table queries with PostgREST embedding. This violates CQRS, causes 406 errors, and breaks multi-tenant isolation.

// ✅ CORRECT
await supabase.schema('api').rpc('list_users', { p_org_id: orgId })

// ❌ WRONG — re-normalizes denormalized projections
await supabase.from('users').select('..., user_roles_projection!inner(...)')

5. Auth: Use IAuthProvider Interface

Never import auth providers directly. Use the factory/interface pattern.

// ✅ CORRECT
import { getAuthProvider } from '@/services/auth/AuthProviderFactory';
const auth = getAuthProvider();

// ❌ WRONG — tight coupling to specific provider
import { SupabaseAuthProvider } from './SupabaseAuthProvider';

6. Accessibility: WCAG 2.1 Level AA Mandatory

Healthcare compliance. All interactive elements MUST have:

  • aria-label or aria-labelledby
  • Full keyboard navigation (Tab, Enter, Escape, Arrow keys)
  • Visible focus indicators
  • Focus trapping in modals (circular Tab navigation)
  • aria-live for dynamic content updates

7. Focus Management: useEffect, Never setTimeout

// ❌ WRONG
setTimeout(() => element.focus(), 100);

// ✅ CORRECT
useEffect(() => {
  if (isOpen) inputRef.current?.focus();
}, [isOpen]);

8. Timing: Centralized Config, No Magic Numbers

All delays, debounce intervals, and transition durations must use TIMINGS from @/config/timings.ts. Never hard-code timing values.

9. Correlation ID: Auto-Injected via tracingFetch

The Supabase client uses tracingFetch (defined in frontend/src/lib/supabase-ssr.ts:112-118) to automatically inject X-Correlation-ID and traceparent headers on every Supabase request. No manual header injection is needed for Supabase calls.

Gap: TemporalWorkflowClient.ts uses direct fetch() and must inject headers manually.

Business-scoping rule — still applies for generating vs reusing IDs:

  • New transactions (create org, invite user): Backend generates a new correlation_id
  • Continuing transactions (accept invitation): Do NOT generate a new one — backend reuses the stored correlation_id

10. Generated Event Types: Import from @/types/events

Domain event types are auto-generated from AsyncAPI schemas. Never hand-write event interfaces.

// ✅ CORRECT — re-exports from generated + app-specific extensions
import { DomainEvent, EventMetadata, StreamType } from '@/types/events';

// ❌ WRONG — bypasses extensions
import { DomainEvent } from '@/types/generated/generated-events';

// ❌ WRONG — file does not exist, do not recreate it
import { DomainEvent } from '@/types/event-types';

See frontend/CLAUDE.md "Generated Event Types" section for regeneration steps.

11. CQRS Write Path: Event Emission Only

All mutations go through event emission. Never write directly to projection tables.

  • Every write call must include a reason field (minimum 10 characters)
  • Use the useEvents hook and ReasonInput component for user-facing mutations
  • Batch multiple related entities in a single emission call
// ✅ CORRECT
const { emitEvent } = useEvents();
await emitEvent('user.deactivated', { userId, reason: 'Account policy violation' });

// ❌ WRONG — direct table write bypasses event sourcing
await supabase.from('users_projection').update({ is_active: false }).eq('id', userId);

See documentation/frontend/guides/EVENT-DRIVEN-GUIDE.md for the full write-path pattern.

12. JWT Utilities: Import from Shared Location

Do NOT duplicate decodeJWT() logic in individual services. There are already 5 copies in the codebase — these are tech debt to be consolidated into @/utils/jwt.ts.

// ✅ CORRECT — import from shared utility (once consolidated)
import { decodeJWT } from '@/utils/jwt';

// ❌ WRONG — inline decode duplicated per service
const claims = JSON.parse(atob(token.split('.')[1]));

When the shared utility exists, all services must import from it. Do not add a 6th inline copy.

13. Logging: Logger.getLogger(), Never Bare Console

All logging must use the category logger. Bare console.log is stripped in production and provides no category filtering for the debug panel.

import { Logger } from '@/utils/logger';
const log = Logger.getLogger('viewmodel'); // or: api, component, navigation, validation

log.debug('Loading users', { orgId });
log.error('Query failed', error);
// ❌ WRONG
console.log('Loading users');

Debug panel shortcuts: Ctrl+Shift+D (control panel), Ctrl+Shift+M (MobX monitor), Ctrl+Shift+P (performance).


File Locations

WhatWhere
Componentsfrontend/src/components/ (ui/, auth/, debug/, layouts/, medication/, navigation/, organization/, organizations/, organization-units/, roles/, schedules/, users/)
Pagesfrontend/src/pages/
Viewsfrontend/src/views/ (client/, medication/)
ViewModelsfrontend/src/viewModels/
Servicesfrontend/src/services/ (admin/, api/, assignment/, auth/, cache/, data/, direct-care/, http/, invitation/, medications/, mock/, organization/, roles/, schedule/, search/, storage/, users/, validation/, workflow/)
Auth configfrontend/src/config/deployment.config.ts (smart detection), dev-auth.config.ts (mock profiles), oauth.config.ts
Timing configfrontend/src/config/timings.ts
Logging configfrontend/src/config/logging.config.ts, mobx.config.ts
Testsfrontend/src/test/, *.test.tsx
File size~300 lines per file; split when exceeding

Deep Reference

  • frontend/CLAUDE.md — Full development guidance, component decision tree, debugging MobX
  • documentation/AGENT-INDEX.md — Search by keyword for architecture docs
  • documentation/architecture/authentication/frontend-auth-architecture.md — Auth system
  • documentation/frontend/ — Frontend-specific guides and reference
  • documentation/frontend/patterns/mobx-patterns.md — MobX observable and action patterns
  • documentation/frontend/patterns/ui-patterns.md — Modal architecture, dropdown patterns
  • documentation/frontend/architecture/auth-provider-architecture.md — Provider injection details

Definition of Done

  • npm run docs:check passes
  • npm run typecheck passes
  • npm run lint passes
  • npm run build passes
  • Zero rule violations in changed files
Skills Info
Original Name:frontend-development-guidelinesAuthor:analytics4change