Repository navigation
fix(ads): keep terminal ads inside the box at narrow widths - #175
Merged
Merged
Conversation
Two separate ways a terminal ad could print outside the frame it was
asked to fit in. Both bite hardest at the minimum width, 44 cols, which
leaves only 40 usable columns.
1. The call-to-action was the one piece of advertiser copy that was never
wrapped. Headline and body both go through wrapText; the CTA row was
emitted whole. `row` clamps its padding at Math.max(0, ...), so an
over-long CTA didn't error -- it silently produced a row wider than
the box. A 63-character CTA at cols=44 rendered a 68-character row,
i.e. advertiser-controlled copy could break the frame. It now wraps
(and hard-splits) exactly like the headline and body.
2. The house ad's click URL spelled its utm params out inline:
https://crawlproof.com/?utm_source=house-ad&utm_medium=motd
That is 59 characters, or 66 once a publisher's &s=<surface> tag is
appended -- far past the 40 columns a 44-col box has, so the renderer
pushed it below the frame on every narrow fill. Since house ads are
currently 100% of terminal inventory, that was every fill.
Added /h, a house-ad click redirector mirroring the existing
/a/<impression_id>, which re-applies the utm params server-side where
characters are free. The printed URL is now 24 characters, or 31 with
a surface tag, and sits inside the box at every supported width.
Attribution is unchanged apart from utm_medium moving from "motd" to
"terminal", with the surface itself recorded as utm_content -- the
same URL is printed into BBS screens and SSH banners, not just MOTDs.
Not fixed, and left deliberately: a *paid* click URL is
/a/<uuid> = 61 characters (68 with a surface tag), which cannot fit 40
columns on this domain. Those still fall outside the frame, as designed
and as covered by the existing "moves an over-wide URL below the box"
test. Making them fit needs short impression codes -- a schema change on
the click-metering path, which does not belong in a layout fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
ralyodio
marked this pull request as ready for review
July 31, 2026 12:34
ralyodio
added a commit
that referenced
this pull request
Jul 31, 2026
) Follow-up to #175, which fixed the house ad's click URL but left the paid one. A paid terminal ad printed: https://crawlproof.com/a/<uuid> 61 chars https://crawlproof.com/a/<uuid>?s=motd 68 chars against the 40 usable columns of a 44-col box, so the URL was always pushed outside the frame. Two changes bring it to 37: 1. A 12-character base62 short code addresses the impression instead of its UUID. The length is derived, not picked: 40 usable columns minus the 25-character "https://crawlproof.com/a/" prefix leaves 15, and 12 keeps three columns of headroom for a longer origin while still being 71 bits. Generated with rejection sampling — a plain byte % 62 would have skewed the first four symbols and quietly cost entropy the width budget is already tight on. 2. The publisher's surface tag moves out of the URL and onto the impression row. As "&s=<tag>" it cost up to 35 more columns of a box that has 40; /a/<code> now reads it back off the row to build utm_content. It is also queryable there, which the query string never was. Entropy matters because the code is all that stands between a stranger and a click charge on someone else's campaign, so this is deliberately well above the ~42 bits a 7-character code would have given. Compatibility, both directions: - /a/<id> still accepts UUIDs. Click URLs printed before this change are sitting in people's MOTDs, SSH banners and BBS screens and are never reissued; they must keep resolving. The ?s= query form is still read as a fallback for the same reason. - Migrations here are applied by hand, so the deploy can land before the schema. Both new columns are additive and nullable, the impression insert retries without them if they are missing, and the click URL falls back to the UUID form. A wide URL is cosmetic; a dropped impression is a lost sale. Either apply order is safe. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #174, which surfaced this while verifying the house-ad rotation.
Two independent ways a terminal ad could print outside the frame it was asked to fit in. Both bite hardest at the minimum width (44 cols → 40 usable columns).
1. Unwrapped call-to-action (advertiser-controlled)
Headline and body both go through
wrapText. The CTA row did not.rowclamps its padding atMath.max(0, …), so an over-long CTA didn't error — it silently emitted a row wider than the box.Before,
cols=44with a 63-char CTA:After — wraps and hard-splits like every other copy field:
This one is worth a look on its own merits: it means advertiser copy could break the box, on any width, for any slot.
2. House ad click URL too long for the box
59 chars, or 66 once a publisher's
&s=<surface>tag is appended — well past the 40 columns available atcols=44, so the renderer pushed it below the frame. House ads are currently 100% of terminal inventory, so this was every fill.Added
/h, a house-ad click redirector mirroring the existing/a/<impression_id>, re-applying the utm params server-side where characters are free. Printed URL is now 24 chars (31 with a surface tag) and sits inside the box at every width:Attribution is unchanged except
utm_mediummovesmotd→terminal, with the surface recorded asutm_content. The same URL is printed into BBS screens and SSH banners, not just MOTDs, so the old fixedmotdwas wrong anyway.Deliberately NOT fixed
A paid click URL is
/a/<uuid>= 61 chars (68 with a surface tag). That cannot fit 40 columns on this domain —https://crawlproof.com/alone is 23. Those still fall outside the frame, which is the documented, tested behavior (moves an over-wide URL below the box rather than cutting it), and is better than hard-wrapping a link the reader can no longer select in one go.Making paid URLs fit at 44 needs short impression codes — a schema change on the click-metering path. That's a separate change and doesn't belong in a layout fix. Happy to do it as a follow-up if you want it.
Verification
npm run typecheckclean.master— 5/12 with both files reverted, and the house-URL test verified to fail when onlylib/ads/house.tsis reverted (it measures the URL the code actually builds, re-hosted on the production origin, rather than a stand-in).🤖 Generated with Claude Code