|
| 1 | +You are an experienced, pragmatic software engineering AI agent. Do not over-engineer a solution when a simple one is possible. Keep edits minimal. If you want an exception to ANY rule, you MUST stop and get permission first. |
| 2 | + |
| 3 | +# Project Overview |
| 4 | + |
| 5 | +This repository contains the Linux-focused Coder Desktop implementation in C#/.NET. The app provides tray-based access to Coder workspaces through a local VPN-like tunnel (`Coder Connect`) and includes Linux packaging assets for distribution. |
| 6 | + |
| 7 | +Primary goals: |
| 8 | +- Provide a Linux desktop/tray UX for connecting to Coder workspaces. |
| 9 | +- Coordinate with a local/root VPN service over RPC. |
| 10 | +- Ship installable Linux artifacts (`.deb`, `.rpm`, `.tar.gz`, and AUR source bundle). |
| 11 | + |
| 12 | +Technology choices: |
| 13 | +- **Language/runtime:** C# on .NET 8 (`net8.0` across projects) |
| 14 | +- **UI:** Avalonia 11 (`App.Avalonia`) |
| 15 | +- **Architecture:** DI with `Microsoft.Extensions.*`, shared ViewModel/service layer in `App.Shared` |
| 16 | +- **RPC/protocol:** Protobuf messages (`Vpn.Proto/vpn.proto`) + custom stream RPC (`Vpn/Speaker.cs`) |
| 17 | +- **Logging:** Serilog + Microsoft.Extensions.Logging |
| 18 | +- **Testing:** NUnit (`Tests.*` projects) |
| 19 | +- **Packaging:** Bash scripts under `Packaging.Linux/` |
| 20 | + |
| 21 | +# Reference |
| 22 | + |
| 23 | +## Important files |
| 24 | + |
| 25 | +- `Coder.Desktop.sln` — solution entrypoint for all projects |
| 26 | +- `App.Avalonia/App.axaml.cs` — application composition root (DI/service registration, startup flow) |
| 27 | +- `App.Avalonia/Program.cs` — UI process entrypoint + bootstrap logging |
| 28 | +- `App.Shared/Services/RpcController.cs` — app-side RPC lifecycle/state orchestration |
| 29 | +- `Vpn/Speaker.cs` — generic request/reply protocol transport over streams |
| 30 | +- `Vpn.Proto/vpn.proto` — protocol contract used by app/service/tunnel |
| 31 | +- `Vpn.Service/Program.cs` — VPN service host, config sources, platform transport wiring |
| 32 | +- `scripts/run-linux-dev.sh` — local Linux dev launcher (service + app) |
| 33 | +- `.github/workflows/ci.yaml` — CI source of truth for baseline build/test checks |
| 34 | +- `.editorconfig` — formatting/style baseline (including line endings) |
| 35 | + |
| 36 | +## Important directories |
| 37 | + |
| 38 | +- `App.Avalonia/` — Avalonia UI, views, controls, Linux desktop host wiring |
| 39 | +- `App.Shared/` — cross-UI application logic (ViewModels/Models/Services abstractions) |
| 40 | +- `App.Linux/` — Linux-only service implementations (startup, credential backend, notifications) |
| 41 | +- `Vpn.Service/` — long-running manager service (systemd-compatible on Linux) |
| 42 | +- `Vpn/`, `Vpn.Linux/`, `Vpn.Proto/` — RPC core, Linux transports, protobuf schema |
| 43 | +- `CoderSdk/`, `MutagenSdk/` — API clients and vendored proto-related integration code |
| 44 | +- `Tests.CoderSdk/`, `Tests.Vpn*/` — NUnit unit/integration-style test projects |
| 45 | +- `Packaging.Linux/` — package build scripts, unit files, desktop metadata |
| 46 | +- `scripts/` — developer automation scripts |
| 47 | + |
| 48 | +## Project architecture (high level) |
| 49 | + |
| 50 | +1. `App.Avalonia` boots and builds DI container in `App.axaml.cs`. |
| 51 | +2. `RpcController` in `App.Shared` connects to the local service via `IRpcClientTransport` (`UnixSocketClientTransport` on Linux). |
| 52 | +3. RPC is executed via `Speaker<TSend,TReceive>` request/reply framing and protobuf payloads. |
| 53 | +4. `Vpn.Service` hosts service logic (`Manager`, `TunnelSupervisor`, etc.) and exposes RPC over Unix socket. |
| 54 | +5. Packaging scripts stage/publish both app and service binaries into Linux package formats. |
| 55 | + |
| 56 | +# Essential commands |
| 57 | + |
| 58 | +Run all commands from repository root. |
| 59 | + |
| 60 | +## Build |
| 61 | + |
| 62 | +```bash |
| 63 | +dotnet restore Coder.Desktop.sln |
| 64 | +dotnet build Coder.Desktop.sln -c Release --no-restore |
| 65 | +``` |
| 66 | + |
| 67 | +## Format |
| 68 | + |
| 69 | +```bash |
| 70 | +dotnet format Coder.Desktop.sln |
| 71 | +``` |
| 72 | + |
| 73 | +## Lint |
| 74 | + |
| 75 | +No dedicated linter script exists. Use formatting/analyzer verification: |
| 76 | + |
| 77 | +```bash |
| 78 | +dotnet format Coder.Desktop.sln --verify-no-changes |
| 79 | +``` |
| 80 | + |
| 81 | +## Test |
| 82 | + |
| 83 | +Match CI coverage: |
| 84 | + |
| 85 | +```bash |
| 86 | +dotnet test Tests.CoderSdk/Tests.CoderSdk.csproj --no-restore -v q |
| 87 | +dotnet test Tests.Vpn.Proto/Tests.Vpn.Proto.csproj --no-restore -v q |
| 88 | +dotnet test Tests.Vpn/Tests.Vpn.csproj --no-restore -v q |
| 89 | +dotnet test Tests.Vpn.Service/Tests.Vpn.Service.csproj --no-restore -v q |
| 90 | +``` |
| 91 | + |
| 92 | +## Clean |
| 93 | + |
| 94 | +```bash |
| 95 | +dotnet clean Coder.Desktop.sln |
| 96 | +``` |
| 97 | + |
| 98 | +## Development server / local run |
| 99 | + |
| 100 | +```bash |
| 101 | +./scripts/run-linux-dev.sh --show --sudo-service |
| 102 | +``` |
| 103 | + |
| 104 | +Useful variants: |
| 105 | + |
| 106 | +```bash |
| 107 | +./scripts/run-linux-dev.sh --no-build -- --minimized |
| 108 | +./scripts/run-linux-dev.sh --help |
| 109 | +``` |
| 110 | + |
| 111 | +## Other important scripts |
| 112 | + |
| 113 | +```bash |
| 114 | +# Multi-format Linux packages (deb/rpm/tar) |
| 115 | +VERSION=1.2.3 ./Packaging.Linux/build-release-packages.sh amd64 |
| 116 | + |
| 117 | +# AUR source bundle |
| 118 | +VERSION=1.2.3 ./Packaging.Linux/build-aur-source.sh |
| 119 | + |
| 120 | +# Deb-only helper wrapper |
| 121 | +VERSION=1.2.3 ./Packaging.Linux/build-deb.sh amd64 |
| 122 | + |
| 123 | +# Refresh vendored Mutagen proto tree |
| 124 | +pwsh ./MutagenSdk/Update-Proto.ps1 -mutagenTag <tag> |
| 125 | +``` |
| 126 | + |
| 127 | +# Patterns |
| 128 | + |
| 129 | +- **Keep App.Shared UI-framework-agnostic.** |
| 130 | + - Do: expose abstractions (`IWindowService`, `IDispatcher`) and neutral types. |
| 131 | + - Don’t: instantiate Avalonia-specific UI types in `App.Shared`. |
| 132 | + |
| 133 | +- **UI thread handoff in ViewModels is explicit.** |
| 134 | + - Do: check `_dispatcher.CheckAccess()` and repost with `_dispatcher.Post(...)` when required. |
| 135 | + - Don’t: mutate UI-bound observable state from background threads. |
| 136 | + |
| 137 | +- **Avoid redundant property writes in ViewModels.** |
| 138 | + - Do: compare old/new before assigning to reduce flashing and unnecessary notifications. |
| 139 | + - Don’t: blindly reassign observable properties during model refreshes. |
| 140 | + |
| 141 | +- **RPC request/reply discipline.** |
| 142 | + - Do: use `SendRequestAwaitReply` for command flows needing responses and preserve message type checks. |
| 143 | + - Don’t: ignore unexpected message types or skip lifecycle transitions on failures. |
| 144 | + |
| 145 | +- **Testing style.** |
| 146 | + - NUnit is standard; async tests commonly include `[CancelAfter(30_000)]` to avoid hangs. |
| 147 | + |
| 148 | +# Anti-patterns |
| 149 | + |
| 150 | +- **Do not wire both tray icon `Command` and click event behavior** in ways that double-trigger UI toggles (`App.Avalonia/App.axaml.cs` explicitly avoids this). |
| 151 | +- **Do not assume unvalidated URLs are safe for UI hyperlink controls**; invalid values can crash UI paths (see `TrayWindowViewModel` comments). |
| 152 | +- **Do not manually edit vendored/generated proto imports in `MutagenSdk/Proto/`** when they originate from Mutagen; regenerate via `Update-Proto.ps1`. |
| 153 | +- **Do not bypass serialization/lock patterns in RPC state management** (`RpcController` uses explicit operation/state locks for consistency). |
| 154 | + |
| 155 | +# Code style |
| 156 | + |
| 157 | +Source of truth: `.editorconfig` |
| 158 | + |
| 159 | +Key rules currently in effect: |
| 160 | +- Default indentation: 4 spaces |
| 161 | +- JSON/YAML indentation: 2 spaces |
| 162 | +- `.proto` indentation: tabs (`indent_size = 1`) |
| 163 | +- Default line endings: `CRLF` |
| 164 | +- UTF-8, trim trailing whitespace, final newline |
| 165 | +- C# style prefers `var` in common scenarios and nullable reference types are enabled |
| 166 | + |
| 167 | +When editing files, match surrounding style and existing naming/structure patterns. |
| 168 | + |
| 169 | +# Commit and Pull Request Guidelines |
| 170 | + |
| 171 | +## Before committing |
| 172 | + |
| 173 | +- Run at minimum the CI-equivalent build/test flow for impacted areas. |
| 174 | +- If you touched shared/protocol/service code, run all test commands listed above. |
| 175 | +- If you touched packaging, run the relevant packaging script(s) and capture outputs/artifact names. |
| 176 | + |
| 177 | +## Commit messages |
| 178 | + |
| 179 | +Current history is minimal and imperative (e.g., `add linux release packaging workflow`). |
| 180 | + |
| 181 | +Preferred format for new commits: |
| 182 | +- `type: concise imperative summary` |
| 183 | +- Examples: `fix: handle rpc reconnect timeout`, `build: adjust linux package dependencies` |
| 184 | + |
| 185 | +If no clear type applies, use a concise imperative summary rather than vague text. |
| 186 | + |
| 187 | +## Pull request description requirements |
| 188 | + |
| 189 | +No PR template is present in this repository, so include: |
| 190 | +- **What changed** (bullet list) |
| 191 | +- **Why** (user/problem impact) |
| 192 | +- **How it was validated** (exact commands + results) |
| 193 | +- **Risk/rollback notes** for service, protocol, or packaging changes |
| 194 | +- **Screenshots/video** for tray/UI behavior changes |
0 commit comments