nginx nasłuchuje wyłącznie na IPv4, a w obrazie alpine localhost rozwiązuje
się najpierw na ::1. Healthcheck kończył się więc odmową połączenia i
kontener nigdy nie osiągał stanu healthy - wyszło to przy pisaniu CI,
gdzie ten sam sposób odpytania nie działał.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Konfiguracja we flat configu: zalecane zestawy js, typescript-eslint oraz
eslint-plugin-vue. Bloki <script setup lang="ts"> obsługuje vue-eslint-parser
przekazujący zawartość do parsera TypeScriptu.
Odstępstwa od domyślnych reguł:
- vue/multi-word-component-names wyłączone - kafelki panelu mają nazwy
własne i nie każda jest dwuczłonowa
- no-unused-vars przepuszcza nazwy z prefiksem podkreślenia
- no-console wyłączone dla testu dymnego, który raportuje wynik na stdout
Szablony przepuszczone przez eslint --fix, co ujednoliciło łamanie
atrybutów i domykanie znaczników. Po zmianach kontrola typów, build
i test dymny nadal przechodzą.
Dochodzą skrypty npm: lint (z --max-warnings 0) oraz lint:fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README opisuje pracę lokalną (npm run dev z proxy na backend), budowanie,
uruchomienie kontenera oraz strukturę katalogu src z rolą każdego pliku.
Spisane też zasady, które utrzymują panel spójnym: stan płynie z serwera,
zero innerHTML, ikony jako komponenty zamiast skryptu z CDN.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ładuje artefakt z dist/ w jsdom, podstawiając atrapy WebSocketu i fetcha,
a następnie sprawdza dwie rzeczy naraz:
- czy dane z serwera faktycznie renderują się w interfejsie (aktualny utwór,
kolejka, historia, baza plików, głośność, tryb zapętlania, odmiana
liczebników, formatowanie czasu)
- czy kliknięcia trafiają we właściwe endpointy - każdy przycisk sterujący
porównywany jest z oczekiwaną parą metoda + ścieżka
Razem 31 sprawdzeń, uruchamiane przez npm run smoke po npm run build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rozdzielenie na dwie niezależne usługi:
- frontend/Dockerfile buduje panel w etapie node:22-alpine, a artefakt
serwuje nginx:1.27-alpine. Obraz nie zawiera Node'a ani źródeł.
- frontend/nginx.conf serwuje aplikację jednostronicową (nieznane ścieżki
wracają jako index.html), cache'uje /assets z sumą kontrolną w nazwie,
a /api i /ws przekazuje do usługi discord-bot. Przeglądarka widzi jedno
źródło, więc backend nie potrzebuje CORS-u.
Adres backendu siedzi w zmiennej nginxa, więc nazwa jest rozwiązywana
przy żądaniu i panel wstaje niezależnie od kolejności startu kontenerów.
- Proxy nie limituje rozmiaru ciała żądania i nie buforuje go, żeby
wysyłka dużych plików wideo/audio przechodziła bez zmian w backendzie.
Timeouty podniesione, bo panel trzyma WebSocket otwarty na stałe.
- Root Dockerfile wraca do samego Pythona i wystawia port 8000.
- compose.yaml uruchamia obie usługi we wspólnej sieci: panel na :80,
API na :8000 (wystawione do diagnostyki).
- Katalog static/ znika z repozytorium - nie ma już czego serwować.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Widok rozbity na komponenty odpowiadające kafelkom pulpitu:
- AppHeader, VoiceChannelPanel, PlayerPanel, AddTrackPanel
- UploadsPanel wraz z UploadedFileRow współdzielonym z oknem modalnym
- QueuePanel i QueueItem - przeciąganie, zmiana kolejności, limit odtworzeń
- HistoryPanel, ModalShell, SettingsModal, UploadsLibraryModal, ToastHost
Arkusz stylów przeniesiony ze static/style.css bez zmian w wyglądzie.
Style, które app.js wstrzykiwał inline przy budowaniu HTML-a, przeniesione
do klas współdzielonych i bloków scoped w komponentach.
Poprawki bezpieczeństwa wynikające z przepisania:
- Tytuły utworów i nazwy plików nie trafiają już do innerHTML. Poprzednia
wersja składała wiersze kolejki, historii i listy plików ze stringów,
więc tytuł zawierający znaczniki HTML był wstawiany do DOM bez escapowania.
- Nazwy plików nie są już wklejane do atrybutów onclick z ręcznym
escapowaniem apostrofu, które i tak nie obejmowało cudzysłowu. Zdarzenia
podpina Vue, bez generowania kodu w stringach.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Logika, która w app.js żyła w zmiennych globalnych, trafia do czterech
composables o wyraźnym zakresie odpowiedzialności:
- useBotStatus jedno połączenie WebSocket na aplikację, z licznikiem
subskrybentów i ponawianiem po zerwaniu. Flaga paused
wstrzymuje aktualizacje na czas przeciągania utworów.
- useToasts kolejka powiadomień wraz z helperem run(), który
opakowuje wywołanie API w parę komunikatów sukces/błąd.
- useTrackProgress płynny pasek postępu - między pakietami z serwera czas
dopisujemy lokalnie, a każdy pakiet resynchronizuje licznik.
- useUploads serwerowa baza plików, współdzielona przez kafelek na
pulpicie i okno z pełną listą.
Dochodzi też utils/plural.ts z polską odmianą liczebników, zamiast
powtarzanego w kilku miejscach warunku count < 5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- types/status.ts spisuje kształt odpowiedzi /api/status oraz pakietów
rozgłaszanych po WebSockecie. Dotąd struktura tych danych istniała
wyłącznie w głowie autora i w rozsianych po kodzie odwołaniach.
- api/client.ts zbiera wszystkie adresy endpointów w jednym miejscu i
ujednolica obsługę błędów: pole "detail" z odpowiedzi FastAPI trafia do
wyjątku ApiError, więc warstwa widoku dostaje gotowy komunikat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Panel przestaje być zestawem plików statycznych serwowanych przez backend
i staje się osobnym projektem w katalogu frontend/.
- Vue 3 z Composition API jako framework - szablony komponentów są bliskie
dotychczasowemu HTML-owi, więc migracja nie wymaga przepisywania widoków
na inny model mentalny.
- TypeScript w trybie strict; kontrakt danych z API będzie opisany typami.
- Vite jako bundler, z aliasem @ na src/ i budowaniem do dist/.
- Serwer deweloperski przekierowuje /api i /ws na backend; adres można
nadpisać zmienną VITE_BACKEND_URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>