+ @if (SelectedCriticalTable?.Groups.Count > 0)
+ {
+
Critical roll
@@ -170,26 +174,14 @@ else
@if (criticalResult is not null)
{
-
-
@criticalResult.CriticalTableName
-
- Column: @criticalResult.Column
- Band: @criticalResult.RollBand
- Roll: @criticalResult.Roll
-
-
@criticalResult.Description
- @if (!string.IsNullOrWhiteSpace(criticalResult.AffixText))
- {
-
@criticalResult.AffixText
- }
-
+
}
- Seeded Reference Data
- The schema supports much more than what is seeded today. These are the initial tables standing up the flow.
+ Loaded Reference Data
+ Attack tables remain starter content. Critical tables below are whatever importer-managed entries are currently loaded into the app database.
@foreach (var attackTable in referenceData.AttackTables)
@@ -202,10 +194,7 @@ else
@foreach (var criticalTable in referenceData.CriticalTables)
{
-
- @criticalTable.Label
- Critical key: @criticalTable.Key, columns: @string.Join(", ", criticalTable.Columns.Select(column => column.Label))
-
+
}
@@ -234,6 +223,7 @@ else
var initialCriticalTable = referenceData.CriticalTables.FirstOrDefault();
criticalInput.CriticalType = initialCriticalTable?.Key ?? string.Empty;
criticalInput.Column = initialCriticalTable?.Columns.FirstOrDefault()?.Key ?? string.Empty;
+ criticalInput.Group = initialCriticalTable?.Groups.FirstOrDefault()?.Key ?? string.Empty;
}
private async Task RunAttackLookupAsync()
@@ -271,11 +261,11 @@ else
criticalInput.CriticalType,
criticalInput.Column,
criticalInput.Roll,
- null));
+ SelectedCriticalTable?.Groups.Count > 0 ? criticalInput.Group : null));
if (response is null)
{
- criticalError = "No seeded critical result matched that table, column, and roll.";
+ criticalError = "No loaded critical result matched that table, group, column, and roll.";
return;
}
@@ -288,6 +278,7 @@ else
var table = referenceData?.CriticalTables.FirstOrDefault(item => item.Key == criticalInput.CriticalType);
criticalInput.Column = table?.Columns.FirstOrDefault()?.Key ?? string.Empty;
+ criticalInput.Group = table?.Groups.FirstOrDefault()?.Key ?? string.Empty;
criticalResult = null;
criticalError = null;
}
@@ -304,6 +295,7 @@ else
{
public string CriticalType { get; set; } = string.Empty;
public string Column { get; set; } = string.Empty;
+ public string Group { get; set; } = string.Empty;
public int Roll { get; set; } = 72;
}
}
diff --git a/src/RolemasterDb.App/Components/Shared/CriticalLookupResultCard.razor b/src/RolemasterDb.App/Components/Shared/CriticalLookupResultCard.razor
new file mode 100644
index 0000000..14f4d6f
--- /dev/null
+++ b/src/RolemasterDb.App/Components/Shared/CriticalLookupResultCard.razor
@@ -0,0 +1,94 @@
+@using System.Text.Json
+
+
+
@Result.CriticalTableName
+
+ Table: @Result.CriticalType
+ Column: @Result.ColumnLabel
+ Role: @Result.ColumnRole
+ Band: @Result.RollBand
+ Roll: @Result.Roll
+ Status: @Result.ParseStatus
+
+
+
+
+ Family
+ @Result.CriticalFamily
+
+
+ Source
+ @Result.SourceDocument
+
+
+ Column Key
+ @Result.Column
+
+
+ Roll Range
+ @FormatRollRange(Result.RollBandMinRoll, Result.RollBandMaxRoll)
+
+ @if (!string.IsNullOrWhiteSpace(Result.Group))
+ {
+
+ Group
+ @(string.IsNullOrWhiteSpace(Result.GroupLabel) ? Result.Group : $"{Result.GroupLabel} ({Result.Group})")
+
+ }
+
+
+ @if (!string.IsNullOrWhiteSpace(Result.TableNotes))
+ {
+
@Result.TableNotes
+ }
+
+
@Result.Description
+
+ @if (!string.IsNullOrWhiteSpace(Result.AffixText))
+ {
+
+
Affix Text
+
@Result.AffixText
+
+ }
+
+
+ Raw Imported Cell
+ @Result.RawCellText
+
+
+
+ Parsed JSON
+ @FormatJson(Result.ParsedJson)
+
+
+
+@code {
+ [Parameter, EditorRequired]
+ public CriticalLookupResponse Result { get; set; } = null!;
+
+ private static string FormatRollRange(int minRoll, int? maxRoll) =>
+ maxRoll is null
+ ? $"{minRoll}+"
+ : minRoll == maxRoll
+ ? minRoll.ToString()
+ : $"{minRoll}-{maxRoll}";
+
+ private static string FormatJson(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return "{}";
+ }
+
+ try
+ {
+ using var document = JsonDocument.Parse(value);
+ return JsonSerializer.Serialize(document.RootElement, new JsonSerializerOptions { WriteIndented = true });
+ }
+ catch (JsonException)
+ {
+ return value;
+ }
+ }
+}
diff --git a/src/RolemasterDb.App/Components/Shared/CriticalTableReferenceCard.razor b/src/RolemasterDb.App/Components/Shared/CriticalTableReferenceCard.razor
new file mode 100644
index 0000000..0765313
--- /dev/null
+++ b/src/RolemasterDb.App/Components/Shared/CriticalTableReferenceCard.razor
@@ -0,0 +1,73 @@
+
+
@Table.Label
+
+
+ Key
+ @Table.Key
+
+
+ Family
+ @Table.Family
+
+
+ Source
+ @Table.SourceDocument
+
+
+ Columns
+ @Table.Columns.Count
+
+
+ Groups
+ @Table.Groups.Count
+
+
+ Roll Bands
+ @Table.RollBands.Count
+
+
+
+ @if (!string.IsNullOrWhiteSpace(Table.Notes))
+ {
+
@Table.Notes
+ }
+
+
+ @foreach (var column in Table.Columns)
+ {
+ @column.Label (@column.Key, @column.Role, #@column.SortOrder)
+ }
+
+
+ @if (Table.Groups.Count > 0)
+ {
+
+ @foreach (var group in Table.Groups)
+ {
+ @group.Label (@group.Key, #@group.SortOrder)
+ }
+
+ }
+
+
+ Roll Bands (@Table.RollBands.Count)
+
+ @foreach (var rollBand in Table.RollBands)
+ {
+ @rollBand.Label (@FormatRollRange(rollBand.MinRoll, rollBand.MaxRoll), #@rollBand.SortOrder)
+ }
+
+
+
+
+@code {
+ [Parameter, EditorRequired]
+ public CriticalTableReference Table { get; set; } = null!;
+
+ private static string FormatRollRange(int minRoll, int? maxRoll) =>
+ maxRoll is null
+ ? $"{minRoll}+"
+ : minRoll == maxRoll
+ ? minRoll.ToString()
+ : $"{minRoll}-{maxRoll}";
+}
diff --git a/src/RolemasterDb.App/Components/_Imports.razor b/src/RolemasterDb.App/Components/_Imports.razor
index 017e577..e516b63 100644
--- a/src/RolemasterDb.App/Components/_Imports.razor
+++ b/src/RolemasterDb.App/Components/_Imports.razor
@@ -11,3 +11,4 @@
@using RolemasterDb.App
@using RolemasterDb.App.Components
@using RolemasterDb.App.Components.Layout
+@using RolemasterDb.App.Components.Shared
diff --git a/src/RolemasterDb.App/Features/LookupContracts.cs b/src/RolemasterDb.App/Features/LookupContracts.cs
index 16310c5..51c7e14 100644
--- a/src/RolemasterDb.App/Features/LookupContracts.cs
+++ b/src/RolemasterDb.App/Features/LookupContracts.cs
@@ -2,11 +2,32 @@ namespace RolemasterDb.App.Features;
public sealed record LookupOption(string Key, string Label);
+public sealed record CriticalColumnReference(
+ string Key,
+ string Label,
+ string Role,
+ int SortOrder);
+
+public sealed record CriticalGroupReference(
+ string Key,
+ string Label,
+ int SortOrder);
+
+public sealed record CriticalRollBandReference(
+ string Label,
+ int MinRoll,
+ int? MaxRoll,
+ int SortOrder);
+
public sealed record CriticalTableReference(
string Key,
string Label,
- IReadOnlyList
Columns,
- IReadOnlyList Groups);
+ string Family,
+ string SourceDocument,
+ string? Notes,
+ IReadOnlyList Columns,
+ IReadOnlyList Groups,
+ IReadOnlyList RollBands);
public sealed record LookupReferenceData(
IReadOnlyList AttackTables,
@@ -28,12 +49,23 @@ public sealed record CriticalLookupRequest(
public sealed record CriticalLookupResponse(
string CriticalType,
string CriticalTableName,
+ string CriticalFamily,
+ string SourceDocument,
+ string? TableNotes,
string? Group,
+ string? GroupLabel,
string Column,
+ string ColumnLabel,
+ string ColumnRole,
int Roll,
string RollBand,
+ int RollBandMinRoll,
+ int? RollBandMaxRoll,
+ string RawCellText,
string Description,
- string? AffixText);
+ string? AffixText,
+ string ParseStatus,
+ string ParsedJson);
public sealed record AttackLookupResponse(
string AttackTable,
diff --git a/src/RolemasterDb.App/Features/LookupService.cs b/src/RolemasterDb.App/Features/LookupService.cs
index 54405dc..a2f380d 100644
--- a/src/RolemasterDb.App/Features/LookupService.cs
+++ b/src/RolemasterDb.App/Features/LookupService.cs
@@ -26,6 +26,7 @@ public sealed class LookupService(IDbContextFactory dbConte
.AsSplitQuery()
.Include(item => item.Columns)
.Include(item => item.Groups)
+ .Include(item => item.RollBands)
.OrderBy(item => item.DisplayName)
.ToListAsync(cancellationToken);
@@ -35,8 +36,21 @@ public sealed class LookupService(IDbContextFactory dbConte
criticalTables.Select(item => new CriticalTableReference(
item.Slug,
item.DisplayName,
- item.Columns.OrderBy(column => column.SortOrder).Select(column => new LookupOption(column.ColumnKey, column.Label)).ToList(),
- item.Groups.OrderBy(group => group.SortOrder).Select(group => new LookupOption(group.GroupKey, group.Label)).ToList()))
+ item.Family,
+ item.SourceDocument,
+ item.Notes,
+ item.Columns
+ .OrderBy(column => column.SortOrder)
+ .Select(column => new CriticalColumnReference(column.ColumnKey, column.Label, column.Role, column.SortOrder))
+ .ToList(),
+ item.Groups
+ .OrderBy(group => group.SortOrder)
+ .Select(group => new CriticalGroupReference(group.GroupKey, group.Label, group.SortOrder))
+ .ToList(),
+ item.RollBands
+ .OrderBy(rollBand => rollBand.SortOrder)
+ .Select(rollBand => new CriticalRollBandReference(rollBand.Label, rollBand.MinRoll, rollBand.MaxRoll, rollBand.SortOrder))
+ .ToList()))
.ToList());
}
@@ -102,12 +116,23 @@ public sealed class LookupService(IDbContextFactory dbConte
.Select(item => new CriticalLookupResponse(
item.CriticalTable.Slug,
item.CriticalTable.DisplayName,
+ item.CriticalTable.Family,
+ item.CriticalTable.SourceDocument,
+ item.CriticalTable.Notes,
item.CriticalGroup != null ? item.CriticalGroup.GroupKey : null,
+ item.CriticalGroup != null ? item.CriticalGroup.Label : null,
item.CriticalColumn.ColumnKey,
+ item.CriticalColumn.Label,
+ item.CriticalColumn.Role,
request.Roll,
item.CriticalRollBand.Label,
+ item.CriticalRollBand.MinRoll,
+ item.CriticalRollBand.MaxRoll,
+ item.RawCellText,
item.DescriptionText,
- item.RawAffixText))
+ item.RawAffixText,
+ item.ParseStatus,
+ item.ParsedJson))
.SingleOrDefaultAsync(cancellationToken);
}
diff --git a/src/RolemasterDb.App/rolemaster.db b/src/RolemasterDb.App/rolemaster.db
index e1a6feb..8992895 100644
Binary files a/src/RolemasterDb.App/rolemaster.db and b/src/RolemasterDb.App/rolemaster.db differ
diff --git a/src/RolemasterDb.App/wwwroot/app.css b/src/RolemasterDb.App/wwwroot/app.css
index 3e3d526..69a8c87 100644
--- a/src/RolemasterDb.App/wwwroot/app.css
+++ b/src/RolemasterDb.App/wwwroot/app.css
@@ -182,6 +182,29 @@ textarea {
margin-bottom: 0.45rem;
}
+.detail-grid {
+ display: grid;
+ gap: 0.7rem;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ margin-bottom: 0.9rem;
+}
+
+.detail-item {
+ display: grid;
+ gap: 0.15rem;
+ padding: 0.65rem 0.75rem;
+ border-radius: 14px;
+ background: rgba(255, 250, 240, 0.72);
+ border: 1px solid rgba(127, 96, 55, 0.12);
+}
+
+.detail-label {
+ color: #75562f;
+ font-size: 0.76rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
.result-stats {
display: flex;
gap: 0.7rem;
@@ -224,6 +247,43 @@ textarea {
.table-list-item strong {
display: block;
+ margin-bottom: 0.6rem;
+}
+
+.chip-row {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ margin-top: 0.8rem;
+}
+
+.chip {
+ border-radius: 999px;
+ padding: 0.38rem 0.68rem;
+ background: rgba(238, 223, 193, 0.5);
+ border: 1px solid rgba(127, 96, 55, 0.14);
+ color: #5b4327;
+ font-size: 0.82rem;
+}
+
+.chip small {
+ color: var(--ink-soft);
+}
+
+.details-block {
+ margin-top: 0.85rem;
+}
+
+.details-block summary {
+ cursor: pointer;
+ color: var(--accent);
+ font-weight: 600;
+ margin-bottom: 0.6rem;
+}
+
+.stacked-copy {
+ margin: 0;
+ white-space: pre-wrap;
}
.code-block {