Skip to content

AI Context Documentation

This section provides context for AI coding agents working on the TendSocial codebase.

Purpose

AI Context documentation is designed for:

  • AI Coding Agents (Cursor, Copilot, Claude, etc.)
  • Quick Reference for key patterns and constraints
  • Agent Rules that must be followed

Key Resources

Agent Rules

The primary rules file is located at:

.agent/agent-rules.md

This file contains critical constraints:

  • ESM module requirements (.js extensions)
  • Prisma v7 patterns (no $use, use $extends)
  • Multi-tenant data isolation (getTenantPrisma)
  • Locked dependency versions
  • Strict Typing (No any, explicit schemas)

Common Patterns

Database Access

typescript
// Always get tenant-scoped Prisma client
import { getTenantPrisma } from '../infra/prisma.js';

async function handler(request) {
  const { companyId } = request.user;
  const tenantPrisma = getTenantPrisma(companyId);
const posts = await tenantPrisma.post.findMany();

Route Pattern

typescript
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';

export default async function routes(fastify: FastifyInstance) {
  const app = fastify.withTypeProvider<ZodTypeProvider>();
  // routes...
}

ESM Imports

typescript
// ✅ CORRECT - Always include .js extension
// WRONG - Global Client (Data Leak Risk)
import prisma from '../infra/prisma.js'; // Don't use default export for queries

// ❌ WRONG - Missing extension
// WRONG - Global Client (Data Leak Risk)
import prisma from '../infra/prisma.js'; // Don't use default export for queries

Shared Types System

typescript
// Use @tendsocial/shared-types for major entities
import { BrandProfile, AppSettingsSchema } from '@tendsocial/shared-types';

// Use directly for Zod validation/transformation
const validated = BrandProfileSchema.parse(rawDataFromDb);

Page Rendering and State Contract

All frontend page screens must render their root JSX wrapped in the <Page> component imported from @/components/shared/Page to ensure layout consistency across all routing contexts:

  • Title and Description Props: Page title and description must be passed as title and description props, not as manual h1/h2 tags or inline descriptions inside the children.
  • Desktop Header Hiding: The <Page> component automatically hides titles/descriptions on desktop using responsive tailwind classes to prevent duplication with the layout's Topbar. Always keep this default behavior unless explicitly overridden.
  • Unified Loading and Error States: Wrap loading states and error states in <Page> to maintain consistent layout structure. For errors, use the <ErrorState> component from @/components/states.

Project Structure

apps/
├── backend/         # Fastify API server
├── frontend/        # React app (app.tendsocial.com)
├── docs/            # VitePress documentation
└── marketing/       # Marketing website

Epic 3 Hook Patterns (Added in Stories 3.1–3.5)

useBrandProfile / useUpdateBrandProfile

  • Location: apps/frontend/src/features/brand-identity/hooks/useBrandProfile.ts
  • GET /api/brand-profile — fetches singleton brand profile for tenant
  • PUT /api/brand-profile — updates brand profile with BrandProfileUpdate (all fields optional)
  • queryKey: queryKeys.brandProfile.detail(companyId) / queryKeys.brandProfile.all
  • Gate: enabled: !!companyId — only fires when user is authenticated

useBlogPosts / useCreateBlogPost / useUpdateBlogPost

  • Location: apps/frontend/src/features/blog/hooks/useBlogPosts.ts
  • GET /api/blog/posts — list with optional status, limit, offset params
  • POST /api/blog/posts — create with { title, slug, markdownBody, status? }
  • PUT /api/blog/posts/:id — update with { title?, slug?, markdownBody?, status? } (no id in body)
  • queryKey: queryKeys.blog.* — see lib/queryKeys.ts for full shape
  • Note: Backend returns DRAFT/PUBLISHED etc.; hook normalizes to lowercase draft/published

useSocialPosts / useCreateSocialPost

  • Location: apps/frontend/src/features/posts/hooks/useSocialPosts.ts
  • GET /api/social-posts — list; pass { campaignId } filter to add ?campaignId= query param
  • POST /api/social-posts — create with { content: string, platforms: string[] }
  • queryKey: queryKeys.posts.*
  • Note: Response is raw array; hook normalizes via normalizePost

useGenerateCampaignContent

  • Location: apps/frontend/src/features/campaigns/hooks/useCampaigns.ts
  • POST /api/campaigns/:id/generate-content — triggers AI content generation for campaign
  • Input: GenerateContentInput (empty {} is valid — uses campaign context from backend)
  • Output: GenerateContentResult with counts and ideas.content.{ socialPosts, blogIdeas, videoIdeas }
  • Used by: CampaignGeneratePanel component

IdentityContextPanel

  • Location: apps/frontend/src/features/brand-identity/components/
  • Displays brand identity completeness summary with setup guidance
  • Reads from useBrandProfile; shows IncompleteContextState when identity fields are missing

CampaignGeneratePanel

  • Location: apps/frontend/src/features/campaigns/components/CampaignGeneratePanel.tsx
  • Gate: checks isIdentityComplete(profile) before allowing generation
  • Uses useGenerateCampaignContent(campaignId) mutation
  • Shows PermissionRestrictedState for non-admin/owner roles

queryKeys.ts additions in Epic 3

  • blog section added in Story 3.2: all, lists(companyId), listFiltered(companyId, filters), detail(id, companyId)
  • brandProfile and posts sections were pre-existing

See Also

Critical References (Read First)

Quick References (for AI Context)

Standards

TendSocial Documentation