Files
notes-qpid-pro/TODO.md
T
Antigravity 438dfdb055 feat(perf): implement HTTP connection pooling with httpx.AsyncClient singleton and Keep-Alive
- get_http_client: singleton AsyncClient with limits(max_keepalive=10, max_connections=20, keepalive_expiry=60s)
- close_http_client: clean socket pool teardown in FastAPI lifespan shutdown
- refactor dictionary fetchers to reuse pooled client and avoid 5x TLS handshakes per word lookup
- add test_http_connection_pooling (22/22 pytest passed)
2026-10-06 11:34:31 +03:00

272 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📋 Задачи проекта: study.qpid.pro (Backlog & Execution Queue)
Список задач для последовательной реализации в рамках принципов **Ponytail** (максимальная надёжность, нативные средства платформы, zero-dependency, высокая скорость).
---
## 🚀 Очередь реализации (Пошаговый план)
### 1. 📑 Полноценный рендеринг Markdown (.md) в читалке
- [x] **Блоки кода (Fenced Code Blocks ```` ```language ````)**:
- Выделение блоков кода в контейнер `<pre><code class="language-...">`.
- Моноширинный шрифт (`JetBrains Mono`, `Consolas`), сохранение отступов и табуляции (`white-space: pre`).
- Тёмная подложка, горизонтальный скролл при переполнении, отключение красной строки (`text-indent: 0`).
- Кнопка «Копировать код» в шапке блока.
- Отключение создания интерактивных словарных спанов `<span class="reader-word">` для кода, чтобы синтаксис не ломал форматирование.
- [x] **Инлайн-код (`` `variable` ``)**:
- Оборачивание в нативный тег `<code class="reader-inline-code">` с бейджик-стилем.
- [x] **Списки и цитаты**:
- Преобразование маркированных (`- `, `* `) и нумерованных (`1. `) списков в `<ul><li>` / `<ol><li>`.
- Преобразование цитат (`> `) в `<blockquote>` с акцентной левой границей.
- **Слова внутри списков и цитат остаются кликабельными** (интерактивный клик-перевод работает!).
- [x] **Заголовки Markdown (`##`, `###`)**:
- Конвертация в `<h3>`, `<h4>` с удалением сырых решёток `#`.
- [x] **Таблицы Markdown (`| col | col |`)**:
- Адаптивные таблицы с рамками, стилизацией заголовков `<thead>`, зебра-подсветкой строк и горизонтальным скроллом на мобильных.
- Каждая ячейка поддерживает интерактивные слова для изучения и инлайн-код.
- [x] **HTML-блоки и теги в Markdown (`<p align="center">`, `<h1>`, `<em>`, `<strong>`, `<sub>`, `<br>`)**:
- Нативный парсинг через встроенный `DOMParser` без внешних библиотек.
- Устранена утечка сырых тегов (`<picture>`, `<source>`, `<p align=...>`) в словарные слова.
- [x] **Ссылки и изображения (`[text](url)`, `![alt](url)`, `<img>`)**:
- Поддержка бейджей shields.io, тренд-графиков и внешних картинок.
- Автоматическое скрытие битых/отсутствующих локальных путей (`assets/...`) через обработчик `onerror`.
- [x] **Точное позиционирование окна перевода у курсора/слова (Popup Positioning)**:
- Исправлен баг двойного смещения координат: убран `window.scrollY` при `position: fixed`.
- Окно перенесено в корень `<body>` для исключения искажений контекста позиционирования.
- Плавное отслеживание скролла через `requestAnimationFrame` с автозакрытием при уходе слова за границы экрана или под липкую шапку.
- [x] **Стилизация в CSS ([`app/static/css/style.css`](file:///home/ai-projects/services/study.qpid.pro/app/static/css/style.css))**:
- Добавлены стили для `.reader-content-body pre`, `code`, `blockquote`, `ul`, `ol`, `.reader-table`, `.reader-heading`, `.reader-link`, `.reader-img`.
---
### 2. 🗜️ Сжатие статического трафика (`GZipMiddleware`)
- [x] Подключить `GZipMiddleware(minimum_size=1000)` в [`app/main.py`](file:///home/ai-projects/services/study.qpid.pro/app/main.py).
- [x] Сократить объём передаваемых файлов `app.js` (111.8 КБ -> 21.5 КБ), `index.html` (55.2 КБ -> 10.3 КБ) и `style.css` (58.2 КБ -> 9.4 КБ) на 81.7% (суммарно с 225.2 КБ до 41.2 КБ).
- [x] Проверить заголовки `Content-Encoding: gzip` в ответах и добавить регрессионный тест в `tests/test_api.py`.
---
### 3. 🛡️ Дедупликация слов и защита от повторного добавления (Reader + API)
- [x] **Проверка дубликатов на бэкенде (`POST /api/cards`)**:
- Проверка слова без учёта регистра `lower(trim(term))`.
- При повторном добавлении не создаётся дубликат, а дополняется контекстное предложение и объединяются теги, возвращается `already_exists: True`.
- [x] **Индикация в читалке и карточке поиска**:
- В читалке при клике на слово и в карточке поиска выводится бейдж `⭐ В библиотеке`.
- Кнопка «+ В словарь» заменяется на бейдж `⭐ Слово уже в библиотеке` с опциональной кнопкой `+ Добавить эту цитату к карточке`.
- [x] **Индекс целостности в SQLite**:
- Создан `UNIQUE INDEX idx_vocab_user_term_nocase ON vocabulary_cards(user_id, term COLLATE NOCASE);` гарантирующий отсутствие дубликатов на уровне движка БД за $O(\log N)$.
- [x] **Слияние существующих дубликатов в базе**:
- Объединены дубликаты `admired` (ID 5 и ID 7) и `suddenly`/`Suddenly` (ID 2 и ID 9) с объединением контекстных предложений, слиянием тегов и сохранением прогресса SRS.
---
### 4. 📱 Мобильная оптимизация и адаптивная верстка (iPhone 12 / iOS Safari)
- [x] **Системный фундамент iOS и сброс ограничений (Core Foundation)**:
- Метатег `viewport`: добавлен `viewport-fit=cover` для поддержки выреза (notch) и нижней панели iPhone.
- Устранение бага принудительного авто-зума: установлен `font-size: 16px` для `<input>`, `<textarea>`, `<select>` на экранах `≤ 768px`.
- Внедрены динамические единицы `100dvh` (Dynamic Viewport Height) и безопасные отступы `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)`.
- Защита от горизонтального сдвига (`overflow-x: hidden` на `html`, `body` и `#app-shell`).
- [x] **Мобильная навигация: переход к нативному паттерну (App Header + Bottom Tab Bar)**:
- Компактная верхняя шапка для смартфонов: логотип `📖 Notes`, переключатель темы `🌓`, кнопка вызова профиля `⚙️`.
- Фиксированная нижняя панель вкладок (Bottom Tab Bar) с нативным iOS UX:
1. 🔍 **Словарь** (Поиск и словарь)
2. 🎓 **Тренажёр** (SRS с бейджем количества повторений)
3. 📚 **Читалка** (Библиотека книг)
4. 📊 **Прогресс** (Статистика и стрик)
5. ⚙️ **Меню** (Смена пароля, экспорт, выход)
- Полное сохранение десктопного `navbar` без изменений на больших экранах (`> 768px`).
- [x] **Адаптация экрана словаря и карточек (Dictionary & Cards View)**:
- Поле поиска/перевода на полную ширину экрана с кнопкой «Перевести».
- Скрытие неактуальных десктопных подсказок горячих клавиш (`<kbd>Ctrl+Enter</kbd>`) на мобильных устройствах.
- Вертикальная раскладка полей метаданных при редактировании карточки.
- Горизонтальный скролл чипсов коллекций и тегов (`overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch`).
- Увеличенные зоны нажатия (Touch Targets ≥ 44px) для кнопок действий над карточкой.
- [x] **Интерактивная читалка и переход к Mobile Bottom Sheet**:
- Трансформация всплывающего попапа перевода (`reader-word-popup`) в нативную выдвижную нижнюю шторку (Bottom Sheet) на смартфонах с ручкой перетаскивания `sheet-drag-handle`.
- Двухстрочный адаптивный тулбар читалки (навигация по главам + размер шрифта `A-/A+` и подсветка).
- Сетка библиотеки `bookshelf-grid` в одну колонку на экранах `≤ 640px`.
- [x] **Тренажёр интервальных повторений (SRS Trainer)**:
- Ограничение высоты карточки SRS (`max-height: 48dvh`), чтобы карточка и кнопки оценки гарантированно помещались без вертикального скролла на 390px.
- Кнопки оценки (Снова / Трудно / Хорошо / Легко) в сетку 2×2 с высотой от 48px и четкими цветовыми акцентами.
- Вертикальный список вариантов в режиме квиза (4 варианта) на всю ширину экрана.
- [x] **Модальные окна (Статистика, Пароль, Экспорт)**:
- Метрики статистики в сетку 2×2 вместо сжатых 4 колонок.
- Плавный горизонтальный скролл календаря активности (Heatmap).
- Разворачивание модальных окон в адаптивный мобильный слой с удобной кнопкой закрытия.
---
---
### 5. 🔤 Кастомизация оформления читалки (Типографика, темы, отступы, LocalStorage)
- [x] **Панель настроек оформления текста (`Aa`)**:
- Кнопка вызова настроек в тулбаре читалки: поповер на ПК и выдвижная шторка (Bottom Sheet) на смартфонах.
- [x] **Настройка размера шрифта и степпер**:
- Степпер `A-` / `A+` с индикатором точного размера (от 14px до 34px).
- [x] **Семейство шрифтов (Font Family)**:
- Переключение в 1 клик между `Sans` (системный), `Serif` (книжный с засечками), `Mono` (моноширинный).
- [x] **Межстрочный интервал (Line Height)**:
- Компактный (1.35), Оптимальный (1.65), Широкий (1.95).
- [x] **Поля и ширина колонки (Margins & Max Width)**:
- Узкие поля (940px), Стандарт (760px), Книжная колонка (600px).
- [x] **Цветовые темы читалки (Themes)**:
- 🌙 Тёмная (Dark Slate), 📜 Сепия (Тёплый пергамент), ☀️ Светлая (Чистый лист), 🖤 OLED (Абсолютный чёрный).
- [x] **Выравнивание текста (Text Alignment)**:
- По левому краю / По ширине (Justified).
- [x] **Автосохранение и персистентность**:
- Мгновенное реактивное применение стилей через CSS-переменные без перезагрузки и сохранение настроек в `localStorage`.
---
### 6. 🔄 Комплексная переработка навигации, роутинга и честного прогресса чтения (Completed)
- [x] **6.1. Нативная маршрутизация окон (URL Hash Router & Zero-Flicker F5)**:
- Внедрение хэш-роутинга: `#dictionary`, `#srs`, `#bookshelf`, `#reader/:book_id`, `#reader/:book_id/ch/:chapter_idx`.
- Синхронное переключение `view` при старте скрипта (до любых сетевых запросов авторизации): если в URL `#reader/...`, сразу скрывать `#dictionary-view` и показывать скелетон читалки, устраняя мигание словарем.
- Развязка асинхронных вызовов: `loadBookshelf()` больше не должен принудительно гасить `readerInterface`, если уже инициировано открытие книги через `openBook()`.
- Поддержка нативной истории браузера (кнопки «Назад» / «Вперёд» на мыши, клавиатуре и мобильных свайпах).
- Автоматическая синхронизация активного экрана в URL и `localStorage`.
- [x] **6.2. Честный непрерывный расчет прогресса книги и главы (Weighted Reading Progress)**:
- Устранение бага ложных «100%»: полная ликвидация формулы `(current_ch + 1) / total_ch`, выдававшей 100% на первой странице одноглавных документов.
- Прогресс текущей главы в реальном времени: непрерывный расчет от вертикального скролла (`scrollTop / (scrollHeight - clientHeight) * 100`) от 0% до 100%.
- Взвешенный общий прогресс книги: расчет по объему слов прочитанных глав + точная доля текущей главы:
$$\text{TotalProgress} = \frac{\sum_{i=0}^{\text{cur}-1} \text{words}(ch_i) + \text{words}(ch_{\text{cur}}) \times \frac{\text{ChapterPct}}{100}}{\text{TotalWords}} \times 100\%$$
- Для документа на 1000 слов прогресс будет плавно расти: 0% $\rightarrow$ 15% $\rightarrow$ 50% $\rightarrow$ 100%.
- Отображение честного суммарного прогресса на карточках книг в библиотеке и в тулбаре читалки.
- [x] **6.3. Интеллектуальный парсинг глав и оглавление (Smart Chapter & ToC Detection)**:
- Расширение детектора глав в `app/reader.py`:
- Markdown-заголовки всех уровней (`# `, `## `, `### `, `#### `).
- Нумерованные разделы и модули (`1. `, `1.1 `, `2.0 `, `Section 1`, `Part 1`, `Раздел 1`).
- Выделенные строки в верхнем регистре (ALL CAPS) от 3 до 60 символов, обрамленные пустыми строками (`INTRODUCTION`, `ETHERNET BASICS`).
- Жирные заголовки в начале абзацев (`**Chapter 1**`, `**Глава 1**`).
- Адаптивный порог авто-разбивки: снижение порога чанкования текста без явных глав с 3000 до 800–1000 слов, чтобы даже небольшие статьи имели логическое деление на части.
- Интерактивное оглавление со статусами прочтения: `✔ Прочитано`, `▶ В процессе (45%)`, `○ Не начато`.
- [x] **6.4. Устойчивая фиксация позиции чтения (Paragraph Anchor + Scroll)**:
- Сохранение не только пикселей `scrollTop`, но и индекса первого видимого абзаца (`anchor_paragraph_idx`).
- Устойчивость к изменению шрифта `Aa` и ориентации экрана: восстановление скролла к конкретному предложению/абзацу, а не слепым пикселям.
- Фиксация скролл-контейнера `#reader-scroll-area` с аппаратным ускорением на мобильных.
- [x] **6.5. Сквозное чтение и бесшовная автоподгрузка (Continuous Book View & Seamless Infinite Append)**:
- Режим «Вся книга целиком» (`flowMode: 'continuous'`): объединение всех глав книги в один непрерывный свиток со стильными разделителями глав без необходимости перелистывания.
- Режим «По главам» с автоподгрузкой (`autoLoadNext: true`): бесшовное присоединение следующей главы снизу при приближении к концу текущей главы.
- Навигационный блок завершения главы: эргономичные кнопки «◀ Назад» и «Следующая: [Название] ▶» прямо под текстом (без необходимости тянуться наверх).
- Настройка режима чтения («По главам» / «Вся книга») и переключатель автоподгрузки добавлены в панель настроек `Aa` с автосохранением в `localStorage`.
- Карточка завершения книги (`🎉 Книга прочитана!`) с быстрыми ссылками на тренировку слов и возврат в библиотеку.
---
### 7. 📑 Поддержка чтения PDF (Cisco CCNA, O'Reilly & Tech Books с оглавлением и текстовым слоем) (Completed)
- [x] **Легковесный Pure-Python парсер (`pypdf`, Zero C-Dependency)**:
- Подключение `pypdf` (< 400 КБ, чистый Python, без раздувания Docker-образа и без системных библиотек `poppler-utils` / `mupdf`).
- [x] **Извлечение оглавления и закладок PDF (`reader.outline`)**:
- Извлечение авторского оглавления (ToC / Bookmarks), стандартного для книг Cisco Press (CCNA Official Cert Guide), O'Reilly и Wiley.
- Разбивка книги на логические главы и разделы (`Part I`, `Chapter 1`, `Chapter 2`) точно по оглавлению автора.
- [x] **Fallback-разбивка при отсутствии оглавления**:
- При отсутствии PDF-закладок — автоопределение заголовков через regex (`Chapter \d+`, `Глава \d+`) или группировка страниц порциями по ~12–15 страниц.
- [x] **Очистка шума и колонтитулов**:
- Фильтрация повторяющихся верхних/нижних колонтитулов («CCNA 200-301 Official Cert Guide», «Page 45 of 900»).
- [x] **Сохранение CLI/Console блоков**:
- Распознавание консольного вывода и команд сетевого оборудования (`Router# show ip interface brief`, `Switch(config)#`) и оборачивание в блоки кода Markdown для читалки с кнопкой «Копировать».
- [x] **Защита от сканов без OCR (Scanned PDF Guard)**:
- Валидация наличия текстового слоя: если суммарный объём извлечённого текста < 100 слов, возврат информативной ошибки с предложением использовать текстовый PDF или OCR.
- [x] **Интеграция в интерфейс читалки**:
- Разрешение загрузки `.pdf` в `<input type="file" accept=".epub,.pdf,.txt,.md">`.
- Обновление кнопки в библиотеке: `📁 Загрузить EPUB / PDF / TXT`.
---
### 7.1. 🖼️ Извлечение и отображение схем, топологий и диаграмм из PDF / EPUB (Planned)
- [ ] **Извлечение бинарных изображений на диск**:
- При парсинге PDF/EPUB извлекать графические объекты (`page.images` / архивы изображений) на файловую систему в изолированную директорию книги: `data/books/{book_id}/images/{img_name}`.
- Избегать раздувания базы данных SQLite: база хранит только текст и метаданные, бинарная графика хранится на диске.
- [ ] **Кэширующий медиа-эндпоинт**:
- Создать эндпоинт `GET /api/books/{book_id}/images/{image_name}` с заголовками кэширования (`Cache-Control: public, max-age=31536000, immutable`).
- [ ] **Встраивание в текст читалки**:
- Привязывать иллюстрации к соответствующей странице/главе через Markdown-теги: `![Схема топологии](/api/books/{book_id}/images/{img_name})`.
- Интерактивный просмотр и зум схем по клику в читалке.
---
### 8. 🔌 Пул соединений HTTP (`httpx.AsyncClient` с Keep-Alive) (Completed)
- [x] В [`app/dictionary.py`](file:///home/ai-projects/services/study.qpid.pro/app/dictionary.py) создать синглтон-клиент `get_http_client()` с пулом соединений:
```python
limits = httpx.Limits(max_keepalive_connections=10, max_connections=20, keepalive_expiry=60.0)
```
- [x] Заменить вызовы `async with httpx.AsyncClient()` в парсерах на переиспользуемый клиент.
- [x] Устранить 5 повторных TCP/TLS хэндшейков на каждое слово, сократив задержку внешних запросов на 300–600 мс.
- [x] Добавить вызов `close_http_client()` в `lifespan` FastAPI при остановке приложения для чистого закрытия открытых сокетов.
- [x] Добавить юнит-тесты `test_http_connection_pooling` в `tests/test_api.py` (22/22 passed).
---
### 9. 🗄️ Тюнинг SQLite PRAGMA и составных индексов
- [ ] В [`app/database.py`](file:///home/ai-projects/services/study.qpid.pro/app/database.py):
- Вынести `PRAGMA journal_mode=WAL;` в `init_db()` (убрать избыточный вызов при каждом запросе).
- Включить параметры производительности памяти Linux:
```python
conn.execute("PRAGMA synchronous = NORMAL;")
conn.execute("PRAGMA mmap_size = 268435456;") # 256MB mmap
conn.execute("PRAGMA cache_size = -32000;") # 32MB cache
```
- [ ] Обеспечить Zero-copy чтение страниц БД ядром Linux.
---
### 10. ✂️ Де-блоатинг внешних парсеров (YAGNI)
- [ ] Удалить лишний сетевой опрос Datamuse (`fetch_datamuse`) — он дублирует определения и перегружает ответ нерелевантными рифмами.
- [ ] Упростить хрупкий regex-парсинг разметки Wikitext, опираясь на стабильные REST JSON эндпоинты.
---
### 11. 📚 Локальный оффлайн-словарь (99%+ покрытия со словоформами, SQLite 10–20 МБ)
- [ ] Создать в SQLite таблицу:
```sql
CREATE TABLE IF NOT EXISTS offline_dictionary (
term TEXT PRIMARY KEY COLLATE NOCASE,
translation TEXT NOT NULL,
phonetic TEXT DEFAULT '',
pos TEXT DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_offline_dict_term ON offline_dictionary(term COLLATE NOCASE);
```
- [ ] Сформировать и интегрировать базу на 120 000–180 000 статей (~10–20 МБ в SQLite, сжатый архив ~3.5–5 МБ):
- Включить все словоформы (past tense, irregular verbs, plurals, participles).
- Включить фразовые глаголы, современный сленг и IT/Tech терминологию.
- [ ] Использовать архитектуру **Zero Daemon**: нативный SQLite с `PRAGMA mmap_size` (без отдельного контейнера Redis, чтение < 0.1 мс прямо из оперативной памяти процесса).
- [ ] Реализовать логику словарного поиска:
1. Сначала запрос в `offline_dictionary` $\rightarrow$ мгновенный ответ за 0.1 мс.
2. Только при отсутствии слова в базе $\rightarrow$ fallback-запрос во внешнюю сеть.
- [ ] Полностью исключить исчерпание лимитов внешних API (MyMemory 100 req/day).
---
### 12. 🧩 Модульность фронтенда на нативных ES-модулях (без Webpack/Vite)
- [ ] Перевести [`app/static/js/app.js`](file:///home/ai-projects/services/study.qpid.pro/app/static/js/app.js) на нативный браузерный синтаксис `<script type="module">`.
- [ ] Выделить модули с нативным `import/export`:
- `modules/reader.js` — читалку, блок-парсер и навигацию по главам.
- `modules/srs.js` — алгоритм SM-2, флэшкарты и квиз.
- `modules/api.js` — клиентские сетевые вызовы.
- `modules/ui.js` — тосты, модальные окна, темы оформления.
- [ ] Сохранить полное отсутствие внешних сборщиков (0 npm-пакетов, чистая платформа).
---
## ⏳ Coming Soon
- [ ] **🌐 Быстрый захват из браузера (Bookmarklet)**:
- Создать JavaScript-букмарклет для добавления слова/фразы из любого окна браузера в личный словарь [`study.qpid.pro`](https://study.qpid.pro) (низший приоритет).
- [ ] **Нативная автономная озвучка (HTML5 Web Speech API / `speechSynthesis`)**:
- Использовать встроенный в браузер синтезатор речи `window.speechSynthesis` (en-US, en-GB).
- 0 сетевых запросов к серверу, 100% оффлайн-работа.
- Поддержка озвучивания отдельных слов, фраз, идиом и предложений-контекстов читалки.
- [ ] **Возврат режима «Диктант» в SRS**:
- Возобновление работы аудиотренажёра на базе нативного браузерного синтезатора речи без зависимости от внешних аудиофайлов.
---
## 🛠️ Правила и соглашения проекта (Ponytail Rules)
1. **Stdlib & Native First**: стандартная библиотека языка и возможности платформы (браузера, SQLite, Linux) всегда приоритетнее внешних библиотек и микросервисов.
2. **Меньше кода — меньше багов**: удаление кода и упрощение структуры всегда лучше усложнения.
3. **Безопасность**: все мутации данных изолированы по `user_id`, пароли защищены PBKDF2 с уникальной солью.
4. **Тестирование**: после каждого изменения запуск `pytest tests/test_api.py -v` (100% тестов должны быть зелеными).