Files
alexey.bagno fed9ad594c
Deploy / deploy (push) Successful in 9s
Added MOCK=1 mode for local dev
2026-08-31 18:45:53 +05:00

47 lines
5.3 KiB
Markdown
Raw Permalink 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.
# Ревью SPA/PWA-части целиком (App.tsx, docsStore, св, карточки, деплой-сборка). Не безопасность — архитектура и баги.
## Критично: крах всего приложения на одном битом доке
docsStore.get() делает JSON.parse(raw) без try/catch. Одна повреждённая запись в localStorage (частичная запись при переполнении квоты, ручная зачистка, битая
сериализация) — и localDocs() кидает исключение прямо в рендере (useState(localDocs())) → белый экран всего дашборда, не только вкладки Docs. Лечится одним
try/catch в get(), возвращающим null. Это единственное место, где SPA может умереть целиком.
## Свежесть данных невидима
.catch(() => {}) в поллинге: офлайн → UI молча показывает последние значения. Для панели за VPN это главный UX-дефект: tailscale отвалился — ты минутами смотришь
на CPU 12%, который был час назад. Ошибки мониторов попадают в значение (error: ... в тексте), а вот пропажа связи — нет. Ленивый фикс: lastOkAt timestamp рядом с
content, если Date.now() - lastOkAt > 10s — один бейдж «offline» в шапке.
## Гонка ответов при медленном VPN
setInterval(refresh, 3000) без коалесинга и отмены: при таймаутах/лагах два запроса в полёте, ответ на старый приходит позже нового — setContent откатывает данные
назад. На 3s-такте и tailscale-латентности реально. Один guard (reqSeq или AbortController) — три строки. Сюда же: connect fetch без таймаута, на мёртвом туннеле
запросы копятся.
## Дублирование транспорт-клиента ×4
createClient(HomelabService, createConnectTransport({ baseUrl: "" })) скопирован в App, ServiceCard, ScriptCard и docsStore. Один api.ts с экспортом client — и
правка транспорта (позже auth через caddy) в одном месте, а не четырёх. Это ровно то место, которое выстрелит, когда будешь прикручивать caddy-auth.
## Docs-синк: три дыры в модели
- Призраки: удалённый на сервере док живёт в localStorage вечно — pull удалять не умеет. Список только растёт.
- Вечный ретрай: dirty-док, который сервер отклоняет, пушится каждые 5s бесконечно, без счётчика и без сигнала юзеру.
- Черновики уезжают на сервер: каждое нажатие клавиши → localStorage → через ≤5s запушено. Второе устройство видит полуфразы; если на нём нет правок — оно
перезапишет твой док, когда ты его допишешь (last-write-wins уже задокументирован, но «push недописанного» делает конфликты чаще). Дешёвый фикс: пушить только
доки, к которым не было правок N секунд (debounce).
## Service worker: лишняя ручная работа
CACHE = "homelab-shell-v4" с ручным бампом версии — мёртвый груз: кэшируются только content-hashed /assets/, а activate и так удаляет старые кэши. Имя можно не
менять никогда. Обновление PWA при этом работает (network-first index + skipWaiting) — тут всё нормально, включая iOS-пункт про stale index.
# Мелочи
- Роутинга нет: вкладка — useState. Рефреш/OSError выкидывает на Monitor, глубокой ссылки нет. Осознанный YAGNI — ок, но одна строка location.hash вернула бы
back-button на iOS, который в standalone-PWA иначе мёртв (свайп назад закрывает приложение).
- String(e).replace(/^.*real error: /, "") — парсинг ошибок по строке. У connect-ошибок есть .message; регексп отвалится при первом изменении текста на сервере.
- error && String(e) в ScriptCard без setError("") при успехе... — есть, при успехе сбрасывается. Ок.
- Search в сервисах без debounce — при 30 карточках не нужно, не трогать.
- Модалки (info/output) без focus-trap и Escape — для личной панели терпимо, одна строка onKeyDown на document закрыла бы Escape.