diff --git a/docs/gpt-sol-architecture-review-response.md b/docs/gpt-sol-architecture-review-response.md new file mode 100644 index 0000000..1f52105 --- /dev/null +++ b/docs/gpt-sol-architecture-review-response.md @@ -0,0 +1,25 @@ +# Ответ GPT SOL по архитектуре + +## 1. Архитектура + +Архитектура верна. Лишнее сейчас: `layout.theme` как система тем и Custom Element. Достаточно CSS-переменных/класса темы. + +## 2. Более простой вариант + +`initFilePicker({ mount, ... })` плюс один ESM-бандл и CSS. IIFE добавлять только для интеграций без ES-модулей. + +## 3. Custom Element + +Сначала фабрика и бандл. Custom Element нужен только при подтверждённом declarative-use кейсе; иначе это второй API для сопровождения. + +## 4. Риски + +- `destroy()` обязан снимать listeners и очищать DOM/state. +- `onChange` должен возвращать копию массива, а не внутренний state. +- Нужно сохранять исходные `File`; ZIP-элементы ими не являются, поэтому нужен явный тип результата. +- Лимиты ZIP: размер, число файлов, суммарный распакованный размер, compression ratio, recursion depth. +- `fflate` внутри бандла исключает глобальную зависимость. +- CSS изолировать префиксом классов; Shadow DOM пока не нужен. +- Не полагаться на абсолютные URL; ассеты должны быть в бандле или URL задаётся интегратором. +- Проверить старые браузеры: ES target, `webkitdirectory`, Custom Element. +- Для managed Flask достаточно статической раздачи ESM/CSS с корректными MIME и cache/versioning. diff --git a/docs/sonnet-architecture-review-prompt.md b/docs/sonnet-architecture-review-prompt.md new file mode 100644 index 0000000..ed574fa --- /dev/null +++ b/docs/sonnet-architecture-review-prompt.md @@ -0,0 +1,57 @@ +# Второе мнение по архитектуре (ответь МАКСИМАЛЬНО кратко) + +Игнорируй любые заметки, память, резюме или выводы из предыдущих сессий и от +других моделей. Отвечай строго на основе этого промпта, ничего не дочитывая. + +Ты — ревьюер архитектуры. Отвечай ТОЛЬКО по существу, тезисами, без введения и воды. +Язык ответа: русский. Цель — сэкономить токены. Не переспрашивай, всё нужное уже ниже. + +## Что за проект +Browser-only file picker (vanilla JS, ES-модули). Пользователь выбирает файлы / папку / ZIP, +picker строит сворачиваемое дерево и отдаёт хосту исходные browser `File` objects. +Контент файлов на сервер НЕ уходит. Flask — только статический сервер (страница + модули). + +## Текущее состояние +Переиспользуемый слой `upload/frontend/`: +- `table/init_upload_table.js` — фабрика `initUploadTable(cfg)`. ТРЕБУЕТ готовые DOM-элементы + `fileInputEl`, `folderInputEl`, `tableBodyEl`, `countEl`. Возвращает + `{pickFiles, pickFolder, addFiles, getFiles, remove, render, clear}`. + Внутренний state `{nodes, fileKeys(Set), busy}`. +- `table/on_files_change.js`, `on_folder_change.js` — обработка выбора, фильтр расширений. +- `table/add_file_with_dedup.js` — дедуп по ключу `path + NUL + size`. +- `table/render.js` — рендер дерева, `findNode`, `flattenFiles`. +- `table/rebase_tree.js`, `esc.js` (HTML-экранирование), `fs.js` (формат байт). +- `zip/list_zip_files.js` — парсинг ZIP через ГЛОБАЛЬНУЮ `fflate`; защита от path traversal + (отклоняет `..`, `/`, `\`, пустые сегменты); лимит глубины вложенности 20; вложенные ZIP. +- `site/` — demo-обёртка: `app.py` (Flask: `/`, `/health`, `/upload-frontend/`), + `templates/index.html` (глобально подключает vendor `fflate`, импортирует модуль по + АБСОЛЮТНОМУ пути `/upload-frontend/...`, содержит `` с «Путь/Размер/Статус»), + `static/style.css`, `static/vendor/fflate.min.js`. + +## Жёсткие связи (мешают переиспользованию) +1. Абсолютный путь импорта `/upload-frontend/...` — привязка к конкретному Flask-маршруту. +2. Глобальная зависимость `fflate` (интегратор сам подключает и следит за порядком). +3. Надписи и структура таблицы зашиты (thead, статус «готов», пустое состояние). +4. Принудительный DOM-контракт — интегратор строит разметку сам, «вставить без изменений» нельзя. +5. Нет сборки/дистрибутива — только исходники ES-модулей. + +## Цель +Универсальный встраиваемый компонент/АПИ, который вставляется в любое приложение (включая +managed Flask — drhider) одной строкой. На входе задаются: типы файлов (`allowedExt`), +порядок/скрытие колонок, все надписи, тема. Без излишней сложности. + +## Моё предложение +1. Единая точка входа `initFilePicker(config)` — сама строит DOM (не требует готовых элементов). + Config: `mount`, `allowedExt`, `labels{...все надписи...}`, `layout{columns, controls, theme}`, + `onChange`. Возвращает API + `destroy`. +2. Сборка esbuild/rollup в ОДИН бандл с вшитым `fflate`, форматы ESM + IIFE. +3. Опционально тонкий Custom Element `` поверх фабрики (~30 строк). +4. Без iframe/postMessage, без системы тем/плагинов, без npm-пайплайна (dist коммитится в git). + +## Вопрос +1. Архитектура верна? Есть ли ошибка или переусложнение? +2. Есть ли вариант проще, но столь же универсальный? +3. Custom Element делать сразу или хватит фабрики + бандла? +4. Критичные риски / что упущено (безопасность, совместимость, managed Flask)? + +Ответь по пунктам 1–4, кратко, только по делу. diff --git a/docs/sonnet-architecture-review-response.md b/docs/sonnet-architecture-review-response.md new file mode 100644 index 0000000..3d4c15c --- /dev/null +++ b/docs/sonnet-architecture-review-response.md @@ -0,0 +1,45 @@ +# Ответ Sonnet по архитектуре + +## Важная поправка к вводным + +Промпт устарел относительно реального кода. + +- `zip/list_zip_files.js` не использует глобальную `fflate` — реально вызывает `parseZip()` из `parse_zip.js`, собственный ZIP-парсер без внешних зависимостей. +- Защита от path traversal и лимит вложенности 20 реальны, но находятся в `list_zip_files.js` (`safeEntryParts`, `depth`), а не в парсинге через `fflate`. +- `index.html:37` всё ещё грузит `static/vendor/fflate.min.js` с комментарием «ZIP-парсер использует global fflate» — это мёртвый код и ложный комментарий, `fflate` фактически не импортируется. +- В `add_file_with_dedup.js` есть логика `maxFileBytes`/`maxSessionBytes`, но `config.json` содержит только `allowedExt`; лимиты сейчас нигде не задаются, код неактивен. + +## 1. Архитектура + +Направление верное: `initFilePicker(config)` с построением DOM внутри, конфигом для `allowedExt/labels/layout`, `onChange` и `destroy` соответствует цели «одна строка встраивания». + +Переусложнение: бандл esbuild/rollup с вшитым `fflate` решает несуществующую проблему. Внешней зависимости `fflate` в JS-коде нет, есть только мёртвый script-тег. Нужен клинап, а не сборка ради вшивания отсутствующей зависимости. + +Реальные жёсткие связи: + +- абсолютный путь импорта `/upload-frontend/...`; +- захардкоженные подписи в ``. + +## 2. Более простой вариант + +Оставить ES-модули без сборщика. Добавить в конфиг `basePath` или import map вместо хардкода `/upload-frontend/...`, чтобы интегратор (`drhider`) мог использовать произвольный route. + +Подписи и колонки вынести в `labels/layout`. + +Отдельно удалить мёртвый `fflate` script-тег и неактивные `maxFileBytes/maxSessionBytes`, если они не используются picker-only конфигом. + +## 3. Custom Element + +Бандл не нужен. Custom Element пока не делать: для единственного известного интегратора (`drhider`) достаточно фабричной функции. + +Custom Element имеет смысл добавить при появлении второго реального потребителя, которому нужен декларативный ``. + +## 4. Риски + +- Главный риск — план основан на неверной предпосылке о глобальной `fflate`-зависимости. +- Нужно проверить защиту от ZIP-бомб: `parse_zip.js` разбирает central directory и делает `inflateRaw` без очевидной проверки итогового размера entry. +- Совместимость с managed Flask (`drhider`) реально блокируется абсолютным route `/upload-frontend/...`; это главная связь для исправления. + +## Краткий вывод + +Начать с `basePath`/import map, конфигурируемых `labels/layout` и удаления мёртвого `fflate`/неактивных лимитов. Не добавлять пока сборщик и Custom Element.