Skip to content

Multi-country Add Keyword picker + drop stale API token#1

Merged
bootuz merged 2 commits into
mainfrom
feat/country-dropdown
May 23, 2026
Merged

Multi-country Add Keyword picker + drop stale API token#1
bootuz merged 2 commits into
mainfrom
feat/country-dropdown

Conversation

@bootuz
Copy link
Copy Markdown
Owner

@bootuz bootuz commented May 23, 2026

Summary

  • Multi-country picker in the Add keyword modal — chip-based searchable select sourced from Apple's full list of 175 App Store territories (source). Adds N keyword rows in one click via per-country fan-out.
  • Last-selected countries persist in localStorage (keywordista:lastCountries), with a Clear button on the form and a defensive fallback to ['us'] on missing/corrupt/stale data.
  • Drop KEYWORDISTA_API_TOKEN from Makefile / launcher / .env.example / requests.http. The server stopped requiring it in Phase 5b (routes.swift:8-12) — only the dev tooling was still passing it around.

Why the picker, not a free-text input

A first-time user faced an empty datalist (the old <input list> only suggested codes already tracked) and had to know ISO codes by heart. Backend validation only checks length, so typos like usa silently created bad rows. The picker is now the de-facto allowlist.

Naming corrections vs. common copies of the App Store country list

Cross-checked against Apple's official territories page. Notable diffs from older third-party copies and from a JSON list I was initially handed:

  • li ≠ Liberia. li is Liechtenstein (not an App Store storefront). Liberia is lr.
  • Both Congos exist: cd (Democratic Republic) and cg (Republic).
  • Apple uses Eswatini (not Swaziland), Türkiye (not Turkey), Republic of Korea, China mainland — overrides applied where Intl.DisplayNames diverges.
  • Added 20 missing territories common copies often lack: Afghanistan, Bosnia & Herzegovina, Cameroon, Côte d'Ivoire, Gabon, Georgia, Iraq, Kosovo (xk), Liberia, Libya, Maldives, Montenegro, Morocco, Myanmar, Nauru, Rwanda, Serbia, Tonga, Vanuatu, Zambia.

Test plan

  • npm run check — 0 errors, 0 warnings
  • End-to-end against local Vapor backend: typed verify-multi-…, submitted 5 countries → all 5 rows persisted via POST /api/v1/keywords; confirmed via GET /api/v1/dashboard.
  • Partial-failure path: resubmitted same term with 4 chips (3 duplicates + DE new) → backend returned constraintUniqueFailed for the 3 dupes; DE landed; modal stayed open with the 3 failures listed per country.
  • localStorage persistence: set 4 codes → page reload → modal opens with those 4 chips.
  • Corrupt storage fallback: set value to garbage → modal falls back to ['us'].
  • Esc inside the popover closes only the popover (modal stays open); Esc with popover closed closes the modal.
  • × on a chip removes that chip without opening the popover.
  • Clear button wipes all chips; submit with zero chips shows "Pick at least one country."
  • bash -n keywordista passes; grep -r KEYWORDISTA_API_TOKEN returns 0 matches.
  • Reviewer: try a 50+ country selection to feel the per-country fan-out latency (each is its own POST). If it feels bad in practice we can add a batch endpoint later.

Follow-ups (out of scope)

  • Backend could return 409 Conflict for duplicate (term, country) instead of surfacing the SQLite constraint string. The modal's failure list would then say "already tracked" instead of API 500: …constraintUniqueFailed….
  • A batch endpoint (POST /api/v1/keywords/batch) would collapse N round-trips into one if heavy multi-country use becomes common.

bootuz added 2 commits May 23, 2026 15:45
…rd modal

Track one term across many App Store storefronts in a single click. The
modal swaps the free-text 2-letter input for a searchable chip-based
multi-select sourced from Apple's 175 official territories (Apple's
"App Store Pricing and Availability" page is the source of truth).

- new web/src/lib/countries.ts: complete storefront list + Intl.DisplayNames
  with Apple-preferred overrides for Türkiye, Republic of Korea,
  China mainland, both Congos, Eswatini, and Kosovo (XK is not in
  Intl.DisplayNames).
- new CountryMultiCombobox.svelte: chip trigger with per-chip remove,
  search by name or ISO code, keyboard navigation, selected items
  pinned to the top of the popover. Esc is scoped to the popover so it
  no longer also closes the parent modal.
- AddKeywordModal: countries[] persisted via localStorage so users
  don't reselect every time; defaults to ['us'] for first-time users
  and falls back to ['us'] on corrupt/invalid storage. Submit fans out
  to addKeyword() per country via Promise.allSettled, reports
  per-country failures with localized name + code, saves on any
  success. "Clear" button wipes selection.
The Vapor server stopped requiring bearer auth in Phase 5b (the
server binds to 127.0.0.1 only — see Sources/App/routes.swift), but
the dev tooling kept threading a `devtoken` through env vars and
Authorization headers. Drop the dead config so the docs and the
launcher match the actual auth surface.

- Makefile: drop KEYWORDISTA_API_TOKEN default + the env-var prefix
  on `dev-backend`; update the help string.
- keywordista launcher: drop the default/export block and the token
  suffix on the startup banner.
- .env.example: drop the token line.
- requests.http: drop @token, the two now-meaningless 401 cases
  (`### 1a` / `### 1b`), and every Authorization: Bearer line.
@bootuz bootuz merged commit 18a4e0b into main May 23, 2026
3 checks passed
bootuz added a commit that referenced this pull request May 23, 2026
A picture beats four lines of architectural prose for 'what does this
actually look like'. Sourced from a live local run with 30 tracked
keywords spread across US/GB/DE/JP/IN/FR — shows ranks, top-result
icon strips, difficulty + entry-barrier dots, and the country picker
that landed in #1.

Lives at docs/dashboard.png so we have a directory to drop more
screenshots into later (charts page, history panel, etc.) without
cluttering the repo root.
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