- Добавлены TuiResources (sealed record), Locale, LocalizationService - Все строки интерфейса вынесены из .razor-файлов в TuiResources - App.razor: клавиша L циклически переключает локаль, заголовок показывает [EN]/[RU] - Дочерние компоненты получают Loc как параметр (stateless) - Создан AGENT.tui.md с правилами работы с TUI для агентов - Обновлены AGENTS.md и CLAUDE.md со ссылками на AGENT.tui.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
62 lines
4.3 KiB
Markdown
62 lines
4.3 KiB
Markdown
## 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`.
|