# Changelog
All notable changes to AI-RPG are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.0] — 2026-06-21
Major UX release: redirect to edit after world creation, environment panel, admin separate routes, collapsible settings, name bank, page titles.
### Backend — Critical fixes
- **KeyError in world_builder_schema prompt**: the prompt contained a literal `{name, type, required, min, max, properties}` brace group that `str.format()` tried to interpret as a format field. Rewrote the prompt to describe field properties in prose. All 11 prompts verified to format correctly.
- **propose_changes not applying world.name / world.description**: `apply_diff` only handled `environment.*` paths. Now supports shorthand paths: `name`, `description`, `language`, `player.*`, `current_location`, `plot_rails.*`, `schemas`. Also skips empty `{}` new values (model sometimes returns empty objects). Also syncs `world.plot_rails` column from environment.
- **action_source validation error**: `IterateRequest.action_source` was `Literal["custom", "suggested"]` which rejected any other string. Changed to `str` with default "custom" — accepts any value.
- **Tool-call retry with nudge**: when the model returns no tool calls, the loop now appends a system message: "You did not call any tools. You MUST use the available tools. If you tried to call a tool but it didn't work, try again with proper JSON arguments." and retries (up to max_substeps).
- **Text replacements applied earlier**: now applied to LLM content in the tool loop (not just scene_text), so reasoning/comments are also cleaned before being shown to the user or fed back to the model.
- **Language instruction in prompts**: added "Language rule" section to `orchestrator_phase1`, `world_builder_schema`, and other prompts: "All entity names, location names, character names, item names, and descriptions that the PLAYER will see MUST be in `{language}`. Internal reasoning and tool arguments stay in English."
- **World description from preset**: when creating a world from a preset, `world.description` is now set from `preset.description` (was using `body.notes` or null).
- **Schema `show` field**: `world_builder_schema` prompt now instructs the LLM to include a `show` boolean on each field (default true; if false, the field is hidden from the player UI).
### Backend — New settings & endpoints
- **`ui.header_title` setting**: separate title for the navbar header. If empty, falls back to `ui.page_title`. Returned in `GET /api/settings/public` as `header_title`.
- **`character_names.en` / `character_names.ru` settings**: name banks for the random name button. Each is a JSON array of ~20 names. Extensible via admin settings.
- **`GET /api/names/{language}`** (no auth) — returns `{name: "random name", language, count}`. Used by the world builder form's 🎲 button.
### Frontend — 16 files changed
- **Redirect to edit after world creation**: WorldBuilder now redirects to `/worlds/{id}/edit` (not `/play`) after the builder stream completes. The user manually clicks "Generate intro scene" then "Play".
- **Play page — intro_scene + suggested actions**: if no recent_steps but `world.intro_scene` exists, it's shown as the first assistant message. `next_actions` shown as clickable buttons. Empty states with links to edit page.
- **Environment panel**: shows Player (name, HP progress bar, mana/strength, inventory), Current Location, Plot Rails (hooks, current_goals, completed_goals). "No environment data" empty state.
- **Edit world button**: ⚙️ button in PlayPage header → links to `/worlds/{id}/edit`.
- **Admin separate routes**: `/admin/stats`, `/admin/logs`, `/admin/users`, `/admin/settings`, `/admin/test`, `/admin/icons`. `/admin` redirects to `/admin/stats`. Page reload preserves the current tab.
- **LLM logs auto-refresh**: polls every 5 seconds (first page only, paused when filter inputs are focused). Pause/Resume button. "N new" badge when new logs arrive.
- **Collapsible settings cards**: all 7 cards (LLM, Embeddings, Qdrant, Context, Game, UI, Text Replacements) are collapsible, default collapsed. Chevron icon (▶/▼). "Expand all" / "Collapse all" buttons.
- **Boolean settings → checkboxes**: replaced dropdowns with `` for all boolean settings.
- **Localized setting descriptions**: all ~36 setting descriptions now have translation keys (`admin.setting_desc.{key}`). Also localized the test page hints and the "Each card saves independently..." text.
- **Page title**: PlayPage and WorldEditPage set `document.title = "{world.name} | {headerTitle}"`. Navbar uses `header_title` from `/api/settings/public`.
- **World builder form simplified**: removed `form_data` JSON. Now has: Setting (textarea), World name (default "New World" / "Новый Мир"), Player name (text + 🎲 random button), Language (select), Notes (textarea). Preset mode hides Setting field, defaults world name to preset name.
- **Name bank random button**: 🎲 button next to player name. Calls `GET /api/names/{language}` and fills the field. Disabled while fetching.
- **World edit — current_time_human**: shows "Day 1, 08:00" instead of "day_1_hour_8".
- **World editor — comment + tool_call display**: `comment` SSE events shown as assistant chat bubbles. `tool_call` events shown as ToolCallBubble components in the chat log.
### Verification
- Backend: 68 unit tests pass, 53 routes.
- Frontend: `tsc --noEmit` → 0 errors. `npm run build` → success (393 KB JS / 24 KB CSS, ~119 KB gzipped).
## [1.3.0] — 2026-06-21
Major release: world builder rewritten to use tools (instead of JSON), resumable builder flow, admin recovery, LLM model list, text replacements, human-readable time.
### Backend — Critical: World Builder rewritten to use tools
- **`world_builder_schema` stage**: previously asked the LLM to output a JSON object with schemas, which frequently failed validation. Now the LLM calls `schema_add_type` tool for each entity type. Added `world_builder_schema` and `world_builder_env` to the `stages` set of all relevant tools (schema_add_type, env_update, entity_create, submit_plan, etc.).
- **`world_builder_env` stage**: previously asked for JSON. Now the LLM calls `env_update` tool to set player, current_location, and plot_rails.
- **Resumable builder**: each stage checks if the world already has the needed data and skips if so. If schemas exist → skip schema generation. If environment has current_location → skip env generation. If entities exist → skip entity generation. If intro_scene exists → skip intro generation. This allows re-running the builder after a failure at any stage without redoing earlier stages.
- **Validation warnings instead of failures**: if `validate_world` finds issues after env generation, the builder emits a `warning` SSE event but continues (instead of failing). The world may still be usable.
- **Better tool-loop prompt**: the user message now says "Use the available tools to accomplish the task. When done, call {terminal_tool}." to encourage tool use.
- **Updated prompts**: `world_builder_schema` and `world_builder_env` prompts now describe the tools to use and give examples of tool arguments.
### Backend — Critical: World Editor fixes
- **No extra LLM call after propose_changes**: after the user accepts or rejects proposed changes, the editor loop now breaks immediately. Previously it made another LLM call (which returned empty text), wasting API requests.
- **`'str' object has no attribute 'get'` fix**: already in v1.2.0, but now also handles cases where `function` is not a dict.
### Backend — New: Admin recovery
- **`POST /api/admin/recover`** (NO AUTH required) — creates a new admin user using the `admin.setup_token` (printed on every backend startup). Body: `{token, email, username, password}`. For disaster recovery when all existing admins lost access. Returns 403 `invalid_admin_token` if the token doesn't match.
### Backend — New: LLM model list
- **`POST /api/admin/llm/models`** (admin) — fetches the list of available models from an OpenAI-compatible API (`GET {api_url}/models`). Returns `{ok: true, models: [...], count: N}` or `{ok: false, error: {...}, models: []}`. Uses the same `_resolve` helper as test endpoints (ignores masked api_key values).
### Backend — New: Text replacements
- **New setting `llm.text_replacements`**: a JSON array of `{from: string, to: string}` pairs. Applied to all LLM scene_text output (both orchestrator Phase 2 and intro_scene). Use empty `to` to remove a word/phrase entirely.
- **`apply_text_replacements(session, text)`** helper in `settings_service.py`.
- Applied in `game_master.py` (Phase 2 writer) and `world_builder.py` (intro scene).
### Backend — New: Human-readable time
- **`format_time_human(time_str, language)`** in `time_utils.py` — converts `"day_1_hour_8"` → `"Day 1, 08:00"` (en) or `"День 1, 08:00"` (ru). Supports years, days, hours, minutes.
- **`GET /api/worlds`** now returns `current_time_human` alongside `current_time`.
- **`GET /api/sessions/worlds/{id}/state`** now returns `current_time_human` and `status` in the world object.
### Backend — New: world_id in LLM logs
- **`GET /api/admin/llm-logs`** now includes `world_id` (string UUID or null) on each log item. Useful for the admin UI to show which world a log belongs to, even when not filtering by world_id.
### Backend — Route fix
- **404 on generate-intro**: the frontend was calling `/api/worlds/{id}/generate-intro` but the route is at `/api/sessions/worlds/{id}/generate-intro`. Fixed the frontend API helper to use the correct path.
### Frontend — 14 files changed, 1 new
- **Admin recovery page**: new `/recover` route (public, no auth). Form with token/email/username/password. Link from LoginPage: "Lost admin access? Recover here".
- **LLM model list dropdown**: "Fetch models" button next to the model input in SettingsPanel. Fetches from `POST /api/admin/llm/models`. Shows a `