diff --git a/BACKEND-BUILD-ENVIRONMENT.md b/BACKEND-BUILD-ENVIRONMENT.md
deleted file mode 100644
index 9ecb7e6e..00000000
--- a/BACKEND-BUILD-ENVIRONMENT.md
+++ /dev/null
@@ -1,374 +0,0 @@
-# AyaNova v8 Backend Build Environment Documentation
-
-> **Purpose**: This document captures the complete backend build process for AyaNova v8, including all deployment targets and installers.
-
-## Overview
-
-The backend build produces **6 deployment packages** from **3 projects**:
-
-### Projects
-
-| Project | Framework | Purpose |
-|---------|-----------|---------|
-| AyaNova (server) | .NET 8.0 | Main ASP.NET Core web server |
-| raven-launcher | .NET 8.0 | Windows launcher for standalone mode |
-| AyaNovaQBI | .NET Framework 4.8 | QuickBooks Desktop integration (separate build) |
-
-### Deployment Targets
-
-| Target | Description | Output |
-|--------|-------------|--------|
-| Subscription Linux x64 | Your hosted service (DigitalOcean) | `ayanova-subscription-linux-x64-server.zip` |
-| Perpetual Linux Server | Self-hosted Linux server | `ayanova-linux-x64-server.zip` |
-| Perpetual Linux Desktop | Single-user Linux desktop | `ayanova-linux-x64-desktop.zip` |
-| Windows Standalone | Self-contained single-user (includes .NET runtime + PostgreSQL) | `ayanova-windows-x64-single-setup.exe` |
-| Windows LAN | Network server (requires separate .NET + PostgreSQL) | `ayanova-windows-x64-lan-setup.exe` |
-| QuickBooks Integration | Windows desktop app | `ayanova-qbi-setup.exe` (separate build) |
-
----
-
-## Directory Structure
-
-```
-c:\data\code\
-├── raven\ # Main repository root
-│ ├── build-release.bat # Master build script
-│ ├── raven.sln # Visual Studio solution
-│ ├── server\
-│ │ └── AyaNova\ # ASP.NET Core server project
-│ │ ├── AyaNova.csproj
-│ │ ├── Program.cs
-│ │ ├── Startup.cs
-│ │ ├── wwwroot\ # Frontend files copied here
-│ │ └── resource\ # Report templates, etc.
-│ ├── docs\
-│ │ └── 8.0\
-│ │ ├── ayanova\ # Main documentation (MkDocs)
-│ │ │ └── mkdocs.yml
-│ │ └── customer\ # Customer portal documentation
-│ │ └── mkdocs.yml
-│ ├── dist\
-│ │ ├── assets\ # Shared assets (license, configs, icons)
-│ │ │ ├── LICENSE
-│ │ │ ├── license.rtf
-│ │ │ ├── linux-desktop\config.json
-│ │ │ ├── linux-server\config.json
-│ │ │ ├── lan-install-config.json
-│ │ │ └── logo.ico
-│ │ ├── install\
-│ │ │ └── windows\x64\ # Inno Setup scripts
-│ │ │ ├── standalone.iss
-│ │ │ └── lan.iss
-│ │ ├── installers\ # Build output (generated)
-│ │ ├── linux-x64\ # Linux build output (generated)
-│ │ ├── subscription-build-linux-x64\ # Subscription build output (generated)
-│ │ └── win-x64\ # Windows build output (generated)
-│ │ ├── ayanova\
-│ │ │ ├── standalone\ # Self-contained build
-│ │ │ └── framework-dependent\ # Requires .NET installed
-│ │ ├── launcher\ # Launcher build output
-│ │ └── postgres-standalone\ # Embedded PostgreSQL for standalone
-│ └── graphics\ # Icons, logos
-│
-├── raven-client\ # Frontend (separate repo/folder)
-│ └── ayanova\
-│
-├── raven-launcher\ # Launcher project
-│ ├── raven-launcher.csproj
-│ └── config.json
-│
-└── ravenqbi\ # QuickBooks integration (separate)
- ├── AyaNovaQBI\
- │ └── AyaNovaQBI.csproj
- └── install\
- └── qbi.iss
-```
-
----
-
-## Build Requirements
-
-### Software
-
-| Tool | Version | Purpose |
-|------|---------|---------|
-| .NET SDK | 8.0.x | Build ASP.NET Core projects |
-| Node.js | 12.22.9 | Build frontend (see frontend docs) |
-| Python | 3.11.x | Build MkDocs documentation |
-| MkDocs | Latest | Documentation generator |
-| Inno Setup | 6.x | Windows installer creation |
-| 7-Zip | Latest | Create Linux distribution zips |
-| Visual Studio 2022 | Latest | Build QBI project (.NET Framework 4.8) |
-
-### Paths (hardcoded in build scripts)
-
-The build scripts expect these exact paths:
-- `C:\data\code\raven\` - Main repository
-- `C:\data\code\raven-client\ayanova\` - Frontend project
-- `C:\data\code\raven-launcher\` - Launcher project
-- `C:\data\code\ravenqbi\` - QuickBooks integration
-- `C:\Program Files\7-Zip\7z.exe` - 7-Zip
-- `C:\Program Files (x86)\Inno Setup 6\ISCC.exe` - Inno Setup compiler
-
----
-
-## Build Process (build-release.bat)
-
-The master build script executes in this order:
-
-### 1. Clean Output Folders
-```batch
-rmdir c:\data\code\raven\server\AyaNova\wwwroot /s/q
-mkdir c:\data\code\raven\server\AyaNova\wwwroot
-rmdir C:\data\code\raven\dist\installers /s/q
-mkdir C:\data\code\raven\dist\installers
-```
-
-### 2. Build Documentation (MkDocs)
-```batch
-cd c:\data\code\raven\docs\8.0\ayanova
-mkdocs build
-
-cd c:\data\code\raven\docs\8.0\customer
-mkdocs build
-```
-Output goes to `wwwroot/docs` (configured in mkdocs.yml).
-
-### 3. Build Frontend (Vue.js)
-```batch
-cd c:\data\code\raven-client\ayanova
-call npm run build
-xcopy dist\* ..\raven\server\AyaNova\wwwroot\ /e
-```
-
-### 4. Build Subscription Linux x64
-```batch
-dotnet publish --property:PublishDir=C:\data\code\raven\dist\subscription-build-linux-x64\ \
- -c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=true
-```
-Key flag: `-p:SUBSCRIPTION_BUILD=true` sets a compile-time constant for subscription-specific features.
-
-Then packages to zip with config:
-```batch
-7z a ayanova-subscription-linux-x64-server.zip subscription-build-linux-x64\*
-7z a ayanova-subscription-linux-x64-server.zip assets\linux-server\config.json
-```
-
-### 5. Build Perpetual Linux x64
-```batch
-dotnet publish --property:PublishDir=C:\data\code\raven\dist\linux-x64\ \
- -c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=false
-```
-
-Creates two packages (same build, different config):
-- `ayanova-linux-x64-desktop.zip` (with desktop config)
-- `ayanova-linux-x64-server.zip` (with server config)
-
-### 6. Build Windows Standalone (Self-Contained)
-```batch
-dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\ayanova\standalone\ \
- -r win-x64 --self-contained
-```
-This includes the .NET runtime in the output (~150MB larger).
-
-### 7. Build Windows Framework-Dependent
-```batch
-dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\ayanova\framework-dependent\ \
- -r win-x64 --no-self-contained
-```
-Requires .NET 8 runtime installed on target machine.
-
-### 8. Build Launcher
-```batch
-cd C:\data\code\raven-launcher\
-dotnet publish -c Release --property:PublishDir=C:\data\code\raven\dist\win-x64\launcher\ \
- -r win-x64 --self-contained
-```
-The launcher is always self-contained (uses `PublishTrimmed` to reduce size).
-
-### 9. Create Windows Installers (Inno Setup)
-```batch
-ISCC.exe standalone.iss
-ISCC.exe lan.iss
-```
-
----
-
-## Project-Specific Notes
-
-### AyaNova Server (AyaNova.csproj)
-
-**Conditional Compilation:**
-```xml
-$(DefineConstants);SUBSCRIPTION_BUILD
-```
-Use `#if SUBSCRIPTION_BUILD` in code for subscription-only features.
-
-**Key Dependencies:**
-- Entity Framework Core 8.0 with Npgsql (PostgreSQL)
-- PuppeteerSharp for PDF report generation (downloads Chromium on first run)
-- MailKit for email
-- JWT authentication
-
-**Resource Files:**
-Report templates and other resources are copied to output:
-```xml
-
- Always
-
-```
-
-### Launcher (raven-launcher.csproj)
-
-Simple console app that:
-1. Starts embedded PostgreSQL (standalone mode only)
-2. Starts the AyaNova server
-3. Opens default browser to the login page
-4. Waits for user to close
-
-Uses `PublishTrimmed` to reduce self-contained size.
-
-### QuickBooks Integration (AyaNovaQBI.csproj)
-
-**Important:** This is a .NET Framework 4.8 Windows Forms application, NOT .NET Core.
-
-- Requires Visual Studio 2022 (not VS Code) for the Windows Forms designer
-- References `interop.QBFC14.dll` from QuickBooks SDK (must be installed)
-- Targets x86 (32-bit) because QuickBooks SDK is 32-bit only
-- Has a pre-build event that runs a timestamp utility
-
-**Why Visual Studio is required:**
-The Windows Forms designer only works in Visual Studio. The code could theoretically be edited in VS Code, but the designer is essential for the UI work.
-
----
-
-## Windows Installer Details
-
-### Standalone (standalone.iss)
-
-Includes:
-- Self-contained AyaNova server (with .NET runtime)
-- Self-contained launcher
-- Embedded PostgreSQL (`postgres-standalone` folder)
-- Empty database template
-
-Creates desktop shortcut to launcher. User clicks one icon, everything starts.
-
-**Key paths:**
-- App: `{autopf}\ayanova`
-- Data: `{commonappdata}\ayanova\database`
-- Logs: `{commonappdata}\ayanova\logs`
-
-### LAN (lan.iss)
-
-Includes:
-- Framework-dependent AyaNova server (no .NET runtime)
-- Config file for network mode
-
-Does NOT include PostgreSQL - user must install separately.
-
-Prompts user to download:
-- ASP.NET Core Runtime Hosting Bundle
-- PostgreSQL
-
----
-
-## Embedded PostgreSQL (Windows Standalone)
-
-The standalone installer includes a portable PostgreSQL in `dist\win-x64\postgres-standalone\`.
-
-This is a pre-configured PostgreSQL installation that:
-- Runs without system installation
-- Stores data in `{commonappdata}\ayanova\database`
-- Is started/stopped by the launcher
-
-**Source:** You'll need to maintain this - it's a copy of a PostgreSQL Windows binary distribution with a pre-initialized data directory.
-
----
-
-## QuickBooks Integration Build (Separate)
-
-The QBI is built separately and not included in the main build script.
-
-```batch
-cd c:\data\code\ravenqbi\AyaNovaQBI
-msbuild /p:Configuration=Release
-
-cd c:\data\code\ravenqbi\install
-"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" qbi.iss
-```
-
-Output: `c:\data\code\ravenqbi\install\output\ayanova-qbi-setup.exe`
-
----
-
-## Version Management
-
-Versions must be updated in multiple places:
-
-| File | Location |
-|------|----------|
-| `AyaNova.csproj` | `8.2.4` |
-| `standalone.iss` | `#define MyAppVersion "8.2.4"` |
-| `lan.iss` | `#define MyAppVersion "8.2.4"` |
-| `qbi.iss` | `#define MyAppVersion "8.0.2"` |
-| `package.json` (frontend) | `"version": "8.2.4"` |
-
-Consider creating a version file or script to update all at once.
-
----
-
-## Containerization Considerations
-
-### What CAN be containerized (for build):
-
-1. **Frontend build** - Already done (Dockerfile.frontend)
-2. **Documentation build** - Already done (Dockerfile.docs)
-3. **Linux server builds** - .NET SDK in Docker can cross-compile for Linux
-4. **Windows framework-dependent build** - Can be built in Docker
-
-### What CANNOT be easily containerized:
-
-1. **Windows self-contained builds** - Need Windows container or Windows host
-2. **Inno Setup installers** - Windows-only tool
-3. **QuickBooks Integration** - .NET Framework 4.8, Windows Forms, requires Visual Studio
-4. **Embedded PostgreSQL** - Windows-specific binaries
-
-### Recommendation:
-
-For a VPS-based build:
-- Use Docker/Linux for: Frontend, Docs, Linux targets
-- Keep Windows laptop for: Windows installers, QBI
-- Or: Use a Windows VM/GitHub Actions for Windows builds
-
----
-
-## Troubleshooting
-
-### "PuppeteerSharp failed to download Chromium"
-
-The server downloads Chromium on first run for PDF generation. If this fails:
-1. Check internet connectivity
-2. Check firewall/proxy settings
-3. Pre-download Chrome and set `REPORT_RENDER_BROWSER_PATH` environment variable
-
-### "SUBSCRIPTION_BUILD not defined"
-
-Ensure you're passing the flag correctly:
-```batch
-dotnet publish ... -p:SUBSCRIPTION_BUILD=true
-```
-
-### Inno Setup errors
-
-- Ensure all source paths exist
-- Check that previous build steps completed
-- Verify 7-Zip and Inno Setup are installed at expected paths
-
----
-
-## Version History
-
-| Date | Change |
-|------|--------|
-| 2026-02-01 | Initial documentation created |
diff --git a/BUILD-ENVIRONMENT.md b/BUILD-ENVIRONMENT.md
deleted file mode 100644
index ca4a76d4..00000000
--- a/BUILD-ENVIRONMENT.md
+++ /dev/null
@@ -1,271 +0,0 @@
-# AyaNova v8 Build Environment Documentation
-
-> **Purpose**: This document captures the exact build environment for AyaNova v8 as of February 2026. Use this to recreate the build environment on a new machine or in a container.
-
-## Overview
-
-AyaNova v8 consists of three main build components:
-
-1. **Frontend** - Vue.js 2 application (JavaScript, not TypeScript)
-2. **Documentation** - MkDocs with Material theme
-3. **Backend** - ASP.NET Core 8.0 server (C#)
-
-The frontend and docs are built first, then copied into the backend project's `wwwroot` folder before the backend is built and packaged for distribution.
-
----
-
-## Current Build Machine Specifications
-
-| Component | Version | Notes |
-|-----------|---------|-------|
-| Operating System | Windows 11 | ThinkPad X13 Gen 3 |
-| Node.js | v12.22.9 | **End-of-life** - do not upgrade without testing |
-| npm | (check with `npm -v`) | Comes with Node |
-| Python | 3.11.1 | For MkDocs |
-| .NET SDK | 8.0.x | For backend build |
-
----
-
-## Frontend Build Environment
-
-### Directory Structure
-
-```
-c:\data\code\
-├── raven-client\
-│ └── ayanova\ # Vue.js frontend project
-│ ├── src\ # Source code
-│ ├── public\ # Static assets
-│ ├── dist\ # Build output (generated)
-│ ├── package.json
-│ ├── vue.config.js
-│ └── buildrelease.bat
-├── raven\
-│ ├── server\
-│ │ └── AyaNova\
-│ │ └── wwwroot\ # Frontend files copied here after build
-│ └── docs\
-│ └── 8.0\
-│ └── ayanova\ # MkDocs source
-│ └── mkdocs.yml
-```
-
-### Node.js Version: Critical Information
-
-**Current version: Node.js 12.22.9**
-
-This project uses an old Node version because:
-
-1. The `fibers` package (used by sass-loader for faster Sass compilation) is a native C++ addon that stopped being maintained and doesn't compile on newer Node versions
-2. Various other dependencies have peer dependency requirements that break on Node 14+
-
-**Do not upgrade Node without first testing in an isolated environment.**
-
-### Key Dependencies
-
-From `package.json`:
-
-| Package | Version | Purpose |
-|---------|---------|---------|
-| vue | 2.6.14 | Core framework (Vue 2, end-of-life Dec 2023) |
-| vuetify | 2.6.6 | UI component library |
-| @vue/cli-service | 4.5.15 | Build tooling |
-| webpack | 4.46.0 | Module bundler |
-| sass | 1.52.3 | Sass compiler |
-| fibers | 4.0.3 | **Problematic** - native addon for Sass |
-| monaco-editor | 0.30.1 | Code editor component |
-
-### Build Process
-
-The frontend build is executed via `buildrelease.bat`:
-
-```batch
-@echo **************************************************************
-@echo ******************** BUILD CLIENT ****************************
-@echo **************************************************************
-rmdir c:\data\code\raven\server\AyaNova\wwwroot /s/q
-mkdir c:\data\code\raven\server\AyaNova\wwwroot
-cd c:\data\code\raven\docs\8.0\ayanova
-mkdocs build
-cd c:\data\code\raven-client\ayanova
-call npm run build
-xcopy c:\data\code\raven-client\ayanova\dist\* C:\data\code\raven\server\AyaNova\wwwroot\ /e
-pause
-```
-
-This does:
-1. Clears and recreates the backend's wwwroot folder
-2. Builds the MkDocs documentation (output goes directly to wwwroot/docs via mkdocs.yml config)
-3. Runs `npm run build` which calls `vue-cli-service build`
-4. Copies the built frontend from `dist/` to the backend's `wwwroot/`
-
-### Known Build Warnings (Safe to Ignore)
-
-These warnings appear during every build and do not affect the output:
-
-1. **Browserslist warning**: "caniuse-lite is outdated"
- - Harmless - just means browser compatibility database is old
-
-2. **Dart Sass division warnings**: "Using / for division is deprecated"
- - Come from Vuetify's stylesheets, not your code
- - Will continue working until Dart Sass 2.0
-
-3. **Asset size warnings**: Various files exceed 244 KiB
- - Expected with Monaco editor (ts.worker.js is 4.5MB)
- - Application still works correctly
-
-### NPM Scripts
-
-```json
-{
- "serve": "vue-cli-service serve", // Development server
- "build": "vue-cli-service build", // Production build
- "lint": "vue-cli-service lint" // Code linting
-}
-```
-
----
-
-## Documentation Build Environment
-
-### MkDocs Configuration
-
-From `mkdocs.yml`:
-
-| Setting | Value |
-|---------|-------|
-| Theme | Material |
-| Output directory | `../../../server/AyaNova/wwwroot/docs` |
-| Site URL | https://ayanova.com/docs/ |
-
-### Required Python Packages
-
-```bash
-pip install mkdocs mkdocs-material
-```
-
-The `pymdownx` extensions (highlight, superfences) come bundled with mkdocs-material.
-
-### Build Command
-
-```bash
-cd c:\data\code\raven\docs\8.0\ayanova
-mkdocs build
-```
-
-Output goes directly to the backend's wwwroot/docs folder.
-
----
-
-## Backend Build Environment
-
-### .NET Version
-
-- **Target Framework**: .NET 8.0
-- **Project Type**: ASP.NET Core
-
-### Build Tools Required
-
-- .NET 8.0 SDK
-- Visual Studio 2022 or VS Code with C# extension
-
-### Build Outputs
-
-The backend build produces multiple deployment targets:
-1. Linux hosted (DigitalOcean service)
-2. Windows networked (IIS hosted)
-3. Windows single-user (self-contained)
-4. Linux server
-5. Linux desktop
-
-Windows installers are created with Inno Setup.
-
----
-
-## Recreating the Build Environment
-
-### Option 1: Native Installation (Windows)
-
-1. Install Node.js 12.22.9 specifically:
- - Download from https://nodejs.org/dist/v12.22.9/
- - Or use nvm-windows: `nvm install 12.22.9 && nvm use 12.22.9`
-
-2. Install Python 3.11:
- - Download from python.org
- - Ensure it's in PATH
-
-3. Install MkDocs:
- ```bash
- pip install mkdocs mkdocs-material
- ```
-
-4. Install .NET 8.0 SDK:
- - Download from https://dotnet.microsoft.com/download
-
-5. Clone/checkout repository from SVN
-
-6. Install frontend dependencies:
- ```bash
- cd c:\data\code\raven-client\ayanova
- npm ci
- ```
- Note: Use `npm ci` (not `npm install`) to install exact versions from package-lock.json
-
-### Option 2: Docker (Recommended for Reproducibility)
-
-See `Dockerfile.frontend` and `Dockerfile.docs` in this repository for containerized builds that don't require any local installation except Docker.
-
----
-
-## Troubleshooting
-
-### "fibers" fails to install
-
-This native module requires:
-- Python 2.7 or 3.x
-- Visual Studio Build Tools (Windows) or build-essential (Linux)
-- Specific Node.js version compatibility
-
-If it fails, you can try removing it (it's optional):
-1. Remove `"fibers": "^4.0.3"` from package.json
-2. Remove the `fiber: require("fibers")` line from webpack.config.js
-3. Run `npm install` again
-
-The build will be slightly slower but should work.
-
-### Node version mismatch
-
-If you see errors about incompatible Node versions:
-1. Check your Node version: `node -v`
-2. It must be 12.22.9 for guaranteed compatibility
-3. Use nvm to switch versions if needed
-
-### "Module not found" errors
-
-Try:
-```bash
-rm -rf node_modules
-rm package-lock.json
-npm install
-```
-
----
-
-## Future Modernization Notes
-
-When time permits, consider:
-
-1. **Remove fibers dependency** - It's optional and causes most build issues
-2. **Upgrade to Vue 3 + Vite** - Much faster builds, better tooling, active support
-3. **Migrate to TypeScript** - Better IDE support and fewer runtime errors
-4. **Update Node to LTS** - Current LTS is Node 20.x
-
-These are significant changes that should be done incrementally with thorough testing.
-
----
-
-## Version History
-
-| Date | Change |
-|------|--------|
-| 2026-02-01 | Initial documentation created |
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..982ab7aa
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,100 @@
+# AyaNova v8 — instructions for Claude Code
+
+Loaded in every session started in this repo, and imported by the CLAUDE.md files in
+`raven-client` and `raven-test-integration`. Keep it short: detail lives in `devdocs/`.
+
+Statements marked **[unverified]** came from older notes and have not been checked against
+the code. Check one before relying on it, then fix the statement or remove the marker.
+
+## What this is
+
+AyaNova v8: commercial service management software (ayanova.com), sold since 1998,
+built and maintained by one developer, John. It is a production product. Hosted
+subscription customers and self-hosted customers on Windows and Linux run this code, and
+deployed clients call its API routes.
+
+## Repositories
+
+Separate Git repos under `C:\data\code\`, hosted on a self-hosted Gitea. The build scripts
+hardcode these paths: do not move or rename the folders.
+
+| Folder | What | Stack |
+|---|---|---|
+| `raven` | **This repo, the master.** Back-end server, customer manual, build and installer scripts | ASP.NET Core 8, C#, EF Core 8, Npgsql, PostgreSQL |
+| `raven-client` | Front end, in `ayanova/` | Vue 2.6, Vuetify 2.6, Vuex, Vue Router, plain JavaScript, Node 12.22.9 |
+| `raven-test-integration` | API integration tests against a running server | xUnit, .NET 8 |
+| `raven-launcher` | Windows standalone launcher | .NET 8, self-contained, trimmed |
+| `ravenqbi` | QuickBooks Desktop integration | .NET Framework 4.8 WinForms, x86, Visual Studio only |
+| `sockeye` | Internal licensing server, a near-copy of this codebase, **already ported to .NET 10** (tag `8.1.0`) **[unverified: folder name]** | same as `raven` |
+
+Cypress E2E tests (Cypress 9.7.0, targeting remote devtest servers) also exist
+**[unverified: which folder]**.
+
+## Layout of this repo
+
+- `server/AyaNova/` — the application, `AyaNova.csproj`. Publish this csproj directly, never `raven.sln`.
+- `docs/` — the **customer** help manual (MkDocs). Not developer documentation. Do not edit unless asked.
+- `devdocs/modernization/` and `devdocs/build/` — current developer docs. Anything else in `devdocs/` is old historical notes: ignore unless asked.
+- `dist/` — packaging assets, Inno Setup scripts, build outputs.
+- `build-release.bat` — master release build.
+
+## Current work: .NET 10 port, branch `net10-port`
+
+Move the server from .NET 8 to .NET 10 and every back-end package to its current release.
+Front end: minimal or no change.
+
+**Before any port work, read `devdocs/modernization/NET10-PORT.md`.** It holds the plan,
+the checklist and the decisions still open. Update its checklist and log in the same
+commit as the change they record.
+
+Sockeye went through the same port first. When a port question comes up, look at how
+Sockeye solved it: `git -C ..\sockeye log --oneline net6-baseline..8.1.0`.
+
+The rest of the modernization plan: `devdocs/modernization/ROADMAP.md`.
+
+## Working rules
+
+- No change to production code without John's explicit approval. Propose a plan first for anything beyond a one-line fix.
+- One concern per commit, so a regression can be found with `git log` and undone with `git revert`. The version bump is always the first commit on a release branch.
+- Do not mix refactoring, bug fixes and features. If you spot something worth refactoring during the port, add it to "Deferred" in `NET10-PORT.md` instead of doing it.
+- Dependency changes: back-end packages only, and only on `net10-port`. No front-end dependency changes (Node is pinned at 12.22.9 because of `fibers`).
+- Do not add `[ApiController]` to controllers that lack it. It changes model binding and error responses on routes deployed clients call.
+- No new features.
+- Git: keep it simple and explicit. Do not push, tag, merge or rewrite history unless asked. Tags are annotated and pushed by name.
+- Nothing is accepted blindly: show the diff and say why.
+- Secrets and customer data never go in a repo. Fixtures that contain customer data live outside the repo and are found through an environment variable.
+
+## Environment
+
+- Windows 11, PowerShell, VS Code. Visual Studio only for `ravenqbi`.
+- .NET 10 SDK installed alongside the .NET 8 runtime **[unverified: check `dotnet --info`]**.
+- Local server: `http://localhost:7575`, API base `/api/v8/`.
+- Compile-time constant `SUBSCRIPTION_BUILD` (`-p:SUBSCRIPTION_BUILD=true`) distinguishes the hosted build; use `#if SUBSCRIPTION_BUILD`.
+
+## Commands
+
+```powershell
+dotnet build server\AyaNova\AyaNova.csproj
+dotnet run --project server\AyaNova
+
+# integration tests: the server must be running first
+cd ..\raven-test-integration; dotnet test raven-integration.csproj
+
+# hosted (subscription) Linux build, as build-release.bat does it
+dotnet publish server\AyaNova\AyaNova.csproj -c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=true
+
+# full release: docs, client, all five packages, installers
+.\build-release.bat
+```
+
+## Reference (read when relevant, not every session)
+
+| File | Covers |
+|---|---|
+| `devdocs/modernization/NET10-PORT.md` | The current port: plan, checklist, open questions, log |
+| `devdocs/modernization/ROADMAP.md` | Modernization steps and their status |
+| `devdocs/modernization/PROJECT-OVERVIEW.md` | Products, deployment targets, business context |
+| `devdocs/modernization/CODEBASE-REFERENCE.md` | Stack, structure, configuration, API conventions |
+| `devdocs/modernization/TESTING.md` | Test suites and their state |
+| `devdocs/build/BUILD.md` | Release build, packaging, version numbers |
+| `devdocs/build/DEV-VM-SETUP.md` | Rebuilding the Windows build VM from scratch |
diff --git a/devdocs/build/BUILD.md b/devdocs/build/BUILD.md
new file mode 100644
index 00000000..14f46209
--- /dev/null
+++ b/devdocs/build/BUILD.md
@@ -0,0 +1,134 @@
+# AyaNova v8 release build
+
+How a full release is built: tools, folder layout, what `build-release.bat` does, and where
+version numbers live. Written for .NET 8 (8.2.4); the .NET 10 port updates it.
+
+To set up a machine that can run this from scratch, see `DEV-VM-SETUP.md`.
+
+## What gets built
+
+| Project | Framework | Output |
+|---|---|---|
+| AyaNova server (`raven/server/AyaNova`) | .NET 8 | Five packages, below |
+| Launcher (`raven-launcher`) | .NET 8, self-contained, `PublishTrimmed` | Inside the Windows standalone installer |
+| QBI (`ravenqbi/AyaNovaQBI`) | .NET Framework 4.8 | `ayanova-qbi-setup.exe`, built separately |
+
+| Package | Build |
+|---|---|
+| `ayanova-subscription-linux-x64-server.zip` | linux-x64, framework-dependent, `SUBSCRIPTION_BUILD=true`, server `config.json` |
+| `ayanova-linux-x64-server.zip` | linux-x64, framework-dependent, server `config.json` |
+| `ayanova-linux-x64-desktop.zip` | Same build, desktop `config.json` |
+| `ayanova-windows-x64-single-setup.exe` | win-x64 self-contained server, launcher, embedded PostgreSQL |
+| `ayanova-windows-x64-lan-setup.exe` | win-x64 framework-dependent server |
+
+## Tools
+
+| Tool | Version | Used for |
+|---|---|---|
+| .NET SDK | 8.0.x | Server and launcher |
+| Node.js | **12.22.9 exactly** | Front end. Newer versions break `fibers` |
+| Python | 3.11 | MkDocs |
+| MkDocs + mkdocs-material | current | Customer manual (`pip install mkdocs mkdocs-material`) |
+| Inno Setup | 6.x | Windows installers |
+| 7-Zip | current | Linux zips |
+| Visual Studio | **[unverified: 2022 or 18/2026]** | QBI only |
+
+The scripts hardcode these paths:
+
+- `C:\data\code\raven\`, `C:\data\code\raven-client\ayanova\`, `C:\data\code\raven-launcher\`, `C:\data\code\ravenqbi\`
+- `C:\Program Files\7-Zip\7z.exe`
+- `C:\Program Files (x86)\Inno Setup 6\ISCC.exe`
+
+## Folder layout
+
+```
+C:\data\code\
+├── raven\
+│ ├── build-release.bat master build script
+│ ├── raven.sln
+│ ├── server\AyaNova\ AyaNova.csproj, wwwroot\ (generated), resource\
+│ ├── docs\8.0\ayanova\ customer manual, mkdocs.yml
+│ ├── docs\8.0\customer\ customer portal docs, mkdocs.yml
+│ ├── devdocs\ developer docs (this file)
+│ ├── dist\
+│ │ ├── assets\ LICENSE, license.rtf, logo.ico, lan-install-config.json,
+│ │ │ linux-server\config.json, linux-desktop\config.json
+│ │ ├── install\windows\x64\ standalone.iss, lan.iss
+│ │ ├── win-x64\postgres-standalone\ embedded PostgreSQL (maintained by hand)
+│ │ └── installers\ final packages (generated)
+│ │ (also generated: linux-x64\, subscription-build-linux-x64\, win-x64\ayanova\..., win-x64\launcher\)
+│ └── graphics\
+├── raven-client\ayanova\ Vue front end
+├── raven-launcher\ raven-launcher.csproj, config.json
+└── ravenqbi\ AyaNovaQBI\AyaNovaQBI.csproj, install\qbi.iss
+```
+
+## `build-release.bat`, step by step
+
+1. **Clean** `raven\server\AyaNova\wwwroot` and `raven\dist\installers`.
+2. **Customer manual**: `mkdocs build` in `docs\8.0\ayanova` and `docs\8.0\customer`. Output goes straight to `wwwroot\docs` (`site_dir` in `mkdocs.yml`).
+3. **Front end**: `npm run build` in `raven-client\ayanova`, then `xcopy dist\* ...\raven\server\AyaNova\wwwroot\ /e`. The .NET build does not produce `wwwroot`: whatever is there is what ships.
+4. **Subscription Linux**:
+ ```bat
+ dotnet publish --property:PublishDir=C:\data\code\raven\dist\subscription-build-linux-x64\ -c Release -r linux-x64 --no-self-contained -p:SUBSCRIPTION_BUILD=true
+ ```
+ Zipped with `assets\linux-server\config.json`.
+5. **Perpetual Linux**: the same with `-p:SUBSCRIPTION_BUILD=false` into `dist\linux-x64\`. One build, two zips: desktop and server `config.json`.
+6. **Windows standalone**: `-r win-x64 --self-contained` into `dist\win-x64\ayanova\standalone\` (includes the .NET runtime, about 150 MB more).
+7. **Windows framework-dependent**: `-r win-x64 --no-self-contained` into `dist\win-x64\ayanova\framework-dependent\`. Needs the .NET runtime on the target.
+8. **Launcher**: `dotnet publish -c Release -r win-x64 --self-contained` in `raven-launcher` into `dist\win-x64\launcher\`.
+9. **Installers**: `ISCC.exe standalone.iss` and `ISCC.exe lan.iss`.
+
+`raven-client\ayanova\buildrelease.bat` runs steps 1–3 alone (main manual only, not the
+customer portal docs).
+
+### Windows installers
+
+- **Standalone** (`standalone.iss`): self-contained server, launcher, embedded PostgreSQL, empty database template, desktop shortcut to the launcher. App in `{autopf}\ayanova`; data in `{commonappdata}\ayanova\database`; logs in `{commonappdata}\ayanova\logs`. The launcher starts PostgreSQL, starts the server, opens the browser at the login page and waits.
+- **LAN** (`lan.iss`): framework-dependent server plus network config. No PostgreSQL. Prompts the user to download the ASP.NET Core Hosting Bundle and PostgreSQL.
+
+### QBI (separate)
+
+```bat
+cd C:\data\code\ravenqbi\AyaNovaQBI
+msbuild /p:Configuration=Release
+cd C:\data\code\ravenqbi\install
+"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" qbi.iss
+```
+
+Output: `ravenqbi\install\output\ayanova-qbi-setup.exe`. Has a pre-build event that runs a
+timestamp utility.
+
+## Version numbers
+
+Set in five places for every release:
+
+| File | Setting | At 8.2.4 |
+|---|---|---|
+| `raven\server\AyaNova\AyaNova.csproj` | `` | 8.2.4 |
+| `raven\dist\install\windows\x64\standalone.iss` | `#define MyAppVersion` | 8.2.4 |
+| `raven\dist\install\windows\x64\lan.iss` | `#define MyAppVersion` | 8.2.4 |
+| `raven-client\ayanova\package.json` | `"version"` | 8.2.4 |
+| `ravenqbi\install\qbi.iss` | `#define MyAppVersion` | 8.0.2 (own cycle) |
+
+## Front-end build notes
+
+- Install dependencies with `npm ci`, never `npm install`: it installs the exact versions in `package-lock.json`.
+- Warnings that appear on every build and are harmless: Browserslist "caniuse-lite is outdated"; Dart Sass "Using / for division is deprecated" (from Vuetify); asset size warnings (Monaco's `ts.worker.js` is about 4.5 MB).
+- Webpack content-hash file names change on every build even when contents do not. Compare file contents, not names.
+
+## Docker builds
+
+`Dockerfile.frontend` and `Dockerfile.docs` build the front end and the manual in containers
+**[unverified: where they live]**. The Linux server packages and the Windows
+framework-dependent build could also build in Docker. Windows self-contained builds, Inno
+Setup, QBI and the embedded PostgreSQL need Windows.
+
+## Troubleshooting
+
+- **`fibers` fails to install**: it needs Python, Visual Studio Build Tools, and Node exactly 12.22.9. Last resort: remove `"fibers"` from `package.json` and the `fiber: require("fibers")` line from `webpack.config.js`; the build still works, a little slower.
+- **Node version errors**: `node -v` must print `v12.22.9`.
+- **"Module not found"**: delete `node_modules`, then `npm ci`.
+- **PuppeteerSharp cannot download the browser**: check connectivity, or point the server at an installed browser through its browser-path setting.
+- **`SUBSCRIPTION_BUILD` not applied**: the flag is `-p:SUBSCRIPTION_BUILD=true` on `dotnet publish`.
+- **Inno Setup errors**: an earlier step failed, or 7-Zip / Inno Setup are not at the expected paths.
diff --git a/V8-DEV-VM-SETUP-GUIDE.md b/devdocs/build/DEV-VM-SETUP.md
similarity index 90%
rename from V8-DEV-VM-SETUP-GUIDE.md
rename to devdocs/build/DEV-VM-SETUP.md
index ee330fcf..4a65139e 100644
--- a/V8-DEV-VM-SETUP-GUIDE.md
+++ b/devdocs/build/DEV-VM-SETUP.md
@@ -1,7 +1,8 @@
-# AyaNova v8 Build VM Setup Guide
+# AyaNova v8 build VM setup
> **Purpose**: Step-by-step instructions to create a portable Windows virtual machine containing the complete AyaNova v8 build environment. This VM can run on Windows or Linux hosts.
-test for commit
+
+Written February 2026 for the .NET 8 build. The .NET 10 port adds the .NET 10 SDK (see `../modernization/NET10-PORT.md`, section 5).
---
## Overview
@@ -237,7 +238,7 @@ Using winget (run in PowerShell as Administrator):
# 7-Zip
winget install --id 7zip.7zip -e
-# Git (useful even if using SVN)
+# Git
winget install --id Git.Git -e
# VS Code
@@ -294,15 +295,7 @@ This is needed for the QuickBooks Integration project (.NET Framework 4.8, Windo
5. Click **Install**
6. Wait (this takes 20-40 minutes and downloads several GB)
-### 4.7 Install TortoiseSVN (if using SVN)
-
-```powershell
-winget install --id TortoiseSVN.TortoiseSVN -e
-```
-
-Reboot after installation (adds shell integration).
-
-### 4.8 Verify All Installations
+### 4.7 Verify All Installations
Open a new PowerShell window and run:
@@ -360,29 +353,21 @@ All items should show versions or [OK].
## Part 5: Set Up Source Code
-### 5.1 Check Out from SVN
-
-If your SVN server is accessible from the VM:
+### 5.1 Clone from Gitea
```powershell
cd C:\data\code
-
-# Check out main repository
-svn checkout https://your-svn-server/path/to/raven raven
-
-# Check out frontend
-svn checkout https://your-svn-server/path/to/raven-client raven-client
-
-# Check out launcher
-svn checkout https://your-svn-server/path/to/raven-launcher raven-launcher
-
-# Check out QBI
-svn checkout https://your-svn-server/path/to/ravenqbi ravenqbi
+git clone /raven.git raven
+git clone /raven-client.git raven-client
+git clone /raven-launcher.git raven-launcher
+git clone /ravenqbi.git ravenqbi
```
+The folder names must be exactly these: the build scripts hardcode the paths.
+
### 5.2 Alternative: Copy from Host via Shared Folder
-If SVN isn't accessible, use VirtualBox shared folders:
+If Gitea isn't reachable from the VM, use VirtualBox shared folders:
1. In VirtualBox: **Devices → Shared Folders → Shared Folders Settings**
2. Click the folder+ icon
@@ -524,10 +509,10 @@ When to re-export:
```powershell
cd C:\data\code\raven
-svn update
+git pull
cd C:\data\code\raven-client
-svn update
+git pull
# etc.
```
@@ -545,7 +530,6 @@ Options:
1. **Shared Folders**: Copy to a shared folder visible on host
2. **Network**: If VM is bridged, access via network share
3. **USB Drive**: Attach a USB drive to the VM
-4. **SVN Commit**: Commit build outputs to repo (not recommended for large binaries)
### 8.5 Snapshots for Safety
@@ -582,19 +566,6 @@ ThinkPads are generally excellent Linux laptops. The X13 Gen 3 AMD should work w
- **SSD**: VM performance is heavily dependent on disk speed; SSD is essential
- **RAM**: 32GB on host would be more comfortable than 16GB
-### 9.4 Used ThinkPad Recommendations
-
-If you're looking at a used/refurbished ThinkPad for Linux:
-
-| Model | Era | Notes |
-|-------|-----|-------|
-| X1 Carbon Gen 9/10 | 2021-2022 | Excellent, lightweight |
-| T14s Gen 2/3 | 2021-2022 | Good value, business-class |
-| T480/T480s | 2018 | Older but very well supported, cheap |
-| P14s | 2020+ | More RAM options (up to 48GB) |
-
-Look for 32GB RAM models if possible. Check that the specific CPU is supported (Intel generally has better Linux support than AMD, though AMD works fine too).
-
---
## Appendix A: Troubleshooting
@@ -620,10 +591,10 @@ If `npm ci` fails with native module errors:
- Check that 3D acceleration is enabled
- Use SSD, not spinning disk
-### Can't Access SVN Server
+### Can't Reach Gitea
- Check VM network mode (NAT vs Bridged)
-- Verify SVN server is accessible from host
+- Verify Gitea is reachable from the host
- Check firewall settings on both host and VM
---
@@ -665,7 +636,7 @@ msbuild C:\data\code\ravenqbi\AyaNovaQBI\AyaNovaQBI.csproj /p:Configuration=Rele
|------|------|
| 7-Zip | `C:\Program Files\7-Zip\7z.exe` |
| Inno Setup | `C:\Program Files (x86)\Inno Setup 6\ISCC.exe` |
-| MSBuild | `C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe` |
+| MSBuild | `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe` **[unverified]**: 6.3 was changed to `18` (Visual Studio 2026) while 4.6 still says install VS 2022. Confirm which is installed |
---
@@ -674,3 +645,4 @@ msbuild C:\data\code\ravenqbi\AyaNovaQBI\AyaNovaQBI.csproj /p:Configuration=Rele
| Date | Change |
|------|--------|
| 2026-02-01 | Initial guide created |
+| 2026-10-22 | SVN steps replaced with Git; moved to `devdocs/build/` |
diff --git a/devdocs/modernization/CODEBASE-REFERENCE.md b/devdocs/modernization/CODEBASE-REFERENCE.md
new file mode 100644
index 00000000..3082bad4
--- /dev/null
+++ b/devdocs/modernization/CODEBASE-REFERENCE.md
@@ -0,0 +1,132 @@
+# AyaNova v8 codebase reference
+
+Technical facts about the v8 code, gathered from notes written in February–March 2026.
+Version numbers are a snapshot from then: **`AyaNova.csproj` and `package.json` are the
+truth**. Anything marked **[unverified]** has not been checked against the code.
+
+## Back end — `raven/server/AyaNova/`
+
+### Stack (February 2026, before the .NET 10 port)
+
+| Component | Version | Notes |
+|---|---|---|
+| .NET / ASP.NET Core | 8.0 | |
+| Entity Framework Core | 8.0.8 | |
+| Npgsql.EntityFrameworkCore.PostgreSQL | 8.0.4 | |
+| Microsoft.AspNetCore.Authentication.JwtBearer | 8.0.4 | Custom JWT implementation |
+| PuppeteerSharp | 17.0.0 | PDF reports through headless Chrome |
+| MailKit | 4.5.0 | Email |
+| Swashbuckle.AspNetCore | 6.5.0 | OpenAPI |
+| NLog.Web.AspNetCore | 5.3.11 | Logging |
+| Asp.Versioning.Mvc | — | API versioning **[unverified]** |
+
+### Structure
+
+```
+raven/server/AyaNova/
+├── AyaNova.csproj
+├── Program.cs, Startup.cs
+├── resource/ report templates and static resources, copied to output
+│ └── rpt/ report templates (.ayrt)
+└── wwwroot/ built front end and customer manual, copied in by the build
+```
+
+Older notes also list `Controllers/`, `Models/`, `Services/` and `Data/` folders
+**[unverified]**: that list reads like a generic template, not the actual layout.
+Data-list classes live in the `AyaNova.DataList` namespace, and their class names are the
+list keys (`WorkOrderDataList`, `CustomerDataList` …).
+
+### Hosted vs self-hosted
+
+```xml
+$(DefineConstants);SUBSCRIPTION_BUILD
+```
+
+```csharp
+#if SUBSCRIPTION_BUILD
+ // hosted-subscription-only code
+#endif
+```
+
+### Reports
+
+PuppeteerSharp renders PDFs in headless Chrome, downloaded on first run. A customer
+setting holds extra browser launch parameters, and an environment variable can point at
+an existing browser instead of downloading one (`REPORT_RENDER_BROWSER_PATH` in older
+notes **[unverified]**).
+
+### Configuration
+
+Older notes list `ConnectionStrings__DefaultConnection`, `JWT_SECRET` and
+`ASPNETCORE_ENVIRONMENT` as the server's settings **[unverified]**. Sockeye, the near-copy,
+reads `SOCKEYE_*` keys from a `config.json` beside the app (`SOCKEYE_DB_CONNECTION`,
+`SOCKEYE_USE_URLS`, `SOCKEYE_JWT_SECRET`, `SOCKEYE_DATA_PATH` …), so AyaNova likely does the
+same with its own prefix. The Linux packages ship a `config.json` from
+`dist/assets/linux-server/` or `dist/assets/linux-desktop/`, which supports that.
+
+### API conventions
+
+Confirmed by the integration tests. Base `http://localhost:7575/api/v8/`.
+
+| Call | Response |
+|---|---|
+| POST | 201, `{ data: }` |
+| GET | 200, `{ data: }` |
+| DELETE | 204, no body |
+| PUT on Customer, QuoteItem, PMItem | 200, `{ data: { Concurrency: N } }` (concurrency only) |
+| PUT on Quote, PM, Contract, WorkOrder headers | 200, `{ data: }` |
+| Concurrency conflict | 409 |
+| Referential integrity violation | 400, `error.code = 2200` |
+| Not found | 404, `error.code = 2010` |
+
+Saved data-list filters: `/data-list-filter` (not the older `data-list-view`); list with
+`GET /data-list-filter/list?ListKey=WorkOrderDataList`.
+
+## Front end — `raven-client/ayanova/`
+
+### Stack
+
+| Package | Version | Notes |
+|---|---|---|
+| vue | 2.6.14 | End of life December 2023 |
+| vuetify | 2.6.6 | |
+| vue-router | 3.5.4 | |
+| vuex | 3.6.2 | plus `vuex-persistedstate` 2.7.1 |
+| @vue/cli-service | 4.5.15 | |
+| webpack | 4.46.0 | |
+| sass / sass-loader | 1.52.3 / 8.0.2 | |
+| fibers | 4.0.3 | Deprecated native addon; the reason Node is pinned |
+| monaco-editor | 0.30.1 | Large worker files |
+| others | | chart.js 2.9, luxon 1.28, marked 1.2, dompurify 2.3, jwt-decode 3.1, papaparse 5.3 |
+
+Node.js **12.22.9**, pinned. Plain JavaScript, no TypeScript.
+
+### Structure
+
+```
+raven-client/ayanova/
+├── src/
+│ ├── api/ API utilities, auth, error handling
+│ ├── components/ ~80 components
+│ ├── views/ ~100 page components
+│ ├── plugins/ Vuetify configuration
+│ ├── App.vue, main.js, router.js, store.js
+├── public/
+├── package.json, vue.config.js, webpack.config.js
+```
+
+View prefixes: `acc-` accounting (rates, taxes), `adm-` administration, `cust-` customers,
+`home-` dashboard and user home, `inv-` inventory, `ops-` server operations, `svc-` service
+(work orders, quotes, PMs).
+
+## QuickBooks integration — `ravenqbi/`
+
+.NET Framework 4.8, Windows Forms, x86 only (the QuickBooks SDK is 32-bit). References
+`interop.QBFC14.dll`, which needs the QuickBooks SDK installed on the build machine.
+Visual Studio is required for the Forms designer. Not part of `build-release.bat`.
+
+## Customer manual — `raven/docs/8.0/`
+
+MkDocs with the Material theme, two sites: `ayanova/` (main manual) and `customer/`
+(customer portal). Each `mkdocs.yml` sets `site_dir` to build straight into the server's
+`wwwroot/docs`.
diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md
new file mode 100644
index 00000000..7434a8c3
--- /dev/null
+++ b/devdocs/modernization/NET10-PORT.md
@@ -0,0 +1,179 @@
+# AyaNova 8 back end → .NET 10
+
+The plan and running record for the port. Tick items and add a log line **in the same
+commit** as the change they record. Anything found along the way that is out of scope goes
+under "Deferred", not into the branch.
+
+| | |
+|---|---|
+| Starting point | AyaNova 8.2.4: ASP.NET Core 8, EF Core 8 |
+| Target | `net10.0`, every back-end package at its current release |
+| Front end | Minimal or no change. Rebuild only if needed (open question) |
+| Branch | `net10-port` in `raven` (and `raven-client` only if the client changes) |
+| Baseline tag | `net8-baseline`, on the last commit before the port |
+| Release version | Not yet decided (open question) |
+
+## Where this plan comes from
+
+Sockeye, the internal licensing server, is a near-copy of this codebase. It was ported from
+.NET 6 to .NET 10 in September–October 2026 and released as `8.1.0`. This plan is that
+port's carry-forward list (Appendix A of the Sockeye migration runbook), arranged as a
+checklist.
+
+**AyaNova starts from .NET 8, not .NET 6.** So every item is "check whether this applies",
+not "this will apply". Several items will turn out to be already done or not present. Mark
+those `n/a` with a one-line reason rather than deleting them.
+
+When an item is unclear, look at how Sockeye did it:
+
+```powershell
+git -C ..\sockeye log --oneline net6-baseline..8.1.0
+git -C ..\sockeye show
+```
+
+**Where AyaNova differs from Sockeye:** customers choose their own database role names and
+passwords, every customer uses reports, and AyaNova *verifies* licences rather than issuing
+them. Items touching those three areas carry more weight here than they did in Sockeye.
+
+## Scope
+
+- **In:** target framework; all NuGet packages in `server/AyaNova`; code changes those require; the Sockeye-found fixes in section 3, each as its own commit.
+- **Out:** front-end dependency changes; refactoring (Roadmap step D); new features; PostgreSQL major upgrades.
+- **Undecided:** see "Open questions".
+
+## Working method
+
+- Version bump first. One concern per commit.
+- Prove each gate on the **unmodified** code before changing anything, so a later failure points at the port, not at the test.
+- Packages that must move together: framework-matched `Microsoft.*` packages all go to 10.x in one step. `BouncyCastle.Cryptography` 2.x goes with MailKit/MimeKit 4.x, because MimeKit 3.x pulls in `Portable.BouncyCastle` and the assemblies clash.
+- Publish `server\AyaNova\AyaNova.csproj` directly, never the solution, so no test project can reach the artifact.
+
+---
+
+## 0. Before changing anything
+
+- [ ] Record any port work already done in earlier sessions (branch, commits) here first, and reconcile it with this list
+- [ ] `raven` and `raven-client` clean on `main` and pushed
+- [ ] Annotated tag `net8-baseline` on the pre-port commit in `raven` and `raven-client`, pushed by name (`git push origin net8-baseline`), checked with `git ls-remote --tags origin`
+- [ ] Branch `net10-port` created and pushed with upstream
+- [ ] **First commit: version bump** in every place listed in `devdocs/build/BUILD.md` → "Version numbers"
+- [ ] Dev machine has the .NET 10 SDK and keeps the .NET 8 runtime for baseline comparisons
+
+### Gates (prove on the unmodified code)
+
+- [ ] **Integration suite baseline.** Run `raven-test-integration` against the unmodified build and record the counts in the log below. Expected: everything passes except the three known server-side failures listed in that repo's CLAUDE.md
+- [ ] **Licence verification gate.** Find out exactly how AyaNova 8 verifies a v8 licence: library, public key, and when it re-checks. If it uses BouncyCastle, write a test that runs every v8 row of Sockeye's `alicense` fixture through AyaNova's own verification routine. The fixture is customer data: it stays outside the repo and is found through an environment variable, as Sockeye's re-sign harness does with `SOCKEYE_RESIGN_FIXTURE`. Must be green on unmodified code. A regression here would reject licences customers already have installed
+- [ ] **Report baseline.** Render the advanced reports on the unmodified build and keep the PDFs for comparison. Sockeye shares only one report with AyaNova, so Sockeye's testing says little about AyaNova's reports
+- [ ] **E2E baseline.** Cypress run against a devtest server on the unmodified build
+
+## 1. Framework and packages
+
+- [ ] `net8.0` → `net10.0`, with every framework-matched `Microsoft.*` package to 10.x in the same step
+- [ ] **Hosting model.** `WebHost`, `IWebHost` and `WebHostBuilder` are obsolete in .NET 10 (ASPDEPR008, ASPDEPR004). `Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(web => web.UseStartup())` keeps the `Startup` class unchanged
+- [ ] **Asp.Versioning.** Older notes say AyaNova already uses `Asp.Versioning.Mvc` **[unverified]**. If not: rename from `Microsoft.AspNetCore.Mvc.Versioning`; `ApiVersion` moves to `Asp.Versioning`; wiring is `services.AddApiVersioning(...).AddMvc().AddApiExplorer(...)`. Leave AV0014 warnings: **do not add `[ApiController]`**
+- [ ] **Npgsql / EF Core 10.**
+ - Check `Npgsql.EnableLegacyTimestampBehavior`. Sockeye's was commented out. If AyaNova sets it, `DateTime.Kind` is a real port item
+ - Check for `ConfigureWarnings(w => w.Throw())`. If present, every new EF warning becomes an exception naming its ID: fix the query, or move that ID to `.Log(...)`
+ - Exercise LINQ-based queries screen by screen. Data-list filters build raw SQL and are unaffected
+- [ ] **BouncyCastle.** If AyaNova references `BouncyCastle`, it becomes `BouncyCastle.Cryptography` 2.x, moved together with MailKit/MimeKit 4.x. In Sockeye, `PemReader` still yields an `AsymmetricCipherKeyPair` for a PKCS#1 key
+- [ ] **Swashbuckle 10 / OpenAPI.NET 2.** `Microsoft.OpenApi.Models` merged into `Microsoft.OpenApi`. Bearer requirement becomes `c.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("Bearer", document)] = [] })`. No explicit `Microsoft.OpenApi` reference
+- [ ] **NLog 6.** `ArchiveNumbering` is gone; `NLogBuilder.ConfigureNLog` is replaced by `NLog.LogManager.Configuration = logConfig`. Archive numbers keep incrementing and the un-numbered file is always current. If the log viewer in the UI sorts by file name, the order breaks past 99
+- [ ] **Newtonsoft.Json.** `Mvc.NewtonsoftJson` 10.x sets a 13.0.3 floor (NU1605). Pin it as a direct reference
+- [ ] **JwtBearer 10.x**, which also retires the vulnerable IdentityModel 6.10.0 dependency if AyaNova still resolves it
+- [ ] **SMTP.** Any use of `System.Net.Mail.SmtpClient` (SYSLIB0014) moves to MailKit 4.x, as in Sockeye and contact
+- [ ] **PuppeteerSharp → 25.x.** Reporting is AyaNova's main Puppeteer surface
+ - `BrowserFetcher.DefaultChromiumRevision` is gone: call `DownloadAsync()` with no argument
+ - `IJSHandle.ExecutionContext` is gone: evaluate on the handle. Evaluate results are `JsonElement?`
+ - The default download is Chrome for Testing, into `Chrome/` and `ChromeHeadlessShell/` beside the app. AyaNova 8.2.0 already moved from Chromium to Chrome to follow Puppeteer's default, so the browser choice is settled
+ - Launch arguments must be separate array elements (`--no-sandbox` on its own). **This includes the report-render browser params setting** customers can edit
+ - AppArmor restricts unprivileged user namespaces on Ubuntu 24.04+, which affects the Chrome sandbox
+- [ ] Remaining packages to current releases
+- [ ] Warnings: CA2022 → `ReadExactly`; ASP0019 → header indexer instead of `Headers.Append`
+- [ ] **MiniProfiler.** Confirm it is absent: `git grep -i -e miniprofiler -e mini-profiler -e StackExchange.Profiling` in **both** `raven` and `raven-client`
+- [ ] **BinaryFormatter.** Confirm it is unused, or that `EnableUnsafeBinaryFormatterSerialization` is already false
+- [ ] **`System.GC.Server`.** Keep the Web SDK default (`true`). On one vCPU the runtime uses workstation GC anyway, and DATAS keeps server GC lean since .NET 9
+- [ ] **`InvariantGlobalization`.** Decide on EF Core string comparison and date-parsing grounds. Still open in Sockeye too
+
+## 2. Configuration behaviour to check
+
+- [ ] **URL binding precedence.** An in-code `UseUrls` beats `ASPNETCORE_URLS`. Find where AyaNova takes its URLs from (Sockeye: `SOCKEYE_USE_URLS` in `config.json`) and make sure the documented setting is the one that wins
+- [ ] **Logging providers.** `appsettings.json` very likely sets the Debug and Console providers to `Trace`, as Sockeye's does. Check whether `Program.cs` calls `ClearProviders()` before adding NLog. If it does not, `Trace` floods the journal under systemd. Fix the levels in the repo either way
+- [ ] **Data Protection keys.** Only matters if AyaNova encrypts anything with Data Protection and runs under a service account with no home directory: then the key ring must be persisted explicitly. A JWT-only API is unaffected
+
+## 3. Fixes found through Sockeye (each its own commit)
+
+These are bugs, not port mechanics, so each is a separate commit that can be reverted alone.
+Sockeye included its equivalents in the port branch. John decides per item whether they go
+in this branch or a follow-up release.
+
+- [ ] **Case 4676: data-list SQL injection — priority.** The numeric and role filters in `DataListSqlFilterCriteriaBuilder` built SQL from input. In AyaNova any authenticated user who can open a data list could reach them. Port the Sockeye fix
+- [ ] **`DbUtil.AdminConnectionString`.** If it is `_dbConnectionString.Replace(_dbName, "postgres")`, any customer whose role name, password or host contains the database name as text gets a mangled admin connection, and the boot check authenticates as the wrong user. Fix: `new NpgsqlConnectionStringBuilder(cs) { Database = "postgres" }.ConnectionString`
+- [ ] **Backup `pg_dump` call.** If it builds a `postgresql://user:password@host:port/db` URI with the raw password and runs it through a shell for a `> file` redirect, a password with spaces or `@ : / ? # %` breaks backups. Fix: password in `PGPASSWORD` in the process environment, connection parameters as separate arguments through `ProcessStartInfo.ArgumentList` (no shell), output with `-f`
+- [ ] **Drop-and-recreate database.** Without `CREATEDB` it drops the database and then fails to create it. Test the permission before dropping, or change the order
+- [ ] **Report browser download location.** Point it under the data directory in code. Beside the app it is read-only under a hardened systemd unit (`ProtectSystem=strict`), and a redeploy that replaces the app folder deletes it
+- [ ] **Report failures must not kill the server.** On the Sockeye destination a failed browser download was an unhandled exception that ended the whole process (systemd `core-dump`, then restart); the client saw only `TypeError … reading 'length'`. Catch download and launch failures, log them through NLog, and return an error to the client
+- [ ] **Data-list date filters, small fixes.** `>=` and `<` should use `ZeroSeconds`, not `MaxSeconds`; date `NotEqual` should exclude the whole minute (`< ZeroSeconds OR > MaxSeconds`). In DEBUG builds, `DataListField.UiFieldDataType` is read before `DataListField` is null-checked
+- [ ] **Relative date tokens use the server's time zone** (`*today*`, `*yesterday*` …), which is wrong for any user in a different zone from the server. The fix builds the range from `clientTimeStamp.Offset`, with care for ranges that cross a DST change. **This changes behaviour** and affects the integration tests' `TIME_ZONE_ADJUSTMENT`. Probably better as a follow-up release
+
+Not port items, listed so they are not forgotten: the three server bugs the integration
+suite is waiting on (case 4653, case 4654, the contract issue).
+
+## 4. Test under realistic conditions
+
+- [ ] **Database role.** Run under a non-superuser role **named like the database**, with a password containing spaces and `@ : / ? # %`. Boot, on-demand backup, scheduled backup, restore. Sockeye ran as `postgres` for years and never exercised either bug in section 3
+- [ ] **Restore with a dedicated role.** `createdb -O `, then `pg_restore --no-owner --no-privileges --role=`. Without `--role`, a superuser restore leaves every table owned by `postgres`, and the integrity check, which counts through `information_schema`, fails. Compare with the restore instructions in the customer manual (`docs/`) and note any change needed
+- [ ] **Reports, all advanced reports**, compared with the baseline PDFs. On Windows and on Linux. On Linux also: `ldd` on the downloaded browser binary for missing libraries (install exactly what is reported; several Ubuntu 24.04+ names carry a `t64` suffix); the sandbox under the service account; no orphaned `chrome` processes after a cancelled render
+- [ ] Integration suite: same result as the baseline
+- [ ] Licence verification gate: green
+- [ ] Cypress E2E against a devtest server running the .NET 10 build
+- [ ] `build-release.bat` produces all five packages; install and start each one: Windows standalone, Windows LAN, Linux server, Linux desktop, subscription Linux
+
+## 5. Release
+
+- [ ] **Every host that runs a framework-dependent build needs the .NET 10 runtime** before it gets the release:
+ - Hosted subscription droplets (Ubuntu 24.04). How `aspnetcore-runtime-10.0` is obtained on 24.04 needs checking **[unverified]**. On 26.04 it is in Ubuntu's own archive
+ - Self-hosted Linux customers (server and desktop zips are framework-dependent)
+ - Windows LAN: `lan.iss` prompts for the ASP.NET Core Hosting Bundle. Update its text and link to .NET 10
+ - Windows standalone and the launcher are self-contained: no runtime needed on the customer's machine
+- [ ] Customer manual (`docs/`): system requirements and upgrade instructions for self-hosted customers
+- [ ] Build VM: install the .NET 10 SDK; update `devdocs/build/DEV-VM-SETUP.md` and `devdocs/build/BUILD.md`
+- [ ] Final gate runs on the finished code
+- [ ] Merge, per repo:
+ ```powershell
+ git switch net10-port; git status; git push; git fetch origin
+ git switch main; git pull
+ git log --oneline net10-port..main # must print nothing: main untouched
+ git merge --no-ff net10-port -m "Merge net10-port: .NET 10 port"
+ git diff net10-port main # must print nothing: main is exactly what was tested
+ git push origin main
+ git tag -a -m ""
+ git push origin
+ git ls-remote --tags origin
+ ```
+ `--no-ff` keeps a merge commit, so the whole port can be backed out with `git revert -m 1 `
+- [ ] Rebuild the client from `main`, publish, and test **the published artifact itself**
+- [ ] Rollout: own test droplet first, then customer droplets by time zone, as in the weekly routine
+- [ ] After release: delete `net10-port` locally and on the remote
+
+---
+
+## Open questions
+
+- Release version number (Sockeye's port was released as 8.1.0)
+- `raven-launcher` (.NET 8, self-contained): move to .NET 10 in the same release?
+- `raven-test-integration`: move to `net10.0` (separate commit) or leave on `net8.0`? It only talks HTTP to the server
+- Does the client need a rebuild in this pass, or can its current output be carried across?
+- Section 3: which fixes go in this branch, which in a follow-up
+- How the .NET 10 runtime gets onto Ubuntu 24.04 hosts
+
+## Deferred
+
+Found during the port, out of scope. One line each.
+
+-
+
+## Log
+
+| Date | Step | Result |
+|---|---|---|
+| | | |
diff --git a/devdocs/modernization/PROJECT-OVERVIEW.md b/devdocs/modernization/PROJECT-OVERVIEW.md
new file mode 100644
index 00000000..59e7874c
--- /dev/null
+++ b/devdocs/modernization/PROJECT-OVERVIEW.md
@@ -0,0 +1,71 @@
+# AyaNova project overview
+
+Background for anyone, human or AI, working on this code: what the products are, who runs
+them where, and the development setup.
+
+## Business
+
+Ground Zero Tech-Works Inc. sells AyaNova, commercial service management software
+(https://ayanova.com), directly to customers online since 1998. One person, John, does all
+development, support and administration.
+
+Revenue comes from three streams:
+
+- **Hosted subscriptions**: AyaNova v8 run by Ground Zero on DigitalOcean droplets. Built with `SUBSCRIPTION_BUILD=true`
+- **Self-hosted v8 licences** with yearly support and update subscriptions. Customers install it on their own Windows or Linux machines
+- **v7 legacy support**: maintenance only
+
+Practical consequence for code changes: a change can reach customer-run servers that
+Ground Zero cannot see or update, and deployed clients cannot be pushed an update. API
+routes, licence handling and database behaviour have to stay compatible.
+
+## Products
+
+| Product | Stack | Status |
+|---|---|---|
+| **AyaNova v8** | ASP.NET Core 8, C#, PostgreSQL; Vue 2 front end | Active. Customers are slowly moving to it from v7 |
+| **AyaNova v7** | Windows desktop app, C#, 20+ years old, Firebird database | Sunset: maintenance only. Depends on discontinued commercial components. Builds in an isolated Windows 7 VirtualBox VM. Customers were told in 2022 it may become unsupportable |
+| **Sockeye** | Same stack as v8, a near-copy of its code | Internal: licensing, customer database, cases (history back to FogBugz). Ported to .NET 10 in October 2026 |
+| **AyaNova QBI** | .NET Framework 4.8, Windows Forms | QuickBooks Desktop integration for v8. Separate release cycle, rarely changes |
+
+## v8 deployment targets
+
+`build-release.bat` produces five packages; QBI is built separately.
+
+| Target | Package | Runtime |
+|---|---|---|
+| Hosted subscription, Linux | `ayanova-subscription-linux-x64-server.zip` | Framework-dependent |
+| Self-hosted Linux server | `ayanova-linux-x64-server.zip` | Framework-dependent |
+| Self-hosted Linux desktop (single user) | `ayanova-linux-x64-desktop.zip` | Framework-dependent |
+| Windows standalone (single user) | `ayanova-windows-x64-single-setup.exe` | Self-contained, embedded PostgreSQL, launcher |
+| Windows LAN server | `ayanova-windows-x64-lan-setup.exe` | Framework-dependent; customer installs the ASP.NET Core Hosting Bundle and PostgreSQL |
+| QuickBooks integration | `ayanova-qbi-setup.exe` | .NET Framework 4.8 |
+
+Hosted customers run on DigitalOcean Ubuntu droplets. They are updated by hand weekly:
+Ground Zero's own test droplet first, then customers through the day by time zone.
+
+## Development environment
+
+- **Workstation:** Beelink SER9 Pro+, 32 GB, Windows 11 Pro. Backup: ThinkPad X13 Gen 3
+- **Editors:** VS Code for everything except QBI, which needs Visual Studio for the Windows Forms designer
+- **Build VM:** a complete Windows 11 build environment in VirtualBox, exported and backed up to several drives (`devdocs/build/DEV-VM-SETUP.md`)
+- **Source control:** Git on a self-hosted Gitea on DigitalOcean. Moved from SVN in early 2026
+- **Location:** off-grid, Starlink internet, solar power. The workstation is not left running overnight in winter, so do not plan work that depends on overnight runs on it
+
+### Pinned tool versions
+
+| Tool | Version | Why |
+|---|---|---|
+| Node.js | 12.22.9 | Newer versions break `fibers` |
+| Python | 3.11 | MkDocs for the customer manual |
+| .NET SDK | 8.0.x, plus 10.x for the port | Server, launcher |
+| .NET Framework | 4.8 | QBI only |
+| Inno Setup | 6.x | Windows installers |
+| 7-Zip | current | Linux zips |
+
+## Working style
+
+- Reliability over speed; patient with build times
+- Keep all historical data: cases, emails, old releases
+- Careful about backups and disaster recovery
+- Changes are reviewed before they are committed; nothing is accepted blindly
diff --git a/devdocs/modernization/ROADMAP.md b/devdocs/modernization/ROADMAP.md
new file mode 100644
index 00000000..d322d71a
--- /dev/null
+++ b/devdocs/modernization/ROADMAP.md
@@ -0,0 +1,44 @@
+# Modernization roadmap
+
+The order of the modernization work and where each step stands. Steps are lettered as in
+the original plan (February–March 2026). Statuses are as of October 2026; any marked
+**[unverified]** were not recorded anywhere recent and need John to confirm.
+
+| Step | What | Status |
+|---|---|---|
+| — | SVN → Git, self-hosted Gitea | ✅ Done |
+| — | Build process documented; Docker builds for front end and docs; Windows build VM exported and backed up | ✅ Done |
+| A1 | E2E tests with Cypress 9.7.0, targeting remote devtest servers, with version validation and environment-based config | **[unverified]**: the March notes listed it as both done and in progress |
+| A2 | Fix the outdated integration tests in `raven-test-integration` | ✅ Done, March 2026: compiles on `net8.0`, broken tests removed or rewritten |
+| A3 | Data-list filter and sort integration tests | ✅ Done, March 2026, together with Tier 1–3 gap coverage. February notes still called them stubs; the March notes supersede that **[unverified]** |
+| — | Fix the three server bugs the integration suite is waiting on: case 4653, case 4654, the contract issue | Open |
+| ▶ | **.NET 10 port of the back end** | **Current**: `NET10-PORT.md` |
+| B | Git baseline: tag plus refactor branch | **[unverified]**. The port's `net8-baseline` tag covers the tag half |
+| C | Architecture review | Not started |
+| D | Refactor: remove redundant code, simplify, improve performance. No behaviour changes | Not started |
+| E | Lower-level tests: unit and integration | Not started |
+| F | Remaining dependency updates (see below) | Back end: in progress as the .NET 10 port. Front end: not started |
+| G | New features, from the case database backlog | Not started |
+
+## Why the .NET 10 port comes before step D
+
+It pulls the back-end half of step F forward. Sockeye, which shares most of this codebase,
+was ported first, so the work is mapped out and comes with a list of fixes found along the
+way. The integration suite from A2/A3 is the regression gate for it, as it will be for D.
+
+## Step F, front end (not part of the current port)
+
+In this order, each step tested before the next:
+
+1. Remove `fibers`. It is optional for `sass-loader`; builds get a little slower
+2. Move Node from 12.22.9 to a current LTS
+3. Update the safe dependencies
+4. Vue 2 → Vue 3 and Vuetify 2 → Vuetify 3
+5. Webpack → Vite
+6. Optional: Options API → Composition API; TypeScript for new files only
+
+## Before step D starts
+
+1. The integration suite passes, apart from the three known server bugs
+2. Fix those three bugs
+3. Then refactor, letting the tests catch regressions
diff --git a/devdocs/modernization/TESTING.md b/devdocs/modernization/TESTING.md
new file mode 100644
index 00000000..2e65a14e
--- /dev/null
+++ b/devdocs/modernization/TESTING.md
@@ -0,0 +1,37 @@
+# Testing
+
+What test suites exist, what they protect, and the rules for adding to them.
+
+## Suites
+
+| Suite | Where | What it covers | State |
+|---|---|---|---|
+| API integration tests | `raven-test-integration` | Tier 1–3 API coverage against a running server | Green apart from three known server bugs (March 2026). Details in that repo's `CLAUDE.md` |
+| E2E | Cypress 9.7.0 **[unverified: which folder]** | User workflows in a browser, against remote devtest servers, with version validation and environment-based config | **[unverified]** |
+| Licence verification gate | To be written (`NET10-PORT.md`, section 0) | Every stored v8 licence still verifies | Not started |
+| Unit tests | none | — | Roadmap step E |
+
+The old regression and load-test project was disabled as flaky; nothing runs it.
+
+## Critical paths
+
+These must never break. Coverage is checked against this list before refactoring:
+
+1. Authentication and authorization (JWT, roles, 401/403)
+2. Customer management
+3. Work order creation, status flow, through to invoicing
+4. Quotes, PMs and contracts
+5. Scheduling
+6. Inventory and parts
+7. Reports
+8. Data lists: filtering, sorting, saved filters, paging
+9. Licence verification
+
+## Rules
+
+- **Prove a gate on the unmodified code** before changing anything, so a later failure points at the change, not at the test.
+- **Fix or delete a broken test.** Never leave it failing without a recorded reason. A known failure waiting on a server fix is recorded with its case number.
+- **Do not test what cannot be seeded.** Tests that need IDs not known at test time (such as warehouse IDs for stock levels) are left out and listed as such.
+- **`data-testid` attributes** for anything Cypress selects, so tests do not depend on CSS classes.
+- **Fixtures with customer data stay outside the repo**, found through an environment variable.
+- New tests go where they protect the most: back-end API first, then browser workflows.