## 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 `` 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(); services.AddHostedService(sp => sp.GetRequiredService()); ``` `AddHostedService()` 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`.