7 Commits

Author SHA1 Message Date
Ben Senescu
052977f20d
Add Palestine as a Google-Ads-only country picker location (#492) 2026-08-17 22:52:47 -04:00
Ben Senescu
13ada5b441
fix(sam): out-of-credits CoT leak, streaming scroll lock, rank-tracking language gap (#470)
* fix(sam): stream canned refusals without a provider call

Out-of-credits and session-gone refusals ran a real LLM turn with a
200-token cap; MiniMax M3 could spend the whole budget on reasoning
tokens, leaving the user a raw truncated chain-of-thought (which also
named the backing model) and no reply. Refusal turns now swap in a
static LanguageModelV3 that streams the refusal text through Think's
normal pipeline — rendered and persisted like any assistant message,
with no provider request at all.

Fixes every-app/open-seo#161

* fix(chat): stop pinning the transcript to the bottom while the user scrolls up

Both chat surfaces (SAM and onboarding) forced scrollTop to the bottom
on every streamed chunk, so scrolling up mid-reply was undone within
milliseconds. A shared stick-to-bottom hook now tracks pinned-ness from
real scroll events: scrolling away releases the pin, returning to the
bottom (or sending a message) re-arms it.

Fixes every-app/open-seo#160

* fix(rank-tracking): allow any SERP language for any country

The Add Domain modal restricted the language picker to the Labs
per-country subset and disabled it when only one option existed, so
e.g. tracking English searches in Czechia was impossible — even though
rank tracking runs against the SERP API, which serves every supported
language in every country. The picker now offers the full SERP language
list; create/update schemas validate codes against the master list so
unknown codes still fail before DataForSEO charges for them; and
keyword-metrics refreshes resolve a Labs-served language so an
unserved pair never reaches a charged Labs call.

Fixes every-app/open-seo#183

* fix(chat): surface silent turn failures in Workers logs

A provider stream dying mid-turn (chat:request:failed) and a DO restart
whose recovery gives up (chat:recovery:exhausted) leave the user a
replayed "Something went wrong" banner and a half-streamed message, but
never reach the onChatError hook — their only signal is the agents:chat
diagnostics channel, which was unsubscribed, so the chat agents' most
common failure modes produced zero log lines. A module-level
subscription now logs both for every chat DO.

SamChatAgent.onChatError also returns the error now: Think uses the
return value as the stored chat-terminal body that reconnecting clients
replay, and returning void stored the literal string "undefined".
2026-08-08 18:47:41 -04:00
bookingseo
0f08437cd0
feat: configurable default market for location/language fallbacks (#72) 2026-07-16 10:46:49 -04:00
RDeemer63
ffa6ec5e70
feat(rank-tracking): add city/region-level location targeting (#62) 2026-07-07 21:44:19 -04:00
Ben Senescu
4e48f8344f Fix production errors: onboarding crash hardening + DataForSEO spend/noise cleanup (#282) 2026-07-02 18:15:46 -04:00
Amol Budhwant
a19baed678
feat(rank-tracking): allow explicit language selection (#46)
* feat(rank-tracking): allow explicit language selection

* fix(rank-tracking): remove duplicate country picker and fix LocationSelect handler

The modal rendered two country pickers: a stale native <select> with a
broken empty onChange and the new LocationSelect combobox. The LocationSelect
handler also treated its numeric argument as a DOM event (Number(e.target.value)
-> NaN). Remove the dead <select> and pass the location code through directly.

* feat(rank-tracking): offer the full DataForSEO SERP language list

The language picker only listed the ~43 languages that happened to be a
country's default, so users could not track e.g. Hindi, Chinese (Simplified),
Tamil, or Urdu. Expand LANGUAGE_OPTIONS to the full set of languages the
DataForSEO SERP (Google) API accepts (sourced live from
/v3/serp/google/languages), dropping the deprecated 'iw' Hebrew duplicate and
the redundant 'no' (Norway uses 'nb', which both SERP and Labs accept). Mismatched
location+language pairs are accepted by SERP and invalid codes fail at zero cost,
so the wider list adds no charged-but-failed risk.

Also drop the now-unused LOCATION_OPTIONS re-export from the client shim.

* docs(rank-tracking): cite DataForSEO source for the language list

Note where LANGUAGE_OPTIONS comes from (/v3/serp/google/languages) and how it
relates to the country list (the Labs locations_and_languages endpoint), plus
the intentional deviations from the raw endpoint.

* chore(knip): treat scripts/** as entry points

The standalone CLI/dev scripts under scripts/ are invoked via package.json
scripts (tsx/node), not imported, so knip flagged all six as unused files.
Add scripts/** to knip entry so ci:check passes. (Pre-existing on main;
bundled here to keep the branch's ci:check green.)

* feat(rank-tracking): filter language picker to the country's supported languages

Showing all 128 languages for every country was noise. Restrict the picker to
the languages DataForSEO actually supports for the selected country via a new
getLanguageOptions() helper, backed by a per-country map from the Labs
locations_and_languages endpoint. Most countries expose only their default
language; the ~20 genuinely multilingual ones (US en/es, Canada en/fr,
Switzerland de/fr/it, India en/hi, etc.) list their real set. googleAdsOnly
countries have no per-country language data, so they show their default only.
The Language select is disabled when a country offers a single language.

LANGUAGE_OPTIONS becomes the internal master list (no longer exported).

---------

Co-authored-by: Ben Senescu <44480372+bensenescu@users.noreply.github.com>
Co-authored-by: Ben Senescu <bensenescu@gmail.com>
2026-06-28 18:04:49 -04:00
Ben Senescu
b809f87b5e
Hybrid keyword data: Google Ads coverage for non-Labs countries + opt-in clickstream volumes (#262) 2026-06-12 14:08:11 -04:00