Skip to content

feat(dex): show version-specific Pokédex entries on the Pokémon detail page - #161

Open
LeeTaek wants to merge 8 commits into
chattymin:mainfrom
LeeTaek:feat/pokedex_desc
Open

LeeTaek wants to merge 8 commits into
chattymin:mainfrom
LeeTaek:feat/pokedex_desc

Conversation

@LeeTaek

@LeeTaek LeeTaek commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds version-specific Pokédex entries (PokéAPI flavor text) to the existing Pokémon detail page, following the review on this PR. The Species data card shows the newest entry in the app language, and All Pokédex entries opens every version's entry, oldest first, on a page laid over the detail page; Back returns to the same individual and scroll position. Stats, nature, abilities, moves and the individual picker are unchanged.

The branch is rebuilt on current main (9be4ea9). Changes from the earlier revision that main has since handled differently — the ja-hrkt casing, shiny sprite seeding, the representative button and cell taps opening the detail page — are no longer part of this PR.

Review follow-up

Request Change
Add flavor text as a section of the current detail page instead of replacing it The newest entry is part of the Species data card. The full list — up to 35 versions in English, too long to inline above the move list — is a page laid over the detail page, so nothing existing is removed or replaced.
Request identity must include species ID and language; apply a response only if the language still matches FlavorTextRequest(speciesID, language) keys results, failures and in-flight tasks. The language is captured before the first await and checked again before a success or a failure is stored.
Regression test for out-of-order responses during a language change DexFlavorTextStoreTests covers both completion orders and a stale failure. Separate tests lock the language-keyed cache and the in-flight identity, because with the landing guard in place a species-only key would still pass the out-of-order test. Each test was confirmed to fail with its defect injected.
Keep the existing detail content on network failure Entries load in their own .task. Loading, failure and retry stay inside the description area, and a failed refetch keeps the entry already shown. DexFlavorTextRenderingTests renders the real detail page in these states.

Implementation notes

  • Source. One GraphQL query returns the requested language's entries and localized version names. Any GraphQL failure — transport, HTTP status, an errors payload (PokéAPI returns these with HTTP 200 and no data), or an undecodable body — falls back to REST pokemon-species/{id}. Those results have slug version labels, so they are marked degraded: shown, never cached, and fetched again on the next visit.
  • Protocol. PokemonFlavorTextProviding is separate from PokeProviding, like PokemonDetailProviding, so existing test doubles are untouched and stores built with stub providers stay offline. PokeAPIClient conforms by delegating to PokemonFlavorTextClient, whose transport is injectable for tests.
  • Request lifetime. The store owns the in-flight task, as dexNameRequests does. Closing the detail page does not cancel it, and reopening the page joins it.
  • Languages. Japanese follows AppLanguage.apiCodes (ja-hrkt first) whatever order the rows arrive in. A version without an entry in the selected language is omitted rather than filled from English. PokéAPI has non-English entries only from X/Y onward (French from Black/White) and none for pt or ru; those two show English with a notice, as species names already do.
  • Placement. The description lives in the species card, so it appears once battle details have loaded and is hidden with them if they fail. The entries request itself still runs in parallel, so it is usually ready when the card appears.
  • Fixtures. Test entry text is synthetic; only identifiers (language codes, version names, the GraphQL error body) are copied from real responses.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

UI changes

All images are sample-data renders of the production PokemonDetailView, hosted offscreen at popover width in the light appearance and cropped to the affected area. Buttons render gray because the offscreen window is inactive; in the popover they use the accent color.

Before After
Before — species data card, sample-data render After — species data card with the newest entry and the All Pokédex entries button, sample-data render
No previous screen: the All Pokédex entries page is new. New — All Pokédex entries page, oldest first, sample-data render
Same card as above; there was no description. After — entries loading inside the card, sample-data render
Same card as above; there was no description. After — entries failed to load, with Retry, sample-data render
Same card as above; there was no description. After — Portuguese UI showing English entries with the fallback notice, sample-data render

Validation

  • swift build and ./scripts/test-gate.sh on this commit: 1,522 tests, 15 skipped (existing opt-in harnesses), 0 failures; logic-core line coverage 93.71%. This PR adds 28 tests: 16 for the client, 6 for the store and 6 rendering tests.
  • Fault injection. Each of these was injected, made its test fail, and was reverted:
    • store: landing guard removed (success and failure); results keyed by species only; in-flight task keyed by species only; the page's cancellation forwarded to the request
    • client: degraded results cached; Japanese candidate rank ignored
    • view (re-run after the rebase): entries loaded in the details task; a failed refetch replacing the entry; the entries page swapped in for the detail page instead of laid over it; the list in newest-first order; the card showing the oldest entry; Retry doing nothing; the page without the fallback notice
  • llvm-cov --show-regions. No ^0 in the new client, store and view code except the default URLSession transport, which tests always replace.
  • Live PokéAPI, through the production client (a one-off check, not committed). For species docs: auto-updating release badge in READMEs #25, ko has 12 entries with localized version names, ja 12 (kana), en 33 and de 12, while pt falls back to English. PokéAPI has no ru language at all.
  • CI. The first runs caught two differences from local runs, both fixed in follow-up commits: a Swift 6.1 isolation error in two test tearDown overrides that local Swift 6.2 accepted, and OCR misreads of body-size text on the macOS 15 runner ("Samole" for "Sample"). The rendering tests now run OCR on an enlarged capture, give Vision the expected words, and allow a few misread letters in text that must be present; text that must be gone is still matched exactly, and every injected view defect above still fails its test.

Documentation

docs/reference/defect-log.md:

  • Concurrency: moving a request from view state into the store changes what is reused and what is cancelled, so name the request's identity and decide both again.
  • Build & toolchain: a synchronous setUp/tearDown of a @MainActor XCTestCase is nonisolated on CI's Swift 6.1, so it may only touch let constants; and OCR on CI is less accurate than locally, so tests that read the screen must tolerate misreads.

Checklist

  • swift build and swift test pass locally
  • PR title follows Conventional Commits and title/description are written in English
  • UI changes include embedded images (before/after for existing screens; image of a new screen), or there is no visible UI change
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

🤖 Generated with Claude Code

@chattymin chattymin left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase this feature onto the current Pokémon detail screen and add flavor text as a section instead of replacing the existing stats, nature, abilities, moves, and instance selection UI. The request identity must include both species ID and language, and a response should only be applied if the current language still matches. Please add a regression test for out-of-order responses during a language change and keep the existing detail content available on network failure.

LeeTaek and others added 4 commits October 6, 2026 09:47
One GraphQL query returns the requested language's entries with localized
version names. Any GraphQL failure (transport, HTTP status, an `errors`
payload, an undecodable body) falls back to REST; that result carries
slug labels, so it is marked degraded and never cached. A language with
no entries at all (PokéAPI has no `pt`) is answered in English.

PokemonFlavorTextProviding is its own protocol, like
PokemonDetailProviding, so existing test doubles stay untouched. The
client's transport is injectable for tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Results, failures and in-flight tasks are keyed by FlavorTextRequest
(species ID + language). The request is captured before the first await
and re-checked before a success or a failure is stored, so a response for
a language that is no longer selected is dropped.

The store owns the in-flight task: a detail page that closes does not
cancel it, and reopening joins it. Degraded results stay visible and are
fetched again on the next visit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ersion

The species data card shows the newest entry and an "All Pokédex
entries" button. The button opens every version's entry, oldest first,
on a page laid over the detail page, so Back returns to the same
individual and scroll position. Loading, failure and retry stay inside
the description area, and a failed refetch keeps the entry already
shown. Entries load in their own task, alongside battle details.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tore

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LeeTaek
LeeTaek force-pushed the feat/pokedex_desc branch from 56ceaa1 to 6268b69 Compare October 6, 2026 01:12
@LeeTaek LeeTaek changed the title feat: show version-specific Pokédex entries when a dex cell is tapped feat(dex): show version-specific Pokédex entries on the Pokémon detail page Oct 6, 2026
@LeeTaek

LeeTaek commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I rebuilt this branch on current main and updated the description.

  • Section, not replacement. The newest entry is part of the Species data card, and "All Pokédex entries" opens every version on a page laid over the detail page, so Back returns to the same individual and scroll position. Stats, nature, abilities, moves and the individual picker are untouched.
  • Request identity. Results, failures and in-flight tasks are keyed by species ID + language. The language is captured before the first await and checked again before a success or a failure is stored.
  • Out-of-order regression test. DexFlavorTextStoreTests covers both completion orders and a stale failure, plus separate tests for the language-keyed cache and the in-flight identity. Each was confirmed to fail with its defect injected.
  • Network failure. Entries load in their own .task, and loading, failure and retry stay inside the description area. DexFlavorTextRenderingTests renders the real detail page in these states.

Images and validation results are in the description.

LeeTaek and others added 2 commits October 6, 2026 10:21
CI (Xcode 16.4, Swift 6.1.2) runs a synchronous tearDown override of a
@mainactor XCTestCase as nonisolated, so the main-actor arrays and the
shared sprite cache it touched failed to compile there; local Swift 6.2
accepted them. Temporary files now live under one `let` directory that
tearDown removes, and the sprite-cache cleanup moved into the test body,
as other rendering tests already do. Recorded in the defect log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the macOS 15 runner, Vision read body-size text as "Samole" or
"Pokedey" and three rendering tests failed although the page was right;
locally (Retina, newer Vision) the reads were exact. Capturing at a
higher scale alone does not help: cacheDisplay resamples text that was
already rasterized at the window's scale.

OCR now runs on a 3x enlargement with the expected words as custom
words, and text that must be present may differ by up to 15% of its
letters, while text that must be gone is still matched exactly. The
entries-page fixture uses different first words so a tolerant match
cannot confuse entries. With a capture degraded to 0.8x, 1x and 2x all
six tests pass (0.6x: 5 of 6, from 1 of 6 before), and each of the seven
injected view defects still fails its test. Recorded in the defect log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LeeTaek
LeeTaek requested a review from chattymin October 6, 2026 02:02
LeeTaek and others added 2 commits October 16, 2026 10:39
Resolve the PokemonDetailView conflict with chattymin#376 (alternate forms): the
detail page's .task stays on the ZStack that hosts the entries page, and
uses the renamed selectedForm / displayedSpecies.form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under heavy load Vision dropped the short accent-colored "Sword" label
while reading every other line of the card, failing the refetch test
although the card was right. The entry text and the retry row still
prove the entry survived the failed refetch; the store now confirms
which label it carries. Injecting a failed refetch that hides the entry
still fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants