Commit Graph

142 Commits

Author SHA1 Message Date
soroush.asadi 56e2202b5b ci(deploy): pin minio to pre-x86-64-v2 release (baseline CPU)
CI/CD / CI · Web (tsc) (push) Successful in 1m9s
CI/CD / Deploy · full stack (push) Failing after 1m4s
Server VPS CPU lacks x86-64-v2; newer minio:latest is built for it and crash-loops
with 'Fatal glibc error: CPU does not support x86-64-v2'. Pin to a 2024 release that
runs on baseline x86-64 (override with MINIO_IMAGE_TAG if a different tag is on the
mirror). Local dev stays on :latest via .env.v2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:32:55 +03:30
soroush.asadi 21a203b012 ci(deploy): fix minio healthcheck for newer image (curl + mc fallback)
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Failing after 19s
Server's mirror minio:latest is newer than dev's cached RELEASE.2025-09-07 and
dropped the bundled mc client, so 'mc ready local' failed → fr2-minio unhealthy →
up aborted. Switch to MinIO's curl liveness endpoint with an mc fallback so it
works across image versions; bump start_period 10s→20s, retries 5→8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 17:09:00 +03:30
soroush.asadi b34904549f ci(build): pull golang base image from kargadan mirror
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 3m26s
mirror.soroushasadi.com serves only cached images (node:20 resolved, golang:1.25
was 'not found' — too new to be cached, upstream can't back-fill). Point the Go
services' golang:1.25-alpine base at mirror.kargadan.ir per infra owner; alpine/
busybox/node/postgres/minio stay on soroushasadi (cached). GOPROXY already kargadan.
2026-06-12 16:47:31 +03:30
soroush.asadi ee2a6b9b60 ci(build): pull Docker Hub base images via Nexus mirror + kargadan GOPROXY
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 7s
Docker Hub blocks Iran (403) on the BUILD base images too (golang/alpine/busybox/
node) once they fall out of cache. Prefix every Docker Hub FROM/COPY --from with
mirror.soroushasadi.com/ (MCR dotnet images are reachable, left as-is). Go builders
also set GOPROXY=mirror.kargadan.ir/repository/go-group/ + GOSUMDB=off so any module/
toolchain fetch avoids the geo-blocked proxy.golang.org.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 16:24:38 +03:30
soroush.asadi 18cdf507f0 ci(deploy): pull infra images (postgres/minio/caddy) via Nexus mirror
CI/CD / CI · Web (tsc) (push) Successful in 1m6s
CI/CD / Deploy · full stack (push) Failing after 6s
Docker Hub blocks Iran IPs (403), so 'docker compose up' couldn't pull the base
infra images on the server even though all service images built fine through the
mirror. Prefix them with ${INFRA_REGISTRY:-mirror.soroushasadi.com/} so they pull
through Nexus by default; set INFRA_REGISTRY= to use plain Docker Hub names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:23:54 +03:30
soroush.asadi cc9910451d ci: trigger deploy (ENV_FILE secret updated for nginx model)
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 20m37s
2026-06-12 14:47:30 +03:30
soroush.asadi 12588b65df ci(deploy): integrate with mirror-nginx instead of Caddy
CI/CD / CI · Web (tsc) (push) Successful in 1m6s
CI/CD / Deploy · full stack (push) Has been cancelled
The server's central mirror-nginx already owns 80/443 + manages TLS, so FlatRender
can't run its own Caddy there. Adapt the deploy to the host-port + reverse-proxy model:

- compose: Caddy moved behind `profiles: [edge]` (not started by default); frontend/
  gateway/minio host ports are now EDGE_BIND + FRONTEND_PORT/GATEWAY_PORT/MINIO_PORT
  (so they can avoid Gitea's :3000 etc.); postgres/render stay on HOST_BIND loopback.
- deploy/ENV_FILE.production.example: nginx model, pre-filled for flatrender.ir,
  host ports 1600/1605/1610, no Caddy/ACME vars.
- deploy/mirror-nginx-flatrender.conf: ready-to-paste server blocks routing
  flatrender.ir / api / storage → 171.22.25.73:{1600,1605,1610}.
- deploy/README.md: nginx integration + cert step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:42:14 +03:30
soroush.asadi 127f40e1c1 ci: Gitea CI/CD pipeline + server deploy (Nexus mirror, Caddy HTTPS)
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 1m41s
- .gitea/workflows/ci-cd.yml: frontend tsc check → self-hosted deploy job that
  builds the full compose stack and brings it up behind Caddy. Locks
  COMPOSE_PROJECT_NAME=flatrender (stable volumes), backs up the DB before each
  deploy, health-waits gateway+frontend, no `down -v`.
- Route all package installs through mirror.soroushasadi.com:
  frontend Dockerfile npm registry → NPM_REGISTRY build arg (Nexus default);
  3× NuGet.Config (content/identity/studio) → HTTPS nuget-group (were a bare IP).
- Harden host ports: ${HOST_BIND:-0.0.0.0} prefix on postgres/minio/render/gateway/
  frontend so prod (HOST_BIND=127.0.0.1) keeps them off the public internet — only
  Caddy 80/443 is public. Dev (unset → 0.0.0.0) unchanged.
- render-svc MINIO_USE_SSL now env-driven (MINIO_HOST_USE_SSL) for HTTPS storage domain.
- deploy/ENV_FILE.production.example (the Gitea secret template) + deploy/README.md
  (one-time setup + go-live checklist).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:29:09 +03:30
soroush.asadi 61ba526122 feat(admin): render-engine kill switch (block renders + show message)
Lets an admin disable rendering when no render node is available — users can't
start new renders and see a localized "service unavailable until <date>" message.

- Admin → فارم رندر → موتور رندر (RenderEngineAdmin): on/off toggle + fa/en message
  + optional Jalali "until" date; saved as one `render_service` Website Setting
  (jsonb) via /v1/settings — no backend change, no migration.
- lib/render-service.ts: fetchRenderServiceStatus (fail-open) + renderServiceMessage
  (locale + appends the date).
- Enforcement: POST /api/render returns 503 {code:render_disabled, messages} when off;
  studio render page reads GET /api/render/service on mount → disables "شروع رندر"
  and shows the banner, and handles the 503 on click.
- i18n: appAdminLayout.renderEngine (fa+en, parity 1045/1045). tsc + next build clean.
  Verified: disabled setting → /api/render/service returns enabled:false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:47:42 +03:30
soroush.asadi a1414f06f6 feat(studio): phone editing for Video Studio + Image Editor (remove desktop gate)
Replaces the "desktop only" gate on phones with real mobile editing layouts.

Shared:
- BottomSheet (mobile slide-up panel) hosting the desktop side-docks on phones.
- Side panels made width-fluid (w-full on mobile, fixed on md+): StudioSidebarContent,
  ImageEditorRightPanel.

Video Studio (VideoStudioMobileLayout):
- Canvas fills the viewport; the vertical tool dock becomes a scrollable bottom bar;
  each tool's panel + the timeline open as bottom sheets. Exported MAIN_DOCK_ITEMS.

Image Editor (ImageEditorMobileLayout):
- Canvas fills the viewport; toolbar → scrollable bottom bar; Adjust/Filters/Layers
  panel + shape picker open as bottom sheets. Exported IMAGE_TOOLS/IMAGE_SHAPES.
- Touch editing: Stage now handles onTouchStart/Move/End (draw, select, move) with
  touch-action:none; draw-tool stroke works with a finger. Pointer handlers widened
  to MouseEvent | TouchEvent.

i18n: added timeline/preview/panels keys (fa+en, parity verified). Full next build +
tsc clean. (Studio is auth-gated — verify editing on a device.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:05:44 +03:30
soroush.asadi 05400947e4 feat(responsive): mobile fixes for pricing, dashboard, admin, templates, hero
- PricingCompareTable: wide 4-col table is hidden on mobile; new tab-per-plan card
  view (Lite/Pro/Business) so pricing fits a phone. Extracted PricingCompareValueInline.
- Dashboard: sidebar becomes an off-canvas drawer on mobile (hamburger top bar +
  overlay, closes on navigation) via DashboardSidebarDrawer; static column on lg+.
  RTL/LTR safe (max-lg: transforms avoid the lg:/rtl: specificity trap).
- AdminResource: search/add row stacks on mobile (w-full sm:w-52), tables scroll
  horizontally (overflow-x-auto + min-w) instead of clipping.
- Templates: added a mobile category chip row (lg:hidden) since the category
  sidebar is desktop-only; exported VIDEO_SIDEBAR_CATEGORY_IDS.
- Hero: CTAs full-width on mobile, auto width on sm+.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 08:23:10 +03:30
soroush.asadi 1ebde6b15c feat(admin): seed colour presets with a placeholder per shared colour
A colour preset is a setup for ALL of a project's shared colours, but the editor
forced the admin to add each colour one by one. Now:

- "+ پریست جدید" pre-fills one item per shared colour (seeded from each colour's
  default), so a new preset is a complete colour setup out of the box.
- New "+ همهٔ رنگ‌های مشترک" button back-fills placeholders for any shared colours
  missing from an existing preset (or after new shared colours are added).

Frontend-only change in ProjectScenes.tsx PresetsTab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 07:48:30 +03:30
soroush.asadi b3637cf839 feat(home): admin-managed homepage section manager (toggle/reorder/edit)
The homepage is now driven by a `home_layout` Website Setting (jsonb) instead of a
hardcoded section stack — zero backend changes, no migration.

- lib/home-layout.ts: section catalog + saved-layout merge + locale-aware config
  reader (`<field>_fa`/`<field>_en`) + public fetchHomeLayout() (falls back to
  defaults when unset/unreachable).
- app/[locale]/page.tsx: renders ordered, enabled sections from the layout, passing
  per-section content overrides.
- sections (Hero/Products/Templates/HowItWorks/Pricing/Testimonials/FAQ): accept an
  optional `config` prop overriding heading/subtitle/CTA, locale-aware, default-safe.
- new HomeSlides + HomeEvents sections render the previously-orphaned admin Slides
  (/v1/slides) and Home Events (/v1/home-events) data.
- admin: HomeSectionsManager (toggle on/off, ↑/↓ reorder, per-section FA/EN content
  editor) at /admin/home, saved via the existing /v1/settings upsert; nav item + i18n.

Verified: a saved layout overrides Hero/Pricing headings and reorders sections;
removing it reverts to the default homepage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 01:21:44 +03:30
soroush.asadi 1f6c35eb7c chore(content): seed demo blog + learn posts and CMS page rows
Build backend images / build content-svc (push) Failing after 1m16s
Build backend images / build file-svc (push) Failing after 53s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 1m7s
Build backend images / build notification-svc (push) Failing after 59s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 1m2s
Idempotent seed (services/content/migrations/002_seed_blog_learn_pages.sql):
one Blog article, one Learn tutorial, and the 7 static Page rows
(about/contact/careers/privacy/terms/cookies/help) so the new public sections
render with real content and the admin "Pages" section has editable rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 23:13:48 +03:30
soroush.asadi c92de06c28 feat(content): public Blog + Learn sections and static CMS pages (full-stack)
Adds the missing public-facing content pages and their admin authoring, all
powered by the existing content-svc Blog entity discriminated by `kind`.

Backend (content-svc):
- BlogKind enum += Learn, Page (reuses Blog CRUD/SEO/slug/publish for all three).
- SQL migration services/content/migrations/001_blog_kind_learn_page.sql
  (ALTER TYPE content.blog_kind ADD VALUE 'Learn','Page').

Frontend (public, Next.js):
- lib/content-api.ts: fetchArticles(kind) / fetchArticle(slug) / fetchPage(slug)
  with safe empty/null fallbacks.
- components/content: article-ui (card/list/detail + RTL prose), CmsPageContent,
  CmsRoute (admin-authored page or localized built-in fallback copy).
- Routes: /blog, /blog/[slug], /learn, /learn/[slug] and static pages
  /about /contact /careers /privacy /terms /cookies /help.
- Navbar "tutorials" → /learn; all footer links now resolve.

Admin:
- AdminResource: new `fixedValues` option (injects kind on create/update).
- learnConfig (kind=Learn) + pagesConfig (kind=Page) reuse the /v1/blogs endpoint;
  /admin/learn + /admin/pages routes + nav items.

i18n: blog, learn and 7 *Page namespaces added to both fa.json and en.json
(verified key parity); admin nav labels learn/pages. Frontend tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 22:43:25 +03:30
soroush.asadi 6cf8716d7e feat(render): node-agent AE snapshot runner (Epic C2) + colour render-binding (Epic B)
Build backend images / build content-svc (push) Failing after 13s
Build backend images / build file-svc (push) Failing after 53s
Build backend images / build gateway (push) Failing after 1m22s
Build backend images / build identity-svc (push) Failing after 19s
Build backend images / build notification-svc (push) Failing after 21s
Build backend images / build render-svc (push) Failing after 20s
Build backend images / build studio-svc (push) Failing after 1m6s
C2 — real-AE scene snapshots on the node:
- node-agent: runner/snapshot.go RunSnapshot (aerender -comp <key> -s f -e f
  → findRenderedOutput → ffmpeg -frames:v 1 PNG); client ClaimSnapshot /
  GetSnapshotUploadURL / ReportSnapshotResult / ReportSnapshotFail; snapshotLoop +
  pollSnapshotOnce mirroring the scan loop (reuses the AE-exclusive lock).
- render-svc: GetSnapshotJobMeta + UploadURL handler presigns a PUT to the
  public-read user-uploads bucket at snapshots/{project}/{scene}.png and returns a
  permanent public_url (not the time-limited export presign); MINIO_UPLOAD_BUCKET +
  MINIO_PUBLIC_URL config + compose env + /snapshot/:id/upload-url route.

Epic B — bind edited colours into the render:
- render-svc GetRenderBindings UNIONs studio.saved_shared_colors +
  saved_scene_colors (type 'color') so the node writes them before render.
- node-agent binder.go routes type:"color" bindings into the bind-spec colors[]
  array that bind.jsx already applies to the frshare colour layers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 18:08:43 +03:30
soroush.asadi 8488acb115 feat(snapshots): AE scene-snapshot pipeline + admin trigger (Epic C, C1)
Build backend images / build content-svc (push) Failing after 31s
Build backend images / build file-svc (push) Failing after 30s
Build backend images / build gateway (push) Failing after 30s
Build backend images / build identity-svc (push) Failing after 30s
Build backend images / build notification-svc (push) Failing after 31s
Build backend images / build render-svc (push) Failing after 31s
Build backend images / build studio-svc (push) Failing after 31s
Per-scene preview thumbnails for templates. Admin clicks "ساخت پیش‌نمایش
صحنه‌ها" → one single-frame AE render per scene → content.scenes.snapshot_url
→ shown as a thumbnail in the admin scene list (and available to the studio).

- migration 30_render_snapshot_jobs.sql: render.snapshot_jobs (queued|running|
  done|error, per scene, image_url).
- render-svc: db/snapshotjobs.go (EnqueueSceneSnapshots, List, Claim, SetResult
  -> writes content.scenes.snapshot_url cross-schema, SetError); handlers/
  snapshotjobs.go (admin POST/GET /v1/scene-snapshots/:project_id + node-facing
  internal claim/result/fail); main.go routes; gateway route.
- devworker: RunSnapshots — fulfils snapshot jobs with a generated placeholder
  PNG (data: URL, scene-key-tinted) so the flow is verifiable without an AE node.
  Gated by RENDER_DEV_SNAPSHOTS (default off; never hijacks real render jobs).
- admin UI: ProjectScenes "generate snapshots" button (enqueue + poll + reload)
  and a thumbnail (snapshot_url || image) per scene row.

Verified e2e via the dev mock: enqueue -> jobs run -> content.scenes.snapshot_url
populated -> scenes API returns it -> admin renders the thumbnail.

Remaining (C2): node-agent real-AE runner — claim snapshot, aerender -s0 -e0 ->
ffmpeg still -> upload to a PERMANENT URL (mirror file-svc, not the time-limited
export presign) -> post result. Needs a live AE node to build + verify.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 09:54:42 +03:30
soroush.asadi 93411da462 feat(presets): pre-fill the user's project from preset values (A4)
Build backend images / build content-svc (push) Failing after 30s
Build backend images / build file-svc (push) Failing after 30s
Build backend images / build gateway (push) Failing after 31s
Build backend images / build identity-svc (push) Failing after 31s
Build backend images / build notification-svc (push) Failing after 30s
Build backend images / build render-svc (push) Failing after 30s
Build backend images / build studio-svc (push) Failing after 31s
"Use this example" now actually fills the new project, not just stores a ref.

- studio-svc: CreateProjectAsync applies the chosen preset story's saved values
  after the template-graph copy. ApplyPresetValuesAsync reads
  content.preset_stories.scenes_spa = { values: {contentKey:value},
  colors: {elementKey:hex} } and overlays them onto studio.saved_scene_contents
  (by key) + saved_shared_colors/saved_scene_colors (by element_key, is_selected).
  Keys are globally unique (AE convention) so key-only matching is safe.
  Malformed scenes_spa is skipped (defaults kept). Runs in the create tx.
- admin UI: ProjectPresetStories raw scenes_spa textarea replaced with a
  structured PresetValueEditor — loads each preset scene's content elements +
  the project's shared colours and renders a type-aware input per item
  (text/textarea/number, media→upload, fill/color→colour). Serializes to
  scenes_spa {values,colors}; parses it back on edit.

Verified e2e: authored a preset with values+colour → used it → the new
project's saved_scene_contents + saved_shared_colors carry the preset values
(which the B2 render binder then writes into AE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 06:49:22 +03:30
soroush.asadi ab568c0663 feat(presets): admin preset stories (premade example videos) end-to-end
Build backend images / build content-svc (push) Failing after 31s
Build backend images / build file-svc (push) Failing after 31s
Build backend images / build gateway (push) Failing after 31s
Build backend images / build identity-svc (push) Failing after 30s
Build backend images / build notification-svc (push) Failing after 30s
Build backend images / build render-svc (push) Failing after 31s
Build backend images / build studio-svc (push) Failing after 31s
Epic A — admins author premade example videos per template; users pick one
on the template detail page to start a pre-filled project.

Backend (content-svc):
- PresetStory DTOs + PresetStoryService (admin CRUD + public published-only
  filter via role check + soft-delete) + PresetStoriesController (/v1/preset-stories)
- DI registration; gateway route /v1/preset-stories (optionalAuth, public read)

Frontend:
- ProjectPresetStories admin authoring UI (name/description/demo upload/published/
  sort + scene picker with order+duration + advanced scenes_spa); «ویدیوهای نمونه»
  button + modal in ProjectsAdmin
- TemplateDetailExamples renders real published stories (image/video preview,
  hover → "use this example" → creates a pre-bound project), falls back to
  placeholders when none; selected aspect's variant id keys the fetch
- public /api/preset-stories route; preset_story_id plumbed through
  createProjectFromTemplate + projects POST route; usePreset i18n (fa+en)

Verified: full CRUD via gateway (public hides unpublished); creating a project
with presetStoryId persists selected_preset_story_id on the saved project.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 05:24:14 +03:30
soroush.asadi 23624f7db9 feat(admin): auto-fill new scene length from the AEP
Build backend images / build content-svc (push) Failing after 2m22s
Build backend images / build file-svc (push) Failing after 1m49s
Build backend images / build gateway (push) Failing after 1m6s
Build backend images / build identity-svc (push) Failing after 59s
Build backend images / build notification-svc (push) Failing after 50s
Build backend images / build render-svc (push) Failing after 54s
Build backend images / build studio-svc (push) Failing after 55s
When adding a scene in the admin scene editor, its duration is now pulled
from the After Effects project automatically (scene key = comp name).

frontend (ProjectScenes):
- the new/edit scene form quick-scans the project .aep for comp names +
  durations and offers a "pick composition" dropdown that fills key, title
  and default duration in one click
- the key field gains a datalist of comp names; typing a key that matches a
  comp auto-fills the length (only when empty, never clobbering a manual value)
- an inline "AEP duration: Ns — insert" hint next to the duration field
- graceful states when no .aep is uploaded / scan fails

render-svc (aep.durationFromCdta): fix the composition-duration offset.
The duration rational lives at cdta offset 44 (numerator) / 48 (time base)
on AE 2024/2026, not 32/36 (previous guess) or 40/44 (boltframe reference,
older builds). Made it version-robust: read the time base from the framerate
dividend (offsets 4/8) and accept whichever offset places the time base right
after the numerator. Verified against a real project — render comp frfinal
parses to 15.02s (matches project_duration_sec 15.00).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 22:22:39 +03:30
soroush.asadi da3f92fbe8 feat(admin): full legacy controller set in scene-inputs editor
Build backend images / build content-svc (push) Failing after 2m19s
Build backend images / build file-svc (push) Failing after 1m18s
Build backend images / build gateway (push) Failing after 2m38s
Build backend images / build identity-svc (push) Failing after 6m44s
Build backend images / build notification-svc (push) Failing after 1m0s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 59s
The V2 scene-inputs editor only exposed ~15 of the content model's
~40 fields. Restore full parity with the legacy admin controller.

content-svc:
- SaveContentElementRequest + ContentElementResponse widened to the
  complete field set (text/font, direction/RTL, media, advanced, DP)
- ApplyElement / ToElementResponse map every field 1:1
  (Enum.TryParse for JustifyKind + AiInputType)

frontend (SceneInputsEditor):
- common fields up top; an "advanced" toggle reveals grouped sections:
  Text and Font, Direction (RTL/LTR), Media, Advanced, Design-Presets (DP)
- editing an element loads the full field set; rows show font/hidden badges
- nullable numbers sent as null, enums as named values (snake_case body)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:38:54 +03:30
soroush.asadi bf6c04aba3 fix(render): node reports progress → moving bar + ETA (was stuck 0%/Preparing)
Build backend images / build content-svc (push) Failing after 58s
Build backend images / build file-svc (push) Failing after 45s
Build backend images / build gateway (push) Failing after 52s
Build backend images / build identity-svc (push) Failing after 54s
Build backend images / build notification-svc (push) Failing after 56s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 49s
The node's onProgress callback only LOGGED — it never POSTed, so render_progress stayed
0 and step stayed Preparing (no bar, no ETA). Add render-svc POST
/v1/internal/render/jobs/{id}/progress (UpdateJobProgress: set render_progress + bump
step Queued/Preparing→Rendering once >0) + client UpdateProgress + wire onProgress to
post it (8s best-effort timeout, AE-CPU/DB-starvation tolerant). Preview already posts;
real-frame preview is epic C.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:51:01 +03:30
soroush.asadi 2879198dec fix(studio): accept numeric scene/content ids (template inputs now load)
THE bug behind 'nothing changed': parseScene required a STRING id, but the V2
relational scene assembly serializes numeric ids (scene 23, content 136) — so every
template scene was rejected → 0 scenes → studio fell back to its default 2-layer
title/subtitle. Coerce numeric ids to string. Verified: insta-promo project now
parses 1 scene / 6 layers (frl_c1t1-t4 text + frl_c1m1/m2 media).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:27:53 +03:30
soroush.asadi 04ca431fbc docs(handoff): #36/#40/#41/#42 done; remaining = epic A/C + B follow-ups
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 05:42:27 +03:30
soroush.asadi 9e16638b2d feat(#40): Persian (Jalali) date pickers in admin
Add react-multi-date-picker + a reusable PersianDateInput (displays Jalali, stores
Gregorian ISO so DTOs are unchanged). Replace admin date inputs: UserProfileEdit
birthdate + CRM date-range filters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 05:39:46 +03:30
soroush.asadi fca6bcac53 feat(#41): admin/renders pagination + user name link + output + project name
Build backend images / build content-svc (push) Failing after 1m1s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 49s
Build backend images / build identity-svc (push) Failing after 50s
Build backend images / build notification-svc (push) Failing after 49s
Build backend images / build render-svc (push) Failing after 1m2s
Build backend images / build studio-svc (push) Failing after 47s
render-svc admin-renders enriches jobs with user_name/email (cross-schema lookup to
identity.users). Page adds prev/next pagination (page_size 30). Table adds User column
(name → /admin/users?q=email) and Output column (export → /admin/exports), and shows
project_name. Verified: 21 jobs, paginated, names resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 05:24:47 +03:30
soroush.asadi d56bcf1b23 feat(#42): FIX projects can't add scenes (studio + admin)
Build backend images / build content-svc (push) Failing after 57s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 54s
Build backend images / build identity-svc (push) Failing after 1m0s
Build backend images / build notification-svc (push) Failing after 47s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 57s
Template copy now carries choose_mode from the content project → studio store gets
chooseMode; AddSceneMenu returns null for FIX/MusicVisualizer. Admin ProjectScenes
hides '+ صحنهٔ جدید' (shows an 'scenes defined in AE' note) for fixed modes. Verified
choose_mode=FIX flows end-to-end. (Visible admin nav link added earlier.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 05:03:46 +03:30
soroush.asadi bccebbd006 feat(render #36): real per-tier output height (360/540/720/1080/4K)
Build backend images / build content-svc (push) Failing after 50s
Build backend images / build file-svc (push) Failing after 57s
Build backend images / build gateway (push) Failing after 50s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 48s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 1m2s
r_height was hardcoded 1080. render-svc now derives r_height from the resolution
(ResolutionHeight) on job create; node-agent ffmpeg downscales to the tier height
(scale=-2:H). Quality picker now actually changes output size.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 04:35:14 +03:30
soroush.asadi c6766b18a1 docs(handoff): phase B done (B1+B2) — edit→render binding
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 01:26:15 +03:30
soroush.asadi 47a4ced973 feat(render B2): render binder writes user edits into AE before render
Build backend images / build content-svc (push) Failing after 52s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 1m29s
Build backend images / build notification-svc (push) Failing after 1m38s
Build backend images / build render-svc (push) Failing after 1m53s
Build backend images / build studio-svc (push) Failing after 56s
Edits previously never reached the MP4 (the node rendered template defaults). Now:
- render-svc claim includes the saved input values as bindings (GetRenderBindings →
  saved_scene_contents with non-empty value).
- node-agent: new binder.go emits a JSON bind-spec + downloads media locally, runs the
  pre-existing data-driven bind.jsx via afterfx (sets text layers' Source Text, replaces
  media footage), saves a bound.aep next to the template, then aerender renders THAT.
- 12-min timeout + fresh-AE + done-marker polling (mirrors scan). Non-fatal: on bind
  failure the job still renders template defaults.

Verified binding data flows (edited frl_c1t1/frl_c1t2 → claim bindings). Live MP4
verification needs the updated node-agent.exe re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 01:22:20 +03:30
soroush.asadi a69bc62724 feat(studio B1): persist input edits to content elements (render-binding foundation)
Build backend images / build content-svc (push) Failing after 1m3s
Build backend images / build file-svc (push) Failing after 1m5s
Build backend images / build gateway (push) Failing after 1m0s
Build backend images / build identity-svc (push) Failing after 1m8s
Build backend images / build notification-svc (push) Failing after 57s
Build backend images / build render-svc (push) Failing after 1m6s
Build backend images / build studio-svc (push) Failing after 1m3s
Studio edits previously went only to edit_state; the render binds saved_scene_contents,
so edits never reached the MP4. Add studio-svc PATCH /v1/saved-projects/{id}/contents
(update saved_scene_contents.value/value_file_id by content key, ExecuteSqlInterpolated
for null-safe params) + Next /api/projects/[id]/contents route + persistence hook pushes
edited values (from bridged c-<key> layers) alongside the scene_data save. Verified
text persists incl. UTF-8 Persian (9 chars/17 bytes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:53:17 +03:30
soroush.asadi d4b1fbd9e6 docs(handoff): next-up = studio↔template binding epic, start phase B (edit→render)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:09:35 +03:30
soroush.asadi 4d32e77f9a fix(studio): show ALL template inputs (bridge V2 content-elements → layers)
The studio parser required scene.layers; a template-created project's scene_data
carries content-elements (scene.contents), so every scene parsed to null and the
editor fell back to the default 2-layer title/subtitle scene. Now parseScene bridges
contents → editable layers (Text→text, Media→image), so all of a scene's inputs
appear (e.g. c1 → 6: 4 text + 2 media). Scene name/duration also read V2 fields.

Remaining studio↔template epic (separate): edit→content-element→AE-render binding,
real AE scene-preview thumbnails, and FIX-mode (hide add-scene).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:57:44 +03:30
soroush.asadi 99f0e9eab1 fix(home): 'use template' card opens template detail page (was → /templates)
Homepage TemplateGallery card called the old Supabase createVideoProject which
failed → fell back to /templates. Now it routes to /templates/{slug} (detail),
where the user picks aspect/style and starts the project. Removed dead handler/imports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:44:23 +03:30
soroush.asadi 1aca734343 feat(admin): scene-inputs editor in /admin/projects scene list (reuse SceneInputsEditor)
The per-scene inputs (content elements) editor existed only on /admin/templates
(SceneColorEditor). /admin/projects → «صحنه‌ها» used the older ProjectScenes which
had no inputs panel, so admins couldn't see/edit a scene's inputs there. Export
SceneInputsEditor and add an «ورودی‌ها» expander per scene row in ProjectScenes
(GET/POST/PUT/DELETE /v1/scene-elements, 15 element types). Verified c1 → 6 inputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:33:06 +03:30
soroush.asadi 8b716a173c fix(images): allow MinIO host in next/image remotePatterns (broken uploads)
next.config had no images.remotePatterns, so the optimizer rejected every remote
URL with HTTP 400 → all MinIO-hosted images (avatars, template art) showed broken.
Add remotePatterns derived from NEXT_PUBLIC_MINIO_URL + dev hosts (172.28.144.1/
localhost/minio :9000). Verified /_next/image → 200 image/jpeg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:19:39 +03:30
soroush.asadi 36d70332f0 feat(nav): visible Admin Panel link in navbar for admins
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:10:51 +03:30
soroush.asadi d7a74daa96 feat(render): 5 quality tiers (360p–4K) + ETA on render page; 24h session
- Render page: resolution picker now 360p/540p/720p/1080p/4K; live ETA
  ('تقریباً … باقی مانده') computed from progress rate; preview+progress bar already wired.
- render-schemas: resolution enum + RESOLUTION_DIMENSIONS add 360p/540p.
- render-jobs.mapQuality: 5-tier → render_quality (Low/Medium/High/Full).
- Session: Jwt__AccessTokenMinutes=1440 (24h) via compose so logins persist
  (refresh middleware + 30d refresh token back it up).

(Real per-tier output height still pending: render-svc r_height is hardcoded 1080 →
node ffmpeg scale — next.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 23:04:32 +03:30
soroush.asadi ad8796a25d feat(admin): edit any user's full profile (PATCH/POST /v1/users/{id} admin + UI modal)
Build backend images / build content-svc (push) Failing after 1m47s
Build backend images / build file-svc (push) Failing after 5m54s
Build backend images / build gateway (push) Failing after 2m8s
Build backend images / build identity-svc (push) Failing after 3m32s
Build backend images / build notification-svc (push) Failing after 12s
Build backend images / build render-svc (push) Failing after 10m27s
Build backend images / build studio-svc (push) Failing after 10s
Identity: admin-only PATCH /v1/users/{id} (reuses UpdateMeAsync) + POST {id}/avatar.
Admin Users panel: «پروفایل» modal to view/edit name/slogan/about/company/website/
country/national-code/birthdate/gender/avatar for any user. Verified admin→other-user edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:36:23 +03:30
soroush.asadi 6ee211fb35 feat(studio): copy repeaters, characters/controllers, color presets into editable project
Build backend images / build content-svc (push) Failing after 1m46s
Build backend images / build file-svc (push) Failing after 2m32s
Build backend images / build gateway (push) Failing after 1m18s
Build backend images / build identity-svc (push) Failing after 1m2s
Build backend images / build notification-svc (push) Failing after 2m59s
Build backend images / build render-svc (push) Failing after 6m12s
Build backend images / build studio-svc (push) Failing after 4m14s
Extends CopyTemplateGraphAsync: repeater children flatten into saved_scene_contents
(repeater_item_key/index via repeater_items); scene characters+controllers and color
presets+items copied, correlated by (new scene, original-id/sort) since studio tables
lack original-id columns. studio character.key is a uuid → store original char id.
No regression on templates without these (copy 0 rows). All enum cols cast ::text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:14:28 +03:30
soroush.asadi f8631fbbc4 fix(admin): auto-promote uploaded AEP to the render bucket on attach
Uploading an .aep/.zip in the template editor only set content.projects.aep_file_url
(a user-uploads reference) — it never copied the file to templates/{id}/ where the
render node-agent's claim looks. Result: uploaded templates weren't renderable.

attachAep now also POSTs /v1/template-bundles/{project_id} {source_url} after saving
the reference, which server-side-copies the file into templates/{id}/(bundle.zip|
template.aep). Uploading a template now makes it immediately renderable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:53:54 +03:30
soroush.asadi 076c2e577f fix(render): resolve template id for render jobs + mock-fallback when no .aep
Build backend images / build content-svc (push) Failing after 1m30s
Build backend images / build file-svc (push) Failing after 1m23s
Build backend images / build gateway (push) Failing after 5m47s
Build backend images / build identity-svc (push) Failing after 1m23s
Build backend images / build notification-svc (push) Failing after 1m51s
Build backend images / build render-svc (push) Failing after 1m23s
Build backend images / build studio-svc (push) Failing after 1m23s
THE bug behind "AEPFilePath is required for real AE render": CreateJob inserted
original_project_id = saved_project_id (VALUES $3,$3), so the claim looked for the
render bundle at templates/{saved_project_id}/ — which never exists. The bundle
lives at templates/{TEMPLATE_id}/. Now original_project_id is resolved from
studio.saved_projects.original_project_id (the template the project was built from).
(Direct-SQL test renders masked this by setting the template id explicitly.)

Also harden the node-agent: Run() falls back to mock render when AEPFilePath is
empty even if AE is installed (previously hard-errored), so a missing/un-promoted
template degrades gracefully instead of failing the job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:37:59 +03:30
soroush.asadi 62807f5f41 fix(node-agent): resilient output upload — 60s HTTP timeout + 4× retry on upload-URL
After a CPU-heavy AE render+transcode the orchestrator/DB can be briefly slow;
the 15s client timeout made the post-render output-upload-url call fail and the
finished MP4 was dropped (completed without export). Bumped client timeout to 60s
and retry the upload-URL call up to 4× with backoff so a finished render's output
is never lost to a transient stall.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:57:09 +03:30
soroush.asadi e59f07df4e fix(node-agent): transcode AE render to MP4 with ffmpeg (real renders deliver MP4)
aerender can't reliably write H.264 directly in modern AE — it renders the
project's output module (Lossless AVI/MOV) and ignores the .mp4 extension,
producing a multi-GB .avi the agent then failed to find/upload.

- findRenderedOutput(): locate the file aerender actually wrote (output.avi/.mov/.mp4)
- transcodeToMP4(): ffmpeg → H.264 yuv420p + AAC + faststart; drops the lossless
  intermediate. ffmpeg located via $FFMPEG_PATH, beside the agent exe, or PATH.
- Graceful fallback: if ffmpeg is missing/fails, upload the raw render so the job
  still delivers a (large but valid) file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 07:50:43 +03:30
soroush.asadi 077b5ac5d5 fix(render): export INSERT used wrong column + lowercase enum labels (the real 500)
Build backend images / build content-svc (push) Failing after 59s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 59s
Build backend images / build identity-svc (push) Failing after 53s
Build backend images / build notification-svc (push) Failing after 55s
Build backend images / build render-svc (push) Failing after 59s
Build backend images / build studio-svc (push) Failing after 54s
The output-upload-url 500 was NOT the enum cast — it was:
1. INSERT referenced original_project_id; the exports table column is project_id
2. file_type/create_type literals were lowercase ('video'/'render') but the
   export_file_type/export_create_type enums are PascalCase ('Video'/'Render')

Verified the corrected INSERT against the live schema. Now real renders produce
a downloadable export instead of completing with export=nil.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 07:31:15 +03:30
soroush.asadi ddc0a2d0d9 feat(admin): manually edit scene inputs (content elements)
Scene content elements (the editable Text/Media/Color/… inputs inside a scene)
had no CRUD — only AEP-import created them, so admins couldn't define or edit
them. Added full management:

content-svc:
- SceneElementsController: GET/POST/PUT/DELETE /v1/scene-elements?scene_id=
- SceneColorService: Get/Create/Update/DeleteContentElementAsync
- ContentElementResponse + SaveContentElementRequest (key, title, type,
  default_value, hint, position, text-box/font/media flags)
gateway: route /v1/scene-elements/*path → content
frontend: SceneColorEditor scenes tab → per-scene "ورودی‌ها" expander with full
  add/edit/delete of inputs (15 element types: Text/Media/Color/Number/…)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 06:54:22 +03:30
soroush.asadi 9d499a89de fix(render): real AE render — pass -comp, fix export insert, ensure exports bucket
Three bugs surfaced bringing up a real After Effects node (verified: AE 2026
claimed + ran, but produced no usable output):

1. aerender got no -comp/-rqindex → "output argument ignored", nothing rendered.
   - Claim now returns comp_name from content.projects.render_aep_comp (e.g. "frfinal")
     via new Store.GetTemplateCompName; threaded through ClaimedJob → runner.Job →
     aerender args (`-comp <name>`, or `-rqindex 1` fallback when unknown).

2. CreateExportForJob INSERT passed render_quality as a bare param into an enum
   column → 500 ("output-upload-url HTTP 500"), so completed renders had no export.
   - Cast $8::render.render_quality (+ explicit casts for file_type/create_type enums).

3. flatrender-exports bucket didn't exist → uploads would fail anyway.
   - render-svc now MakeBucket(exports, templates) idempotently at startup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:40:20 +03:30
soroush.asadi d8d0f6c363 chore: gitignore node-agent local build dir + agent.env secrets 2026-06-05 22:26:20 +03:30
soroush.asadi 2a6bbcd408 fix(render-page): register completion without requiring a download URL
Build backend images / build content-svc (push) Failing after 56s
Build backend images / build file-svc (push) Failing after 29s
Build backend images / build gateway (push) Failing after 41s
Build backend images / build identity-svc (push) Failing after 5m32s
Build backend images / build notification-svc (push) Failing after 1m18s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 1m5s
The full-screen render page only transitioned to "completed" when status was
completed AND an outputUrl existed, so dev renders (which produce no export file)
polled forever at 100%. Now completion is driven by status alone; the download/
share buttons render only when a URL is present, otherwise a "dev render, no file"
note is shown. Same guard helps real renders whose export URL resolves a beat late.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:13:26 +03:30
soroush.asadi 43d0e10543 fix(render+studio): dev mock worker (unstick the queue) + lock predefined layers
Render — "stuck in Queued" fix:
- Jobs were created Queued and only a Windows AE node could claim them, so in the
  dev stack (no node) they queued forever.
- New devworker package: in-process mock worker drives Queued jobs through the steps
  with progress + live preview frames → Done. Enabled via RENDER_DEV_WORKER (default
  true in compose; set false in prod where real nodes claim jobs).
- db: DevClaimNextQueued (atomic oldest-queued → Preparing) + UpdateJobStepProgress
- Verified live: a stuck job advanced Preparing→Done in ~10s with frontend polling.

Studio — predefined template structure:
- Projects are always copied from a template; structure is fixed. Users customise
  existing layers, they don't add new ones.
- New studio-config flag ALLOW_ADD_LAYERS (false): StudioToolbar (add text/image/
  video/shape) returns null; SceneEditSidebar "add text layer" button hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 22:10:05 +03:30