The backend cancel was solid (CancelJob/StopJob; the dev worker abandons a cancelled
job, a real node kills its process) — but the UI couldn't reach it: the render page
had NO cancel button, and the global progress pill's X only HID the pill (the job kept
running). So a render couldn't actually be stopped from where you watch it.
- Render page: a prominent «لغو رندر» button while a render is in flight (Queued or
Running); cancelRender() calls /renders/:id/cancel and returns to config optimistically.
The poll now also handles a `cancelled` status (when stopped from another surface).
- Global pill: the X now CANCELS the render (with confirm) instead of just hiding it —
so any in-flight render is cancellable from any page.
- (Dashboard MyRenders already had a working cancel.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hotspot overlay used fixed percentages on a w-full/h-auto image, so a 9:16 scene
ballooned vertically and the placeholders (tuned for landscape) floated off the image.
Now the still is CONTAIN-fit inside the measured area (portrait + landscape both fit,
no overflow) and the hotspot overlay is anchored to the fitted image rectangle, so
placeholders always track and scale with the image. Hotspot positions are aspect-aware
(tall vs wide) and clamped to stay on the still.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The card + detail read template.sceneCount, but the API never sent one — so the
frontend mapper hardcoded sceneCount:0 for every DB-backed template.
- content-svc: ContainerSummaryResponse + ContainerDetailResponse now carry
SceneCount. The list computes it with one grouped query (scenes per aspect project,
max across aspects); the detail loads scenes and counts them.
- frontend: V2ContainerSummary.scene_count → AdminProject.sceneCount → the catalog
card/detail (adminProjectToCatalogTemplate no longer hardcodes 0).
Verified on the live local API: fr-instagram-promo → 5, single-scene templates → 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move a template fully between environments (local → live): container, projects, all
scenes + editable fields, shared colours/layers, its categories & tags, and the asset
files.
- export_template.py <slug> → a self-contained bundle (template.json + assets/). One
SQL query captures the whole tree as JSON; assets are resolved from template-media
references and copied in. Source DB via PSQL env (default = local docker).
- import_template.py <bundle> → idempotent SQL (pipe to target psql). Replaces by slug
via one cascading delete (all content.* FKs are ON DELETE CASCADE), recreates rows
verbatim (UUIDs preserved → FKs intact), merges categories/tags BY SLUG so they line
up across DBs. --assets-to copies media; docker cp / mc cp hints for remote.
- TEMPLATE_BUNDLES.md documents it.
Round-trip tested on fr-instagram-promo: DB → bundle → DB restores identical
3 projects / 15 scenes / 138 fields and field values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/seed_instagram_promo.py — dedicated seeder for the 5-scene FlexStory IG
promo (the generic seeder only handles CharacterStory's 2-text-per-scene shape).
Scenes keyed `<BlockId>__<n>` (render decodes the block from the key); each scene's
content-elements are that block's real fields (Text + Media); colours as shared.
render_remotion_comp = FlexStory-<asp> so GetFlexStoryProps routes it.
- public/template-media/InstagramPromo* — thumbnails, per-scene stills, preview MP4.
Applied to local fr2-postgres + fr2-frontend: container fr-instagram-promo (3 aspects,
15 scenes, 138 fields), served by the gateway and the /templates detail page (200).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- igkit: a Media component that detects video by extension and renders a frame via
OffthreadVideo (muted), else an Img — so any post slot takes images or reels.
- IGProfile: the profile-page grid is now editable — 6 post media fields (was static
colour placeholders); videos get a ▶ reel badge.
- IGFeed: post slots now accept video too; labels say «عکس/ویدیو».
Verified: a profile still with an image cell + a video cell + avatar image renders
both correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reference-round workflow, run end to end for a real template:
Taste system (how we learn the user's taste, persisted):
- references/TASTE_PROFILE.md (living design contract) + references/README.md (the
daily loop) + a "reference round" stage in docs/TEMPLATE_BRIEF.md (provide refs or
I suggest+mock directions).
Design-quality before/after:
- HeroDemo — the fix recipe vs the faint default: layered-depth background, a proper
big video type scale, and a bold composed focal object. (Backgrounds were naked,
text too small, scenes had no objects.)
- YaldaSofreh3D + IGPromoDirections + IGProfileMock — reference-match proofs
(low-poly 3D, 3 IG-promo style directions, the realistic IG-light page).
Instagram channel-promo template (the deliverable — a flexible 5-scene FlexStory):
- igkit + 5 blocks: IGIntro, IGProfile (realistic IG-light profile, scales to all
aspects), IGFeed (post grid), IGStats (animated count-up), IGFollowCTA (Follow taps
to "Following").
- FlexStory gains a `finish` toggle so the IG-light scenes render clean (no brand
grade). INSTAGRAM_PROMO preset + 3 aspect comps in Root.
Verified: a still of every scene at 9:16 renders clean; full preview MP4 rendering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The real visible quality leap — a handmade craft aesthetic code can't fake by being
smooth:
- craft.ts: useStopMotion (quantize the frame to "on twos/threes" + per-step jitter
→ choppy handmade motion), paperShadow (layered cast shadows for paper depth),
PAPER_TEXTURE (procedural fibrous paper grain).
- PaperCut block: a layered paper-cut landscape — sun + 4 brand-coloured paper hills
with real cast shadows + paper grain, rising into place on stop-motion timing with
an idle wobble, + paper-cut title/subtitle. Re-flows 16:9/1:1/9:16.
Registry now has 13 blocks. Verified: warm Yalda render (fits the Persian/seasonal
moat) + a stop-motion demo clip showing the on-threes choppy rise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The single highest-ROI quality lift — one finish applied at the FlexStory level
lifts all 12 blocks at once, no per-block change:
- GRADE_FILTER: a headless-safe colour grade (contrast/saturation/lift) applied as
a CSS `filter` on the content root — backdrop-filter does NOT render in headless
Chrome, so the grade lives on the content, not an overlay.
- FinishPass: split-tone (cool-shadows multiply + warm-highlights screen) + a soft
brand duotone + top light-bloom, layered over each scene.
- Installed @remotion/lottie@4.0.290 (artist-made animations — next lever).
Verified: visible richer/graded look on CharacterScene + Slideshow, subtle enough
to suit the muted palette, consistent across blocks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unlocks the biggest catalog gaps by composition:
- BarChart: animated infographic bars (value + label, normalized, staggered grow).
- Stomp: punchy beat-synced typography — words slam in with overshoot + shake +
accent impact bar (titles / fashion / openers).
- DeviceMockup: phone/browser frame holding the user's screenshot + title/caption
(app / website promo); placeholder when no image.
Registry now has 12 blocks. All verified via FlexStory props-override stills.
docs: CATALOG_PLAN.md (the full template taxonomy + production map + build waves;
the Persian/Islamic occasions = the moat) and PREMIUM_TOOLCHAIN.md (the stop-motion/
paper-cut/premium tool plan; editable-backdrop architecture; Iran/OFAC reality).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the misleading flat Konva canvas (for FLEXIBLE/Remotion templates) with a
real preview the user can edit against:
- ScenePreview shows the scene's rendered still (scene.image) centred, and overlays
labelled, clickable HOTSPOTS over each editable field (logo / text), positioned by
a layout heuristic tuned to our blocks (visual centred, text stacked below).
- Clicking a hotspot selects that field; BlockFieldForm highlights + scrolls to the
matching field (and focusing a field highlights its hotspot) — "click the logo to
edit it" works both ways.
- CanvasEditor branches to ScenePreview when isFlexStoryProject(); AE/Konva
templates keep the full editor.
Fixes: (1) clicking a scene now shows its real image centre-screen; (2)/(3) the logo
and text are visible placeholders you can click to edit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the per-scene loop video all the way to the scene card:
- studio-svc: SavedSceneResponse now includes Demo (was stored + copied but never
serialized); MapSceneResponse passes s.Demo.
- Scene type gains image?/demo?; parseScene reads them from the loaded scene data.
- SceneThumbnailBlock shows scene.image as the still and plays scene.demo (muted,
looped) on hover, resetting on mouse-leave.
Existing projects backfilled (saved_scenes.image/demo from content.scenes). Both
services rebuilt + deployed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each scene now carries both a still (content.scenes.image) AND a short ~1.5s LOOP
video (content.scenes.demo), so the studio scene cards show a looping preview, not
a static swatch.
- LOOP_SCENES = {CharacterStory, LogoMotion3D}: their scenes get a dedicated
per-scene loop ({tid}-{asp}-c{n}-loop.mp4 / {tid}-{asp}-loop.mp4); other
templates fall back to their full preview MP4.
- Renders 42 loops: LogoMotion3D ×3 (frames 30–74) + CharacterStory 13 scenes ×3
aspects (frames (n-1)*90+20 … +64), each a 45-frame / 1.5s clip mid-scene.
- Seed sets image + snapshot_url + demo per scene; verified all 42 serve 200 and
the DB wires each scene to its own still + loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
For FLEXIBLE (Remotion / FlexStory) templates the render uses fixed positions —
dragging or resizing a layer on the Konva canvas does nothing to the output, which
is confusing. Make the canvas a read-only PREVIEW for those projects: the Konva
Layer is listening=false (no drag/select/transform), the Transformer is hidden, and
the auto-thumbnail capture is skipped so the flat Konva snapshot can't overwrite the
real rendered per-scene image. Editing happens only through the field form
(BlockFieldForm). AE/Konva templates are unchanged. Gated on isFlexStoryProject().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scenes were seeded with just a scene_color_svg swatch — content.scenes.image /
snapshot_url were empty, so the studio/admin scene previews showed swatches, not
the actual scene. Now every scene gets a real rendered image:
- single-scene templates → their per-aspect thumbnail;
- multi-scene templates → one still per scene, captured at that scene's own frame.
Adds the 39 CharacterStory per-scene stills (13 scenes × 3 aspects), each rendered
at (sceneIndex*90 + 45). LogoMotion3D's single scene now points at its thumbnail.
Verified: DB image/snapshot_url populated, all per-scene images serve 200, and the
stills are distinct per scene (c7 = «یک مانع», kicker ۰۷/۱۳).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First template built through the new flow (brief → quality-gate approval → build →
seed → deploy). Tech/3D logo motion: a 3D metallic card + radial light rays + lens
flare + bloom (genuine @remotion/three), with the user's uploaded logo composited
on the card as a reliable HTML <Img> (renders any SVG/PNG/data-URI; static camera
keeps it aligned), brand text + tagline, grain. Falls back to a branded play-mark
when no logo is set. Re-flows across 16:9/1:1/9:16.
- LogoMotion3D.tsx registered per aspect in Root.tsx.
- Seeded as fr-logo-motion-3d: text fields (brandText, tagline) + a logoUrl image
upload field + the dark-tech palette (light text) + per-aspect previews.
- 3 thumbnails + 3 preview MP4s rendered, deployed; detail page + assets serve 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the product/app-showcase template type the engine was missing: a 2.5D device
frame (rounded phone with notch, or a browser window with traffic-lights + URL bar)
holding an uploaded screenshot, with title/subtitle and the shared Three backdrop.
Fields: screenshot, title, subtitle, device (phone|browser). Registry now 9 blocks.
Verified via FlexStory props-override stills (both device modes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From the audio-sourcing-sweep (45 sources verified). The load-bearing test —
can a paid SaaS render the audio into customers' MP4s AND vendor the file —
rules out almost all "royalty-free" libraries; only CC0/PD passes cleanly.
- USE (CC0, vendorable): FreePD (music; site dead → archive.org/details/freepd),
Kenney.nl (SFX; the one clean-from-Iran source), Freesound-CC0, OpenGameArt-CC0.
- CAUTION: incompetech CC-BY (needs attribution pipeline), aggregators (verify
per-track), Sonniss/Pixabay (render-input-only, never vendor raw).
- AVOID/reference-only: Mixkit/Uppbeat/Bensound/Envato/Zapsplat/… (clauses + OFAC).
- Persian = no clean CC0 bulk source → commission + self-CC0 long-term.
Real files need a VPN/non-Iran fetch (acquire-once-then-vendor makes the licence
perpetual); only the 4 self-authored ffmpeg stubs are vendored today. Firewall
rules mirror the illustration assets.json + check-assets guard (already scans audio).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds audio to the scene engine without any third-party/geo-blocked sourcing: the
beds + SFX are synthesized with ffmpeg, so they're license-free (CC0, self-authored)
and need no acquisition — the same play as self-authoring Lottie.
- public/audio/: music-ambient.mp3 (soft 3-tone pad, looped) + sfx-whoosh/pop/chime.
- FlexStory: optional music/musicVolume/sfx props (optional so the existing render
binding needs no change). Renders <Audio loop> for the bed + a whoosh at each
scene start and a chime on the final scene, driven by precomputed scene starts.
- check-assets: now also scans public/audio (+ lottie) with folder-prefixed keys;
assets.json ledgers the 4 audio files (CC0 self-authored).
Verified: tsc clean; a 6s FlexStory render produces an MP4 with a real audio stream
(ffprobe: codec_type=audio). NOTE: these are placeholder/SFX-grade; a premium
curated music library (by vibe) is a separate sourcing sweep, and the studio music
picker → FlexStory `music` prop is a follow-up wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the theme→render gap: the studio theme picker now actually drives a
FlexStory render's colours. GetFlexStoryProps reads saved_shared_colors by
element_key (accentColor/secondaryColor/backgroundColor/textColor), but the studio
only wrote the theme into scene_data — so the picker never reached the MP4.
- studio-svc: UpdateSharedColorsAsync upserts saved_shared_colors by (project,
element_key) + PATCH /v1/saved-projects/{id}/shared-colors endpoint +
UpdateColorsRequest/UpdateColorItem. Mirrors UpdateSceneContentsAsync. (dotnet
build: 0 errors.)
- gateway already wildcard-routes /v1/saved-projects/*path → studio-svc (no change).
- Next: /api/projects/[id]/colors route → gateway; project-api patchProjectColors
+ themeColorsFromSceneData (maps scene_data sceneAccentColor… → the colorSchema
keys); performSave best-effort pushes the 4 colours alongside contents.
Chain: theme picker → store → scene_data → performSave → patchProjectColors →
gateway → studio-svc upsert → saved_shared_colors → GetFlexStoryProps → render.
Verified: Next build + dotnet build both clean; theme presets render cohesively
across all 6 (incl. dark Midnight). End-to-end studio→render needs the live stack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scene-engine (FLEXIBLE) projects now get a clean per-field content editor instead
of the Konva layer panel. The scoping confirmed content VALUES already flow to
saved_scene_contents via the existing `c-`-layer + updateLayer + autosave path —
so this is purely a cleaner presentation over the working save path, no new
persistence.
- isFlexStoryProject(chooseMode) helper (FLEXIBLE → scene engine).
- BlockFieldForm: renders one labelled field per content layer (label from
layer.name — the field's Persian label, already preserved from the content
title), text→textarea, image→upload; writes back via the unchanged
updateLayer(props) call. No Konva geometry/layer chrome.
- StudioSidebarContent: the "scenes" tool branches on chooseMode — FlexStory →
BlockFieldForm, AE/Konva → SceneEditSidebarContent (zero regression).
- i18n: componentsStudioSidebarBlockFieldForm in fa + en.
Verified `npm run build`. NOTE: preview stays the live Konva canvas for v1 (a true
@remotion/player embed is deferred — 8–12MB Three.js bundle). Remaining: confirm
the FlexStory render binder reads the 4 theme colours from scene_data (already
persisted) vs saved_shared_colors (would need a small colours endpoint).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grows the scene-block library toward full template-type coverage:
- LogoReveal: premium logo-motion — spring scale-in + glint sweep over the logo
(image upload or a branded play-mark placeholder) + brand text + tagline, on the
shared 2.5D Three backdrop. Fields: logoUrl, brandText, tagline.
- StatCounter: animated count-up to a target (English-digit value → Persian
display) + suffix + label. Fields: value, suffix, label.
Registry now has 8 blocks. Both verified via FlexStory props-override stills.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the studio's 2-color palette to the full 4-color brand theme
(accent / secondary / background / text) matching the Remotion SceneColors,
so the studio's colour state maps 1:1 to the scene engine.
- studio-store: add sceneSecondaryColor + sceneTextColor + their setters + an
applySceneTheme(accent,secondary,background,text) action (sets all four +
recolours canvas layers: bg→background, overlays→secondary, shapes→accent,
text→text explicitly); persist both new fields in hydrate + getSceneDataForSave.
- studio-scene-data: carry sceneSecondaryColor + sceneTextColor through
VideoPersistedSceneData / build / parse (with defaults).
- ColorsCustomTab: 6 one-click theme presets (Warm/Berry/Midnight/Ocean/Sunset/
Mono) + 4 manual colour inputs + Apply.
- i18n: secondaryColor/textColor/themePresets/applyTheme(+preset) in fa + en.
Verified with `npm run build`. NOTE: the theme persists in scene_data and
recolours the canvas; wiring the 4 colours all the way to a FlexStory render's
saved_shared_colors depends on the studio-svc shared-colour sync (a small
follow-up). Block-FIELD editing remains the Phase 4 follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- 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>
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>
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>
- 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>
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>
- 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>
- .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>
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>
- 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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>