Skip to content

feat(ads): rotate the house ad so unsold slots stop looking frozen - #174

Merged
ralyodio merged 1 commit into
masterfrom
feat/rotate-house-ads
Jul 31, 2026
Merged

ralyodio merged 1 commit into
masterfrom
feat/rotate-house-ads

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Why

profullstack.com/motd prints the same CrawlProof ad block on every request, which reads as a stale or cached endpoint.

It isn't caching. Both ends are already correct:

  • /api/ads/motd is force-dynamic + cache-control: no-store, and picks a bid-weighted random winner per request.
  • The /motd consumer in profullstack-web fetches per request with cache: 'no-store' and serves Cache-Control: no-store (verified against live headers).

The block never changes because the network has no paid terminal inventory yet, so every fill is the house ad — and the house ad was one hard-coded creative. One thing to serve, served identically, forever.

What

HOUSE becomes HOUSE_VARIANTS, a pool of six, drawn per fill. Copy is pitched at both sides of the network: publishers with a surface to monetize, advertisers with a product to sell.

Effects:

  • Unsold slots rotate on every request instead of freezing.
  • The existing 10% HOUSE_AD_ROTATION_RATE on sold slots stops repeating itself.

Two details worth review:

  1. The draw happens once, in houseFill, and is threaded through both houseCreative and renderHouseAdHtml. Drawing independently in each would let a single impression pitch one thing in its HTML and another in its text — covered by a test.
  2. utm_content is added to non-terminal click URLs only. The terminal URL is printed as literal text inside the ASCII box and has no width to spare.

House fills stay unmetered and unbilled; campaignId/creativeId remain "house".

Verification

  • npm run typecheck clean.
  • Full suite: 1221 passed, 7 skipped (was 1217 + 4 new).
  • All six variants rendered through renderCreativeText at cols=44/72/120: ASCII-clean, boxes stay rectangular.
  • New test tests/contract/ads-house-rotation.test.ts confirmed to fail on master (2 of 4) and pass on this branch.

One pre-existing issue this surfaced but does not change: at cols=44 the click-URL line overflows the box (58 chars vs 44). It is URL-length driven and identical on master across all variants. Flagging it rather than fixing it here.

🤖 Generated with Claude Code

Until a slot has paid inventory every fill on it is a house ad, and the
house ad was a single hard-coded creative. That made the unit
byte-identical on every request: profullstack.com/motd printed the same
"Your ad here" block on every login, which reads as a cached or broken
endpoint rather than a live one. Nothing was actually cached -- both
/api/ads/motd and the /motd consumer are already no-store -- there was
simply only ever one thing to serve.

Make the house ad a pool of six variants and draw one per fill, pitched
at both sides of the network (publishers with a surface to monetize,
advertisers with a product to sell). An unsold slot now rotates on every
request, and the 10% house rotation on sold slots stops repeating itself
too.

The draw happens once in houseFill and is threaded through both
houseCreative and renderHouseAdHtml; drawing independently in each would
let a single impression show one pitch in its HTML and another in its
text. Variants carry a slug that rides the click URL as utm_content so we
can tell which pitch converts -- left off the terminal URL, which is
printed as literal text inside the ASCII box and has no width to spare.

Verified all six variants render ASCII-clean with aligned boxes at
cols=44/72/120.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio marked this pull request as ready for review July 31, 2026 11:32
@ralyodio
ralyodio merged commit 1090493 into master Jul 31, 2026
8 checks passed
@ralyodio
ralyodio deleted the feat/rotate-house-ads branch July 31, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant