Add character aliases/identity links, move-beats, keyboard help overlay

- Characters can carry aliases and be linked as the same underlying
  person (canonical SameCharacterAsId, optional reveal chapter/note),
  surfaced through the API, MCP tools, agent toolset, and web UI.
- Characters page redesigned as a filterable/sortable table (name+
  aliases, role, importance, occupation, tags) instead of a sidebar
  list, to stay usable as the cast grows.
- Beats can be moved between chapters (BeatService.MoveAsync + MCP/
  agent tool + endpoint).
- Add a keyboard-shortcuts help overlay (HelpButton/HelpOverlayContext)
  wired into the project layout.
- CLAUDE.md: require every frontend component to carry a unique id
  attribute; apply it to CharacterMultiSelect and MarkdownEditor.
This commit is contained in:
James Wampler
2026-08-17 17:26:50 -07:00
parent b4f4b35e3c
commit f124b9b4bb
32 changed files with 3002 additions and 356 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Novelly.Api.Data.Migrations
{
/// <inheritdoc />
public partial class AddCharacterAliasesAndIdentityLinks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Aliases",
table: "Characters",
type: "TEXT",
nullable: false,
defaultValue: "[]");
migrationBuilder.AddColumn<string>(
name: "IdentityNote",
table: "Characters",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "RevealedInChapterId",
table: "Characters",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "SameCharacterAsId",
table: "Characters",
type: "TEXT",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Characters_RevealedInChapterId",
table: "Characters",
column: "RevealedInChapterId");
migrationBuilder.CreateIndex(
name: "IX_Characters_SameCharacterAsId",
table: "Characters",
column: "SameCharacterAsId");
migrationBuilder.AddForeignKey(
name: "FK_Characters_Chapters_RevealedInChapterId",
table: "Characters",
column: "RevealedInChapterId",
principalTable: "Chapters",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Characters_Characters_SameCharacterAsId",
table: "Characters",
column: "SameCharacterAsId",
principalTable: "Characters",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Characters_Chapters_RevealedInChapterId",
table: "Characters");
migrationBuilder.DropForeignKey(
name: "FK_Characters_Characters_SameCharacterAsId",
table: "Characters");
migrationBuilder.DropIndex(
name: "IX_Characters_RevealedInChapterId",
table: "Characters");
migrationBuilder.DropIndex(
name: "IX_Characters_SameCharacterAsId",
table: "Characters");
migrationBuilder.DropColumn(
name: "Aliases",
table: "Characters");
migrationBuilder.DropColumn(
name: "IdentityNote",
table: "Characters");
migrationBuilder.DropColumn(
name: "RevealedInChapterId",
table: "Characters");
migrationBuilder.DropColumn(
name: "SameCharacterAsId",
table: "Characters");
}
}
}
@@ -299,6 +299,10 @@ namespace Novelly.Api.Data.Migrations
b.Property<string>("Age")
.HasColumnType("TEXT");
b.PrimitiveCollection<string>("Aliases")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Appearance")
.HasColumnType("TEXT");
@@ -314,6 +318,9 @@ namespace Novelly.Api.Data.Migrations
b.Property<string>("ExternalConflict")
.HasColumnType("TEXT");
b.Property<string>("IdentityNote")
.HasColumnType("TEXT");
b.Property<string>("Importance")
.IsRequired()
.HasMaxLength(32)
@@ -345,11 +352,17 @@ namespace Novelly.Api.Data.Migrations
b.Property<string>("Pronouns")
.HasColumnType("TEXT");
b.Property<Guid?>("RevealedInChapterId")
.HasColumnType("TEXT");
b.Property<string>("Role")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid?>("SameCharacterAsId")
.HasColumnType("TEXT");
b.Property<long>("UpdatedAt")
.HasColumnType("INTEGER");
@@ -363,6 +376,10 @@ namespace Novelly.Api.Data.Migrations
b.HasIndex("ProjectId");
b.HasIndex("RevealedInChapterId");
b.HasIndex("SameCharacterAsId");
b.ToTable("Characters");
});
@@ -963,7 +980,21 @@ namespace Novelly.Api.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Novelly.Api.Chapters.Chapter", "RevealedInChapter")
.WithMany()
.HasForeignKey("RevealedInChapterId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Novelly.Api.Characters.Character", "SameCharacterAs")
.WithMany("OtherIdentities")
.HasForeignKey("SameCharacterAsId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Project");
b.Navigation("RevealedInChapter");
b.Navigation("SameCharacterAs");
});
modelBuilder.Entity("Novelly.Api.Characters.CharacterArcStage", b =>
@@ -1082,6 +1113,8 @@ namespace Novelly.Api.Data.Migrations
{
b.Navigation("ArcStages");
b.Navigation("OtherIdentities");
b.Navigation("Relationships");
});