This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Platform standards, skills, and scaffold templates for all SyncUpSuite projects. This repo defines the standard that every project — greenfield or brownfield — aligns to.
Org: syncupsuite
Repo: syncupsuite/webplatform4sync
Every project built from this platform follows the 3-tier multi-tenant architecture:
Tier 0 (Platform) → Owns control plane, global defaults, design tokens
Tier 1 (Partner) → Branded instance, manages sub-tenants, owns domains
Tier 2 (Customer) → Consumes platform, inherits branding, scoped access
Simple projects use the same architecture with dormant tiers (hardcoded tenant_id). The machinery exists from day one — it activates when needed without retrofitting.
Source of truth: shared/conventions/stack.md — always check there for locked versions.
| Component | Technology | Version |
|---|---|---|
| UI Framework | React | ^19.2 |
| Language | TypeScript | ^5.9 (strict mode) |
| Styling | Tailwind CSS | ^4.1 (CSS-first @theme) |
| Build | Vite | ^7.0 |
| Edge Runtime | Cloudflare Workers | wrangler ^4.x |
| Database | Neon PostgreSQL | Serverless driver + Hyperdrive |
| ORM | Drizzle ORM | ^0.45 |
| Auth (Sessions) | Better Auth | ^1.4 (neon_auth schema) |
| Auth (Identity) | Firebase / Google Identity Platform | europe-west6 |
| Design Tokens | @syncupsuite/themes | ^0.4.0 (12 themes, Semantic Color API) |
| Routing | React Router | ^7.13 (unified react-router package) |
| Analytics | PostHog | posthog-js ^1.x + reverse proxy |
skills/ # Claude Code skills (abstract, universal)
├── multi-tenant-platform/ # 3-tier architecture patterns
├── theme-inspired-tokens/ # Culturally-grounded design tokens
├── graduated-auth/ # Anonymous → OAuth → Full Account
└── neon-multi-tenant/ # Neon-specific multi-tenant patterns
scaffold/ # Project scaffolding templates
├── greenfield/ # New project from scratch
│ └── base/ # Canonical project template (src/, styles/, db/, server/)
├── brownfield/ # Migration to standards
└── overlay/ # Token/theme system only
shared/ # Shared conventions, contracts, and validation
├── contracts/ # Canonical types and constants (auth.ts, tenant.ts, tokens.ts, constants.ts, env.ts)
├── conventions/ # stack.md (versions), naming.md, deployment.md, analytics.md
└── validation/ # Token and tenant validators
commands/ # Claude Code command files (.md)
frames/ # Command frames (construction, shuhari)
.claude-plugin/ # Plugin manifest (plugin.json, marketplace.json)
docs/ # Canonical documentation
Neon's serverless HTTP driver executes each query as a separate HTTP request. set_config('app.tenant_id', ...) must be in the same Drizzle transaction as the data queries — otherwise the config is lost.
Correct pattern (in skills/neon-multi-tenant/templates/drizzle-tenant.ts):
// tenantQuery() wraps set_config + queries in a single transaction
const results = await tenantQuery(db, tenantId, async (tx) => {
return tx.select().from(items);
});The greenfield scaffold uses two Drizzle schemas:
platformSchema(platform) — tenant infrastructure: tenants, domain_mappings, tenant_relationshipsappSchema({{SCHEMA_NAME}}) — application-domain tables
Both must be in drizzle.config.ts schemaFilter.
Auth is not binary — projects support graduated access:
Anonymous → Preview/Inquiry → OAuth (Google/GitHub) → Full Account (Better Auth + Firebase)
Firebase provides identity. Better Auth provides sessions, RBAC, and tenant-scoped authorization in Neon. See shared/contracts/auth.ts for the AuthLevel enum and discriminated union types.
Tailwind v4 uses @theme blocks in CSS (no tailwind.config.js). Self-referential var() inside @theme is the standard registration pattern — not a bug:
@theme {
--color-primary: var(--color-primary); /* registers :root value as Tailwind token */
}Dark mode uses [data-theme="dark"] selector (class strategy), not @media (prefers-color-scheme).
When editing .claude-plugin/plugin.json:
- Only flat fields:
name,version,description,commands(path string),skills(path string),agents,hooks,mcpServers,outputStyles,lspServers - No custom nested objects — causes "invalid input" errors
When editing .claude-plugin/marketplace.json:
- Plugin
sourcemust be"./"for local plugins — not"npm:package-name"string format @syncupsuite/themesis an npm package, not a Claude Code plugin — don't list it in marketplace.json
repo name = domain name = Google project ID
Example: brandsyncup-com → brandsyncup.com → GCP project brandsyncup-com
Google project IDs: max 30 chars, lowercase, hyphens only.
- No build commands — this repo is templates and patterns only
- Skills are standalone — each has a
skill.mdentry point - Scaffold templates are concrete files with
{{PLACEHOLDER}}tokens shared/conventions/stack.mdis the source of truth for dependency versions- Changes to skills should be validated against BrandSyncUp or LegalSyncUp
- No secrets — Doppler references are placeholders showing where secrets go
- BrandSyncUp (
brandsyncup.com): Reference implementation of this standard - LegalSyncUp (
legalsyncup.com): Loosely coupled partner, shared auth infrastructure - @syncupsuite/themes: npm package with 12 culturally-grounded themes (separate
themes/repo) - SyncUpSuite: Umbrella org, public marketplace