diff --git a/.claude/plan/arma3-ux-enhancement.md b/.claude/plan/arma3-ux-enhancement.md
new file mode 100644
index 0000000..6a23325
--- /dev/null
+++ b/.claude/plan/arma3-ux-enhancement.md
@@ -0,0 +1,891 @@
+# Plan: Arma 3 Adapter UX Enhancement
+
+**Status:** APPROVED — Ready to implement
+**Branch:** main
+**Estimated effort:** ~20h total (6 phases)
+
+---
+
+## Background & Context
+
+### What was analyzed
+
+`arma-server-web-admin` (Node.js/Express + Backbone.js) was deep-analyzed as a UX benchmark. Full documentation was written to `E:\TestScript\arma-server-web-admin\docs\`:
+- `ANALYSIS.md` — feature inventory, tech stack, directory structure
+- `HOW_IT_WORKS.md` — internal flows (server start/stop, mission upload, mod discovery, Socket.IO bridge)
+- `CHERRY_PICK.md` — adapter candidates with file paths and adapter strategies
+
+**You do NOT need to re-read arma-server-web-admin.** All relevant patterns are self-contained below.
+
+### Problem statement
+
+languard-servers-manager has a **complete backend** (42+ endpoints, Arma3ConfigGenerator, Arma3MissionManager, Arma3ModManager, Arma3BanManager, Arma3RemoteAdmin) but the **frontend has gaps** that make daily Arma 3 server administration painful:
+
+| Problem | Root cause |
+|---------|-----------|
+| All config fields render as text boxes | ConfigEditor is generic; no per-field widget hints |
+| Can't build a mission rotation | Backend config supports `missions[]` array; no UI |
+| Mods show `@CBA_A3` not "Community Base Addons" | `mod.cpp` not parsed; no `display_name` field |
+| Can't kick a player from the UI | `Arma3RemoteAdmin.kick_player()` exists; endpoint missing |
+| Can't browse/download historical log files | Only real-time WebSocket stream; no file browser |
+| Must navigate to detail page to start/stop | No quick-action buttons on ServerCard |
+
+---
+
+## Cross-Reference: Cherry-Pick Classification
+
+### MUST HAVE
+
+| Feature | Gap in languard |
+|---------|----------------|
+| Config field UI widgets (textarea/dropdown/toggle/tag-list) | Generic `` for everything |
+| Kick player from Players tab | `Arma3RemoteAdmin.kick_player(slot_id, reason)` exists but no HTTP endpoint |
+| Mission rotation table + per-mission difficulty | Backend: `Arma3MissionManager.get_rotation_config()` exists. Frontend: nothing |
+| Multi-file mission upload | `useUploadMission` accepts single `File` only |
+
+### GOOD TO HAVE
+
+| Feature | Gap |
+|---------|-----|
+| Mod display names (mod.cpp parsing) | `list_available_mods()` returns path only, no `display_name` |
+| Split-pane mod assignment UI | Flat checkbox list |
+| Server card start/stop quick actions | Must navigate to ServerDetailPage |
+| Log file browser + download | WebSocket stream only |
+| Admin UIDs tag list in config | Hidden in raw JSON config editor |
+| Ban from player list quick action | User must go to Bans tab manually |
+
+### OPTIONAL
+
+| Feature | Note |
+|---------|------|
+| Steam Workshop mission download | Requires external `steamcmd` binary |
+| Mod Steam Workshop ID (meta.cpp) | Informational only |
+| Log viewer level filter | Pure client-side |
+| Headless client count in UI | Niche advanced feature |
+
+---
+
+## Current Codebase — Critical File Inventory
+
+### Backend (FastAPI + Python)
+
+```
+backend/
+├── adapters/arma3/
+│ ├── adapter.py # Arma3Adapter — registers capabilities, returns sub-managers
+│ ├── config_generator.py # Arma3ConfigGenerator
+│ │ SECTIONS: ServerSection, BasicSection, ProfileSection,
+│ │ LaunchSection, RconSection
+│ │ KEY FIELDS IN ServerSection:
+│ │ hostname (str), max_players (int), password (str),
+│ │ admin_password (str), motd_lines (list[str]),
+│ │ forced_difficulty (str), battle_eye (bool), von (bool),
+│ │ admin_uids (list[str])
+│ │ KEY FIELDS IN LaunchSection:
+│ │ additional_args (list[str])
+│ │ METHODS: get_sections(), write_configs(), build_launch_args(),
+│ │ get_sensitive_fields(), get_defaults()
+│ │ ADD: get_ui_schema() -> dict
+│ ├── mission_manager.py # Arma3MissionManager
+│ │ list_missions() -> [{name, filename, size_bytes}]
+│ │ parse_mission_filename(fn) -> {mission_name, terrain, filename}
+│ │ get_rotation_config(entries) -> Arma3 missions config block string
+│ │ UPDATE: list_missions() add terrain field
+│ ├── mod_manager.py # Arma3ModManager
+│ │ list_available_mods() -> [{name, path, size_bytes, enabled}]
+│ │ get_enabled_mods(), set_enabled_mods(), build_mod_args()
+│ │ UPDATE: add display_name, workshop_id to list_available_mods()
+│ ├── log_parser.py # RPTParser
+│ │ parse_line(), get_log_file_resolver()
+│ │ ADD: list_log_files(server_dir), get_log_file_path(server_dir, filename)
+│ ├── remote_admin.py # Arma3RemoteAdmin (DO NOT MODIFY — stable)
+│ │ kick_player(slot_id, reason) -> bool ← USE THIS
+│ │ ban_player(uid, duration_minutes, reason) -> bool
+│ │ get_players() -> list[dict]
+│ └── ban_manager.py # Arma3BanManager — bans.txt sync (stable)
+│
+├── core/servers/
+│ ├── router.py # Main server endpoints
+│ │ ADD: GET /api/servers/{id}/config/schema
+│ ├── service.py # ServerService
+│ │ ADD: get_config_schema(), kick_player(), ban_from_player()
+│ ├── missions_router.py # GET/POST/DELETE /api/servers/{id}/missions/*
+│ │ ADD: GET /api/servers/{id}/missions/rotation
+│ │ ADD: PUT /api/servers/{id}/missions/rotation
+│ ├── players_router.py # GET /api/servers/{id}/players
+│ │ ADD: POST /api/servers/{id}/players/{slot_id}/kick
+│ │ ADD: POST /api/servers/{id}/players/{slot_id}/ban
+│ └── [NEW] logfiles_router.py
+│ GET /api/servers/{id}/logfiles
+│ GET /api/servers/{id}/logfiles/{filename}/download
+│ DELETE /api/servers/{id}/logfiles/{filename}
+│
+└── main.py # ADD: include_router(logfiles_router)
+```
+
+### Frontend (React 19 + TypeScript + TanStack Query)
+
+```
+frontend/src/
+├── hooks/
+│ └── useServerDetail.ts # All query/mutation hooks for server detail
+│ ADD: useServerConfigSchema()
+│ ADD: useServerMissionRotation(), useUpdateMissionRotation()
+│ ADD: useKickPlayer(), useBanPlayer()
+│ ADD: useServerLogFiles(), useDeleteLogFile()
+│ UPDATE: useUploadMission(File[]) — was single File
+│ UPDATE: Mod type — add display_name, workshop_id
+│ UPDATE: Mission type — add terrain field
+│
+├── components/servers/
+│ ├── ConfigEditor.tsx # UPDATE: consume schema, render per-widget type
+│ ├── MissionList.tsx # REDESIGN: Available section + Rotation section
+│ ├── ModList.tsx # REDESIGN: split pane (Available vs Selected)
+│ ├── PlayerTable.tsx # UPDATE: add Actions column (Kick/Ban buttons, admin only)
+│ ├── LogViewer.tsx # UPDATE: level filter + Log Files browser section
+│ └── ServerCard.tsx # UPDATE: Start/Stop quick-action buttons
+│
+└── components/ui/
+ └── [NEW] TagListEditor.tsx # Dynamic string-list editor (reused in ConfigEditor)
+```
+
+---
+
+## Execution Order
+
+| Phase | Description | Priority | Est. |
+|-------|-------------|----------|------|
+| 1 | Config UI Schema (widget hints per field) | MUST | ~4h |
+| 4 | Player Kick/Ban | MUST | ~3h |
+| 2 | Mission Rotation + Multi-file upload | MUST | ~5h |
+| 3 | Mod Display Names + Split Pane | GOOD | ~4h |
+| 6 | Server Card Quick Actions | GOOD | ~1h |
+| 5 | Log File Browser + Level Filter | GOOD | ~3h |
+
+---
+
+## Phase 1 — Config UI Schema System (MUST HAVE, ~4h)
+
+**Goal:** Each Arma 3 config field renders with the right UI widget instead of a generic text box.
+
+### 1.1 `backend/adapters/arma3/config_generator.py` — add `get_ui_schema()`
+
+Add this method to `Arma3ConfigGenerator`:
+
+```python
+def get_ui_schema(self) -> dict:
+ return {
+ "server": {
+ "hostname": {"widget": "text", "label": "Server Hostname"},
+ "max_players": {"widget": "number", "label": "Max Players", "min": 1, "max": 256},
+ "password": {"widget": "password", "label": "Player Password"},
+ "admin_password": {"widget": "password", "label": "Admin Password"},
+ "motd_lines": {"widget": "textarea", "label": "Message of the Day (one line per row)"},
+ "forced_difficulty": {"widget": "select", "label": "Difficulty Preset",
+ "options": ["", "Recruit", "Regular", "Veteran", "Custom"]},
+ "battle_eye": {"widget": "toggle", "label": "BattleEye Anti-Cheat"},
+ "von": {"widget": "toggle", "label": "Voice over Net (VoN)"},
+ "verify_signatures": {"widget": "toggle", "label": "Verify Addon Signatures"},
+ "persistent": {"widget": "toggle", "label": "Persistent (keep running when empty)"},
+ "admin_uids": {"widget": "tag-list", "label": "Admin Steam UIDs",
+ "placeholder": "76561198000000000"},
+ },
+ "basic": {
+ "max_packet_size": {"widget": "number", "label": "Max Packet Size"},
+ "max_custom_file_size": {"widget": "number", "label": "Max Custom File Size (bytes)"},
+ },
+ "launch": {
+ "additional_args": {"widget": "tag-list", "label": "Additional Startup Parameters",
+ "placeholder": "-limitFPS=100"},
+ },
+ "rcon": {
+ "password": {"widget": "password", "label": "RCon Password"},
+ "port": {"widget": "number", "label": "RCon Port"},
+ },
+ }
+```
+
+### 1.2 `backend/core/servers/service.py` — add `get_config_schema()`
+
+```python
+async def get_config_schema(self, server_id: int, db: Session) -> dict:
+ server = self.server_repo.get_by_id(server_id, db)
+ adapter = self.adapter_registry.get(server.game_type)
+ config_gen = adapter.get_config_generator()
+ if hasattr(config_gen, "get_ui_schema"):
+ return config_gen.get_ui_schema()
+ return {}
+```
+
+### 1.3 `backend/core/servers/router.py` — new endpoint
+
+Add after existing config routes:
+```python
+@router.get("/{server_id}/config/schema")
+async def get_config_schema(
+ server_id: int,
+ db: Session = Depends(get_db),
+ current_user: User = Depends(get_current_user),
+):
+ schema = await server_service.get_config_schema(server_id, db)
+ return {"success": True, "data": schema}
+```
+
+### 1.4 `frontend/src/hooks/useServerDetail.ts` — add schema types + hook
+
+```typescript
+export interface FieldSchema {
+ widget: "text" | "number" | "password" | "textarea" | "select" | "toggle" | "tag-list";
+ label?: string;
+ placeholder?: string;
+ min?: number;
+ max?: number;
+ options?: string[];
+}
+
+export interface ConfigSchema {
+ [section: string]: { [field: string]: FieldSchema };
+}
+
+export function useServerConfigSchema(serverId: number) {
+ return useQuery({
+ queryKey: ["servers", serverId, "config", "schema"],
+ queryFn: async () => {
+ const res = await apiClient.get<{ success: boolean; data: ConfigSchema }>(
+ `/api/servers/${serverId}/config/schema`,
+ );
+ return res.data.data;
+ },
+ enabled: serverId > 0,
+ });
+}
+```
+
+### 1.5 `frontend/src/components/ui/TagListEditor.tsx` — NEW component
+
+```typescript
+interface TagListEditorProps {
+ value: string[];
+ onChange: (v: string[]) => void;
+ placeholder?: string;
+ disabled?: boolean;
+}
+
+export function TagListEditor({ value, onChange, placeholder, disabled }: TagListEditorProps) {
+ const update = (idx: number, val: string) =>
+ onChange(value.map((v, i) => (i === idx ? val : v)));
+ const remove = (idx: number) => onChange(value.filter((_, i) => i !== idx));
+ const add = () => onChange([...value, ""]);
+
+ return (
+
+ {value.map((item, idx) => (
+
+ update(idx, e.target.value)}
+ />
+
+
+ ))}
+
+
+ );
+}
+```
+
+### 1.6 `frontend/src/components/servers/ConfigEditor.tsx` — consume schema
+
+- Import `useServerConfigSchema` and `TagListEditor`
+- Call `useServerConfigSchema(serverId)` alongside existing config queries
+- For each field in a config section, look up `schema?.[sectionName]?.[fieldName]`
+- Render based on `widget`:
+ - `"text"` → ``
+ - `"number"` → ``
+ - `"password"` → ``
+ - `"textarea"` → ``
+ - `"select"` → `