Commit Graph

181 Commits

Author SHA1 Message Date
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
soroush.asadi 81912cac66 feat(render): full-screen render page, one-active-render limit, app-wide progress
Build backend images / build content-svc (push) Failing after 14s
Build backend images / build file-svc (push) Failing after 1m28s
Build backend images / build gateway (push) Failing after 1m43s
Build backend images / build identity-svc (push) Failing after 3m0s
Build backend images / build notification-svc (push) Failing after 51s
Build backend images / build render-svc (push) Failing after 1m3s
Build backend images / build studio-svc (push) Failing after 1m1s
Concurrent-render ceiling (a user runs 1 render at a time unless granted more):
- Identity: TokenService emits max_renders claim from User.ParallelRenderingCeiling
- Identity: admin POST /v1/users/{id}/render-slots (AdminService.SetRenderSlotsAsync,
  clamped 1..50) — gamification or admin raises a user's ceiling
- render-svc: middleware reads max_renders (default 1); CreateJob rejects with 409
  active_render_limit when active jobs >= ceiling
- render-svc: db.CountActiveJobs + ListActiveJobs; GET /v1/renders/active returns
  in-flight renders + can_start_new

Full-screen render page (replaces the modal):
- /studio/render/[projectId]: config (resolution/fps) → live preview + progress →
  download; resumes this project's in-flight render on mount; blocks when another
  render is active; reads ?preset=
- StudioTopBar export menu now navigates to the page; RenderModal deleted (dead)

App-wide minimal progress:
- GlobalRenderProgress pill mounted in the locale layout for authed users; polls
  /api/render/active every 4s, shows thumbnail + step + % on every page, click →
  the render page; hidden on the render page and when idle

Admin: UserActions gains a "concurrent render slots" control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:48:05 +03:30
soroush.asadi 2918b7acbf feat(admin/media): folders in the media library
- admin-files: fetchFolders / createFolder / deleteFolder + FolderItem; fetchFiles
  takes a folderId filter
- admin files upload route forwards target_folder_id so uploads land in the open folder
- FileManager: breadcrumb navigation, folder cards (open / delete), "+ new folder",
  folder-scoped file listing + upload. Folders hidden while searching (search spans all)

Uses the file-svc folder API (GET/POST/DELETE /v1/folders, folder_id list filter)
that already existed but had no UI. "Pick from library" was already wired via
FilePicker in FileUploadField.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:34:56 +03:30
soroush.asadi 1142c38c62 feat(editor+trimmer): save output to cloud account via V2 File service
- New /api/files/upload: generic user-scoped Browser→Next→MinIO upload
  (presign → PUT → confirm), 200MB cap, image+video only, returns public URL
- image-editor-export: stageToBlob() + saveStageToCloud(); "Save to my account"
  button in the Image Editor export popover
- Trimmer: "Save to my account" button uploads the trimmed clip blob
- i18n: saveToCloud/savingToCloud/savedToCloud/saveToCloudFailed in fa+en
  (parity 1002/1002)

Connects the two client-side editors to V2 storage — output now lands in the
user's account instead of only a local download.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:29:03 +03:30
soroush.asadi 52be5be93f feat(node-agent): production ops kit — Windows service + WireGuard mesh
config:
- LoadEnvFile(): reads agent.env beside the exe (or $AGENT_ENV_FILE) before env,
  so the sc.exe service needs no per-service environment plumbing; real env wins

deploy/ (new):
- build-windows.ps1     cross-compile → dist\ + stage the deploy kit
- agent.env.example     fully documented config template
- install-service.ps1   register as auto-start Windows service (native sc.exe),
                        crash-restart 3×/5s, no NSSM dependency
- uninstall-service.ps1 stop + remove
- wireguard-node.conf.template + setup-wireguard.ps1  node dials out only, no
                        public IP / inbound rules; tunnel installed as boot service
- README.md             full control-plane + node walkthrough, ops table, troubleshooting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:20:48 +03:30
soroush.asadi 67060c73b2 feat(admin): discount edit/delete + project-scoped scene/color editor
Identity (discounts):
- DiscountsController: PUT /v1/discounts/{id}, DELETE /v1/discounts/{id}
- DiscountService.UpdateAsync (partial update, code-clash guard) + DeleteAsync
- UpdateDiscountRequest record (all fields optional incl. is_active)
- Frontend discountsConfig: canEdit + canDelete + is_active field

Content (scenes/colors — UI for existing CRUD endpoints):
- New SceneColorEditor.tsx: 3-tab modal (scenes / shared-colors / color-presets),
  project-scoped, full add/edit/delete per tab, colour pickers + palette item editor
- Wired into TemplatesAdmin: "صحنه‌ها و رنگ‌ها" button per template variant row
- Routes through the generic admin proxy with ?project_id=

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:16:13 +03:30
soroush.asadi ac700787bd docs: session handoff + portable gotchas (account/machine change)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:55:02 +03:30
soroush.asadi 5b2617d621 fix(studio): crypto.randomUUID crash on non-secure origins (http LAN IP)
Build now (and every studio/image editor id generation) called crypto.randomUUID,
which is undefined outside a secure context — so over http://<LAN-IP> (used because
localhost is VPN-hijacked) the click threw 'crypto.randomUUID is not a function' and
the spinner hung forever, never reaching the editor.

Add lib/uuid.ts (crypto.randomUUID → crypto.getRandomValues → Math.random fallback)
and use it in studio-store, image-editor-store, project-defaults, dev-mock-project.

Verified headless (Chrome over http://172.28.144.1): Build now → 201 → navigates to
/studio/video/<id> → editor renders Scene 1 with editable title/subtitle fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:36:57 +03:30
soroush.asadi 8ab86a5cc6 feat(templates): aspect-ratio picker drives which variant is built
The detail page now loads a template's real published aspect variants (16:9/1:1/9:16)
from the content container and the preview chips select among them. Build now copies
the SELECTED variant's scene graph (passes that variant's content project UUID), not a
default. Selection is lifted to TemplateDetailContent and shared by the preview picker
and the build button; the preview box reflects the chosen aspect.

Verified on insta-promo (16:9 + a duplicated 1:1 variant): both chips render, and
building 1:1 copies the 1:1 project's scenes (1 scene, 6 fields).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:08:11 +03:30
soroush.asadi 0ca11f19dd feat(studio): copy template scene graph into editable project (use-template works)
Build backend images / build content-svc (push) Failing after 53s
Build backend images / build file-svc (push) Failing after 51s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 45s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 53s
Build now created an EMPTY project: (1) the studio binds camelCase but the frontend
sent snake_case → original_project_id dropped to Guid.Empty; (2) CreateProjectAsync
never copied scenes. Now:
- saved-projects.ts sends camelCase (originalProjectId/copyDefaultValues).
- /api/projects resolves the container slug → first published variant content project.
- StudioService.CreateProjectAsync deep-copies the content scene graph (scenes +
  content elements + scene colours + shared colours) into the new saved project via
  one atomic cross-schema SQL copy (enum cols cast to text; temp scene-id map).

Verified: insta-promo → 1 scene, 6 content fields, 4 shared colours, loadable by the
studio editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:27:25 +03:30
soroush.asadi baf6e40dde fix(templates): wire template detail page to real content service
/templates/[id] only searched the hardcoded demo catalog, so real published
containers (e.g. insta-promo) 404'd even though the browser listed and linked them.
Now resolveTemplate() fetches the container by slug via fetchProject(), falling back
to the demo catalog, else notFound(). Page + generateMetadata made async (await params).

Also fix TemplateDetailBreadcrumb: it called server-only getTranslations while
rendered inside the client TemplateDetailContent tree (500 at request time) — switched
to the useTranslations hook. Was latent because demo pages were static-prerendered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 06:45:02 +03:30
soroush.asadi d4fee8d1d7 feat(profile): role-aware nav + avatar menu + full editable profile
Build backend images / build content-svc (push) Failing after 1m59s
Build backend images / build file-svc (push) Failing after 3m18s
Build backend images / build gateway (push) Failing after 3m28s
Build backend images / build identity-svc (push) Failing after 2m1s
Build backend images / build notification-svc (push) Failing after 4m45s
Build backend images / build render-svc (push) Failing after 5m18s
Build backend images / build studio-svc (push) Failing after 2m12s
Navigation:
- UserMenu (avatar + role-aware dropdown: Dashboard, Admin Panel for admins,
  Profile, Sign out) replaces Sign In/Try Free when logged in (desktop + mobile).
- Real avatars in dashboard sidebar + a new admin-shell profile section.
- Shared Avatar primitive (image with initials fallback). SiteChrome excludes /admin.

Profile (data-collection surface for future AI video generation):
- SettingsProfile rebuilt: avatar upload + slogan, about, company, website,
  country, national code, birthdate, gender. No resume builder (per scope change).
- /api/profile forwards all fields; new user-scoped /api/profile/upload (avatar →
  MinIO via file-svc, sets avatar). Identity UpdateUserRequest/UserResponse widened
  (country/national/method); no DB migration (columns already exist).
- fa+en strings; verified GET/PATCH round-trip + logged-in SSR render.

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