Add ChapterKind for front/back matter chapters

Chapters can now be marked FrontMatter/Body/BackMatter. Number stays
the manuscript sort key for every chapter; the author-facing display
number is now computed per-request as the chapter's ordinal among
Body chapters only, so a foreword or afterword no longer shifts the
numbering of the rest of the book. Surfaced through the API, agent
toolset, and import toolset.
This commit is contained in:
James Wampler
2026-08-19 17:54:20 -07:00
parent 71953220aa
commit ef5260a111
12 changed files with 1526 additions and 27 deletions
+10 -4
View File
@@ -10,6 +10,8 @@ public record ChapterSummaryResponse(
Guid Id,
Guid NovelId,
int Number,
ChapterKind Kind,
int? DisplayNumber,
string Title,
string? Summary,
IReadOnlyList<LocationResponse> Locations,
@@ -24,6 +26,8 @@ public record ChapterResponse(
Guid Id,
Guid NovelId,
int Number,
ChapterKind Kind,
int? DisplayNumber,
string Title,
string? Summary,
IReadOnlyList<LocationResponse> Locations,
@@ -39,6 +43,7 @@ public record ChapterResponse(
public record CreateChapterRequest(
string Title,
int? Number = null,
ChapterKind Kind = ChapterKind.Body,
string? Summary = null,
IReadOnlyList<string>? Locations = null,
string? Notes = null,
@@ -63,6 +68,7 @@ public class CreateChapterRequestValidator : IModelValidator<CreateChapterReques
public record UpdateChapterRequest(
string? Title = null,
int? Number = null,
ChapterKind? Kind = null,
string? Summary = null,
IReadOnlyList<string>? Locations = null,
string? Notes = null,
@@ -115,8 +121,8 @@ file static class ChapterValidation
public static class ChapterMapping
{
public static ChapterResponse ToResponse(this Chapter c) => new(
c.Id, c.NovelId, c.Number, c.Title, c.Summary,
public static ChapterResponse ToResponse(this Chapter c, int? displayNumber = null) => new(
c.Id, c.NovelId, c.Number, c.Kind, displayNumber, c.Title, c.Summary,
[.. c.Locations.OrderBy(l => l.Name).Select(l => l.ToResponse())],
c.Notes,
c.Status, c.TargetWordCount,
@@ -125,8 +131,8 @@ public static class ChapterMapping
[.. c.Tags.OrderBy(t => t.Name).Select(t => t.ToResponse())],
c.UpdatedAt);
public static ChapterSummaryResponse ToSummaryResponse(this Chapter c) => new(
c.Id, c.NovelId, c.Number, c.Title, c.Summary,
public static ChapterSummaryResponse ToSummaryResponse(this Chapter c, int? displayNumber = null) => new(
c.Id, c.NovelId, c.Number, c.Kind, displayNumber, c.Title, c.Summary,
[.. c.Locations.OrderBy(l => l.Name).Select(l => l.ToResponse())],
c.Status, c.TargetWordCount,
c.Beats.Count, c.WordCount,