fix: harden browser file picker and remove legacy

This commit is contained in:
“Naeel”
2026-09-05 20:50:36 +03:00
parent f6b2f6d1b3
commit 017bd8c354
20 changed files with 499 additions and 216 deletions
@@ -0,0 +1,84 @@
# Ревью Gemini 3.8 Flash — 2026-09-05
## Контекст
Ревью ограничивалось проектом `upload-platform`. Проверены frontend-модули
универсального browser-only picker-а, Flask-обёртка, конфигурация и структура
проекта. Ниже зафиксированы findings ревью; исправления по этим пунктам в рамках
данной записи не выполнялись.
## Критические ошибки и логические баги (High)
1. **Не сбрасывается `fileInputEl.value` при выборе файлов.**
**Файл:** `upload/frontend/table/on_files_change.js`.
После удаления файла повторный выбор того же файла может не вызвать событие
`change`, если значение input не сброшено.
2. **Распаковка лишних файлов и преждевременный OOM в ZIP-фильтре.**
**Файл:** `upload/frontend/zip/list_zip_files.js`.
Callback `filter` проверяет размер и бюджет, но до декомпрессии не отбрасывает
записи с неразрешёнными расширениями. Бинарные файлы могут быть распакованы в
память, хотя затем будут отброшены.
3. **Синхронная блокировка UI при распаковке архивов.**
**Файл:** `upload/frontend/zip/list_zip_files.js`.
`unzipSync` выполняется в главном потоке браузера и может замораживать UI на
больших архивах. Предложенное направление: async unzip с чанками или Web Worker.
## Архитектурные недочёты и надёжность (Medium)
4. **Нарушается семантика `File.name` для файлов из папок и ZIP.**
**Файлы:** `upload/frontend/table/rebase_tree.js`,
`upload/frontend/zip/list_zip_files.js`.
При создании нового `File` полный логический путь может попасть в `file.name`,
хотя путь должен храниться отдельно в метаданных узла.
5. **Не выполняется слияние папок с одинаковыми путями.**
**Файл:** `upload/frontend/table/add_file_with_dedup.js`.
Повторное добавление корневого узла с тем же путём может создать два отдельных
узла вместо объединения их дочерних элементов.
6. **Потенциально небезопасная вставка HTML в статус.**
**Файл:** `upload/frontend/table/set_status.js`.
Прямое присваивание `innerHTML` требует гарантировать экранирование всех
передаваемых значений либо заменить его на безопасную работу с текстом.
7. **Молчаливый пропуск ошибок ZIP без `cfg.onError`.**
**Файлы:** `upload/frontend/table/on_files_change.js`,
`upload/frontend/table/on_folder_change.js`.
При отсутствии callback интегратор может не получить индикацию ошибки
повреждённого архива или превышения лимита.
8. **Падение `app.py` при отсутствии или повреждении `config.json`.**
**Файл:** `site/app.py`.
Чтение конфигурации выполняется на уровне импорта без обработки исключений;
при ошибке Flask-приложение не запускается.
## Мёртвый код и гигиена (Low)
9. **Дубликат `init_upload_table.js`.**
Файл выглядит неиспользуемым и дублирует логику текущего picker API.
Предложенное направление: удалить либо явно пометить как deprecated после
проверки всех импортов.
10. **Пустые служебные каталоги backend.**
Каталоги `session` и `upload_refs` содержат только старые `__pycache__`.
Требуется отдельное решение о сохранении или очистке структуры.
11. **Недостаточная обработка ошибок сборки в `build.mjs`.**
**Файл:** `build.mjs`.
Предложено добавить явную обработку ошибки сборки с `process.exit(1)` и
определить поведение каталога `dist/` перед сборкой.
## Итог
Gemini 3.8 Flash создал 4 задачи: три high/medium направления по ZIP и
событиям выбора, а также дополнительный набор архитектурных и гигиенических
проверок. Эта запись фиксирует результаты ревью как backlog. Код проекта и
рабочая реализация picker-а в рамках документирования не изменялись.
## Проверка записи
- Файл ревью создан в `HISTORY/`.
- Исходники проекта не изменялись.
- Findings сохранены в исходной классификации High, Medium и Low.
@@ -0,0 +1,98 @@
# Продолжение вопросов и ответов по ревью Gemini 3.8 Flash — 2026-09-05
## Контекст
Зафиксирован второй блок ответов Gemini на дополнительные вопросы по ревью
`upload-platform`. Ответы уточняют границы ответственности picker-а, приоритеты
и декомпозицию findings. Код приложения в рамках документирования не изменялся.
## Уточнения
### №4: `File.name` и логический путь
Рекомендация Gemini: сохранять в `file.name` только базовое имя файла, а полный
логический путь передавать через `node.path` и контракт `getFiles()`.
Основание: стандартный browser `File` использует `name` как имя файла без
каталогов. Полный путь в `file.name` может некорректно обрабатываться при
`FormData.append('files', file)` и сторонними upload-библиотеками. Передача пути
третьим аргументом `FormData.append(name, file, filename)` устраняет проблему,
но перекладывает внутреннюю особенность picker-а на интегратора.
### №8: fallback для `config.json`
Пункт признан защитной мерой demo-сервера, а не production-риском библиотеки.
В целевом сценарии распространяется JavaScript bundle из `dist/`, а `site/app.py`
служит demo-обёрткой и не участвует в интеграции picker-а с host-системой. Поэтому
пункт не входит в обязательный pre-release топ-3.
### №3: порог `unzipSync`
Оценочный порог заметного фриза заявлен как 15–25 MiB сжатых данных или сотни
мелких XML/DOCX entries с суммарной распаковкой свыше 50 MiB. Для архивов до
10 MiB задержка обычно несущественна. Для целевого профиля офисных документов и
текущих лимитов переход на Web Worker признан преждевременной оптимизацией.
Приоритетнее сначала фильтровать расширения до декомпрессии и сохранять жёсткие
лимиты. Worker остаётся backlog для архивов порядка 100 MiB и более.
### №7: поведение без `onError`
Рекомендованный default — выводить ошибку в общий статус picker-а, например в
`elements.statusEl` или `.fp-status`. Молчаливый `continue` создаёт плохой UX:
пользователь не понимает, почему содержимое архива не появилось. Создание
неотправляемого сломанного корневого узла в таблице также признано нежелательным.
### №10: состояние backend-каталогов
По заявленному результату проверки файловой системы в `upload/backend/` остаются
только каталоги `session/` и `upload_refs/`, внутри которых нет исходников; есть
лишь пустые каталоги `__pycache__` от удалённых модулей. Вердикт Gemini: чинить
нечего, это остатки, которые можно удалить отдельным разрешённым изменением.
## Декомпозиция 11 findings на 4 задачи
### Задача 1: pre-release hotfixes UI и ZIP
- №1: сброс `fileInputEl.value` после обработки выбора;
- №2: фильтрация расширения внутри ZIP `filter` до декомпрессии;
- №5: слияние корневых папок с одинаковым путём.
Результат: корректный повторный выбор, снижение риска OOM и отсутствие дубликатов
корневых папок.
### Задача 2: контракт данных и обработка ошибок
- №4: базовое имя в `file.name`, путь в метаданных;
- №7: fallback-вывод ошибок ZIP в общий статус;
- №8: безопасная загрузка `config.json` с fallback-конфигурацией.
Результат: предсказуемый контракт browser `File` и понятная обратная связь.
### Задача 3: очистка репозитория
- №6: удаление или отдельное решение по legacy `set_status.js`;
- №9: удаление или отдельное решение по legacy `init_upload_table.js`;
- №10: удаление пустого `backend/` с остатками `__pycache__`;
- №11: определение поведения `dist/` перед сборкой.
Результат: в репозитории остаются актуальные исходники и ясная структура сборки.
### Задача 4: производительность больших архивов
- №3: перевод тяжёлой ZIP-распаковки в async-поток или Web Worker.
Задача отнесена в backlog и актуальна при появлении сценариев с очень большими
архивами.
## Итоговый приоритет
До следующего release в первую очередь предлагаются пункты **№2, №1 и №5**.
Пункты №4, №7, №8 и очистка legacy-кода относятся ко второй очереди. №3
остаётся производительным backlog до подтверждения реальных сценариев больших
архивов.
## Статус документирования
- Ответ Gemini сохранён в `HISTORY/`.
- Исходный код picker-а не изменялся.
- Удаление `__pycache__` и любых каталогов не выполнялось.
+78
View File
@@ -0,0 +1,78 @@
# Вопросы и ответы по ревью Gemini 3.8 Flash — 2026-09-05
## Контекст
После критического разбора ревью Gemini 3.8 Flash были заданы уточняющие
вопросы по версии исходников, дублированию API, семантике `File.name`, legacy
XSS, `build.mjs`, ZIP-фильтрации и приоритетам исправлений.
## Зафиксированные ответы
### Версия кода и номера строк
Ревью выполнялось по исходникам `upload-platform`; `dist/` отдельно не
исследовался. Номера строк в первой сводке были смещены из-за объединения
отчётов субагента. Фактические участки находятся в текущих исходниках
`on_files_change.js` и `list_zip_files.js`.
### `init_upload_table.js`
Буквального совпадения интерфейса с `initFilePicker` нет: один entry point
принимает готовые DOM-узлы, другой создаёт DOM внутри `mount`. Однако внутренняя
логика жизненного цикла таблицы параллельна: состояние `nodes/fileKeys/busy`,
удаление узлов, делегирование кликов и похожий API. Файл не входит в граф сборки
`build.mjs`, поэтому это legacy-альтернатива, создающая риск рассинхронизации.
### `File.name` и логический путь
`flattenFiles()` возвращает `name`, `path` и `file` раздельно, но при `rebaseTree`
вложенный browser `File` получает полный путь в `file.name`. При прямом
`FormData.append('files', file)` сервер получает filename с разделителями пути.
Это может конфликтовать с серверными `basename`/`secure_filename` и нарушает
обычный контракт browser `File`, где `name` является базовым именем. При явной
передаче третьего аргумента `FormData.append('files', file, path)` проблема не
возникает.
### XSS в `set_status.js`
Замечание относится только к legacy-файлу. `set_status.js` не входит в активный
граф сборки и не используется боевым рендерером. В активном `render.js` значения
статуса экранируются через `esc()`. Реальная уязвимость в текущем активном
рендеринге не подтверждена; пункт относится к гигиене мёртвого кода.
### `build.mjs`
Для top-level `await` необработанная ошибка сборки сама приводит Node.js к
завершению с ненулевым кодом. Явные `try/catch` и `process.exit(1)` технически
избыточны. Реальное возможное улучшение этого пункта — определить поведение
каталога `dist/` перед сборкой, например его предварительную очистку.
### Проверка фильтра `fflate`
Проверка исходника `fflate` подтвердила, что `filter` вызывается после чтения
метаданных ZIP entry, но до копирования или декомпрессии данных. Если фильтр
возвращает `false`, `inflateSync` для этой записи не вызывается и буфер для
распакованных данных не создаётся. Поэтому добавление проверки расширения в
`filter` является корректным способом не распаковывать тяжёлые неразрешённые
entries.
### Приоритет до прода
Согласован обязательный приоритет:
1. **№2:** отбрасывать неразрешённые расширения внутри ZIP `filter` до
декомпрессии, чтобы снизить риск OOM и лишнего расхода бюджета.
2. **№1:** сбрасывать `elements.fileInputEl.value`, чтобы повторный выбор того
же файла после удаления снова генерировал `change`.
3. **№5:** объединять корневые папки с одинаковыми путями, чтобы избежать
дублирующихся деревьев при последовательном выборе.
Пункты `File.name`, `onError`, fallback-конфигурация, legacy-код и очистка
служебных каталогов отнесены ко второй очереди.
## Итог
Уточнение подтвердило, что главным техническим finding является ZIP-фильтрация
до декомпрессии. Пункты про `set_status.js` и явный `process.exit(1)` не являются
активными production-дефектами. Исправления кода в рамках этой записи не
выполнялись.
+35
View File
@@ -0,0 +1,35 @@
# План исправлений по ревью Gemini — 2026-09-05
## Обязательный релизный набор
1. Сбрасывать `fileInputEl.value` после обработки выбора, чтобы повторный выбор
того же файла снова генерировал `change`.
2. Фильтровать ZIP entries по разрешённому расширению до декомпрессии, сохраняя
отдельный проход для вложенных `.zip`.
3. Объединять корневые папки с одинаковым путём вместо создания дублей.
4. Сохранять базовое имя в `File.name`, а логический путь — в `node.path` и
результате `flattenFiles()`.
5. Показывать ошибку ZIP в статусе по умолчанию, если `onError` не передан.
6. Использовать fallback-конфигурацию при отсутствии или повреждении
`config.json` в demo-сервере.
## Статус выполнения
- Выполнено: обязательный релизный набор пунктов 1–6.
- Выполнено: удалены legacy `set_status.js`, `init_upload_table.js` и пустой
`upload/backend/` с остатками `__pycache__`.
- Выполнено: живая документация очищена от ссылок на удалённую legacy-архитектуру.
## Отложенные задачи
- Перевести тяжёлую синхронную ZIP-распаковку в Web Worker после подтверждения
реальными измерениями необходимости.
- Перед сборкой определить явное поведение каталога `dist/`.
## Проверка
- Пересобрать ESM/IIFE bundles.
- Проверить синтаксис изменённых JavaScript и Python-файлов.
- Обновить версию с `0.1.11` до `0.1.12` в `package.json` и `site/app.py`.
- Выполнить browser smoke-тесты для повторного выбора, ZIP-фильтрации,
слияния папок и fallback ошибки.