diff --git a/docs/player_gm_ux_redesign_plan.md b/docs/player_gm_ux_redesign_plan.md
index 7e4fbf1..18647e7 100644
--- a/docs/player_gm_ux_redesign_plan.md
+++ b/docs/player_gm_ux_redesign_plan.md
@@ -667,6 +667,16 @@ Acceptance criteria:
### Phase 5: Final tooling boundary cleanup
+Status:
+
+- implemented in the web app on March 15, 2026
+
+Implemented model:
+
+- the primary critical-cell editor now keeps only compact correction tools plus advanced review for generated-versus-current comparison
+- engineering-only parser metadata, source text, and payload inspection moved to a dedicated Diagnostics page in the main navigation
+- normal curation no longer mixes raw JSON and storage-facing diagnostics into the default correction flow
+
Scope:
- verify that normal curation mode contains only user-facing correction tools
diff --git a/src/RolemasterDb.App/Components/Layout/NavMenu.razor b/src/RolemasterDb.App/Components/Layout/NavMenu.razor
index a2bfe8d..b5e1b0d 100644
--- a/src/RolemasterDb.App/Components/Layout/NavMenu.razor
+++ b/src/RolemasterDb.App/Components/Layout/NavMenu.razor
@@ -20,6 +20,12 @@
Critical Tables
+
+
+
+
+ @if (referenceData is null)
+ {
+ Loading table list...
+ }
+ else if (!referenceData.CriticalTables.Any())
+ {
+ No critical tables are available yet.
+ }
+ else
+ {
+
+
+ Table
+
+ @foreach (var table in referenceData.CriticalTables)
+ {
+ @table.Label
+ }
+
+
+
+
+ Roll Band
+
+ @if (tableDetail is not null)
+ {
+ @foreach (var rollBand in tableDetail.RollBands)
+ {
+ @rollBand.Label
+ }
+ }
+
+
+
+ @if (tableDetail is { Groups.Count: > 0 })
+ {
+
+ Variant
+
+ @foreach (var group in tableDetail.Groups)
+ {
+ @group.Label
+ }
+
+
+ }
+
+
+ Severity
+
+ @if (tableDetail is not null)
+ {
+ @foreach (var column in tableDetail.Columns)
+ {
+ @column.Label
+ }
+ }
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(detailError))
+ {
+ @detailError
+ }
+ else if (tableDetail is null)
+ {
+ The selected table could not be loaded.
+ }
+ else if (!tableDetail.Cells.Any())
+ {
+ The selected table has no filled cells to inspect.
+ }
+ else if (selectedCell is null)
+ {
+
+
+
+ }
+ else
+ {
+
+ Inspecting
+ @tableDetail.DisplayName
+ · Roll band @selectedCell.RollBand
+ · Severity @selectedCell.ColumnLabel
+ @if (!string.IsNullOrWhiteSpace(selectedCell.GroupLabel))
+ {
+ · Variant @selectedCell.GroupLabel
+ }
+ · Result ID @selectedCell.ResultId
+
+
+ @if (isDiagnosticsLoading)
+ {
+ Loading diagnostics...
+ }
+ else if (!string.IsNullOrWhiteSpace(diagnosticsError))
+ {
+ @diagnosticsError
+ }
+ else if (diagnosticsModel is not null)
+ {
+
+ }
+ }
+ }
+
+
+@code {
+ private LookupReferenceData? referenceData;
+ private CriticalTableDetail? tableDetail;
+ private CriticalTableCellDetail? selectedCell;
+ private CriticalCellEditorModel? diagnosticsModel;
+ private string selectedTableSlug = string.Empty;
+ private string selectedRollBand = string.Empty;
+ private string selectedColumnKey = string.Empty;
+ private string? selectedGroupKey;
+ private bool isDetailLoading;
+ private bool isDiagnosticsLoading;
+ private string? detailError;
+ private string? diagnosticsError;
+
+ private bool isBusy => isDetailLoading || isDiagnosticsLoading;
+
+ protected override async Task OnInitializedAsync()
+ {
+ referenceData = await LookupService.GetReferenceDataAsync();
+ selectedTableSlug = referenceData.CriticalTables.FirstOrDefault()?.Key ?? string.Empty;
+ await LoadTableDetailAsync();
+ }
+
+ private async Task HandleTableChanged(ChangeEventArgs args)
+ {
+ selectedTableSlug = args.Value?.ToString() ?? string.Empty;
+ await LoadTableDetailAsync();
+ }
+
+ private async Task HandleRollBandChanged(ChangeEventArgs args)
+ {
+ selectedRollBand = args.Value?.ToString() ?? string.Empty;
+ ResolveSelectedCell();
+ await LoadSelectedCellDiagnosticsAsync();
+ }
+
+ private async Task HandleGroupChanged(ChangeEventArgs args)
+ {
+ selectedGroupKey = NormalizeOptionalText(args.Value?.ToString());
+ ResolveSelectedCell();
+ await LoadSelectedCellDiagnosticsAsync();
+ }
+
+ private async Task HandleColumnChanged(ChangeEventArgs args)
+ {
+ selectedColumnKey = args.Value?.ToString() ?? string.Empty;
+ ResolveSelectedCell();
+ await LoadSelectedCellDiagnosticsAsync();
+ }
+
+ private async Task LoadTableDetailAsync()
+ {
+ if (string.IsNullOrWhiteSpace(selectedTableSlug))
+ {
+ tableDetail = null;
+ selectedCell = null;
+ diagnosticsModel = null;
+ return;
+ }
+
+ isDetailLoading = true;
+ detailError = null;
+ diagnosticsError = null;
+ diagnosticsModel = null;
+ selectedCell = null;
+
+ try
+ {
+ tableDetail = await LookupService.GetCriticalTableAsync(selectedTableSlug);
+ if (tableDetail is null)
+ {
+ detailError = "The selected table could not be loaded.";
+ return;
+ }
+
+ SetDefaultSelection(tableDetail);
+ ResolveSelectedCell();
+ await LoadSelectedCellDiagnosticsAsync();
+ }
+ catch (Exception exception)
+ {
+ detailError = exception.Message;
+ tableDetail = null;
+ selectedCell = null;
+ diagnosticsModel = null;
+ }
+ finally
+ {
+ isDetailLoading = false;
+ }
+ }
+
+ private void SetDefaultSelection(CriticalTableDetail detail)
+ {
+ if (!detail.Cells.Any())
+ {
+ selectedRollBand = detail.RollBands.FirstOrDefault()?.Label ?? string.Empty;
+ selectedColumnKey = detail.Columns.FirstOrDefault()?.Key ?? string.Empty;
+ selectedGroupKey = detail.Groups.FirstOrDefault()?.Key;
+ return;
+ }
+
+ var rollOrder = detail.RollBands
+ .Select((rollBand, index) => new { rollBand.Label, index })
+ .ToDictionary(item => item.Label, item => item.index, StringComparer.Ordinal);
+ var columnOrder = detail.Columns
+ .Select((column, index) => new { column.Key, index })
+ .ToDictionary(item => item.Key, item => item.index, StringComparer.Ordinal);
+ var groupOrder = detail.Groups
+ .Select((group, index) => new { group.Key, index })
+ .ToDictionary(item => item.Key, item => item.index, StringComparer.Ordinal);
+
+ var firstCell = detail.Cells
+ .OrderBy(cell => rollOrder.GetValueOrDefault(cell.RollBand, int.MaxValue))
+ .ThenBy(cell =>
+ {
+ if (cell.GroupKey is null)
+ {
+ return -1;
+ }
+
+ return groupOrder.GetValueOrDefault(cell.GroupKey, int.MaxValue);
+ })
+ .ThenBy(cell => columnOrder.GetValueOrDefault(cell.ColumnKey, int.MaxValue))
+ .First();
+
+ selectedRollBand = firstCell.RollBand;
+ selectedColumnKey = firstCell.ColumnKey;
+ selectedGroupKey = firstCell.GroupKey;
+ }
+
+ private void ResolveSelectedCell()
+ {
+ if (tableDetail is null)
+ {
+ selectedCell = null;
+ return;
+ }
+
+ selectedCell = tableDetail.Cells.FirstOrDefault(cell =>
+ string.Equals(cell.RollBand, selectedRollBand, StringComparison.Ordinal) &&
+ string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.Ordinal) &&
+ string.Equals(cell.GroupKey ?? string.Empty, selectedGroupKey ?? string.Empty, StringComparison.Ordinal));
+ }
+
+ private async Task LoadSelectedCellDiagnosticsAsync()
+ {
+ diagnosticsError = null;
+ diagnosticsModel = null;
+
+ if (selectedCell is null || string.IsNullOrWhiteSpace(selectedTableSlug))
+ {
+ return;
+ }
+
+ isDiagnosticsLoading = true;
+
+ try
+ {
+ var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, selectedCell.ResultId);
+ if (response is null)
+ {
+ diagnosticsError = "The selected cell could not be loaded.";
+ return;
+ }
+
+ diagnosticsModel = CriticalCellEditorModel.FromResponse(response);
+ }
+ catch (Exception exception)
+ {
+ diagnosticsError = exception.Message;
+ }
+ finally
+ {
+ isDiagnosticsLoading = false;
+ }
+ }
+
+ private static string? NormalizeOptionalText(string? value) =>
+ string.IsNullOrWhiteSpace(value) ? null : value.Trim();
+}
diff --git a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor
index e2c3133..e379e9e 100644
--- a/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor
+++ b/src/RolemasterDb.App/Components/Shared/CriticalCellEditorDialog.razor
@@ -1,7 +1,6 @@
@using System
@using System.Collections.Generic
@using System.Linq
-@using System.Text.Json
@using Microsoft.JSInterop
@using RolemasterDb.App.Domain
@using RolemasterDb.App.Features
@@ -206,11 +205,11 @@
}
-