Repository navigation
Commit 30bd7f6
Teach the ad generator to write prose, for ads that live inside content (#202)
Every format so far renders the same tiny copy set: a headline, one benefit
line averaging 76 characters, and a call to action. That is the right size for
a 300x250 box and far too little for a placement that sits *inside* somebody's
writing -- a sponsored paragraph in a blog post, or the body of a feed item,
where the ad is read rather than glanced at.
So the generator now also writes two lengths of editorial prose from the
destination site it already fetches for colours and copy:
summary_short one or two sentences -- the inline mention
summary_long two or three paragraphs -- the blog-post form
The register is the point, and it is stated explicitly in the prompt: third
person, factual, no second person, no call to action, no hype adjectives, "the
way a journalist would describe the product in one line of a round-up".
Without that instruction the model returns three restatements of the headline,
which is useless -- ad voice is exactly what makes a sponsored paragraph read
as an intrusion and get skipped. The no-invention rule is restated harder for
these than for the display copy, because more room is more room to fabricate:
if the page does not say who it is for or what it costs, neither do we.
**The domain decides what is stored.** summary_domain records which site the
prose was written from. A user can paste a URL, get a preview, then edit the
URL before saving, and prose confidently describing the first site is worse
than no prose at all. So it is only stored when the domain it was written from
matches where the campaign points, and serving re-checks the same thing on
read: a mismatch is treated as absent and the ad falls back to its short
creative body. Regeneration re-derives the domain from the campaign's current
URL, since a changed destination is exactly when a summary goes stale.
Rendering: a new feed style `article` -- artwork, heading, the real paragraphs,
the call to action, and a disclosure line -- in HTML, Markdown and plain text.
`card` now prefers the short summary over the banner line. Both fall back to
the old behaviour for the campaigns generated before this existed, because an
"article" carrying a single line is just a worse card.
Disclosure is strongest in the article form, deliberately. The whole point of
the long form is that it reads like editorial, so the line saying it is paid
for is the only thing distinguishing it from the post above it.
Two structural choices worth keeping:
The summary lookup is its own query rather than two more columns on the
creative join in serveAd. That join is *the* serving query -- if it fails,
every unit on every slot goes dark -- and these columns sit behind an `add
column if not exists` in a migration applied by hand, so a deploy can run ahead
of the schema. One extra round trip on the feed path, which is one fetch per
publisher build rather than one per reader, is a cheap price for not being able
to take banner and terminal serving down with it. The write path is resilient
for the same reason: an unknown column retries without the summary rather than
refusing to create the campaign.
A test caught a real bug on the way: `^\s*` in the markdown-stripping regex
also matches the newline *before* the anchor under the m flag, so stripping a
heading or bullet swallowed the blank line separating it from the previous
paragraph and silently merged two paragraphs into one.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent f6fd26b commit 30bd7f6
9 files changed
Lines changed: 626 additions & 17 deletions
File tree
- app
- actions
- api/ads/feed
- lib/ads
- supabase/migrations
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
| 15 | + | |
13 | 16 | | |
14 | 17 | | |
15 | 18 | | |
| |||
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
29 | 70 | | |
30 | 71 | | |
31 | 72 | | |
32 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
33 | 81 | | |
34 | 82 | | |
35 | 83 | | |
| |||
42 | 90 | | |
43 | 91 | | |
44 | 92 | | |
45 | | - | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
46 | 96 | | |
47 | 97 | | |
48 | 98 | | |
49 | 99 | | |
50 | 100 | | |
51 | 101 | | |
| 102 | + | |
52 | 103 | | |
53 | 104 | | |
54 | 105 | | |
| |||
89 | 140 | | |
90 | 141 | | |
91 | 142 | | |
| 143 | + | |
92 | 144 | | |
93 | 145 | | |
94 | 146 | | |
| |||
126 | 178 | | |
127 | 179 | | |
128 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
129 | 188 | | |
130 | 189 | | |
131 | 190 | | |
| |||
144 | 203 | | |
145 | 204 | | |
146 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
147 | 220 | | |
148 | 221 | | |
149 | 222 | | |
| |||
278 | 351 | | |
279 | 352 | | |
280 | 353 | | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
281 | 374 | | |
282 | 375 | | |
283 | 376 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
20 | 23 | | |
21 | 24 | | |
22 | 25 | | |
| |||
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
36 | | - | |
| 39 | + | |
37 | 40 | | |
38 | 41 | | |
39 | 42 | | |
| |||
187 | 190 | | |
188 | 191 | | |
189 | 192 | | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
190 | 199 | | |
191 | 200 | | |
192 | 201 | | |
| 202 | + | |
193 | 203 | | |
194 | 204 | | |
195 | 205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
12 | 15 | | |
13 | 16 | | |
14 | 17 | | |
| |||
60 | 63 | | |
61 | 64 | | |
62 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
63 | 85 | | |
64 | 86 | | |
65 | 87 | | |
| |||
72 | 94 | | |
73 | 95 | | |
74 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
75 | 111 | | |
76 | 112 | | |
77 | 113 | | |
| |||
111 | 147 | | |
112 | 148 | | |
113 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
114 | 206 | | |
115 | 207 | | |
116 | 208 | | |
117 | | - | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
118 | 215 | | |
119 | 216 | | |
120 | 217 | | |
| |||
149 | 246 | | |
150 | 247 | | |
151 | 248 | | |
152 | | - | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
153 | 259 | | |
154 | 260 | | |
155 | 261 | | |
| |||
0 commit comments