Skip to content

feat(server): learn personalized ranking from served-page clicks (Android A2)#50

Merged
ErikChevalier merged 1 commit into
mainfrom
feat/click-personalization-served
Jun 2, 2026
Merged

feat(server): learn personalized ranking from served-page clicks (Android A2)#50
ErikChevalier merged 1 commit into
mainfrom
feat/click-personalization-served

Conversation

@ErikChevalier
Copy link
Copy Markdown
Contributor

What

A2 of the Android click-personalization feature: an owner-only /click redirector so searches run from the browser (through the local server) also train the model, matching the native app (A1, merged in #48) and the desktop app's D2. Also lands the add-click-personalization OpenSpec change for the whole feature (the repo's CONTRIBUTING requires it; it was deferred in A1).

How it stays safe

  • GET /click?rid=..&pos=.. is loopback-only (a non-owner gets 404).
  • The server remembers each owner render (a fresh, unguessable rid → the displayed (url, host) order) in a small, bounded, in-memory map (never persisted). /click resolves the destination from that server state, never a caller-supplied URL, so it cannot be an open redirect and a network client cannot forge a rid.
  • A stale rid or bad pos falls back to / and learns nothing.
  • Owner result links route through /click only when personalization is on (a new renderResultsPage linkBuilder); everyone else (and a disabled owner) gets the plain destination link.
  • Trains the model with Personalizer.updateFromClick, then 302s. Persisted to the encrypted store; only when the feature is enabled.

Tests

ktlint + lint + assembleDebug + unit tests green. New ClickRouteTest on the real-loopback harness: owner records the skip-above update + redirects to the right URL; disabled owner gets plain links; forged/stale rid and bad pos fail safe. Network refusal stays covered by the isLoopbackHost unit tests + the handler's isOwnerRequest 404 gate. openspec validate add-click-personalization --strict passes.

🤖 Generated with Claude Code

…roid A2)

Add an owner-only `/click` redirect so searches run from the browser
through the local server also train the personalization model, matching
the native app and the desktop app's D2.

- server/SearchServer.kt: a loopback-only `GET /click?rid=..&pos=..` route.
  The server remembers each owner render (a fresh unguessable id -> the
  displayed (url, host) order) in a small, bounded, in-memory map, and
  `/click` resolves the destination from that server state, never a
  caller-supplied URL. So it cannot be an open redirect, a network client
  gets 404 and cannot forge a rid, and a stale rid / bad pos falls back to
  home. It trains the model (Personalizer.updateFromClick) then 302s.
- Owner result links route through `/click` only when personalization is
  on (a new renderResultsPage linkBuilder); everyone else gets plain links.
- Wire personalizationPreferences + the enabled flag through SearchServer
  and SearchMobService.
- Add the add-click-personalization OpenSpec change (proposal/design/specs/
  tasks) for the whole feature; `openspec validate --strict` passes.

Gate green: ktlint, lint, testDebugUnitTest (new ClickRouteTest), assemble.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ErikChevalier ErikChevalier merged commit d0b6d8d into main Jun 2, 2026
2 checks passed
@ErikChevalier ErikChevalier deleted the feat/click-personalization-served branch June 2, 2026 07:56
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