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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
/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>
Root cause of 'stuck on AE': heavy expression-driven projects take >10min for AE
to open, exceeding the scan timeout → job dies → admin UI stuck 'scanning'.
Fix: extend the stdlib .aep RIFX parser to collect every Utf8 name (ParseNames),
since FIX media placeholders are renamed footage ITEMS (frl_c1m1), not layers, and
text are layer names (frl_c1t1) — both are Utf8 chunks. QuickScan now branches on
?mode= (or auto-detects frl_ names) and scaffolds FIX scenes/elements + frd_*color
slots directly from the binary. Verified on the real final.aep that timed out in AE:
1 scene, 6 elements, 4 colors in 0.5s vs 10-min AE timeout.
Admin 'Quick scan (no AE)' is now the recommended path and passes the project mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- nav/footer/admin brand → فلترندر in fa (FlatRender in en)
- aspect-ratio 'All Sizes' option now uses t('allSizes') (همه اندازهها)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Category badges now show the live template count per category computed from the
catalog (0 → no badge), instead of hardcoded demo numbers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The public templates page and homepage gallery fell back to hardcoded demo
templates (VIDEO_TEMPLATES_CATALOG / TEMPLATES) whenever the admin list was
empty — so dummy templates showed even though the DB had none. Now both render
only real admin-sourced templates (empty when there are none). Categories are
untouched (kept as-is).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- slug fields auto-fill from the name (slugify keeps Persian + latin letters,
spaces → "-") until the slug is edited by hand; applies to all data-driven
forms (categories/tags/blogs/…) and the Templates form
- Projects page (/admin/projects) gains "+ پروژه جدید": pick a template
(container) + name/aspect/resolution/size/duration/fps/mode → POST /v1/projects.
Previously a project could only be added while editing a template.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- routing: localeDetection:false — a non-prefixed URL always serves fa (default);
English only via explicit /en/ prefix. Browser Accept-Language no longer
redirects fa pages to /en on every click.
- AdminShell + DashboardSidebarNav: use next-intl Link + usePathname (from
@/i18n/navigation) instead of plain next/link, so links preserve the current
locale and active-state matches the prefix-stripped path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /dashboard/renders: user's own render jobs (live status + progress bar + cancel)
and finished exports (thumbnail + size/duration + download); bilingual fa/en
- server lib my-renders.ts (user-scoped /v1/renders + /v1/exports via session JWT)
- user action routes: POST /api/renders/[id]/cancel, GET /api/exports/[id]/download
(presigned URL)
- dashboard sidebar: "رندرهای من / My Renders" nav item
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AdminShell: the rtl:/ltr: translate variants ([dir] selector) out-specified
lg:translate-x-0, so the sidebar stayed off-screen on desktop and the mobile
drawer couldn't open. Pin physically right + plain translate-x-full/0; content
uses lg:mr-60.
- /admin now redirects to /admin/stats (overview) instead of /admin/nodes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AdminShell: fixed RTL sidebar with grouped nav (نمای کلی / محتوا / رشد و ارتباطات
/ کاربران و مالی / فارم رندر / سیستم), active-link highlighting via usePathname,
sticky header showing the current section, mobile drawer with hamburger + overlay
- layout: build the grouped nav and render via AdminShell
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The admin render queue called the user-scoped /v1/renders (so it only showed the
admin's own jobs) and parsed items/total instead of data/meta (→ always empty).
- render-svc: GET /v1/admin-renders (admin) → ListAllJobs across users, optional
?status= filter; gateway-wired
- admin renders page now fetches /v1/admin-renders and reads data/meta correctly
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-node "جزئیات" button opens a modal with live health (status/CPU/RAM/AE/cache/
last heartbeat), a 24h CPU history mini-chart, and the recent crash log (signal,
auto-recovered, last frame, error log, log-file link). Uses existing render-svc
GET /v1/nodes/:id/health, /health/history, /crashes endpoints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- render-svc: admin-scoped store (ListAllExports / GetExportByIDAny /
SoftDeleteExportAny) + GET/DELETE/download-url under /v1/admin-exports
(admin-gated; separate prefix so it routes to render, not identity's /admin)
- gateway: /v1/admin-exports/* → render
- admin /admin/exports: paginated table of every rendered export with thumbnail,
type/quality, size, duration, dimensions, produce + expiry dates; download
(presigned URL) and delete
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AdminResource: client-side search box (matches across all fields) + 25/page
pagination with prev/next and a filtered-count footer
- bumped pageSize on server-paged configs (users/blogs/comments/discounts/music/
fonts/tags) so search/paginate covers the full set
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Push a font once → every node installs it → admin sees per-node status.
- render-svc: font_requests + node_fonts tables (mig 25); admin GET/POST/DELETE
/v1/node-fonts (with per-node status matrix); internal (HMAC) GET pending +
POST status for node-agents
- node-agent: fontSyncLoop polls pending fonts every 60s, downloads, installs
(Windows Fonts dir + registry / macOS / linux fc-cache), reports Installed/Failed
- gateway: /v1/node-fonts/* → render
- admin /admin/node-fonts: upload a .ttf/.otf → install on all nodes; per-node
Installed/Pending/Failed badges + counts + delete
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The render-queue cancel button used the owner-scoped /cancel (WHERE user_id=…),
so an admin couldn't stop another user's job. Added:
- render-svc: POST /v1/renders/:job_id/stop (admin-gated) → store.StopJob cancels
any in-progress job regardless of owner and frees the assigned node
- admin: render-queue button now "توقف" → /api/admin/renders/{id}/stop (with confirm)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-file checkboxes + "حذف موارد انتخابشده (N)" bar that deletes all selected
files in parallel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FileManager: type tabs (همه/تصاویر/ویدیوها/صدا/پروژههای AE و سایر) + name
search (uses file_type + search params the file svc already supports; type
values capitalized to match the enum), video thumbnails via <video>, AE/zip
shown under "AE و سایر"; delete + copy-URL retained
- FilePicker: reusable modal to re-choose an existing file from the library
(search + filter + click to pick)
- FileUploadField: new "از کتابخانه" button on every upload field → pick from
library instead of re-uploading; picker auto-filters by the field's accept
- shared src/lib/admin-files.ts helpers (fileUrl/isImage/isVideo/fetchFiles)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Nodes page: "+ افزودن نود" opens a full-screen form (name, region, IP, worker
port, AE version, node kind, RAM, CPU, priority) → POST /v1/nodes
- current_ae_version is now a dropdown (2025…2020, matching the ae_version DB
enum) instead of free text; node_kind is a dropdown (Shared/Dedicated/Spot)
- new POST /api/admin/nodes proxy route (forwards body; admin-gated). The backend
POST /v1/nodes existed but had no UI — you couldn't define nodes before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>