Files
flatrender/docs/SESSION_HANDOFF.md
2026-06-07 05:42:27 +03:30

6.9 KiB

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)

# 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 contextcrypto.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 6e5efbd5b2617d)

  • 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").