Skip to content

feat(alarms): say what each rule does, in Poracle's own words - #819

Merged
hokiepokedad2 merged 3 commits into
developfrom
feat/810-rule-descriptions
Aug 24, 2026
Merged

feat(alarms): say what each rule does, in Poracle's own words#819
hokiepokedad2 merged 3 commits into
developfrom
feat/810-rule-descriptions

Conversation

@hokiepokedad2

Copy link
Copy Markdown
Contributor

Closes #810. Merge last — it touches nine list components, seven of which #809 also touches.

A card gave you the species and a row of filter chips, and working out that a rule meant "Bulbasaur within 5 km, 90% IV or better, level 20 to 35" meant decoding the chips. Poracle already writes that sentence — the same wording its bot answers a !pokemon command with — and was returning it on every read of this page, where it was thrown away.

It now sits at the foot of each card under a hairline, below the chips, which still read first when you are scanning forty rules. Long ones clamp to two lines with a control to open them.

Two deliberate exclusions

Fort-change cards keep chips only. The sentence Poracle renders for them still has a raw JSON array in the middle of it, so it is worse than what it would replace.

The line appears only when the alert language matches the display language. Poracle localizes the description using the language it writes your alerts in; our chips follow the display language. Those can differ, and a card must never carry two languages at once. When they differ, or on a Poracle too old to send the field, the card is exactly what it was before.

Known limitation, worth a look in review

cleanRuleSummary strips underscores and backticks anywhere in the string, not only where they are markdown. Nothing in production descriptions contains either today, but area names and override_location_label values routinely do. If Poracle ever renders one of those into the sentence, the characters vanish silently. A tighter markdown-only rule would be safer; flagging rather than fixing since it needs a decision about what Poracle actually emits.

Verification

Backend 2137 passed. Angular production build passes. Jest 105 suites / 1184 tests.

PoracleNG renders a human-readable sentence for every tracking rule and
already returned it on every alarm-list read this site makes. Verified live
against 5.1.0 and 5.2.1: `GET /api/tracking/{type}/{id}` carries
`description` unconditionally, with no query parameter asked for, on all ten
types. Nothing new is fetched -- the field was being discarded at
PoracleJsonHelper.DeserializeList.

Backend: a nullable Description on the ten domain models, and one line in
ShouldStrip so it can never reach PoracleNG on a write. No tracking table has
a description column, and the field travelled three separate write paths --
SerializeToElement, RewriteRows, and PreserveUnmodelled, which was already
echoing the stored description back on every edit. Stripping it in ShouldStrip
covers all three. Deliberately not added to the twenty *Create/*Update DTOs:
the controllers bind those, and a client-stated description is not a thing.

Frontend: app-rule-summary at the foot of nine card types, under a hairline,
below the filter pills. The pills still read first -- they are the scan layer
across forty cards -- and the sentence is what you read once you have stopped
on one. Clamped to two lines with an expand control above 90 characters.

Two gates, both failing to nothing rather than to a placeholder: no
description (an older Poracle) renders no line, and a mismatch between the
alert language Poracle localises the sentence with and the display language
the pills follow renders no line either, so a card never mixes two languages.
The gate uses a new tri-state AlertLanguageService.resolved() rather than
selected(), which coerces to 'en' and would claim a match on a ja/ru/zh-cn
deployment.

Fort changes get the backend field but no card line: upstream still renders
'Fort updates: **pokestop** | distance: 5000m ["name"]' with a raw JSON array
in it, which reads worse than the change-type pills already on the card.

Tests pin both directions and the blast radius: the read carries it, an absent
field leaves it null, all three write paths drop it, every other property
survives the strip, and the collision reconciler stays blind to it (#553/#561
shapes). Watched the three strip tests fail before the ShouldStrip line went
in.
…riptions

# Conflicts:
#	Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/models/index.ts
#	Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/gyms/gym-list.component.ts
#	Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/max-battles/max-battle-list.component.ts
#	Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/nests/nest-list.component.ts
#	Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/pokemon/pokemon-list.component.ts
#	CHANGELOG.md
AlarmDescriptionPassthroughTests merged without a conflict but stopped compiling,
then stopped asserting. Two things landed underneath it:

MonsterService gained two constructor parameters -- the uid remapper from #805 and
the costume capability from #804 -- and this file was not in either merge's conflict
list, so nothing pointed at it.

More subtly, #805 moved the pokemon update path from CreateAsync onto the
uid-addressed UpdateByUidAsync. The test captured CreateAsync, so an edit wrote
nothing into the slot and TryGetProperty threw on a default JsonElement rather than
answering the question the test was asking.

CaptureUpdates() hooks the call an edit actually makes, and says why in a comment so
the next person moving a write path knows to look here.
@hokiepokedad2
hokiepokedad2 merged commit d735b8e into develop Aug 24, 2026
6 checks passed
@hokiepokedad2
hokiepokedad2 deleted the feat/810-rule-descriptions branch August 24, 2026 16:53
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