Split advanced diagnostics from cell editor
This commit is contained in:
@@ -567,6 +567,10 @@ Acceptance criteria:
|
|||||||
|
|
||||||
### Phase 5: Advanced curation and diagnostics split
|
### Phase 5: Advanced curation and diagnostics split
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
- implemented in the web app on March 15, 2026
|
||||||
|
|
||||||
Scope:
|
Scope:
|
||||||
|
|
||||||
- move debug/import details into a separate advanced surface
|
- move debug/import details into a separate advanced surface
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@using System
|
@using System
|
||||||
@using System.Collections.Generic
|
@using System.Collections.Generic
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
|
@using System.Text.Json
|
||||||
@using RolemasterDb.App.Domain
|
@using RolemasterDb.App.Domain
|
||||||
@using RolemasterDb.App.Features
|
@using RolemasterDb.App.Features
|
||||||
|
|
||||||
@@ -94,12 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (Model.ValidationMessages.Count > 0)
|
@if (Model.ValidationMessages.Count > 0)
|
||||||
{
|
{
|
||||||
<div class="critical-editor-validation-list">
|
<p class="muted critical-editor-advanced-hint">@GetParserNoteSummary(Model.ValidationMessages.Count)</p>
|
||||||
@foreach (var message in Model.ValidationMessages)
|
|
||||||
{
|
|
||||||
<p class="critical-editor-validation-item">@message</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div class="field-shell">
|
<div class="field-shell">
|
||||||
<label>Result Text Override</label>
|
<label>Result Text Override</label>
|
||||||
@@ -228,6 +224,94 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="critical-editor-section critical-editor-diagnostics-section">
|
||||||
|
<details class="critical-editor-advanced">
|
||||||
|
<summary class="critical-editor-advanced-summary">
|
||||||
|
<span>Advanced Diagnostics</span>
|
||||||
|
<span class="critical-editor-advanced-meta">@GetAdvancedSummary(Model)</span>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="critical-editor-advanced-body">
|
||||||
|
<div class="critical-editor-card nested">
|
||||||
|
<div class="critical-editor-card-header">
|
||||||
|
<div>
|
||||||
|
<strong>Parser Metadata</strong>
|
||||||
|
<p class="muted critical-editor-inline-copy">Last loaded or re-parsed parser result.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="critical-editor-diagnostic-grid">
|
||||||
|
<div>
|
||||||
|
<dt>Parse Status</dt>
|
||||||
|
<dd>@Model.ParseStatus</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Raw Affix Text</dt>
|
||||||
|
<dd>@(string.IsNullOrWhiteSpace(Model.RawAffixText) ? "—" : Model.RawAffixText)</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
@if (Model.ValidationMessages.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="critical-editor-validation-list">
|
||||||
|
@foreach (var message in Model.ValidationMessages)
|
||||||
|
{
|
||||||
|
<p class="critical-editor-validation-item">@message</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="field-shell">
|
||||||
|
<label>Parsed Effects JSON</label>
|
||||||
|
<pre class="critical-editor-diagnostic-block">@FormatJson(Model.ParsedJson)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var effectMetadata = GetEffectMetadataRows(Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (effectMetadata.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="critical-editor-card nested">
|
||||||
|
<div class="critical-editor-card-header">
|
||||||
|
<div>
|
||||||
|
<strong>Effect Source Metadata</strong>
|
||||||
|
<p class="muted critical-editor-inline-copy">Stored source markers and labels for the current effect list.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="critical-editor-diagnostic-list">
|
||||||
|
@foreach (var entry in effectMetadata)
|
||||||
|
{
|
||||||
|
<div class="critical-editor-diagnostic-item">
|
||||||
|
<div>
|
||||||
|
<strong>@entry.Scope</strong>
|
||||||
|
<p class="muted critical-editor-inline-copy">@entry.EffectLabel</p>
|
||||||
|
</div>
|
||||||
|
<div class="critical-editor-diagnostic-values">
|
||||||
|
<span><strong>Type:</strong> @entry.SourceType</span>
|
||||||
|
<span><strong>Text:</strong> @(string.IsNullOrWhiteSpace(entry.SourceText) ? "—" : entry.SourceText)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="critical-editor-card nested">
|
||||||
|
<div class="critical-editor-card-header">
|
||||||
|
<div>
|
||||||
|
<strong>Current Save Payload</strong>
|
||||||
|
<p class="muted critical-editor-inline-copy">Request built from the visible editor state, including generated branch internals.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre class="critical-editor-diagnostic-block">@BuildCurrentSavePayloadJson(Model)</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="critical-editor-footer">
|
<footer class="critical-editor-footer">
|
||||||
@@ -272,6 +356,11 @@
|
|||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public EventCallback OnSave { get; set; }
|
public EventCallback OnSave { get; set; }
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions DiagnosticJsonOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
private async Task HandleBackdropClicked()
|
private async Task HandleBackdropClicked()
|
||||||
{
|
{
|
||||||
await OnClose.InvokeAsync();
|
await OnClose.InvokeAsync();
|
||||||
@@ -393,6 +482,69 @@
|
|||||||
effect.SourceType = AffixDisplayMap.TryGet(effect.EffectCode, out _) ? "symbol" : "manual";
|
effect.SourceType = AffixDisplayMap.TryGet(effect.EffectCode, out _) ? "symbol" : "manual";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetAdvancedSummary(CriticalCellEditorModel model)
|
||||||
|
{
|
||||||
|
var noteCount = model.ValidationMessages.Count;
|
||||||
|
return noteCount == 0 ? "Parser metadata and save payload" : $"{noteCount} parser note{(noteCount == 1 ? string.Empty : "s")}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetParserNoteSummary(int noteCount) =>
|
||||||
|
noteCount == 1
|
||||||
|
? "1 parser note is available under Advanced Diagnostics."
|
||||||
|
: $"{noteCount} parser notes are available under Advanced Diagnostics.";
|
||||||
|
|
||||||
|
private static string FormatJson(string json)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse(json);
|
||||||
|
return JsonSerializer.Serialize(document.RootElement, DiagnosticJsonOptions);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return json.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildCurrentSavePayloadJson(CriticalCellEditorModel model) =>
|
||||||
|
JsonSerializer.Serialize(model.ToRequest(), DiagnosticJsonOptions);
|
||||||
|
|
||||||
|
private static List<(string Scope, string EffectLabel, string SourceType, string? SourceText)> GetEffectMetadataRows(CriticalCellEditorModel model)
|
||||||
|
{
|
||||||
|
var rows = new List<(string Scope, string EffectLabel, string SourceType, string? SourceText)>();
|
||||||
|
|
||||||
|
foreach (var effect in model.Effects)
|
||||||
|
{
|
||||||
|
if (ShouldIncludeEffectMetadata(effect))
|
||||||
|
{
|
||||||
|
rows.Add(("Base Result", GetEffectLabel(effect), effect.SourceType, effect.SourceText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var branch in model.Branches.OrderBy(item => item.SortOrder))
|
||||||
|
{
|
||||||
|
var scope = string.IsNullOrWhiteSpace(branch.ConditionText) ? "Condition" : branch.ConditionText.Trim();
|
||||||
|
foreach (var effect in branch.Effects)
|
||||||
|
{
|
||||||
|
if (ShouldIncludeEffectMetadata(effect))
|
||||||
|
{
|
||||||
|
rows.Add((scope, GetEffectLabel(effect), effect.SourceType, effect.SourceText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldIncludeEffectMetadata(CriticalEffectEditorModel effect) =>
|
||||||
|
!string.IsNullOrWhiteSpace(effect.SourceText) ||
|
||||||
|
!string.IsNullOrWhiteSpace(effect.SourceType);
|
||||||
|
|
||||||
private static IReadOnlyList<CriticalEffectLookupResponse> BuildPreviewEffects(IEnumerable<CriticalEffectEditorModel> effects) =>
|
private static IReadOnlyList<CriticalEffectLookupResponse> BuildPreviewEffects(IEnumerable<CriticalEffectEditorModel> effects) =>
|
||||||
effects.Select(CreatePreviewEffect).ToList();
|
effects.Select(CreatePreviewEffect).ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -843,6 +843,101 @@ textarea {
|
|||||||
color: #6b4c29;
|
color: #6b4c29;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced-hint {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced {
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid rgba(127, 96, 55, 0.14);
|
||||||
|
background: rgba(247, 239, 225, 0.42);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
color: #5b4327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced-summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced-meta {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: #7b6243;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-advanced-body {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
padding: 0 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-grid dt {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: #8a7355;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-grid dd {
|
||||||
|
margin: 0.2rem 0 0;
|
||||||
|
color: #4f3c25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-block {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.85rem 0.95rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(92, 68, 41, 0.08);
|
||||||
|
border: 1px solid rgba(92, 68, 41, 0.12);
|
||||||
|
color: #3f311f;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.85rem;
|
||||||
|
padding: 0.75rem 0.85rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.65);
|
||||||
|
border: 1px solid rgba(127, 96, 55, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-values {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.25rem;
|
||||||
|
justify-items: end;
|
||||||
|
color: #5d4429;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-editor-effect-grid {
|
.critical-editor-effect-grid {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
}
|
}
|
||||||
@@ -887,8 +982,15 @@ textarea {
|
|||||||
|
|
||||||
.critical-editor-chip-card,
|
.critical-editor-chip-card,
|
||||||
.critical-editor-card-header,
|
.critical-editor-card-header,
|
||||||
.critical-editor-section-header {
|
.critical-editor-section-header,
|
||||||
|
.critical-editor-advanced-summary,
|
||||||
|
.critical-editor-diagnostic-item {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-editor-diagnostic-values {
|
||||||
|
justify-items: start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user