Add review candidate panel headers

This commit is contained in:
Nick Beckley 2026-09-02 19:53:50 +00:00
parent 64225dde1a
commit 6263cf6b00
8 changed files with 56 additions and 20 deletions

View File

@ -941,12 +941,23 @@ static void StoryIntelligenceReviewModulePagesOmitWizardChrome()
Assert(view.Contains("<p class=\"eyebrow\">Story Intelligence</p>", StringComparison.Ordinal), $"{viewName} should use a simple module header eyebrow.");
Assert(view.Contains("StoryIntelligenceReviewCentre", StringComparison.Ordinal), $"{viewName} should return to Review Centre.");
Assert(view.Contains($">{title}</h1>", StringComparison.Ordinal), $"{viewName} should keep its focused module title.");
Assert(view.Contains("summary class=\"story-review-card-header\"", StringComparison.Ordinal), $"{viewName} should give each candidate panel a clear shared header.");
Assert(!view.Contains("StoryIntelligenceComplete", StringComparison.Ordinal), $"{viewName} should not route to the old final wizard step.");
foreach (var marker in forbidden)
{
Assert(!view.Contains(marker, StringComparison.OrdinalIgnoreCase), $"{viewName} should not render obsolete wizard chrome: {marker}.");
}
}
var stylesheet = File.ReadAllText(Path.Combine(root, "wwwroot/css/onboarding.css"));
Assert(stylesheet.Contains(".story-review-card-header", StringComparison.Ordinal), "Shared review candidate header CSS should be present.");
Assert(stylesheet.Contains("justify-content: space-between;", StringComparison.Ordinal), "Review candidate headers should align title and status consistently.");
Assert(stylesheet.Contains("border-bottom: 1px solid rgba(31, 42, 68, .1);", StringComparison.Ordinal), "Review candidate headers should be visually separated from card bodies.");
var knowledgeView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceKnowledge.cshtml"));
var relationshipsView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceRelationships.cshtml"));
Assert(knowledgeView.Contains("<strong>@candidate.KnowledgeStatement</strong>", StringComparison.Ordinal), "Knowledge review headers should foreground the knowledge item.");
Assert(relationshipsView.Contains("<strong>@candidate.CharacterAName / @candidate.CharacterBName</strong>", StringComparison.Ordinal), "Relationship review headers should foreground both participants.");
}
static void StoryIntelligenceDownstreamReviewUsesCompletedSceneAnalysis()

View File

@ -55,7 +55,7 @@
{
var candidate = assetCandidates[i];
<details class="story-character-card" open data-asset-card data-asset-existing-match="@candidate.IsExistingMatch.ToString().ToLowerInvariant()">
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.AssetName</strong>
<small>@candidate.Category · @candidate.AppearsInScenes.ToString("N0") scene@(candidate.AppearsInScenes == 1 ? string.Empty : "s")</small>

View File

@ -77,7 +77,7 @@
{
var candidate = characterCandidates[i];
<details class="story-character-card" open data-character-card>
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.CharacterName</strong>
<small>@candidate.Classification &middot; @candidate.AppearsInScenes.ToString("N0") scene@(candidate.AppearsInScenes == 1 ? string.Empty : "s")</small>

View File

@ -59,10 +59,10 @@
{
var candidate = knowledgeCandidates[i];
<details class="story-character-card" open data-knowledge-card>
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.CharacterName</strong>
<small>@candidate.KnowledgeStateName - @candidate.KnowledgeStatement</small>
<strong>@candidate.KnowledgeStatement</strong>
<small>@candidate.CharacterName - @candidate.KnowledgeStateName</small>
</span>
<em>@candidate.Confidence</em>
</summary>

View File

@ -60,7 +60,7 @@
: string.Empty;
var searchText = string.Join(" ", new[] { candidate.LocationName, candidate.ExistingLocationName, candidate.Category, candidate.ParentLocationHint, candidate.ProposedParentLocationName });
<details class="story-character-card" open data-location-card data-location-status="@status" data-location-existing-match="@candidate.IsExistingMatch.ToString().ToLowerInvariant()" data-location-search-text="@searchText">
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.LocationName</strong>
<small>@candidate.Category · @candidate.AppearsInScenes.ToString("N0") scene@(candidate.AppearsInScenes == 1 ? string.Empty : "s")</small>

View File

@ -55,7 +55,7 @@
{
var candidate = narrativeCandidates[i];
<details class="story-character-card" open>
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.ProposedTitle</strong>
<small>@candidate.CandidateType @(!string.IsNullOrWhiteSpace(candidate.Category) ? $"- {candidate.Category}" : string.Empty)</small>

View File

@ -59,10 +59,10 @@
{
var candidate = relationshipCandidates[i];
<details class="story-character-card" open data-relationship-card>
<summary>
<summary class="story-review-card-header">
<span>
<strong>@candidate.CharacterAName</strong>
<small>@candidate.RelationshipType / @candidate.CharacterBName - @candidate.AppearsInScenes.ToString("N0") scene@(candidate.AppearsInScenes == 1 ? string.Empty : "s")</small>
<strong>@candidate.CharacterAName / @candidate.CharacterBName</strong>
<small>@candidate.RelationshipType - @candidate.AppearsInScenes.ToString("N0") scene@(candidate.AppearsInScenes == 1 ? string.Empty : "s")</small>
</span>
<em>@candidate.Confidence</em>
</summary>

View File

@ -1469,35 +1469,52 @@ summary.story-review-chapter-heading {
border: 1px solid rgba(31, 42, 68, .1);
border-radius: 8px;
background: rgba(255, 255, 255, .64);
padding: .8rem;
overflow: hidden;
}
.story-character-card summary {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
gap: .65rem;
.story-review-card-header {
align-items: center;
background: rgba(47, 111, 99, .07);
border-bottom: 1px solid rgba(31, 42, 68, .1);
cursor: pointer;
display: flex;
flex-wrap: wrap;
gap: .55rem 1rem;
justify-content: space-between;
padding: .75rem .85rem;
}
.story-character-card summary span {
.story-review-card-header span {
display: grid;
gap: .1rem;
gap: .12rem;
min-width: 0;
}
.story-character-card summary small,
.story-character-card summary em {
.story-review-card-header strong {
color: var(--bs-body-color);
font-size: 1rem;
line-height: 1.25;
overflow-wrap: anywhere;
}
.story-review-card-header small,
.story-review-card-header em {
color: var(--bs-secondary-color);
font-size: .82rem;
font-style: normal;
font-weight: 700;
}
.story-review-card-header em {
color: var(--plotdirector-green, #2f6f63);
flex: 0 0 auto;
text-align: right;
}
.story-character-card__body {
display: grid;
gap: .75rem;
padding-top: .85rem;
padding: .85rem;
}
.story-character-card__body dl {
@ -2324,6 +2341,14 @@ summary.story-review-chapter-heading {
grid-template-columns: 1fr;
}
.story-review-card-header {
align-items: flex-start;
}
.story-review-card-header em {
text-align: left;
}
.story-review-centre-grid {
grid-template-columns: 1fr;
}