f8ea9af3b67bd98ee16843f23f138fbf2e5cfcef
20 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f8ea9af3b6 |
feat(render): Phase 2 — FlexStory render passthrough + journey template seed
Closes the render boundary so a user's scene list (order, per-scene content,
per-scene duration, theme) actually drives the FlexStory engine — the one gap the
scene-engine mapping found.
- render-svc GetFlexStoryProps (db.go): structured per-scene query that groups
saved_scene_contents BY scene (the flat GetRenderBindings union collides when
scenes share keys like "title"), recovers blockId from the scene key
("<BlockId>__<n>"), and emits the FlexStory props object
{scenes:[{blockId,durationSec,props}], accentColor, …}.
- render-svc Claim (internal.go): when the template is Remotion + comp starts with
"FlexStory", send that object as a single "__flexprops__" binding (no protocol
struct change).
- node-agent remotionProps (remotion.go): if "__flexprops__" is present, pass it
to `remotion render --props` verbatim (it's the complete props object).
- scripts/seed_flexstory.py: seeds the CharacterJourney template (7 scenes, theme
colours, FLEXIBLE) with blockId-encoded scene keys, so the studio's existing
CopyTemplateGraphAsync copies them into saved_scenes with zero studio-svc change.
Both Go services compile; template is live in the catalog (detail 200, per-aspect
previews). End-to-end render verification needs a live Remotion render node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
4f04f6bf75 |
feat(render+templates): Remotion engine, 16 branded templates (incl. 3D), seconds pricing, coming-soon
Render engine - Add Remotion (code-based) as a 2nd render engine alongside After Effects. node-agent dispatches on Job.Engine; RunRemotion maps bindings -> --props, renders native then ffmpeg-scales to the quality tier (aspect-preserving). - content.projects.render_engine + render_remotion_comp (migration 32); render-svc claim resolves engine and routes (skips .aep for Remotion). - Admin TemplatesAdmin gains an engine picker + Remotion composition id field. Template pack (services/remotion) - 16 branded, Persian (Vazirmatn), color- and text-editable templates, each in 3 aspects (16:9 / 1:1 / 9:16): LogoMotion, Opener, InstaPromo, YouTubeIntro, Slideshow, HappyBirthday, SalePromo, QuoteCard, EventInvite, Countdown, GlitterReveal (editable logo image), NowruzGreeting (animated characters), and 4 cinematic 3D templates via @remotion/three (Hero3D, Nowruz3D, Birthday3D, Promo3D) with reflections + bloom/DOF/vignette. - scripts/seed_remotion_templates.py seeds containers/projects/scenes/colors. Pricing - Rewrite /pricing to the seconds-based model (charge = length x resolution), data-driven from /v1/plans, Toman, broker checkout. Coming-soon - Persian experimental-build overlay on all pages (launch date + countdown). Fixes - middleware matcher bypasses all static asset paths; catalog mapping passes cover image + preview video so real thumbnails render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
010f975a0e |
fix(nodes): add disk columns to ListNodes SELECT (scanNodes 37/39 mismatch → 500)
Build backend images / build content-svc (push) Failing after 50s
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 58s
Build backend images / build notification-svc (push) Failing after 58s
Build backend images / build render-svc (push) Failing after 51s
Build backend images / build studio-svc (push) Failing after 59s
The disk-column append only hit GetNodeByID (whitespace differed); ListNodes lacked last_disk_pct/disk_total_gb, so the node list 500'd and rendered empty. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0a7dd9b84c |
feat(nodes): live CPU/RAM/disk monitoring in the node list
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 54s
Build backend images / build notification-svc (push) Failing after 53s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 51s
- node-agent: internal/metrics — read CPU% (GetSystemTimes), RAM (GlobalMemoryStatusEx), disk used%/total (GetDiskFreeSpaceEx) via stdlib kernel32 (no external dep; windows build + non-windows stub). Heartbeat now reports cpu_pct/ram_available_mb/disk_used_pct/ disk_total_gb + ae_running. - render-svc: heartbeat persists last_disk_pct + disk_total_gb (migration 29); RenderNode model + node SELECT/scan carry them. - admin: rewrite NodesTable to the real RenderNode shape (fixes a pre-existing items/V2Node mismatch that left the list empty) + a CPU/RAM/disk bars column + stale-heartbeat flag. - assets-bundle ingestion: ProjectMediaBundle (jszip) auto-maps project.zip → project/scene image/demo/colour + music; PatchProject gains image/full_demo/shared_colors_svg. - scan: RGBA (4-number) colours recognised + frshare single-int controls detected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1f52f53cf7 |
feat(render+identity): daily render-limit — consume on submit, refund on admin-stop
Build backend images / build content-svc (push) Failing after 51s
Build backend images / build file-svc (push) Failing after 53s
Build backend images / build gateway (push) Failing after 1m1s
Build backend images / build identity-svc (push) Failing after 48s
Build backend images / build notification-svc (push) Failing after 42s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 1m13s
Business rule: each user has a daily render limit. Admin-stop refunds the used
charge (not the user's fault); a user's own cancel does not.
- identity: ConsumeRenderChargeAsync / RefundRenderChargeAsync on DailyRemainRenderCount
with lazy daily reset (mig 24: daily_renders_reset_at). Convention: max=0 ⇒ UNLIMITED,
so existing 0/0 users keep rendering until an admin sets a real limit.
- identity InternalController (service-token): POST /v1/internal/render-charge/{consume,refund}
- render-svc: identityclient + on Create consume (block 429 when limit reached, fail-open
on identity outage); on admin Stop refund the job owner; user /cancel unchanged
- compose: IDENTITY_URL for render-svc, ServiceToken for identity-svc
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
7f7feabb85 |
feat(render+admin): stop a render job (admin, any owner)
Build backend images / build content-svc (push) Failing after 1m0s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 1m20s
Build backend images / build notification-svc (push) Failing after 1m13s
Build backend images / build render-svc (push) Failing after 1m5s
Build backend images / build studio-svc (push) Failing after 1m0s
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>
|
||
|
|
c076345ceb |
feat(render+admin): delete render node
Build backend images / build content-svc (push) Failing after 1m7s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 55s
Build backend images / build identity-svc (push) Failing after 56s
Build backend images / build notification-svc (push) Failing after 1m7s
Build backend images / build render-svc (push) Failing after 53s
Build backend images / build studio-svc (push) Failing after 59s
- render-svc: DELETE /v1/nodes/:node_id (store.DeleteNode + handler + route) - admin: حذف button per node row + DELETE /api/admin/nodes/[nodeId] proxy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
bcc69f0a2e |
feat: complete node-agent pipeline, TLS proxy, billing cancel, password reset
Node-agent — full render pipeline (items 1-3):
- render-svc: ClaimedJob now includes aep_download_url (presigned MinIO GET,
2h TTL, path=templates/{original_project_id}/template.aep)
- render-svc: POST /v1/internal/render/jobs/:id/output-upload-url
allocates Export row + returns presigned MinIO PUT URL + export_id
- render-svc: db.CreateExportForJob() inserts export row with 30-day retention
- render-svc: InternalHandler now owns minio client (templatesBucket + exportsBucket)
MINIO_TEMPLATES_BUCKET env var (default flatrender-templates)
- node-agent: runner/download.go — DownloadFile() + UploadFile() (stdlib only)
- node-agent: client.GetOutputUploadURL() + ClaimedJob.AEPDownloadURL field
- node-agent: runJob() full flow: download AEP → render → get upload URL →
PUT output to MinIO → Complete(export_id)
All steps are non-fatal with fallback (AEP miss → mock, upload fail → no export)
TLS reverse proxy (item 15):
- Caddyfile: three virtual hosts (DOMAIN, API_DOMAIN, STORAGE_DOMAIN)
auto-TLS via Let's Encrypt; security headers; 512MB upload limit on API
- docker-compose.v2.yml: caddy:2-alpine service, ports 80/443/443udp,
caddy_data + caddy_config volumes; env vars DOMAIN/API_DOMAIN/STORAGE_DOMAIN/ACME_EMAIL
- .env.v2.example: new Caddy + MINIO_TEMPLATES_BUCKET entries
Billing portal (item 5):
- Identity: POST /v1/users/me/plan/cancel — sets cancelled_at, auto_renew=false
(access continues to expiry); 404 when no active plan
- POST /api/billing/cancel — frontend proxy, validates auth
- GET /api/billing/portal — redirects to /dashboard/settings?tab=billing
- SettingsBilling: "Cancel plan" button with confirm dialog + optimistic UI,
"Change plan" button; becomes "use client" component
Password reset UI (item 7):
- POST /api/auth/password-reset — proxies /v1/auth/password/reset/request
(always 200, anti-enumeration)
- POST /api/auth/password-reset-confirm — proxies /v1/auth/password/reset/confirm
- AuthPageContent: "Forgot password?" link on sign-in tab opens 2-step reset flow
(email → OTP+new-password) without leaving the auth page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
d7743a6fbe |
feat: live render preview — node agent pushes PNG frames, frontend displays them in real time
render-svc: - db.UpdateJobPreview(): writes base64 PNG to render_jobs.image_preview_b64 (only on active jobs; Done/Failed/Cancelled rows ignored) - POST /v1/internal/render/jobs/:job_id/preview — node agent endpoint - Route registered under /v1/internal (nodeAuth) node-agent: - runner.PreviewFn callback type alongside ProgressFn - runner.preview.go: GeneratePreviewB64(percent, quality, resolution) — pure stdlib (image/png + encoding/base64), no external deps — 320×180 dark frame with animated progress bar + colored indicator dots - mock render: pushes a preview frame at every step (5→95%) - real AE render: pushes a preview frame every 30s - client.UpdatePreview(): POST /v1/internal/render/jobs/:job_id/preview - main.go: onPreview callback wires client.UpdatePreview() into runner.Run() frontend: - render-jobs.ts: RenderJobRow.preview_b64 field; read from progress endpoint - status/route.ts: previewB64 included in JSON response - RenderModal: aspect-ratio preview pane during polling — shows spinner until first frame arrives, then live-updates with each poll (every 3s); step label overlaid as badge bottom-right Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ee421ccc68 |
feat(render-svc+node-agent): add job-claim endpoint and build node-agent skeleton
render-svc: - db: ClaimJob() — atomic SELECT FOR UPDATE SKIP LOCKED; transitions job to Preparing, marks node Busy in a single transaction - models: ClaimJobRequest + ClaimedJob types - handlers/internal: POST /v1/internal/render/jobs/claim — 200 with job or 204 when queue empty - main: register the claim route under /v1/internal (nodeAuth) services/node-agent/ (new Go module github.com/flatrender/node-agent): - internal/config: env-var based config (NODE_ID required, sensible defaults) - internal/client: typed orchestrator HTTP client (Online, Heartbeat, ClaimJob, Complete, Fail, ReportCrash) — X-Node-Signature auth - internal/runner: AE render via aerender.exe or mock (for dev without AE) - cmd/agent/main: register online → heartbeat loop (5s) + poll loop (3s) → claim job → run render → report complete/fail; health endpoint on :7777 - Dockerfile: cross-compiles to Windows amd64 static binary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
90ac0b81d1 |
feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |