# FlatRender V2 — Session Handoff & Gotchas

> Context captured 2026-06-05 so it survives a Claude-account/machine change.
> The `~/.claude` memory is machine-local; this file is the portable copy.

## How to run (V2 stack)

```bash
# Backend services + infra (Postgres + MinIO) — V2 compose, NOT the default docker-compose.yml
docker compose -f docker-compose.v2.yml --env-file .env.v2 up -d
# Rebuild one service after a code change:
docker compose -f docker-compose.v2.yml --env-file .env.v2 build <svc> && \
docker compose -f docker-compose.v2.yml --env-file .env.v2 up -d <svc>
# Services: identity-svc, content-svc, file-svc, studio-svc, render-svc, notification-svc, gateway, frontend
```

- **Open the app at `http://172.28.144.1:3000`** (the host LAN IP), NOT `localhost`.
- Gateway: `:8088`. JWT secret + service creds live in `.env.v2`.
- DB: single Postgres `flatrender`, one schema per service (`identity`, `content`, `studio`, `render`, `file`, …). User `postgres`.
- DB migrations: `backend/db/migrations/NN_*.sql`, applied **once** by `scripts/init-db.sh` on first volume creation. New SQL files must be applied manually: `docker exec fr2-postgres psql -U postgres -d flatrender -f ...` (or inline `-c`).

## ⚠️ Critical gotchas (these have each cost hours)

1. **Localhost is VPN-hijacked.** EonVPN intercepts `127.0.0.1:*`. Curl/loopback to localhost returns junk/000 even when the app is up. Use the LAN IP `172.28.144.1`, or `docker exec <container> wget -qO- http://<svc>:<port>`.

2. **Non-secure browser context.** Because the app is served over `http://172.28.144.1` (plain HTTP, non-localhost), the browser is a **non-secure context** → `crypto.randomUUID`, `crypto.subtle`, clipboard, etc. are **undefined**. NEVER call them in client code — use `src/lib/uuid.ts` `uuid()` (falls back to `crypto.getRandomValues`). This silently broke the entire studio editor (`crypto.randomUUID is not a function`).

3. **Studio service binds camelCase JSON** (no snake_case naming policy, unlike identity/content which use `SnakeCaseLower`). Frontend→studio request bodies MUST be camelCase (`originalProjectId`, not `original_project_id`) or fields drop to defaults (Guid.Empty). Studio responses are camelCase too.

4. **EF Core global query filters** (`HasQueryFilter(DeletedAt==null)`) require `.IgnoreQueryFilters()` to see/revive soft-deleted rows (bit the AEP import apply).

5. **AE automation** runs on a Windows node via `node-agent.exe` (PULL model: agent dials render-svc with HMAC). Before each AE launch the agent kills stale AE processes + clears crash/Safe-Mode markers (`SCRPriorState.json` + registry `AppStates`) and launches AE with the project as an arg (bypasses the Home screen). Heavy expression-driven AEPs take >10min to open → FIX scans are now **binary-only** (`services/render/internal/aep/parse.go` `ParseNames` reads `frl_`/`frd_` names from the .aep RIFX directly; no AE).

## What this session built (commits 6e5efbd → 5b2617d)

- **AE scan hardening:** binary FIX scanner (no AE, never hangs); kill stale AE + clear crash dialog before each launch.
- **Profile = data-collection surface** (for future AI video gen, NO resume builder): full editable profile (avatar upload + slogan/about/company/website/country/birthdate/gender), `/api/profile` + user-scoped `/api/profile/upload`. Identity DTOs widened (no migration — columns existed).
- **Role-aware nav:** `UserMenu` (avatar + dropdown; admins get Admin Panel link) replaces Sign-In when logged in; real avatars in dashboard sidebar + admin shell; `Avatar` primitive; `getNavUser()`.
- **Template detail page** wired to real content (`fetchProject(slug)`; was hardcoded demo catalog → 404'd).
- **"Use template" works end-to-end:** `StudioService.CreateProjectAsync` deep-copies the content template scene graph (scenes + content elements + scene colors + shared colors) into the editable studio project via one atomic cross-schema SQL copy (enum cols cast `::text`; temp `_scene_map`). `/api/projects` resolves container slug → published variant project. **Aspect-ratio picker** (16:9/1:1/9:16) on the detail page drives which variant is copied.

## ⏭️ NEXT UP — Studio↔Template binding EPIC (agreed priority)

**Phase B DONE (commits a69bc62 B1, 47a4ced B2).** Edit→render binding works:
- **B1 ✅** studio input edits persist to `saved_scene_contents` via studio-svc
  `PATCH /v1/saved-projects/{id}/contents` (Next `/api/projects/[id]/contents`); the
  persistence hook pushes edited values (bridged `c-<key>` layers) on every save.
- **B2 ✅** render-svc claim now includes `bindings` (GetRenderBindings = saved_scene_contents
  with non-empty value); node-agent `binder.go` emits a JSON bind-spec + downloads media, runs
  the data-driven `bind.jsx` via afterfx (sets Source Text, replaces footage) → saves `bound.aep`
  → aerender renders THAT.
- **VERIFY (needs node-agent re-run):** re-run the updated `node-agent.exe`, edit a text input in
  the studio (wait for "saved"), render, confirm the MP4 shows the edited text. Colours
  (saved_shared_colors → spec.colors) + footage-item media (vs layer-source) are follow-ups.

- **Done since:** per-tier render height (#36), FIX-hides-add-scene (#42), admin/renders
  pagination + user-name links + output (#41), Persian/Jalali date pickers (#40).
- **Still open in B:** colours binding (saved_shared_colors → spec.colors), footage-item
  media (vs layer-source), deeper per-input controllers in the admin scene-inputs editor.
- Next epic phase: **A** admin preset stories (premade videos — model `preset_stories`/`preset_scenes`
  exists, no endpoints/UI; detail "ویدیوهای ساخته‌شده" is placeholder), and **C** AE single-frame
  scene snapshots (`scenes.snapshot_url` empty → node `aerender -s 0 -e 0`).

Also smaller, still open: per-tier render **height** (render-svc r_height hardcoded 1080 + node
ffmpeg scale), **FIX hides add-scene** (mode not plumbed into studio store), **Persian/Jalali
date pickers** in admin, **admin/renders** pagination + video/output + user-name → profile link,
deeper per-input controllers in the admin scene-inputs editor.

## Done follow-ups (this session)
- Scene-graph copy now includes repeater children, characters/controllers, color-presets.
- Admin CAN edit any user's full profile (Users → «پروفایل»).
- Studio now shows ALL template inputs (contents→layers bridge).

## Debugging client-side behavior

No Playwright/Puppeteer in the repo. To reproduce browser issues headlessly: `npm i puppeteer-core` in a temp dir, launch the user's Chrome (`C:\Program Files\Google\Chrome\Application\chrome.exe`), set the auth cookie via `page.setCookie({name:'fr_access', value:<JWT>, url:'http://172.28.144.1:3000'})`, and listen on `page.on('pageerror')` / `console`. Mint a test admin JWT with the `.env.v2` `JWT_SECRET` (HS256; claims `sub`=real user id, `tenant_id`, `is_admin:"true"`, `role:"Admin"`, `iss:"flatrender-identity"`, `aud:"flatrender"`).
