Let's create your first social posts
The Social Post Creator uses AI to write engaging posts and generate matching images, giving you a batch of ready-to-publish content in minutes.
We'll walk you through a few quick settings, then generate 3 posts so you can see how it works.
Generate Ads
Describe your offer and let AI create unique, diverse ad images — ready to download.
Your generated ads will appear here
Style Match Ads
Upload ads that are already winning and generate new ones in the same look and feel.
Your style-matched ads will appear here
Campaign History
Social Post Creator
Generate a full content calendar — AI-written posts with matching images, planned to avoid repetition across all days.
Your generated posts will appear here
Post Session History
User Management
| Username | Role | Status | Credits | Created | Actions |
|---|
Features & Changelog
What you can do with each tool, and what's new.
New Campaigns
Generate complete Facebook ad sets from a single topic. AI creates multiple ad variations with unique angles, layouts, and color palettes, then renders images with Gemini and writes social post copy for each ad.
Style Match
Feed in ads that are already performing and generate new ones that look like they came from the same designer. Add your own product shots, logo, headshots or brand references in a second box and tag what each one is. Pick how closely to match — a loose nod to the style, the same design system with a fresh layout, or a straight clone of the reference composition.
Split Test Lab
Turns a batch of ads into an actual experiment. It writes several distinct angles — each becoming its own ad set — and gives every angle a set of image variants that share identical copy, so the first round measures the creative and nothing else. Once an image wins, the next rounds hold it fixed and vary the hook, then the body, then the call to action, one at a time. Connect a Meta Ads System User token and it can build the campaign for you.
Social Posts
Create batches of social media posts powered by source material you provide — paste text, upload a document, enter a URL, or pull a YouTube transcript. Match your writing style, generate images or go text-only, and create HeyGen talking-head videos.
History
Browse and reload any past campaign or post session. Clone a previous session to reuse its settings and source material as a starting point for new content, or rename sessions to keep things organized.
Cinematic Video (admin)
Turn a storytelling script into a finished short film. The AI breaks the script into scenes and shots, builds a locked asset library of characters and locations for consistency, storyboards each shot with a suggested video model, generates keyframes and a timed animatic to check pacing, then renders and stitches every shot with per-character voices, music, and captions.
Calendar
See all your scheduled posts in one place — across GoHighLevel, Meta (Facebook & Instagram), and LinkedIn. A visual day grid shows exactly when each post is set to publish.
Changelog
- Split Test Lab: fixed “ads still have no image” blocking bring-your-own videos. The pre-push check only looked for an image, so an uploaded video was reported as missing creative and the push could not start. It now accepts a video or an image.
- Split Test Lab: long-form body copy no longer gets cut off mid-word. Body copy was hard-capped at 600 characters and sliced there without warning, so a long rewrite saved ending mid-sentence. Copy still runs short by default (the AI is still told 600), but when you ask the Rewrite box for longer, extended or long-form copy it may now go up to 2,000 characters, and the hand-edit box accepts up to 2,000. Anything over its limit is now cut at the last complete sentence rather than mid-word.
- Split Test Lab: add more creative to a bring-your-own angle. The upload used to be one drop per angle, so a test started with one video could never get its second. While an angle's round is still a draft (nothing pushed to Meta), the angle card now keeps a smaller “Add more creative” box until the angle holds its ads-per-angle limit. After the first push the round is locked, since an ad added to a delivering ad set would be judged against ads that already have days of data.
- Product Shots: generation cost shown per image. Each render tile on the project screen now shows how many ChannelCopy credits that specific image cost to generate (or tweak). Internal only — never shown on the client-facing approval link.
- Product Shots: fixed the client approval page. Clicking an image no longer picks it — it opens full-size instead, with a separate “Choose this version” button for actually picking one. Fixed every image on the page flashing on a single click (the page was rebuilding its entire HTML on every interaction; picks, notes, and the lightbox now update only the one tile that changed). Feedback is now per-image instead of one box per line item, and is independent of picking — a client can leave notes on several images without picking a winner yet. The internal prompt text is no longer sent to or shown on the client page.
- Product Shots: hide individual bad renders from the client. The per-item "show to client" checkbox only hid whole line items — some individual model outputs are just hopeless and shouldn't be an option even on a line item the client otherwise sees. Each ready render now has its own "Show to client" checkbox. An item where every render ends up hidden drops off the approval link entirely rather than showing an empty card.
- Product Shots: pick which line items go to the client. Now that staged compositing is a thing, a project has intermediate steps (equipment + logo, then recolor) the client should never see. Each line item has a “Show this on the client approval link” checkbox (on by default), and the contact sheet only ever shows the ones left checked. Clicking “Get approval link” now also confirms how many of the project's line items are actually included.
- Product Shots: fixes from the first real test run. A render could hang forever on “generating” with no error if a model call got stuck — every render now has a hard timeout and a Retry button, whether it failed or just never finished. You can now choose exactly which models to run per generation instead of always all of them. Render thumbnails open full-screen on click. And since asking one model to combine a background, a product, a logo, and a recolor all in a single prompt was asking too much, you can now save any render straight to the asset library and build the next step (recolor, then place on background, etc.) on top of it instead.
- New tool: Product Shots. Build a shared library of a client's background, product, and logo photos, write a prompt per shot pulling in whichever assets it needs, and generate it through every image model at once to see which one handles the brief best. Follow-up tweaks re-attach the same source images every time, so a instruction like “make it powder-coated black” can't quietly redesign the logo or background. A shareable contact-sheet link lets a client pick their favorite render per shot, with optional feedback, no login required.
- Left-hand sidebar navigation. The top nav was getting crowded, so every tool now lives in a sidebar down the left of the page on desktop (mobile keeps the existing menu). Product Shots sits at the top.
- Two fixes found while dry-running the new bring-your-own-assets feature. Cloning a bring-your-own-assets test now keeps that setting on the copy — a clone was quietly falling back to the AI reference-image flow, which a test with no reference images and only uploaded creative can never satisfy. The credits estimate on the create form no longer claims a bring-your-own-assets test will spend image credits — it now says plainly that nothing is AI-generated.
- Split Test Lab: bring your own images and videos. A new test can now be built from your own finished ad creative instead of always generating it. Turn on “Bring your own assets” when creating a test, and the AI still writes and tests the angle, hook, body and call to action — it just runs that copy against the images or videos you upload per angle instead of rendering new ones. Stage 0 still runs a knockout across whatever you uploaded for an angle, same as it does with generated images.
- Video ads go all the way to Meta. An uploaded video is pushed as a real video ad creative: Meta gets the file, is polled until it finishes processing, and its own generated thumbnail is used — no separate poster image to prepare. A test using your own assets skips the reference-image step entirely, and AI regenerate/edit/bakeoff are disabled on anything you uploaded yourself, since there is nothing there to re-render.
- Targeting tests on an instant-form campaign are judged on leads. The comparison between an ad set and its targeting duplicate fell back to click rate whenever conversions were thin — which for a form campaign is nearly always — and could recommend pausing the original ad set on the strength of clicks. It now compares leads per impression, shows leads, cost per lead and lead rate in place of CTR, and says “too early” until there are at least 8 leads across both ad sets rather than naming a winner on clicks.
- “Who’s converting” now includes today so far. The live report, and the buttons that build an ad set from a finding (which re-check the same report), read up to the current moment rather than the last complete day. The nightly scan that files recommendations stays on complete days.
- Early indicators no longer apply click rate to an instant-form test. Those patterns are learned from tests whose winners were picked on clicks, and they were being learned from form tests too, where the winner is picked on leads — teaching the account something that is really noise. Form rounds are now left out of the learning, and a form test’s panel shows leads so far and delivery share instead. The “early signals” note recorded when you promote a winner is written in leads too.
- New angles built from a winner are described in leads. When you generate angles from ads that worked on an instant-form test, the model is now told each ad’s leads and cost per lead, and the “worse ads” it is told to avoid are the ones with the fewest leads — not the lowest link CTR, which such an ad barely logs.
- Smaller fixes. The import preview asks for a “target cost per lead” on a lead campaign. The Meta panel no longer says data is “complete days only” — decisions use complete days, the tables include today.
- “Who’s converting” now reads an instant-form test in leads. It was still showing link clicks and “conversions”, and working out a conversion rate per link click — which an instant-form ad barely logs, since the button opens a form rather than a link. For these tests the breakdown tables now show impressions, clicks, leads, cost per lead and lead rate (leads per impression), and every finding says “leads” and “cost per lead” instead of “conversions” and “CPA”. The nightly scan, the live report and the AI read-out of imported campaigns all use the same wording.
- The “what it is looking for” panel and the apply previews match too. For an instant-form test it now describes the rules as they actually work — cost per lead, “no leads and no clicks of any kind” for broken creative, and verdicts on leads once there are at least 8 — rather than link clicks and CPA.
- Fixed: findings read “2 countrys”; it is now “countries”.
- Split Test Lab tables now refresh every 6 hours and include today so far. They used to hold complete days only and update overnight, so anything that happened today — a form filled in this morning — was invisible until the next day. A separate refresh now pulls yesterday and today every six hours and stores them. The leaderboard says so above the table: what date it includes and how long ago it refreshed. “Sync now” brings today up to date as well.
- Decisions are held to complete days. Auto-pauses and the nightly recommendations still read complete days only — that is enforced at the database query, so today’s partial numbers cannot reach a kill rule. The refresh never evaluates, never pauses anything and never writes to the Recommendations inbox.
- Verdicts wait for whole days. The winner/carry-forward card now needs at least the test’s minimum number of complete days (2 by default) before it names a leader, and says “Too early to judge” until then. Checking four times a day is exactly how a lucky morning gets mistaken for a winner. The same bar the nightly rules already used, now applied to what the card shows.
- Fixed: saving Settings switched an instant-form test onto a key that can never match a form. Every Settings save recomputed the counted conversion from the conversion-event dropdown, so an instant-form test that had been counting on-Facebook leads was silently moved onto the website-pixel Lead event — which a form completed on Facebook never triggers. Leads then read as zero even though Meta was reporting them. Settings no longer touches the counted conversion on an instant-form test, and every read now derives it from the destination as well, so no path can put a form test back on a key that cannot work.
- Instant-form tests now show today’s leads. The tables only hold complete days (deciding on a partial day would apply kill rules to numbers still moving), so a form filled in today read as zero until after midnight in the ad account’s timezone. A strip at the top of an instant-form test now asks Meta for today so far, including every action type it reports. It is display only — nothing stored, and no rule reads it.
- Fixed: “Check conversions” called a working setup broken. It matched only the one configured name, so a lead reported under a different name of the same kind showed as “never reported” while the sync was in fact counting it. It now applies the same matching as the sync.
- Instant-form tests are now judged on leads, not clicks. The Split Test Lab ranked every test on link click-through rate, which is the wrong measure for an instant form: the button opens a form rather than a link, so link clicks understate engagement, and the ad that draws the most clicks is not necessarily the one that gets forms completed. For these tests the leaderboard now shows leads, cost per lead and lead rate; the dashboard card shows leads and cost per lead instead of CTR; and a round is only decided once it has enough leads to compare, on leads per impression — it never falls back to naming a winner on clicks.
- Fixed: completed instant forms reading as zero conversions. Meta reports the same lead under several names (on-Facebook leads, form leads, plain leads) and the sync counted exactly one, so a form that had been filled in could read as nothing. It now counts whichever of them Meta returns, once, never summed. The “conversion event may be misconfigured” alarm also now waits until at least one target cost of spend has gone through before firing, since a young campaign with no results yet is not a fault — and that alarm halted the analysis for the whole test.
- Fixed: ads that were producing leads could be paused for “no clicks”. The dead-creative rule counted link clicks, which an instant-form ad barely logs. It now only applies to a form ad with no leads and no clicks of any kind, and the “click rate less than half the others” warning is not raised for form ads at all.
- Budget advice for instant forms is sized in leads. Impressions were never the bottleneck for these tests — leads are. The advisor now works out how many leads each ad set can collect at your cost per lead (measured once there are a few, your target until then), ranks angles on leads rather than CTR, and says plainly when no angle has a lead yet so the ranking would be arbitrary.
- Fixed: "Refresh Meta statuses" crashing with a database error. The key used to re-find an ad that may or may not have finished creating did not include which ROUND it belonged to, only the angle and the ad's position in it — and every later round of the same angle numbers its ads from one again. A hook round's "Ad 1" and a body round's "Ad 1" for the same angle produced the identical key, so once a test had gone through more than one round, refreshing statuses could try to attach the same Meta ad to two different local records and fail outright. The key now includes the round, and a bad match is skipped and logged rather than crashing the whole refresh.
- Fixed: pausing a campaign or ad set directly in Meta not reflected here. Local records of whether a test or ad set was live were only ever written by this tool's own actions — nothing ever read Meta's own status back. A pause made directly in Ads Manager left the dashboard, the recommended-action card and the Recommendations inbox all still describing something as live that had already stopped. "Refresh Meta statuses" and the nightly sync now both check the campaign's and every ad set's actual status on Meta and correct local records to match, closing out anything in the inbox that pausing it made moot.
- Fixed: a recommendation not noticing an ad set had been paused. The recommended-action card had no case at all for an already-paused angle, so it could fall straight through into the normal decision logic and keep recommending something — including, absurdly, recommending pausing an angle that was already paused. A paused angle now shows plainly that nothing is running there, before anything else is considered, and the ad set header itself now carries a visible Paused tag, which it never did before.
- Fixed: the Recommendations inbox growing without bound. An early warning that stays true for weeks — spend creeping toward the kill line, click rate lagging — was getting a brand new entry every single night the sync ran, because the de-duplication only worked within one day, not across the life of the condition. One test had accumulated 20–30 of these. It now keeps one open entry per ongoing condition instead of one per night.
- Fixed: recommendations for ads that had already been dealt with. Pausing an ad, pausing an ad set, or promoting a round's winner now immediately closes out whatever open suggestions were about that ad or ad set — instead of leaving them sitting in the inbox describing something that was already done. A nightly sweep also catches anything paused by hand in Ads Manager, outside this tool entirely.
- Fixed: suggestions that did not say which ad set they meant. "Ad 9 approaching the kill threshold" was ambiguous the moment a test had more than one ad set, since ad numbering restarts in each one. Every such suggestion now names its ad set directly in the title.
- A recommended-action card replaces the raw statistics. Opening a test used to lead with a dense verdict box — probability, confidence intervals, per-arm rates — with the actual decision buried in the middle of it. Every angle now leads with one plain-language card: what is recommended, why, exactly what happens if approved (which ads get paused, what gets frozen, what starts next), the expected outcome, and how it will keep being watched afterward — with a single Approve button that carries it out. The statistics are still there for anyone who wants to check the working, collapsed under “Show the statistics behind this” rather than being the first thing in the way.
- The recommendation adapts to what is actually true of the round. A decided winner offers to promote it. A round with no clear leader but a real lead offers to carry the top two forward instead of forcing a call. Starved ads that never got a fair share of delivery offer a rescue. An angle the budget advisor flagged for consolidation offers to pause it and move its budget to the stronger one. And where none of that applies, it says plainly that there is nothing to decide yet, rather than manufacturing a next step to look useful.
- Fixed: decision text overflowing off the edge of the screen. The
portfolio dashboard shipped a CSS class named
split-verdictfor its small badge, not realising the full test view already used that exact name for the round- decision box. Being declared later in the stylesheet, the badge rules silently won on every page in the app — turning every test's decision box into a tiny uppercase pill with a full paragraph crammed into it, spilling off the right edge of the screen. The dashboard's classes are now named distinctly so the two can never collide again.
- The budget advisor now says when its budget figure is a guess. A rescue or targeting-duplicate ad set can carry its own daily budget, set independently of the test's usual figure, and that value is not stored anywhere this tool can read without a live call to Meta. The advisor's per-angle budget assumed it always matched the test's configured amount; it now says so explicitly whenever a rescue or duplicate is part of the live mix, rather than presenting a number that might be silently wrong as though it were certain.
- A budget advisor for split tests on a tight daily spend. With a small daily budget split across several angles, most tests were never going to reach a readable result — but nothing said so, or what to do about it. Every live test is now assessed against its own measured cost per thousand impressions and told in concrete terms whether its current spread of angles can actually answer the question it is asking within a reasonable window.
- Concrete reallocation, not just a warning. When a test's angles are diluting each other's budget past the point of a usable read, the advisor says exactly which angles to pause, how much daily budget that frees, and what it buys the survivors — e.g. pausing two of three angles and moving their $4/day onto the strongest one, cutting the wait from “never” to a stated number of days. When a test already has a decided winner, that is surfaced first since it costs nothing to act on. A one-click Pause button on each recommended angle carries it out, with the same explicit confirmation every other Meta-changing action here requires — the advisor only ever suggests; it never pauses anything itself.
- A dashboard, not a list. The Split Test Lab's front page now opens on a grid of every running test with its assessment at a glance — spend rate, click-through, angles live, and a colour-coded verdict — and scrolling down reaches a full reasoning block and a compact per-angle table for each one, without leaving the page. The full editor (images, round advancement, settings) is still one click away; this page is the map, not the territory.
- The advertiser and payer are set when the ad set is created. The EU Digital Services Act requires every ad set that can reach the EU — or a worldwide audience — to name who pays for the ads and who benefits. Meta does not refuse an ad set that omits them: it creates it and then limits every ad in it, which had to be fixed by hand, one ad set at a time, after each push.
- Set it once in Settings. Meta Ads settings takes an advertiser / payer and an optional beneficiary; leave the beneficiary blank and the advertiser is used for both, which is the usual case. It is the advertiser's legal entity rather than anything campaign-specific, so it lives with the connection and applies to every ad set the tool creates — including targeting challengers and rescue ad sets, which are real ad sets carrying the same obligation.
- If the ad account already carries account-level defaults from Ads Manager, saving the connection picks them up, so there is nothing to retype.
- Check Write Access works again. It failed with “you must specify True or False in the field is_adset_budget_sharing_enabled” on accounts whose real pushes were working perfectly well. The check built its own copy of the campaign payload, and that copy never got the field Meta began requiring at API v24.0.
- It now exercises the real create path. A write-access check that sends a different payload from the one it is vouching for can fail when a push would succeed, and pass when it would not — which is worse than having no check. Sharing the code with the push is what keeps its answer worth having.
- The Meta Ads settings form can be edited again. It returned the ad account and pixel only as masked hints, so on load it blanked every field and could restore only the Page ID. Saving a new token then failed asking for an ad account the form had just erased — and the values had to be retyped from memory to get past it. Both are identifiers rather than secrets, so they are now sent in full and the form pre-fills.
- A blank field means “leave it alone”, not “clear it”. The save replaced every value wholesale, so a save made with the pixel box empty set the pixel to none — silently, and on this account that is a documented way to lose a campaign to the wrong dataset. Blank fields now keep what is stored, and the token can be left blank to keep the current one.
- “The terms aren't accepted” was the token missing a
permission. A System User token without
pages_manage_adscannot see a Page's terms acceptance, and Meta answers the question with false rather than with an error — so the tool reported terms as unaccepted on a Page that had accepted them. A negative from a token that cannot reach the Page at all is now treated as “don't know”, because that is what it is. - The diagnostic names the permission. When Meta refuses to list a Page's forms, that outranks every other finding — nothing else can be checked and the terms answer is meaningless — so the check now leads with it, quotes Meta's own wording, and says what to do: assignment of the Page to the System User is not the same as the token carrying Page permissions, which are fixed when the token is generated.
- A diagnostic for a refused lead ads push. Meta's
“your Page hasn't accepted the lead ads terms” error names no Page, and
it covers three different faults: the ads running from a different Page than the
one whose terms were accepted, the form belonging to a different Page than the ad
set promotes, or the terms genuinely not being registered. A read-only check at
/api/split-tests/<id>/leadgen-checknow asks Meta which Page the form actually lives on, which Page the push will use and where that came from, and returns a plain-English verdict naming the fault.
- The lead ads terms check no longer blocks a push. Yesterday's check refused the push when Meta's API reported the terms as unaccepted. On a real Page that reading was wrong — Meta's own terms page said accepted at the same moment — so the check stopped a push Meta may well have taken. It is now a warning: the only authority on the terms is Meta when it creates the ad set, and a pre-flight guess must never override it. Pushing creates paused objects and spends nothing, so trying and being refused costs a clear error message.
- It says what Meta reported, not what is true. The field's value depends on which token asks, so the warning now names the token that answered and says outright that it is unreliable — rather than sending you off to accept terms you have already accepted. A positive from the Page's own token is trusted over a negative from the ads token, since the Page knows its own state.
- The lead ads terms are checked before the push, not during it. Meta enforces its Lead Generation Terms of Service when it creates an ad set — so an instant-form test would push its campaign happily and then fail on every ad set, with an error that names no Page. The readiness check now reads the Page's acceptance up front and refuses the push with the Page named and a link that accepts the terms for it.
- Which Page, specifically. The terms are accepted per Page, and a test runs from its own Page rather than the one in Settings — so “I already accepted these” and “you have not accepted these” can both be true at once, and the old error gave you no way to tell. The form picker now says so at the point the form is chosen, and both messages link to the terms for the Page the ads will actually run from.
- Where the token cannot read the Page's acceptance — it needs Page-level access the ads token may not have — the push is still allowed and says it could not confirm, rather than blocking on a field it failed to read.
- Split tests can run to a Meta instant form. Under the Leads objective a new Destination choice sends every ad to an instant form instead of a landing page, picked from the forms that already exist on the Page the ads run from. The lead is then captured on Facebook: the ad sets optimise for lead volume and promote the Page rather than a pixel, so no pixel is needed and the conversion event is ignored. The leaderboard and the CPA kill rules read Meta’s on-Facebook lead count in place of a pixel event, and work exactly as they do on a website test.
- Forms are chosen, not built. The picker lists the forms on the selected Page with their lead counts, and re-reads them when the Page changes — a form can only be used by an ad running from its own Page. Reading that list needs Page-level access on top of ads access, which a System User token often lacks, so when Meta refuses the list it says why and offers a box to paste the form ID into instead of dead-ending.
- Buttons that a form will actually accept. A form only takes a subset of the call-to-action buttons, and Meta rejects the rest — which previously would have failed the push after the images were rendered and the credits spent. The setup form, the copy editor and the CTA test round are all narrowed to the valid set up front.
- The destination is fixed once the ad sets exist. A form test and a website test are built differently at the ad set level, and Meta fixes that at creation — so switching after a push means Disconnect from Meta and pushing again, the same route the objective already takes. Settings says so rather than accepting a change that would silently not apply.
- Ads already on Meta no longer block the push. The readiness check counted every ad in a test, including the ones already created on Meta. A targeting challenger duplicates a live ad set by reusing its creatives, so its ads carry no image of their own and never need one — yet they were screened as “no image” and refused the push of entirely unrelated ads that were ready to go. The check now looks only at what a push would actually create, so the count it screens matches the count on the button.
- Push one ad set from its own card. Each ad set with ads waiting now carries its own push button, saying how many and creating them paused in that ad set only. A hook round that is ready goes out on its own without scrolling to the top for a button that does not say which ads it means. The whole-test button stays for pushing everything at once.
- Releases sharing a date still published backwards. Ordering by release date is only as fine-grained as the dates themselves, and several releases often go out on the same day. Sorting is stable, so within a single day the original pasted order survived — and that order is newest-first, so each day’s releases published in reverse. Release dates, version numbers and pasted order now act as successive tie-breaks rather than one being picked outright, so the sequence is strictly oldest to newest however coarse the dates are.
- A post X rejected can be sent back to drafts. A failed post could not be rescheduled and could not be unscheduled, so the only way out was to delete it and lose the copy. It now has a Return to drafts button.
- Release headings were being split off as versionless entries. WebMage’s changelog puts each release’s heading on the line above its version number, and the splitter read those headings as separate entries with no version attached — which left half the posts with a blank version chip and no place in the publishing order. Headings are now recognised as part of the release they introduce, wherever they sit relative to the version line, with a fallback that folds any stray block into its release rather than stranding it.
- Those headings now inform the writing. A hand-written release heading says what a release was for better than any of its bullet points, so it is passed through to the copywriting step as context.
- Changelog posts were publishing in reverse. Release notes are written newest-first, so scheduling them in the order they were pasted told the story backwards — the newest release went out first and the oldest last. Bulk scheduling now orders by release date where the dates parse, falling back to version numbers (comparing them properly, so 2.10 correctly follows 2.9) and only then to the pasted order. Oldest first is the default, and a Publish order control offers newest-first or strict list order. The confirmation names which release goes out first and which goes last, so you can check it without opening every post.
- Time slots follow the posts-per-day count. Asking for three posts a day used to leave you typing the third time in by hand. The slots now appear and disappear as you change the number, keeping any times you have already set, and each one is a proper time picker rather than a comma-separated list.
- Split Test Lab showed one hook for a round that was testing three. From the hook stage onwards each ad in a round carries its own copy, but the summary panel under the thumbnails was reading the angle’s settled copy rather than each ad’s. Since the winning hook isn’t recorded until a round finishes, what it actually displayed was the previous stage’s hook — so a hook round appeared to be testing nothing at all. The panel now lists the field under test once per ad, with the frozen copy stated separately, and each row opens that ad. The test itself was always correct; only the display was wrong.
- Opening an ad now shows what it says. The reviewer led with the image and kept the hook and body inside a collapsed “edit by hand” form, so you could look at an ad without ever seeing its copy. It now opens with the full text, and on a hook, body or CTA round the line being tested is marked.
- Changelog → X. A new admin tool that turns release notes into posts. Paste in a pile of changelogs — every version, in whatever format they happen to be in — and it splits them by version, decides which changes are actually worth telling anyone about, and rewrites each one as a post that reads like product news rather than a build log. Versions that are only typo fixes and dependency bumps are skipped outright.
- Edit, rewrite, schedule. Every post can be edited in place with a live character count that follows X’s own rules, rewritten from a different angle, shortened to fit, or thrown away. Schedule them one at a time, or select a batch and spread them over days at the times you choose — slots already taken by another X post are skipped automatically.
- Scheduled posts publish on their own and appear in the Calendar alongside Instagram, Facebook, GoHighLevel and LinkedIn.
- X (Twitter) can now be connected. Settings has an X card that runs the standard OAuth sign-in, the same way LinkedIn does. Once connected the app can publish to your account — single posts, threads, and posts with an image or video attached — and it keeps the connection alive on its own. X access tokens expire after two hours, so the app refreshes them in the background before every call rather than asking you to sign in again.
- Nothing posts to X yet. This release is the connection and the plumbing behind it. The Post Creator, calendar and scheduler are untouched; where X appears in the tools comes next.
- Long copy can be split into a numbered-free thread automatically, breaking on paragraphs and sentences rather than mid-word, and character counting follows X’s own rules — every link costs 23 characters regardless of length.
- Vertical posts are no longer cropped by Instagram in the wrong place. Instagram will not show anything taller than 4:5, so every 9:16 post was being trimmed to fit — and the trim was Instagram’s, taken from the middle, which sliced the top off the headline and left the mascot as a pair of ears in the corner. We had built a crop to prevent exactly this, but it only ever ran on PNG files, and the image generator returns JPEG, so it had quietly been doing nothing at all. It now reads the picture whichever format it arrives in.
- New images are composed to survive the crop. Prompts for 9:16 posts now state the safe area outright: the title, the mascot, labels and the main subject have to sit inside the central 4:5, with the top and bottom of the frame left for background and atmosphere. What Instagram shows is now the frame we chose rather than the one it picked, and the full-height original is still kept for Stories and Reels.
- The animation prompt for a video is now written from the actual image. Turning a post into a video starts with a prompt describing how the picture should move — and until now that prompt was written from the instructions we sent the image generator, never from the picture that came back. The two routinely differ, so the motion described belonged to a slightly different image: a mascot that ended up front and centre went unmentioned, background figures were animated as though they led the frame, and details that never made it into the render were given movement anyway.
- The prompt writer is now shown the finished image and writes motion only for what is actually in it — naming the real subject, the real light source and the real headline — with the original image instructions kept only as background intent. The prompt still appears in the Generate Video box for you to edit before anything is rendered.
- Any paused ad set can now be switched on from its own card. Challengers, rescues and combined-finding ad sets are all created paused on a test that is already running — and the whole-test Go live button only appears while a test has not launched yet, so there was no way to start one without going to Ads Manager. Each ad set that is pushed but not live now carries its own button, and it affects that ad set alone: anything else you have paused stays paused.
- If the campaign above it is paused, that is asked about separately — activating a campaign restarts every ad underneath it, including ones you paused on purpose.
- Fixed “the field targeting_automation must be enabled to use the field age_range”, which appeared straight after the previous fix. Meta reports an ad set’s effective age band back as an extra read-only field and then refuses to accept it on a write. It was being stripped when copying an ad set’s configuration but not when copying its audience — harmless until Advantage+ Audience was switched off, at which point the two settings became invalid together.
- Fixed “you can add a lower maximum age as a suggestion instead” when building an ad set from an age recommendation. Duplicated ad sets copied their original’s targeting exactly — including Advantage+ Audience, if it was on. With that on, Meta treats age and gender as suggestions and refuses a fixed maximum age outright, so the plan failed at creation with an error about a setting you had never touched. Advantage+ Audience is now switched off on the copy.
- That is deliberate rather than incidental: with expansion on, Meta reaches past the age or gender you set, so an ad set built to test a narrower audience would not have been testing it. The panel now says so — if the original has Advantage+ Audience on, the two ad sets differ in that as well as in the change itself. Under a special ad category, where Meta forbids narrowing anyway, the original’s settings are still copied untouched.
- Fixed “one of those findings is no longer in the current report”, which could appear the moment you ticked a recommendation and pressed the button. The panel worked the findings out over one window and the build step recomputed them over a different, shorter one, so it could not find what it had just offered you — then blamed the numbers for moving. The window is now resolved in a single place and carried through, and if a finding really has gone the message says which one and what the report holds instead.
- A finding that names a winning segment no longer offers to switch it off. “Instagram converts at $18 against $45 overall” was producing the plan Run on facebook only — the exact opposite of what it had just recommended. Every finding was being fed to the same planner, which only knew how to remove the segment it was given, so findings that named a winner came out inverted. They now isolate it instead: Run on instagram only, matching what the finding itself suggests.
- The two kinds now behave differently and say which they are. A segment that costs too much or never converts is dropped; one that converts cheaply, or produces most of the results, is kept on its own. The plan says keeps only what the finding named rather than claiming it removes it.
- Isolating an age band refuses when the bands are not adjacent — an ad set has one age range, so targeting 25-34 and 55-64 together would also include everyone between them. Isolating an “unknown gender” segment refuses too, because that group is only reachable while the ad set targets everyone: setting any gender loses exactly the people the finding is about.
- A campaign that stops converting no longer stops the nightly sync. The check for a misconfigured conversion event fired whenever the event was absent from the last week — which is also what a genuine drought looks like. A campaign that had converted for three months and then went quiet for a fortnight was declared broken, and the sync halted, so the rules and the audience findings stopped running at exactly the point they were most needed. It now only fires when the event has never reported for that test.
- Conversions drying up is now reported as the finding it is — when they stopped, and what has been spent since.
- “Who’s converting” widens its window instead of giving up. It looked at 30 days by default; a campaign whose conversions stopped three weeks ago has almost none in there, so the report declared itself underpowered and offered nothing — while 90 days held 86 conversions and five actionable findings. It now falls back to the longer window when the short one is too thin, and says on the page that it has done so.
- “Who’s converting” findings can now be acted on from the report itself. Each one that names a segment gets a Build an ad set from this button, and several can be combined into one. Previously the report was read-only and the only way to act on a finding was through a recommendation the nightly scan had recorded — which it does not do while a campaign is underpowered or its sync is stalled, i.e. exactly when you go looking.
- When nothing can be acted on, the panel says so and why. These recommendations work by naming a segment to drop, which needs conversions to compare segments on. With none, no finding can name one — so the report now states that outright instead of simply having no buttons on it.
- Duplicated ads now inherit their original’s likes, comments and shares. Challengers, rescues and combined-finding ad sets attach to the original ad’s Page post — what Ads Manager calls Use existing post — so engagement accumulates in one place across every ad set a creative runs in.
- This was previously broken, and silently. Passing the original creative’s ID when creating an ad does not keep that creative: when it is already in use Meta clones it, and a clone built from an inline creative spec mints a brand new unpublished Page post. Measured on a live campaign, four rescued ads came back with four new creatives and four new posts, each starting from zero engagement — while our own records still claimed they shared a creative with their originals. Ads created before this release kept their own separate posts; that cannot be undone retroactively, but it stops here.
- A new social proof check reports which ads share a Page post, so this is verifiable on a live campaign instead of taken on trust.
- “Who converts” findings can be combined into one ad set. Combine recommendations on a test lists every open finding with the targeting options each one offers, and you tick the ones you want. Applying them one at a time produced one duplicate ad set per finding, all targeting overlapping slices of the same audience and bidding against each other — “drop this country AND narrow this age band AND leave this placement” is a single hypothesis and now gets a single ad set.
- Your existing ads are reused, never recreated. On Meta an ad belongs to one ad set permanently, so it cannot be pointed at a second one. What is reused is the creative — an account-level object — so the new ad set carries the same image, copy and Page post, and likes, comments and shares keep accumulating in one place rather than starting from zero. The panel lists exactly which ads will be carried and which cannot be, before anything is created.
- Each option shows what it costs as well as what it fixes: an exact change says so, and one that has to drop more than the finding named lists the spend and conversions going with it. Where two corrections would give up the same segment it is counted once, not twice.
- Pause any ads you choose, straight from the leaderboard. Tick the rows and press Pause selected ads. The counterpart to Restore, and the fix for a rescue created without ticking “pause the originals” — which leaves each rescued creative live in two ad sets aimed at the same people, bidding against itself, with its results split across two rows that cannot be added together.
- A paused ad now says why it is paused rather than all of them reading as rule kills: “moved to the rescue ad set” when the same creative is already running in a rescue, “paused by you” otherwise, and the original wording when the kill rules did it. If some ads in a batch cannot be paused you are told which and why, rather than the failure passing quietly.
- Ticking rows now works on every angle. Selection was previously wired through the promote block, so it silently did nothing on any angle that did not have one — a completed stage, for instance.
- A rescue is now sized before you create it. Rescuing nine starved ads onto the same budget that starved ten of them recreates the problem while looking like a fix — on a $2/day ad set it moves the wait from seventeen days to fifteen. The rescue panel now sizes what you have actually ticked, at the budget you have actually typed, against that campaign’s own cost per thousand impressions, and updates as you change either.
- Rescued ads can be paused in the ad set they came from. Opt-in, and off by default. Left running, a rescued creative is live in two ad sets aimed at the same people, bidding against itself, with its results split across two rows that cannot be added together. Each original is paused only once its replacement exists, so a failure part-way through can never leave a creative running nowhere. The caveat is stated in the panel: changing an ad set’s composition can reset that ad set’s learning phase, and that lands on the ad you are keeping as the control.
- Split Test Lab tells you when a round is too small to answer its own question. Ten ads in an ad set carrying $2/day is 20¢ per ad per day — about 57 impressions each — against a 1,000-impression floor. Even if Meta split delivery perfectly evenly, which it never does, that round needs eighteen days to produce a readable result. The setup form now works this out live as you change the budget and ad count, using your own account’s cost per thousand impressions, and recommends either a smaller ad count or the budget that would make the current one work. It is advice, not a gate — nothing stops you launching anyway.
- An ad Meta starved is no longer shown as an ad that lost. These are completely different facts and the leaderboard used to blur them: an ad on 20 impressions and no clicks looked like a failed creative when nothing whatsoever was known about it. Untested ads are now labelled as such, and the verdict says how much of the round it actually covers — “3 of 10 ads never reached 1,000 impressions, so this is not a verdict on the ones Meta starved.”
- Starved ads can be rescued into an ad set of their own. Same targeting, its own budget, without the ad Meta was favouring — so they compete only with each other and Meta has to spend the budget on something. The original ad set is untouched, so nothing is paused and no ad loses its learning phase. Created paused, and the auction-overlap cost is stated up front: the two ad sets target the same people and will bid against each other.
- Round sizing measures your cost per thousand impressions from comparable campaigns — the test’s own delivery where it has any, then campaigns with the same kind of objective. Averaging a conversion campaign at $88 per thousand against a traffic campaign at $3.40 produced a figure describing neither, and since the average was weighted by spend the bigger spender won.
- A round can reach a verdict again after ads have been killed. “Has this round run its course?” counts an ad that was killed as finished — but killed ads were being dropped from the count entirely, so each kill made the test harder to conclude rather than easier. Killed ads now count as decided, which is what was intended all along. They are still kept out of the statistical comparison, where they never belonged.
- The performance view worked out whether a test is judged on cost per result or on click rate from the objective in every case. It previously assumed any test built in this tool was conversion-based, which is untrue for a Traffic or Awareness campaign.
- Split Test Lab no longer kills ads on traffic campaigns. The cost-per-result kill rules were running on every test, including ones with a Traffic or Awareness objective — where Meta never reports a conversion, so every ad reads as zero forever. Because the rule triggers on spend, and Meta spends most on the ad it likes best, it removed each ad set’s ads in descending order of merit: it killed the winner first. Those rules now only run on campaigns that actually optimise for a conversion, which is what the nightly summary has been claiming all along. Click-based rules are unaffected — they are the right way to judge a traffic test and they still run.
- “View content” and “Initiate checkout” tests
were counting nothing. Meta’s setup field spells these
CONTENT_VIEWandINITIATED_CHECKOUT, but its reporting spells themview_contentandinitiate_checkout. We were asking for the first pair, which Meta never returns, so conversions read as a permanent zero. New tests now ask for the right name. Existing tests keep the name they were created with — Check conversions will tell you if yours is one of them, and the event can be corrected in place. - A paused ad can be put back. Killed ads now have a Restore button that reactivates them on Meta and clears the kill, instead of the only route being Ads Manager. Meta treats a re-activated ad as a fresh start, so you are warned before it goes back out.
- The “no conversions — expected for this objective” note was meant to appear once per test and was in fact appearing every night. It now appears once.
- Social posts can now be drawn by fal.ai as well as Gemini. Three new choices in Image model: Seedream 4 at roughly a quarter of Gemini 3 Pro’s cost per image and the strongest of the group at rendering text, FLUX 1.1 Pro Ultra for the most photoreal look, and FLUX.1 Kontext Max, which is built for editing an existing image rather than inventing one. They work everywhere images do — the main run, Regenerate Image, Selfie Mode, and Apply Changes.
- Seedream gets your aspect ratio exactly right. Gemini only accepts a short list of shapes, so a 4:5 post was quietly rendered at 3:4 and a 1.91:1 post at 16:9, then cropped. Seedream takes real pixel dimensions, so 4:5 now comes back as a true 1080×1350.
- The model picker beside Regenerate Image now also decides which model applies your edit, instead of every edit going to Gemini 3 Pro regardless.
- If fal is having a bad day, the post falls back to Gemini Flash rather than arriving with no image. Each fal model has its own price dial on the admin Pricing screen, and every image is billed at what it actually cost.
- Split Test Lab no longer cries “no delivery data” at a freshly launched test (v2.78.1). Ad sets are created to start at midnight in the ad account’s timezone, and the nightly sync only ever judges complete days — so a test launched this afternoon has no day Meta can report on for another day or two. The warning that called that “a reporting or delivery problem” now waits until a full day of delivery has actually elapsed, and only fires when something is genuinely wrong.
- Recommendations stop duplicating themselves and stop coming back after you dismiss them. Findings that belong to an ad set or to the whole test — no delivery data, angle stalled, conversion event may be misconfigured — were being written again by every Sync now, because the rule that made recommendations one-per-day only covered findings about an individual ad. Existing duplicates are collapsed automatically, keeping the copy you had already read.
- The leaderboard’s “no data yet” note now says why the wait is what it is, rather than promising numbers the next day.
- Clone a split test and relaunch it as a second campaign (v2.78.0). Clone test on any test makes a full copy — the settings, the reference images, the angles, the ad copy and the generated images — as a fresh draft with nothing on Meta. The settings panel opens on the copy straight away, so the way to run the same creative at a bigger budget, in another country, or against a new landing page is now to copy it rather than to edit a test that is already delivering.
- Why copying rather than editing. Meta resets an ad set's learning phase when its targeting or budget changes, which makes the numbers either side of the edit incomparable and throws away the reading you were waiting for. A clone leaves the original running untouched.
- The copy leaves behind everything that belonged to the original's run: the Facebook campaign link, the performance history, the nightly sync state, the recommendation inbox, and any ad the kill rules had already killed — relaunching a proven loser only spends the money twice.
- Images are shared with the original rather than re-rendered, so a clone costs nothing and is instant no matter how many ads it holds. Editing or re-rendering an image on either test still leaves the other one alone.
- Fixed: a traffic or awareness campaign stopped its own nightly sync (v2.77.2). The check that catches a mis-set conversion event fired whenever Meta reported none — which is the normal, expected state for an objective Meta is not aiming at conversions with. It raised a false alarm every night and, worse, halted the sync before the rule engine and the “who's converting” analysis ever ran. It now only applies to campaigns that optimise for conversions; the others get a one-off note explaining that no conversions is the expected result.
- Traffic campaigns do not attach a pixel — and the picker now says so (v2.77.1). Meta does not attach a conversion pixel to a traffic ad set in any configuration; an attempt to do so is rejected outright. That is Meta's design, not a setting. The objective is now labelled Traffic (no pixel optimisation), and the note explains that your pixel still tracks and conversions are still reported — Meta simply is not aiming at them.
- If you want the pixel optimised for and visibly attached, choose Leads or Sales and set your pixel event as the conversion event. That combination is a conversions campaign by definition, which is what Meta steers you towards in its own interface.
- A rejected promoted object no longer costs the whole push: the ad set is created without one instead of failing, so targeting, budget and ads all still land.
Traffic campaigns now attach your pixel (v2.77.0).Traffic was set to optimise for link clicks, which takes no pixel — so the pixel you chose was stored correctly but never appeared on the campaign in Ads Manager, and there was no way to tell by looking that it was wired up. Traffic now optimises for landing page views instead, which is a better signal anyway: a click that bounces before the page loads is not traffic. Your pixel is attached so page loads can be counted.- Meta is still not aiming at your conversion event under Traffic, so ads are ranked on link CTR and the CPA kill rules stay off — but conversions are reported as normal. With no pixel set it falls back to link clicks and says so on the push dialog rather than refusing.
- Fixed: a Traffic campaign would not push — “the following combinations of click-through and view-through attribution window values are allowed: (1, 0)” (v2.76.2). Ad sets were still being sent the conversion attribution window (7-day click, 1-day view), which Meta only accepts for conversion optimisation. The window now follows the objective like the optimisation goal and the pixel already did: 1-day click for Traffic, none at all for Awareness. If Meta ever names a different allowed pair, the push now adopts the one it asked for instead of failing.
- The objective is now in test settings too, not just on the new-test form (v2.76.1). It is editable while nothing has been pushed, and shown read-only once the campaign exists — Meta fixes a campaign's objective at creation and will not change it, so the route for a live campaign is Disconnect, change it, push again. The campaign's objective is also now named on the Meta panel, so what a campaign is set up as is visible without opening anything.
- Choose the campaign objective. Every campaign was created as Sales regardless of what the setup form said, because the objective was never actually sent to Meta — so a traffic campaign came out conversion-shaped, with a pixel event bolted onto an ad set that should not have had one. You now pick Sales, Leads, Traffic or Awareness, and the ad set is built to match: traffic optimises for link clicks and awareness for reach, neither carrying a pixel event.
- The form says what the choice costs as you make it. Under Traffic or Awareness there is no conversion for Meta to optimise towards, so the CPA kill rules cannot fire and ads are ranked on link CTR instead — stated at the moment of choosing rather than discovered a week later. Your conversion event is still recorded for reporting.
- A pixel is no longer demanded for objectives that do not use one, which previously blocked the push outright.
- The duplicate is now scored against the ad set it duplicates. A head-to-head panel compares the two on impressions, clicks, CTR, spend, conversions and CPA — counted only from the moment the duplicate launched, so the original gets no credit for the weeks it ran first. It waits for $25 of spend on both sides, uses cost per conversion once both have the volume for it and click rate before that, and says which it used.
- “Pause the original ad set” appears only once the duplicate has actually won, and the server re-checks that before pausing anything. Every verdict carries the caveat that matters: the two ad sets overlap in the auction by construction, so this is evidence rather than a controlled test.
- Fixed: a stale page could show an old version indefinitely. Nothing cache-busts index.html itself, so a browser holding an old copy showed an old changelog and loaded old scripts while the server underneath was current. The page now compares what it was built with against what the server reports and reloads itself once if they differ.
- Act on a “who converts” finding in one step. A finding like “Canada has spent $400 with no conversions” now has an Act on this button that duplicates the ad set with that segment excluded. The duplicate carries the same creatives — not copies, the very same Meta creative objects — so the copy, headline and video file are identical and only the targeting differs. Where a creative is backed by a Page post, both ads share that post, so likes and comments keep accumulating in one place.
- Both ad sets run side by side; the original is never paused for you. Two identical ads can perform very differently on delivery alone, so a duplicate has to earn its place before a proven ad set is retired. The trade-off is stated plainly in the dialog: the duplicate targets a subset of the same people, so the two compete in the auction and cost more while both run.
- Age exclusions now show what they really cost. Meta gives an ad set a single age range, so removing a band from the middle is impossible — excluding 45–54 from 18–65 leaves 18–44 and 55–65, and one ad set cannot hold both. Both remainders are offered with the spend and conversions each one would give up, priced from your own data. Neither is preselected.
- Country exclusions use Meta's own exclusion field rather than editing the country list, so they work even when an ad set targets by region, city or radius. Findings that would exclude everything an ad set targets are refused with the reason.
- A duplicate is marked as a targeting challenger, and the nightly creative rules skip it. Every ad in it is identical to one in the original, so picking a “winner” between them would be reporting delivery noise as a result.
- Ads paused by the kill rules or rejected by Meta are left out of the duplicate, with the reason listed, rather than re-spending on a known loser. Everything is created paused and spends nothing until you launch it.
- “Who's converting” findings now reach you on their own. The country, age, gender and placement analysis existed only behind a button, so a campaign could spend for weeks in a country that had never converted and nothing would say so unless you thought to look. It now runs as part of the nightly check and its findings land under Recommendations with everything else — dead spend, results bunched in one segment, and segments well above or below your average cost.
- These never pause anything, and they stay quiet until there are enough conversions to mean something — splitting a handful of conversions across countries and age bands leaves single figures per segment, which is noise, not a finding. A finding is not repeated once it is already in your inbox, but dismissing one lets it come back if the pattern is still true later.
- A “Watching” panel on every live test. It says when the next check runs, whether auto-kill is armed, and exactly what each rule is waiting for — with your own thresholds filled in, not described in prose, so it cannot drift from what the rules actually do. Previously the only signal that anything was keeping an eye on a test was a “Last synced” timestamp.
- Each recommendation now carries a label saying what kind it is — an ad being paused and a dead-spend finding need acting on very differently, and the titles alone did not separate them.
- Disconnect a test from its Meta campaign, so it can be rebuilt. The Page baked into every creative, the pixel on each ad set, and whether the budget sits on the campaign are all fixed by Meta at creation and cannot be edited afterwards. When one of them was wrong there was no way forward: the test stayed permanently tied to a campaign it could not fix. There is now a Disconnect from Meta action that cuts the link so the same ads — same copy, same images — can be pushed again as a new campaign.
- It never touches Meta. No Meta call is made: the old campaign, its ad sets and its ads stay in the ad account exactly as they were. Because pushing afterwards creates a second campaign, the dialog says plainly that the original is yours to delete or archive in Ads Manager.
- Two independent confirmations. An acknowledgement checkbox and typing the campaign ID, with the full list of what is deleted on screen while you decide — the synced performance history goes for good, and it cannot be re-pulled afterwards. The server independently requires the ID to match, so the enabled button is a convenience rather than the guard. Ad copy, images, angles and completed stage winners are kept, as are the images already uploaded to Meta.
- Refused on imported tests, where the campaign is the whole point of the test — deleting the test is the honest action there, and it leaves the campaign untouched.
- Choose which pixel each test optimises for. Every campaign used the single Pixel ID set in Settings, with no way to pick another — so a campaign for one brand was optimised against, and reported conversions from, whichever pixel happened to be configured. There is now a Pixel (dataset) picker on the setup form and in test settings, listing the pixels the connected Meta token can actually use. Unlike the Page, the pixel lives on the ad set rather than the creative, so it can be changed mid-test — but it only governs ad sets created after the change. Hidden on imported tests, whose ad sets reuse the pixel of the ad set they were cloned from.
- A pixel that has never fired is now called out. The picker labels it and explains the consequence: conversions read zero, Meta cannot optimise delivery towards it, and the kill rules would treat that as failure rather than as a configuration problem.
- Only pixels assigned to your Meta connection appear in the list. A dataset that exists in your Business but was never assigned to the connection is invisible to the tool — which is the usual reason a pixel you just created is missing.
- Fixed: “Sync now” said “Synced — no-op” and pulled nothing. Once a day had been synced — by the nightly run or by an earlier press of the button — every later sync that day found the finished job and skipped straight past it without contacting Meta. Pressing Sync now re-opens the day and re-pulls it, so numbers that have accumulated since the last sync appear. A day whose sync failed is re-opened too, instead of staying wedged until tomorrow.
- The sync message now says what it fetched. It reports the date the data runs through, e.g. “data through 2026-09-08, 0 decisions”. Meta only reports complete days, so the leaderboard always trails what Ads Manager shows live for today — a campaign gathering stats right now will not show them here until tomorrow.
- Choose which Facebook Page the ads run from. Every ad used the single Page set in Settings, with no way to see the alternatives or pick a different one per test. There is now a Page picker on the setup form and in test settings, listing the Pages the ad account can actually advertise for. Meta writes the Page into each creative at creation and creatives are immutable, so this governs ads created from that point on — ads already pushed keep their Page and can only be moved by deleting and pushing them again. The picker is hidden on imported tests, whose ads run from whatever Page the original advertiser used.
- Fixed: new ad sets ignored the imported campaign's audience. They were built with the schema defaults — United States, ages 18–65, and no interest targeting — rather than the countries, age band and interests the campaign actually used. That is not a stricter test, it is a different audience, and its results were never comparable with the ads it was meant to beat. New ad sets now inherit the audience and override only the delivery settings (placements pinned, Advantage+ audience off), which is the part that genuinely keeps a creative test clean. Imported tests seed their countries and age band at import, and existing ones have a Match the imported campaign's audience button in settings.
- Who's converting. A new panel reads Meta's own country, age, gender and placement breakdowns and reports where the money goes versus where the results come from — spend in segments that have never converted, conversions bunched in one country, bands well above or below average cost. Each finding proposes a concrete next test, and they feed into angle generation so new creative is written for the people actually buying. Volume floors throughout: with too few conversions to split meaningfully, it says so instead of inventing a pattern.
- Go live now shows progress. Activating is one Meta call per ad set and per ad, and it used to run as a single silent request — indistinguishable from a hung page. It now works in slices and reports back, so a real progress bar moves as ads go live. The push got the same bar, counting ads rather than ad sets.
- Launching is resumable. A large test could previously exceed the request limit part-way through activating, leaving some ads live and no clear way to finish. Each pass activates what fits and reports what is left; already-active ads are skipped rather than re-sent, and a round is only stamped as launched once every ad under it is actually live.
- Refreshing keeps your place in Split Test Lab. The URL now tracks
which test you are looking at (
#split/12), and the import and setup screens too, so a reload returns you there instead of dropping you back at the list to click your way in again. Links to a specific test can be shared or bookmarked. The URL is rewritten in place rather than pushed, so Back still leaves the tool in one step rather than walking every screen you visited.
- Fixed: pushing new ad sets into an imported campaign failed immediately. The push loop walked every ad set on the test, including the imported ones, and the server correctly refuses to push those — so it stopped on the first one and no new ad set was ever created. It now pushes only the ad sets built here. Nothing was half-created by the failure; the guard fired before anything was sent to Meta.
- You now choose how many images per angle. On an imported test this was never asked for — it was derived at import as (imported ads ÷ imported ad sets), a number describing the campaign being read rather than how wide the next test should be, and it then silently governed every angle generated afterwards. Building angles from a winner now asks for both counts and shows the credit cost before you commit, and the number is editable in test settings.
- Rebuild an image from scratch. When a render comes out fundamentally wrong, editing it means negotiating with a bad starting point. The ad reviewer now has Rebuild this image, which discards the current one and renders fresh from the original spec, with a different reference leading the composition each time so it does not drift back to the same result. The copy is untouched. The existing image stays on screen until the new one lands, so a failed render costs a credit and nothing else.
- Choose the image model. Every ad rendered on Gemini 3 Pro with no way to say otherwise. There is now a picker next to the Generate button and on the setup form, showing the credit cost per image, and the estimate follows your choice. Gemini 3.1 Flash is roughly half the price and is the same family — it is already what a timed-out Pro render falls back to, so it is not an exotic option. Pro still renders text better, which matters when the headline is doing the work.
- Reference images no longer hide themselves. The upload control lived inside a warning that only appeared when there were zero references, so adding the first one made the only way to add a second disappear. There is now a permanent panel showing every reference as a thumbnail, tagged with what it is, each removable, with the upload control and the written visual style always present.
- Images can be generated with no reference ads. A campaign whose ads are all video has no still to match, and generation refused to run at all. You can now generate a written visual style from the imported ads' own copy — subject matter, lighting, register, how text sits — review and edit it, and render from that. The prompt is explicitly barred from inventing brand colours, typefaces or logos, since ad copy cannot know them. (v2.67.0)
- Reference images can be added to an existing test, and you choose what each one is: a reference ad from any brand (used for its look only — the model is told not to copy its wording or branding), or your own product, logo or brand material, which are reproduced faithfully rather than imitated. Previously everything uploaded became a style reference, so there was no way to tell the tool what your product actually looks like. (v2.67.1)
- A conversion diagnostic. "Conversions read as zero" and "there were no conversions" looked identical. Check conversions now asks Meta what it actually reports for the campaign and lists every event with its totals, ranked, with a note on each explaining the difference — pixel events, custom conversions and Meta's aggregates all count the same sales differently. Pick one and the whole history is recounted, not just the last week. (v2.66.2)
- Build new angles from an ad that already works. An imported ad set cannot be promoted — there is no stage to advance and promoting would pause your own live ads. Ticking the ads that performed now feeds their copy and real numbers to the AI, which works out why they won and designs new angles from it. Thin results are labelled so a CPA from two conversions is not treated as proof. (v2.66.1)
- Fixes. Unreadable white-on-white code labels in the dark theme; video ads importing without their thumbnail; ads with no image now say why instead of showing a blank tile; and the version in the corner is correct again — it had been stuck at 2.65.1 since the import work began. (v2.67.2)
- Import an existing Facebook campaign. Split Test Lab could only build a campaign from scratch, so an account already running ads started from nothing. You can now point it at a live campaign and pull it in with up to 90 days of history: its ad sets become angles, its ads become variants, and the leaderboard, CPA/CTR figures and Bayesian verdicts all work on them straight away.
- It never touches the campaign you imported. Go live only activates ad sets created here, Pause only pauses those, and the automatic kill rules are switched off for imported ads entirely — they are read for their numbers and nothing else. If the campaign itself is paused, activating it is a separate question you are asked explicitly, because that would restart every other ad in it.
- New ad sets go into the same campaign. They copy how the existing ad sets are optimised and billed, rather than assuming a sales objective — so a leads or traffic campaign is extended correctly instead of being rejected by Meta. Where a campaign's ad sets are configured differently from each other, you choose which one to copy rather than the tool guessing.
- Campaigns with nothing to convert are still useful. Traffic and awareness campaigns have no conversion to measure, so they are ranked on link CTR and cost per link click instead of showing an empty CPA column.
- Video, catalog and boosted-post ads import too. Their copy cannot always be read, and the tool says which ones and why rather than silently dropping them — their spend is real and belongs on the leaderboard either way.
- Two sync bugs fixed along the way. A sync that needed more than five passes was being marked failed even when nothing had gone wrong, and a long metrics pull could be driven with the wrong date window. Both affected large tests already, not just imports.
- Build new angles from an ad that already works. An imported ad set cannot be "promoted" — there is no test stage to advance, and promoting would pause your own live ads. Ticking the ads that performed and pressing Build new angles from this winner now feeds their copy and real numbers to the AI, which works out why they won and designs new angles from it. Thin results are labelled as such so a CPA measured on two conversions is not treated as proof. (v2.66.1)
- Imported ads can become style references. New images are composed from reference ads, and an imported test started with none — so the first batch came out generic with no explanation. You can now adopt the ads that work as references, and the tool warns you when there are none rather than quietly producing off-brand images. (v2.66.1)
- Creative fields are requested explicitly, which fixes video ads importing without their thumbnail. Where an image still cannot be fetched, the ad now says why instead of showing a blank tile. (v2.66.1)
- Fixed creatives being rejected. Meta retired the single "standard enhancements" field that switched off Advantage+ creative changes, and now requires every enhancement to be named individually. Ads now opt out of each one by name.
- It heals itself when Meta renames them. There is no published list of valid names and they change, so a rejected name is dropped and the request retried rather than failing the push — and the list is a runtime setting, so a future rename is a config change instead of a deploy.
- Now using the names Meta actually accepts — it lists the valid keys in its own rejection message, so the code reads that list and keeps the intersection instead of guessing one name at a time. Worth knowing: the set is much narrower than the retired bundle. Image touch-ups and text rewriting can no longer be refused on this endpoint at all. (v2.65.1)
- You are told when an opt-out is lost. The push warns if Meta refused any of them or reports an enhancement still switched on. This matters more than it sounds: with enhancements on, Meta rewrites headlines and crops images per impression, so two "identical" variants stop being identical and the test measures Meta's edits rather than yours.
- Compare image models on a real ad. Open any ad and press "Compare image models": the same prompt and the same reference images go to Gemini 3 Pro, Gemini 3.1 Flash, Seedream v4 and FLUX.1 Kontext max, and the four come back side by side with cost per image and cost per fifty.
- Because the only question that matters — can a cheaper model hold your reference style and spell the headline correctly — cannot be answered from a price list.
- Worth knowing: fal's "nano banana" models are the Gemini models we already call directly, so routing through them would be the same model with a second margin on top. The comparison uses genuinely different engines.
- Fixed the push error. Meta now requires campaigns to state whether ad sets may share budget with each other when the budget sits on the ad sets. We send false deliberately: lending 20% of an ad set's budget to its neighbours means judging an angle on a budget it never actually got.
- Campaign budget optimisation is now an option, on the setup form and in a test's settings, with the budget field relabelling itself from "per ad set" to "for the whole campaign".
- It is off by default, and the screen says why: CBO spends more efficiently, but an angle Meta starves looks exactly like an angle that failed. Comparing images within an angle still works either way — comparing angles against each other does not. Spend-triggered kill rules also fire days apart between favoured and starved ad sets.
- Which level holds the budget is fixed when the campaign is created on Meta, so the switch locks itself once a test has been pushed rather than letting the record drift from what is live.
- Fixed image renders that timed out and could never be retried. Raising the reference-image budget to 5 MB for the 10-reference change made reference-heavy renders slow enough to miss the 90-second timeout on every attempt. Each render now uses at most six references and a 3.5 MB budget, with a longer timeout for headroom.
- Which six matters. When a campaign has more references than a render can carry, the one leading that ad's composition is kept, then your own assets — the actual product and logo, whose absence makes an ad wrong rather than merely off-style — and spare reference ads are dropped first. You can still upload ten; each ad uses a rotating subset, which is what makes a batch of ads structurally varied rather than ten versions of one layout.
- Test settings can be changed after the fact. Countries, ages, target CPA, budget, kill multiple, conversion event, call to action and the never-mention list are all editable from a Settings button on the test. Ad sets already created on Meta keep what they were built with, and the dialog says so — editing a live ad set resets Meta's learning phase and makes the data either side of the change incomparable.
- Auto-kill is now a switch. Turned off, the kill rules still run and still report what they would have done — they just don't act. Worth leaving off for a first test.
- Any image can be retried individually, from inside the reviewer, whatever state the last attempt left behind.
- Fixed: an image edit that died mid-render left an ad with no picture and no way to ask for another one. Edits no longer clear the existing image — the original stays on screen and is only replaced once the new render lands, so a failure now costs one wasted render and nothing else.
- The policy check now says when it is happy, not only when it isn't: a green line confirming how many ads were screened and what was looked for. A checker that only speaks up to complain is indistinguishable from one that never ran. (v2.61.1)
- A "Next" line at the top of every test saying what the tool is waiting for you to do — and, once the ads are live, that it is now waiting on Meta rather than on you.
- Meta's ad rules are now built into the writing. Every copy and image prompt carries Meta's actual advertising standards, written as concrete rewrites rather than vague warnings. The big one: an ad may not assert or imply it knows something personal about the reader. "Depression counselling available" is fine; "Depression getting you down?" is a rejection. Same for age, money, and anything medical.
- No competitors, ever. The copy is instructed never to name another company, and the image model is told not to re-render any brand name, logo or watermark it can see in your reference ads — the quiet way a competitor's branding was ending up in a generated image.
- A "never mention" list on the split test setup, for the brands and phrases you want banned by name.
- Every ad is screened after it is written. Prompts drift, so what comes back is checked too: personal attributes, cure claims, guaranteed outcomes, results-in-N-days, before-and-after, body shaming, clickbait, and your banned list. Flagged ads are marked in the grid; open one and it names the rule, quotes the phrase and explains the fix.
- The warning is loud because the failure is silent: a rejected ad still reports as active while delivering nothing, so it doesn't look broken — it looks like a creative that flopped, and it drags down whichever angle it sits in.
- The screen never blocks a push. It is conservative, not infallible, and the call stays yours.
- Review any ad full size. Click a thumbnail in a split test and the ad opens at full size, with arrow keys to move through the rest of the angle. The grid is for seeing the shape of the test; this is where an ad is actually judged.
- Edit an image with a prompt. "Move the headline off his face", "make the price bigger" — the rendered image is edited in place, layout and typography intact, one ad at a time before anything reaches Facebook.
- Edit the copy the same way. Rewrite the hook, body, headline, link description and button by instruction, or type them yourself. At the creative stage the edit applies to every ad in the angle, because they are supposed to be identical — that is what makes it a test of the image.
- Both editors refuse to touch an ad that is already on Meta, where creatives are immutable and a local edit would just make our records lie about what ran.
- Countries are a picker now, not a box you have to know the two-letter codes for, with presets for the groupings people actually buy.
- Reference ads: up to 10 winning ads and 10 of your own assets, up from 5 and 6.
- Early indicators. Every time an ad wins, its first day, first two days and first three days are recorded and compared against the ads it beat. Over time this answers the question you actually care about: what does a winner look like before you have spent enough to be sure?
- Running ads get an early read. Each live ad is placed against its own ad set — "top third on click rate, which has gone on to win 54% of the time in your history" — so a laggard can be killed on day two instead of waiting for it to burn through the CPA threshold.
- Comparisons are relative to each ad's own ad set rather than absolute, which cancels out partial first days and the fact that a good click rate for one offer is a bad one for another.
- Meta's own delivery split is tracked as an indicator in its own right: the share of impressions it hands each ad is effectively its early prediction, available well before any conversions are.
- It refuses to guess. Every rate carries a confidence interval and is compared against the rate you would get by chance. A pattern is only presented as real once there is enough of it and the edge is big enough to change a decision — three perfect results out of three is reported as "too early", not as a finding.
- Split Test Lab now reads its own results and acts on them. Performance is pulled from Meta nightly (or on demand), and each ad set gets a leaderboard with spend, link CTR, conversions and CPA per variant.
- Ads that spend past your kill threshold without converting are paused automatically — but never below two live ads per ad set, and never more than three a day, because emptying an ad set stops delivery and resets Meta's learning. Ads that are converting are only ever flagged for you, never killed.
- Winners are called honestly. With a handful of conversions no test is conclusive, so the decision is made on link click-through rate and says so out loud. When the data genuinely cannot separate two variants it says "no measurable difference" instead of picking one, and where the lead is weak it carries the top two forward rather than gambling on one.
- Promote a winner and the next round writes itself. The winning element is frozen, losing ads are paused, and a fresh set of variants is drafted that changes only the next thing — hook, then body, then call to action. Stages 1–3 reuse the winning image, so they cost copy generation only.
- Every angle shows which parts are actually proven by testing and which are still the untested starting draft.
- Split Test Lab can now build the campaign for you. One click creates the Meta campaign, an ad set per angle, and an ad per variant — images uploaded, creatives assembled, everything paused. Going live is a separate, explicitly confirmed action, so nothing spends until you say so.
- Dry run shows the exact payloads that would be sent to Meta without sending anything, and each variant can be previewed through Meta's own renderer — which also validates the whole creative before it exists.
- Advantage+ creative enhancements are explicitly turned off, and audience expansion and placements are pinned. Left on, Meta rewrites your copy and shifts your placements between variants, and you end up measuring its changes rather than yours.
- The push is resumable and safe to retry. Meta has no way to make ad creation idempotent, so each ad carries a key in its name and a repeated push adopts what already exists instead of creating duplicates.
- Rejected ads are surfaced as such. A disapproved ad reports itself as active while delivering nothing, so it is flagged in the UI and excluded from the numbers.
- Split Test Lab (admin only) — a fourth tool that generates a structured testing matrix instead of a one-off batch. Give it your winning reference ads and an offer, and it writes a set of distinct marketing angles, each with a complete baseline ad.
- Each angle becomes its own ad set, and gets a configurable number of image variants that all share identical copy — so stage 0 measures the creative and nothing else. Later stages hold the winning image fixed and vary the hook, then the body, then the CTA, one at a time.
- Meta Ads connection in Settings, using a Business Manager System User token. It is stored separately from the existing Meta connection, because that one holds a Page token which cannot create ads.
- Cost estimate before generating, and a resumable image loop that runs three at a time — a 50-image test takes about ten minutes instead of half an hour, and closing the tab no longer loses the run.
- The admin user list now shows each user's email address.
- Signup now allows one account per email address. Emails are matched case-insensitively, and Gmail dot/+tag aliases (e.g. u.s.e.r+promo@gmail.com) are treated as the same address, to prevent duplicate accounts re-collecting the welcome credits.
- New tool: Style Match Ads. Upload ads that are already winning and generate new ones in the same look and feel — instead of the randomised styling the standard Ad Generator uses. Add your own assets in a second box (product shots, logo, headshots, brand references) and tag what each one is, so the ad places them correctly.
- Choose how closely to match per campaign: Loose (same brand world, free composition), Balanced (same style system, new layout), or Clone (reuse the reference layout as a template).
- Upload several winning ads and each generated ad rotates through them as its lead reference, so you get real variety instead of one design repeated.
- Style Match campaigns appear in History with their own badge, and reuse the existing review tools — regenerate copy, edit an image with an instruction, and download the whole set as a ZIP. The standard Ad Generator is unchanged.
- Billing is now available to everyone — add a card, top up credits, and set automatic top-ups from the new Billing tab. (Cinematic remains admin-only.)
- Admins get a Pricing & margins panel in Billing: set a per-service margin (or a global default), adjust each provider's raw cost as pricing shifts, and monitor real cost-vs-revenue and realised margin per service over the last 7/30/90 days.
- Credit pricing is now tied directly to real provider costs. Every paid service — text, images, video, voice, lip-sync, sound effects, music, captions, avatar videos — is metered at its actual cost plus a standard margin. Prices update automatically if a provider's cost changes. Image, video, and avatar-video credit costs rise to reflect true costs; text (writing) costs drop. Nothing an external service does is unbilled anymore.
- Admin — the user list now shows each account's live credit balance from the new ledger, instead of the frozen pre-migration number.
- Billing (admin) — a new Billing area to add a card on file and top up credits, powered by Stripe hosted checkout. Auto top-up when your balance runs low, with a monthly limit. Plumbing only for now; it stays inactive until payments are configured.
- New credit-ledger billing system. Your balance is now tracked as an append-only ledger of every grant and charge, which makes it accurate and fully auditable. Prices are unchanged. Every account starts with a 500-credit opening balance.
- Cinematic: lip-sync now happens on the Video tab, not during assembly — so you can review each voiced scene in its finished form before compiling. When you generate (or turn on "Use generated voice" for) a dialogue shot, it generates your ElevenLabs voice and lip-syncs the clip's mouth to it right there; the card then previews the finished voiced clip. It's cached per shot, with "Redo"/"Retry" controls. Assembly no longer waits on lip-sync (it only fills in as a safety net if a voiced clip is somehow missing).
- Cinematic: "Use generated voice" shots are now properly lip-synced to your ElevenLabs voice. During assembly, each such shot is passed (clip + your voice) through a lip-sync model that re-animates the mouth to match your audio — so you get a consistent chosen voice AND matching lips, instead of the model's varying voice or a drifting overlay. It's cached (only runs once per shot unless you regenerate the clip or change the line), best-effort per shot (~$0.20/shot), and adds a little time to assembly. Shots left on the clip's own audio are untouched.
- Cinematic: added a warning when "Use generated voice" is ticked on a clip that already has its own lip-synced talking (veo / kling-3). Overriding a native-audio clip with a separately-recorded voice can't match the animated lips — the warning now points you to untick it and use the clip's own perfectly-synced audio instead.
- Cinematic: found and fixed the remaining audio drift. Separately from the sample-rate fix, each shot's video could land a frame short of its target length (frame-rounding, and the trim step can only shorten, never extend), so the picture fell progressively behind the audio. Each shot's video is now over-padded before the exact trim, guaranteeing its length matches the audio to the frame — voices stay locked to the picture across the whole film.
- Cinematic: your generated background music is now kept across re-assembles instead of being regenerated each time. Re-assembling reuses the exact track from your last assembly (so a take you like is preserved); a "Regenerate music" checkbox on the final screen makes a fresh one or applies description changes when you want a change.
- Cinematic: fixed the injected voices drifting out of sync in the stitched final cut. The video runs at 24fps but the audio was 44.1kHz, which don't divide evenly (1837.5 samples per frame) — so a tiny rounding error accumulated at every shot boundary and slid the voice off the picture the further into the film you got. Audio is now 48kHz (exactly 2000 samples per frame), so every shot stays sample-locked to the video and the voices stay in sync all the way through. Re-assemble to get the corrected mix.
- Cinematic: full control over the background music. Beyond the mood presets, the final screen now has an editable "Music description" box — pick a mood to pre-fill it, then rewrite it however you want (specific instruments, tempo, no drums, how it should build, etc.). Your description is sent straight to the music generator.
- Cinematic: fixed background music silently not being added. The music generator returns its file under a field name we weren't reading, so the finished track was treated as "failed" and skipped every time. Now read correctly — and music generation failures are logged instead of swallowed, so any future issue is diagnosable. Re-assemble to get music in your cut.
- Cinematic: the Sound FX tab now opens with each prompt pre-filled. If a shot already has an SFX prompt it's shown for you to tweak; otherwise the AI reads the scene and suggests fitting sound effects (e.g. "echoing footsteps, dripping water, distant machinery") as an editable starting point — so you're adjusting rather than writing from scratch. A "✨ Suggest prompts" button re-fills any empty ones on demand.
- Cinematic: new Sound FX tab. For each silent clip you can now write exactly what it should sound like (e.g. "distant crowd murmur, footsteps on stone"), generate the effects, and preview them per shot before assembling — regenerate or remove any you don't like. Whatever you set is used in the final cut; shots you don't touch are still auto-filled during assembly. Reachable from the stage bar or the "🔊 Sound FX →" button on the Video screen once you have clips.
- Cinematic: silent clips now get sound effects. During the final assembly, any shot whose model doesn't make its own audio (wan, hailuo, kling-2.5) and has no dialogue gets ambient foley matched to what's on screen (via MMAudio, ~half a cent per clip), instead of playing in dead silence. Clips with their own audio or dialogue are untouched; it's best-effort per shot, so a failure just leaves that clip silent under the music.
- Cinematic: background music is now a real full-length track instead of a tiny looped snippet. The assembly generates an instrumental piece (via CassetteAI text-to-music) sized to the whole film and mixes it under the dialogue — no more 2-second loop repeating endlessly. (Films longer than ~3 minutes may repeat the track once; a few cents per assembly.)
- Cinematic: fixed a 500 error when starting/re-assembling the final cut. A transient database timeout while writing the initial assembly state wasn't being retried, so it surfaced as a hard failure; all the assembly's database writes now retry through the same backoff used elsewhere, so a cold-DB blip no longer derails an assemble.
- Cinematic: background music now actually works in the final cut. Previously the chosen mood was saved but never reached the stitcher, so it did nothing. The assembly now composes a mood-matched music bed (via MMAudio) and mixes it quietly under the dialogue across the whole film. Music is optional — if it can't be generated, the film still assembles without it.
- Cinematic: the final cut now uses each clip's own audio when the model generated it (veo-3.1, kling-3.0-pro) — the model's dialogue + sound effects, perfectly synced to its animation — instead of muting it and overlaying a separately-generated voice that drifts out of time. Each shot has a new "Use generated voice" toggle to override back to the ElevenLabs voice per shot. Clips from models that don't make their own speech (wan, hailuo, kling-2.5) still use the generated voice. This reverses the earlier "audio off" cost saving, so veo/kling-3 clips cost more again (veo ~$3.20, kling-3 ~$0.84) — the model picker shows the updated prices.
- Cinematic: prompts are now auto-softened to avoid the video model's content-safety checker — high-risk wording in the shot's visual description is swapped for neutral, still-filmable language (e.g. "lifeless" → "unconscious", "corpse" → "fallen figure") before the keyframe and video are generated. Spoken dialogue is left untouched. New scripts are also broken down with safer phrasing up front. Regenerate the keyframe (then the video) on a flagged shot to apply it.
- Cinematic: fixed the skip flag not being honoured by the assembly/stage gates — you can now assemble the final cut (and advance stages / build the animatic) once every non-skipped shot is done, instead of being told "every shot needs a completed video".
- Cinematic: added a "Skip from final cut" toggle on each shot in the video screen. Flagged shots are excluded from the compile (and from the done/cost/stuck counts and the "Assemble" gate), so a single shot that keeps getting rejected — e.g. by the video model's safety checker — no longer blocks you from assembling the rest of the film.
- Cinematic: added a per-generation fal ledger — every shot video now records a "submit" entry at the moment fal is charged (with the exact model, resolution, duration and request id + our $ estimate), plus "success" (with file size and generation time) and "failed" (with the real reason) entries. Viewable at
/api/admin/video-log, which also rolls up total estimated spend. This makes it easy to confirm we're not over-spending: exactly one charge per generation, minimal durations, no duplicate submits.
- Cinematic: fixed a wan-2.6 regression from v2.49.36 — it was being sent resolution "480p", which wan-2.6 doesn't accept (only 720p/1080p), causing every wan shot to fail. wan now uses 720p on Low (still cheaper than its old 1080p default) and 1080p on High.
- Cinematic: much better failure logging — when a shot's video fails, we now capture fal.ai's actual error message (bad parameter, safety-checker rejection, unsupported image, etc.) instead of a bare "Failed to fetch result (422)", show it on the shot card, and record it in the admin error log so recurring failures are diagnosable.
- Cinematic — big fal cost cuts: native audio is now forced OFF on every shot (we always overlay our own voice/music, so the model's audio was pure waste) — that alone cuts veo-3.1 ~50% and kling-3.0-pro ~33%. Added a Low/High resolution toggle on the video screen (defaults to Low ≈ half cost). The model picker now shows the real fal price per clip (e.g. veo-3.1 ~$1.60 ⚠, wan ~$0.20) with a warning on premium models, and the video screen shows the real "cost to finish" in dollars instead of misleading credits.
- Cinematic: the video and animatic screens now lazy-load clips (show a keyframe poster, only fetch the video when you press play) so 20+ embedded videos no longer hammer browser memory.
- Cinematic: when video generation fails because the fal.ai balance is exhausted, you now get a clear "top up your balance" message instead of a generic "failed — retry", and "Generate all remaining" stops immediately instead of hammering through dozens of doomed attempts.
- Cinematic: fixed the "Reset stuck" button falsely counting shots that had just been submitted and were still generating. It now only flags shots that have been processing far longer than any real generation takes (~12 min), so freshly-started shots are left alone.
- Cinematic: added a "Reset stuck" button on the video screen. If shots are wedged on "processing" (e.g. a generation job that expired days ago and can never return), this clears them back to un-generated so you can regenerate them.
- Cinematic: a shot stuck "processing" (e.g. an expired generation job) now times out after ~9 minutes and becomes clickable again to retry, instead of spinning forever.
- Cinematic: fixed shot video generation hanging or silently failing under load. Database write blips during generation are now retried, so a shot can't be orphaned after it's submitted or get stuck mid-download — and stuck shots now self-heal on the next check.
- "Generate" now shows the shot as processing immediately, and "Generate all remaining" reports progress, no longer stops at the first hiccup, retries once, and tells you exactly which shots (if any) need a re-click.
- Cinematic: you can now change a shot's video model directly on the animatic and video screens (previously storyboard-only) — so if a "seamless" shot warns it needs Kling, you can switch it right there instead of going back.
- Cinematic: the "Seamless into next shot" toggle now appears on all three stages — storyboard, animatic, and video. It's the same flag everywhere, so setting it on one page shows up on the others as you jump between them during the final edit.
- Cinematic: added an "Auto-detect continuations" button on the storyboard — the AI flags which shots flow into the next with no cut and sets the "Seamless into next shot" toggles for you, without changing your models, cameras or durations. Lets existing projects use seamless continuity without re-storyboarding.
- Cinematic: seamless scene continuity. Shots that flow into the next with no cut can now end exactly on the next shot's opening frame, so continuation scenes no longer jump. The AI flags likely continuations in the storyboard and you can toggle "Seamless into next shot" on any shot.
- Seamless joins use the next shot's keyframe as the clip's end frame (Kling 2.5 / 3.0 only) — the storyboard warns and nudges the model when needed. Fresh-cut shots and the animatic are unchanged.
- Cinematic video screen now shows each shot's scene number, action, dialogue and camera under the clip, so you can watch and confirm the video matches the script.
- Regenerating a shot now opens a dialog: describe the change you want, and/or edit the exact prompt that built the shot (your edits are saved for next time). Nothing regenerates until you confirm.
- The stage bar lets you jump forward to any stage whose work is already done — rebuilding the animatic no longer traps you away from your finished shot videos. Renamed the animatic's confusing "generate shot videos" button to "Continue to shot videos" (it never charged anything).
- Fixed the intermittent "failed (500)" seen on the very first action right after an update (a cold-start database hiccup that used to surface as an error, then worked on refresh). The first request now shrugs it off and self-heals instead of erroring.
- Errors are now recorded to a persistent log (admin → errors), so any future glitch can be diagnosed after the fact instead of vanishing.
- Cinematic: fixed audio slowly drifting out of sync over long animatics. Each shot's voice is now padded to fill its frame-aligned slot and joined losslessly, with a single final audio encode — so picture and sound stay locked from the first shot to the last.
- Cinematic: the animatic/final stitch no longer uses fal's merge tool (which kept failing). Videos are now assembled by a dedicated ffmpeg service, so shots concat reliably with per-character voices and burned captions.
- Cinematic: animatic shots are now sized to their full voiceover (base clip looped to the voice length, then audio merged), fixing the ~2s cutoff.
- Cinematic: fixed animatic shots being capped at ~2 seconds and cutting off longer voiceovers 2014 each shot now holds long enough for its full dialogue (or its shot duration when silent).
- Cinematic: the animatic screen now shows each shot clip live as it's generated (with its dialogue), so you can review shots individually while the build runs 2014 no need to wait for the full stitch to spot a problem.
- Cinematic: the stitch 422 is fully fixed 2014 the generated silent audio now matches the dialogue clips' sample rate (44.1kHz), so all clips concat cleanly. Only the silent shots rebuild; dialogue clips are preserved.
- Cinematic: fixed the animatic/final stitch failing (HTTP 422) when some shots had dialogue and others didn't 2014 every shot clip now carries a uniform audio track (voice or silence), so the concat no longer rejects the mix. Clips are versioned so old ones rebuild once.
- Cinematic: fixed assembly stalling at the stitch step and showing a “Build” button mid-run — the concat/music/caption phases weren't recognised as “in progress”, so the cron stopped driving them and the UI looked idle. All active phases are now handled uniformly (cron keeps driving, UI shows live progress).
- The app HTML is no longer browser-cached, so new versions load automatically without a manual hard-refresh (fixes stale UI showing an old cinematic screen)
- Cinematic: the cron now runs schema migrations and auto-recovers an assembly that failed from a first-deploy migration race — so server-side runs heal themselves without a manual restart
- Cinematic: assembly is now resumable — each shot's built clip is saved, so a restart skips finished shots (52/66 done means only 14 to build) instead of starting over. The stitch phase resumes too.
- Cached clips auto-refresh when you change a keyframe, shot video, or dialogue, so edits are always reflected.
- Cinematic: while an animatic/final assembly is running, the screen now shows live progress (with a Cancel option) instead of a “Build” button — so an in-progress run can no longer be accidentally restarted from step one
- Cinematic: smoother server-side assembly — a driver now releases its lease when it yields, so the next tick resumes immediately instead of stalling for a minute between batches
- Cinematic: animatic and final assembly now run fully on the server (cron-driven, leased) — you can close the window and they keep going; reopening shows live progress or the finished video
- Cinematic: fixed the animatic “Build/Rebuild” button re-showing the old failure instead of starting a fresh build (the click event was being mistaken for a “resume” flag, so it skipped the reset)
- Cinematic: fixed animatic/final assembly failing on long films — clips are now stitched in batches (recursively) instead of one oversized merge call that fal rejected. Re-running reuses existing keyframes and dialogue audio.
- Cinematic: optional per-asset “physical scale / weight” note — injected into every keyframe and video prompt that uses the asset, so size and mass stage correctly (e.g. a huge, heavy object isn't framed as something a person casually holds)
- Cinematic: the stage progress bar is now clickable — jump back to any reached stage (e.g. Assets or Storyboard) and forward again without re-running generation
- Cinematic: re-running the storyboard or breakdown now asks for confirmation so you don't accidentally overwrite edits
- Cinematic: keyframe generation never falls back to a reference-less (text-only) image — it always keeps the character/location references, so results stay on-model instead of varying wildly
- Cinematic: fixed broken styling after a page refresh on a film (cinematic styles are now injected on every entry, including deep-link restore)
- Cinematic: keyframe Edit now passes the shot's locked character/location references, so edits keep characters on-model instead of redrawing a new person
- Cinematic: refreshing now keeps you on the current film (deep-linked #film/N) instead of jumping to New Campaign
- Cinematic: name a film when creating it, and rename any film from the list or the film header
- Cinematic refinements: storyboard cost breakdown (per-scene + total), per-asset reference images with context notes, keyframe storyboard context + click-to-enlarge
- Keyframes: per-shot reference visibility/editing, fixed Edit (image cache), throttled generation to avoid provider rate limits, recover stuck shots, clearer error messages
- Assets: "Lock all ready" + explicit gate status for continuing to storyboard
- Cinematic Video (admin): turn a 5–10 min script into a fully assembled short film
- Staged pipeline: script breakdown → locked asset library → storyboard → keyframes → animatic → shot videos → final cut
- Consistency: multi-reference keyframes + per-character locked ElevenLabs voices + shot-to-shot continuity
- Per-scene video model suggestion (cheap for B-roll, premium for close-ups), timed animatic gate before paid video, auto-stitched final with music & captions
- Optionally seed the asset library with your own character/location photos
- Voiceover captions: auto-generated text captions burned into voiceover videos via fal.ai
- Full caption styling: font (5 options), color (6 presets), size, position, alignment, outline, text case
- Words per caption control: shorter phrases prevent text from overflowing the video edges
- Post edit: AI Rewrite, Save, and Cancel all moved above textarea — no scrolling needed
- Carousel: font family selector (Sans-serif, Serif, Monospace, Display) and font size slider (32–72px)
- Fix: "Mute original video audio" now works — uses MMAudio V2's mask_away_clip API parameter
- Instagram vertical crop: 9:16 images are pre-cropped to 4:5 from the top, preserving headings
- Fix: Edit & Retry from History now correctly carries over source material
- Title style dropdown: 7 style options (hook, clickbait, question, etc.) for image titles
- Add background music to videos — 6 mood presets (Upbeat, Chill, Corporate, Dramatic, Fun, Ambient) powered by fal.ai MMAudio V2
- Music modal with editable prompt, per-mood preset text
- Remove music option to revert to silent version
- Publishing (GHL) automatically uses music version when available
- Multi-model video — 5 video models via fal.ai: Wan 2.6 (20 cr), Hailuo 02 (27), Kling 2.5 Turbo (35), Kling 3.0 Pro (56), Veo 3.1 (100)
- Async video generation — queue-based pattern replaces old blocking approach; videos now reliably complete
- Model selection modal with descriptions and credit costs
- Video progress state shown in Transform dropdown
- Selfie Mode — upload your own photos to a selfie library, then use them as post images
- AI selfie enhancement — Gemini enhances each selfie to match the post topic and adds stylized text overlay
- New Selfies tab for managing your photo library
- LinkedIn carousel fix — upgraded API version (was sunset), added document processing wait before publishing, fixed silent failures when PDF retrieval fails
- Carousel posts — convert any single-image social post into a multi-slide carousel with blurred cover background + GPT-generated text per slide
- GHL carousel scheduling: multi-image posts for Instagram & Facebook
- LinkedIn carousel scheduling: PDF document upload for swipeable carousel experience
- Improved cron error handling: publish failures are now always logged even when D1 times out
- Client approval — send a shareable link to clients so they can review, approve, or reject individual posts without logging in
- Approval badges on post cards reflect client decisions in real time
- GHL calendar integration — scheduled GoHighLevel posts now appear in the calendar view with per-channel error reporting
- Mobile-friendly — responsive layout for all screens: hamburger nav, stacked forms, single-column grids, larger touch targets
- Onboarding wizard — new users are guided through creating their first social posts step by step, one field per screen with explanations
- Parallel social post generation — campaign social posts now generate in parallel with images instead of waiting, so they always complete even if images fail
- Logo compression — logos are now automatically resized and compressed before upload (max 800px, JPEG). A 2 MB logo becomes ~50-100 KB, dramatically improving image generation reliability
- Timeout tuning — first-attempt timeout for multimodal requests increased from 25s to 45s
- Concurrency reduction — when a reference image is attached, batch size drops from 3 to 2 concurrent requests to avoid overloading the AI
- IDOR hardening — all API endpoints now enforce resource ownership checks; scheduling endpoints verify post ownership
- Image gen diagnostics — every failed generation attempt logs prompt size, ref image size, model, timeout, and error type to the error log
- Image type diversity — new imageType dimension (graphic, photo, meme, illustrated, cinematic, mockup, infographic, collage) for visually distinct ads
- Randomized variations — ad specs now shuffle all creative dimensions per campaign instead of deterministic assignment
- Server-side logo cap — oversized logos loaded from R2 (old uploads) are skipped to prevent worker timeouts during regeneration
- Crash-safe background tasks — top-level try/catch around all waitUntil bodies to ensure errors are always logged
- GHL calendar debug — new
/api/ghl/debug-postsendpoint returns raw GHL API response for diagnosing why scheduled posts don't appear in the calendar - Conflict checking — single-post scheduling on GHL, Meta, and LinkedIn now warns if another post is already scheduled at the same time
- Visual diversity overhaul — ad images are now dramatically more varied. Expanded from 5 layouts to 10, 5 styles to 8, 4 backgrounds to 8, and 10 palettes to 14
- New layouts — diagonal, asymmetric, poster, layered, typographic, floating — each producing distinct visual compositions
- New styles — retro (vintage/halftone), luxe (metallic/premium), handcrafted (brush strokes/organic)
- New backgrounds — texture (marble/metal/fabric), 3D (rendered shapes with lighting), duotone, bokeh (cinematic blur)
- New palettes — pastel (lavender/pink), earth (terracotta/sand), neon (electric accents on black), cream (warm ivory/copper)
- Creative prompts — Gemini now gets creative direction instead of rigid blueprints, producing more unique and eye-catching images
- HeyGen video scheduling — posts with HeyGen videos can now be scheduled to Instagram (as Reels), GHL, and Facebook. Previously, posts without images but with HeyGen videos were rejected at scheduling time
- Media fallback — GHL and Instagram scheduling now automatically falls back to HeyGen video when no image exists, instead of failing with "no image available"
- Instagram Reels — HeyGen videos are published as Instagram Reels via Meta's Content Publishing API
- Anti-repetition — each post now sees the actual text of prior posts in the series, so the AI avoids reusing sentences, phrases, and examples across posts
- Max characters setting — optional field to cap post length (e.g. 800 for shorter posts). Stored per session and used on regeneration too
- GHL scheduling fix — scheduling now fails with a clear error if the image hasn't been generated yet, instead of silently scheduling as text-only
- Calendar fix — calendar now shows all posts (pending, published, failed) instead of only pending, so past months are no longer empty
- Calendar error logging — all 4 data channels (IG, Facebook, GHL, LinkedIn) now log errors instead of silently failing
- Status badges — calendar events show colored status dots: green for published, red for failed, with reduced opacity on published posts
- Single-image regen resilience — 90s timeout per attempt (up from 25–60s), 4 attempts (up from 3), stays synchronous so the Worker gives Gemini the full request lifetime
- Image generation resilience — increased timeout (45s → 60s), 3s retry delay, reduced concurrency (3 → 2) to prevent Gemini overload cascades
- Logo preserved on retry — supporting image now included on fallback attempt 2 (Flash + logo), only dropped as last resort on attempt 3
- HeyGen error clarity — failure messages now show the actual reason (e.g. insufficient API credits) instead of generic errors
- HeyGen import fix — importing portal-generated videos by ID now falls back to v1 API when v2 returns 404
- Paste Text source option — replaced "None" with a textarea to paste articles, notes, or transcripts directly as source material
- No-images mode — new "No images — text only" option in the image model selector, skips image generation entirely for text-only or video-focused workflows
- Features & Changelog page — this page! Accessible via the info icon in the nav bar
- LinkedIn integration — OAuth connection, single & bulk post scheduling, calendar view, cron-based publishing with token refresh
- HeyGen integration — AI-generated talking-head videos from post text, avatar & voice selection, script generation, R2 storage
- Background image generation — campaigns and posts return instantly while images render in the background with live polling
- Gemini resilience — 3-attempt retry with timeout, automatic Pro → Flash model fallback, simplified prompt fallback
- Deep links — URL hash routing for sessions and campaigns (#session/N, #campaign/N) survives page refresh
- Campaign clone improvements — logo and ref_context carry over when cloning
- Error context logging — structured context data on all error log entries for easier debugging
- Initial release — ChannelCopy with campaign creation, social post pipeline, GHL & Meta scheduling, calendar view, admin panel, credit system
Selfie Library
Upload photos to use in Selfie Mode when creating social posts.
Settings
Manage your integrations and connected accounts.
Integrations