# AGENTS.md

## ⚠️ IMPORTANT: DO NOT USE BACKGROUND TASKS ⚠️

**DO NOT USE BACKGROUND TASKS. DO NOT USE BACKGROUND TASKS. DO NOT USE BACKGROUND TASKS.**

Background task support is currently unreliable. Never use:
- `run_in_background: true` on Bash commands
- Background task execution
- Any command that runs in the background

**DO NOT USE BACKGROUND TASKS.** This section will be removed when the feature works again.

---

## ⚠️ BASH + POWERSHELL INTEROP ⚠️

**The Bash tool runs under bash, not PowerShell.** PowerShell-specific syntax like `@()` arrays will cause bash parse errors.

**NEVER** use `pwsh -File script.ps1` when passing PowerShell arrays or complex arguments. Instead:

```bash
# WRONG — bash chokes on @()
pwsh -NoProfile -File scripts/release-dev.ps1 -ReleaseNotes @("note1", "note2")

# RIGHT — wrap everything in single quotes via -Command
pwsh -NoProfile -Command '& ./scripts/release-dev.ps1 -ReleaseNotes @("note1", "note2")'
```

**Rule:** Always use `pwsh -NoProfile -Command '...'` for any pwsh invocation that uses PowerShell-specific syntax (arrays, splatting, script blocks, etc.).

## Terminal Title

Set the terminal title to the current overall topic you are working on.

- Keep it between 5 and 30 characters
- Prefer a short task label, not a generic shell/program name
- Update it when the task focus changes

---

## Local Dev Loop First

For normal MidTerm iteration, do not default to a GitHub dev release.

Use this workflow first:
- keep the hard-installed MidTerm service on `https://localhost:2000` running as the stable supervisor
- start a separate local source instance on another port with `pwsh -NoProfile -Command '& ./scripts/dev.ps1'`
- open that local source instance in the MidTerm dev browser and do validation there
- keep using the installed release `mthost`
- keep iterating the local Debug `mtagenthost`
- only cut GitHub dev releases for release-worthy improvements that should be shared beyond the local source loop

`scripts/dev.ps1` is the preferred fast path and is expected to keep the source instance isolated from the installed service.

When using this loop:
- target the local source app in the MidTerm browser, usually `https://127.0.0.1:2100`
- validate Agent Controller Session and UX changes in that local browser before considering a GitHub dev release
- every C# change is a restart boundary for the local source `mt`: wait for `https://127.0.0.1:2100/api/sessions` to respond again before trusting any browser result
- a restarted source app can leave the dev preview/browser bridge stale even when the target URL still looks right; re-check `mt_status` after C# changes instead of assuming the existing preview is still controllable
- when using one MidTerm instance to test another MidTerm instance, remember the stable outer MidTerm tab owns the `/ws/state` browser bridge; the nested preview target alone is not enough
- if the browser looks stale after a TS/CSS iteration, prefer `mt_preview_reset` on the local source URL over guessing
- if a clean build/test is blocked by a lingering local source `mt.exe`, stop the local dev loop first and restart it after verification

---

## ⚠️ INSTALLER / SELF-UPDATER ROBUSTNESS ⚠️

**Changes to `install.sh`, `install.ps1`, or `UpdateScriptGenerator.cs` must NEVER cause user lockouts.**

The self-update script is generated by the **currently running version**. If it has bugs that cause updates to fail and rollback, users get stuck on that version forever with no way to self-heal.

**Priority order:**
1. **User can always update** — This is non-negotiable. A stuck user requires manual intervention.
2. **Losing certificates/passwords is acceptable** — These can be re-entered via the web UI.
3. **Service temporarily down is acceptable** — User can manually restart or reinstall.

**Rules:**
- Keep update logic simple and defensive
- Prefer failing forward (new version running) over rollback when possible
- Use correct platform checks (e.g., `secrets.json` on Unix, `secrets.bin` on Windows)
- Test the exact script that will be generated, not just the C# code
- Remember: the update script runs without user interaction

---

Guidance for AI coding agents working with this repository.

## What This Is

MidTerm is a web-based terminal multiplexer. Native AOT compiled, runs on macOS/Windows/Linux. Serves terminal sessions via browser at `http://localhost:2000`.

**Binaries:**
- `mt` / `mt.exe` — Web server (UI, REST API, WebSockets)
- `mthost` / `mthost.exe` — TTY host (spawned per terminal, all platforms)

**Settings locations:**
- Service mode: `%ProgramData%\MidTerm\settings.json` (Win) or `/usr/local/etc/midterm/settings.json` (Unix)
- User mode: `~/.midterm/settings.json`

**Platform defaults:**
- Windows — ConPTY, default shell `Pwsh`
- macOS — `forkpty`, default shell `zsh`
- Linux — `forkpty`, default shell `bash`

## Build Commands

```bash
# Build web server (debug)
dotnet build src/Ai.Tlbx.MidTerm/Ai.Tlbx.MidTerm.csproj

# Integration tests
dotnet test src/Ai.Tlbx.MidTerm.Tests/Ai.Tlbx.MidTerm.Tests.csproj

# Unit tests
dotnet test src/Ai.Tlbx.MidTerm.UnitTests/Ai.Tlbx.MidTerm.UnitTests.csproj

# AOT publish (platform-specific)
src/Ai.Tlbx.MidTerm/build-aot.cmd        # Windows
./src/Ai.Tlbx.MidTerm/build-aot-linux.sh # Linux
./src/Ai.Tlbx.MidTerm/build-aot-macos.sh # macOS
```

## OpenAPI Contract Generation

**Do not hand-edit generated OpenAPI artifacts.**

- `src/Ai.Tlbx.MidTerm/openapi/openapi.json` is generated by `Ai.Tlbx.MidTerm.OpenApi`
- `src/Ai.Tlbx.MidTerm/src/ts/api.generated.ts` is generated from that OpenAPI document during `frontend-build.ps1`
- If the generated contract is wrong, fix the source in `src/Ai.Tlbx.MidTerm.Api/` and `src/Ai.Tlbx.MidTerm.OpenApi/`, then rebuild
- When adding or changing REST endpoints, update the API contract layer first:
  1. Add/update endpoint definitions under `src/Ai.Tlbx.MidTerm.Api/Endpoints/`
  2. Add/update handler interfaces under `src/Ai.Tlbx.MidTerm.Api/Handlers/`
  3. Add/update stub handlers under `src/Ai.Tlbx.MidTerm.OpenApi/Stubs/`
  4. Regenerate the OpenAPI document and frontend types by running the normal build
- Never “patch the output” in `openapi.json` or `api.generated.ts` to make the frontend compile

## Asset Optimization

When adding new PNG assets, ask the user before running pngcrush for lossless compression:

```bash
C:/Tools/pngcrush/pngcrush_1_8_11_w64.exe -ow -reduce -brute <file.png>
```


## Testing

When adding new functionality beyond styling changes, consider adding tests. `Ai.Tlbx.MidTerm.Tests` uses xUnit with `WebApplicationFactory` for HTTP and WebSocket coverage, and `Ai.Tlbx.MidTerm.UnitTests` covers smaller units.

## Code Layout

- `src/Ai.Tlbx.MidTerm/` is the main web server plus frontend TypeScript.
- `src/Ai.Tlbx.MidTerm.TtyHost/` is the PTY host used for terminal processes.
- `src/Ai.Tlbx.MidTerm.Common/` holds shared protocol, logging, and shell abstractions.
- `src/Ai.Tlbx.MidTerm.Tests/` contains integration tests; `src/Ai.Tlbx.MidTerm.UnitTests/` contains unit tests.
- `scripts/` holds build, install, release, and promotion scripts.
- `docs/` holds product and implementation docs.
- `Services/` is organized by domain. Reuse an existing area if one fits: `Browser`, `Certificates`, `Git`, `Secrets`, `Security`, `Sessions`, `StaticFiles`, `Tmux`, `Updates`, `WebPreview`, `WebSockets`.
- `Models/` contains API DTOs grouped by domain.
- `Settings/` contains settings models, persistence, and JSON context.
- `src/ts/modules/` is feature-based frontend code. Extend an existing module when possible, export from its `index.ts`, and wire required callbacks in `main.ts`.
- `src/ts/stores/` holds nanostores; `state.ts` holds non-reactive browser infrastructure.
- HTTP routes are registered in `Startup/EndpointSetup.cs` and domain `*Endpoints.cs` files. Read the code instead of relying on static endpoint inventories.

## Settings Model Pattern

- `MidTermSettings` is the internal settings type and may include secrets.
- `MidTermSettingsPublic` is the API-safe subset exposed by `GET/PUT /api/settings`.
- When adding a user-editable setting:
  1. Add it to `MidTermSettings.cs`
  2. Add it to `MidTermSettingsPublic.cs` if it is non-sensitive
  3. Update `FromSettings()` and `ApplyTo()`
  4. Add JSON context entries if a new serialized type is involved
- `DevMode` exists in both settings types and enables developer-only features.

## AOT JSON Serialization

All types serialized via `System.Text.Json` must be registered in a source-generated context for Native AOT compatibility.

- Add new app-level types to `Services/AppJsonContext.cs`
- Add collection forms too when needed
- Use the domain-specific contexts where appropriate:
- `GitHubReleaseContext` - GitHub API response types
- `SecretsJsonContext` - Secret storage serialization
- `SettingsJsonContext` - Settings file serialization
- `VersionManifestContext` - Version manifest files

Red flags that usually mean missing context:
- Works in debug but fails in AOT publish
- `JsonException` mentioning "metadata" or "reflection"
- Serialization returns `{}`

## i18n Requirements

Every user-facing string must use the i18n system.

1. Add the key to `src/static/locales/en.json`
2. Add translations to all 9 other locale files (`zh`, `es`, `hi`, `fr`, `bn`, `pt`, `ru`, `ja`, `de`)
3. In TypeScript: use `t('key.name')` from `modules/i18n`
4. In HTML: use `data-i18n="key"`, `data-i18n-title="key"`, or `data-i18n-placeholder="key"`
5. Never use CSS `content:` for translatable text — use `content: attr(data-*)` with JS

What does not need translation: brand names, shell names, font names, theme identifiers, keyboard key names, and technical identifiers.

## WebSockets

- `/ws/mux` — Multiplexed terminal I/O (binary protocol)
- `/ws/state` — Session state changes (JSON, for sidebar sync)
- `/ws/settings` — Real-time settings sync across connected clients (JSON)

Before changing mux behavior, read `Services/WebSockets/MuxClient.cs` and `MuxProtocol`. Active sessions are sent immediately; background sessions are batched and compressed.

## Terminal Resize — CORE DESIGN PRINCIPLE

**The user decides when a terminal is resized. Never auto-resize existing sessions.**

- **No auto-resize** — terminals maintain their server-side dimensions across all clients
- **New sessions** created at optimal size for the creating client's viewport
- **Manual resize** via sidebar button (⤢) fits terminal to current screen
- Each terminal has independent dimensions
- **Multi-client scenario** (e.g., PC + iPad): connecting from a second device must NOT resize sessions. The second device shows terminals scaled (CSS transform) to fit its viewport. The user can explicitly press resize on the second device to claim optimal dimensions.
- **Page reload / reconnect**: must never send resize commands to the server. Terminals sync server dimensions and apply CSS scaling locally.
- **Layout restore from persisted state**: when a saved layout is restored on page load, terminals are moved to panes and CSS-scaled — NOT resized to fit pane dimensions. User must explicitly resize.
- **Scaled terminals** should be visually centered in their container, not top-left aligned.

## Tmux Compatibility Layer

MidTerm includes a tmux shim that allows AI coding tools to use split panes and related tmux behaviors natively.
- Controlled by `TmuxCompatibility` setting (default: true)
- Implemented under `Services/Tmux/`
- Used by tools that detect tmux and issue pane or send-keys commands

## Web Preview

MidTerm includes a reverse proxy for previewing local dev servers (or any website) inside an iframe panel.

- Implemented under `Services/WebPreview/` plus the frontend `web/` module
- Handles cookie bridging, URL rewriting, external resource proxying, and WebSocket proxying
- DOM snapshots save into `<cwd>/.midterm/snapshot_*/`
- **Before modifying any web preview proxy code, read `docs/devbrowser.md` first. After changes, update that document to reflect the new state.**
- For browser-control workflows inside a MidTerm terminal, see `.midterm/AGENTS.md`

## Code Style (C#)

- **Braces:** Allman (opening brace on new line)
- **Indent:** 4 spaces
- **Private fields:** `_camelCase`
- **Async methods:** `Async` suffix
- **Access modifiers:** Always explicit
- **Namespaces:** File-scoped (`namespace Foo;`)
- **Null checks:** `is null` / `is not null`
- **Comments:** Minimal, only for complex logic

## Code Style (TypeScript)

- **Braces:** K&R / One True Brace Style (opening brace on same line)
- **Indent:** 2 spaces
- **Semicolons:** Required
- **Quotes:** Single quotes for strings
- **Naming:** `camelCase` for values/functions, `PascalCase` for types/classes, `SCREAMING_SNAKE_CASE` for constants
- **Private members:** no underscore prefix; use TypeScript visibility modifiers
- **Types:** Always explicit return types on exported functions
- **Null handling:** Use `strictNullChecks`, prefer `undefined` over `null`
- **Comments:** JSDoc for exported functions, module header comment required

## Branch Strategy & Release Process

### ⚠️ MANDATORY WORKFLOW - DO NOT DEVIATE ⚠️

**ALL development happens on the `dev` branch. The `main` branch is for stable releases ONLY.**

```
dev (default branch - all work here)
  ↓ (PR merge when ready to release)
main (stable releases only - never commit directly)
```

**At the start of every session:** Check the current branch. If on `main`, switch to `dev` before making ANY changes. Never start work on `main`.

**If the user tries to:**
- Commit directly to `main` → **DECLINE.** Explain they must work on `dev` and promote via PR.
- Run `release.ps1` on `dev` → **DECLINE.** Explain they should use `release-dev.ps1` for dev releases.
- Run `release-dev.ps1` on `main` → **DECLINE.** Explain `main` only gets stable releases via promotion.
- Push changes to `main` without a PR → **DECLINE.** All changes to `main` come through PR merges from `dev`.
- Start making edits while on `main` → **STOP.** Switch to `dev` first, then proceed.

### Pull Strategy: Rebase (No Merge Bubbles)

All working copies use `pull.rebase = true` to keep a linear history. This avoids "Merge branch 'dev' of remote" noise commits when multiple machines push to `dev`.

```powershell
# Set per-repo (already configured in this repo)
git config pull.rebase true

# Or set globally for all repos on a machine
git config --global pull.rebase true
```

If a rebase gets messy, abort and fall back to a one-time merge pull:
```powershell
git rebase --abort
git pull --no-rebase
```

### Dev Release Procedure (Agent Instructions)

Only use this when Johannes explicitly asks for a release or when the change is substantial enough to justify a shared installable build.

When the user asks to "do a dev release" (minor/major/patch), follow this exact sequence:

**Step 1 — Verify branch & clean state**
```powershell
git branch --show-current   # Must be "dev". If not, switch: git checkout dev
git status --short          # Check for uncommitted changes
```
If there are uncommitted changes: commit them first with a good, specific message and continue the release flow. A user request like "do a patch release" or "do a dev release" already implies the full workflow: inspect the diff, create the commit yourself, push, draft release title/notes, and run `release-dev.ps1` without stopping just because a commit is needed.

**Step 2 — Integrate remote changes**
```powershell
git pull --rebase           # pull.rebase=true is configured, but be explicit
```
If rebase fails: `git rebase --abort` then `git pull --no-rebase` as fallback.

**Step 3 — Push local commits**
```powershell
git push
```
The release script will fail if local is ahead of remote without pushing first (it fetches and compares).

**Step 4 — Build release notes from commit history**
```powershell
# Find what's new since last tag
git log --oneline $(git describe --tags --abbrev=0)..HEAD
```
Use these commits to draft `-ReleaseTitle` (concise headline) and `-ReleaseNotes` (array of meaningful changelog entries). **Do NOT ask the user to confirm release titles or notes — write them yourself with care and proceed immediately.** The user trusts you to make good choices, and asking breaks their multi-agent workflow.

**Step 5 — Determine `-mthostUpdate`**
- Check if any commits touched `Ai.Tlbx.MidTerm.TtyHost/`, `Ai.Tlbx.MidTerm.Common/`, or protocol code → `yes`
- Check if `Ai.Tlbx.MidTerm.AgentHost/` changed in a way that must ship to running installs → `yes`
- Web-only / frontend-only changes → `no`
- If unclear, default to `yes` (safer)

**Step 6 — Run the release script**
```powershell
pwsh -NoProfile -File scripts/release-dev.ps1 `
    -Bump <major|minor|patch> `
    -ReleaseTitle "Concise headline" `
    -ReleaseNotes @("Detailed entry 1", "Detailed entry 2") `
    -mthostUpdate <yes|no>
```

**Post-release:** After any release (dev or stable), your final message must include both:
- the release result (`version`, branch/tag/push status, and whether it completed successfully)
- a short "what's in this release" summary with 2-5 concrete shipped items

Do not send a release completion message that only reports the version/tag. The user runs 100+ agents simultaneously and needs instant context about what actually shipped.

**Common mistakes to avoid:**
- Running the release with uncommitted changes (script commits version.json — dirty tree causes conflicts)
- Forgetting to push before releasing (script detects diverged branches and aborts)
- Vague release notes like "Fix bug" or "Update UI" (script rejects notes < 20 chars)

### Promoting to Stable Release

When dev is tested and ready for stable release, use `promote.ps1` from the dev branch:

```powershell
# Auto-gathers all changelog entries from dev tags since last stable release (recommended)
.\scripts\promote.ps1

# Override title, still auto-gather notes
.\scripts\promote.ps1 -ReleaseTitle "Major UI overhaul"

# Fully manual (legacy behavior)
.\scripts\promote.ps1 -ReleaseTitle "Feature X" -ReleaseNotes @("Added feature X")
```

The script automatically:
1. Finds all dev tags since the last stable release (by version comparison)
2. Extracts changelog entries from each tag's annotation
3. Creates a PR with grouped changelog, merges it
4. Tags the stable release on main with the combined changelog

### Update Channels

Users can choose which releases to receive via `settings.json`:

| Channel | Setting | Receives |
|---------|---------|----------|
| Stable (default) | `"updateChannel": "stable"` | Stable releases from `main` (web-only or full, e.g. `v6.10.32`) |
| Dev | `"updateChannel": "dev"` | Dev prereleases plus stable releases (web-only or full, e.g. `v6.10.32-dev`) |

### Version Management in Dev

`release-dev.ps1` automatically reads `main:src/version.json` to ensure dev versions are always >= main. If main is at `6.16.0` and dev is behind, the script uses main's version as the base before bumping. **Never manually set src/version.json on main** — versions flow from dev→main via PR merges.

### Release Script Parameters

Both scripts share the same parameters:
- `-Bump`: `major`, `minor`, or `patch`
- `-ReleaseTitle`: One-line headline (NO version number)
- `-ReleaseNotes`: MANDATORY array of detailed changelog entries
- `-mthostUpdate`: the single low-level-runtime switch

With `-mthostUpdate no`: web-only release. Running installs preserve their current `mthost` and `mtagenthost`.
With `-mthostUpdate yes`: full runtime refresh. Running installs replace both `mthost` and `mtagenthost`.

**Good ReleaseNotes:**
- "Fixed bug where settings panel would close when checking for updates"
- "Complete rewrite of update script with 6-phase process and automatic rollback"

**Bad ReleaseNotes:**
- "Fix bug" (too vague)
- "Update UI" (what specifically?)

## Install System

**Scripts:** `install.ps1` (Windows), `install.sh` (macOS/Linux)

Flow: choose system vs user install, set the password, download and extract binaries, write settings, then register the service if needed.

Password preservation: install scripts preserve an existing `passwordHash` during updates.

## ⚠️ Version Management - DO NOT CHANGE ⚠️

**version.json is the single source of truth for all version numbers.**

Both csproj files read their versions dynamically at build time via MSBuild targets:
- `Ai.Tlbx.MidTerm.csproj` reads `web` → `$(WebVersion)`
- `Ai.Tlbx.MidTerm.TtyHost.csproj` reads `pty` → `$(PtyVersion)`

**DO NOT:**
- Add hardcoded `<Version>X.Y.Z</Version>` to csproj files
- Pass `-p:Version` to dotnet build/publish commands
- Update csproj files in release scripts

**Release scripts only update version.json** - the csproj files pick up the new version automatically on next build.

This architecture exists because hardcoded versions caused update failures where the wrong version was baked into binaries.

## Important Rules

- Never `dotnet run` without user permission
- Never `Task.Run` unless explicitly asked for threading
- Aim for 0 build warnings
- Use interfaces + DI, not static classes
- Platform checks: `OperatingSystem.IsWindows()`, `.IsLinux()`, `.IsMacOS()`
- **After plan executions or large implementation tasks finish**, prefer the local source dev loop first; only offer a GitHub dev release when the result is release-worthy

## Code Discoverability

New code should be easy to find by folder, file name, and type name alone.

- File names should match contents. `FooEndpoints` is routing only; `FooService` is business logic; `*Middleware` is middleware.
- Namespaces should follow folders.
- Use clear domain names, not internal nicknames.
- Endpoint files stay thin; heavy logic belongs in services.
- Extract duplicated logic instead of maintaining parallel copies.
- Keep one concern per file.
