Development

An ASP.NET Core + React full-stack project with a Tauri desktop shell. Read AGENTS.md in the repository root before changing code.

Repository layout

src/
├── Hetu.Api/                                  controllers, SSE, background workers, Program.cs
├── Hetu.Core/                                 entities, domain services, repository interfaces
├── Hetu.Infrastructure/                       EF Core, repositories, AI providers, MCP, sqlite-vec
├── Hetu.Infrastructure.PostgresMigrations/    PostgreSQL migrations
└── Hetu.Shared/                               DTOs, enums, constants (frontend/backend contract)
frontend/src/{pages,components,hooks,stores,services,types,utils,i18n}
shell/hetu-desktop/{src,src-tauri}             Rust shell, Tauri config, installer hooks
scripts/                                       start / publish / release / smoke tests
docs/                                          this site and screenshots

Build and verify

# backend (expect zero warnings and zero errors)
dotnet build src/Hetu.Api/Hetu.Api.csproj -c Release -v q

# frontend type check and lint
cd frontend && npx eslint . && npx tsc -b

# API smoke tests (backend running)
./scripts/test-api.sh

# Rust shell
cd shell/hetu-desktop/src-tauri && cargo check
Zero warnings. No #pragma warning disable, SuppressMessage, eslint-disable or @ts-ignore. Fix nullability with guards, platform APIs with OperatingSystem.IsXxxVersionAtLeast, and dependency advisories by upgrading.

Gotchas worth knowing

AreaRule
List endpointsAlways paged, with paging and ordering done in the database (never ToList() then Skip/Take)
SQLite time orderingUse GetPagedByDateAsync / CountByAsync / PruneAsync, or project the sort key and order in memory
Frontend effectsNever call setState synchronously inside useEffect — express it as derived state
Component exportsComponent files export components only; helpers go to utils/ (otherwise Fast Refresh breaks)
LocalizationAdd both languages together (backend Locales/{zh,en}, frontend i18n/locales/{zh,en})
Async iteratorsCancellation token parameters need [EnumeratorCancellation], otherwise cancellation is ignored (CS8425)
Background workSubmit to IBackgroundTaskCoordinator rather than fire-and-forget Task.Run
This siteKeep both language versions in sync and click through pages locally after editing
Version (two files) tauri.conf.json Hetu.Api.csproj PR merged main Tag scripts/tag-release.ps1 triggers Release Build CI build fat / slim × Windows / Linux signing + manifests (fails if a channel is missing) GitHub Release installers + .sig latest*.json (primary + 3 mirrors) Client update primary → three mirror fallbacks NSIS preinstall hook clears leftover backends
Bump the version in two files, push a tag, and CI handles building, signing, manifests and publishing.

Release flow

  1. Bump the version in two places: shell/hetu-desktop/src-tauri/tauri.conf.json and src/Hetu.Api/Hetu.Api.csproj (the About page reads the latter).
  2. Merge the PR into main.
  3. Tag to trigger the release build:
pwsh ./scripts/tag-release.ps1 -Version 0.4.0

CI builds signed Windows and Linux installers for both the fat and slim channels, generates the updater manifests (latest*.json plus three mirror variants) and creates the GitHub Release. If any channel manifest cannot be generated the release fails on purpose, so a build that cannot update itself is never published.

Commit messages

<type>(<scope>): <subject>

# type : feat | fix | docs | style | refactor | perf | test | chore
# scope: api | ui | db | ai | work | desktop | config

Current status

  • No automated test project yet; the current gate is a warning-free Release build plus eslint / tsc, API smoke tests and real browser checks. Contributions of an xUnit test project are welcome.
  • Known limits: MCP is fully wired for stdio only; full-text note search uses LIKE (FTS5 / tsvector is planned).