docs/frontend.pl.md
Rama main · réplica de lectura del repositorio original.
---
source_sha: "8ca784f8a0b0"
---
# Kod konsoli { #the-consoles-code }
[Architektura](architecture.md) to backend. To jest druga połowa: aplikacja
Next.js w `frontend/`, dla kogoś, kto zaraz ją zmieni.
**Stack.** Next.js 15 (App Router) · React 19 · TypeScript strict · Tailwind ·
`next-intl` · TanStack Query · Zustand · vitest i Testing Library ·
Playwright. Menedżer pakietów i runner: **bun**.
## Gdzie co leży { #where-things-live }
| Ścieżka | |
| -------------------------------- | -------------------------------------------------------------------- |
| `src/app/[locale]/(dashboard)/…` | Produkt. Ścieżki mają **prefiks lokalizacji** |
| `src/app/api/…` | Route handlery proxujące do backendu |
| `src/lib/` | Typowani klienci API, `query-keys.ts` i rejestry opisane niżej |
| `src/hooks/` | Jeden na zasób — `use-agents`, `use-permissions`, … |
| `src/stores/` | Zustand, jeden na zagadnienie |
| `src/components/<domain>/` | UI według domeny; prymitywy w `ui/`, stany puste i błędu w `states/` |
Server Components są domyślne. `"use client"` jest od stanu, efektów i
handlerów, a nie z przyzwyczajenia.
## Wygląd konsoli { #console-presentation }
Używaj `PageHeader` do zwartych tytułów i akcji. Katalogi kart korzystają z
`ListCard layout="grid"`, bez dodatkowego panelu; tabele zachowują panel.
Przyciski używają neutralnych tokenów `action`, niezależnych od `primary`
dla zaznaczonych kontrolek i odznak. Fokus i krawędzie pól pozostają widoczne.
Osoby i organizacje korzystają z `EntityAvatar`: zdjęcia są kadrowane do koła,
a zastępują je inicjały na stałym kolorze. Agenci zachowują generowane twarze.
Opisy w katalogach zajmują dwa wiersze; otwarcie elementu pokazuje pełny opis.
Akcje asynchroniczne korzystają z `Button loading`, aby zachować etykietę i wymiary oraz blokować ponowne wysłanie. Stan oczekiwania trwa do końca wszystkich kroków, także przesyłania plików. Przyciski oparte na linkach zachowują jedno dziecko i blokują aktywację, gdy są niedostępne. Kontrolki mają spójny fokus klawiatury, zwarte rozmiary na desktopie i pola dotykowe 44px tam, gdzie jest miejsce. Nakładki animują się od swojego punktu zaczepienia; podpowiedzi zawijają tekst w granicach okna. Kalendarz używa języka konsoli, a postęp udostępnia rzeczywistą wartość i maksimum technologiom asystującym. Animacje respektują ustawienie Motion użytkownika.
## Przeglądarka nigdy nie woła backendu { #the-browser-never-calls-the-backend }
Każde żądanie idzie do `/api/*` w tej aplikacji, która przekazuje je do FastAPI
z tokenem dostępu pobranym z ciasteczka **HttpOnly**. To właśnie trzyma token
poza JavaScriptem, a URL backendu poza bundlem klienta.
Robi to jeden przekaźnik — `src/lib/platform-proxy.ts` — a nie ręcznie napisany
plik route na każdy endpoint, powtarzający te same dwanaście linijek.
!!! warning "Odpowiedź bez `Cache-Control` to nie odpowiedź, której nikt nie cache'uje"
Proxy stempluje `no-store` na wszystkim, czego backend nie oznaczył. Każda
odpowiedź tutaj zależy od ciasteczka, zestawu uprawnień i nagłówka
organizacji, a lista pobrana ponownie po zapisie musi dotrzeć do serwera.
Ręcznie napisany plik route jest winien ten sam nagłówek — proxy jest
jedynym miejscem, które nakłada go za Ciebie.
## Dane i to, gdzie mieszka stan { #data-and-where-state-lives }
**Cały dostęp do API idzie przez klienta w `src/lib/`, konsumowanego przez
hooka.** Żadnego `fetch` w komponencie.
Dane serwerowe mieszkają w warstwie zapytań; **store'y trzymają wyłącznie stan
UI i stan ulotny**. Zarejestruj każdy klucz zapytania w `query-keys.ts`, żeby
unieważnianie po zapisie pozostawało spójne.
## Historia czatu i aktualizacje streamu { #chat-history-and-streamed-updates }
Konsola otwiera najnowszą stronę transcriptu i doczytuje wcześniejsze wiadomości na
żądanie. Chip projektu odczytuje `/conversations/{id}/metadata`, z tą samą kontrolą
czytelnika i organizacji, bez ładowania wiadomości. Odczyty repozytorium pomijają
zapisane podsumowanie modelu, chyba że odtworzenie historii modelu jawnie go żąda.
Socket czatu dostarcza serie ramek raz na klatkę animacji, z fallbackiem 50 ms dla
ukrytych kart i limitem kolejki 256 ramek. Sąsiednie delty tekstu lub rozumowania
są łączone; granice sterowania, narzędzi i części zachowują kolejność. Zmiana
rozmowy, konta lub organizacji usuwa oczekujące ramki.
Niezmieniony Markdown i wyniki narzędzi są wykorzystywane ponownie. Przewijanie
śledzi zmierzony przyrost treści. Gotowe wizualizacje montują się blisko viewportu
i odmontowują poza nim, zachowując zmierzone miejsce; ich skrypty uruchamiają się
ponownie po powrocie. Streamowane wizualizacje pozostają aktywne, a pełny ekran
wstrzymuje kopię w transcripcie.
## Uprawnienia to decyzja o renderowaniu { #permissions-are-a-rendering-decision }
`use-permissions.ts` daje efektywny zestaw uprawnień dla aktywnej organizacji.
**Kontrolka, której wołający nie może użyć, nie jest renderowana** — nie
renderowana, a nie renderowana i dopiero potem 403.
Dwie pułapki, obie już tutaj wydane:
- **To, które role oferuje picker, jest arytmetyką, a nie listą.**
`assignableRoles` odzwierciedla regułę serwera na katalogu uprawnień: rola
jest oferowana tylko wtedy, gdy własna rola wołającego ściśle ją przewyższa.
Zahardkodowane „każda rola poza owner” to to, co zaoferowało Adminowi opcję
Admin i zwróciło 403 po wpisaniu adresu e-mail.
- **Strona, która nazywa organizację w swoim URL-u, _jest_ tą organizacją.**
Klient API stempluje `X-Organization-Id` z _aktywnej_ organizacji, więc
strona działająca na organizacji ze swojej ścieżki, a czytająca uprawnienia
dla aktywnej, rozstrzyga o członkach Acme na podstawie Twojej roli w Globeksie.
Przyjęcie organizacji mieszka w `ActiveOrgGuard`, raz.
## Każdy widoczny dla użytkownika napis idzie przez `next-intl` { #every-user-facing-string-goes-through-next-intl }
`make lint` egzekwuje to w obie strony: czytelny napis siedzący w komponencie
oblewa i tak samo oblewa klucz, który trzyma katalog, a którego żaden komponent
nie czyta.
Trzy reguły, na których ludzie się przewracają:
- **Liczebnik to ICU `plural`, nigdy operator warunkowy.**
`{n} file{n === 1 ? "" : "s"}` to zdanie, które tak buduje tylko angielski.
- **Rzeczownik, z którym zdanie się zgadza, nie jest parametrem.** `{matched} of
{total} {noun}` renderuje po polsku `3 of 40 skills`. Rzeczownik wchodzi do
środka `plural` albo `select`.
- **Katalog trzyma copy i tylko copy.** Fałszywy alarm bierze `i18n-exempt` z
uzasadnieniem; nigdy nie bierze klucza. Odpowiedzenie na jeden z nich przez
przeniesienie listy klas Tailwinda do `en.json` to sposób, w jaki
przetłumaczenie jednego napisu pozbawiło kiedyś komponent jego stylowania.
Angielski jest językiem źródłowym i jest podkładany pod każdą lokalizację, więc
brakujące tłumaczenie renderuje angielski, a nie klucz.
!!! info "Własne rzeczowniki produktu zostają po angielsku w każdej lokalizacji"
agent, spec, capability, skill, embed, budget, run, prompt, provider, token,
vault, workspace, sandbox, MCP. Nazywają rzeczy, które klient spotyka też w
dokumentacji, w API i w wyeksportowanym YAML-u — tłumaczenie ich w UI i
nigdzie indziej robi dwa słowniki dla jednego produktu. Odmieniaj je, nie
zastępuj.
## Cztery rejestry i żadnego drugiego źródła { #four-registries-and-no-second-source }
Każdy z nich to jedna tabela, którą czyta kilka części UI. Dopisanie do tabeli
jest całą zmianą; dodanie drugiego źródła jest błędem.
| | Trzyma | Dodajesz przez |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `lib/tool-catalog.ts` | Ikonę, podpis w trakcie działania, nazwę po zakończeniu i renderer dla każdego narzędzia, które rejestruje backend | Wiersz kluczowany na id narzędzia z capability. Test backendu porównuje jedno z drugim w obie strony |
| `lib/brand-glyphs.generated.ts` | Każdy znak usługi, konektora i providera, jako surowe dane ścieżek | Wiersz w `scripts/gen-brand-icons.ts`, a potem `bun run gen:brand-icons`. Nigdy import z paczki z ikonami |
| `lib/dashboard/registry.ts` | Widgety dashboardu i uprawnienie, którym każdy z nich jest bramkowany | Pięć edycji, wymienionych na stronie poniżej |
| `lib/dialog-sizes.ts` | Jeden token szerokości i jeden token kształtu na dialog | Wybór tokenu, nigdy wysokość szytą na miarę |
## Dwie rzeczy, które nowa powierzchnia jest winna { #two-things-a-new-surface-owes }
Oba są rejestrami o tym samym trybie awarii: strona dodana gdziekolwiek indziej
jest po prostu nieobecna, nic nie oblewa, a funkcja wychodzi niewidoczna.
**Przystanek w przewodniku.** `lib/onboarding/tour.ts` to bierny spacer, który
odtwarza „?” danej strony, a `flows.ts` to prowadzone tworzenie, które spacer
oferuje na końcu. Strona bez przystanku nie renderuje **żadnego „?”** — więc
nowa strona, której nagłówek nie ma przycisku pomocy, nie została
zarejestrowana. Zabramkuj krok uprawnieniem, które niesie jego kontrolka, oznacz
go jako `optional`, gdy kontrolka wymaga istnienia danych, i zakotwicz go na
czymś ograniczonym.
**Widget dashboardu**, jeśli funkcja wytwarza stan, który ktoś chciałby widzieć
na pierwszy rzut oka. Pięć edycji: id i definicja w `dashboard/registry.ts`,
komponent w `components/dashboard/widgets/`, umiejscowienie w `layouts.ts`, id
odzwierciedlone w `backend/app/schemas/dashboard_layout.py` — test oblewa, gdy
te dwa się rozjadą — oraz copy w `en.json` i w każdym tłumaczeniu obok niego.
## Weryfikacja { #verify }
Z katalogu `frontend/`. W korzeniu repozytorium vitest nie znajduje konfiguracji,
zgłasza około 164 widmowych porażek i zostawia niepotrzebny katalog cache.
```bash
bunx vitest run src/components/chat/usage-meter.test.tsx # while writing
```
Raz, przed pushem — z korzenia repozytorium:
```bash
make lint-frontend && make test-frontend-cov && make build-frontend
```
!!! danger "`test:coverage`, a nie `test:run`"
Zadanie, które uruchamia CI, mierzy pokrycie i oblewa poniżej 100% linii,
instrukcji i funkcji albo 97,5% gałęzi. Zestaw, w którym każdy test
przechodzi, nadal może być czerwony — i już bywał.
Martwą gałąź łatwiej usunąć niż pokryć: `?? ""` za sprawdzeniem, które już
dowiodło wartości, jest właśnie tym, co bramka słusznie zauważa.
**Spec, który przekracza czas, to zwykle maszyna.** `testTimeout` wynosi 15 s, a
`asyncUtilTimeout` 5 s, oba zmierzone, a nie zgadnięte. Żaden z nich nie jest
powodem, by trzymać spec montujący więcej, niż czytają jego asercje.
## Podsumowanie { #recap }
- Przeglądarka rozmawia z **`/api/*` w tej aplikacji**, nigdy z FastAPI — jeden
przekaźnik, i to on stempluje `no-store`.
- Dane serwerowe mieszkają w **warstwie zapytań**; store'y trzymają wyłącznie
stan UI.
- Kontrolka, której wołający nie może użyć, **nie jest renderowana**, a pickery
ról są **wyliczane, a nie wypisane**.
- Copy idzie przez **`next-intl`**, liczebniki są liczbami mnogimi ICU, a katalog
trzyma copy i tylko copy.
- Nowa powierzchnia jest winna **przystanek w przewodniku**, a jeśli ma stan
widoczny na pierwszy rzut oka — także widget; oba rejestry milczą.
[Backend →](architecture.md) · [Dodawanie funkcji →](adding_features.md) ·
[Testowanie →](testing.md)
## Testy w konsoli { #evals-in-the-console }
Testy korzystają z konsolowych `PageHeader`, `ListCard`, rozmiarów dialogów, kart i `WizardSteps`. Zmiany danych odświeżają zarejestrowane rodziny zapytań przez `/ws/events`; ręczna ocena odświeża też zestawy sprawdzeń, sesje poprawiania i ochronę środowisk. Błąd odczytu udostępnia ponowienie zamiast pustego widoku sukcesu. Wąskie układy zachowują pola formularzy o rozmiarze 16 px i miejsce na przycisk Architekta.
Datasety zachowują własne udostępnianie i granty oraz mogą być używane przez wiele agentów. Model i instrukcje oceniania należą do datasetu; generowanie i ulepszanie mają zakładkę Ustawienia. Pomocnicy zdefiniowani w kodzie zachowują wewnętrzne identyfikatory runów dla atrybucji, ale nie występują w katalogu agentów użytkownika. Przypadki i ewaluacje korzystają ze wspólnych tabel konsoli, a szczegóły przypadków i review z paneli bocznych.
Architekt nie otrzymuje w tym milestone nowej capability do zapisywania datasetów. Tworzenie pytań jest jawnie wycenianą akcją z kontrolą uprawnień w procesie poprawiania; asystent w rogu nie może sam uruchamiać tej płatnej pracy. Jego przycisk pozostaje dostępny obok widoków testów.
`e2e/improvement.mobile.spec.ts` przechodzi przez rzeczywiste API i worker. Sprawdza przypadki datasetu, edytor przypadku, ocenianie, przegląd, wynik testu, kreator poprawiania oraz potwierdzoną publikację z ochroną środowiska. Zastąpiony jest wyłącznie zewnętrzny model. GitHub Actions zachowuje zrzuty w obu motywach przy szerokości telefonu i komputera do kontroli wizualnej.