Commit Graph
30 Commits
Author SHA1 Message Date
lgooseandClaude Bot 1f0a92b6f6 fix(ci): stabilne sprawdzenie proxy w jobie image panelu
Panel / Lint, typy i build (pull_request) Successful in 4m56s
Panel / Testowy build obrazu (pull_request) Successful in 18s
Krok padał na runnerze bez żadnego komunikatu. Powód był podwójny:

- asercja zakładała, że bez backendu nginx odpowie 502, a to zależy od
  tego, jak szybko zawiedzie DNS. Na runnerze zapytanie wisiało 30 sekund,
  aż do domyślnego resolver_timeout.
- grep bez dopasowania kończy się kodem 1, a Gitea uruchamia kroki w
  bash -e -o pipefail, więc podstawienie zmiennej przerywało cały krok,
  zanim zdążył wypisać powód. Lokalnie tego nie było widać, bo zsh nie
  ustawia tych flag.

Zamiast opierać się na tym, że proxy zawiedzie, job stawia teraz atrapę
backendu pod nazwą discord-bot - dokładnie tą, którą wskazuje nginx.conf
i compose.yaml - i sprawdza, że przez proxy wraca treść z atrapy. Test
przechodzi więc wtedy, gdy przekazywanie faktycznie działa, a nie wtedy,
gdy zawodzi w oczekiwany sposób. Sprawdzane jest też /ws.

Pozostałe usztywnienia: limit czasu na każde zapytanie, || true tam, gdzie
brak dopasowania jest poprawnym wynikiem, jawne komunikaty błędów z logiem
kontenera oraz sprzątanie kontenerów i sieci przez trap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:44:07 +02:00
lgooseandClaude Bot c5e016605b ci: pipeline panelu dla Gitea Actions
Panel / Lint, typy i build (pull_request) Successful in 4m55s
Panel / Testowy build obrazu (pull_request) Failing after 49s
Workflow .gitea/workflows/frontend.yaml z dwoma jobami:

- verify  npm ci, ESLint, kontrola typów, build produkcyjny i test dymny
- image   walidacja compose.yaml, build obrazu panelu i sprawdzenie
          działającego kontenera; job czeka na verify

Test dymny działa na zbudowanym artefakcie, a nie na źródłach, więc
sprawdza dokładnie to, co trafia do obrazu.

Job image weryfikuje trzy rzeczy w uruchomionym kontenerze: czy nginx
oddaje panel, czy nieznana ścieżka wraca jako index.html (fallback SPA)
oraz czy /api jest przekazywane do backendu. Backendu w tym kroku nie ma,
więc oczekiwaną odpowiedzią jest błąd bramy - 502 dowodzi, że reguła proxy
istnieje i celuje w usługę discord-bot, podczas gdy 404 oznaczałoby, że
nginx potraktował /api jak ścieżkę aplikacji.

Filtry paths ograniczają uruchomienia do zmian w katalogu frontend,
compose.yaml i samym workflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:34:55 +02:00
lgooseandClaude Bot 1b424eaa7d fix(panel): healthcheck obrazu odpytuje 127.0.0.1 zamiast localhost
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>
2026-08-21 01:34:55 +02:00
lgooseandClaude Bot 105f3e6c6a chore(frontend): ESLint dla Vue i TypeScriptu
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>
2026-08-21 01:34:55 +02:00
lgooseandClaude Bot fd8d71a80d ci: pipeline backendu dla Gitea Actions
Backend / Lint i formatowanie (pull_request) Successful in 20s
Backend / Testy i kontrakt API (pull_request) Successful in 4m58s
Backend / Testowy build obrazu (pull_request) Successful in 1m34s
Workflow .gitea/workflows/backend.yaml z trzema jobami:

- lint   ruff check oraz ruff format --check --diff
- test   testy jednostkowe i kontrakt API, plus compileall całego pakietu
- image  testowy build obrazu; job czeka na lint i test

Job image nie poprzestaje na zbudowaniu obrazu - uruchamia kontener bez
prawdziwego tokenu Discorda i czeka, aż /api/status odpowie. Panel łączy
się z backendem po sieci, więc sam start procesu niczego nie dowodzi.
Zapytanie leci z wnętrza kontenera, dzięki czemu krok nie zależy od tego,
czy runner ma zainstalowanego curla albo wgeta.

Filtry paths ograniczają uruchomienia do zmian w kodzie Pythona,
zależnościach, konfiguracji lintera, Dockerfile i samym workflow.

Zależności instalują się z gotowych kółek, więc obraz CI nie potrzebuje
kompilatora. ffmpeg i libopus są wymagane dopiero przy odtwarzaniu,
a testy nie dotykają warstwy audio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:25:40 +02:00
lgooseandClaude Bot 9ae22704ff test: przypięcie powierzchni API
Panel jest osobnym klientem HTTP, więc zmiana ścieżki albo metody psuje go
po cichu - w kodzie Pythona nic nie przestaje się kompilować. Test buduje
aplikację, generuje specyfikację OpenAPI i porównuje ją z listą 22
endpointów w obie strony:

- brak oczekiwanego endpointu to błąd
- nowy endpoint spoza listy również, żeby rozszerzenie API było świadomą
  decyzją, a nie skutkiem ubocznym

Sprawdzany jest też sam dokument OpenAPI, co wyłapuje błędy w deklaracjach
tras i modelach żądań.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:25:40 +02:00
lgooseandClaude Bot 2a961e1a8a chore: konfiguracja ruff i poprawki wskazane przez lintera
Ruff jako linter i formatter, konfiguracja w pyproject.toml:

- reguły E, W, F, I, N, UP, B, C4, SIM, TID, BLE, RUF
- extend-immutable-calls dla fastapi.Depends, File i Query - wstrzykiwanie
  zależności opiera się na wywołaniach w wartościach domyślnych argumentów,
  więc bugbear B008 zgłaszał tam 66 fałszywych trafień
- zakaz importów względnych, aby ścieżki w pakiecie czytało się jednoznacznie
- katalog frontend wyłączony z analizy

Poprawki, które wskazał linter:

- Awaitable, Callable i AsyncIterator importowane z collections.abc zamiast
  z typing
- zbędny argument trybu w open(path, "r")
- contextlib.suppress zamiast try/except/pass przy anulowaniu zadania
- zdjęte dwie niepotrzebne dyrektywy noqa
- pięć linii przekraczających 100 znaków zawiniętych

Całość przepuszczona przez ruff format. Do .gitignore dochodzi .ruff_cache/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:25:40 +02:00
lgooseandClaude Bot ccfb0d2e34 docs: README z opisem struktury pakietu
Opisane uruchamianie, zmienne środowiskowe, testy oraz mapa pakietu
z klasą przypisaną do pliku. Spisane też trzy zasady, które trzymają
strukturę w ryzach: MusicPlayer jest jedynym właścicielem stanu, kolejka
jest nieusuwalna i śledzona indeksem, a zależności idą przez konstruktory
zamiast globalnych singletonów.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:11:37 +02:00
lgooseandClaude Bot 0879120c85 test: regresja arytmetyki kolejki wskaźnikowej
Kolejka jest nieusuwalna, a pozycję śledzi indeks, więc przeskoki,
usuwanie i zmiana kolejności muszą ten indeks poprawnie przesuwać. To
najbardziej podatna na błędy część przeniesionego kodu, więc dostaje
testy przypinające zachowanie:

- start odtwarzania przy dodaniu do pustej kolejki
- limit odtworzeń usuwający utwór i cofający indeks
- przechodzenie dalej oraz oba tryby zapętlania
- koniec kolejki czyszczący aktualny utwór
- przeskok, usunięcie utworu przed indeksem i usunięcie odtwarzanego
- zmiana kolejności zachowująca indeks na odtwarzanym utworze
- stosowanie głośności serwera i zatrzymanie przy niedziałających utworach

16 przypadków na atrapach Discorda, bez sieci i ffmpega. Uruchamiane
przez python -m unittest discover -s tests -t . - bez nowych zależności.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:11:37 +02:00
lgooseandClaude Bot c8a188647b fix(music): zatrzymanie pomijania, gdy nie da się odtworzyć żadnego utworu
Utwór, dla którego nie udało się zbudować świeżego źródła, był pomijany
przez rekurencyjne wywołanie play_next(advance=True). Przy włączonym
zapętlaniu kolejki - a przy zapętlaniu jednego utworu tym bardziej -
kolejny indeks wskazywał znowu na utwór niedziałający, więc pomijanie
kręciło się w kółko, zasypując kanał powiadomieniami.

Licznik nieudanych prób wędruje teraz przez play_next. Po liczbie
niepowodzeń równej długości kolejki odtwarzanie zatrzymuje się z jednym
komunikatem zamiast pętli.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:11:22 +02:00
lgooseandClaude Bot ab1d0d4a91 fix(music): stosowanie głośności serwera do nowo uruchamianych utworów
YTDLSource powstaje z domyślną głośnością 0.5, a ustawienie głośności
zmieniało tylko źródło aktualnie odtwarzane. Nowy utwór startował więc
na 50%, podczas gdy panel i /api/status raportowały stan serwera - domyślnie
100%. Zmiana głośności "znikała" po przejściu do kolejnego utworu.

Teraz play_next ustawia na źródle głośność ze stanu serwera, zanim
przekaże je do odtworzenia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:11:10 +02:00
lgooseandClaude Bot 526e9ab644 refactor: korzeń kompozycji i usunięcie starych modułów
- Application składa wszystkie zależności w jednym miejscu i prowadzi bota
  równolegle z serwerem web. Panel przeżywa awarię logowania do Discorda,
  tak jak dotąd, ale komunikat idzie przez logging zamiast print.
- zyrarbot/__main__.py pozwala uruchomić pakiet przez python -m zyrarbot
  i konfiguruje logowanie; main.py zostaje cienkim punktem wejścia.
- Usunięte bot.py, web.py i ytdl.py - ich zawartość rozeszła się po
  pakiecie. Zniknął przy tym globalny obiekt bota tworzony przy imporcie
  modułu, który wymuszał kolejność importów między bot.py a web.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:59 +02:00
lgooseandClaude Bot 3fb1e26fb4 refactor(web): routery tematyczne i wstrzykiwanie zależności
22 endpointy z jednego pliku rozdzielone na routery według obszaru:
status, playback, queue, voice, uploads, settings i websocket.

- Powtarzany w każdym endpoincie blok "pobierz cog, sprawdź czy istnieje,
  ustal aktywny serwer, w razie czego rzuć 400" zastąpiony zależnościami
  z dependencies.py. Zniknęło 22-krotne bot.get_cog("MusicBot").
- Zależności wędrują przez app.state zamiast importu globalnego obiektu
  bota, więc warstwa web nie ma już zależności cyklicznej z modułem bota.
- create_app składa aplikację i montuje routery.
- Wycofane @app.on_event("startup") zastąpione lifespanem, który dodatkowo
  sprząta po sobie: odpina hook i zatrzymuje pętlę rozgłaszania.

Kontrakt HTTP pozostaje bez zmian - te same ścieżki, metody, kody błędów
i kształt odpowiedzi.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:59 +02:00
lgooseandClaude Bot c2f6d265c1 refactor(web): rozgłaszanie stanu po WebSockecie
- ConnectionManager - rejestr aktywnych połączeń (bez zmian poza typami).
- StatusService - budowanie obrazu stanu bota. Endpoint get_status miał
  126 linii i składał odpowiedź w dwóch wariantach; teraz jest to zestaw
  metod budujących poszczególne fragmenty payloadu.
- StatusBroadcaster - rozgłaszanie na żądanie oraz pętla cykliczna, wraz
  z zatrzymywaniem zadania przy wyłączaniu aplikacji. Wcześniej zadanie
  było tworzone w on_event("startup") i nigdy nie anulowane.

Wariant odpowiedzi dla bota bez serwerów zwraca teraz komplet kluczy
(dochodzi queue_index: -1), więc panel dostaje zawsze ten sam kształt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:42 +02:00
lgooseandClaude Bot 6e61892c04 refactor(web): modele żądań w osobnych plikach
Jedenaście klas pydantic zadeklarowanych jednym blokiem na górze web.py
rozdzielone na pliki zgodnie z zasadą jedna klasa = jeden plik. Każdy
model dostaje docstring mówiący, do czego służy - z samych nazw pól
(np. channel_id akceptujące pusty string jako reset) nie wynikało to
w oczywisty sposób.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:42 +02:00
lgooseandClaude Bot b2651bb957 refactor(bot): klient Discorda i cienki cog komend
- ZyrarBot buduje intencje, rejestruje cog i synchronizuje drzewo komend.
- MusicCog zawiera już wyłącznie obsługę komend ukośnikowych: walidację
  argumentów, komunikaty dla użytkownika i wywołania MusicPlayer. Cała
  praca ze stanem zniknęła z tego pliku.
- GuildResolver ustala kontekst serwera dla panelu webowego, który nie ma
  interakcji Discorda - wcześniej robiła to funkcja get_active_guild
  w web.py, sięgająca po globalny obiekt bota.
- Powtarzana w czterech komendach odpowiedź "dodano do kolejki" albo
  "teraz odtwarzane" zebrana w jednym helperze.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:28 +02:00
lgooseandClaude Bot a143cb21d3 refactor(music): MusicPlayer jako jedyny właściciel stanu odtwarzania
Logika odtwarzania wyprowadzona z coga Discorda do osobnej klasy. Dotąd
kolejkę modyfikowały dwie niezależne ścieżki: metody MusicBot oraz
endpointy w web.py sięgające po music_cog.queues[guild_id] i ustawiające
music_cog.jump_flag wprost. Teraz obie warstwy wołają MusicPlayer.

Przeniesione bez zmiany zachowania: enqueue, play_next wraz z arytmetyką
indeksu i limitem odtworzeń, remove, reorder, jump, sterowanie głośnością
oraz hook on_state_change rozgłaszający stan po WebSockecie.

Wybór kolejnego indeksu wydzielony do _next_index, dzięki czemu reguły
trybów zapętlania i przeskoku dają się czytać bez śledzenia całej metody.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:28 +02:00
lgooseandClaude Bot 4cc00618d1 refactor(music): stan serwera, tryb zapętlania i budowanie powiadomień
- GuildMusicState zbiera stan jednego serwera w jedną strukturę. Dotąd
  było to dziewięć równoległych słowników indeksowanych guild_id
  (queues, currents, volumes, loop_currents, loop_playlists, last_channels,
  history, queue_indices, jump_flag), co wymagało pamiętania, że przy
  każdej zmianie trzeba ruszyć kilka z nich naraz.
- LoopMode zastępuje parę booleanów loop_current/loop_playlist, których
  kombinacja "oba włączone" nie miała sensu, a mimo to była osiągalna.
  Wartości enuma (off/playlist/single) odpowiadają kontraktowi API panelu.
- TrackEmbedFactory zbiera budowanie osadzeń Discorda, powielone dotąd
  w enqueue_track i play_next.
- NotificationChannelResolver opisuje kolejność szukania kanału logów:
  zapamiętany -> z konfiguracji -> pierwszy zapisywalny na serwerze.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:11 +02:00
lgooseandClaude Bot 53ffae1663 refactor(audio): wydzielenie warstwy pozyskiwania dźwięku
ytdl.py rozbity na cztery pliki o rozłącznych rolach:

- YtdlOptions   opcje yt-dlp i FFmpeg, wcześniej moduły-globalne
- MediaProbe    odczyt długości pliku przez ffprobe
- YTDLSource    samo źródło audio - dane utworu i zliczanie odtworzonego
                czasu; dochodzą właściwości is_local, filepath, display_url
                i has_web_link, które zastępują powtarzane w kilku miejscach
                data.get("webpage_url") or url
- TrackFactory  budowanie źródeł z zapytania, adresu lub pliku

Rozdzielenie źródła od sposobu jego tworzenia usuwa globalną instancję
YoutubeDL i pozwala podmienić fabrykę w testach. Metoda recreate zbiera
w jednym miejscu odtwarzanie utworu od nowa z zachowaniem jego tożsamości
i pozostałej liczby odtworzeń.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:10:11 +02:00
lgooseandClaude Bot e59b4ad9f1 refactor(storage): repozytoria konfiguracji, historii i plików
Dostęp do danych trwałych schodzi za trzy repozytoria, zamiast być
rozsmarowany po metodach coga i endpointach:

- GuildConfigRepository - kanały powiadomień per serwer i domyślna liczba
  odtworzeń.
- HistoryRepository - historia per serwer z buforem w pamięci i limitem
  wpisów. Zasada odsiewania duplikatów (zgodny tytuł albo adres) siedzi
  teraz w HistoryEntry.is_same_track_as zamiast w warunku wbudowanym
  w listę składaną.
- UploadsRepository - katalog uploadów. Każda nazwa przechodzi przez
  os.path.basename w jednym miejscu, co zamyka temat ścieżek względnych
  przychodzących z API.

Wpisy historii i pliki opisują struktury HistoryEntry oraz UploadFileInfo
zamiast luźnych słowników z kluczami sprawdzanymi przez .get().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:09:52 +02:00
lgooseandClaude Bot e194a8611c refactor(config): Settings i JsonFileStore jako podstawa pakietu
Początek pakietu zyrarbot, budowanego wedle zasady jedna klasa = jeden plik.

- Settings zbiera konfigurację ze zmiennych środowiskowych oraz ścieżki
  i limity, które dotąd były literałami rozsianymi po bot.py i web.py
  ("config/config.json", "uploads", zakres 1-999, domyślne 5 odtworzeń).
- JsonFileStore zastępuje sześć niemal identycznych bloków open/json.load
  z ręcznym try/except. Brakujący lub uszkodzony plik daje pusty słownik
  zamiast wyjątku, a zapis sam tworzy katalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:09:52 +02:00
lgooseandClaude Bot 1b5ce2109b docs(frontend): opis panelu, struktury i uruchamiania
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>
2026-08-21 01:09:02 +02:00
lgooseandClaude Bot 2feb963e6a test(frontend): test dymny zbudowanego panelu
Ł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>
2026-08-21 01:09:02 +02:00
lgooseandClaude Bot bbd8a321f4 build: panel w osobnym kontenerze za nginxem
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>
2026-08-21 01:08:50 +02:00
lgooseandClaude Bot 8d32d5afc6 feat(api): backend wystawia wyłącznie API i steruje botem
Panel działa w osobnym kontenerze, więc serwis Pythona przestaje serwować
pliki statyczne:

- usunięty mount StaticFiles na "/" wraz z importem
- tytuł aplikacji zmieniony na "ŻyrarBOT API"
- GET / zwraca opis usługi zamiast panelu, żeby wejście pod adres backendu
  nie kończyło się nic niemówiącym 404
- GET /api/health informuje, czy bot zdążył połączyć się z Discordem;
  przydaje się jako healthcheck kontenera i przy diagnozie proxy

Pozostałe endpointy oraz WebSocket pozostają bez zmian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:08:50 +02:00
lgooseandClaude Bot d3408da2ed refactor: usunięcie starego panelu ze statycznych plików
static/app.js (1196 linii imperatywnego JS operującego na getElementById)
oraz static/index.html zastąpione w całości aplikacją z frontend/.
Arkusz stylów został wcześniej przeniesiony, nie usuwamy go ponownie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:08:31 +02:00
lgooseandClaude Bot 038fa2ead4 feat(frontend): komponenty panelu
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>
2026-08-21 01:08:31 +02:00
lgooseandClaude Bot 119bff00a3 feat(frontend): composables ze stanem panelu
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>
2026-08-21 01:08:04 +02:00
lgooseandClaude Bot 6bfa9b2a77 feat(frontend): kontrakt danych i typowany klient API
- 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>
2026-08-21 01:08:04 +02:00
lgooseandClaude Bot ab3c980597 build(frontend): szkielet projektu Vue 3 + TypeScript + Vite
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>
2026-08-21 01:07:47 +02:00