Skip to content

feat: searches can carry a partner code, so a search deal can pay us - #459

Open
aamancio wants to merge 1 commit into
mainfrom
feat/search-partner-codes
Open

aamancio wants to merge 1 commit into
mainfrom
feat/search-partner-codes

Conversation

@aamancio

Copy link
Copy Markdown
Owner

Part 1 of candoa-cloud#37 — the precondition for search-deal revenue.

Why

An engine pays for traffic it can attribute to us, and the only thing that reaches it is the search URL. So the code it issues has to ride in that URL. SearchProvider.searchURL(for:) appended just the query, which meant Candoa could not have accepted a deal if one were offered today.

What changed

SearchPartnerCode carries the engine's own parameter name — DuckDuckGo reads t, Ecosia reads tt — because the name is the engine's choice, not ours.

It also carries an expiry, which is not decoration: search deals lapse. Vivaldi moved its default off Bing in some regions when that deal ended. A code that outlives its deal tags every search for a partner who is no longer paying for it. Codes arrive as configuration rather than living in the binary, so one can be issued, rotated, or revoked without shipping a release.

Partner codes are a required argument, deliberately

A call site that forgot an optional partnerCodes: would still return a perfectly good search URL — the search works, the money quietly doesn't. That failure is invisible in testing and in production. Making it required means the compiler asks at every site.

It immediately earned its keep: WebViewCoordinator.searchInNewTab was building a search URL directly off the provider rather than through NavigationService, so it would have been the one path silently dropping the code. It now routes through the service like every other search.

Behaviour today: none

No deal exists, so partnerCodes is .none, nothing is appended, and a search URL is byte-for-byte identical to before. That's asserted rather than assumed — testSearchURLIsUnchangedWithNoPartnerCode pins the exact query items.

No privacy-policy change ships with this, because nothing new leaves the machine yet. The policy line lands with the commit that activates a real code, per the standing rule.

Tests

CandoaTests: 101 passed (94 + 7), Debug build clean.

  • no code → URL unchanged, exact query items pinned
  • code appended under the engine's own parameter name
  • each engine names its own parameter (t vs tt)
  • an expired code is not sent
  • a code is sent right up to its expiry
  • a code for one engine never leaks onto another
  • a code joins query items the engine's own base URL already requires

Next in #37

Search volume per provider, and the privacy-policy line that ships with it. Also open: the default is Google, the one major engine that doesn't pay independents — so most search volume currently routes to the non-paying option.

🤖 Generated with Claude Code

Search-deal revenue is the browser business model with a decade-long proof,
and Candoa could not accept a deal if one were offered: the engine attributes
traffic by a code it issues and reads from the search URL, and there was
nowhere to put one.

Searches now carry the code in force. It travels with the parameter name the
engine chose — DuckDuckGo reads t, Ecosia tt — and with the date the deal ends,
because deals lapse and a code that outlives its deal tags searches for a
partner no longer paying for them. Codes come from configuration rather than
the binary, so one can be issued, rotated, or revoked without a release.

Partner codes are a required argument to searchURL rather than an optional
one. A call site that forgot them would still search, so the mistake would be
invisible and would cost revenue silently; the compiler asks instead. That
turned up searchInNewTab building a search URL without going through the
service, which now routes through it like every other search.

No code exists yet, so none is sent and a search URL is byte-for-byte what it
was — asserted, not assumed.

Refs #37

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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