Skip to content

feat(quests): track pokecoin rewards - #821

Merged
hokiepokedad2 merged 2 commits into
developfrom
feat/quest-pokecoins
Aug 24, 2026
Merged

feat(quests): track pokecoin rewards#821
hokiepokedad2 merged 2 commits into
developfrom
feat/quest-pokecoins

Conversation

@hokiepokedad2

Copy link
Copy Markdown
Contributor

Extracted from the abandoned feat/poracleng-two-branch-support (preserved at 3d33b35). Independent of the other open PRs.

Quest alarms gain a sixth reward tab: pokécoins. It works the way stardust does — a minimum amount and no item selector — because Poracle matches this reward on the amount alone.

Verified by calling both servers, not by reading release notes

A throwaway human on each dev instance, one POST each:

/health reward_type: 8 reward_type: 3 control
5.1.0 5.1.0 400 {"message":"Unrecognised reward_type value","status":"error"} 200, stored
5.2.1 5.2.1 200, newUids:[519] 200, stored

Read back from 5.2.1:

{"reward_type":8,"reward":50,"amount":0,"description":"Reward: **50 or more pokecoins** "}

That confirms the stardust modelling: the floor travels in reward, amount stays 0, and Poracle renders it as "N or more". The test data has been removed from both servers.

Gating

This is the one capability with neither a column nor a flag behind it — PoracleNG only widened its validRewardTypes allowlist, so the version string is the only available signal. 5.2.1's /health map is {buttons, snapshots, autocreate, tomlDts, buttonResponseObject, derivedDtsTypes}; nothing about quests. The comment at the gate says so, since it is the exception to the otherwise-preferable rule of gating on the migration number.

Built on the existing pattern rather than a new one:

  • IQuestPokecoinCapabilityService copied from the ISummaryCapabilityService shape, reading IPoracleServerProfileService (already cached five minutes, already fails closed). No cache of its own.
  • AlarmValidationException, not a new exception type. Its global filter already produces 400 {error: "..."}, which is exactly what the add dialog's existing error handler surfaces. Upstream answers 400 for the same request, so the status is honest.
  • GET /api/quests/capability{pokecoins: bool}, mirroring GET /api/summary-schedules/capability, rather than a global capability list.

The registry from the abandoned branch — PoracleCapabilityKeys, IPoracleCapabilityService, PoracleCapabilityService, poracle-capabilities.ts and the /api/settings/poracle-capabilities endpoint — was deliberately dropped. It duplicated a convention this codebase already had.

An existing pokécoin rule stays usable on an old server

Someone may have set one with the bot. Reading and deleting are never gated — only creating. A row nobody can see is a row nobody can delete.

Tests

26 backend tests across two classes, plus the frontend spec. Every refusal is paired with a legitimate case: create/update/bulk refuse type 8 on an old server, and a [Theory] proves all five previously-valid reward types still write, a mixed batch with no pokécoin row still writes, and an existing pokécoin rule is still readable and deletable on that same old server. The version boundary is pinned — 5.1.0 and 5.0.9 false; 5.2.0, 5.2.1, 6.0.0 and 5.2.1-rc1 true.

Watched failing first: neutering the guard turned exactly the 3 refusal tests red and left the 11 legitimate-case tests green. On the frontend, forcing the @if false reddened the tab-count test and removing case 5 from save() reddened two more.

Housekeeping

All five i18n keys are in all eleven locales, actually translated using the official Pokémon GO currency names (PokéMünzen, Poképièces, PokéMonedas, PokéMonete, PokéMoedas, PokéMunten, PokéMonety, PokéMynt, PokéMønter) rather than English placeholders. The sibling quest-add-dialog.component.spec.ts mock needed pokecoinsSupported added and got it.

One dead-code note for the reviewer: isStardust on the edit dialog is now read only by its spec, since usesRewardSlot replaced it in the template. Kept because it pairs with the new isPokecoins, but happy to drop it.

Verification

Backend 2156 passed. Jest 104 suites / 1173 tests. ng build --configuration production, ng lint and prettier-check all clean.

…e them

PoracleNG 5.2.0 widened its quest validRewardTypes allowlist to include reward
type 8. The quest dialogs gain a sixth reward tab that behaves like stardust --
a minimum amount and no item selector, because Poracle matches this reward on
the amount alone and reads the floor from `reward` rather than `amount`.

Gated on the server version, which is the exception to the otherwise preferable
"gate on the applied migration number" rule: pokecoins added no column and no
config flag, and /health carries no capability key for it, so the version string
is the only signal there is. QuestPokecoinCapabilityService follows the shape of
SummaryCapabilityService and MuteCapabilityService and fails closed.

Verified by calling both dev servers rather than reading upstream source: 5.1.0
answers 400 "Unrecognised reward_type value"; 5.2.1 stores the row and describes
it as "50 or more pokecoins".

The refusal lives in QuestService, not the controller, so quick-pick apply and
profile import are covered -- both reach BulkCreateAsync without passing a quest
action. Reads and deletes are deliberately ungated: a pokecoin rule set with the
bot, or left behind by a downgrade, has to stay visible and removable, and a row
nobody can see is a row nobody can delete.

The tab is rendered last so the five existing tabs keep their indices whether it
appears or not -- tabIndex is positional and the save switch reads it.
# Conflicts:
#	Applications/Pgan.PoracleWebNet.Api/Configuration/ServiceCollectionExtensions.cs
#	CHANGELOG.md
@hokiepokedad2
hokiepokedad2 merged commit 0062cf9 into develop Aug 24, 2026
6 checks passed
@hokiepokedad2
hokiepokedad2 deleted the feat/quest-pokecoins branch August 24, 2026 17:09
hokiepokedad2 added a commit that referenced this pull request Aug 24, 2026
* fix(alarms): strip only paired markdown from the rule sentence

cleanRuleSummary removed every asterisk, underscore and backtick in the
string. PoracleNG's rowtext emits only **bold** today, so nothing on screen
changes -- but it interpolates template names, areas and saved-place labels
into that sentence unescaped, and work_gym is a name, not italics.

Stripping is now pair-aware: code spans first (a backtick pair wins over
emphasis inside it), then **bold**, *italic*, __underline__ and _italic_.
The two underscore forms additionally require a non-word character outside
the delimiter, which is the boundary Discord itself applies, so an
underscore between two word characters is left alone.

Verified against PoracleNG 5.2.1 on the dev instance: every live description
returned by /api/tracking/allProfiles?includeDescriptions=true uses ** and
nothing else, and all of them come through unchanged.

* test(profiles): pin the schedule pills to a live language switch

Reported as a defect from #816: the pill labels call translate.instant
inside a computed, instant is not a signal, so a language switch should
leave already-rendered pills in the previous language.

It does not, and the reason is worth writing down rather than papering
over. @ngx-translate v18 backs its store with signals, and instant() reads
_currentLang and _translations on the way to a value -- so calling it
inside a computed registers them as dependencies and the labels invalidate
on a switch like any other signal read. The premise held on the versions
before v18; this repo has been on v18 since #377, well before #816.

So: no behaviour change, because there is no defect to fix. What was
missing is the guard. The new spec switches language through I18nService,
the path the language menu takes, and asserts the rendered pill follows.
Confirmed it goes red when the instant() call is wrapped in untracked(),
which is exactly the shape a future ngx-translate dropping those signal
reads would produce.

* refactor(quests): drop isStardust from the quest edit dialog

usesRewardSlot took over in the template when #821 gave pokecoins the same
control, and nothing else read isStardust -- only its own two assertions,
which is a property kept alive by the test that tests it.

The assertions were worth keeping, so they now name usesRewardSlot: on a
stardust rule it is the flag that puts the floor field on screen, and on
the pokemon-encounter twin it is the flag that keeps reward from being
overwritten on save. STARDUST stays, REWARD_SLOT_TYPES is built from it.

* docs(changelog): one heading per section under Unreleased

Squash-merging nine branches in a row left the section with two Added blocks and
two Changed blocks. Each merge resolved its CHANGELOG conflict by keeping both
sides, which is right for the bullets and wrong for the heading above them.

The release workflow promotes this section verbatim, so the published notes would
have carried the duplicates.

Bullets are unchanged and all fifteen are still here; only the headings are merged,
into Keep a Changelog's order.
hokiepokedad2 added a commit that referenced this pull request Aug 24, 2026
…es (#822)

#817 shipped the Pokéstop Events feature with its 29 new keys carrying the
English text verbatim in da, de, es, fr, it, nl, pl, pt, pt-BR and sv. Key
parity held, so nothing rendered as a raw key and no test caught it -- the
page simply read in English beside a sidebar that did not.

Terminology follows the INVASIONS.EVENT_TYPES block each locale already had,
so the event names on the new page match the chips the invasion list has been
showing all along. The one departure is French, where SHOWCASE was
"Présentation"; Niantic's French term is "Vitrine", so both the existing chip
and the new strings now use it.

The other five features merged today (#815, #816, #818, #819, #821) were
checked the same way and were translated properly. What still matches English
in them is legitimately identical in that language: "Costume" in French and
Italian, "Event" in German, "Area" in Italian, "Gym", "Pokestop", "Station",
"15 min" and the countdown abbreviations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant