Added MOCK=1 mode for local dev
Deploy / deploy (push) Successful in 9s

This commit is contained in:
2026-08-31 18:45:53 +05:00
parent 53d98bfd53
commit fed9ad594c
3 changed files with 137 additions and 2 deletions
+46
View File
@@ -0,0 +1,46 @@
# Ревью 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.