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 screenshotsBuild 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 checkZero 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
| Area | Rule |
|---|---|
| List endpoints | Always paged, with paging and ordering done in the database (never ToList() then Skip/Take) |
| SQLite time ordering | Use GetPagedByDateAsync / CountByAsync / PruneAsync, or project the sort key and order in memory |
| Frontend effects | Never call setState synchronously inside useEffect — express it as derived state |
| Component exports | Component files export components only; helpers go to utils/ (otherwise Fast Refresh breaks) |
| Localization | Add both languages together (backend Locales/{zh,en}, frontend i18n/locales/{zh,en}) |
| Async iterators | Cancellation token parameters need [EnumeratorCancellation], otherwise cancellation is ignored (CS8425) |
| Background work | Submit to IBackgroundTaskCoordinator rather than fire-and-forget Task.Run |
| This site | Keep both language versions in sync and click through pages locally after editing |
Release flow
- Bump the version in two places:
shell/hetu-desktop/src-tauri/tauri.conf.jsonandsrc/Hetu.Api/Hetu.Api.csproj(the About page reads the latter). - Merge the PR into
main. - Tag to trigger the release build:
pwsh ./scripts/tag-release.ps1 -Version 0.4.0CI 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 | configCurrent 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
stdioonly; full-text note search usesLIKE(FTS5 /tsvectoris planned).