Remove chapter-level POV character

Character assignment now lives on beats only, per batch-assign. Drops
Chapter.PovCharacterId (entity, contracts, service, agent/import/MCP
tools, web UI) and the backing column via migration.
This commit is contained in:
James Wampler
2026-08-15 20:44:10 -07:00
parent df10b1f99b
commit 7d8dd0c4fd
13 changed files with 973 additions and 117 deletions
+4 -21
View File
@@ -11,8 +11,6 @@ public record ChapterSummaryResponse(
int Number,
string Title,
string? Summary,
Guid? PovCharacterId,
string? PovCharacterName,
string? Setting,
DraftStatus Status,
int? TargetWordCount,
@@ -27,8 +25,6 @@ public record ChapterResponse(
int Number,
string Title,
string? Summary,
Guid? PovCharacterId,
string? PovCharacterName,
string? Setting,
string? Notes,
DraftStatus Status,
@@ -43,7 +39,6 @@ public record CreateChapterRequest(
string Title,
int? Number = null,
string? Summary = null,
Guid? PovCharacterId = null,
string? Setting = null,
string? Notes = null,
DraftStatus Status = DraftStatus.Planned,
@@ -57,11 +52,7 @@ public class CreateChapterRequestValidator : IModelValidator<CreateChapterReques
{
var result = new ValidationResult();
if (string.IsNullOrWhiteSpace(model.Title))
result.AddError("Title", "'Title' must not be empty.");
else if (model.Title.Length > 200)
result.AddError("Title", "'Title' must be 200 characters or fewer.");
result.AddRequiredTextErrors("Title", "Title", model.Title, 200);
ChapterValidation.OptionalFields(model.Number, model.Summary, model.Setting, model.Notes, model.TargetWordCount, model.Prose, model.Tags, result);
return result;
@@ -72,7 +63,6 @@ public record UpdateChapterRequest(
string? Title = null,
int? Number = null,
string? Summary = null,
Guid? PovCharacterId = null,
string? Setting = null,
string? Notes = null,
DraftStatus? Status = null,
@@ -86,14 +76,7 @@ public class UpdateChapterRequestValidator : IModelValidator<UpdateChapterReques
{
var result = new ValidationResult();
if (model.Title is not null)
{
if (model.Title.Length == 0)
result.AddError("Title", "'Title' can not be cleared — a chapter always needs one.");
else if (model.Title.Length > 200)
result.AddError("Title", "'Title' must be 200 characters or fewer.");
}
result.AddUnclearableTextErrors("Title", "Title", model.Title, "a chapter", 200);
ChapterValidation.OptionalFields(model.Number, model.Summary, model.Setting, model.Notes, model.TargetWordCount, model.Prose, model.Tags, result);
return result;
@@ -133,7 +116,7 @@ public static class ChapterMapping
{
public static ChapterResponse ToResponse(this Chapter c) => new(
c.Id, c.ProjectId, c.Number, c.Title, c.Summary,
c.PovCharacterId, c.PovCharacter?.Name, c.Setting, c.Notes,
c.Setting, c.Notes,
c.Status, c.TargetWordCount,
[.. c.Beats.OrderBy(b => b.SortOrder).Select(b => b.ToResponse())],
c.Prose, c.WordCount,
@@ -142,7 +125,7 @@ public static class ChapterMapping
public static ChapterSummaryResponse ToSummaryResponse(this Chapter c) => new(
c.Id, c.ProjectId, c.Number, c.Title, c.Summary,
c.PovCharacterId, c.PovCharacter?.Name, c.Setting, c.Status, c.TargetWordCount,
c.Setting, c.Status, c.TargetWordCount,
c.Beats.Count, c.WordCount,
[.. c.Tags.OrderBy(t => t.Name).Select(t => t.ToResponse())],
c.UpdatedAt);