May 2026

ContentOps

A brand's voice was only as consistent as whoever wrote that day's post, and a campaign's results never made it back into how the next one got written. ContentOps is the system that closes both gaps — a workspace where voice is structured once, content is generated against it per channel, and performance data trains the next draft.

It also has a version-one story. The first build stood the whole thing up fast inside Lovable, an AI app builder, and Lovable is exactly where it stopped being able to grow.

01

At a glance

Role
I designed the architecture and own the skills/prompt system, and I build and evolve the app directly, in Cursor, rather than through a no-code builder.
Built on
React and TypeScript on Supabase (Postgres, Auth, Storage), Payload CMS for owned pages, and functions for generation, evaluation, and publishing.
Publishes to
Payload-backed website pages, plus Meta, Google Ads, TikTok, and LinkedIn through native publish and discovery functions.
Quality gate
A new skill version is scored against a frozen set of benchmark briefs before it can become the version generation actually uses.
02

The problem

A brand's voice was only as consistent as whoever wrote that day's post.

  1. Every channel needed its own copy, written by hand

    A Google ad, a Meta ad, a LinkedIn post, and an email each carry their own character limits and structure. Each one got rewritten from scratch by whoever was free, and consistency depended on how closely that person happened to follow guidelines nobody was checking against.

  2. Brand voice lived in a document, not in the tool

    Voice, audience, and product guidelines existed as a reference doc. Whether a given piece of copy actually reflected them was a matter of who wrote it and how recently they'd reread it, which means it was not reliable.

  3. Performance never fed back into production

    A campaign's results sat in an ads dashboard. The next brief got written the same way regardless of what the last one proved, so nothing about the system improved with volume — it just got more expensive to run at volume.

03

Where it stands

500+website pages generated and published through the pipeline
20+per-content-type skills authored and versioned
34edge functions running generation, evaluation, and publishing
5channels wired for one-click publish: Payload, Meta, Google Ads, TikTok, LinkedIn
04

How it works

Scroll to see the full diagram

Input, once per workspace A team defines Brand DNA in SKILLS.md — voice, audiences, products, competitors, goals Configuration Per-content-type skills: a system prompt plus guidelines and limits Author or import skill_versions Benchmark briefs Eval score Every save snapshots a version. Each candidate is scored against frozen briefs before it can go live. Production A brief becomes reviewed, on-brand drafts inside one campaign workspace Brief Generate Variations Human review Generation calls the champion skill version and records exactly which version produced each draft. Payload Output, one click Payload for owned pages, four ad platforms through native publish functions Payload CMS Meta Google Ads TikTok LinkedIn Each platform gets its own discovery step to pick an account or campaign, and UTM parameters are generated automatically on publish. Feedback Daily metrics sync back from every platform into one performance table Weekly digest, human-approved
The dashed line is the part that makes this a loop rather than a pipeline. A weekly job drafts an improved skill version from what actually performed, but it only ever files a proposal — a person approves the promotion, and it has to clear the benchmark-brief eval first.
05

Version one, and why it changed

The first version was fast to stand up and expensive to keep.

  1. Lovable proved the shape, then became the constraint

    The initial build was assembled in Lovable, an AI app builder, which is a fast way to get a real app — auth, a database, dozens of edge functions — in front of a team. It got the architecture right early: Brand DNA, skills, campaigns, and the Supabase schema underneath all trace back to that first pass. What didn't hold up was iterating on it at the pace a skills-versioning and evaluation system needs. Lovable bills by usage, and every prompt-driven change to a backend this size is a paid generation, not a local edit.

  2. Moving into Cursor made the codebase mine again

    The app moved from being generated through Lovable's chat interface to being developed directly, in Cursor, against the same Supabase project. Nothing about the data model or the edge functions had to be thrown away — changing tools changed how the code got written, not what it was.

  3. A headless CMS replaced a dashboard as the output

    The heaviest use case — generating and publishing website pages at volume — didn't need a bespoke content dashboard as its final home. Payload, a headless CMS, became the publish target for owned pages, which is where most of the 500+ pages this system has produced actually live.

06

Design decisions

Most of these exist to make the feedback loop trustworthy, not just fast.

  1. A skill is a version, not a live prompt

    Every edit snapshots an immutable row in skill_versions rather than overwriting what's currently running. Generation records exactly which version produced a given draft, so a performance number always traces back to a specific, inspectable configuration — not "the current prompt," which by the time anyone asks has usually already changed.

  2. Champion and shadow, not one live version

    Each skill carries a champion version, the one generation actually uses, and a shadow version, a candidate being evaluated. A new version has to clear the benchmark-brief eval before it's promoted, so an improvement is a comparison against a frozen benchmark rather than a feeling that the new copy reads better.

  3. Benchmark briefs are frozen on purpose

    The benchmark inputs used to grade a skill version are curated once and then locked. If the test set could drift alongside every skill change, a version could look improved simply because the bar it was measured against moved with it.

  4. Secrets never reach the browser

    Platform credentials for Meta, Google Ads, and the rest are written and read only inside edge functions, using the service role. The client reads a credential-stripped view of the same table. A compromised browser session can't leak an ad account's access token, because it was never sent one.

  5. The loop proposes. A person approves

    The weekly digest job drafts an improved skill version and files it as a proposal — it doesn't self-promote. Every version currently live got there because someone read the benchmark-brief scores and the real traffic and decided it was actually better, not because a cron job decided for them.

07

What I did

I designed the architecture and the skills system, and I'm the one building it — in Cursor, not a no-code tool.

  1. Designed the project-scoped data model

    Projects as the isolation boundary, Brand DNA and skills scoped underneath it, row-level security on every table keyed to project membership. That structure is what lets campaigns, skills, and performance data all live in one place without one team's content ever being queryable by another's.

  2. Built the skills and evaluation system

    The skill_versions / skill_runs / golden_briefs / skill_evals / skill_proposals chain — the part of the system that turns "the AI wrote something" into "the AI wrote something, we know which version, and we know if it's actually good." This is what makes the loop a loop instead of a one-way pipe.

  3. Moved development off Lovable and into Cursor

    Kept the Supabase schema and the edge functions, changed how they get written. What used to be a paid, chat-driven generation against the whole app became ordinary version-controlled development against the same backend.

  4. Introduced Payload as the publish target for owned pages

    Wired page generation to a headless CMS instead of a bespoke dashboard, which accounts for most of the 500+ pages this system has shipped — Payload structures and serves them, ContentOps just has to produce a draft worth publishing.

  5. Wired the ad-platform integrations

    Discovery and publish functions for Meta, Google Ads, TikTok, and LinkedIn, each with its own account or campaign picker and its own publish shape, sharing one UTM-generation utility so attribution stays consistent across all four.