Ctrl K
ComunidadGuías…

docs/frontend.pl.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
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.