Skip to content

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:

  1. Canva reference (temp_new_ui/) — visual and interaction specifications
  2. UX Design Specification (_bmad-output/planning-artifacts/ux-design-specification.md) — user flows and interaction patterns
  3. PRD (_bmad-output/planning-artifacts/prd.md) — feature definitions and requirements
  4. 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:

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:

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 useContext within the feature boundary
  • Form state: Use controlled components with local state or the feature's context provider
tsx
// 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:

tsx
// ✅ 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:

tsx
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:

tsx
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:

tsx
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/:

ts
// 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

PatternStory/EpicKey Files
Route adapterEpic 4.1 (Campaign Hub)apps/frontend/src/routes/campaigns/CampaignsRoute.tsx
State management (TanStack Query)Story 4.5Various feature hooks
Contract test patternEpic 4.xapps/frontend/src/features/posts/components/PlantASeedComposer.contract.test.tsx
Visual regression testStory 7.2test/e2e/visual-regression.spec.ts
Accessibility testStory 7.3test/e2e/accessibility-*.spec.ts
Page component usageAll Epic 1-5 screensapps/frontend/src/components/shared/Page.tsx
Design tokensStory 1.2apps/frontend/src/styles/globals.css

TendSocial Documentation