Skip to content

Commit 28a49bc

Browse files
authored
Merge pull request #412 from codemonkey85/dev
Add per-field legality indicators to Pokémon editor tabs (#411)
2 parents 76a625d + 4ce0d38 commit 28a49bc

25 files changed

Lines changed: 1025 additions & 324 deletions

PKHEX_PARITY_ROADMAP.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ This roadmap outlines the path to achieving 100% feature parity with PKHeX. Task
141141
- [x] Add "Fix" buttons for common legality issues (ball, met location, moves, TechRecord) — closes #401
142142
- [x] Implement batch legality checking — "Legality Report" tab sweeps all party/box slots, sortable/filterable table, Legal/Fishy/Illegal counts, click-to-jump-to-slot — closes #402
143143
- [x] Show legality warnings on Pokémon slot display (valid/warn icon overlay)
144+
- [x] Add inline per-field legality indicators throughout Pokémon editor tabs (#411)
145+
- [x] Reusable `LegalityIndicator` component (MudTooltip + severity icon, renders nothing when valid)
146+
- [x] `LegalityAnalysis` computed once in `PokemonEditForm` and passed to all tabs
147+
- [x] Per-move-slot indicators (current moves + relearn moves) in **Moves tab**
148+
- [x] Group-level indicators for IVs/EVs/AVs/GVs in **Stats tab**
149+
- [x] Field-level indicators (PID, Ability, Gender, Shiny, Nature, Form, Held Item, Nickname, Language, Egg) in **Main tab**
150+
- [x] Field-level indicators (Ball, Encounter/Met Location, Level, Game Origin) in **Met tab**
151+
- [x] Field-level indicators (EC, Trainer/OT, Handler, Memory) in **OT/Misc tab**
152+
- [x] Per-ribbon indicators in **Ribbons tab**
144153
- [ ] Create comprehensive unit tests
145154
- **Note:** `ParseSettings.InitFromSaveFileData` is intentionally not called (see `MainLayout.razor.cs` comment); relies on default `AllowGBCartEra = false` so VC encounters are always checked regardless of filename. PKHeX bug filed: [kwsch/PKHeX#4734](https://github.com/kwsch/PKHeX/issues/4734).
146155

@@ -883,4 +892,5 @@ This roadmap is a living document. Community contributions are welcome!
883892
**Last Updated:** 2026-02-27
884893
**Next Review:** 2026-03-27
885894
<!-- Legality Checker (§1.2): fix buttons (#401) + batch report (#402) done 2026-02-27; comprehensive unit tests still pending -->
895+
<!-- Legality Checker (§1.2): per-field inline indicators (#411) implemented 2026-02-27; covers Moves/Stats/Main/Met/OT-Misc/Ribbons tabs -->
886896
<!-- PKHeX bug filed 2026-02-27: SAV1.IsVirtualConsole filename heuristic causes false cart-era detection for renamed VC saves → kwsch/PKHeX#4734 -->

Pkmds.Rcl/Components/EditForms/PokemonEditForm.razor

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,26 @@
8585
Border>
8686

8787
<MudTabPanel Text="Main">
88-
<MainTab Pokemon="@Pokemon"/>
88+
<MainTab Pokemon="@Pokemon"
89+
Analysis="@Analysis"/>
8990
</MudTabPanel>
9091

9192
@if (saveGeneration >= 2)
9293
{
9394
<MudTabPanel Text="Met">
94-
<MetTab Pokemon="@Pokemon"/>
95+
<MetTab Pokemon="@Pokemon"
96+
Analysis="@Analysis"/>
9597
</MudTabPanel>
9698
}
9799

98100
<MudTabPanel Text="Stats">
99-
<StatsTab Pokemon="@Pokemon"/>
101+
<StatsTab Pokemon="@Pokemon"
102+
Analysis="@Analysis"/>
100103
</MudTabPanel>
101104

102105
<MudTabPanel Text="Moves">
103-
<MovesTab Pokemon="@Pokemon"/>
106+
<MovesTab Pokemon="@Pokemon"
107+
Analysis="@Analysis"/>
104108
</MudTabPanel>
105109

106110
@if (saveGeneration >= 3)
@@ -120,16 +124,19 @@
120124
@if (saveGeneration >= 3)
121125
{
122126
<MudTabPanel Text="Ribbons">
123-
<RibbonsTab Pokemon="@Pokemon"/>
127+
<RibbonsTab Pokemon="@Pokemon"
128+
Analysis="@Analysis"/>
124129
</MudTabPanel>
125130
}
126131

127132
<MudTabPanel Text="OT/Misc">
128-
<OtMiscTab Pokemon="@Pokemon"/>
133+
<OtMiscTab Pokemon="@Pokemon"
134+
Analysis="@Analysis"/>
129135
</MudTabPanel>
130136

131137
<MudTabPanel Text="Legality">
132-
<LegalityTab Pokemon="@Pokemon"/>
138+
<LegalityTab Pokemon="@Pokemon"
139+
Analysis="@Analysis"/>
133140
</MudTabPanel>
134141

135142
</MudTabs>

Pkmds.Rcl/Components/EditForms/PokemonEditForm.razor.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,29 @@ public partial class PokemonEditForm : IDisposable
66
[EditorRequired]
77
public PKM? Pokemon { get; set; }
88

9+
private LegalityAnalysis? Analysis { get; set; }
10+
911
public void Dispose() =>
10-
RefreshService.OnAppStateChanged -= StateHasChanged;
12+
RefreshService.OnAppStateChanged -= Refresh;
13+
14+
protected override void OnInitialized()
15+
{
16+
RefreshService.OnAppStateChanged += Refresh;
17+
ComputeAnalysis();
18+
}
19+
20+
protected override void OnParametersSet() => ComputeAnalysis();
21+
22+
private void Refresh()
23+
{
24+
ComputeAnalysis();
25+
StateHasChanged();
26+
}
1127

12-
protected override void OnInitialized() =>
13-
RefreshService.OnAppStateChanged += StateHasChanged;
28+
private void ComputeAnalysis() =>
29+
Analysis = Pokemon is { Species: > 0 }
30+
? AppService.GetLegalityAnalysis(Pokemon)
31+
: null;
1432

1533
private void ExportAsShowdown() =>
1634
DialogService.ShowAsync<ShowdownExportDialog>(

Pkmds.Rcl/Components/EditForms/Tabs/LegalityTab.razor

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@
2222
var issues = analysis.Results
2323
.Where(r => !r.Valid)
2424
.ToList();
25+
var invalidMoves = GetInvalidMoves();
26+
var invalidRelearns = GetInvalidRelearnMoves();
27+
var totalIssues = issues.Count + invalidMoves.Count + invalidRelearns.Count;
2528
}
2629

27-
@if (issues.Count > 0)
30+
@if (totalIssues > 0)
2831
{
2932
<MudText Typo="@Typo.subtitle2"
30-
Class="mt-2">Issues (@issues.Count)
33+
Class="mt-2">Issues (@totalIssues)
3134
</MudText>
3235

33-
<MudList T="CheckResult"
36+
<MudList T="string"
3437
Dense>
3538
@foreach (var result in issues)
3639
{
37-
<MudListItem T="CheckResult"
40+
<MudListItem T="string"
3841
Icon="@GetSeverityIcon(result.Judgement)"
3942
IconColor="@GetSeverityColor(result.Judgement)"
4043
@key="@($"{result.Identifier}:{result.Result}")">
@@ -51,6 +54,44 @@
5154
</MudStack>
5255
</MudListItem>
5356
}
57+
@foreach (var (moveResult, slotNum) in invalidMoves)
58+
{
59+
<MudListItem T="string"
60+
Icon="@GetSeverityIcon(moveResult.Judgement)"
61+
IconColor="@GetSeverityColor(moveResult.Judgement)"
62+
@key="@($"move:{slotNum}")">
63+
<MudStack Row
64+
AlignItems="@AlignItems.Center"
65+
Spacing="1">
66+
<MudChip T="string"
67+
Size="@Size.Small"
68+
Color="@GetSeverityColor(moveResult.Judgement)"
69+
Variant="@Variant.Outlined">
70+
Move @slotNum
71+
</MudChip>
72+
<MudText Typo="@Typo.body2">@GetMoveSummary(moveResult)</MudText>
73+
</MudStack>
74+
</MudListItem>
75+
}
76+
@foreach (var (relearnResult, slotNum) in invalidRelearns)
77+
{
78+
<MudListItem T="string"
79+
Icon="@GetSeverityIcon(relearnResult.Judgement)"
80+
IconColor="@GetSeverityColor(relearnResult.Judgement)"
81+
@key="@($"relearn:{slotNum}")">
82+
<MudStack Row
83+
AlignItems="@AlignItems.Center"
84+
Spacing="1">
85+
<MudChip T="string"
86+
Size="@Size.Small"
87+
Color="@GetSeverityColor(relearnResult.Judgement)"
88+
Variant="@Variant.Outlined">
89+
Relearn @slotNum
90+
</MudChip>
91+
<MudText Typo="@Typo.body2">@GetMoveSummary(relearnResult)</MudText>
92+
</MudStack>
93+
</MudListItem>
94+
}
5495
</MudList>
5596
}
5697

Pkmds.Rcl/Components/EditForms/Tabs/LegalityTab.razor.cs

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public partial class LegalityTab : IDisposable
88
[EditorRequired]
99
public PKM? Pokemon { get; set; }
1010

11-
private LegalityAnalysis? Analysis { get; set; }
11+
[Parameter]
12+
public LegalityAnalysis? Analysis { get; set; }
1213

1314
// Moves are validated in la.Info.Moves / la.Info.Relearn (MoveResult[]), not in la.Results.
1415
// A legal Pokémon must have all of those valid in addition to all CheckResults being valid.
@@ -38,26 +39,10 @@ public partial class LegalityTab : IDisposable
3839
la.Results.Any(r => r is { Valid: false, Identifier: CheckIdentifier.Level or CheckIdentifier.Encounter });
3940

4041
public void Dispose() =>
41-
RefreshService.OnAppStateChanged -= Refresh;
42-
43-
protected override void OnInitialized()
44-
{
45-
RefreshService.OnAppStateChanged += Refresh;
46-
ComputeAnalysis();
47-
}
48-
49-
protected override void OnParametersSet() => ComputeAnalysis();
42+
RefreshService.OnAppStateChanged -= StateHasChanged;
5043

51-
private void Refresh()
52-
{
53-
ComputeAnalysis();
54-
StateHasChanged();
55-
}
56-
57-
private void ComputeAnalysis() =>
58-
Analysis = Pokemon is { Species: > 0 }
59-
? AppService.GetLegalityAnalysis(Pokemon)
60-
: null;
44+
protected override void OnInitialized() =>
45+
RefreshService.OnAppStateChanged += StateHasChanged;
6146

6247
private void RemoveInvalidRibbons()
6348
{
@@ -175,6 +160,57 @@ private void SuggestMetLocation()
175160
Snackbar.Add("Met location and level updated. Click Save to apply changes.", Severity.Success);
176161
}
177162

163+
private IReadOnlyList<(MoveResult Result, int SlotNumber)> GetInvalidMoves()
164+
{
165+
if (Analysis is not { } la)
166+
{
167+
return [];
168+
}
169+
170+
var result = new List<(MoveResult, int)>();
171+
var moves = la.Info.Moves;
172+
for (var i = 0; i < moves.Length; i++)
173+
{
174+
if (!moves[i].Valid)
175+
{
176+
result.Add((moves[i], i + 1));
177+
}
178+
}
179+
180+
return result;
181+
}
182+
183+
private IReadOnlyList<(MoveResult Result, int SlotNumber)> GetInvalidRelearnMoves()
184+
{
185+
if (Analysis is not { } la)
186+
{
187+
return [];
188+
}
189+
190+
var result = new List<(MoveResult, int)>();
191+
var relearns = la.Info.Relearn;
192+
for (var i = 0; i < relearns.Length; i++)
193+
{
194+
if (!relearns[i].Valid)
195+
{
196+
result.Add((relearns[i], i + 1));
197+
}
198+
}
199+
200+
return result;
201+
}
202+
203+
private string GetMoveSummary(MoveResult result)
204+
{
205+
if (Analysis is not { } la)
206+
{
207+
return string.Empty;
208+
}
209+
210+
var ctx = LegalityLocalizationContext.Create(la);
211+
return result.Summary(ctx);
212+
}
213+
178214
private static Color GetSeverityColor(PKHexSeverity severity) => severity switch
179215
{
180216
PKHexSeverity.Valid => Color.Success,

0 commit comments

Comments
 (0)