Adds SoftDelete/Trash across characters, chapters, locations, beats with a purge schedule and Trash page. Reworks the web client for keyboard-driven navigation (focus helpers, help overlay, keyboard.md doc). Moves the ChapterPage tag editor to the bottom of the page to match CharacterDetailPage.
110 lines
6.9 KiB
Markdown
110 lines
6.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
Guidance for Claude Code (claude.ai/code) in repo.
|
|
|
|
## Project
|
|
|
|
Novelly: software plan + write novel. ASP.NET Core 10, C#, TypeScript, React, .NET Aspire. Chapter outlines, character dossiers, prose drafting, embedded Claude agent, MCP server over same API.
|
|
|
|
## Structure
|
|
|
|
- `src/Novelly.Api/` — whole back end, organised by feature. One folder per feature holds
|
|
entity, DTOs, service, endpoints together: `Novels/`, `Characters/`, `Chapters/`, `Beats/`,
|
|
`Scenes/`, `Tags/`, `Agent/`. `Common/` holds what crosses features; `Data/` holds
|
|
`DbContext` + EF migrations.
|
|
- `src/Novelly.AppHost/` — .NET Aspire orchestration; run this to bring up API + web client
|
|
- `src/Novelly.ServiceDefaults/` — shared Aspire wiring: OpenTelemetry, health checks, service discovery
|
|
- `src/Novelly.Mcp/` — MCP stdio server
|
|
- `src/Novelly.Web/` — React + Vite client
|
|
- `tests/` — test suite
|
|
- `docs/` — documentation
|
|
- `scripts/ci/` — bash CI steps; `prepush.sh` = what Husky pre-push hook runs
|
|
|
|
## Best Practices
|
|
|
|
- Use latest .NET + latest supported nuget packages for that version
|
|
- Set `langVersion` latest in all csproj files; enable nullable
|
|
- Organize code by feature/area, not layer or type. New capability adds files to one feature folder
|
|
rather than row to each of entity/DTO/service/endpoint folder
|
|
- New features need unit tests covering logic much as possible
|
|
- Modified file: check missing test coverage, all tests pass
|
|
|
|
## Architecture
|
|
|
|
- React client, embedded agent, MCP server all go through same REST API. One source of truth — never let client reach past API to database.
|
|
- Agent tools + MCP tools call same application services endpoints do. New capability = new service method, then surface in all three.
|
|
|
|
## Logging
|
|
|
|
Serilog console via `AddSerilog` (not `UseSerilog` — keeps OTel provider for Aspire dashboard). `app.UseSerilogRequestLogging()` registered before `UseExceptionHandler` (outermost), else it logs raw exception status instead of handled one.
|
|
- Endpoints: Information via `RequestLoggingEndpointFilter` on each `MapGroup`. No per-lambda logging.
|
|
- Service public methods: Information at entry, ids/enums/counts as args.
|
|
- Deeper/private methods: Debug at start + end.
|
|
- Caught exceptions: `LogError(ex, ...)` with identifying values.
|
|
- Expected/recoverable (NotFound, validation, agent tool errors): Warning before throw/return.
|
|
- Structured templates only — `{ChapterId}`, never interpolation.
|
|
- Never log prose (summary/notes/content) or `ANTHROPIC_API_KEY`. Prose = length only.
|
|
|
|
# Coding
|
|
|
|
- No comments — no `///` XML doc, no `//` line comments, no `/* */` blocks, in C#, TS, or CSS. Unclear code → rename for
|
|
clarity or extract a well-named method instead of explaining it.
|
|
- Descriptive names all classes/methods. No generic: Provider, Manager, Helper
|
|
- Match formatting/style from `.editorconfig`
|
|
- Wrap lines at 220 chars, single line if fewer
|
|
- Interface implemented by single class → bottom of class file. Interface w/ multiple implementations → separate file.
|
|
- No tuples for return types. Prefer records or classes for multiple values
|
|
- Use `record` for data objects, `class` for objects with behavior. Avoid mutable state where possible.
|
|
- DTOs are records; entities are classes. DO NOT use Dto in names.
|
|
- `PATCH` requests partial: null field = leave alone, empty string = clear. Keep new update endpoints consistent with `Patch.Apply`.
|
|
- Enums cross wire as names, never ordinals
|
|
- All frontend components should have an id attribute that identifies them uniquely.
|
|
- Web client is keyboard-first: read `docs/keyboard.md` before adding any interactive UI (forms, editable rows, create flows).
|
|
|
|
## Testing
|
|
|
|
- Min 70% code coverage, target 90%. Unit tests focus end-user scenarios first.
|
|
- Don't write tests just for coverage. Call out missing coverage rather than cover stuff not valuable to end user.
|
|
- Code not cleanly unit-testable → mark `[ExcludeFromCodeCoverage]` or exclude namespace from coverage in .runsettings file
|
|
- BDD-style unit tests, end-to-end as possible. e.g. `Deleting_a_scene_leaves_its_beats_alone`
|
|
- NUnit. No FluentAssertions — assert with `Assert.That` + NUnit's constraint model:
|
|
`Assert.That(beat.SceneId, Is.Null)`, `Assert.That(listed, Has.Count.EqualTo(3))`,
|
|
`Assert.That(titles, Is.EqualTo(new[] { "First", "Second" }))`
|
|
- Grouping related asserts in `Assert.Multiple` beats chain that stops at first failure
|
|
- Expected exceptions: `Assert.That(() => service.Foo(), Throws.TypeOf<NotFoundException>())`
|
|
- Tests run against real in-memory SQLite via `TestDatabase`, not EF InMemory provider — cascade deletes + query translation must be exercised, InMemory provider fakes both
|
|
- Model calls faked at `IAgentModelClient` seam (see `ScriptedModelClient`). Never hit Anthropic API from test.
|
|
- No "Mock" in mocked object names
|
|
- No Arrange/Act/Assert comments
|
|
- All tests pass before commit
|
|
|
|
## Verifying
|
|
|
|
Build + tests passing ≠ working. Anything touching endpoint, agent loop, or MCP server — run it:
|
|
|
|
- Everything at once: `dotnet run --project src/Novelly.AppHost` — Aspire starts API on :5080 +
|
|
Vite dev server on :5173, dashboard for logs + traces
|
|
- API alone: `ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`, then exercise route with curl
|
|
- Web alone: `cd src/Novelly.Web && npm run dev` — proxies `/api` to :5080
|
|
- MCP: build it, then drive over stdio JSON-RPC (`initialize` → `notifications/initialized` → `tools/list` → `tools/call`)
|
|
|
|
Several real bugs here — SQLite refusing ORDER BY DateTimeOffset, agent's model client throwing at construction + taking read-only endpoints down with it — passed build + test suite, only showed up when app actually ran.
|
|
|
|
## Claude
|
|
|
|
- Plans = `.md` files in `docs/plans/`. Web → `docs/plans/web/`, API → `docs/plans/api/`
|
|
- Split large plans into discrete chunks — each buildable + committable independently
|
|
- Plan generated from `docs/plans/<name>.md` → save as `docs/plans/<name>_plan.md`
|
|
- Plan implemented from `docs/plans/<name>.md` → save summary as `docs/plans/<name>_output.md`
|
|
|
|
## Stack
|
|
|
|
`.gitignore` set for .NET/Visual Studio (C#, NuGet, MSBuild) plus Node. Update if stack change.
|
|
|
|
- Anthropic model id lives in `appsettings.json` under `Agent:Model`. Don't hardcode.
|
|
- API key comes from `ANTHROPIC_API_KEY` or `Agent:ApiKey` — never commit one. App must stay fully usable without key; only agent endpoints require it.
|
|
- EF migrations: `dotnet ef migrations add <Name> -p src/Novelly.Api -o Data/Migrations`. API migrates on boot.
|
|
- Outline import root lives in `appsettings.json` under `Imports:RootPath` (`Imports__RootPath` env var). When set, it's the only folder the browse/upload import endpoints and the source picker can reach; unset, those endpoints are disabled and the dialog falls back to a typed path with no sandbox. Created at boot if missing.
|
|
- `git push` runs `scripts/ci/prepush.sh` through Husky: build, test, then web build. Run `npm install`
|
|
once at repo root to install hook.
|