# Specyfikacja aplikacji — AI Arena (Lenavio Arena)

Data sporządzenia: 2026-07-31 (aktualizacja: opis stanu po wdrożeniu
widoku ekranu projektora i wiadomości Moderatora do wszystkich grup).
Zakres: kompletny opis stanu aplikacji na dzień sporządzenia (architektura,
dane, API, UI, bezpieczeństwo).

---

## 1. Cel i opis funkcjonalny

**AI Arena** to platforma do prowadzonych, moderowanych burz mózgów
("Aren"), w których **grupy uczestników** (nie pojedynczy użytkownicy)
dyskutują nad zadanym tematem (wizją, problemem, pytaniem) pod okiem
**Moderatora/Administratora**, a **Lena AI** — wirtualny asystent oparty o
duży model językowy (Groq API) — analizuje przebieg rozmowy, zadaje pytania
prowokujące, wychwytuje konflikty idei i na koniec generuje ustrukturyzowany
**Raport Syntezy Wiedzy**.

Każdą wypowiedź w dyskusji publikuje **Przewodnik Grupy** (`GROUP_LEADER`)
w imieniu swojej grupy — nie ma tu koncepcji pojedynczego "użytkownika
piszącego post": treści są zawsze przypisane do grupy, którą dana osoba
prowadzi w danej Arenie.

Dyskusja w każdej Arenie przechodzi przez 5 sztywno zdefiniowanych **faz**:

| # | Nazwa | Cel fazy | Rola Leny |
|---|-------|----------|-----------|
| 1 | Inspiracja | Swobodna generacja pomysłów bez krytyki | Zachęca do dzielenia się "szalonymi" wizjami |
| 2 | Eksploracja | Analiza możliwości i konsekwencji | Mapuje zależności, grupuje pomysły |
| 3 | Konfrontacja | Analiza ryzyk i słabych punktów | Adwokat diabła |
| 4 | Synteza | Budowanie mapy idei, wnioski | Krystalizuje wiedzę, usuwa sprzeczności |
| 5 | Rezultat | Raport końcowy | Formułuje gotowe Repozytorium Idei |

Po osiągnięciu fazy 5 Arena zmienia status na `COMPLETED` i trafia do
**Repozytorium Idei** (widok "Baza wiedzy") jako zamknięty case study z
raportem.

### 1.1. Widok na ekran (projektor sali) i głos Moderatora — nowość

Dyskusja grupowa prowadzona jest zwykle na żywo, w sali, gdzie każda grupa
widzi wypowiedzi wszystkich pozostałych grup oraz Leny AI na wspólnym,
dużym ekranie z projektora. Aby to wsparcie:

- **Osobny, publiczny widok `screen.php?arenaId=...`** ("Widok na ekran")
  — pełnoekranowa, ciemna strona zoptymalizowana pod odczyt z odległości
  (duża czcionka, wysoki kontrast, karty w stylu "3D" z mocnym cieniem i
  gradientem), z **kolorowaniem wypowiedzi wg grupy** (każda grupa/autor
  dostaje spójny kolor z 8-elementowej palety), osobnym stylem dla wpisów
  **Leny AI** (indygo) i osobnym, mocno wyróżnionym stylem banera dla
  **wiadomości Moderatora** (bursztynowo-złoty, cała szerokość, ikona 📢).
  Strona odpytuje `api.php?action=get_all` co 4 sekundy (proste
  odświeżanie w pętli — brak WebSocketów/SSE) i automatycznie przewija się
  do najnowszej wypowiedzi. Link do niej ("🖥 Widok na ekran (projektor)")
  jest dostępny w Panelu Moderatora w widoku Areny i otwiera się w nowej
  karcie — przeznaczonej do wyświetlenia na projektorze/dużym monitorze
  sali.
- **Wiadomość Moderatora do wszystkich grup** — ponieważ dyskusja
  ograniczona wyłącznie do wypowiedzi grup i cyklicznych interwencji Leny
  mogła być mało dynamiczna, Moderator/Admin danej Areny ma teraz
  dedykowany formularz w Panelu Moderatora ("📢 Wiadomość Moderatora do
  wszystkich grup"), który publikuje wpis w głównym strumieniu dyskusji
  (i na ekranie projektora) **wyraźnie oznaczony jako wiadomość Moderatora**
  — odróżniony od wypowiedzi grup i od komentarzy Leny AI zarówno etykietą
  (badge "📢 MODERATOR — wiadomość do wszystkich"), jak i kolorystyką karty.
  Służy do zadawania pytań, podbijania dyskusji i kierowania uwagi grup na
  konkretny wątek — bez konieczności wywoływania Leny AI.

---

## 2. Stos technologiczny

- **Backend:** czysty PHP (bez frameworka, bez Composera) — `api.php` jako
  jedyny endpoint danych (routing przez parametr `?action=...`), plus dwa
  osobne punkty wejścia renderujące HTML: `index.php` (SPA) i `screen.php`
  (widok na ekran projektora).
- **Frontend:** Vanilla JavaScript (ES6). Główna aplikacja to klasa
  `AIArenaApp` (`js/app.js`) — SPA-lite z ręcznym routingiem widoków
  (`switchView`), renderowaniem przez wstrzykiwanie HTML (`innerHTML`).
  Widok ekranu projektora (`js/screen.js`) to osobny, samodzielny skrypt
  (IIFE), bez zależności od `app.js`, korzystający tylko z `js/lena-ai.js`
  (nazwy faz) i własnego arkusza `css/screen.css`.
- **Baza danych:** **MariaDB/MySQL** (silnik InnoDB, `utf8mb4`) — tabele
  `users`, `arenas`, `posts`, `groups`, `moderator_arena_scope`,
  `site_settings`. *(Historyczna wersja aplikacji trzymała dane w pliku
  `store.json` — ten model został całkowicie zastąpiony bazą SQL; patrz
  sekcja 5. Instalator (`install.php`) potrafi jednorazowo zaimportować
  dane ze starego `store.json`, jeśli je znajdzie.)*
- **AI:** Groq API (`api.groq.com/openai/v1/chat/completions`), z listą
  modeli próbowanych po kolei w razie błędu:
  `llama-3.3-70b-versatile → llama3-70b-8192 → mixtral-8x7b-32768 → openai/gpt-oss-120b`.
  Klucz API **nie jest przechowywany na serwerze** — każdy Moderator/Admin
  wprowadza własny klucz Groq w modalu "Ustawienia AI", zapisywany
  wyłącznie lokalnie w przeglądarce (IndexedDB, `js/idb-settings.js`) i
  dołączany do żądań `trigger_lena`/`evaluate_post`/`change_phase` (faza 5).
- **Serwer WWW (środowisko deweloperskie):** Apache (XAMPP), `.htaccess`
  aktywne (`AllowOverride` wystarczające — potwierdzone self-testem).
- **Brak build stepu:** żadnego bundlera, transpilera ani menedżera
  pakietów — pliki `.css`/`.js` są serwowane bezpośrednio.

---

## 3. Struktura katalogów (stan aktualny)

```
LenavioArena/                         ← katalog projektu (poza public_html na docelowym hostingu)
├── SpecyfikacjaLenavioArena.md        ← ten dokument
├── lenavio_arena_engine/              ← POZA katalogiem publicznym (Logic Flow Gate)
│   ├── data/                          ← store.json (tylko jako legacy import przy instalacji)
│   └── config/
│       ├── db.php                     ← define('DB_HOST'/'DB_NAME'/'DB_USER'/'DB_PASS'/'DB_PORT', ...)
│       └── secrets.php                ← (opcjonalnie) klucze zapasowe — obecnie klucz Groq API pochodzi z klienta
├── test/                               ← wcześniejszy prototyp/piaskownica (nieużywany przez APP)
│   ├── index.html, app.js, style.css, api/lena.php
└── APP/                                ← KATALOG PUBLICZNY (= public_html na docelowym hostingu)
    ├── .htaccess                       ← anty-enumeracja + blokada plików wrażliwych rozszerzeń
    ├── index.php                       ← punkt wejścia SPA (front controller)
    ├── screen.php                      ← publiczny widok "na ekran" (projektor sali), tylko-odczyt
    ├── api.php                         ← jedyny backend endpoint danych (routing przez ?action=)
    ├── install.php                     ← instalator Logic Flow Gate + schemat MariaDB (samoblokujący się)
    ├── core/
    │   ├── .htaccess                    ← `Require all denied` — blokada całego folderu
    │   ├── bootstrap.php                ← guard constant + realpath() boundary-check + połączenie z DB (PDO) + sesja
    │   ├── auth.php                     ← currentUser()/requireAuth()/requireRole()/requireArenaModerator()
    │   ├── schema.php                   ← idempotentna migracja schematu (v1 → v2: model grupowy)
    │   ├── config.php                   ← wygenerowany: SECURE_PATH / PUBLIC_PATH (tylko stałe)
    │   └── .installed                   ← znacznik blokujący ponowne uruchomienie install.php
    ├── css/
    │   ├── style.css                    ← design system głównej aplikacji (custom properties, komponenty)
    │   └── screen.css                   ← osobny design system widoku na ekran (ciemny, duże czcionki, karty 3D)
    ├── js/
    │   ├── app.js                       ← klasa AIArenaApp — cała logika UI i wywołania API (SPA)
    │   ├── screen.js                    ← samodzielny skrypt widoku na ekran (polling + render kart)
    │   ├── lena-ai.js                    ← statyczna konfiguracja faz i person Leny (window.LenaAI)
    │   ├── idb-settings.js               ← przechowywanie klucza Groq API per-użytkownik w IndexedDB
    │   └── voice-recorder.js             ← SYMULOWANE nagrywanie głosu (patrz sekcja 9 — ograniczenia)
    └── data/                            ← USUNIĘTY po instalacji Logic Flow Gate (był tu store.json)
```

**Uwaga o środowisku lokalnym (XAMPP):** DocumentRoot Apache w tym
środowisku deweloperskim to `C:/xampp/htdocs` — czyli **cała** zawartość
`htdocs` (w tym `LenavioArena/lenavio_arena_engine/`) jest technicznie
osiągalna przez HTTP, ponieważ XAMPP nie rozdziela w standardowej
konfiguracji "public_html" od reszty konta tak, jak robi to typowy hosting
współdzielony. Na **docelowym serwerze online** katalog `APP/` będzie
właściwym `public_html` konta, a `lenavio_arena_engine/` będzie leżał
faktycznie poza zasięgiem serwera WWW (tak jak zakłada wzorzec Logic Flow
Gate) — dlatego ścieżka `lenavio_arena_engine` jest wyznaczana w kodzie
dynamicznie (`dirname($_SERVER['DOCUMENT_ROOT'])`), a nie na sztywno wpisana
pod XAMPP-a.

---

## 4. Bezpieczeństwo — Logic Flow Gate (Outside-Root Pattern)

Wdrożony wzorzec MrPrompt Logic Flow Gate, warstwy niezależne od siebie:

1. **Fizyczne wyniesienie danych i sekretów poza katalog publiczny** —
   dane bazy MariaDB (`lenavio_arena_engine/config/db.php` z danymi
   dostępowymi) nie znajdują się w `APP/`, więc żaden downloader/skaner nie
   może ich pobrać przez HTTP, niezależnie od konfiguracji PHP. Klucz Groq
   API nie jest w ogóle przechowywany po stronie serwera (patrz sekcja 2).
2. **`core/.htaccess`** — `Require all denied` na cały folder `core/`
   (niezależnie od PHP; działa nawet gdyby ktoś podmienił rozszerzenie pliku).
3. **`APP/.htaccess`** — `Options -Indexes`, blokada plików zaczynających
   się od `.` oraz rozszerzeń `.db/.sqlite/.log/.ini/.bak/.old/.sql/.md/.yml/.yaml/.env`.
4. **Guard constant + boundary-check w `bootstrap.php`** — `MRP_BOOTSTRAP`
   ustawiane jako pierwsza instrukcja; `realpath()` obu ścieżek weryfikuje,
   że `SECURE_PATH` **nie** jest zagnieżdżony wewnątrz `PUBLIC_PATH` (chroni
   przed błędną konfiguracją `DOCUMENT_ROOT` i symlinkami).
5. **Instalator samoblokujący się** — `install.php` po udanej instalacji
   zapisuje `core/.installed`; każde kolejne wejście HTTP zwraca `403` i nie
   renderuje już żadnej diagnostyki środowiska (uniemożliwia to, by żywy
   installer był mapą rekonesansową dla atakującego).
6. **Self-test `.htaccess` podczas instalacji** — installer sam odpytuje
   HTTP swój `core/config.php` i raportuje, czy blokada faktycznie
   zadziałała (wykrywa hosting z `AllowOverride None`, gdzie `.htaccess`
   jest cicho ignorowany).
7. **Autentykacja sesyjna** — sesje PHP (`session_start()` w
   `bootstrap.php`) z ciasteczkiem `httponly`, `samesite=Lax`, `secure`
   automatycznie włączane pod HTTPS. Hasła haszowane `password_hash()`
   (bcrypt/Argon — zależnie od PHP), weryfikowane `password_verify()`.

**Widok `screen.php` jest celowo publiczny (bez logowania)** — pokazuje te
same dane co niezalogowany widok Aren w SPA (dane Areny/postów są jawnie
publiczne przez `get_all`), więc nie stanowi to nowej powierzchni ataku;
patrz sekcja 6 co do zakresu tego, co jest, a co nie jest publiczne.

**Do zrobienia / do ustalenia przy migracji na docelowy hosting:**
- Uruchomić `install.php` ponownie na produkcji (po skopiowaniu katalogu
  `APP/` jako `public_html`) — utworzy `lenavio_arena_engine` automatycznie
  jeden poziom nad `public_html` (`dirname($_SERVER['DOCUMENT_ROOT'])`).
  Jeśli struktura konta wymaga 2 poziomów wyżej, trzeba będzie ręcznie
  utworzyć folder w docelowym miejscu i wskazać go instalatorowi, lub
  dostosować `install.php` (patrz sekcja 9, punkt "ścieżka o zmiennej
  głębokości").
- Skonfigurować dane dostępowe do MariaDB produkcyjnej w
  `lenavio_arena_engine/config/db.php` (host/port/nazwa bazy/użytkownik/hasło).

---

## 5. Model danych — baza MariaDB (zastępuje dawny `store.json`)

Baza relacyjna (schema v2), tabele główne:

```sql
users (
  id, username, email, password_hash, name, avatar, bio,
  interests JSON, achievements JSON,
  role ENUM('GROUP_LEADER','MODERATOR','ADMIN'),
  status ENUM('PENDING','ACTIVE','BLOCKED'),
  moderator_scope ENUM('ALL','SELECTED'),   -- tylko dla MODERATOR
  created_at
)

arenas (
  id VARCHAR(64) PK, title, description, image, category,
  author_id, author_name, author_avatar,
  moderator_id,                             -- domyślny Moderator/Admin Areny
  created_date, status ENUM('ACTIVE','COMPLETED'),
  current_phase TINYINT (1..5), persona,
  views_count, participants_count, ideas_count, is_bookmarked,
  pinned_idea TEXT,
  mind_map_nodes JSON,
  report_summary TEXT, report_key_ideas JSON, report_risks JSON
)

groups (
  id, arena_id, name, leader_id,             -- 1 Przewodnik = max 1 grupa na Arenę (UNIQUE arena_id+leader_id)
  created_at
)

posts (
  id VARCHAR(64) PK, arena_id, author_id, group_id,
  author_name, author_avatar, role VARCHAR(64),   -- 'Grupa' | 'Moderator AI' | 'MODERATOR_BROADCAST'
  content TEXT, timestamp_label, created_at,
  is_lena TINYINT(1), lena_mode VARCHAR(32),
  reactions JSON,                            -- {expand, alternative, shift, verify}
  evaluated_by_lena TINYINT(1), lena_evaluation TEXT,
  media JSON
)

moderator_arena_scope (user_id, arena_id)    -- przypisania Moderatorów o zakresie 'SELECTED' do konkretnych Aren
site_settings (setting_key, setting_value)   -- registration_mode ('OPEN'|'APPROVAL'), schema_version
```

Uwaga o kolumnie `posts.role`: pole tekstowe pełni podwójną funkcję —
etykiety wyświetlanej w UI **oraz** znacznika typu wpisu odczytywanego przez
frontend:
- `'Grupa'` — zwykła wypowiedź grupy (dodana przez jej Przewodnika),
- `'Moderator AI'` (z `is_lena = 1`) — wpis wygenerowany przez Lenę AI,
- `'MODERATOR_BROADCAST'` — wiadomość Moderatora/Admina do wszystkich grup
  (nowość, patrz sekcja 1.1 i 6) — renderowana w UI i na ekranie projektora
  z odrębnym, mocno wyróżnionym stylem, nigdy nie mylona z wypowiedzią
  Leny AI mimo że obie "przemawiają do całej sali".

`api.php` mapuje wiersze SQL na kształt JSON zgodny z dawnym
`store.json` (funkcje `arenaRow()`, `postRow()`, `sanitizeUser()`) — dzięki
temu kontrakt zwracany do frontendu (`{arenas: [...], posts: {arenaId: [...]}}`)
pozostał stabilny mimo zmiany silnika danych.

---

## 6. API (`api.php`) — kontrakt

Pojedynczy endpoint, routing przez `$_GET['action']` lub `action` w body
JSON. Odpowiedzi mutujące zwracają `{ status: 'success', data: { arenas, posts } }`
(pełny, świeży zestaw danych po każdej mutacji — brak dedykowanych,
częściowych odpowiedzi ani paginacji). Błędy zwracają `{ status: 'error', message }`
z odpowiednim kodem HTTP (`401`/`403`) dla akcji chronionych.

| Action | Metoda | Auth wymagane | Opis |
|---|---|---|---|
| `get_all` | GET | brak (publiczne) | Zwraca wszystkie Areny i posty (bootstrap SPA **i** źródło danych dla `screen.php`) |
| `me` | GET | brak | Zwraca dane zalogowanego użytkownika (lub `null`) |
| `register` | POST | brak | Rejestracja jako `GROUP_LEADER`; status `ACTIVE` od razu (tryb `OPEN`) lub `PENDING` (tryb `APPROVAL`, wymaga akceptacji Admina) |
| `login` / `logout` | POST | — | Logowanie/wylogowanie sesyjne |
| `create_arena` | POST | ADMIN | Tworzy nową Arenę (faza 1, status `ACTIVE`), opcjonalnie z przypisanym Moderatorem |
| `add_post` | POST | GROUP_LEADER prowadzący grupę w danej Arenie | Dodaje wypowiedź **grupy** do dyskusji, inkrementuje `ideasCount` |
| `moderator_post` | POST | Moderator Areny (patrz `requireArenaModerator`) | **Nowość.** Publikuje wiadomość Moderatora do wszystkich grup — `role='MODERATOR_BROADCAST'`, `is_lena=0`; wyświetlana wyraźnie oznaczona w strumieniu i na ekranie projektora |
| `add_reaction` | POST | zalogowany | Inkrementuje licznik reakcji na wpisie (`expand/alternative/shift/verify`) |
| `change_phase` | POST | Moderator Areny | Zmienia fazę Areny; przy fazie 5 → status `COMPLETED` + wywołanie Groq API generujące raport końcowy (z fallbackiem statycznym, jeśli API zawiedzie/brak klucza) |
| `evaluate_post` | POST | Moderator Areny | Zleca Lenie ocenę konkretnego wpisu przez Groq API; wynik zapisywany w poście (`evaluatedByLena`, `lenaEvaluation`) i publikowany jako osobny wpis Leny |
| `trigger_lena` | POST | Moderator Areny | Wywołuje Lenę w jednym z trybów (`ANALYSIS`, `PROVOCATION`, `EXPERT`) z opcjonalnym `customPrompt`; wynik dodawany jako wpis Leny |
| `toggle_bookmark` | POST | zalogowany | Przełącza zakładkę na Arenie |
| `pin_idea` | POST | zalogowany | Przypina wybraną myśl jako "Kluczową myśl" Areny |
| `list_group_leaders` | GET | MODERATOR/ADMIN | Lista aktywnych kont `GROUP_LEADER` (do przypisania grupom) |
| `admin_list_groups` | GET | Moderator Areny | Lista grup danej Areny wraz z ich Przewodnikami |
| `admin_create_group` | POST | Moderator Areny | Tworzy grupę w Arenie, z istniejącym Przewodnikiem lub tworząc nowe konto `GROUP_LEADER` "w locie" |
| `admin_delete_group` | POST | Moderator Areny | Usuwa grupę (jej dotychczasowe wpisy pozostają w dyskusji, `group_id` ustawiane na `NULL`) |
| `my_group` | GET | GROUP_LEADER | Zwraca grupę, którą zalogowany Przewodnik prowadzi w danej Arenie (lub `null`) |
| `my_groups` | GET | GROUP_LEADER | Wszystkie grupy prowadzone przez zalogowanego Przewodnika, we wszystkich Arenach |
| `admin_list_users` / `admin_create_user` / `admin_update_user` / `admin_delete_user` | GET/POST | ADMIN | Zarządzanie kontami i rolami |
| `admin_set_registration_mode` | POST | ADMIN | Przełącza tryb rejestracji (`OPEN` / `APPROVAL`) |

**Autoryzacja jest teraz egzekwowana po stronie serwera** (sesja PHP +
`requireAuth()`/`requireRole()`/`requireArenaModerator()` w `core/auth.php`)
— to zmiana względem wcześniejszej wersji prototypu, gdzie kontrola ról
była wyłącznie kosmetyczna po stronie frontendu. `requireArenaModerator()`
sprawdza, czy Moderator ma `moderator_scope = 'ALL'` albo czy Arena znajduje
się w jego `moderator_arena_scope` — Admini mają dostęp do wszystkiego.

Wciąż **brak**: tokenu CSRF, rate-limitingu wywołań do Groq API.
`Access-Control-Allow-Origin` odzwierciedla nagłówek `Origin` żądania z
`Access-Control-Allow-Credentials: true` — działa poprawnie z sesją
ciasteczkową, ale nie ogranicza originów do zdefiniowanej białej listy.

---

## 7. Model ról i uprawnień

| Rola | Widoczna nawigacja | Możliwości |
|---|---|---|
| **Niezalogowany (gość)** | Areny, Repozytorium Idei | Tylko odczyt (w tym `screen.php` — widok na ekran jest zawsze publiczny) |
| **GROUP_LEADER** (Przewodnik Grupy) | + Dashboard | Prowadzi maksymalnie jedną grupę na Arenę; dodaje wypowiedzi **w imieniu swojej grupy** (`add_post`), reaguje, zakłada zakładki |
| **MODERATOR** | + Panel Moderacji | W ramach Aren objętych jego zakresem (`ALL` lub `SELECTED`, patrz `moderator_arena_scope`): zmienia fazy, wywołuje Lenę, ocenia pojedyncze wpisy, przypina idee, kończy Arenę, tworzy/usuwa grupy i przypisuje im Przewodników, **publikuje wiadomości do wszystkich grup** (`moderator_post`) |
| **ADMIN** | + Administracja | Wszystko z MODERATOR na **wszystkich** Arenach + tworzenie Aren, zarządzanie kontami/rolami, tryb rejestracji |

Kontrola ról jest egzekwowana zarówno w UI (ukrywanie elementów), jak i
**po stronie serwera** w `api.php` (patrz sekcja 6) — próba wywołania akcji
spoza uprawnień zwraca `403` z komunikatem, niezależnie od tego, co
renderuje frontend.

---

## 8. Widoki frontendu

### 8.1. `index.php` + `js/app.js` (klasa `AIArenaApp`) — aplikacja SPA

SPA z ręcznym routingiem (`switchView`), widoki przełączane klasą
`.active` na elementach `.view-section`:

1. **ARENAS** (`renderArenas`) — siatka kart Aren z wyszukiwarką,
   filtrowaniem po kategorii/statusie, zakładkami (bookmark), skróconym
   opisem, licznikami (wpisy/uczestnicy/wyświetlenia), przypiętą myślą.
2. **ARENA_DETAIL** (`renderArenaDetail`) — najbardziej złożony widok:
   nagłówek Areny, pasek postępu faz (`phase-track`), **Panel Moderatora**
   (warunkowy, tylko MOD/ADMIN z uprawnieniami do tej Areny), zawierający:
   - przyciski wywołań Leny (analiza/prowokacja/konflikty/ryzyko),
   - przycisk **"🖥 Widok na ekran (projektor)"** — otwiera `screen.php?arenaId=...`
     w nowej karcie,
   - formularz **"📢 Wiadomość Moderatora do wszystkich grup"** — osobny od
     wywołań Leny; publikuje wpis wyraźnie oznaczony jako Moderator,
   - panel zarządzania grupami dyskusyjnymi (lista + dodawanie nowej grupy
     z istniejącym lub nowo tworzonym Przewodnikiem),

   dalej: strumień wpisów (dymki czatu, reakcje, ocena Leny inline, teraz
   też karty wiadomości Moderatora z odrębnym stylem bursztynowym), panel
   Przewodnika Grupy (zakładki "Podgląd dyskusji" / "Nowa wypowiedź",
   widoczny tylko dla Przewodnika prowadzącego grupę w tej Arenie, z
   opcjonalnym "nagraniem głosu" — patrz ograniczenia), sidebar z Mapą Idei
   (`mindMapNodes`) i statusem Leny — zgrupowane w jeden panel
   `sidebar-panel`, przypięty (`sticky`) przy scrollu.
3. **DASHBOARD** (`renderDashboard`) — dla `GROUP_LEADER`: lista
   prowadzonych grup (z linkiem do odpowiedniej Areny); dla
   MODERATOR/ADMIN: lista Aren z licznikiem grup.
4. **KNOWLEDGE** (`renderKnowledge`) — "Repozytorium Idei": lista
   zakończonych Aren (`status === 'COMPLETED'`) z podglądem raportu.
5. **ADMIN_PANEL** (`renderAdmin`) — statystyki globalne, tryb rejestracji,
   lista użytkowników z możliwością zmiany roli/statusu/zakresu moderacji,
   tworzenie nowych kont.

Dodatkowo: modale logowania/rejestracji, tworzenia Areny, tworzenia
użytkownika (Admin), ustawień AI (klucz Groq per-przeglądarka w
IndexedDB), raportu syntezy, system toastów (`showToast`), prosty parser
Markdown → HTML bez zależności zewnętrznych (`renderMarkdown` — obsługuje
nagłówki, pogrubienie, kursywę, kod inline, listy, akapity) używany do
renderowania odpowiedzi Leny.

### 8.2. `screen.php` + `js/screen.js` — Widok na ekran (projektor sali)

Osobna, samodzielna strona (poza routingiem SPA), przeznaczona do
wyświetlania na dużym ekranie/projektorze sali podczas warsztatu:

- **Publiczna, tylko do odczytu** — nie wymaga logowania; adres
  `screen.php?arenaId=<id>` jest udostępniany przez Moderatora osobie
  obsługującej projektor (lub otwierany na komputerze podłączonym do niego).
- **Nagłówek** ze sticky-owanym tytułem Areny, paskiem 5 faz (aktywna faza
  podświetlona gradientem indygo z uniesieniem 3D) i ewentualną przypiętą
  kluczową myślą.
- **Strumień kart** — każda wypowiedź to osobna, duża karta w stylu "3D"
  (mocny wielowarstwowy `box-shadow`, zaokrąglone rogi, delikatna górna
  poświata) z:
  - kolorem tła **rotowanym deterministycznie wg autora/grupy** (paleta 8
    gradientów: niebieski/zielony/pomarańczowy/fioletowy/różowy/turkusowy/
    żółty/czerwony) — ta sama grupa zawsze dostaje ten sam kolor w danej
    sesji przeglądarki,
  - osobnym stylem dla wpisów **Leny AI** (gradient indygo, avatar ✦),
  - osobnym, maksymalnie wyróżnionym stylem dla **wiadomości Moderatora**
    (baner bursztynowo-złoty na całą szerokość, gruba obwódka, ikona 📢,
    etykieta "MODERATOR — DO WSZYSTKICH GRUP", tekst pogrubiony) — nie do
    pomylenia z żadnym innym typem wpisu, nawet z odległości sali.
  - dużą, czytelną z odległości czcionką (`clamp(1.15rem, 1.6vw, 1.5rem)`
    dla treści, jeszcze większą dla tytułu Areny).
- **Automatyczne odświeżanie** — odpytuje `api.php?action=get_all` co 4
  sekundy; przy zmianie liczby postów automatycznie przewija widok do
  najnowszej wypowiedzi (`scrollIntoView`/`scrollTo` płynne).
- Brak zależności od `app.js` — korzysta tylko z `js/lena-ai.js` (nazwy i
  kolejność faz) oraz własnego arkusza `css/screen.css` (osobny, ciemny
  design system, niezależny od jasnego motywu głównej aplikacji w
  `css/style.css`).

---

## 9. Znane uproszczenia i ograniczenia (ważne, by nie zakładać więcej niż jest)

- **Nagrywanie głosu jest w pełni symulowane** (`voice-recorder.js`) — nie
  używa `MediaRecorder`/mikrofonu ani żadnego prawdziwego API
  transkrypcji. Po kliknięciu "Zatrzymaj" losuje jeden z 3 zaszytych na
  sztywno tekstów przykładowych. Wizualizacja "fal dźwiękowych" na
  `<canvas>` to losowe słupki, niepowiązane z żadnym realnym sygnałem
  audio.
- **`screen.php` nie ma własnego uwierzytelniania ani rate-limitingu** —
  jest celowo publiczny (tak jak dane Areny są jawnie publiczne przez
  `get_all`), ale każdy, kto zna/odgadnie `arenaId`, może otworzyć podgląd
  na żywo tej Areny. `arenaId` to `"arena-" + timestamp w ms"` — praktycznie
  nieodgadnialne z zewnątrz, ale nie jest to sekret kryptograficzny.
- **Widok na ekran działa przez odpytywanie (polling), nie push** — przy 4
  sekundach odświeżania nowa wypowiedź pojawia się na ekranie z opóźnieniem
  do ~4s; brak WebSocketów/Server-Sent Events. Przy wielu jednoczesnych
  Arenach/dużym ruchu to prosty, ale nieskalujący się mechanizm.
- **Wiadomość Moderatora jest jednokierunkowa i bez adresata** — trafia do
  całego strumienia widocznego przez wszystkie grupy jednocześnie; nie ma
  możliwości wysłania jej tylko do wybranej grupy ani oznaczenia jej jako
  "przeczytane".
- **Brak walidacji wejścia po stronie serwera** poza podstawowym
  `trim()`/pustym stringiem — treści są jednak poprawnie escapowane po
  stronie wyjścia w JS (`esc()`) przed wstrzyknięciem do DOM w obu
  widokach (`app.js` i `screen.js`), co chroni przed prostym XSS w
  renderowanych postach, także w widoku na ekran.
- **Brak paginacji/streamowania** — `get_all` zawsze zwraca cały zestaw
  danych; przy dużej liczbie Aren/postów odpowiedź będzie rosła liniowo
  bez limitu — dotyczy to też odpytywania przez `screen.js`.
- **Zapis nie jest opakowany w transakcje SQL** poza pojedynczymi
  zapytaniami `UPDATE`/`INSERT` — przy równoczesnych requestach możliwe są
  wyścigi na poziomie logicznym (np. dwa jednoczesne `add_post` nie kolidują
  ze sobą dzięki autoinkrementowanym ID, ale liczniki typu `ideas_count`
  nie są chronione blokadą wiersza poza domyślną atomowością pojedynczego
  `UPDATE ... SET x = x + 1`).
- **Folder `test/`** to wcześniejszy, niezależny prototyp
  (`test/index.html`, `test/app.js`, `test/api/lena.php`) — nie jest
  używany przez aplikację `APP/` i nie został objęty bramą
  bezpieczeństwa; wymaga osobnej decyzji (usunąć / zabezpieczyć / zostawić
  jako referencję).
- **Ścieżka o zmiennej głębokości** — obecny `install.php` liczy lokalizację
  folderu bezpiecznego jako `dirname($_SERVER['DOCUMENT_ROOT'])` (jeden
  poziom nad `public_html`). Jeśli docelowy hosting wymaga dwóch poziomów
  wyżej, trzeba to dostosować ręcznie przy migracji (patrz sekcja 4).

---

## 10. Jak dokładnie działa aplikacja — przebieg end-to-end

1. Przeglądarka wchodzi na `APP/index.php` (lub `APP/screen.php` dla
   widoku projektora). Plik na starcie ładuje `core/bootstrap.php`, który
   sprawdza istnienie `core/config.php` (jeśli brak — przekierowanie na
   `install.php`), weryfikuje przez `realpath()`, że `SECURE_PATH`
   (`lenavio_arena_engine/`) rzeczywiście leży poza `PUBLIC_PATH` (`APP/`),
   nawiązuje połączenie PDO z MariaDB (`db()`, z automatyczną migracją
   schematu przez `ensureSchema()`) i startuje sesję PHP. Dopiero po
   przejściu tej bramy renderowany jest właściwy HTML.
2. **`index.php`:** ładowane są `css/style.css`, `js/lena-ai.js`,
   `js/idb-settings.js`, `js/voice-recorder.js`, `js/app.js`. Na
   `DOMContentLoaded` tworzona jest instancja `window.app = new AIArenaApp()`,
   która woła `fetchMe()` (`GET api.php?action=me` — sesja) i `fetchStore()`
   (`GET api.php?action=get_all`), po czym renderuje domyślny widok
   **ARENAS**.
3. **`screen.php`:** ładowane są `css/screen.css`, `js/lena-ai.js`,
   `js/screen.js`. Skrypt odczytuje `arenaId` z atrybutu `data-arena-id`
   elementu root (wypełnianego przez PHP z `$_GET['arenaId']`), po czym
   natychmiast i cyklicznie (co 4s) woła `GET api.php?action=get_all`,
   znajduje właściwą Arenę w odpowiedzi i renderuje nagłówek + strumień
   kart — bez żadnej logowanej sesji.
4. **Logowanie/rejestracja:** `POST api.php?action=login|register` →
   sesja PHP (`$_SESSION['user_id']`) → frontend cache'uje
   `this.currentUser`/`this.currentRole` i odświeża store.
5. Użytkownik klika Arenę → `switchView('ARENA_DETAIL', arenaId)` →
   `loadArenaContext()` (dociąga grupę Przewodnika lub listę grup/Przewodników
   dla Moderatora/Admina) → `renderArenaDetail()` buduje: nagłówek, pasek
   faz, (jeśli Moderator/Admin z uprawnieniami) panel moderatora z linkiem
   do ekranu projektora i formularzem wiadomości do wszystkich grup,
   strumień postów, panel Przewodnika (podgląd/kompozycja), sidebar z mapą
   idei i statusem Leny.
6. **Dodanie wypowiedzi grupy:** `addPost()` → `POST api.php?action=add_post`
   (serwer weryfikuje `requireRole(['GROUP_LEADER'])` i że wywołujący
   faktycznie prowadzi grupę w tej Arenie) → zapis do `posts` z
   `role='Grupa'`, inkrementacja `ideas_count` → zwrot świeżego zestawu
   danych → frontend nadpisuje `this.store` i przerenderowuje widok.
7. **Wiadomość Moderatora do wszystkich grup:** `moderatorPost()` →
   `POST api.php?action=moderator_post` (serwer weryfikuje
   `requireArenaModerator($arenaId)`) → zapis do `posts` z
   `role='MODERATOR_BROADCAST'`, `is_lena=0` → wpis natychmiast widoczny w
   strumieniu SPA (wyróżniony bursztynowo) i — przy najbliższym odpytaniu
   (do 4s) — na ekranie projektora (baner na całą szerokość).
8. **Reakcja na wpis:** `addReaction()` → `POST ...action=add_reaction` →
   inkrementacja licznika w `reactions` danego posta → zapis → re-render.
9. **Interwencja Leny (Moderator/Admin):** `triggerLena(arenaId, mode,
   customPrompt)` → ustawia `lenaTriggering = true` (spinner w UI) →
   `POST ...action=trigger_lena` (z kluczem Groq pobranym z IndexedDB
   przeglądarki wywołującego) → backend weryfikuje uprawnienia Moderatora
   do tej Areny, buduje transkrypt całej dotychczasowej dyskusji
   (`author: content` linia po linii), składa system prompt zależny od
   trybu (`ANALYSIS`/`PROVOCATION`/`EXPERT`) + ewentualny `customPrompt`,
   woła `callGroqAPI()` — **realne zapytanie HTTP do Groq** (`curl`, próba
   po kolei 4 modeli, timeout 20s). Sukces → odpowiedź modelu dopisywana
   jako nowy post `isLena: true` w strumieniu. Porażka (brak klucza/limit/
   timeout) → **statyczny fallback tekstowy** zaszyty w PHP, żeby UI nigdy
   nie został bez odpowiedzi.
10. **Ocena pojedynczego wpisu:** `evaluatePost()` → podobny przepływ co
    wyżej, ale kontekstem dla Groq jest tylko jeden wskazany post; wynik
    zapisywany zarówno *na* ocenianym poście (`evaluatedByLena`,
    `lenaEvaluation` — pokazywane inline pod postem) jak i jako osobny nowy
    wpis Leny w strumieniu.
11. **Zmiana fazy / zakończenie Areny:** `changePhase()` →
    `POST ...action=change_phase`. Przy przejściu na fazę 5: status →
    `COMPLETED`, backend woła Groq z transkryptem całej Areny prosząc o
    "Raport Syntezy Wiedzy" (podsumowanie + 3 kluczowe idee + 2 ryzyka).
    Sukces → raport z odpowiedzi modelu (idee/ryzyka w tym wariancie są
    tekstem generycznym dopisanym przez PHP, nie parsowanym z odpowiedzi
    modelu — tylko `summary` pochodzi bezpośrednio z Groq). Porażka →
    kompletny fallback statyczny.
12. Zakończona Arena znika z aktywnego przepływu i pojawia się w widoku
    **KNOWLEDGE** (Repozytorium Idei) z przyciskiem "Zobacz raport"
    otwierającym modal z pełną treścią (`openReport()`).
13. **Dashboard/Admin** czytają ten sam `this.store` po stronie klienta i
    filtrują go lokalnie (np. grupy prowadzone przez Przewodnika przez
    dedykowane zapytanie `my_groups`) — żadne dodatkowe zapytania do API
    poza początkowym `get_all`/`me` (chyba że wykonywana jest mutacja lub
    dociągane są dane specyficzne dla roli, np. `admin_list_groups`).
14. Każda mutująca akcja API kończy się zapytaniami SQL (`INSERT`/`UPDATE`)
    do MariaDB — nie ma już pojedynczego pliku JSON pełniącego rolę całej
    bazy danych; `screen.js` czyta te same dane co SPA, wyłącznie przez
    `get_all`, więc każda zmiana widoczna w SPA pojawia się też (z
    opóźnieniem do czasu odpytania) na ekranie projektora.
15. Cała warstwa bezpieczeństwa (Logic Flow Gate) jest transparentna dla
    normalnego działania aplikacji — użytkownik końcowy nigdy jej nie
    widzi; ujawnia się tylko wtedy, gdy ktoś spróbuje wejść bezpośrednio na
    `core/*` (403) albo na starą ścieżkę `data/store.json` (404, plik już
    tam nie istnieje).
