Commit Graph

168 Commits

Author SHA1 Message Date
soroush.asadi 8ddca5647b feat(studio): Phase 3 — scene reorder + numeric duration + FIX/FLEXIBLE gating
Wires the scene-list operations users asked for into the existing timeline
(model-agnostic — works for any scene, layer- or block-based):

- SceneThumbnailBlock: now sortable (@dnd-kit useSortable) with a left-edge grip
  handle (listeners only on the handle so select/rename/resize still work); adds a
  numeric per-scene duration input (commit on blur/Enter, clamped 1–30s) next to
  the drag-resize; a `locked` prop makes it read-only.
- SceneThumbnailStrip: wraps the blocks in DndContext + SortableContext
  (horizontal, 6px pointer-activation so clicks/resize aren't hijacked) and calls
  the existing reorderScenes store action; gates add/browse + reorder/duplicate/
  delete/duration behind isFixedSceneMode(chooseMode).
- studio-store: isFixedSceneMode() helper (single source for FIX vs FLEXIBLE).
- i18n: reorderScene / durationLabel / secondsUnit in fa + en.

Verified with `npm run build` (rules-of-hooks clean). NOTE: a THEME PICKER and
FlexStory block-FIELD editing are deferred — the studio editor is Konva-layer-
centric, so both need a FlexStory-aware editing path (a follow-up), not this phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:18:00 +03:30
soroush.asadi 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>
2026-06-23 13:45:04 +03:30
soroush.asadi 2104dd3c84 feat(remotion): theme system + CharacterJourney pilot template
- src/scenes/themes.ts: 6 curated themes (the cohesion rail) — pick one, then
  tweak the 4 brand colors; every block derives its shades so a theme re-skins
  the whole video coherently (verified: same journey rendered in warm-editorial
  vs berry-pop by overriding only the 4 colors).
- src/scenes/presets.ts: CHARACTER_JOURNEY — the pilot template's scene list
  ("Idea → struggle → tool → win", 7 beats) as a FlexStory preset.
- briefs/character-journey.md: the filled Template Spec from the guided brief.
- Root.tsx: register CharacterJourney per aspect (FlexStory + the preset).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:19:51 +03:30
soroush.asadi d830c56ea0 feat(remotion): FlexStory scene engine — ordered editable scene-blocks (Phase 1)
Turns a template into an ordered list of editable scene blocks instead of one
monolithic composition — the foundation for the scene-based template engine
(all Renderforest-style types, per-scene editable duration, add/duplicate/
delete/reorder). Render-side only; backend wiring is Phase 2.

- src/scenes/types.ts: SceneInstance/BlockProps/SceneBlock + withDefaults/clamp.
- src/scenes/chrome.tsx: shared 2.5D Three.js backdrop (parallax camera, blobs,
  particles, optional 3D confetti) + grain/vignette/progress/kicker/transition.
- src/scenes/blocks/*: Core 6 blocks — TitleCard, CharacterScene (full room +
  vendored CC0 character behind a desk), ImageCaption, KineticQuote, Slideshow,
  OutroCTA — each with editable fields + its own duration range.
- src/scenes/registry.ts: the block registry (blockId -> block).
- src/compositions/FlexStory.tsx: the sequencer — stacks blocks in <Sequence>,
  clamps per-scene duration, and computes composition length dynamically via
  calculateMetadata (so add/delete/reorder/duration all flow to the render).
- StoryScenes.tsx: the 2.5D story proof this productizes; docs/TEMPLATE_BRIEF.md:
  the guided creator flow + Template Spec.

Verified: all 6 blocks render via FlexStory in 16:9/1:1/9:16; a custom props
override (reordered scenes, custom characters/durations/colors) renders correctly
and the total length tracks Σ per-scene durations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:45:57 +03:30
soroush.asadi fd364209e7 feat(coming-soon): hard-lock the live curtain; closable only on local/dev hosts
The curtain was sessionStorage-dismissible everywhere. NODE_ENV can't tell the
live deploy from the local Docker site (both are prod builds), so gate on the
hostname instead: localhost + private LAN ranges (incl. 172.28.x) keep the
"view experimental (local)" close button; any public domain is hard-locked to
just the countdown. Starts the curtain up by default so the live site never
flashes a page before it mounts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:45:16 +03:30
soroush.asadi cb6512fee3 feat(remotion): asset-library catalog + Phase 0 (license firewall, @remotion/lottie, 30 CC0 characters)
- docs/ASSET_LIBRARY.md: curated catalog from the asset sweep (91 sources -> 62
  usable) + completeness-critic reality check; clean CC0/MIT tier, license/geo
  traps, and the 2.5D layered-scene plan (sky->room->furniture->device->character
  ->grain) to fix the "naked scene".
- deps: add @remotion/lottie@4.0.290 (runtime) + DiceBear (build-time devDep).
- scripts/gen-dicebear.mjs: generate 30 CC0 Open-Peeps characters OFFLINE (no
  runtime CDN) into public/illustrations/dicebear/ + a per-file assets.json ledger.
- scripts/check-assets.mjs: license-firewall CI guard — fails on any un-ledgered
  vendored asset.
- AssetSheet dev composition: proves vendored SVG -> staticFile() -> Remotion render
  (30 real characters render cleanly).
- NOTE: GitHub (Open Peeps/IRA/Notion git clones) + Gumroad (Lukasz) are geo-blocked
  headless here; those + Humaaans (Figma export) need a manual/mirror fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:59:03 +03:30
soroush.asadi a3152ee84f feat(remotion): premium CharacterStory template (13 flexible scenes) + fix detail-page SSR
- CharacterStory: refined flat-illustration character (gradient-shaded sweater,
  modern hair, calm minimal face), muted editorial palette (coral/teal/sand/navy),
  abstract environment (soft depth blobs, ground "stage", sparse particles,
  vignette + grain), scene-number kicker. Verified in 16:9/1:1/9:16 and all poses.
- seed: 13 editable scene cards (c1..c13, keys s{N}_title/s{N}_text) via new
  MULTISCENE path; per-aspect previews; muted defaults.
- assets: 3 thumbnails + 4 preview MP4s vendored into public/template-media.
- fix: load BrandedVideoPlayer (plyr-react) client-only via next/dynamic
  (ssr:false) — plyr touches `document` at import, which was 500-ing every
  template detail page during SSR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:58:48 +03:30
soroush.asadi 863b9503b3 fix(detail+docker): per-aspect template preview + Debian frontend base
CI/CD / CI · Web (tsc) (push) Successful in 1m17s
CI/CD / Deploy · full stack (push) Failing after 15s
- Template detail page now shows the render matching the SELECTED aspect (poster +
  preview video) instead of the 16:9 cover cropped into a 9:16/1:1 box. TemplateVariant
  carries per-aspect image/previewVideo; fetchTemplateVariants + the detail page wire them.
- AppShowcase3D ships a distinct preview video per aspect (seed PERASPECT_VIDEO).
- Frontend Dockerfile: Alpine -> node:20-slim (glibc). Fixes next-swc ("ld-linux..."
  load failure that broke `next build` once libc6-compat was removed) AND the original
  CI Alpine-CDN issue. Healthcheck switched to node (slim has no wget).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:04:04 +03:30
soroush.asadi 60759f35b4 polish(remotion): shiny titanium finish on AppShowcase3D phone
CI/CD / CI · Web (tsc) (push) Successful in 1m22s
CI/CD / Deploy · full stack (push) Failing after 14s
Polished-metal look: low-roughness (0.15) titanium + contrasty studio Environment
(light bases + bright softbox strips) so the rounded edges catch hot reflection
streaks that sweep as the phone rotates; shinier side buttons. Re-rendered all
aspects + preview, redeployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:00:26 +03:30
soroush.asadi 1795bc855b feat(remotion): premium 3D app-showcase template (AppShowcase3D)
CI/CD / CI · Web (tsc) (push) Successful in 1m19s
CI/CD / Deploy · full stack (push) Failing after 12s
- New @remotion/three template: titanium flagship phone (thin rim, glossy black
  glass, rounded-corner screen via ShapeGeometry, dynamic island, side buttons),
  light keynote studio (contact shadow + env reflections + DOF + soft bloom),
  film grain + entrance light-sweep. All 3 aspects re-flowed.
- Editable screenUrl (user app screenshot textured onto the screen via TextureLoader
  + delayRender), appName/tagline/cta, 4 colours (dark text on light bg).
- Add pick(wide,square,tall) helper to lib/aspect.ts (Tier-0 from the R&D).
- Seed: AppShowcase3D + per-template text colour; built with the flat-artist skill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:28:49 +03:30
soroush.asadi f83d657844 chore(skills+remotion): add flat-artist skill bundle; register 3D templates
CI/CD / CI · Web (tsc) (push) Successful in 1m19s
CI/CD / Deploy · full stack (push) Failing after 12s
- .claude/skills/flat-artist: the bundled FlatRender template-creation suite
  (orchestrator + 16 sub-skills + design/motion R&D), mirrors the Gitea AISkills repo.
- services/remotion Root.tsx/templates.tsx: register the 3D templates + Three3DTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 19:39:25 +03:30
soroush.asadi cb11c177a7 fix(ci): stop pulling Alpine packages from the geo-blocked CDN
CI/CD / CI · Web (tsc) (push) Successful in 1m21s
CI/CD / Deploy · full stack (push) Failing after 15s
The CI server can't reach dl-cdn.alpinelinux.org (TLS error) — only the Nexus
mirror is reachable, and it proxies Docker images, not apk packages.

- frontend: drop `apk add libc6-compat` (vestigial Next.js-template line; the
  deps stage only runs `npm ci` and the build/runtime stages never had it).
- 5 Go services (file/gateway/notification/payment/render): replace
  `apk add ca-certificates tzdata` with copying ca-certificates.crt from the
  golang builder stage + embedding tzdata via `go build -tags timetzdata`.
  No more apk -> no dependency on the Alpine CDN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:08:16 +03:30
soroush.asadi af3c73c560 feat(templates): branded Plyr video player for demos with download protection
CI/CD / CI · Web (tsc) (push) Successful in 1m24s
CI/CD / Deploy · full stack (push) Successful in 8m14s
- BrandedVideoPlayer (plyr-react) plays template demo videos with FlatRender
  blue branding (Plyr CSS vars) and NO download control.
- Download protection: no download button, native controls replaced, underlying
  <video> gets controlsList="nodownload" + disablePictureInPicture, and the
  right-click context menu is blocked.
- Template detail page uses it; gallery hover-preview cards get the same
  nodownload / no-context-menu / no-PiP guards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:44:55 +03:30
soroush.asadi 4f04f6bf75 feat(render+templates): Remotion engine, 16 branded templates (incl. 3D), seconds pricing, coming-soon
CI/CD / CI · Web (tsc) (push) Successful in 1m21s
CI/CD / Deploy · full stack (push) Failing after 20s
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>
2026-06-21 15:52:52 +03:30
soroush.asadi b9b91397b0 fix(deploy): configurable postgres host port (avoid 5432 conflict)
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Successful in 3m58s
fr2-postgres failed to start after another local project's postgres grabbed host
port 5432 during downtime. The internal stack always connects via postgres:5432 on
the docker network, so the published host port is only for external tooling — make
it ${PG_HOST_PORT:-5532} to avoid the clash. (Also recovered from a stale bind-mount
where scripts/init-db.sh had become a directory; current compose mounts
deploy/postgres-initdb/.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 01:38:33 +03:30
soroush.asadi 6d79ddb8d1 feat(render): real progress %, ETA, and frequent preview during AE renders
The render page already displayed progress/ETA/preview — but the node agent never
fed real data: aeRender used fake +5%/10s increments, discarded aerender stdout,
and pushed a preview only every 30s. (Plus the deployed agent predated even the
progress-reporting wiring.)

node-agent (aeRender):
- Capture aerender stdout; parse "(N):" current frame + "N frames"/"to N" total.
- Real percentage when total is known (5–90%, headroom for transcode/upload),
  else a smooth time-asymptotic estimate that never sticks — message shows the
  live frame number either way.
- Push a preview frame ~every 8s (was 30s) so the box fills in quickly.

render-svc:
- GET /v1/renders/:id/progress now computes eta_seconds from started_at + progress
  (linear extrapolation) instead of returning null.

frontend:
- Thread eta_seconds → status route → render page; page prefers the server ETA and
  falls back to the client-observed rate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 01:18:54 +03:30
soroush.asadi 23d1fd8fb1 fix(payment): default broker host port to 1607 (8090 was allocated on the server)
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Successful in 3m6s
2026-06-16 07:28:05 +03:30
soroush.asadi 376cdf6a1c feat(payment): route FlatRender plan purchases through the broker
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Failing after 11m4s
- identity: when FlatPay (broker) is configured, InitiateZarinPalAsync
  routes through pay.flatrender.ir instead of calling ZarinPal directly;
  new HandleBrokerCallbackAsync confirms the payment via the broker
  inquiry API (authoritative, not trusting the redirect) and activates
  the plan. New public endpoint GET /v1/payments/callback/broker
  (already public at the gateway via /callback/*). Env-gated — empty
  FlatPay__ApiKey keeps the legacy direct-ZarinPal path.
- broker: deliver webhooks inline on enqueue (best-effort) in addition
  to the retry loop, so clients credit near-instantly (db.GetWebhook +
  goroutine kick).
- compose + ENV_FILE: FlatPay__* for identity (FLATPAY_FLATRENDER_*).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 00:34:45 +03:30
soroush.asadi ec51e87d2d feat(payment): standalone ZarinPal broker on pay.flatrender.ir
A generic multi-client payment gateway so FlatRender, meezi.ir and
bargevasat.ir can all pay through ZarinPal's single verified callback
domain (pay.flatrender.ir).

New Go service services/payment (clones the notification skeleton +
vendored deps):
- migration 31_payment_broker.sql — `payment` schema: client_apps,
  transactions, webhook_deliveries.
- ZarinPal v4 client ported from the proven identity PaymentService
  (request.json -> StartPay -> verify.json; codes 100/101).
- client API: POST /v1/pay/request + /v1/pay/inquiry, authed by
  X-Api-Key + HMAC body signature; GET /callback/zarinpal (the single
  verified endpoint) verifies, then 302s the user back to the site's
  return_url (signed) and fires a signed, retried webhook.
- per-client ZarinPal merchant override (default = shared merchant);
  amount stored canonically in Rial, unit to ZarinPal env-configurable.
- admin API /v1/admin/* (FlatRender admin JWT): client-app CRUD +
  key issue/rotate + transactions list.

Deploy wiring: payment-svc in docker-compose.v2.yml (host port 1607),
pay.flatrender.ir server block in mirror-nginx conf, ENV_FILE +
README updates (cert SAN + manual migration note).

Admin UI: src/components/admin/PaymentsAdmin.tsx (client apps with
one-time key reveal + rotate, transactions table) + /admin/payments
page + nav link + fa/en strings; pay-admin proxy route to payment-svc.

Docs/SDK: deploy/PAYMENTS.md (integration contract) + deploy/sdk/flatpay.js
(zero-dep Node client + webhook verifier) for meezi/any site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:59:54 +03:30
soroush.asadi 896ce3dfa9 feat(render): plan-gate quality tiers — free=360p watermarked, paid=all
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Successful in 3m8s
Monetization gate for the template render flow:
- render-quality.ts: single source of truth (free -> 360p only +
  watermark; pro/business -> 540p..4K, no watermark).
- /api/render: server-authoritative gate — rejects >360p for free
  users with 403 quality_locked; passes a watermark flag through
  createRenderJob -> /v1/renders (render-svc passthrough, wired later).
- /api/render/limits: GET endpoint exposing the user's allowed tiers
  and watermark state to the studio.
- render page: locks higher tiers for free users (dashed + lock badge,
  click routes to /pricing), clamps the selected resolution down,
  shows the "free = 360p + watermark, upgrade" notice, and handles the
  403 quality_locked response.

AI-video "no free preview" rule is a future hook (no AI gen yet).
Watermark rendering (ffmpeg drawtext on the node) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:17:25 +03:30
soroush.asadi 468ae2ae97 docs(deploy): fix init-script path + add stale-volume reset note
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Successful in 3m10s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:48:51 +03:30
soroush.asadi 1106c03feb docs(deploy): sync nginx/cert/DNS docs with the real working setup
CI/CD / CI · Web (tsc) (push) Successful in 1m12s
CI/CD / Deploy · full stack (push) Has been cancelled
Reflect what the live deploy actually required:
- cert must be NESTED under an already-mounted dir (/etc/ssl/soroushasadi/flatrender/)
  — mirror-nginx mounts cert dirs individually, so a fresh /etc/ssl/flatrender is
  invisible in the container.
- after a sed -i edit of the bind-mounted nginx.conf, restart (not reload) — inode swap.
- DNS: box is behind NAT (171.22.25.73 private; public via edge/CDN 185.239.1.100 or
  direct 31.171.101.x) — register the domain the same way the other sites enter.
- local SNI test command to verify routing bypassing DNS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:46:32 +03:30
soroush.asadi 514cd3705f ci(deploy): mount postgres init as a DIRECTORY (fix 'Is a directory')
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Successful in 2m50s
The single-file bind mount ./scripts/init-db.sh left a stale empty dir in the
reused act_runner workspace → mounted as a directory → migrations never ran →
empty schemas → backend 28P01/connection failures. Move the init script to
deploy/postgres-initdb/00-init.sh and mount the whole DIR at
/docker-entrypoint-initdb.d (robust, like the migrations dir). Deploy checkout
now 'git clean -ffd' to purge stale workspace dirs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:39:15 +03:30
soroush.asadi c67d746004 ci: redeploy after resetting stale pgdata volume (fresh init + migrations)
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 20s
2026-06-12 21:17:34 +03:30
soroush.asadi 072ac78b77 ci(deploy): pull minio -cpuv1 from Liara docker mirror (baseline CPU)
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 20s
soroushasadi mirror only has cached x86-64-v2 minio builds; Liara
(docker-mirror.liara.ir) back-fills the -cpuv1 variants. Confirmed pullable +
runs on the server CPU. MINIO_REGISTRY defaults to Liara, MINIO_IMAGE_TAG to a
real -cpuv1 release; dev overrides both to plain Docker Hub :latest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:09:26 +03:30
soroush.asadi 0fefedbb86 ci(deploy): use minio -cpuv1 image for baseline-CPU server
CI/CD / CI · Web (tsc) (push) Successful in 1m8s
CI/CD / Deploy · full stack (push) Failing after 1m6s
The server CPU lacks x86-64-v2 (started being required at minio RELEASE.2023-11-01).
MinIO publishes '-cpuv1' variants compiled for plain x86-64. Pin to
RELEASE.2025-09-07T16-13-09Z-cpuv1 — same release as local dev, runs on the old CPU.
Override via MINIO_IMAGE_TAG (dev = latest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 20:39:19 +03:30
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