diff --git a/History/llm-analysis/decoupling-final-plan.md b/History/llm-analysis/decoupling-final-plan.md new file mode 100644 index 0000000..208788c --- /dev/null +++ b/History/llm-analysis/decoupling-final-plan.md @@ -0,0 +1,85 @@ +# Финальный план рефакторинга — store + render + +Согласовано: DeepSeek + Opus. Все открытые вопросы закрыты. + +## Архитектурное решение + +**Патерн:** единый `state` объект + одна `render(state)` + actions мутируют state. + +``` +action → мутация state → render(state) +``` + +- Асинхронные операции (upload, classify, compare-SSE) — actions, пишут в state +- Синхронные трансформации (group, applyParseResult) — чистые функции +- DOM — проекция state, никто не читает из DOM +- `console.log(state)` показывает всё + +## Структура state + +```javascript +state = { + batchId, contractId, + files: [{ id, name, size, status: { kind, pct, text, count }, parse, classify, supp_id, detailOpen }], + groups: [{ + contractNumber, counterparty, documents, unresolved, + compare: { status, sections: { [suppId]: { header, body, ops } }, totalTime, collapsed } + }], + compareAll: { status, sections, totalTime }, + ui: { steps: { upload, classify, groups, compare } }, + _activeCompare: { es, timer } +} +``` + +## Фазы + +### Фаза 0 — Каркас +- Ввести `state`, `render(state)` (пока вызывает существующие рендеры) +- Соглашение: мутировал → `render()` + +### Фаза 1 — Файлы +- `fileQueue` → `state.files` +- `renderTable` → `renderFiles(state)` +- `fileInput change` разбит: + - `onFilesSelected(files)` — оркестратор (10-15 строк) + - `reconcileSelection(newFiles)` — чистая мутация массива + - `addZipFiles(file)` — async-action + - `addRegularFile(file)` — async-action + - `applyParseResult(entry, parsed)` — чистая функция (убирает дубль zip/обычной ветки) + - `finalizeUpload()` — refreshSupps + stepDone + syncDB + кнопки +- `status` → структура `{ kind, pct, text, count }`, HTML через `statusToHTML(status)` +- Убрать `renderTable()` из обработчиков — только `render()` в конце action + +### Фаза 2 — Группы +- `loadGroups` → `loadGroupsAction()` (fetch → state.groups) + `renderGroups(state)` +- `buildGroupCard` → `renderGroupCard(group)` — чистая HTML-функция +- **`markGroupDone` удалить** — готовность = `state.groups[gi].compare.status === 'done'` +- Убрать `window._groupsData` +- `renderGroups` пересобирает ВСЕ карточки целиком + +### Фаза 3 — Сравнение/SSE +- Унификация через общие функции: + - `applyCompareEvent(target, event)` — чистая мутация SSE-события в target + - `startCompareSSE(url, target, { onDone })` — жизненный цикл SSE + - `renderCompareSections(sections)` — общий рендер таблиц ops +- `runCompareForGroup` → тонкая обёртка над `startCompareSSE` +- `llmBtn` → тонкая обёртка над `startCompareSSE` +- ~160 строк дублирования → ~60 строк общих + 2 вызова + +### Фаза 4 — Чистка +- `contractId`, `batchId` → `state` +- Степпер → `renderStepper(state)` +- Промпты, чат, showText — вне scope (отдельная итерация) + +## Верификация + +1. После каждой правки: `node -c` на app.js + app_utils.js +2. После Фазы 2: node-тесты на чистые функции (`applyParseResult`, `statusToHTML`, `renderGroupCard`) +3. Ручной прогон upload→classify→group→compare после git push +4. Регресс: «Готово» не перезапускается, параллельные сравнения заблокированы, группы не затираются + +## Релевантные файлы + +- `contractor/deploy/app.js` — все фазы (~1040 строк) +- `contractor/deploy/app_utils.js` — removeFile/moveUp/moveDown (Фаза 1) +- `contractor/index.cfm` — DOM-скелет, версия v1.0.177, `?v=` для cache bust