feat(quests): track pokecoin rewards - #821
Merged
Merged
Conversation
…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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extracted from the abandoned
feat/poracleng-two-branch-support(preserved at3d33b35). 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:
/healthreward_type: 8reward_type: 3control{"message":"Unrecognised reward_type value","status":"error"}newUids:[519]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,amountstays 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
validRewardTypesallowlist, so the version string is the only available signal. 5.2.1's/healthmap 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:
IQuestPokecoinCapabilityServicecopied from theISummaryCapabilityServiceshape, readingIPoracleServerProfileService(already cached five minutes, already fails closed). No cache of its own.AlarmValidationException, not a new exception type. Its global filter already produces400 {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}, mirroringGET /api/summary-schedules/capability, rather than a global capability list.The registry from the abandoned branch —
PoracleCapabilityKeys,IPoracleCapabilityService,PoracleCapabilityService,poracle-capabilities.tsand the/api/settings/poracle-capabilitiesendpoint — 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 and5.2.1-rc1true.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
@iffalse reddened the tab-count test and removingcase 5fromsave()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.tsmock neededpokecoinsSupportedadded and got it.One dead-code note for the reviewer:
isStarduston the edit dialog is now read only by its spec, sinceusesRewardSlotreplaced it in the template. Kept because it pairs with the newisPokecoins, but happy to drop it.Verification
Backend 2156 passed. Jest 104 suites / 1173 tests.
ng build --configuration production,ng lintandprettier-checkall clean.