Beats, tags, locations, open questions, and character/arc-stage
responses that reference a chapter now carry a ChapterLabel/DisplayNumber
alongside the raw Number, computed via the new
ChapterDisplayNumberLookup. Front/back matter chips show their title;
body chapters show "Chapter N: Title".
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.
Record create/update/delete events across novel content (chapters, beats,
characters, arc stages, tags, locations, questions) into an append-only
ActivityEvent log, aggregate by UTC day, and surface as a heatmap on the
novels list and each novel's dashboard. Backfills history from existing
CreatedAt timestamps on first boot after the migration.
Collapse Want/Need into Motivation and Internal/External Conflict into
Conflict, drop Arc summary field, and move tags below open questions on
the character page. Gate Pronouns display behind new UiSettings:ShowPronouns
config (default off).
Chapters now carry many Locations (new Tags-style entity with cross-referencing)
instead of a single free-text Setting field, with a Locations tab on the novel
for browsing them and seeing every chapter set at each one. Also surfaces the
distinct characters appearing in a chapter's beats, linked, under the beat/word
count on the outline tab.
Renames the domain concept from Project to Novel throughout the backend
(entities, DTOs, services, endpoints, ProjectAccessService/Permission,
ProjectId foreign keys), MCP server (tool names and routes), and the
React/Vite frontend (types, hooks, routes, components). Adds a new EF
Core migration (RenameProjectToNovel) using RenameTable/RenameColumn to
preserve existing data instead of dropping/recreating tables. Updates
CLAUDE.md's structure section to reference Novels/ instead of Projects/.
Beat create/update/delete/reorder/assign/move now touch the parent
chapter's UpdatedAt so the dashboard's recency sort reflects beat edits,
not just chapter-level prose/tag changes.
Arc stages now group the beats that establish or pay off that stage of a
character's arc (many-to-many via ArcStageBeats), and carry a Result field
(renamed from Description) describing what the stage results in for the
character. Assigning a beat to a stage moves it out of any other stage of
the same character. New endpoint POST /api/arc-stages/{id}/beats, MCP tool
set_arc_stage_beats, and frontend grouping UI in CharacterArc/CharacterBeats.
Also records a relationship's reciprocal type so both characters' dossiers
show the correct direction (e.g. "sister" / "brother") instead of mirroring
the same label.
- 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.
ProjectResponse now carries OwnerId and a server-resolved MyRole so
Editors/Reviewers see read-only fields and no delete/grant-management
affordances instead of only finding out via a 403 after the fact.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuBH9QSv66DPXSSBERmPs6
Introduces accounts (ASP.NET Identity + cookie auth), four global
roles (Admin/Writer/Editor/Reviewer), per-novel ownership and grants
via ProjectMember, and a service-API-key principal for the MCP server
and background import jobs. Enforcement lives in the application
services (not endpoint filters) so the embedded agent and MCP tools,
which call the same services directly, can't bypass it. Web client
gets a login page, session-aware routing, and a People section for
managing per-novel access.
Also includes prior in-flight changes from this branch (CLAUDE.md
compliance pass, dev-deploy docker-compose setup) that were
uncommitted when this feature work started.
Lets a writer select several beats and add one character to all of them
at once, without disturbing each beat's existing characters. Surfaced
through the REST API, the embedded agent, and the MCP server per the
project's rule that all three share the same service methods.
Genres seed on boot, are editable per project, and gate agent config
errors more gracefully. ConfirmModal replaces ad-hoc confirm prompts
across chapters, tags, and characters pages.
Drop the Scene entity/grouping in favor of chapters carrying prose directly
and beats belonging to many characters. Add markdown editor + character
multi-select components to the web client. Remove all XML doc and inline
comments across the touched C#/TS/CSS files in favor of self-documenting
names, and record that convention in CLAUDE.md. Add .mcp.json (local MCP
server config, no secrets) and ignore .idea/.
Dashboard replaces the tabbed overview: Outline + Characters cards
(recent-first), gated by a new Project.Phase (Brainstorming ->
Outlining -> Writing -> Editing -> Complete). Brainstorming shows just
a notes editor; existing projects backfill to Outlining. Old brief
editor moves to a Settings route.
Beat table now renders as plain readable text, turning into editable
fields only for the clicked row. Fixed horizontal overflow on the
chapters list and beat table caused by unconstrained CSS grid/table
tracks sizing to their widest unwrapped child. Widened the project
layout's max width.
A missing record isn't exceptional — services now return null (logged
at Info) instead of throwing, and endpoints map null to 404. Agent and
import toolsets route not-found through their existing OrNotFound
result pattern rather than a caught exception.
Services now return entities; endpoints (and the agent toolsets) map to
*Response records instead of services building wire DTOs themselves.
Also brings in the outline-import agent, MCP tool, ledger and web dialog
that were already in progress on disk.
Not-found lookups return null/false instead of throwing NotFoundException
across all services — a missing row is expected control flow, not an
exceptional condition. NotFoundException stays for embedded precondition
checks inside mutations (missing parent, invalid foreign reference).
Guard (copied from mic-check) enforces required arguments at the top of
every service method. A ported IModelValidator<T> framework validates
every request DTO at the API layer via a new ValidationEndpointFilter,
returning a 400 with field-level messages; services re-run the same
validator and throw for direct callers that bypass the API.
Endpoints translate null/false into 404 via a new ToApiResult() helper.
The agent toolset boundary translates the same nullable/bool results
into the tool-error text the model already expected.
Information at endpoint and service-method boundaries, Debug in deeper
helpers, Warning before expected/recoverable failures (not-found,
validation, agent tool errors), Error on caught exceptions. Serilog
wraps the exception handler so request-completion logs report the
resolved status code rather than the raw exception. Never logs prose
bodies or the Anthropic API key.
The character page gains three sections under the dossier: the arc as an
editable ordered table with each stage pinnable to a chapter, every beat the
character appears in across the book (each row linking into that chapter's
outline), and the character's open questions. The sidebar groups main
characters above supporting ones, and both the sheet and the add dialog let you
set importance.
The arc section shows for main characters, and also for supporting ones that
already have stages — demoting someone should not hide work they thought they
had lost.
The outline page gains a notes section and an open-questions section at the
bottom. Beat rows are now anchored so the character page can link straight to a
row. Raising a question from either page attaches it to what that page is
about, and the section hides the association it is already scoped to rather
than repeating "Landfall" on every row.
Also fixes an ordering wart the browser run exposed: both CharacterRole and
CharacterImportance are stored as text, so ordering them in SQL ordered the
spelling — "Deuteragonist" beat "Protagonist" and the sidebar put the second
lead above the character the book is about. Listing now sorts after
materialising, which uses the enums' declaration order. The test for it was
checked both ways: it fails on the SQL ordering and passes on the fix.
73 tests pass, the web client builds and lints clean. Driven in a browser
end to end: resolving a question with "also add to notes" drops it off the open
list and appends the decision under the chapter's existing note, "show
resolved" brings it back with a Reopen button, and a beat link on the character
page lands on the right chapter outline at that beat's anchor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S56bfZMGe1hnhpWP4CjjNw
Three things the outline could not express before:
Main vs supporting. A new CharacterImportance sits alongside CharacterRole
rather than inside it — role is the part a character plays (protagonist,
mentor, foil), importance is how much of the book they carry, and a mentor can
be either. Characters start Supporting and get promoted. Listings put main
characters first.
Character arcs. A main character's arc is a flat ordered list of stages, the
same shape as a chapter's beats and for the same reason: an arc is a sequence
of changes, not a tree. A stage can be pinned to the chapter where it lands.
Nothing refuses an arc on a supporting character — demoting someone should not
delete their work.
Open questions. What the writer has not decided yet, hanging off a chapter
outline, a character, both, or neither. They can be resolved, reopened or
deleted, and resolving can append the decision to the notes of whatever the
question was attached to, so it lands where the writer will re-read it.
Resolved questions drop off the list unless asked for.
Also adds GET /api/characters/{id}/beats — every beat a character appears in,
in manuscript order, carrying each beat's chapter so the character page can
link straight into that chapter's outline.
Deletes are deliberately asymmetric: deleting a chapter unpins arc stages and
detaches questions rather than taking them, because a plan outlives a decision
about where the chapter break falls. Deleting a character or project does take
their arcs and questions.
All three capabilities are surfaced in the REST API, the agent toolset and the
MCP server, per the one-source-of-truth rule.
Two things worth flagging in the migration: EF's generated default for the new
Importance column was an empty string, which does not parse back to a
CharacterImportance and would have faulted every read of an existing dossier —
it now defaults to Supporting, verified by migrating a database seeded on the
old schema and reading the row back through the API. And the earlier migrations
were renamed to the namespace EF derives from the output folder, so future
`migrations add` runs stop drifting.
72 tests pass (28 new). The endpoints were also exercised over curl end to end:
arc stages resolving their chapter, a character's beats across chapters, and a
question attached to both a chapter and a character resolving into both sets of
notes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S56bfZMGe1hnhpWP4CjjNw
The layered split into Domain/Application/Infrastructure/Api was forcing
organisation by layer: adding one capability meant touching four projects and
four folders that each held a slice of it. Those four projects are now one
feature-organised Novelly.Api, where each folder — Projects, Characters,
Chapters, Beats, Scenes, Tags, Agent — holds its entity, DTOs, service and
endpoints together. Common/ holds what genuinely crosses features (the patch
semantics, the two exception types, DraftStatus) and Data/ holds the DbContext
and migrations.
Six .NET projects become five: the three layer projects are gone, and
Novelly.AppHost and Novelly.ServiceDefaults are new.
- Namespaces move from NovelSoftware.* to Novelly.*, including the entity type
names recorded in the EF model snapshots. The migration ids are untouched, so
an existing novel.db still migrates cleanly — verified against a fresh file.
- Aspire orchestration mirrors the mic-check setup: the AppHost starts the API
on :5080 and the Vite dev server on :5173, and the API picks up OpenTelemetry,
health checks and service discovery from ServiceDefaults. /health and /alive
now answer in development.
- A Husky pre-push hook runs scripts/ci/prepush.sh: build, test, then a web
build. The scripts are plain bash so CI can run the same steps.
- The MCP server's env var is now NOVELLY_API_URL.
Verified beyond the build: 44 tests pass, the web client builds, the API was
exercised over curl (project/chapter/beat/tag round trip, tag cross-reference,
503 on the agent without a key while conversation listing still returns 200),
the MCP server was driven over stdio JSON-RPC (26 tools, errors still surface
the API's own message rather than being flattened), and the AppHost was run to
confirm both resources come up and Vite proxies /api through to the API.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S56bfZMGe1hnhpWP4CjjNw