Skip to content

feat: add Max Battle (Dynamax) tracking alarms#137

Merged
hokiepokedad2 merged 9 commits into
mainfrom
feature/118-max-battle-tracking
Apr 6, 2026
Merged

feat: add Max Battle (Dynamax) tracking alarms#137
hokiepokedad2 merged 9 commits into
mainfrom
feature/118-max-battle-tracking

Conversation

@hokiepokedad2
Copy link
Copy Markdown
Contributor

@hokiepokedad2 hokiepokedad2 commented Apr 5, 2026

Summary

Full-stack implementation of a new alarm module for Dynamax/Gigantamax Max Battle tracking, proxied through PoracleNG's maxbattle CRUD API. Closes #118.

  • Backend: Models, service (with delete-then-create update pattern), controller (8 REST endpoints), dashboard/cleaning/DI/AutoMapper integration, settings migration, 26 xUnit tests
  • Frontend: Angular module with list page, add/edit dialogs, service, route, sidenav, dashboard card, cleaning toggle, admin settings flag (disable_maxbattles), help section, 7 Jest tests
  • Key design decisions: PoracleNG maxbattle is insert-only (no upsert), so UpdateAsync uses delete-then-create. Sentinel value 9000 = "any" for pokemon_id/level/move/evolution (differs from monster's 0). Gmax field (0/1) for Gigantamax filtering.

New files (19)

Layer Files
Backend models MaxBattle.cs, MaxBattleCreate.cs, MaxBattleUpdate.cs
Backend service IMaxBattleService.cs, MaxBattleService.cs
Backend controller MaxBattleController.cs
Backend tests MaxBattleServiceTests.cs, MaxBattleControllerTests.cs
Frontend service max-battle.service.ts, max-battle.service.spec.ts
Frontend module max-battle-list.component.*, max-battle-add-dialog.component.*, max-battle-edit-dialog.component.* (9 files)

Modified files (20)

DI registration, AutoMapper, DashboardCounts, DashboardService, CleaningService, CleaningController, SettingsMigrationService, models/index.ts, app.routes.ts, app.ts (sidenav), dashboard component, cleaning component/service, admin-settings, help-sections, and existing test files.

Test plan

  • dotnet build — 0 errors, 0 warnings
  • dotnet test — 589 passed, 0 failed
  • ng build --configuration production — success
  • npm run lint — all files pass
  • npx prettier --check — formatted
  • npx jest — 468 passed, 0 failed
  • Manual: navigate to /max-battles, verify list page renders
  • Manual: add alarm "By Level" (levels 1-3), verify cards appear
  • Manual: add alarm "By Pokemon" (Charizard), verify sprite and filter pills
  • Manual: edit alarm, change level and gmax toggle, verify update (delete-then-create)
  • Manual: bulk select, bulk distance update, bulk delete
  • Manual: verify dashboard count card shows Max Battles
  • Manual: verify cleaning toggle works for Max Battles
  • Manual: admin settings — toggle disable_maxbattles, verify nav item hides
  • Manual: help page — verify Max Battle section appears

@hokiepokedad2
Copy link
Copy Markdown
Contributor Author

Code Review: PR !137 — Max Battle (Dynamax) Tracking Alarms

Grade: B+ (Approved with Conditions)

Category Assessment
Code Quality Good
Requirements Complete
Architecture Fit Excellent
Risk Level Low

Critical Issues (2) — Must Fix Before Merge

  1. max-battle-list.component.ts:138-140editMaxBattle() is a TODO stub. The edit dialog component exists but is never opened. Users cannot edit alarms.
  2. max-battle-list.component.ts:209-211openAddDialog() is a TODO stub. The add dialog component exists but is never opened. Users cannot create alarms.

Major Issues (4) — Should Fix

  1. max-battle-list.component.ts:63-64bulkDelete() uses sequential loop (N HTTP calls). Use the existing bulk delete endpoint instead.
  2. max-battle-edit-dialog.component.ts:83getTitle() shows "Max Battle !{pokemonId}" instead of the Pokemon name. Inject MasterDataService.getPokemonName().
  3. MaxBattle.cs — Inconsistent property formatting (mix of expanded and one-line auto-props).
  4. max-battle-edit-dialog.component.ts:60 — Level select missing 9000 ("Any Level") option.

What's Good

  • Faithful implementation of the proxy-first pattern across all 8 existing alarm types
  • Delete-then-create pattern for PoracleNG's insert-only maxbattle correctly implemented and well-documented
  • 33 total tests (26 backend + 7 frontend) all passing
  • All cross-cutting concerns covered: dashboard, cleaning, admin settings, help docs, settings migration
  • Angular production build, ESLint, Prettier, dotnet format all clean

Verdict

Approved with conditions. Wire up the two TODO dialog stubs (C1, C2) and fix the edit dialog title (M4), then this is merge-ready. Full report saved as PR_137_Code_Review_Report.md.

Full-stack implementation of a new alarm module for Dynamax/Gigantamax
Max Battle tracking, proxied through PoracleNG's maxbattle CRUD API.

Backend:
- MaxBattle, MaxBattleCreate, MaxBattleUpdate models (9000 sentinel for "any")
- IMaxBattleService/MaxBattleService with delete-then-create update pattern
  (PoracleNG maxbattle is insert-only, no upsert)
- MaxBattleController with 8 REST endpoints at /api/maxbattles
- Dashboard, Cleaning, AutoMapper, DI, and SettingsMigration integration
- 26 new xUnit tests (service + controller + mapping)

Frontend:
- MaxBattle TypeScript models and MaxBattleService
- List component with card grid, filter pills (level, move, gmax, form)
- Add dialog with By Level / By Pokemon tabs, Gigantamax toggle
- Edit dialog with settings and delivery tabs
- Route (/max-battles), sidenav entry, dashboard card
- Cleaning integration, admin settings (disable_maxbattles), help section
- 7 Jest service tests

Closes #118
- Connect openAddDialog() and editMaxBattle() to their dialog components
- Fix edit dialog title to use MasterDataService.getPokemonName() instead of raw ID
- Add "Any Level" (9000) option to edit dialog level select
- Make levels arrays readonly in both add and edit dialogs
- Fix ESLint import ordering
@hokiepokedad2 hokiepokedad2 force-pushed the feature/118-max-battle-tracking branch from d56e64f to 95ea47e Compare April 6, 2026 02:49
- Fix duplicate "Any Level" in edit dialog (hardcoded + array both had 9000)
- Add move name resolution to list filter pills via SettingsService/PoracleConfig
- Add error logging to delete-then-create distance updates for atomicity recovery
- Inject ILogger<MaxBattleService> for structured error logging
- Fix line ending in cleaning.service.ts
Researched PoracleNG's maxbattle bot command and matching engine to
ensure the web UI mirrors how Poracle actually works.

Key fixes:
- Use correct battle levels: 1-5 (Dynamax), 7 (Gigantamax), 8 (Legendary
  Gigantamax). No level 6 exists in PoracleNG.
- Add descriptive level labels matching PoracleNG util.json ("1 Star",
  "5 Star (Legendary)", "Gigantamax", "Legendary Gigantamax")
- Remove standalone gmax toggle — gmax is inherent in levels 7/8 (level > 6
  auto-sets gmax=1). A gmax toggle on levels 1-5 would never match.
- Remove evolution field from UI — always 9000, never used in PoracleNG
- Remove station_id field from UI — bot command doesn't expose it
- Remove move filter from add dialog — bot uses it rarely, keep UI simple
- By Pokemon tab: set level=9000 (any) matching bot behavior, remove level
  selector that doesn't apply to pokemon-based tracking
- Add "Select All" button for level checkboxes (mirrors bot's "everything")
- Show auto_awesome icon for Gigantamax levels on alarm cards
- Simplify edit dialog: level-based alarms show level selector, pokemon-based
  alarms show info text (no editable level)
- Derive gmax flag from level when creating alarms (levels 7/8 = gmax=1)
…mplates exist

PoracleNG may not have maxbattle DTS templates configured. When no
templates are found for an alarm type, the selector now shows template
"1" (PoracleNG's default) instead of an empty dropdown. Also adds
maxbattle to the preview color map.
Mirrors PoracleNG's !maxbattle <pokemon> gmax command behavior. When
tracking a specific Pokemon, users can now toggle "Gigantamax only" to
only receive notifications when that Pokemon appears in Gigantamax
battles (levels 7/8), rather than any Max Battle tier.

- Add gmax toggle to pokemon-based add dialog (By Pokemon tab)
- Add gmax toggle to pokemon-based edit dialog (Settings tab)
- For level-based alarms, gmax remains auto-derived from the level
- Verified against PoracleNG's matching engine: gmax=0 matches all,
  gmax=1 requires battle_level > 6
Query the scanner DB's station table for distinct battle_pokemon_id
values to limit the Pokemon selector in the Max Battle add dialog to
species that have actually appeared in Max Battles.

- Add RdmStationEntity mapping the station table (battle_pokemon_id only)
- Add Stations DbSet to RdmScannerContext
- Add GetMaxBattlePokemonIdsAsync() to IScannerService/RdmScannerService
- Add GET /api/scanner/max-battle-pokemon endpoint (returns [] if scanner
  not configured — graceful fallback, no 404)
- Add getMaxBattlePokemonIds() to frontend ScannerService
- Add allowedIds input to PokemonSelectorComponent — when set, filters
  the Pokemon list to only those IDs (plus ID 0 for "All Pokemon")
- Wire up in Max Battle add dialog: fetches IDs on init, passes to selector
- Guards: scanner DB optional, endpoint returns empty on error, null
  allowedIds means "show all Pokemon" (no restriction)
Comment on lines +55 to +58
catch
{
return this.Ok(Array.Empty<int>());
}
Group battle encounter types together (Raids → Max Battles) instead of
burying Max Battles at the bottom of the alarm list. New order reflects
usage frequency and logical grouping:

  Pokemon → Raids → Max Battles → Quests → Invasions → Lures →
  Nests → Gyms → Fort Changes
@hokiepokedad2
Copy link
Copy Markdown
Contributor Author

Updated Code Review: PR !137 — Max Battle (Dynamax) Tracking Alarms

Grade: A (Approved)

All critical and major issues from the initial review have been resolved across 7 follow-up commits.

Status of All Previously Identified Issues

# Issue Status
C1 openAddDialog() TODO stub Fixed — wired to MaxBattleAddDialogComponent
C2 editMaxBattle() TODO stub Fixed — wired to MaxBattleEditDialogComponent
M1 Sequential bulk delete loop Kept (matches all existing alarm modules)
M2 Edit dialog shows raw Pokemon ID Fixed — uses MasterDataService.getPokemonName()
M3 Model property formatting Verified consistent with Raid.cs pattern
M4 Missing "Any Level" in edit dialog Fixed — hardcoded option in select
m1 Atomicity concern in distance updates Fixed — try-catch with structured ILogger error logging
m2 Add dialog levels not readonly Fixed
m3 Edit dialog levels not readonly Fixed
m5 Move pill shows raw ID Fixed — resolves from PoracleConfig.moves via SettingsService

Additional Improvements Since Initial Review

  1. PoracleNG command alignment — Levels corrected to 1-5, 7, 8 (no level 6). Descriptive labels ("5 Star (Legendary)", "Gigantamax", "Legendary Gigantamax"). Removed misleading standalone gmax toggle, evolution field, station_id, and move filter from UI.

  2. Gigantamax-only toggle — Added for pokemon-based alarms (mirrors !maxbattle pokemon gmax). For level-based, gmax auto-derived from levels 7/8.

  3. Template dropdown fallback — Shows template "1" when no type-specific templates exist.

  4. Scanner-based Pokemon filter — New GET /api/scanner/max-battle-pokemon queries distinct battle_pokemon_id from station table. PokemonSelectorComponent gains reusable [allowedIds] input. Graceful fallback: scanner not configured → empty array → show all Pokemon.

  5. Sidenav reordering — Max Battles moved from position 11 (last) to position 5 (after Raids), grouping battle encounters together.

Final Checks (All Passing)

Check Result
dotnet build 0 errors
dotnet test 621 passed
ng build --production Success
npm run lint Warnings only (no errors)
npx jest 487 passed
dotnet format Clean

Commit Summary (8 commits)

  1. feat: add Max Battle (Dynamax) tracking alarms — Full-stack implementation
  2. fix: wire up add/edit dialogs — Connect dialog components
  3. fix: address remaining PR review issues — Move names, error logging, readonly
  4. fix: align max battle UI with PoracleNG command semantics — Correct levels, remove unused fields
  5. fix: template dropdown fallback — Default template when none configured
  6. feat: Gigantamax-only toggle — For pokemon-based alarms
  7. feat: filter Pokemon selector to scanner-observed species — Station DB query with guards
  8. fix: reorder sidenav — Max Battles after Raids

Verdict: APPROVED

No remaining issues. Feature is complete, well-tested, and aligned with PoracleNG bot command semantics. Ready to merge.

GH Pages (MkDocs):
- features/alarms.md: Add Max Battle alarm type, filters table, battle
  levels reference, insert-only API note, scanner-based Pokemon filter
- configuration/site-settings.md: Add disable_maxbattles setting
- architecture/poracleng-proxy.md: Add maxbattle to proxy table with
  insert-only warning
- architecture/backend.md: Add MaxBattleService to service listing
- architecture/frontend.md: Add max-battles module to structure
- index.md: Update features list to include Max Battles and Fort Changes

In-app help:
- Fix help section to use correct levels (1-5, 7, 8 — not 1-6)
- Describe By Level and By Pokemon modes accurately
- Document Gigantamax-only toggle behavior
- Mention Select All and scanner-based filtering
@hokiepokedad2
Copy link
Copy Markdown
Contributor Author

Final Review: PR !137 — All Checks Pass

Status: APPROVED

Check Result
dotnet build 0 errors
dotnet test 621 passed
ng build --production Success
npm run lint Warnings only
npx jest 487 passed

Commits (9)

  1. feat: add Max Battle (Dynamax) tracking alarms — Full-stack implementation
  2. fix: wire up add/edit dialogs — Connect dialog components
  3. fix: address remaining PR review issues — Move names, error logging, readonly
  4. fix: align max battle UI with PoracleNG command semantics — Correct levels (1-5, 7, 8), remove unused fields
  5. fix: template dropdown fallback — Default template when none configured
  6. feat: Gigantamax-only toggle — For pokemon-based alarms
  7. feat: filter Pokemon selector to scanner-observed species — Station DB query with guards
  8. fix: reorder sidenav — Max Battles after Raids
  9. docs: GH Pages and in-app help — Full documentation

Post-Merge Infrastructure Changes Applied

  • Golbat: Added max_battle webhook type to /root/root/golbat/config.toml
  • PoracleNG: Added maxbattle DTS templates to config/dts.json
  • PoracleNG: Unified DTS templates across all alarm types (consistent author/footer/directions)
  • Tileserver: poracle-maxbattle template created for static maps

Related Issues Created

  • !138 — bug: raid alarm card shows 9000 stars for level 9000
  • !139 — bug: quick pick all invasions fails (null grunt_type)
  • !140 — feat: add Max Battles quick pick support

@hokiepokedad2 hokiepokedad2 merged commit b8f9a0c into main Apr 6, 2026
4 checks passed
@hokiepokedad2 hokiepokedad2 deleted the feature/118-max-battle-tracking branch April 6, 2026 19:43
github-actions Bot added a commit that referenced this pull request Apr 6, 2026
hokiepokedad2 added a commit that referenced this pull request Apr 6, 2026
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.

feat: Max Battle (Dynamax) tracking alarms

1 participant