Skip to content

Commit f2791b7

Browse files
authored
Merge pull request #754 from Euterpe-org/dev
Release 2.0.0
2 parents 6198e41 + 6fb0fa7 commit f2791b7

850 files changed

Lines changed: 39548 additions & 9056 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Euterpe
2+
3+
Avalonia desktop app. Projects under `src/`:
4+
5+
- `Euterpe` — the app: one `Features/<Name>/` folder per feature, holding only views, viewmodels and view-specific code (converters etc.); `Shell/` for app-level windows.
6+
- `Euterpe.Abstractions` — service interfaces.
7+
- `Euterpe.Contracts` — wire contracts: any request/response DTO serialized to or from an external API belongs here.
8+
- `Euterpe.Controls` — reusable Avalonia controls and the app theme. `Themes/EuterpeTheme.axaml` is generated at build time — edit the per-control/per-style files, never the generated file.
9+
- `Euterpe.Core` — service implementations under `Services/`.
10+
- `Euterpe.Generators` — Roslyn incremental source generators, driven by the marker attributes in `Euterpe.Shared`.
11+
- `Euterpe.Localization` — localized `.resx` string tables: `XAML` for view text, `Interaction` for code-side messages.
12+
- `Euterpe.Models` — app-internal models, DTOs, enums and records (types that cross the network go in `Euterpe.Contracts` instead).
13+
- `Euterpe.Shared` — dependency-free utilities referenced across projects: attributes, collections, extensions, threading helpers, constants.
14+
- `Euterpe.Tasks` — custom MSBuild build tasks (e.g. generating `EuterpeTheme.axaml`).
15+
- `Euterpe.Updater` — standalone console app that applies a downloaded update after the main app exits.
16+
17+
Tests under `tests/` (TUnit):
18+
19+
- `Euterpe.Generators.Tests` — source generator tests, snapshot-verified with Verify (`snapshots/`).
20+
- `Euterpe.Headless.Tests` — anything that needs running Avalonia UI (controls, views, bindings, input, theming) on Avalonia.Headless.
21+
- `Euterpe.Tests` — plain unit tests; folders mirror the source projects (`App/` = `src/Euterpe`, plus `Core/`, `Models/`, `Shared/`, `Contracts/`).
22+
23+
## Workflow
24+
25+
- Never push to a remote (`jj git push` / `git push`) — commit locally at most; the user reviews and pushes themselves.
26+
27+
## Rules
28+
29+
- C#: @.claude/rules/csharp.md
30+
- Tests: @.claude/rules/tests.md
31+
- XAML: @.claude/rules/xaml.md

.claude/rules/csharp.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# C# Rules
2+
3+
## Design
4+
5+
- Design the change, don't just complete the feature: a working implementation that leaves tech debt is not acceptable. If every viable approach leaves debt, the module itself is due for a redesign — surface that instead of bolting on.
6+
- Build along the known extension axes; this app already paid for assuming a single game (the `[PerGame]`/game-scope rework when multi-game support arrived). Ask "what varies when the next game/variant arrives" before hardcoding, and keep game-specific state and services game-scoped.
7+
8+
## Language & APIs
9+
10+
- Use the newest C# syntax and BCL APIs the repo's `LangVersion`/TFM allows (`field` keyword, `extension` members, collection expressions, span/`IFormatProvider` overloads) — never an older equivalent out of habit; the newer API is usually the better-optimized one.
11+
- When several APIs solve the same problem, pick the best-performing one for the concrete situation (allocations, lookup cost, parse overloads) — deliberately, not first-that-works.
12+
13+
## Naming & comments
14+
15+
- Method and variable names must be self-explanatory; if a name needs a comment to be understood, rename it (e.g. `PlayingFolderName`, not `CurrentlyPlaying` + a comment).
16+
- When a name is hard to settle, mirror an existing name in this project first; with no precedent, follow the general .NET/community convention for that concept (how the BCL or well-known libraries name it).
17+
- No narration comments: never comment what the code does, where it came from, or why a change is correct.
18+
- The only acceptable comment is a single short line stating a constraint the code cannot show (threading requirements, external library quirks). No multi-line essays.
19+
- No XML doc comments on members whose name already says everything.
20+
21+
## Async
22+
23+
- Never wrap an async call chain in `Task.Run`. If the chain has real awaits (file I/O, network), it does not block the caller. `Task.Run` is only justified to push genuinely synchronous blocking work (e.g. synchronous audio decode) off the UI thread.
24+
- `ConfigureAwait(false)` on every await outside UI-bound continuations; use `Dispatcher.UIThread` explicitly when touching UI-bound state afterwards.
25+
26+
## Code organization
27+
28+
- Where a type belongs is defined by the project map in CLAUDE.md.
29+
- Values derivable from a model are computed properties on the model itself (see `ChartDto.SizeDisplay`, `BpmDisplay`, `DifficultyBadges`), not static helper classes or converter wrappers.
30+
- Large types split into `Name.Topic.cs` partial files.
31+
- Service layout: the suffix-less `<Service>.cs` holds only the public interface implementation. Everything else lives in partials — `<Service>.Private.cs` when the helpers are few, `<Service>.<Topic>.cs` (`.Core`, `.Import`, ...) when there is enough to split by topic.
32+
- Private helper types are nested inside the owning partial class file, not separate top-level types.
33+
34+
## Services & notifications
35+
36+
- Toast notifications live in core services, not viewmodels. Worker methods return results; the public method notifies per granularity: single operation = per-item toast, bulk operation = one summary toast. Never add a notification-suppress flag.
37+
- Bulk methods return a count so an explicit UI action can toast "nothing to do" (silent no-op stays correct for automatic startup paths).
38+
- No redundant wrapper services or pass-through methods: put the method on the existing domain service and make the real method public.
39+
- File/IO primitives go through `IFileSystemService` (`Try*` pattern); never raw `Directory`/`File` calls with ad-hoc try/catch in services.
40+
41+
## Localization
42+
43+
- During development add the neutral `.resx` plus `zh-Hans`/`zh-Hant` only — those are the locales the user can read and test with, and keys may still churn while the feature settles. The remaining locales get batch-translated once the feature is final.
44+
- Pick the table by where the text is shown, not by who assigns it: `XAML` = presentation-surface text (labels, titles, descriptions, button captions, and inline status/progress shown inside a page/panel — even when set from a viewmodel or core service, e.g. `MelonLoader_State_Downloading`, `Setup_Step_Failed`, `Setup_Progress_*`); `Interaction` = transient interaction surfaces (message boxes, toast notifications, OS file/folder pickers). So "code-side messages in `Interaction`" below means dialog/notification text, not every code-set string; a core service reads an `XAML` value via `XAML.Key` (Core imports only `static Interaction`, so add `global using Euterpe.Localization;` there).
45+
- Keys are `Name_Action_State` segments, scoped by where the text belongs:
46+
- Page/panel-scoped view text: `<PanelName>_<Element>` (`ChartManage_SortBy`, `Setting_Title_Language`).
47+
- Self-identifying concepts, especially enum members: `<TypeName>_<Member>` (`ChartDifficulty_Easy`, `ChartSortField_MapCount`, `ModFilterType_All`) — never borrow another feature's key because the value happens to match today.
48+
- Code-side messages in `Interaction`: `<Kind>_Content_<Domain>_<Action>_<State>` (`Notification_Content_Chart_UpdateAll_Success`, `MessageBox_Content_SetPathEnvironment_Windows`).
49+
- Keep `.resx` data entries alphabetically ordered — merge new keys into place, never append at the end.
50+
51+
## Misc
52+
53+
- Deduplicate shared one-liners into a single home (e.g. `ChartFiles.MapName`), even tiny ones.
54+
- Before writing anything by hand, check the packages already referenced (`Directory.Packages.props`) for an existing facility — especially the easily-forgotten ones: CommunityToolkit.Mvvm's `[NotifyPropertyChangedFor]` for dependent properties instead of manually raising `OnPropertyChanged`, R3 `Observable`s for event wiring/composition instead of manual event handlers.
55+
- Prefer an existing library over hand-rolling, but verify its actual behavior (decompile if needed) before adopting or rejecting it.
56+
- Logging via ZLogger interpolation (`Logger.ZLogInformation($"...")`).
57+
- DI: `public required T Name { get; init; }` properties inside `#region Injections`.
58+
- `.cs` files end with exactly one trailing newline (`insert_final_newline` applies to `[*.cs]` only).

.claude/rules/tests.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Test Rules
2+
3+
All C# rules apply to test code too; the rules below are test-specific.
4+
5+
## Placement
6+
7+
- Plain unit test → `tests/Euterpe.Tests`, at the mirrored path of the type under test (`App/` = `src/Euterpe`, plus `Core/`, `Models/`, `Shared/`, `Contracts/`).
8+
- Anything needing live Avalonia UI (control, view, binding, input, theme) → `tests/Euterpe.Headless.Tests`; inherit `HeadlessTest` and touch UI only inside `RunOnUI`.
9+
- Source generator test → `tests/Euterpe.Generators.Tests`, snapshot-verified with Verify (`snapshots/`).
10+
11+
## Conventions
12+
13+
- TUnit: `[Test]` methods named `Method_Scenario_Expectation`, assertions via `await Assert.That(...)`; Verify.TUnit for snapshot assertions.
14+
- TUnit releases fast and its API surface is large (`[Arguments]`, `[MethodDataSource]`, `[ClassDataSource]`, hooks, ...) — don't write TUnit patterns from memory: fetch the docs (https://tunit.dev) to confirm the current idiom for the situation and check it against the version pinned in `Directory.Packages.props`.
15+
- One `sealed class <Type>Test` per type under test, annotated `[TestSubject(typeof(T))]` and a `[Category]`.
16+
- A large test class becomes `partial`, split per tested method into `<Type>Test.<Method>.cs` in a folder named after the type (mirrors the `Name.Topic.cs` source convention).
17+
- Mocks via TUnit.Mocks: `IFoo.Mock()` for interfaces, `Mock.Logger<T>()` for loggers, TUnit.Mocks.Http for HTTP. Build the subject with a `Create<Service>()` helper whose optional parameters default to mocks.
18+
- Layer invariants (visibility, sealed, namespaces) are enforced by `Architecture/ArchitectureTests.cs` (NetArchTest) — extend it when adding a project or layer rule.
19+
20+
## Running
21+
22+
- Test projects are Microsoft.Testing.Platform executables: `dotnet run --project tests/Euterpe.Tests`; filter with `--treenode-filter "/*/*/<ClassName>/*"`, not `--filter`.

.claude/rules/xaml.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# XAML Rules
2+
3+
## Formatting
4+
5+
- XamlStyler formatting: one attribute per line, attributes alphabetically ordered, two-space comment padding (`<!-- text -->`).
6+
- Files are UTF-8 without BOM, LF line endings, no trailing newline (`insert_final_newline = false` for non-`.cs` files).
7+
8+
## Bindings
9+
10+
- Always use compiled bindings: `x:DataType` on the root element and on every `DataTemplate`. The app is `PublishAot`, so reflection bindings break AOT/trimming — never reach for `{ReflectionBinding}` or `x:CompileBindings="False"` to escape a binding that won't compile.
11+
- When a reusable control's theme `ItemTemplate` is fed item types it can't reference (the control sits below the app), it still needs an `x:DataType` to compile: extract the minimal display contract as an interface in `Euterpe.Controls.Models` (e.g. `IEnumOption { LocalizedString Display { get; } }`), have the app type implement it, and `x:DataType` the interface — do not move the whole app/VM type down into `Euterpe.Controls`, and do not fall back to reflection.
12+
- Bind directly to model computed properties (`{Binding SizeDisplay}`); do not add a converter for anything derivable from the model — add a property to the model instead.
13+
- Converters that encode genuine view logic (icons, brushes, multi-input visual state) live in `FuncValueConverters` as static `FuncValueConverter`/`FuncMultiValueConverter` properties.
14+
15+
## Localization
16+
17+
- All user-facing text via `{Localize {x:Static loc:XAMLLiteral.Key}}` — never hardcoded strings.
18+
19+
## Styling
20+
21+
- Prefer Semi theme tokens (`{DynamicResource SemiBlue6}`, `SemiColorText0`, `SecondaryCardColor`, ...) over hardcoded hex. Alpha overlay masks (`#40000000`-style) are acceptable when no token fits.
22+
- A local property value outranks a style setter: when a pseudo-class (`:pointerover`) must change a property, define both the idle and the pseudo-class value as style setters.
23+
- Styles reused across views live in `Euterpe.Controls/Themes/ControlStyles/` (one file per control type, e.g. `ButtonStyles.axaml`); any `.axaml` there is picked up by the theme generation automatically — no manual include.
24+
- A page/panel's own styles stay inline only while small; once they dominate the view file and bury the actual content, extract them to `ControlStyles/Pages/` or `ControlStyles/Panels/` as `<Name>Styles.axaml`.
25+
- Comments: short section labels or one-line constraint notes only.
26+
27+
## Controls
28+
29+
- Need a new control? Source it in this order: Ursa first, then a mature community package, and only write our own (in `Euterpe.Controls`) when neither fits.
30+
- Before adopting a community package, vet it: maintenance activity, Avalonia version compatibility, AOT/trimming friendliness — and verify its actual behavior per the C# library rule.

0 commit comments

Comments
 (0)