## AGENT.tui.md

Read this file whenever you touch anything in `LazyBear.MCP/TUI/`.

### TUI Structure

- Entry point: `TUI/Components/App.razor` — owns all keyboard input and tab routing.
- Keyboard input: `TUI/GlobalKeyboardService.cs` — single source; dedicated blocking thread on `Console.ReadKey(intercept: true)`. Do not add other console readers.
- Localization: `TUI/Localization/` — `LocalizationService` singleton + `TuiResources` record with `En`/`Ru` static instances.
- Components: `OverviewTab`, `LogsTab`, `SettingsTab` — pure display; receive state as parameters, fire no keyboard events.
- Models: `TUI/Models/` — `OverviewRow`, `SettingsEntry`.
- Palette: `TUI/UiPalette.cs` — all colors. Do not hardcode `Spectre.Console.Color` values in components.

### Keyboard Rules

- All keys are handled in `App.razor.HandleKeyDown()`. Do not attach `@onkeydown` to `<Select>` or any other RazorConsole component — framework intercepts Tab and arrows before they reach component-level callbacks.
- `GlobalKeyboardService` fires `OnKeyPressed(ConsoleKeyInfo)`. `App.razor` converts via `ConvertKey()` and calls `HandleKeyDown()` inside `InvokeAsync`.
- Do not use `Console.KeyAvailable` polling — it acquires the console mutex on every call and causes rendering lag. Always use blocking `Console.ReadKey` in a dedicated thread.
- Keyboard shortcuts: `Tab`/`Shift+Tab` — switch tabs; Arrows — navigate list; `Space` — toggle; `Enter` — open/expand; `L` — cycle language.

### RazorConsole Gotchas (0.5.0)

- `@onkeydown` on `<Select>` is captured as `AdditionalAttributes` and not called for Tab/arrow keys.
- `FocusManager` intercepts Tab globally — do not call `FocusNextAsync()` in `OnAfterRenderAsync` unconditionally; it shifts focus on every re-render.
- No public global key-intercept API exists in 0.5.0.
- `Select.Value` = committed selection (updated on Enter). `Select.FocusedValue` = highlighted item during navigation. Use `FocusedValue` to reflect external state immediately.
- `StateHasChanged()` from a background thread must go through `InvokeAsync(() => { /* mutate state */ StateHasChanged(); })`.
- Every `StateHasChanged()` triggers a full terminal redraw. Batch log-driven re-renders to avoid visible lag.

### Localization Rules

- All UI strings live in `TUI/Localization/TuiResources.cs` only. No hardcoded strings in `.razor` files or other `.cs` files.
- To add a string: add a property to `TuiResources`, fill both `En` and `Ru` static instances. Build will fail if a `required init` is missing — by design.
- Log level names (`Info`, `Warn`, `Error`) stay in English in all locales — they are technical identifiers, not UI labels.
- Internal filter keys in `App.razor` (`"All"`, `"Info"`, `"Warn"`, `"Error"`) are English regardless of locale; `LogsTab` maps `"All"` → `Loc.FilterAll` for display.
- Language toggle: `L` key cycles through locales. `LocalizationService.SwitchNext()` fires `OnChanged`; `App.razor` re-renders via `OnLocaleChanged()`.
- Locale indicator shown in panel title: `"LazyBear MCP  [EN]"` / `"LazyBear MCP  [RU]"`.
- Do not inject `LocalizationService` into child tab components — `App.razor` passes the current `TuiResources` as `Loc` parameter. Child components are stateless regarding locale.

### Component Contract

- Child tab components (`OverviewTab`, `LogsTab`, `SettingsTab`) accept `[Parameter] TuiResources Loc` — always pass it from `App.razor`.
- Child components must not subscribe to events or inject services. Keep them as pure render components.
- `SelectedIndexChanged` callbacks exist for forward-compatibility; actual selection state is managed exclusively in `App.razor`.

### DI Registration Pattern

Services that must be both injectable and run as hosted services:
```csharp
services.AddSingleton<MyService>();
services.AddHostedService(sp => sp.GetRequiredService<MyService>());
```
`AddHostedService<T>()` alone creates a transient instance — unusable as injectable singleton.

### Working Rules

- Read `App.razor` before touching any keyboard or tab logic.
- Match the style of the file being edited.
- After changes, run `dotnet build`.
- Do not add new hardcoded strings to `.razor` files — add to `TuiResources` instead.
- Do not add new `Console.KeyAvailable` or `Console.ReadKey` calls outside `GlobalKeyboardService`.
