Frontend Replacement Extension Guide
This guide documents the canonical process for adding new screens to the TendSocial replacement UI system. Follow these steps in order when extending the frontend.
Source of Truth Hierarchy
When implementing a new screen, consult these sources in order:
- Canva reference (
temp_new_ui/) — visual and interaction specifications - UX Design Specification (
_bmad-output/planning-artifacts/ux-design-specification.md) — user flows and interaction patterns - PRD (
_bmad-output/planning-artifacts/prd.md) — feature definitions and requirements - Architecture (
_bmad-output/planning-artifacts/architecture.md) — system design decisions
New-Screen Implementation Checklist
Step 1: Define Route in Router
Add the route in apps/frontend/src/app/router.tsx:
// Example — add a new route for "Analytics Dashboard"
{
path: 'analytics',
lazy: () => import('@/routes/analytics/AnalyticsRoute'),
}Reference: See Epic 4.1 (campaign hub) or Story 6.8 (blog redesign) for route adapter patterns.
Step 2: Create Route Adapter
Create a route adapter component in apps/frontend/src/routes/<feature>/<Feature>Route.tsx:
// apps/frontend/src/routes/analytics/AnalyticsRoute.tsx
import { AnalyticsPage } from '@/features/analytics/components/AnalyticsPage';
export function Component() {
return <AnalyticsPage />;
}Route adapters are thin wrappers that decouple routing from feature components. They live in apps/frontend/src/routes/*/.
Step 3: Create Feature Page Components
Create feature pages in apps/frontend/src/features/<feature>/components/<Page>.tsx.
State Management Pattern:
- Async data (API calls): Use TanStack Query v5 hooks (see Story 4.5 for reference)
- Local UI state: Use
useContextwithin the feature boundary - Form state: Use controlled components with local state or the feature's context provider
// apps/frontend/src/features/analytics/components/AnalyticsPage.tsx
import { Page } from '@/components/shared/Page';
import { SectionCard } from '@/components/shared/SectionCard';
export function AnalyticsPage() {
return (
<Page title="Analytics" description="Track your content performance">
<SectionCard title="Overview">
{/* Page content */}
</SectionCard>
</Page>
);
}Step 4: Use Shared State Components
For pages with async data, use these shared components from apps/frontend/src/components/states/:
<LoadingState />— loading/skeleton state<ErrorState />— error with retry action<EmptyState />— empty state with call-to-action
Step 5: Use Design Tokens
Use CSS variables or Tailwind utilities — never hardcode colors:
// ✅ Correct — uses design tokens
<div className="bg-background text-foreground border-border">
Content
</div>
// ❌ Wrong — hardcoded colors
<div style={{ backgroundColor: '#ffffff', color: '#000000' }}>
Content
</div>Reference: See apps/docs/internal/architecture/ui-strategy.md and apps/docs/internal/frontend/ui-theming.md for the complete token reference. Tokens are defined in apps/frontend/src/styles/globals.css under the @theme block.
Step 6: Wrap Page in <Page> Component
Every feature page must use the <Page> component:
import { Page } from '@/components/shared/Page';
export function FeaturePage() {
return (
<Page title="Page Title" description="Page description">
{/* Page content */}
</Page>
);
}Reference: apps/frontend/src/components/shared/Page.tsx
Step 7: Write Tests
Component tests (*.test.tsx) — use vi.mock() for hooks:
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { AnalyticsPage } from './AnalyticsPage';
vi.mock('../hooks/useAnalytics');
it('renders loading state', () => { /* ... */ });Contract tests (*.contract.test.tsx) — use MSW with absolute URLs:
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('http://localhost:4000/api/analytics', () => {
return HttpResponse.json({ data: [] });
}),
);Reference: apps/frontend/src/features/posts/components/PlantASeedComposer.contract.test.tsx — real example of the contract test pattern.
Step 8: Add Playwright E2E Test
If the new screen has a critical user workflow, add an E2E test in test/e2e/:
// test/e2e/analytics.spec.ts
import { test, expect } from '@playwright/test';
test('analytics dashboard loads', async ({ page }) => {
await page.goto('/analytics');
await expect(page.locator('h1')).toHaveText('Analytics');
});For visual regression tests, add screenshots following the pattern from Story 7.2.
Step 9: Update Documentation
After implementing, update:
apps/docs/internal/feature-matrix.md— add the new screen to the feature matrix- Relevant architecture file in
apps/docs/internal/architecture/
Common Pitfalls to Avoid
1. Hardcoded Colors
Don't: Write raw hex codes like #ffffff, #000, or color names like white, black, slate-100. Do: Use design tokens via CSS variables (--background, --fg-1, etc.) or Tailwind utilities (bg-background, text-foreground).
2. Unsigned Asset URLs
Don't: Return raw S3/CDN URLs from asset APIs. Do: Use assetDto.ts to generate signed viewUrls with TTL ≤ 15 minutes. See CLAUDE.md media-transforms guidance.
3. Tenant Data Leaks
Don't: Use the base Prisma client for tenant-scoped queries. Do: Use getTenantPrisma(companyId) from apps/backend/src/infra/prisma.ts. See CLAUDE.md multi-tenant section.
4. Missing .js Extensions in ESM Imports
Don't: Write import x from './file' (missing extension). Do: Write import x from './file.js'. See CLAUDE.md ESM Module System section.
5. Skipping State Handling
Don't: Only implement the happy path (success state). Do: Handle loading, empty, error, and unauthorized states for every async data source.
Reference Implementations
| Pattern | Story/Epic | Key Files |
|---|---|---|
| Route adapter | Epic 4.1 (Campaign Hub) | apps/frontend/src/routes/campaigns/CampaignsRoute.tsx |
| State management (TanStack Query) | Story 4.5 | Various feature hooks |
| Contract test pattern | Epic 4.x | apps/frontend/src/features/posts/components/PlantASeedComposer.contract.test.tsx |
| Visual regression test | Story 7.2 | test/e2e/visual-regression.spec.ts |
| Accessibility test | Story 7.3 | test/e2e/accessibility-*.spec.ts |
| Page component usage | All Epic 1-5 screens | apps/frontend/src/components/shared/Page.tsx |
| Design tokens | Story 1.2 | apps/frontend/src/styles/globals.css |