Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.9 KiB
FlatRender V2 — Session Handoff & Gotchas
Context captured 2026-06-05 so it survives a Claude-account/machine change. The
~/.claudememory is machine-local; this file is the portable copy.
How to run (V2 stack)
# 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), NOTlocalhost. - Gateway:
:8088. JWT secret + service creds live in.env.v2. - DB: single Postgres
flatrender, one schema per service (identity,content,studio,render,file, …). Userpostgres. - DB migrations:
backend/db/migrations/NN_*.sql, applied once byscripts/init-db.shon 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)
-
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 IP172.28.144.1, ordocker exec <container> wget -qO- http://<svc>:<port>. -
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 — usesrc/lib/uuid.tsuuid()(falls back tocrypto.getRandomValues). This silently broke the entire studio editor (crypto.randomUUID is not a function). -
Studio service binds camelCase JSON (no snake_case naming policy, unlike identity/content which use
SnakeCaseLower). Frontend→studio request bodies MUST be camelCase (originalProjectId, notoriginal_project_id) or fields drop to defaults (Guid.Empty). Studio responses are camelCase too. -
EF Core global query filters (
HasQueryFilter(DeletedAt==null)) require.IgnoreQueryFilters()to see/revive soft-deleted rows (bit the AEP import apply). -
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+ registryAppStates) 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.goParseNamesreadsfrl_/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;Avatarprimitive;getNavUser(). - Template detail page wired to real content (
fetchProject(slug); was hardcoded demo catalog → 404'd). - "Use template" works end-to-end:
StudioService.CreateProjectAsyncdeep-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/projectsresolves 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_contentsvia studio-svcPATCH /v1/saved-projects/{id}/contents(Next/api/projects/[id]/contents); the persistence hook pushes edited values (bridgedc-<key>layers) on every save. -
B2 ✅ render-svc claim now includes
bindings(GetRenderBindings = saved_scene_contents with non-empty value); node-agentbinder.goemits a JSON bind-spec + downloads media, runs the data-drivenbind.jsxvia afterfx (sets Source Text, replaces footage) → savesbound.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_scenesexists, no endpoints/UI; detail "ویدیوهای ساختهشده" is placeholder), and C AE single-frame scene snapshots (scenes.snapshot_urlempty → nodeaerender -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").