# Figma Make Compatibility — Xertica UI

Figma Make previews any project through **its own proxied Vite dev server**, not through the project's normal `index.html` → `main.tsx` boot sequence. This causes a specific, recurring set of breakages in xertica-ui projects that never show up in `npm run dev`, `npm run build`, or Vercel/Netlify deploys. This document is a **reverse-engineered checklist**, distilled from real fixes applied to a shipped project, for bringing any xertica-ui-scaffolded app into compatibility with Figma Make.

> Use this doc reactively (a project already fails in Figma Make) or proactively (about to publish a project to Figma Make for the first time). It only changes behavior for the Figma Make preview target — nothing here should degrade the normal dev/build/deploy path.

---

## Why Figma Make is different

- **It does not execute `main.tsx`.** Its own bootstrap renders the app's root component (`src/app/App.tsx`) directly. Any side-effect import that only exists in `main.tsx` (most importantly `import './i18n'`) never runs.
- **It transforms source files before serving them.** Figma Make injects its own instrumentation (observed: `import { FGCmp } from "fginspector";`) into components as it serves them. This can shift a file's effective resolved location, which breaks plain relative imports (`../i18n`) that depended on the importer's original path.
- **It wraps components mid-render for inspection.** This interacts badly with two specific React patterns: guard components that conditionally render a `children` prop, and auth state that starts `null` and flips to a value inside `useEffect`. Both can throw React 18's `"Expected static flag was missing"` error under Figma Make specifically, even though they work everywhere else.
- **It uses its own pnpm-based install with `supportedArchitectures` constraints**, and appears to require explicit `package.json` `dependencies` entries — packages that resolve fine locally only via hoisting/transitive resolution can fail to resolve in Figma Make's install.
- **It supports its own `figma:asset/<filename>` import convention** for assets brought in through Figma's design-import flow, which a project's Vite config has no reason to understand out of the box.

None of this is visible from a local `npm run build` — it only surfaces inside the actual Figma Make preview.

---

## Checklist — apply in order

### 1. i18n must initialize from the root component, not `main.tsx`

Figma Make renders `App.tsx` directly, so `main.tsx`'s `import './i18n'` never runs there.

**Fix:** add a side-effect import of the i18n init file as the **first line** of `src/app/App.tsx`, in addition to (not instead of) the existing import in `main.tsx`:

```tsx
// src/app/App.tsx
import '@/i18n';
```

Use the `@` alias, **not** a relative path (`../i18n`). Figma Make's file instrumentation can change a component's effective resolved path, and a relative import breaks in a way a rooted alias import does not.

**Companion fix — the alias must exist in two places, not one.** `vite.config.ts` already declares `@` → `src/`, but that only satisfies the bundler. TypeScript needs its own copy in `tsconfig.json`, or `tsc` fails (`Cannot find module or type declarations for side-effect import`) even though Vite resolves the import fine:

```json
// tsconfig.json — compilerOptions
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
```

Never remove the `main.tsx` import when adding the `App.tsx` one — normal (non-Figma-Make) boot still goes through `main.tsx` first, and initializing i18next twice is a no-op guarded by `i18n.isInitialized` (see [i18n.md](./i18n.md)).

If translations still flash as raw keys on first paint (worse under Figma Make's direct-root-render bootstrap than in normal dev), set `initImmediate: false` in the `i18n.init()` call so initialization is synchronous.

### 2. Route guards: use layout routes + `<Outlet>`, not children-wrapping components

A guard component that conditionally returns `children` vs `<Navigate>` interacts badly with Figma Make's inspector wrapping and can throw `"Expected static flag was missing"`.

**Before (breaks under Figma Make):**

```tsx
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" replace />;
  return <>{children}</>;
}
// <Route path="/home" element={<ProtectedRoute><HomePage /></ProtectedRoute>} />
```

**After (layout route + Outlet):**

```tsx
export function AuthedLayout() {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" replace />;
  return <Outlet />;
}
// <Route element={<AuthedLayout />}>
//   <Route path="/home" element={<HomePage />} />
// </Route>
```

Apply the same transformation to guest-only routes (redirect away when already authenticated) and role-restricted routes — one layout component per guard type, grouped with nested `<Route>` children instead of wrapping each page element.

### 3. Auth state must hydrate synchronously, not via `useEffect`

`useState(null)` followed by a `useEffect` that hydrates the real user causes a `null → user` flip after mount. Combined with the guard pattern above, this is the other half of the `"Expected static flag was missing"` failure mode.

**Fix:**

```tsx
// src/app/context/AuthContext.tsx
const [user, setUser] = useState<User | null>(() => getStoredUser());
```

Read directly from storage in the `useState` initializer. Drop the async hydration `useEffect` and any `isLoading` state that existed only to gate rendering until hydration completed.

### 4. Don't lazy-load the first route Figma Make renders

If the login page (or whichever route renders first for a logged-out user) is behind `React.lazy`, Figma Make's preview can hit a Suspense-related synchronous-input warning that doesn't appear in normal dev. Import it eagerly instead:

```tsx
import { LoginPage } from '../../pages/LoginPage'; // not React.lazy(() => import(...))
```

Other, non-entry routes can remain lazy — this only applies to whatever route is on screen at first paint.

### 5. Add a `figma:asset/*` resolver if the project uses Figma's asset-import convention

Components authored via Figma's own design-import flow can contain imports like `import img from 'figma:asset/abc123.png'`. A plain Vite config has no rule for this scheme. Add a small resolver plugin:

```ts
// vite.config.ts
function figmaAssetResolver() {
  return {
    name: 'figma-asset-resolver',
    resolveId(id: string) {
      if (id.startsWith('figma:asset/')) {
        return path.resolve(__dirname, 'src/assets', id.replace('figma:asset/', ''));
      }
    },
  };
}
// plugins: [figmaAssetResolver(), react(), tailwindcss()]
```

This is safe to add even when nothing currently uses `figma:asset/*` imports — it's inert until something does. Confirm with `grep -r "figma:asset" src` whether it's actually needed before spending time debugging asset 404s.

### 6. If a critical static asset (e.g. the logo) fails to resolve, inline it as an SVG component

Figma Make's asset pipeline can be unreliable for locally-imported static assets even without the `figma:asset/` scheme. For small, critical assets (logos, brand marks) where a broken image is highly visible, convert the plain `import logo from './logo.svg'` into a hand-authored inline SVG React component:

```tsx
// src/shared/assets/brand/MpmaLogos.tsx
export function MpmaLogo({ className }: { className?: string }) {
  return <svg className={className} viewBox="..." {/* markup copied 1:1 from the .svg */}>...</svg>;
}
```

Only do this for assets where breakage is unacceptable — it's a workaround with a maintenance cost (the SVG markup now lives in two places if the original `.svg` is kept for other tooling), not a general policy for all images.

### 7. Declare every runtime dependency explicitly in `package.json`

Figma Make's own install appears to enforce non-hoisted, explicit dependency resolution (its generated `pnpm-workspace.yaml` includes a `supportedArchitectures` block). A package that's only ever resolved transitively/hoisted locally (e.g. a chart library pulled in by a UI component, a validation library used by one feature) can fail to resolve in Figma Make even though `npm run dev` and `npm run build` work fine locally.

**Fix:** `grep` your actual imports against `package.json`'s `dependencies` and add anything missing:

```bash
grep -rohE "from ['\"][a-zA-Z@][a-zA-Z0-9_.-]*['\"]" src | sed -E "s/from ['\"]//;s/['\"]//" | sort -u
```

Cross-reference against `dependencies` (not `devDependencies`) and add explicit version entries for anything used at runtime but currently absent.

### 8. Recognize and ignore inert Figma Make scaffold files

Figma Make's own export/sync process tends to drop boilerplate that isn't wired into the actual build: extra `README.md`/`ATTRIBUTIONS.md`, a `pnpm-workspace.yaml`, duplicate `postcss.config.mjs`, alternate theme/CSS files under `public/` or a fresh `src/styles/` set. Before "fixing" any of these, verify whether they're actually imported/referenced:

```bash
grep -rn "theme.css\|globals.css\|tailwind.css" src index.html vite.config.ts
```

If the project's real stylesheet (e.g. `src/styles/index.css`) is untouched and still the only one imported, these extra files are harmless noise from the export process — leave them, don't delete them (Figma Make may regenerate or depend on their presence for its own sync bookkeeping), and don't spend fix effort on them.

---

## Changes to flag, not silently apply

Some changes observed coming out of Figma Make syncs are **not** Figma Make compatibility fixes — they're tradeoffs or unrelated regressions that happened to ride along. Always call these out explicitly rather than treating them as part of "making it work":

- **Removing `tsc -b` from the `build` script** (`"build": "tsc -b && vite build"` → `"build": "vite build"`). This drops the type-check gate from production builds. It may be necessary if Figma Make's build step can't run project references, but it's a real risk (type errors can now reach production silently) — disclose it, don't apply it as a default.
- **Swapping a local/self-hosted image for an external URL** (e.g. a hero image moving from a bundled asset to an Unsplash URL). This introduces a network dependency and a third-party asset that wasn't there before. Confirm it's intentional.
- **Content-only rewrites** (copy changes, mock data changes) that happened to land in the same sync — these are product/localization work, unrelated to Figma Make technical compatibility. Don't conflate them with the checklist above when summarizing "what changed" to a stakeholder.

---

## Verification

Since the actual Figma Make preview isn't scriptable from a local shell, verify as much as possible locally and disclose the gap:

1. `npm run check` (type-check + lint) — must pass clean.
2. Start the dev server, `curl` the entry HTML and the transformed `i18n` module URL, confirm the alias resolved to a real file with real content.
3. Stop the dev server; confirm it actually stopped (`curl` → connection refused, `lsof -i :<port>` → empty) rather than trusting a shell's exit code alone.
4. Tell the user explicitly that local checks don't prove the Figma Make preview itself renders correctly — ask them to confirm visually, and give them a concrete fallback diagnostic (e.g. "if it's still broken, check whether `src/i18n.ts` and `src/locales/` actually made it into Figma Make's synced file tree").

## AI Rules

- **Always keep the `main.tsx` i18n import when adding the `App.tsx` one** — never replace it, the two serve different boot paths.
- **Never use a relative import for the root-component i18n side-effect import** — always the `@` alias, and always add the matching `tsconfig.json` `paths` entry if it isn't already there.
- **Never wrap a route's `element` in a children-prop guard component** — use a layout route + `<Outlet>` instead, one layout per guard type.
- **Never hydrate auth state asynchronously in `useEffect`** — initialize with a `useState(() => getStoredUser())` initializer.
- **Never make the first-rendered route `React.lazy`** — eager-import whatever route is on screen at first paint.
- **Never silently drop `tsc -b` from the build script, or swap a local asset for an external URL, without flagging it to the user as a disclosed tradeoff.**
- **Never delete unfamiliar files dropped by a Figma Make sync without first checking whether they're referenced** — most are inert scaffold noise, not bugs.
- Cross-reference this checklist against [i18n.md](./i18n.md) for the underlying i18next setup and [getting-started.md](./getting-started.md) for the standard routing/AuthContext pattern being adapted here.
