Weekly automated detection of new and updated entries on
portfolio.apexure.com, writing skeleton drafts
into _landing-page-examples/ for editorial
review.
.github/workflows/portfolio-sync.ymlmax_drafts input)ruby script/portfolio-sync/sync.rb (writes drafts to working tree)https://portfolio.apexure.com/wp-json/wp/v2/postsstate.json — a manifest of every WP post ID
we’ve seen and its last-known modified timestamp_landing-page-examples/<slug>-landing-page.md with:
portfolio-id and portfolio-url for re-matching, published: falsestate.json so subsequent runs only flag genuine deltasPortfolio sync — new portfolio.apexure.com itemsSLACK_WEBHOOK_URL secret is configured)Drafts are published: false, so Jekyll skips them at build time — they
never appear in the lookbook listing or get a public URL until a human flips
the flag.
When you git checkout a sync branch in VSCode and ask Claude to fill in the
drafts, this is the contract it implements per file:
| Field | What to do |
|---|---|
title |
Replace TODO with: <Client> <Vertical> Landing Page \| CRO Breakdown (~60-70 chars) |
description |
1-2 sentences, ~150-200 chars, mentions vertical and the standout conversion mechanic |
meta-keywords |
5-10 search terms for the vertical and page type |
industry |
Pick from the established taxonomy (SaaS, Healthcare, FinTech, etc.) |
page-type |
Lead Generation / Sales / Click-Through / Squeeze |
technology |
WordPress / Unbounce / Webflow / etc. — inferred from the rendered page |
audience |
B2B / B2C / B2B2C |
cro-score |
Integer 0-100 — see scoring rubric below |
primary-cta |
Verbatim button text from the live portfolio page |
conversion-goal |
Demo Request / Trial Signup / Lead Form / etc. |
design-features |
List, drawn from established taxonomy |
psychological-principles |
List: authority-bias, social-proof, cognitive-load, visual-hierarchy, scarcity, etc. |
your-question-section |
5-8 FAQ entries, each ~200-400 words, doubling as CRO commentary |
social-image |
Generate /images/generated/lp-<slug>.svg |
published |
Flip false → true once everything above is done |
The <!-- DRAFT auto-generated --> block at the bottom of each file is a
checklist for the agent. Delete it once the file is fully authored.
Score 0-20 in each of five dimensions, sum to a total out of 100:
| Dimension | What to look at |
|---|---|
| Hierarchy | Is the value prop instantly clear? Single dominant element above the fold? |
| Clarity | Headline-ad match, plain language, scannable structure |
| Proof | Specific named clients, metrics, testimonials with attribution |
| CTA | Single primary CTA, contextual, low form friction |
| Friction | Form length, navigation distractions, trust signals near commit point |
A strong B2B SaaS demo page typically scores 80-95. A page with distracting navigation, generic stock proof, and a fuzzy headline scores 50-70.
{
"last_run": "2026-05-02T11:02:37Z",
"items": {
"2823": {
"slug": "valley-solar",
"modified": "2026-04-28T08:16:16",
"link": "https://portfolio.apexure.com/valley-solar/"
}
}
}
Keyed by WP post ID, so slug renames don’t break tracking. Committed to the repo so the cron is stateless across runs.
When this system was set up, portfolio.apexure.com had 495 posts vs.
311 examples in _landing-page-examples/ — i.e. ~184 portfolio items
have never been written up here. The cron does not auto-draft this
backlog: it was seeded into state.json as “already known” so weekly runs
only flag genuinely new items.
If you want to chip away at the backlog:
# Drop a slug from state.json and re-run sync — that one item gets drafted
ruby -rjson -e '
s = JSON.parse(File.read("script/portfolio-sync/state.json"))
s["items"].delete("2823") # ← whichever WP post id
File.write("script/portfolio-sync/state.json", JSON.pretty_generate(s) + "\n")
'
ruby script/portfolio-sync/sync.rb --max-drafts 1
Or batch through them with --max-drafts 5 once a week.
To enable the Slack notification:
https://hooks.slack.com/services/...)SLACK_WEBHOOK_URLThe workflow gracefully no-ops the Slack step if the secret isn’t set.
| What you’d do | How |
|---|---|
| Force a sync now | Actions tab → Portfolio sync → Run workflow |
| Cap drafts on a one-off run | Actions tab → workflow_dispatch → set max_drafts |
| Inspect deltas without writing | ruby script/portfolio-sync/sync.rb --dry-run |
| Reset state from current portfolio | ruby script/portfolio-sync/sync.rb --init |
| Disable temporarily | comment out the schedule: block in the workflow |
| Change cadence | edit the cron expression — current is 0 8 * * 1 (Mon 08:00 UTC) |