v1.0.177: финальный план рефакторинга — store+render, 4 фазы

This commit is contained in:
“Naeel”
2026-06-25 08:28:51 +04:00
parent 72e6e5a127
commit 2668fb7a38
@@ -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