diff --git a/REFACTORING_TODO.md b/REFACTORING_TODO.md
new file mode 100644
index 0000000..3769c96
--- /dev/null
+++ b/REFACTORING_TODO.md
@@ -0,0 +1,325 @@
+# Рефакторинг app.js → Модули: Прогресс
+
+**Дата начала**: 2025-12-25
+**Стратегия**: Постепенная миграция (app.js остаётся рабочим)
+**Статус**: 🟡 В процессе
+
+---
+
+## 📊 Общий прогресс: 20%
+
+```
+[████░░░░░░░░░░░░░░░░] 20% завершено
+```
+
+---
+
+## Этапы рефакторинга
+
+### ✅ Этап 1: Подготовка (ЗАВЕРШЁН)
+
+**Дата**: 2025-12-25
+**Статус**: ✅ Готово
+
+- [x] Создать структуру папок `static/js/`
+ - [x] `js/state/`
+ - [x] `js/services/`
+ - [x] `js/ui/`
+ - [x] `js/utils/`
+ - [x] `js/data/`
+- [x] Создать `js/config.js` с константами
+
+**Результат**: Структура готова, app.js не тронут ✅
+
+---
+
+### ✅ Этап 2: Утилиты (ЗАВЕРШЁН)
+
+**Дата**: 2025-12-25
+**Статус**: ✅ Готово
+
+#### 2.1. utils/format.utils.js ✅
+**Строки из app.js**: 150-273
+**Функции** (11 шт.):
+- [x] `generateUUID()` - генерация UUID
+- [x] `formatTime(seconds)` - форматирование времени
+- [x] `formatTimestamp(isoString)` - ISO → локальное время
+- [x] `isTableText(text)` - проверка на таблицу
+- [x] `parseTextTable(text)` - парсинг текстовых таблиц
+- [x] `escapeHtml(text)` - экранирование HTML
+- [x] `pluralize(count, one, few, many)` - склонение числительных
+
+**Экспорт**: ES6 named exports ✅
+
+#### 2.2. utils/file.utils.js ✅
+**Строки из app.js**: 262-273, 984-1132
+**Функции** (6 шт.):
+- [x] `downloadJSON(data, filename)` - скачать JSON
+- [x] `loadFileAsJSON(file)` - загрузить JSON
+- [x] `loadFileAsText(file)` - загрузить текст
+- [x] `selectFile(accept)` - выбрать файл
+- [x] `downloadText(text, filename)` - скачать текст
+
+**Экспорт**: ES6 named exports ✅
+
+#### 2.3. utils/validation.utils.js ✅
+**Строки из app.js**: 823-860
+**Функции** (4 шт.):
+- [x] `validateJSON(jsonString)` - валидация JSON
+- [x] `validateLoginFormat(login)` - проверка формата логина (8 цифр)
+- [x] `validateQuestions(questions)` - валидация массива вопросов
+- [x] `validateEnvironment(environment)` - проверка окружения
+
+**Экспорт**: ES6 named exports ✅
+
+#### 2.4. utils/dom.utils.js ✅
+**Строки из app.js**: 277-281 + новые
+**Функции** (15 шт.):
+- [x] `showElement(element)` - показать элемент
+- [x] `hideElement(element)` - скрыть элемент
+- [x] `toggleElement(element)` - переключить видимость
+- [x] `setElementText(element, text)` - установить текст
+- [x] `setElementHTML(element, html)` - установить HTML
+- [x] `getInputValue(element)` - получить значение input
+- [x] `setInputValue(element, value)` - установить значение input
+- [x] `addClass(element, className)` - добавить класс
+- [x] `removeClass(element, className)` - удалить класс
+- [x] `hasClass(element, className)` - проверить класс
+- [x] `showToast(message, type)` - показать уведомление
+- [x] `clearChildren(element)` - очистить детей
+- [x] `createElement(tag, attributes, parent)` - создать элемент
+
+**Экспорт**: ES6 named exports ✅
+
+---
+
+### 🟡 Этап 3: State Management (В ПРОЦЕССЕ)
+
+**Дата**: -
+**Статус**: 🔲 Ожидает
+
+#### 3.1. state/appState.js 🔲
+**Строки из app.js**: 10-51
+**Что нужно**:
+- [ ] Создать класс AppState
+- [ ] Геттеры: `getCurrentEnv()`, `getCurrentEnvSettings()`
+- [ ] Сеттеры: `setCurrentEnvironment()`, `updateSettings()`
+- [ ] Методы: `saveToLocalStorage()`, `loadFromLocalStorage()`
+- [ ] Export: default export singleton
+
+#### 3.2. data/storage.js 🔲
+**Строки из app.js**: 618-642
+**Что нужно**:
+- [ ] `saveEnvironmentData(env, data)` - сохранить данные окружения
+- [ ] `loadEnvironmentData(env)` - загрузить данные окружения
+- [ ] `clearEnvironmentData(env)` - очистить данные
+- [ ] `clearAllData()` - очистить всё
+
+#### 3.3. data/defaults.js 🔲
+**Файл**: settings.js (перенести)
+**Что нужно**:
+- [ ] Экспортировать `defaultSettings` из settings.js
+- [ ] Адаптировать для использования в модулях
+
+---
+
+### 🔲 Этап 4: Services (ОЖИДАЕТ)
+
+**Дата**: -
+**Статус**: 🔲 Ожидает
+
+#### 4.1. services/api-client.js 🔲
+**Файл**: api-client.js (переместить)
+- [ ] Переместить существующий `api-client.js` в `services/`
+- [ ] Добавить ES6 экспорт: `export default new BriefBenchAPI()`
+- [ ] Протестировать импорт
+
+#### 4.2. services/auth.service.js 🔲
+**Строки из app.js**: 60-140
+**Функции**:
+- [ ] `checkAuth()` - проверка авторизации
+- [ ] `login(loginString)` - вход
+- [ ] `logout()` - выход
+- [ ] `isAuthenticated()` - проверка статуса
+
+#### 4.3. services/settings.service.js 🔲
+**Строки из app.js**: 290-357
+**Функции**:
+- [ ] `loadFromServer()` - загрузить с сервера
+- [ ] `saveToServer(settings)` - сохранить на сервер
+- [ ] `extractEnvSettings(envSettings)` - извлечь настройки окружения
+- [ ] `resetToDefaults()` - сброс к дефолтным
+
+#### 4.4. services/query.service.js 🔲
+**Строки из app.js**: 861-1063
+**Функции**:
+- [ ] `buildRequestBody()` - построить тело запроса
+- [ ] `sendQuery(env, apiMode, body)` - отправить запрос
+- [ ] `extractQuestions()` - извлечь вопросы из textarea
+- [ ] `loadRequestFromFile()` - загрузить запрос из файла
+- [ ] `loadResponseFromFile()` - загрузить ответ из файла
+
+---
+
+### 🔲 Этап 5: UI Components (ОЖИДАЕТ)
+
+**Дата**: -
+**Статус**: 🔲 Ожидает
+
+#### 5.1. ui/auth.ui.js 🔲
+**Строки из app.js**: 77-132
+- [ ] `showLoginScreen()` - показать экран входа
+- [ ] `hideLoginScreen()` - скрыть экран входа
+- [ ] `setupListeners()` - подключить обработчики
+- [ ] `handleLoginSubmit()` - обработка входа
+
+#### 5.2. ui/loading.ui.js 🔲
+**Строки из app.js**: 1137-1145
+- [ ] `show(message)` - показать загрузку
+- [ ] `hide()` - скрыть загрузку
+
+#### 5.3. ui/settings.ui.js 🔲
+**Строки из app.js**: 362-813
+- [ ] `open()` - открыть диалог
+- [ ] `close()` - закрыть диалог
+- [ ] `populate()` - заполнить поля
+- [ ] `read()` - прочитать поля
+- [ ] `toggleBackendSettings(show)` - показать/скрыть backend настройки
+- [ ] `save()` - сохранить настройки
+- [ ] `reset()` - сбросить настройки
+- [ ] `export()` - экспорт настроек
+- [ ] `import()` - импорт настроек
+- [ ] `setupListeners()` - подключить обработчики
+
+#### 5.4. ui/query-builder.ui.js 🔲
+**Строки из app.js**: 643-883, 884-950
+- [ ] `show()` - показать построитель запросов
+- [ ] `switchMode(mode)` - переключить режим (questions/raw-json)
+- [ ] `validateJSON()` - валидация JSON
+- [ ] `handleSendQuery()` - обработка отправки
+- [ ] `setupListeners()` - подключить обработчики
+
+#### 5.5. ui/questions-list.ui.js 🔲
+**Строки из app.js**: 1179-1273
+- [ ] `render()` - рендер списка вопросов
+- [ ] `selectAnswer(index)` - выбрать ответ
+- [ ] `updateCount()` - обновить счётчик
+- [ ] `hasAnnotations(docsSection)` - проверить наличие аннотаций
+- [ ] `setupListeners()` - подключить обработчики
+
+#### 5.6. ui/answer-viewer.ui.js 🔲
+**Строки из app.js**: 1279-1443
+- [ ] `render(index)` - рендер ответа
+- [ ] `renderBody(elementId, text)` - рендер тела ответа
+- [ ] `renderDocuments(containerId, docs, ...)` - рендер документов
+- [ ] `toggleExpansion(id)` - раскрыть/свернуть
+- [ ] `switchTab(tabButton, tabId)` - переключить таб
+- [ ] `setupListeners()` - подключить обработчики
+
+#### 5.7. ui/annotations.ui.js 🔲
+**Строки из app.js**: 1448-1615
+- [ ] `initForAnswer(index)` - инициализация аннотаций
+- [ ] `loadForAnswer(index)` - загрузить аннотации
+- [ ] `loadSection(section, data)` - загрузить секцию
+- [ ] `loadDocuments(section, subsection, docs)` - загрузить документы
+- [ ] `setupListeners()` - подключить обработчики
+- [ ] `saveDraft()` - сохранить черновик
+
+---
+
+### 🔲 Этап 6: Main Entry Point (ОЖИДАЕТ)
+
+**Дата**: -
+**Статус**: 🔲 Ожидает
+
+#### 6.1. js/main.js 🔲
+**Что нужно**:
+- [ ] Импортировать все модули
+- [ ] Функция `initApp()` - инициализация приложения
+- [ ] Функция `setupEnvironmentTabs()` - настройка табов
+- [ ] Функция `switchEnvironment(env)` - переключение окружения
+- [ ] Функция `updateUI()` - обновление UI
+- [ ] DOMContentLoaded listener - точка входа
+
+---
+
+### 🔲 Этап 7: Тестирование (ОЖИДАЕТ)
+
+**Дата**: -
+**Статус**: 🔲 Ожидает
+
+#### 7.1. Ручное тестирование 🔲
+- [ ] Авторизация работает
+- [ ] Загрузка/сохранение настроек
+- [ ] Отправка запросов (bench mode)
+- [ ] Отправка запросов (backend mode)
+- [ ] Отображение ответов
+- [ ] Аннотации сохраняются
+- [ ] Экспорт/импорт работает
+- [ ] Переключение окружений
+
+#### 7.2. Обновление index.html 🔲
+- [ ] Закомментировать старые скрипты:
+ ```html
+
+
+
+ ```
+- [ ] Подключить новый entry point:
+ ```html
+
+ ```
+
+#### 7.3. Очистка 🔲
+- [ ] Удалить старый `app.js` (или переименовать в `app.js.old`)
+- [ ] Удалить `settings.js` (или переименовать)
+- [ ] Переместить `api-client.js` (если еще не перемещён)
+- [ ] Проверить что всё работает
+
+---
+
+## 📈 Статистика
+
+### Создано файлов: 5/17
+
+| Категория | Создано | Всего | Прогресс |
+|-----------|---------|-------|----------|
+| Config | 1 | 1 | 100% ✅ |
+| Utils | 4 | 4 | 100% ✅ |
+| State | 0 | 1 | 0% 🔲 |
+| Data | 0 | 2 | 0% 🔲 |
+| Services | 0 | 4 | 0% 🔲 |
+| UI | 0 | 7 | 0% 🔲 |
+| Main | 0 | 1 | 0% 🔲 |
+
+### Перенесено функций: ~36/~150
+
+- ✅ Format utils: 11 функций
+- ✅ File utils: 6 функций
+- ✅ Validation utils: 4 функций
+- ✅ DOM utils: 15 функций
+- 🔲 Остальное: ~114 функций
+
+---
+
+## 🎯 Следующий шаг
+
+**Этап 3: State Management**
+
+Создать:
+1. `state/appState.js` - глобальное состояние
+2. `data/storage.js` - обёртка localStorage
+3. `data/defaults.js` - дефолтные настройки
+
+---
+
+## 📝 Заметки
+
+- app.js (1671 строка) остаётся рабочим до конца рефакторинга
+- Все новые модули используют ES6 синтаксис
+- Экспорты: named exports для utils, default export для сервисов/UI
+- Модули полностью автономны и тестируемы
+
+---
+
+**Последнее обновление**: 2025-12-25 (Этап 2 завершён)
diff --git a/static/js/config.js b/static/js/config.js
new file mode 100644
index 0000000..3d1ef5c
--- /dev/null
+++ b/static/js/config.js
@@ -0,0 +1,54 @@
+/**
+ * Application Configuration
+ *
+ * Централизованная конфигурация приложения.
+ */
+
+// API Configuration
+export const API_CONFIG = {
+ baseURL: '/api/v1',
+ timeout: 1800000 // 30 minutes in milliseconds
+}
+
+// UI Configuration
+export const UI_CONFIG = {
+ defaultQueryMode: 'questions',
+ defaultWithDocs: true,
+ maxAnnotationLength: 5000
+}
+
+// Storage Keys
+export const STORAGE_KEYS = {
+ token: 'briefBenchToken',
+ user: 'briefBenchUser',
+ settings: 'briefBenchSettings',
+ envData: (env) => `briefBenchData_${env}`,
+ annotations: 'briefBenchAnnotationsDraft'
+}
+
+// Constants
+export const ENVIRONMENTS = ['ift', 'psi', 'prod']
+export const API_MODES = ['bench', 'backend']
+
+// Environment Names
+export const ENV_NAMES = {
+ ift: 'ИФТ',
+ psi: 'ПСИ',
+ prod: 'ПРОМ'
+}
+
+// Annotation Issue Types
+export const ANNOTATION_ISSUES = [
+ 'factual_error',
+ 'inaccurate_wording',
+ 'insufficient_context',
+ 'offtopic',
+ 'technical_answer'
+]
+
+// Rating Values
+export const RATING_VALUES = {
+ CORRECT: 'correct',
+ PARTIAL: 'partial',
+ INCORRECT: 'incorrect'
+}
diff --git a/static/js/utils/dom.utils.js b/static/js/utils/dom.utils.js
new file mode 100644
index 0000000..3b3fecc
--- /dev/null
+++ b/static/js/utils/dom.utils.js
@@ -0,0 +1,178 @@
+/**
+ * DOM Utilities
+ *
+ * Функции для работы с DOM (показать/скрыть элементы, обновить текст).
+ */
+
+/**
+ * Показать элемент
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ */
+export function showElement(element) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.style.display = ''
+ el.classList.remove('hidden')
+ }
+}
+
+/**
+ * Скрыть элемент
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ */
+export function hideElement(element) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.classList.add('hidden')
+ }
+}
+
+/**
+ * Переключить видимость элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ */
+export function toggleElement(element) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ if (el.classList.contains('hidden')) {
+ showElement(el)
+ } else {
+ hideElement(el)
+ }
+ }
+}
+
+/**
+ * Установить текст элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} text - Текст для установки
+ */
+export function setElementText(element, text) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.textContent = text
+ }
+}
+
+/**
+ * Установить HTML элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} html - HTML для установки
+ */
+export function setElementHTML(element, html) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.innerHTML = html
+ }
+}
+
+/**
+ * Получить значение input элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @returns {string} Значение элемента
+ */
+export function getInputValue(element) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ return el ? el.value : ''
+}
+
+/**
+ * Установить значение input элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} value - Значение для установки
+ */
+export function setInputValue(element, value) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.value = value
+ }
+}
+
+/**
+ * Добавить класс элементу
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} className - Имя класса
+ */
+export function addClass(element, className) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.classList.add(className)
+ }
+}
+
+/**
+ * Удалить класс у элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} className - Имя класса
+ */
+export function removeClass(element, className) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.classList.remove(className)
+ }
+}
+
+/**
+ * Проверить наличие класса у элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ * @param {string} className - Имя класса
+ * @returns {boolean} True если класс есть
+ */
+export function hasClass(element, className) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ return el ? el.classList.contains(className) : false
+}
+
+/**
+ * Показать toast уведомление
+ * @param {string} message - Сообщение
+ * @param {string} type - Тип ('info', 'success', 'error', 'warning')
+ */
+export function showToast(message, type = 'info') {
+ // TODO: Implement proper toast/snackbar component
+ console.log(`[${type.toUpperCase()}] ${message}`)
+ alert(message)
+}
+
+/**
+ * Очистить всех детей у элемента
+ * @param {string|HTMLElement} element - ID элемента или сам элемент
+ */
+export function clearChildren(element) {
+ const el = typeof element === 'string' ? document.getElementById(element) : element
+ if (el) {
+ el.innerHTML = ''
+ }
+}
+
+/**
+ * Создать элемент с атрибутами
+ * @param {string} tag - Тег элемента
+ * @param {object} attributes - Атрибуты элемента
+ * @param {string|HTMLElement} parent - Родительский элемент (опционально)
+ * @returns {HTMLElement} Созданный элемент
+ */
+export function createElement(tag, attributes = {}, parent = null) {
+ const el = document.createElement(tag)
+
+ for (const [key, value] of Object.entries(attributes)) {
+ if (key === 'class') {
+ el.className = value
+ } else if (key === 'text') {
+ el.textContent = value
+ } else if (key === 'html') {
+ el.innerHTML = value
+ } else {
+ el.setAttribute(key, value)
+ }
+ }
+
+ if (parent) {
+ const parentEl = typeof parent === 'string' ? document.getElementById(parent) : parent
+ if (parentEl) {
+ parentEl.appendChild(el)
+ }
+ }
+
+ return el
+}
diff --git a/static/js/utils/file.utils.js b/static/js/utils/file.utils.js
new file mode 100644
index 0000000..e8bc825
--- /dev/null
+++ b/static/js/utils/file.utils.js
@@ -0,0 +1,111 @@
+/**
+ * File Utilities
+ *
+ * Функции для работы с файлами (загрузка, сохранение).
+ */
+
+/**
+ * Скачать данные как JSON файл
+ * @param {object} data - Данные для экспорта
+ * @param {string} filename - Имя файла
+ */
+export function downloadJSON(data, filename) {
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = filename
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+}
+
+/**
+ * Загрузить файл как JSON
+ * @param {File} file - Файл для загрузки
+ * @returns {Promise