Commit Graph

34 Commits

Author SHA1 Message Date
gitea 62d6f9d4d5 astrololo: generowanie tekstu jako moduł odłączalny (2/5)
build / build (push) Failing after 2s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 3s
Testy / Testy astrodemo (push) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 2s
Testy / Kontrola składni wszystkich warstw (push) Failing after 3s
Drugi z pięciu kroków budowy trzech produktów. Czysty refaktor — zachowanie
aplikacji się nie zmienia, liczba testów rośnie tylko o nowe.

DLACZEGO WARUNEK W SZABLONIE NIE WYSTARCZA. Dotąd generowanie było chowane przez
`{% if can(request, 'ai') %}`. To sprawia, że funkcji nie WIDAĆ, ale nie że jej
NIE MA: plik szablonu dalej leży w obrazie i dalej zawiera jej nazwy, więc `grep`
po kontenerze pokazuje wszystko, czego warunek nie pokazał na ekranie. Astroklient
ma nie mieć śladu, nie mieć wyłączoną funkcję — więc granica musi być KATALOGIEM.

CO SIĘ PRZENIOSŁO do app/dodatki/: dwa szablony, pięć plików statycznych, trasa
strumienia, cztery metody klienta warstwy logicznej, wpis w katalogu funkcji,
wpisy tras i zasobów, akcje formularza i reguły CSS nazywające funkcję
(`textarea.prompt`).

JAK APLIKACJA O TO PYTA. Neutralny most `app/rozszerzenia.py` odpowiada wyłącznie
na pytanie „czy coś jest podpięte i co wnosi". Sam nie wymienia ani jednej nazwy —
pilnuje tego osobny test, bo most jest w KAŻDYM obrazie. Dlatego też katalog
nazywa się `dodatki`, a nie `ai`: nazwa w instrukcji importu byłaby dokładnie tym
śladem, którego wydzielanie ma się pozbyć.

TRZY MIEJSCA, KTÓRE OKAZAŁY SIĘ TRUDNE:

1. Pola formularza. FastAPI czyta je z SYGNATURY, a wspólny handler nie może
   wymieniać `prompt_budget` ani `llm_provider`. Rozwiązane zależnością: moduł
   deklaruje własne pola u siebie, handler wie tylko, że dostaje słownik.
2. Klient warstwy logicznej jest wspólny, więc metody `prompt`/`horoscope`/
   `llm_models` musiały z niego wyjść. Wspólny klient daje teraz samą drogę
   w dół (`wywolaj`, `pobierz`, `strumien`) — z szyfrowaniem łącza i tokenem
   międzywarstwowym; co nią pojedzie, jest sprawą modułu.
3. Podtytuł ekranu Skompiluj miał wariant „z AI" i wariant bez. Zamiast warunku
   jedno zdanie prawdziwe niezależnie od tego, jakie moduły są w obrazie.

DOCKERFILE. `COPY . .` wnosiło do obrazu także testy — a plik testowy nazywa
funkcje wprost. Teraz wchodzi wyłącznie `app/`.

TEST GRANICY. `test_ai_tylko_w_module.py` przechodzi po wszystkim poza modułem
i szuka siedemnastu słów. Znalazł dwanaście resztek, których nie widziałem:
komentarze w main.py i security.py, przykład `models.js` w komentarzu features.py,
komentarze w compile.js i w trzech arkuszach, oraz regułę `textarea.prompt`,
która przy rozbijaniu CSS trafiła do arkuszy ekranów zamiast do modułu.

Ma też kontrolę pozytywną (te słowa MAJĄ padać w module — inaczej test
przechodziłby, gdyby funkcję wydrążono) i test odłączalności mostu.

SPRAWDZONE NA KOPII BEZ MODUŁU: aplikacja startuje, wszystkie ekrany oddają 200,
katalog ma 13 funkcji zamiast 14, trasy strumienia nie ma, spreparowane
`action=prompt` wraca do akcji domyślnej bez śladu, a `grep` po drzewie nie
znajduje ani jednego ze słów.

Testy: presentation 362, logic 342, data 37, render 41.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 14:25:56 +00:00
gitea 320a0ab24e PRE-27: pełne ukrycie niedostępnych funkcji (paranoja)
build-render / build (push) Failing after 8s
build-swisseph / build (push) Successful in 9s
build / build (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 4s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Failing after 4s
Ukrywanie jest teraz nadrzędne wobec wygody i czytelności komunikatów.
Persona: konto z uprawnieniami files + files_input, zatrudnione wyłącznie do
wgrywania plików. Nie ma się dowiedzieć, po co je wgrywa ani co program będzie
robił — bo to rozgada.

Audyt sześciu kanałów wycieku (statyki, HTML, sondowanie HTTP, ekran plików,
odpowiedzi JSON i błędy, pozostałe warstwy) potwierdził 26 wycieków, każdy
odtworzony uruchomionym kodem i zweryfikowany adwersarialnie. Ani jeden nie był
przyciskiem.

ZASÓB JEST CZĘŚCIĄ FUNKCJI
/static/ omijało CAŁĄ bramkę (PUBLIC_PREFIXES), więc każdy skrypt i arkusz
pobierał ktokolwiek, także niezalogowany, pod zgadywalnym adresem — a ich treść
wymienia ekrany, dostawców modeli i przeznaczenie plików. Ruch ten nie trafiał
przy tym ani do dziennika, ani pod limit żądań, więc wyciek był niewidoczny.
Zasoby idą teraz trasą z bramką; każdy ma w features.STATIC uprawnienie swojego
ekranu. Publiczny został jeden base.css, bo potrzebuje go ekran logowania.

KOMENTARZ NIE JEDZIE NA DRUT
Komentarze w CSS/JS opisywały funkcje pełnymi zdaniami po polsku — łącznie
z „Wstrzymane widzi tylko administrator", czyli i mechanizmem kwarantanny,
i istnieniem konta o wyższych uprawnieniach. _asset_body() usuwa je przy
serwowaniu; w repozytorium zostają.

styles.css rozbity na base.css + arkusz na ekran + x-ai.css. Jeden plik z
wszystkimi selektorami był spisem treści programu. Podział zrobiony
mechanicznie, z osobnym sprawdzeniem, że żaden ekran nie stracił reguły.

base.html ładował skrypty kosmogramu na KAŻDEJ stronie — konto mające wyłącznie
Pliki pobierało je przy wejściu na swój jedyny ekran, razem ze wzmianką
o „przyszłej zakładce". Teraz dokłada je ekran, który ich używa.

RÓŻNICA JEST INFORMACJĄ
Komunikat po wgraniu pliku różnił się zależnie od wyniku walidacji — czyli był
wyrocznią do odgadywania reguł, które ma znać tylko administrator — i mówił
wprost, że plik „musi zatwierdzić administrator". Teraz jest jeden, ten sam.

_logic_error wypisywał na ekran nazwę trasy, nazwę podsystemu, nazwę gałęzi
rozwojowej i wewnętrzny host:port. Jedno zdanie dla wszystkich awarii, szczegóły
do dziennika. Odsiew w jednym punkcie, nie w siedemnastu wywołaniach.

Ponadto: stopka nie ogłasza architektury, /health nie nazywa warstwy, konto bez
ekranów dostaje 404 zamiast tłumaczenia, ekran plików mówi o plikach zamiast
o „bazach interpretacyjnych", klasy .house-warning i .account-card przemianowane
na neutralne, a logic/data/render/engine-swisseph nie wystawiają już /docs ani
/openapi.json i nie publikują portów na hoście.

ZAPORA SŁOWNIKOWA
test_slownik_zakazany.py nie sprawdza miejsc, tylko przechodzi wszystko, co dane
konto może pobrać, i szuka słów, które nie mają prawa paść (87 pozycji dla tej
persony). Nazwy funkcji, adresy ekranów i nazwy zasobów biorą się wprost
z katalogu, więc nowa funkcja obejmuje się sama. Kontrola pozytywna pilnuje, żeby
test nie przechodził dlatego, że program jest pusty.

Sprawdzone: zapora puszczona na treść sprzed poprawek daje 16 trafień na samym
styles.css i łapie każdy ze zneutralizowanych komunikatów. 358 testów zielonych,
ekrany obejrzane w przeglądarce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 17:54:03 +02:00
gitea f0d07ee8c3 feat(bezpieczeństwo): sesje logowania zamiast HTTP Basic (LOG-34)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m17s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m30s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 4s
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m18s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Basic nie miał wylogowania i nie dało się tego obejść: przeglądarka zapamiętuje
hasło i dosyła je SAMA przy każdym żądaniu, więc serwer nie ma czego zapomnieć.
Poprzednia próba (LOG-32) opierała się na nakłonieniu przeglądarki, żeby porzuciła
zapamiętane dane — zachowaniu powszechnym, ale nigdzie nie zapisanym. Teraz to
serwer decyduje, czy dana przeglądarka jest w środku, i może to cofnąć.

TRZY POZIOMY UNIEWAŻNIENIA, celowo rozdzielone, bo każdy kosztuje co innego:
  1. wylogowanie = skasowanie ciasteczka. Natychmiastowe, bez magazynu.
  2. zmiana hasła albo skasowanie konta = odcisk poświadczenia wpisany
     w ciasteczko przestaje pasować. Dzieje się SAMO, bez pamiętania o tym.
     Bez tego odebranie komuś dostępu nie odbierałoby dostępu aż do wygaśnięcia.
  3. „zamknij sesje" z ekranu kont = licznik pokolenia. Jedyny wymagający zapisu,
     więc jedyny opcjonalny: gdy licznika nie ma, poziomy 1 i 2 nadal działają.

KONTO ADMINISTRACYJNE ODSEPAROWANE. Sprawdzane pierwsze i BEZ DOTYKANIA pliku
kont, co daje dwie rzeczy naraz: konto z pliku o tym samym loginie nie przesłoni
administratora, a administrator zaloguje się także wtedy, gdy plik jest uszkodzony
— czyli w jedynej sytuacji, w której ktoś MUSI wejść, żeby to naprawić. Trzymanie
jego stanu w tym samym pliku dawałoby zakleszczenie: nie da się naprawić, bo nie
da się wejść. Jego odpowiednikiem „wyloguj zewsząd" jest zmiana APP_PASSWORD.

KLUCZ WYMAGANY, FAIL-CLOSED. Usługa z kontami, ale bez klucza podpisu, nie
odróżniłaby ważnej sesji od podrobionej, więc nie wstaje — i lepiej przy starcie
niż przy pierwszym logowaniu człowieka. Losowanie klucza byłoby wygodne, ale
wylogowywałoby wszystkich przy każdym restarcie poda: wygląda jak awaria i uczy
ludzi ignorować ekran logowania.

Ciasteczko HttpOnly (jeden wstrzyknięty skrypt inaczej wynosi sesję) i
SameSite=Strict (obca strona nie zadziała w imieniu zalogowanego). Wylogowanie
POST-em, nie odsyłaczem: pod adresem GET wystarczyłby obrazek na obcej stronie.
Adres powrotu po zalogowaniu przechodzi przez sito — bez tego `?dokad=https://obcy`
zamieniłby nasz ekran logowania w narzędzie do wyłudzania haseł.

Kres bezczynności 8 h i twardy 30 dni. Znacznik aktywności odświeżany z progiem,
inaczej Set-Cookie leciałby przy każdym obrazku i arkuszu stylów.

Zdarzenia logowania w dzienniku (PRE-17). Nieudane próby są tam ważniejsze od
udanych: pojedyncza nic nie znaczy, ale seria pod jednym adresem to jedyny
widoczny ślad zgadywania haseł. Test pilnuje, że hasło tam nie trafia.

Sprawdzone w przeglądarce: po zalogowaniu ciasteczko jest NIEWIDOCZNE dla
JavaScriptu, a po wylogowaniu wejście na chronioną stronę ląduje na ekranie
logowania — czyli dokładnie to, czego Basic nie potrafił.

Testy: 21 na rdzeń podpisywania (w tym podrabianie ładunku, podpisu i klucza,
ciasteczko z przyszłości, śmieci na wejściu), reszta przepisana z Basic na sesje.
Prezentacja 338 zielonych.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:18:15 +02:00
gitea 71bb3b9c0a feat(bezpieczeństwo): przycisk wylogowania (LOG-32)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m17s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 4s
build / build (push) Successful in 1m18s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m21s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 5s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Aplikacja nie miała jak z siebie wyjść: raz podane hasło działało do zamknięcia
przeglądarki, a na wspólnym komputerze nie było sposobu, żeby oddać ekran komuś
innemu.

HTTP BASIC NIE MA PRAWDZIWEGO WYLOGOWANIA i to jest sedno tej zmiany. Nie ma
sesji do skasowania: przeglądarka zapamiętuje login i hasło, po czym dosyła je
SAMA przy każdym żądaniu. Serwer nie ma czego zapomnieć — kolejne kliknięcie
przyszłoby z kompletem poświadczeń i weszłoby z powrotem.

Działa natomiast doprowadzenie do tego, żeby to PRZEGLĄDARKA porzuciła zapamiętane
dane, i robimy to dwutorowo, bo ani jedno, ani drugie osobno nie wystarcza:

  1. /wyloguj odpowiada ZAWSZE 401 z nagłówkiem WWW-Authenticate, co wymusza
     ponowne pytanie o hasło. Działa bez JavaScriptu, ale samo w sobie zostawia
     stare dane w pamięci przeglądarki: po anulowaniu okienka wystarczyłoby wejść
     na dowolny adres, żeby wrócić do środka.
  2. wyloguj.js wysyła żądanie z CELOWO błędnymi danymi, którym przeglądarka
     nadpisuje swój wpis. To jest część, która faktycznie czyści pamięć — ale
     opiera się na zachowaniu powszechnym, a nie zapisanym w standardzie, więc
     nie może być jedynym mechanizmem.

Strona wylogowania mówi wprost, że pewnym sposobem w KAŻDEJ przeglądarce jest
zamknięcie okna. Nie obiecujemy więcej, niż Basic potrafi — obietnica bez pokrycia
byłaby tu gorsza od braku przycisku, bo dawałaby złudzenie, że ekran jest oddany.

Trasa stoi POZA bramką logowania, celowo: inaczej dostałaby 200 od zalogowanej
sesji i nie miałaby jak odpowiedzieć 401. Odpowiada też bez logowania — inaczej
wyjście wymagałoby bycia w środku, co jest błędnym kołem. Nagłówki zakazują
zapamiętania strony, bo oddana z pamięci podręcznej nie dotarłaby do serwera
i okienko w ogóle by się nie pojawiło.

Obok wyjścia pokazujemy, KTO jest zalogowany: bez tego przycisk jest w połowie
bezużyteczny, bo na wspólnym komputerze nie wiadomo, kogo się wylogowuje. Przy
wyłączonym logowaniu nie ma ani jednego, ani drugiego — przycisk sugerowałby
ochronę, której nie ma.

Sprawdzone w przeglądarce: oba żądania wychodzą (to z błędnymi danymi i samo
przejście), oba wracają 401, strona renderuje się poprawnie. Samego unieważnienia
pamięci poświadczeń NIE dało się tu potwierdzić — wymaga okienka systemowego,
którego automat nie obsłuży.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:52:04 +02:00
gitea 6466ab89a9 feat(dane): interfejs zarządzania plikami baz — trzy poziomy dostępu (DAN-27)
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m33s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 12m25s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m33s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 16s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
Ekran „Pliki" z trzema poziomami, wpiętymi w kontrolę dostępu z PRE-27:

  „files"        widzi listę i KLIKANIEM decyduje, z których baz program korzysta,
  „files_input"  dokłada wgrywanie i ARCHIWIZACJĘ,
  administrator  kasowanie, przywracanie z archiwum i REGUŁY WALIDACJI.

STAN JEST TERAZ TRWAŁY. DAN-15 trzymał go w zmiennej DISABLED_BASES, bo warstwa
danych nie miała gdzie zapisywać — udział był montowany read-only. Skoro stan ma
być klikany, musi przetrwać restart, więc udział jest zapisywalny, a stan leży
w pliku obok baz (zapis atomowy: plik opisuje CAŁY zbiór, więc obcięcie w połowie
skasowałoby wiedzę o wszystkich naraz). DISABLED_BASES zostaje jako awaryjne
wyłączenie z konfiguracji i odsiewa DODATKOWO — nie odwrotnie, bo inaczej ktoś
z dostępem do ekranu włączyłby bazę wyłączoną świadomie na poziomie wdrożenia.

ARCHIWIZACJA NIE KASUJE. Plik zostaje na dysku, zamrożony, ze znacznikiem czasu;
znika wyłącznie z użytku. To najdalej idąca operacja osoby wgrywającej dane —
kasować może tylko administrator. Test sprawdza, że plik po archiwizacji nadal
istnieje, bo to jest cała istota tej operacji.

WALIDACJA JEST BRAMKĄ DO UŻYTKU, NIE FILTREM NA WEJŚCIU. Plik wgrany zostaje
NIEZALEŻNIE od wyniku — nie tracimy niczego, co ktoś wgrał. Zmienia się tylko to,
czy da się go włączyć. Sprawdzenie biegnie też w chwili włączania, nie tylko przy
wgrywaniu: reguły mogą się zmienić po fakcie.

O WALIDACJI WIE TYLKO ADMINISTRATOR. Pliki wstrzymane są odsiewane W WARSTWIE
DANYCH przy for_admin=False, a nie ukrywane w szablonie — gdyby dochodziły do
przeglądarki, wystarczyłby podgląd źródła, żeby poznać reguły. Odmowa włączenia
wraca do konta bez uprawnień BEZ POWODU, bo powód zdradza regułę. Sekcja reguł
nie trafia nawet do źródła strony. Test parametryzowany po obu niższych poziomach
szuka w odpowiedzi śladów mechanizmu i wymaga, żeby żadnego nie było.

Każdy plik ma policzony sha256 — tożsamość niezależna od nazwy. Wykorzystuje ją
już odrzucanie duplikatów, a w kroku drugim posłuży do pilnowania zgodności
lustra w SQL.

Przy okazji naprawiony błąd, który dopiero co bym wprowadził: Path("") to
Path("."), czyli wartość PRAWDZIWA, więc `Path(os.getenv(...)) or domyślna`
zawsze wybierało pustą zmienną i zapisywało stan do katalogu bieżącego.

Wymaga zapisywalnego udziału — osobny PR w repo deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:39:42 +02:00
gitea a833965909 feat(bezpieczeństwo): konta z uprawnieniami do zakładek i funkcji (PRE-27)
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m58s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 3m37s
Testy / Kontrola składni wszystkich warstw (push) Successful in 9s
build / build (push) Successful in 18s
Ekran „Konta" dla administratora: zakładanie, kasowanie i nadawanie uprawnień.
Zestaw funkcji zależy od konta, a konto ograniczone widzi program KOMPLETNY —
tylko mniejszy.

PODZIAŁ NA GRUPY. Ekrany to zakładki (7), bo zakładka jest naturalną jednostką —
to ją widać w nawigacji. Rozszerzenia to POZIOMY ZŁOŻONOŚCI wewnątrz ekranów:
porównanie systemów domów, wykresy dodatkowe, obliczenia zaawansowane, generowanie
tekstu przez model (kosztuje pieniądze) i eksport plików. Konto bez porównania
domów dostaje horoskop w Whole Sign i nie wie, że systemów jest trzynaście.

NIC NIE ZDRADZA, ŻE JEST WIĘCEJ:
- brak pozycji w menu zamiast pozycji wyszarzonej,
- 404 zamiast 403 — odmowa z powodem sama mówi, że coś tam jest,
- rysunki bez uprawnienia w OGÓLE NIE POWSTAJĄ, więc nie ma ich nawet w źródle,
- automatyczna dokumentacja API wyłączona. /docs, /redoc i /openapi.json wypisują
  komplet tras, czyli spis wszystkich funkcji programu — ochrona zakładek nic by
  nie dała, gdyby obok leżał ich katalog. Znalezione TESTEM przechodzącym po
  trasach aplikacji, nie przeglądem kodu.

KONTO ADMINISTRACYJNE zostaje w APP_USER/APP_PASSWORD, jak było. Nie leży w pliku
kont, więc nie da się go skasować ani ograniczyć z ekranu. Konto założone w pliku
o tym samym loginie NIE przesłoni administracyjnego — kolejność sprawdzania jest
odwrotna, inaczej dałoby się odebrać uprawnienia jedynemu, kto może je nadawać.
Uprawnienia administracyjnego nie da się też nadać z formularza: odsiewamy je
w normalise(), a nie w handlerze, więc żadne spreparowane żądanie tam nie sięgnie.

GRANICA JEST W HANDLERZE, NIE W SZABLONIE. Ukrycie pola chroni przed przypadkiem,
nie przed kimś, kto zna nazwy pól — _limit_options() ścina opcje po stronie
serwera i test wysyła spreparowane żądanie, żeby to potwierdzić.

MAPA TRASA→UPRAWNIENIE JEST JEDNA (features.ROUTES). Rozproszenie jej po
dekoratorach kończy się trasą, o której ochronie ktoś zapomniał — a taka dziura
jest niewidoczna, dopóki ktoś jej nie znajdzie. Trasa bez wpisu wymaga
administratora: przeoczenie ma ZAMYKAĆ, nie otwierać. Test idzie po trasach
APLIKACJI, nie po wpisach mapy — inaczej potwierdzałby tylko sam siebie.

Konta w pliku JSON na własnym podkatalogu NFS (nie tam, gdzie bazy — zamontowanie
całego udziału obeszłoby bokiem DAN-25). Hasła wyłącznie jako hash scrypt, tym
samym mechanizmem co APP_USERS. Zapis atomowy, bo przerwanie zapisu na NFS
obcięłoby plik, czyli skasowało wszystkie konta naraz.

Przy okazji przepisane trzy testy, które greppowały nawigację i main.py: menu
powstaje teraz z katalogu funkcji, więc szukanie sztywnych linków w base.html
niczego już nie sprawdzało.

Wymaga wolumenu na konta — osobny PR w repo deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:18:14 +00:00
gitea baf4e0e38a fix(kosmogram): własne dymki zamiast natywnych — działają też po powiększeniu
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m51s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 19s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 25s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 17s
Testy / Kontrola składni wszystkich warstw (push) Successful in 9s
Rysunek niesie <title> przy każdym obiekcie i każdej linii aspektu i to
wystarczało, dopóki koło było statyczne. Po dodaniu powiększania na pełne okno
dymki przestały się pokazywać.

CO SPRAWDZIŁEM W PRZEGLĄDARCE (1280×900, komplet danych): elementy z <title>
SĄ osiągalne kursorem — 19 z 20 trafień w hit-teście — więc nic ich nie zasłania
i problem nie leży w geometrii ani w pointer-events. Której dokładnie reguły
przeglądarka używa do stłumienia natywnego dymka, nie ustaliłem.

Nie ma to jednak znaczenia, bo natywny dymek jest tu i tak kiepskim narzędziem:
pojawia się po sekundzie zwłoki, nie da się go stylować, nie działa na dotyku
i nie ma go czym wywołać z klawiatury. Własny dymek usuwa zależność od zachowania
przeglądarki i przy okazji jest czytelniejszy.

Tekst bierzemy z <title> JUŻ OBECNEGO w rysunku, a nie z drugiej kopii opisów
w JS — inaczej rozjechałyby się przy pierwszej zmianie treści. Na czas najechania
<title> jest odpinany i wieszany z powrotem po zejściu kursora: dzięki temu nigdy
nie widać dwóch dymków naraz, a czytniki ekranu zachowują nazwę dostępną.

Warstwa 1150 CELOWO pomiędzy: nad nakładką powiększonego koła (1100), bo tam
właśnie zgłoszono problem, i pod oknem postępu (1200), które ma zostać na wierzchu
podczas pisania horoskopu. Test pilnuje tej kolejności liczbowo.

Ścieżka PDF nietknięta — składa się po stronie serwera, <title> zostają w rysunku.

Zweryfikowane w obu stanach koła: dymek pokazuje „Sun · 12°30'00'' · dom 1" nad
glifem i „Sun trygon Moon · orb 3.64°" nad linią aspektu, po powiększeniu też
(kursor-lupa mu nie przeszkadza), a po zejściu kursora wszystkie 20 <title>
wraca na miejsce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:31:07 +02:00
gitea 8b6ecc727d feat(domy): przypisanie do domu przez wyrocznię + poprawki układu strony
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m44s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 20s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 23s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 17s
Testy / Kontrola składni wszystkich warstw (push) Successful in 9s
ETAP 5, część pierwsza: assign_house pod wyrocznią (swe_house_pos).
Osobny rodzaj błędu niż same cuspy — i od razu jeden znalazł.

BŁĄD: assign_house szło ZAWSZE do przodu. Przy dużych szerokościach systemy
dzielące koła wielkie mają kolejność domów ODWRÓCONĄ (przy φ=−84,3° cusp domu I
wypada na 174,2°, a domu II na 165,3°) — co nie jest usterką, bo wyrocznia zwraca
dokładnie te same wartości. Suma przeskoków „do przodu" wychodziła 3960° zamiast
360°, czyli każdy krok obchodził koło dookoła. Planety lądowały w złych domach
dla regiomontanusa, campanusa i topocentrica: 9,7-12,5% przypadków. Błąd cichy —
wykres wyglądał bez zarzutu. Kierunek bierzemy teraz z samych cuspów.

TOPOCENTRIC MA JEDNAK GRANICĘ DZIEDZINY — korekta tego, co pisałem wcześniej.
Cuspy są poprawne wszędzie (zgodne z wyrocznią co do zera), ale powyżej koła
podbiegunowego przestają DZIELIĆ OKRĄG: cusp VII (= I + 180°) wypada przed
cuspem VI i domy nachodzą na siebie. Przypisanie planety traci wtedy sens —
co potwierdza sama wyrocznia, której swe_house_pos przeczy tam własnym cuspom
(100% zgodności do 62°, 83,9% przy 66°, ok. 50% przy 72°; regiomontanus 100%
w tych samych punktach). Odmawiamy, z jawnym fallbackiem jak Placidus i Koch.

Próg jest WYPROWADZONY z warunku „dwanaście cuspów sumuje się do 360°", nie
dobrany pod wynik testu — i wypada na kole podbiegunowym (zmierzone: 100%
podziałów do 65°, 78% w pasie 66-67°). To inny rodzaj granicy niż u Placidusa
i Kocha: tam nie istnieją same cuspy, tu istnieją, tylko nie tworzą podziału.

Framework dostał pojęcie dziedziny WĘŻSZEJ niż wyroczni (NARROWER_THAN_ORACLE),
zamiast wyjątku „bo topocentric": skoro wyrocznia przeczy sama sobie, nie może
rozstrzygać, więc tam nie porównujemy — a nasze przypisanie jest w tym obszarze
sprawdzane testem samospójności z cuspami, bez swissepha.

UKŁAD STRONY — zmierzony na żywej stronie, nie na oko:
- tabela porównania przy 13 systemach miała 14 kolumn i 1863 px, a stała
  w rodzicu bez overflow-x, więc ROZPYCHAŁA CAŁY DOKUMENT: 1713 px przy oknie
  1280 px, poziomy pasek na body. Teraz ma własny kontener przewijany
  (dokument 1265 px, nie przewija się), a numer domu jest przyklejony do lewej,
  bo inaczej po przewinięciu nie wiadomo, który to wiersz.
- przypis „* nie działa za kołem podbiegunowym" siedział WEWNĄTRZ <label>
  selektora, łamał się na dwie linie i rozciągał wiersz siatki ze 66 do 108 px,
  rozjeżdżając go z sąsiednim polem. Wyjaśnienie stoi teraz raz, przy
  checkboxach z gwiazdkami; wiersz wrócił do 66 px.
- 13 checkboxów na flexie zawijało się w poszarpane wiersze — jest siatka
  o stałej szerokości kolumny (auto-fill, więc na wąskim ekranie kolumn mniej).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 13:55:13 +02:00
gitea 7266f671a4 feat(domy): Placidus i Koch + jawny fallback poza kołem podbiegunowym
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 17s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m28s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 18s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
Etap 2: dwa systemy łuku dobowego — jedyne, które mają miejsca, gdzie po prostu
NIE ISTNIEJĄ. Koch zgadza się z wyrocznią co do zera; Placidus do 1,7e-6°, przy
czym to próg zbieżności WYROCZNI, nie nasz: nasze cuspy spełniają definicję
Placidusa z dokładnością 1e-12° (osobny test, działa bez swissepha).

Placidus jako jedyny nie ma wzoru zamkniętego — cusp jest zdefiniowany warunkiem
na samego siebie („punkt, który przebył 1/3 swojego półłuku"), więc iterujemy po
punkcie stałym do 1e-11°. Brak zbieżności traktujemy jako wyjście poza dziedzinę,
nie jako wynik.

Koch okazał się natomiast ZAMKNIĘTY: jego kryterium to czas od wschodu stopnia
stojącego na MC, a półłuk tego stopnia znamy wprost z jego deklinacji. Definicja
za Astrodienst (astro.com/astrowiki/en/Koch_House_System) — nie zgadywana.

Powyżej koła podbiegunowego oba odmawiają liczenia i wyrocznia odmawia dokładnie
tych samych przypadków (5245/20 000 losowych, zero rozjazdów dziedziny). Odmowa
jest wyjątkiem, nie liczbą: cicha podmiana systemu jest niewykrywalna z wykresu.

Ustępstwo wobec rzeczywistości siedzi osobno, w cusps_detailed(): podstawia
Porphyry'ego i ZAWSZE zostawia ślad. Ten ślad idzie wszystkimi trzema wyjściami —
na ekran (ramka, nie „muted"), w prompt do modelu (inaczej napisze „Twój Placidus"
o Porphyrym) i do PDF-a, w ramce PRZED rysunkami. Astrolog z Tromsø dostaje
wynik i wie, że go dostał inaczej.

Przy okazji: nazwy systemów były zaszyte w czterech szablonach naraz. Przy trzech
systemach uchodziło to na sucho, przy dziesięciu nie — jest katalog w jednym
miejscu, a testy szablonów RENDERUJĄ je zamiast szukać tekstu w źródle, więc
łapią też literówki w Jinja.

Większe jądro efemeryd (de441): świadomie zdegradowane do „nice to have" —
rozszerza wyłącznie zakres dat, nie poprawia niczego w obecnym. Odnotowane
w domain.py przy JD_MIN/JD_MAX, żeby nie wróciło po cichu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:04:33 +02:00
gitea b36b3bee19 feat: konfigurowalne aspekty i orby (PRE-06)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m47s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m27s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 12s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 9s
build / build (push) Successful in 26s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 12s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Dotąd orb (8°) i aspekty były zaszyte w silniku. Astrolodzy pracują na różnych
orbach i lubią dokładać aspekty poboczne — teraz da się to ustawić w UI.

Silnik (aspects.py): `find_aspects` przyjmuje orb, luminary_bonus i `minor`.
Aspekty poboczne = tylko trzy (półsekstyl 30° / półkwadratura 45° / kwinkunks
150°) — bo mają już glify i barwy w prezentacji i w engine/glyphs.py, więc
dokładają się bez ruszania czegokolwiek poza silnikiem. Bazy zwykle ich nie
opisują (brak tokenu), więc trafiają na kosmogram i do tabeli, ale NIE tworzą
faset — most sygnifikatorów po cichu je pomija (skip przy braku tokenu).

Plumbing przez wszystkie warstwy: request logiki, build_chart, klient prezentacji,
oba handlery (/, /compile) ORAZ PDF (/compile/pdf). Ustawienia wędrują między
zakładkami (formsync) i lecą do PDF-a (compile.js) — żeby podsumowanie liczyło
aspekty tym samym orbem co horoskop (ta sama zasada co fix podsumowania #46).
UI: orb, bonus dla świateł, checkbox „aspekty poboczne" na obu formularzach.

Weryfikacja na żywym /chart/positions: domyślnie 24 aspekty (główne); minor → 52
(dochodzą quincunx/semisextile/semisquare); orb 3 → 13, orb 12 → 32. Testy:
logika +3 (minor/orb/bonus), prezentacja +6 (obecność pól, przekazanie przez
warstwy, sync, PDF). Logika 273, prezentacja 220.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 13:16:32 +02:00
gitea 52b7c20c2a fix(prezentacja): podsumowanie bierze WSZYSTKIE policzone opcje (stacje, tabele, domy)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 15s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 10s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m31s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 15s
Testy / Kontrola składni wszystkich warstw (push) Successful in 10s
build / build (push) Successful in 2m49s
Regresja: „Skompiluj" przeliczało horoskop od nowa z OKROJONYM zestawem opcji
(tylko system domów + zodiak), więc jeśli przy horoskopie policzyłeś stacje,
tabele (żywioły, faza Księżyca…) albo porównanie domów — w podsumowaniu ich NIE
było. Zamiast pokazać to, co policzono, liczyło uboższą wersję.

Zostaje czysty przelicz (bez trzymania dużego wyniku w przeglądarce), ale z TYMI
SAMYMI opcjami co przy horoskopie:
- `formsync`: synchronizuje między zakładkami też opcje — checkboxy (stacje,
  tabele) i wielo-checkbox (porównanie domów). Wcześniej umiał tylko `.value`.
- „Skompiluj" (formularz): dostaje te same opcje; `compile_build` i `compile_pdf`
  przekazują je do logiki (stations/tables/house_systems). `compile.js` wysyła je
  w payloadzie PDF-a.
- Wspólny plik `_result_tables.html`: podsumowanie renderuje DOKŁADNIE te same
  tabele co „Horoskop" (porównanie domów, stacje, aspekty, paralele, antyscja,
  żywioły/faza/godziny, Lots…). Każda sekcja pokazuje się tylko, gdy jej dane są
  w wyniku — opcja niepoliczona → sekcji nie ma (zgodnie z prośbą).
  (TODO: przełączyć też chart.html na ten include, by widoki nie mogły się
  rozjechać — na razie zgodność pilnowana ręcznie, jest komentarz w pliku.)

Weryfikacja: render wspólnego pliku na bogatym wyniku pokazuje wszystkie sekcje.
Testy: +8 (opcje niesione w /compile i /compile/pdf, sync checkboxów/multi,
wspólny include). Prezentacja 202.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 14:01:42 +02:00
gitea 4bdfb673cc feat(prezentacja): strefa czasowa z lokalizacji — DST-świadomy offset (PRE-03)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m2s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m56s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 34s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 23s
build / build (push) Successful in 1m40s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 32s
Testy / Kontrola składni wszystkich warstw (push) Successful in 19s
„Logika dwóch lokalizacji": dotąd offset GMT był ręcznym polem (PRE-19) — trzeba
było go znać i samemu pamiętać o czasie letnim. Teraz liczymy go z lokalizacji.

Sedno wymagania: strefę ustalamy RAZ i trzymamy jako stałą liczbę, żeby drobna
zmiana współrzędnych nie przerzuciła DST i nie „przeskoczyła" Ascendenta na
sąsiedni znak. „Większa miejscowość z bazy" okazuje się zbędna — strefa IANA jest
i tak regionalna, więc wioska daje tę samą strefę co pobliskie miasto.

Jak:
- `timezone.py`: współrzędne → strefa IANA (tzfpy, OFFLINE — bez sieci), a z niej
  offset DLA DATY URODZENIA. `zoneinfo`/`tzdata` znają reguły historyczne i DST:
  Kraków 1984 to +1h zimą, +2h latem; Katmandu +5:45. Degraduje się do None (brak
  biblioteki / punkt bez strefy / zła data) — wtedy zostaje ręczny offset.
- Endpoint `GET /timezone?lat&lon&date&time` → {tz, offset, dst, label}. 404, gdy
  nie da się ustalić.
- `geo.js`: po wyborze miejsca (mapa / wyszukiwarka / „Tu i teraz") oraz przy
  zmianie DATY (bo DST zależy od pory roku) pobiera offset i wypełnia pole
  tz_offset, pokazując wykrytą strefę („Wykryto: Europe/Warsaw · +2:00 (czas
  letni)"). Pole zostaje edytowalne. Na wejściu podpowiada tylko gdy offset
  wygląda na nieustawiony — nie nadpisuje wartości ręcznie wpisanej i wysłanej.

Zależności (lekkie, offline): tzfpy (wheel Rust) + tzdata (dla zoneinfo w slim-obrazie).

Weryfikacja: żywy serwer — Kraków 1984-06 → +2:00 (czas letni), 1984-01 → +1:00,
Katmandu → +5:45. Testy: +12 strefa (moduł + endpoint), +5 JS. Prezentacja 187.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 22:42:31 +02:00
gitea adce568729 feat(prezentacja): wykres deklinacji i oś antyscji (LOG-07, kosmogram etap 6)
Ostatni etap rysowania kosmogramu: wizualizacje pochodne z aspektów
pozazodiakalnych (LOG-07). Do tej pory paralele i antyscja były tylko w tabelach —
teraz widać je na rzut oka.

WYKRES DEKLINACJI (`render_declination`):
- Pionowa skala deklinacji z równikiem (0°) i zwrotnikami (±ε, kreskowane) —
  ε bierzemy z `obliquity` w wyniku, więc granica jest dokładna dla daty.
- Obiekty na osi X ułożone wg POSORTOWANEJ deklinacji, więc paralele (ta sama
  wysokość) lądują obok siebie. Łączniki: paralela zielona (jak koniunkcja),
  kontrparalela czerwona (jak opozycja) — te same barwy co linie na kole, grubość
  wg orbu. Dymek z nazwą zjawiska i orbem.
- Strefa poza zwrotnikami cieniowana; obiekt OOB (out-of-bounds) w kolorze
  wyróżnienia + „OOB" w dymku. Od razu widać ciała o skrajnej deklinacji.

OŚ ANTYSCJI (`render_antiscia`):
- Ekliptyka rozwinięta w poziomą oś ze znakami; pionowo zaznaczona oś przesileń
  (0° Raka/Koziorożca) — lustro antyscji — i oś równonocy (0° Barana/Wagi) dla
  kontrantyscji. Pary połączone łukiem (zielony antyscja / czerwony kontrantyscja),
  z dymkiem. Bez par oś i obiekty i tak coś mówią.

Oba w obu motywach (screen + print), więc gotowe też do PDF-a. Pokazują się na
/chart pod odpowiednimi tabelami LOG-07. Testy: +13 (etap 6). Prezentacja: 170.

Domyka etapy kosmogramu (PRE-12): 1 szkielet, 2 obiekty, 3 aspekty, 4 dopracowanie,
5 aspektarian, 6 deklinacja/antyscja.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:27:40 +00:00
gitea 4b17f2dd67 feat(prezentacja): aspektarian — siatka aspektów (PRE-18, kosmogram etap 5)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m37s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m32s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 25s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 19s
build / build (push) Successful in 48s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m34s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m2s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 1m7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 41s
Trójkątna siatka aspektów obiekt×obiekt obok koła. Koło pokazuje GEOMETRIĘ
aspektów (linie w środku), aspektarian domyka temat od drugiej strony — TABELĘ
na jeden rzut oka: kto z kim i jak.

Klasyczny „schodkowy" układ: glify obiektów biegną po przekątnej, a każda komórka
pod nią to aspekt między obiektem ze swojego wiersza a obiektem ze swojej kolumny.
Pusta komórka też niesie informację — że pary NIC nie łączy.

Spójność z kołem trzymana świadomie:
- te same barwy aspektów (niebieski harmonijny / czerwony napięty / zielony
  koniunkcja) — oko łapie ten sam kod na kole i w siatce;
- ciasny aspekt (orb <1°) pogrubiony, tak jak grubsza linia na kole;
- natywne dymki <title> (bez JS): „Słońce trygon Mars · orb 0.20° · aplikacyjny",
  polskie nazwy tylko w dymku — obliczenia trzymają angielskie;
- oba motywy: „screen" (zmienne CSS aplikacji) i „print" (konkretne kolory + font
  glifów wprost), więc siatka jest gotowa też do PDF-a.

Glify aspektów (☌ ☍ △ □ ⚹ ⚺ ⚻ ∠) trzymamy lokalnie w prezentacji — tam gdzie już
są kolory i polskie nazwy — więc aspektarian jest samowystarczalny i nie zależy od
tego, czy pojedynczy rekord aspektu niesie glif. Renderer degraduje się do pustego
przy mniej niż dwóch obiektach; niekompletny obiekt (bez glifu) go nie wywala.

Aspektarian pokazuje się na /chart i /compile pod kołem. Testy: +12 (etap 5).
Całość prezentacji: 157.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 01:13:46 +02:00
gitea f24616d342 feat(render): raport PDF przez LaTeX jako osobna usluga (PRE-24)
build / build (push) Successful in 1m27s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m25s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m1s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 27s
Ostatnia z osmiu wskazowek partnerow. Nowa usluga services/render sklada raport:
generuje plik posredni .tex i kompiluje go XeLaTeX-em do PDF.

OSOBNY komponent, nie czesc prezentacji — ta sama zasada co przy izolacji
swissepha (LOG-27). TeX Live wazy setki megabajtow; w obrazie produktu
spowalnialby kazdy build, a tak aktualizuje sie niezaleznie i jego awaria nie
kladzie aplikacji, tylko przycisk „Pobierz PDF".

SZYFROWANIE, o ktore prosiles: lacze prezentacja↔render idzie tak samo jak
pozostale, ale z WLASNYM, TRZECIM kluczem (LINK_KEY_PRESENTATION_RENDER). Osobny,
bo tym laczem plynie CALY raport — dane urodzeniowe i opisy z baz — wiec przejecie
go nie moze otwierac lacza do logiki ani do danych. Fail-closed: bez klucza pod
nie wstaje. Test kopii link_crypto obejmuje teraz cztery uslugi.

Uklad PDF wg prosby: imie i nazwisko -> wprowadzone dane -> RYSUNEK kosmogramu
-> interpretacja natalna -> predykcje okresowe. Test pilnuje kolejnosci.

Dwie rzeczy, ktore wyszly dopiero przy skladaniu tego kawalka:

1. KOSMOGRAM NIE NADAWAL SIE DO PDF. Uzywa zmiennych CSS (var(--line)) i klasy
   .glyph, ktorej font podaje styles.css — a samodzielny konwerter SVG→PDF nie zna
   naszego arkusza. Wyszlyby czarne kreski BEZ SYMBOLI. Stad wariant „print":
   konkretne kolory na bialym tle i font glifow wpisany wprost w rysunek. Przy
   okazji zalatwia to etap 4 planu kola (wariant do druku).

2. UCIECZKA ZNAKOW LATEXA byla zepsuta — i zlapal to moj wlasny test. Zamiana
   „\” na \textbackslash{} szla w tej samej petli co nawiasy, wiec kolejne
   podmiany ucieklyby nawiasy dopiero co wstawione: wychodzilo
   \textbackslash\{\}. Poprawka: ukosnik chowany pod znacznik zastepczy i
   rozwijany na koncu. To nie kosmetyka — niezauwazony „%” komentuje RESZTE LINII,
   wiec zdanie od modelu urywaloby sie w polowie, a PDF powstawalby normalnie,
   tylko krotszy. Test na wrogim tekscie sprawdza tez, ze \end{document} ani
   \input{} nie wyrwa sie z dokumentu.

Usluga nie zapisuje nic poza katalogiem tymczasowym, ktory sprzata po sobie;
w manifescie readOnlyRootFilesystem + emptyDir na /tmp. /health raportuje
obecnosc xelatex i rsvg-convert, zeby zepsuty obraz bylo widac od razu.

Zweryfikowane na zywo (TestClient uslugi render): zadanie bez szyfrowania
z POPRAWNYM tokenem -> 400; obcy klucz -> 400 i tajny opis nie wraca; wlasciwy
klucz -> zadanie dochodzi do aplikacji, tresci baz NIE MA na kablu, odpowiedz
zaszyfrowana. Manifesty przechodza kubectl apply --dry-run=server na zywym
klastrze.

CZEGO NIE SPRAWDZILEM: samej kompilacji PDF. W tym srodowisku nie ma ani TeX
Live, ani dzialajacego runtime'u kontenerow (docker CLI jest, daemon nie) —
probowalem zbudowac obraz i sie nie dalo. Sprawdzone jest wszystko dookola:
generowanie .tex, ucieczka znakow, szyfrowanie, kontrakt API, samowystarczalnosc
SVG. Pierwsze uruchomienie na klastrze trzeba obejrzec — dlatego PRE-24 zostaje
jako „W trakcie", nie „Zrobione".

Instrukcja wdrozenia: docs/wdrozenie-render-pdf.md (klucz, obraz, merge,
weryfikacja, znane ograniczenia).

Testy: 15 nowych (render) + 14 (lacze i wariant druku w prezentacji).
Calosc: prezentacja 131, logika 265/1 skip, render 15.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 19:21:49 +02:00
gitea 6c9029c496 feat(prezentacja): zakladka „Skompiluj" — zbiorczy raport (PRE-23)
build / build (push) Successful in 52s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m16s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 32s
Testy / Kontrola składni wszystkich warstw (push) Failing after 8s
Czwarta wskazowka partnerow. Nowa zakladka sklada w jedno trzy kawalki liczone
w roznych miejscach: policzony horoskop, interpretacje natalna z AI i wszystkie
zapamietane predykcje okresowe.

Uklad sekcji dokladnie wg prosby: imie i nazwisko -> wprowadzone dane ->
RYSUNEK kosmogramu -> interpretacja natalna -> predykcje okresowe. Test pilnuje
tej kolejnosci, bo to jedyna rzecz, ktora latwo przestawic przy refaktorze,
a partnerzy podali ja wprost.

Skad material:
- HOROSKOP liczymy TU NA NOWO z danych formularza, zamiast go zapamietywac.
  To czysta funkcja wejscia — tanio powtorzyc, a odpada trzymanie w przegladarce
  duzego wyniku, ktory moglby sie rozjechac z aktualnym formularzem.
- INTERPRETACJA NATALNA — nowy natal.js, odpowiednik predictions.js. Natalna jest
  JEDNA (dotyczy momentu urodzenia, nie okresu), wiec ponowne wygenerowanie
  podmienia slot zamiast dokladac wpis.
- PREDYKCJE — z magazynu z PRE-22.

compile.js czyta magazyny przez ICH API (window.astrololoNatal /
astrololoPredictions), a nie siegajac wprost do localStorage — format danych ma
jednego wlasciciela: modul, ktory je zapisuje. Test tego pilnuje.

Zakladka MOWI, CZEGO BRAKUJE: panel gotowosci z trzema pozycjami i podpowiedzia,
na ktorej zakladce uzupelnic. Bez tego uzytkownik zlozylby niekompletny raport
i dowiedzialby sie o tym dopiero po otwarciu PDF-a.

Tresc od modelu jest ESKEJPOWANA przed wstawieniem do DOM — to tekst z zewnatrz,
wiec bez tego mielibysmy wektor wstrzykniecia.

Weryfikacja na zywej aplikacji: dwie predykcje zapisane w Kalendarzu, natalna
w Interpretacjach, obie odczytane na Skompiluj (panel: brak horoskopu na zolto,
dwa pozostale na zielono). Po zlozeniu wszystkie trzy zielone, a raport zaczyna
sie od „Jan Kowalski" i danych wejsciowych. Kolejnosc sekcji sprawdzona na
wyrenderowanym HTML: naglowek 2931 < dane 3033 < kosmogram 3158 < natalna 26544
< predykcje 26575.

Testy: 16 nowych. Poprawione tez trzy wlasne testy, ktore lapaly nazwy plikow
w KOMENTARZACH zamiast w tagach skryptow. Calosc: prezentacja 117 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:09:56 +02:00
gitea 8cc329ab17 feat(prezentacja): zapamiętane predykcje okresowe (PRE-22) + wymaganie o cache-bustingu
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 12m9s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 20s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m42s
Piata wskazowka partnerow: Kalendarz ma pozwalac liczyc predykcje dla KILKU
okresow i je zachowywac, zeby wszystkie trafily potem do raportu („Skompiluj",
PRE-23/24).

Nowy predictions.js — magazyn w localStorage, tak jak wspolne dane formularza
(PRE-21): serwer zostaje bezstanowy, zadne dane urodzeniowe ani tresci z baz nie
laduja po stronie uslugi. Zakladka „Skompiluj" odczyta to samo miejsce przez
window.astrololoPredictions (jedno zrodlo prawdy).

Decyzje projektowe:

- KLUCZEM TOZSAMOSCI JEST OKRES. Ponowne policzenie tego samego zakresu podmienia
  wpis zamiast dokladac duplikat — inaczej lista puchlaby przy kazdej probie
  z innym modelem albo budzetem. Dzieki temu zapis jest idempotentny, wiec dziala
  tez wariant bez strumienia (zapis przy wczytaniu strony z gotowym wynikiem)
  i odswiezenie niczego nie mnozy.
- progress.js OGLASZA gotowy horoskop zdarzeniem `astrololo:horoscope` z profilem,
  zamiast sam zapisywac. To okno postepu, a nie magazyn — zapisywanie zostaje
  odpowiedzialnoscia predictions.js. Filtr po profilu pilnuje, zeby interpretacja
  natalna nie trafila na liste predykcji okresowych.
- Przepelniony magazyn (horoskopy bywaja dlugie) jest ZGLASZANY uzytkownikowi,
  a nie polykany — inaczej wynik znikalby po cichu.

UI: lista zapamietanych predykcji na Kalendarzu — okres, data zapisu, objetosc
i przycisk usuwania. Skrypt podpiety w timeline.html (nie w base.html), zeby nie
kolidowac z rownolegle otwartym #29.

PRE-26 — dopisane wymaganie o wersjonowaniu plikow statycznych. Przy PRE-25
przegladarka podala STARY styles.css i powiekszanie kosmogramu „nie dzialalo",
mimo ze klasa byla nakladana. Objaw jest zdradliwy: szablony sa nowe, wiec strona
wyglada na zaktualizowana, a funkcja po prostu milczy. Po wdrozeniu moze to
spotkac uzytkownikow.

Weryfikacja na zywej aplikacji: dwa okresy zapisane; powtorzenie tego samego
zakresu podmienilo wpis (dalej 2, tekst zaktualizowany); lista posortowana po
dacie; predykcje przetrwaly przejscie na inna zakladke i powrot; interpretacja
natalna NIE wpadla na liste; usuwanie zmniejszylo licznik 2 -> 1 i przerysowalo
liste. Testy: 14 nowych. Calosc: prezentacja 101 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:57:24 +02:00
gitea 76fbdefeaa feat(prezentacja): kliknięcie powiększa kosmogram na pełne okno (PRE-25)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m52s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 46s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 19s
build / build (push) Successful in 1m10s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m59s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m50s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 42s
Testy / Kontrola składni wszystkich warstw (push) Successful in 19s
Ósma wskazówka partnerów, jako małe QoL przed PRE-22. Koło rysujemy w kolumnie
tekstu, więc na mniejszym ekranie glify i stopnie robią się nieczytelne.
Kliknięcie rozciąga wykres na całe okno, ponowne wraca do strony. SVG skaluje
się bez utraty jakości, więc nie potrzeba drugiej wersji rysunku ani biblioteki.

Wyjście z powiększenia na trzy sposoby: klik w wykres, klik w tło, Esc.
Dostępne też z klawiatury (Enter/Spacja, focus-visible), bo inaczej obejrzenie
szczegółów wymagałoby myszy.

Dwie decyzje warte odnotowania:

1. WARSTWA. z-index 1100 CELOWO pomiędzy: ponad kontrolkami Leafleta (1000),
   ale PONIŻEJ okna postępu (1200). Gdy trwa pisanie horoskopu, log operacji ma
   zostać na wierzchu. Test pilnuje tej nierówności, bo to jedyna liczba tutaj,
   którą łatwo zmienić bez zastanowienia i zepsuć coś niewidocznego na oko.

2. ROZMIAR. Koło jest kwadratowe, więc ograniczamy je KRÓTSZYM bokiem okna
   (min(96vw, 92vh)) — inaczej na szerokim ekranie wystawałoby w pionie.

Skrypt podpięty globalnie w base.html i osłonięty sprawdzeniem, czy koło w ogóle
jest na stronie — zadziała też na przyszłej zakładce „Skompiluj" bez zmian.
Przy powiększeniu blokujemy przewijanie strony pod spodem.

Weryfikacja na żywej aplikacji, pomiarami w DOM: po kliknięciu .wheel-fig ma
position:fixed, display:flex, z-index:1100; SVG rośnie z 460×460 do 662×653
przy oknie 1280×720, visibility visible; body dostaje overflow:hidden. Ponowne
kliknięcie wraca do 460×460 i zdejmuje klasę. Zrzutu ekranu stanu powiększonego
NIE mam — panel przeglądarki zaciął się w trakcie (puste klatki, viewport
raportowany jako 0×0), więc opieram się na pomiarach i testach.

Testy: 10 nowych. Całość: prezentacja 87 passed, logika 265 / 1 skip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:22:48 +02:00
gitea f34016a4a5 feat(prezentacja): wspolne dane formularza miedzy zakladkami + imie i nazwisko (PRE-21)
build / build (push) Successful in 58s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Trzecia wskazowka partnerow. Kazda zakladke (Horoskop / Interpretacje /
Kalendarz) wypelnialo sie od nowa — te same imie, data, godzina, strefa
i miejsce. Teraz raz wpisane dane wedruja za uzytkownikiem, a zmiana w jednej
zakladce przenosi sie na pozostale.

Nowy formsync.js trzyma stan w localStorage. Dlaczego nie sesja na serwerze:
serwer zostaje BEZSTANOWY — zadnego magazynu sesji, zadnych danych urodzeniowych
trzymanych po stronie uslugi (spojne z postawa z LOG-32/PRE-16). Przy okazji
dane synchronizuja sie tez miedzy osobnymi kartami przegladarki, bo zdarzenie
`storage` daje to za darmo.

Dolozone pole „imie i nazwisko" (wszystkie trzy zakladki) — potrzebne do
naglowka raportu PDF (PRE-24). Handlery przyjmuja je i oddaja, wiec nie znika
po przeliczeniu.

Dwie rzeczy, ktore trzeba bylo domknac, zeby to dzialalo naprawde:

1. KOLEJNOSC SKRYPTOW. formsync.js ladowany w <head> z `defer` — skrypty defer
   wykonuja sie w kolejnosci dokumentu, wiec ten zdazy odtworzyc wspolrzedne,
   ZANIM geo.js zbuduje mape. Mapa startuje od razu we wlasciwym miejscu,
   zamiast przeskakiwac po chwili.

2. geo.js ustawia pola z KODU (`.value = ...`), co samo z siebie NIE wywoluje
   zdarzen — bez tego synchronizacja przegapilaby kazdy wybor z mapy, z
   wyszukiwarki i z „Tu i teraz". Dolozony setVal(), ktory jawnie zglasza
   `change`.

Zakladka bez danego pola (np. Sygnifikatory) nie kasuje wartosci zapamietanej
gdzie indziej; uszkodzony wpis w localStorage nie blokuje formularza.

Q-14 rozstrzygniete: lancuch LaTeX->PDF stanie jako OSOBNA USLUGA render —
spojne z izolacja swisseph (LOG-27), obraz produktu zostaje maly.

Weryfikacja na zywym stacku (data+logika+prezentacja) w przegladarce: dane
wpisane w Horoskopie pojawily sie w Interpretacjach; zmiana godziny w
Interpretacjach dotarla do Kalendarza; pola nieobecne na zakladce (zodiak,
system domow) przetrwaly; po POST imie zostalo, a horoskop policzyl sie
normalnie. Testy: 22 nowe strukturalne. Calosc: prezentacja 67 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:12:49 +00:00
gitea 5c82e8bd9f fix(prezentacja): offset wzgledem GMT + nazwa lokalizacji po „Tu i teraz”
Dwie pierwsze wskazowki od partnerow biznesowych; pozostale piec zapisane jako
wymagania (PRE-21..PRE-24) do zrobienia w kolejnych krokach.

PRE-19 — offset wzgledem GMT. Etykieta mowila „Strefa (offset h)”, czyli nie
bylo jasne, wzgledem czego liczymy przesuniecie. Teraz „Offset wzgledem GMT (h)”
z podpowiedzia. Krok juz byl 15-minutowy (0,25 h) — dolozony zakres −12…+14,
zeby nie dalo sie wpisac strefy, ktora nie istnieje. Zmiana w trzech zakladkach,
ktore maja to pole (Horoskop, Interpretacje, Kalendarz).

PRE-20 — po „Tu i teraz” wspolrzedne i pineska skakaly na biezace polozenie, ale
w polu tekstowym zostawala STARA, wczesniej wpisana nazwa. Formularz pokazywal
jedno miejsce, a liczyl dla innego — cicha pomylka, nic sie nie wywalalo.
Handler zdarzenia astrololo:coords odswieza teraz nazwe przez reverseName.

Przy okazji druga strona tego samego bledu: reverseName czysci pole ZANIM
wysle zapytanie. Gdyby /reverse nie odpowiedzialo (brak sieci), zostalaby stara
nazwa — lepiej puste pole i poprawne wspolrzedne niz nazwa, ktora klamie.

Wymagania: PRE-19/20 (zrobione), PRE-21 wspolne dane miedzy zakladkami wraz
z polem imie i nazwisko, PRE-22 wiele predykcji okresowych w pamieci sesji,
PRE-23 zakladka „Skompiluj”, PRE-24 raport PDF przez LaTeX. Dolozone pytanie
otwarte Q-14 o lancuch LaTeX→PDF (gdzie postawic TeX Live, silnik unicode owy
pod glify, konwersja SVG) — decyzja wplywa na deploy i rozmiar obrazow.

Testy: 12 nowych, strukturalnych na zrodle (JS-a nie uruchomimy, a obie regresje
sa ciche). Sprawdzone sabotazem — po cofnieciu kazdej poprawki czerwienieja.
Calosc: prezentacja 45 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:12:49 +00:00
gitea 1d7f3e2136 feat(prezentacja): szkielet kosmogramu — koło horoskopowe SVG (PRE-12)
build / build (push) Successful in 1m26s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m10s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Pierwszy krok rysowania wykresu (PRE-12): SZKIELET koła — pierścień znaków,
podział na domy i osie. Planety i linie aspektów w kolejnych iteracjach.

Rysowane po stronie serwera jako czysty string SVG (bez zależności zewnętrznych
— spójne z CSP). Ciemny motyw, spójny z paletą aplikacji (kolory z --line,
--accent, --muted; glify znaków barwione wg żywiołu w stonowanych kolorach
czytelnych na ciemnym tle).

Geometria wg konwencji astrologicznej: Ascendent po LEWEJ, długość ekliptyczna
rośnie przeciwnie do ruchu wskazówek zegara. Punkt λ → kąt φ = 180° − (λ − Asc);
w SVG y rośnie w dół, co formuła uwzględnia. Zakotwiczenie sprawdzone testami
liczbowo: Asc po lewej, Dsc po prawej, oś pozioma; Asc+90° (II dom) na dole.

Elementy: dwa okręgi + piasta, podziałki co 5°/10°, granice znaków co 30° z
glifem w środku sektora, szprychy domów od pasa do piasty, numery domów w środku
każdego domu, osie Asc–Dsc i MC–IC wyróżnione akcentem z etykietami AC/DC/MC/IC.

Dane bierzemy WYŁĄCZNIE z wyniku /chart/positions — prezentacja nic nie liczy:
- `sign_glyphs` (pierścień 12 znaków) i glify — z LOG-22,
- `angles` z długością `decimal` — już były,
- `cusps` z `decimal` — DOŁOŻONE w tym PR (jedna linia w build_chart). Bez tego
  domy dało się narysować tylko dla whole sign; z długością cuspu działa dla
  KAŻDEGO systemu. Zweryfikowane na porphyry: cuspy poza wielokrotnościami 30°,
  szprychy odchodzą od granic znaków.

Degradacja: silnik bez osi/domów albo starszy wynik bez `decimal` w cuspach →
brak koła (pusty string), nie wyjątek.

Testy: 8 (poprawność XML, 12 glifów, 4 osie, numery domów, Asc po lewej liczbowo,
CCW, degradacja). Całość: prezentacja 33 passed, logika 265 / 1 skipped. Render
potwierdzony wizualnie na horoskopie referencyjnym (AC=Leo po lewej, MC=Aries
u góry, domy CCW).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 12:32:47 +02:00
gitea ace76183e5 feat(logic): glify astrologiczne — konwersja tekst↔symbol (LOG-22)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m55s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 37s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 1m22s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m39s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
Warunek wstępny pod kosmogram (PRE-12): planety i znaki na kole rysujemy
symbolami. Nowy moduł engine/glyphs.py:

- forward: nazwa → glif (planety, punkty, Loty, znaki, aspekty, retro ℞),
- reverse: glif → nazwa (znosi obecność/brak selektora wariantu),
- glyphify(): tokeny [XX z bazy → glify; przyklad z wymagan
  „[Sa [Pis 26°08' [conj [PF" → „♄  26°08' ☌ ⊗" (stopnie nietkniete).

DAN-18 „tylko tekst, nigdy emoji" potraktowane serio, TRZEMA warstwami — bo
sam selektor NIE wystarcza (potwierdzone wizualnie w przegladarce):
1. w danych: selektor wariantu tekstowego U+FE0E na znakach zodiaku oraz ♀/♂
   (maja wariant emoji); ☉ i reszta go nie dostaja (zbedny),
2. CSS font-variant-emoji:text,
3. CSS font-family celujacy w MONOCHROMATYCZNE fonty symboli PRZED emoji.
   Bez pkt. 3 macOS/Chromium i tak renderowal znaki  jako kolorowe kafelki
   Apple Color Emoji mimo VS15 — planety wychodzily tekstem, znaki nie. Stack
   „Apple Symbols / Segoe UI Symbol / Noto Sans Symbols2" naprawia render.
   NIGDZIE nie emitujemy U+FE0F (emoji) — pilnuje tego test.

Wpiete w build_chart: kazda pozycja dostaje glyph (planeta) + sign_glyph (znak,
zalezny od zodiaku, wiec po przesunieciu), osie i Loty tak samo (Fortuna ⊗,
reszta Lotow bez standardowego symbolu → None), aspekty dostaja glif,
result[„sign_glyphs"] to pierscien 12 znakow pod kolo.

UI: kolumna „Sym." w tabeli pozycji (planeta + znak) i symbol przy aspekcie.

Testy: 14 (kompletnosc — kazdy obiekt/znak/aspekt ma glif; dwukierunkowosc;
DAN-18 brak FE0F, znaki maja VS15, ☉ nie; przyklad glyphify z wymagan).
Calosc: logika 265 passed / 1 skipped, prezentacja 25. Render znakow
potwierdzony wizualnie (czarno-biale symbole, nie emoji).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 20:46:38 +02:00
gitea 929b691238 fix(ui): okno postepu nad mapa, nie pod nia
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m48s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 10m1s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 42s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 32s
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m54s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 39s
Testy / Kontrola składni wszystkich warstw (push) Successful in 23s
Modal postepu przy pisaniu horoskopu renderowal sie POD kontrolkami mapy —
przyciski zoomu i atrybucja OSM przebijaly przez zaciemnione tlo okna.

Przyczyna: Leaflet trzyma kontrolki (.leaflet-top/.leaflet-bottom) na
z-index:1000, a .leaflet-container NIE tworzy wlasnego kontekstu stackowania
(position:relative bez z-index), wiec te 1000 trafia wprost do korzenia. Zaden
przodek mapy (form, .geo, .geo-map, .wrap) tez kontekstu nie tworzy. Okno
postepu mialo z-index:50 — czyli ladowalo pod mapa.

Poprawka: z-index okna 50 -> 1200 (zapas nad 1000). Jedna wartosc.

Zweryfikowane w przegladarce na PRAWDZIWYCH arkuszach (leaflet.css + styles.css):
- runtime elementFromPoint w punkcie kontrolek zoomu: przed = element mapy na
  wierzchu („MAPA PRZYKRYWA MODAL"), po = overlay na wierzchu („MODAL NA WIERZCHU"),
- wizualnie: kontrolki zoomu i © OSM przed poprawka jasne na wierzchu, po —
  przygaszone pod modalem.

Niezalezne od PRE-16 (nie rusza styles.css) — mozna zmergeowac przed nim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:24:25 +02:00
gitea 114b7eebdf feat(ui): okno postepu z logiem podczas pisania horoskopu
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m21s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m50s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 34s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 1m49s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m20s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m0s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 37s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Generowanie trwa minutami, a zwykly POST nie dawal zadnego sygnalu — aplikacja
wygladala na zawieszona. Teraz w trakcie pracy pojawia sie okno z logiem,
zegarem i spinnerem.

Log pokazuje RZECZYWISTE zdarzenia z serwera, nie udawany pasek postepu:
- app/progress.py — strumien NDJSON; praca leci w watku roboczym, generator
  odpompowuje kolejke, wiec zdarzenia docz w TRAKCIE pracy, nie na koncu;
  heartbeat co 10s, zeby proxy nie uznalo polaczenia za martwe,
- providers.generate(..., on_event) — raportuje kazda ture (start, czas trwania,
  liczba znakow, czy urwana), bo to tura trwa,
- POST /chart/horoscope/stream w logice + proxy /horoscope/stream w prezentacji.

Wynik: ostatnie zdarzenie niesie GOTOWY HTML wyrenderowany z tego samego
szablonu, ktory renderuje przeladowanie strony (_prompt_result.html wydzielony
z _prompt_block.html). Jedno zrodlo prawdy dla wygladu wyniku — okno wstawia go
bez przeladowania.

Degradacja: bez strumieniowania w przegladarce formularz idzie klasycznie
i wszystko dziala jak wczesniej, tylko bez okna. Blad polaczenia konczy sie
komunikatem w logu, nie cisza.

BLAD ZNALEZIONY PRZY TESCIE NA ZYWO: petla kontynuacji odejmowala od budzetu
ZAMOWIONY limit tury zamiast tokenow faktycznie wyprodukowanych — pierwsza tura
zjadala caly budzet, wiec urwana odpowiedz nigdy nie doczekala sie dokonczenia
i wracala do uzytkownika jako calosc. Naprawione i pokryte testem regresyjnym.

Testy: 176 passed / 1 skipped (logika) + 17 (prezentacja). Zweryfikowane na zywo
z wolna atrapa modelu: zdarzenia z poprawnymi czasami, okno z 11 liniami logu,
wynik wstawiony bez przeladowania strony.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:58:56 +02:00
gitea 163ace4283 feat(ui): interaktywny wybor modelu u dostawcy
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m14s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 10m19s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 46s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 27s
build / build (push) Successful in 1m18s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m2s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 36s
Testy / Kontrola składni wszystkich warstw (push) Successful in 23s
Uzytkownik wybiera nie tylko dostawce, ale konkretny model — Fable czy Opus
u Anthropica, gpt-4o-mini czy gpt-5 u OpenAI, cokolwiek ma pobrane lokalnie.

- app/llm/catalog.py: podpowiedzi modeli per dostawca wraz z oknem kontekstu,
  nadpisywalne przez <DOSTAWCA>_MODELS; GET /llm/models wystawia je dla UI.
- Pole modelu w UI jest TEKSTOWE z datalista, nie zamknietym <select> — konto
  moze miec dostep do modeli, o ktorych kod nie wie, a nowe wychodza szybciej,
  niz aktualizuje sie katalog. Puste pole = model domyslny dostawcy.
- static/models.js: zmiana dostawcy przelacza podpowiedzi, podmienia placeholder
  na model domyslny i pokazuje okno kontekstu wybranego modelu.
- build_provider(name, model) — model z zadania wygrywa nad konfiguracja.

WAZNE (znalezione przy tescie e2e): liczenie budzetu „maksymalny kontekst modelu"
szlo przez build_provider(), ktory WYMAGA klucza API — bez klucza budzet cicho
spadal do wartosci zapasowej i byl identyczny dla wszystkich modeli Anthropic.
Budzet zalezy wylacznie od okna kontekstu, wiec doszlo resolve_model(), ktore
rozwiazuje nazwe modelu bez budowania dostawcy. Teraz budzet realnie sie rozni:
Opus/Fable 3,48 mln znakow, Haiku 536 tys., gpt-4o-mini 438 tys., llama3.1 8 tys.

Pewnosc danych w katalogu: modele Anthropic pochodza z oficjalnej dokumentacji
API (okna i limity zgodne z limits.py); modele OpenAI to podpowiedzi, ktorych
nie weryfikowalem; lokalne zaleza od tego, co masz pobrane.

Testy: 174 passed / 1 skipped (logika) + 17 (prezentacja). Nowy test strukturalny
pilnuje, ze KAZDE wywolanie w dol niesie wybrany model i dostawce — dokladnie ta
klasa bledu zlapala brakujacy parametr przy horoskopie okresowym.
Zweryfikowane w przegladarce: przelaczanie dostawcy podmienia liste modeli.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:40:19 +02:00
gitea 04b26afa6d feat(logic): generator promptow do LLM + budzetowanie (LOG-29, LOG-30)
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Pierwszy krok ustalonej kolejnosci: prompt z podgladem, uzyteczny od razu
BEZ integracji API (ta przyjdzie w LOG-31).

LOG-29 — app/prompt.py:
- profil natal (ekran Interpretacje) i period (ekran Kalendarz),
- prompt po polsku: zadanie -> dane horoskopu (pozycje, osie, Lots, aspekty,
  sekta, zodiak) -> wskazania z baz wg wagi -> instrukcje -> zastrzezenie,
- twarde reguly: kazda teza musi cytowac konkretny sygnifikator, zakaz
  wychodzenia poza dostarczone dane, jawne wskazanie sprzecznosci,
- deterministyczny: ten sam horoskop + budzet = ten sam prompt.

LOG-30 — redukcja do budzetu (concise/medium/extensive):
dedup -> grupowanie z licznikiem -> sortowanie wg punktacji sily (LOG-21) ->
obciecie ogona (jednostka = CALE wskazanie) -> skracanie dlugich opisow.
Statystyki zwracaja ile weszlo/pominieto i jaki byl prog — takze w tresci
promptu, zeby model wiedzial, ze widzi wybor.

POST /chart/prompt — zwraca sam prompt + statystyki, bez wolania modelu.

Prezentacja: przycisk „Generuj prompt (AI)" na obu ekranach, wybor budzetu,
pole z promptem + kopiowanie (z fallbackiem dla http bez secure context).

WAZNE (znalezione przy tescie e2e): padnieta warstwa danych zabiera tylko
wskazania — horoskop i OS CZASU zostaja, bo sa czysto obliczeniowe. Wczesniej
blad bazy gubil cala osie czasu, czyniac prognoze okresowa bezuzyteczna.
Zabezpieczone testem.

Testy: 19 nowych, calosc 118 passed / 1 skipped. Zweryfikowane e2e w
przegladarce: profil natal (3340 znakow) i period (15 zdarzen, 4728 znakow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:44:17 +00:00
gitea a2aabd37a5 feat(presentation): wyszukiwarka lokalizacji + mapa (OSM/Leaflet)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m39s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 57s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
QoL 2/2: pole „Szukaj miejsca" (nazwa/adres/POI) + interaktywna mapa na
formularzach horoskopu / interpretacji / kalendarza. Bez klucza API.

- geocode.py: proxy OSM/Nominatim PO STRONIE SERWERA (poprawny User-Agent,
  throttling ~1 req/s, cache TTL 1h) — endpointy /geocode i /reverse.
  Wolanie z serwera, nie z przegladarki: latwiej trzymac polityke Nominatim
  i dziala niezaleznie od secure-context (http://<ip>).
- _location_picker.html: wspolny partial (search + wyniki + mapa + atrybucja),
  wpiety includem do 3 formularzy z polami lat/lon.
- geo.js: Leaflet — wyszukiwanie (debounce), klik na wynik ustawia lat/lon i
  centruje mape, klik/drag pineski ustawia wspolrzedne + /reverse pokazuje
  nazwe; sync z „Tu i teraz" (now.js emituje event astrololo:coords).
- Leaflet 1.9.4 vendorowany lokalnie (static/vendor/leaflet, BSD-2-Clause,
  permisywny) — niezaleznosc od CDN; kafelki mapy z OSM. Marker jako divIcon
  (bez plikow PNG).
- styles.css: style pod ciemny motyw.

Zweryfikowane w przegladarce: domyslny widok (pineska na Szpitalu Barlickiego),
wyszukanie „Wawel Krakow" -> lista -> klik ustawia 50.0547/19.9361 i przesuwa
mape, klik w mape ustawia wspolrzedne + reverse wypelnia nazwe. Zero bledow
w konsoli. /geocode i /reverse zwracaja szpital; cache dziala.

Uwaga wdrozeniowa: pod prezentacji potrzebuje egressu do
nominatim.openstreetmap.org; przegladarki — do tile.openstreetmap.org.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 03:32:07 +02:00
gitea 4a86d34f5a Geolokalizacja: jawny komunikat gdy brak secure context (http://)
Przyczyna "nie pyta o zgodę": navigator.geolocation działa tylko w secure
context (https:// lub localhost). Na http://<ip> przeglądarka po cichu
odmawia — bez promptu; kod nie miał callbacku błędu, więc nic nie było widać.

- wspólny static/now.js (deduplikacja skryptu z chart.html i interpret.html)
- jawna detekcja window.isSecureContext + czytelny komunikat w #geoNote
  ("wymaga HTTPS lub localhost — wpisz lat/lon ręcznie")
- callback błędu (odmowa/timeout) też widoczny; status "Pobieram lokalizację…"
  i potwierdzenie po sukcesie

Zweryfikowano: /static/now.js serwowany (200), obie strony referencjonują
skrypt i mają #geoNote.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:59:03 +02:00
gitea f6323cac10 Ranking siły (LOG-21) + grupowanie identycznych opisów + geolokalizacja
Punktacja siły (LOG-21):
- scoring.py: siła fasety z sygnałów obliczalnych (typ fasety, rodzaj aspektu,
  ciasnota orbu). Konfigurowalne wagi. Hook na przyszłość: kolumny countas*/level*
  z SIGNIFICATORS KEY (obecnie puste).
- aspekty niosą orb+allowed; fasety dostają "score"; ranking faset malejąco.

Grupowanie:
- opcja group: zwija próbki po opisie (ten sam efekt = jedna grupa z listą
  sygnifikatorów i licznikiem). Checkbox "grupuj identyczne opisy" w /interpret.

Geolokalizacja (bajer):
- "Tu i teraz" (widok Horoskop i Interpretacje) uzupełnia lat/lon z przeglądarki
  (navigator.geolocation; wymaga zgody, https/localhost).

Zweryfikowano na realnym main_base.xlsx: ranking sensowny (ciasna opozycja z
Saturn 9.59 > szeroka koniunkcja z Moon 6.25 > znak/dom 5.0); grupowanie zwija
powtórzone opisy. 41 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 01:16:47 +02:00
gitea 82809665ff Odsiewanie szumu + rozwijanie skrótów sygnifikatorów
- abbreviations.py: słownik skrót -> pełna nazwa (z SIGNIFICATORS KEY, built-in)
  + expand(): [Su in [Tau -> "Sun in Taurus", [Sa in 6th H. -> "...6th house",
  affl. -> afflicted itd.
- significators: każda próbka ma pole "expanded" (postać czytelna); hartowanie
  filtra szumu (efekty zastępcze x/?/-, wiersze *MARKER, legendy/nagłówki).
- prezentacja /interpret: pokazuje rozwiniętą postać, surowy skrót w tooltipie.

Zweryfikowano na realnym main_base.xlsx: "[Sa or [Ma in the 5th H." ->
"Saturn or Mars in the 5th house". 9 testów przechodzi (w tym test_abbreviations).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:42:47 +02:00
gitea 0e74566a78 Bogatsze sygnifikatory: faseta "w domu" obok "w znaku" (LOG-15/16)
Most horoskop -> sygnifikatory generuje teraz dla każdego obiektu dwie fasety:
- "w znaku": planeta + token znaku ([Su + [Tau)
- "w domu":  planeta + token domu ([Su + 11th H.) — używa domów z LOG-05

- significators.build_report: przyjmuje pozycje z build_chart (z numerami domów),
  generuje fasety znak/dom, filtruje szum. Ordinal helper (1st..12th).
- /chart/report: używa build_chart (pozycje z domami).
- Prezentacja /interpret: render faset (znak/dom) per obiekt.

Aspekty ([conj/[sq/[opp) na później — wymagają policzenia aspektów (LOG-06).

Zweryfikowano na realnym main_base.xlsx (53969 wierszy), 30.04.1984:
Mars w 5. domu 10 dopasowań ("[Sa or [Ma in the 5th H." -> "abortion/miscarriage"),
Neptune w 7. domu 6, Uranus w 6. domu 4. 7 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:37:20 +02:00
gitea eef67d37b5 Wyszukiwarka: wynik obliczeń szukany w bazie interpretacji
Pierwsza wersja mostu horoskop -> sygnifikatory -> baza (zalążek LOG-16/18/19).

- logic/significators.py: z pozycji generuje tokeny w składni bazy (planeta
  [Su, znak [Tau...), pyta warstwę danych o rekordy z tokenem planety i zawęża
  do tych, które wspominają też jej znak ("planeta w swoim znaku"); odsiewa szum.
- logic /chart/report: nowy endpoint (pozycje -> raport dopasowań z interpretacjami).
- logic DataClient.search: parametr fields (lżejszy payload).
- data: naprawa str.contains regex=True -> regex=False (sygnifikatory zawierają
  [ + itd., metaznaki regex); podniesiony górny limit zapytania (le=50000).
- prezentacja: strona /interpret (formularz -> wyszukane interpretacje per obiekt)
  + nawigacja.

Zweryfikowano end-to-end na realnym pliku (Encyclopaedia of Medical Astrology,
53969 wierszy): dla horoskopu 30.04.1984 znaleziono m.in. Sun w Taurus 46,
Mars w Scorpio 57, Saturn w Scorpio 61 dopasowań; przykłady: "[Su in [Tau" ->
"the bump of amativeness prominent". 15 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:57:26 +02:00
gitea 3195d9b003 Warstwa prezentacji: widok horoskopu do ręcznego testowania
Strona główna "/" = formularz podstawowych danych momentu (data, godzina,
strefa, opcjonalnie lokalizacja) → tabela policzonych pozycji w formie
human-readable (znak, pozycja w znaku, absolutna, kierunek, prędkość).
Woła logic /chart/positions; przelicza czas lokalny + offset na UTC.
Przycisk "Tu i teraz" uzupełnia bieżącą datę/godzinę i strefę przeglądarki.
Retrogradacja wyróżniona w tabeli.

Wyszukiwarkę sygnifikatorów przeniesiono pod "/significants" -> /significators,
dodano nawigację (base.html). Czytelny komunikat, gdy logika nie ma jeszcze
endpointu silnika.

Zweryfikowano end-to-end: formularz → przeliczenie UTC → render tabeli
(przez stub kontraktu /chart/positions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:08:43 +02:00
gitea 16d35c16dc Szkielet aplikacji trójwarstwowej (prezentacja / logika / dane)
Trzy niezależne usługi FastAPI komunikujące się przez HTTP/JSON, każda
zna tylko adres warstwy bezpośrednio pod nią:

- presentation (:8000) — strona WWW + formularz
- logic (:8001) — reguły biznesowe, pośrednik
- data (:8002) — wyszukiwanie danych za interfejsem DataProvider

Warstwa danych: czytanie setek plików .xlsx z wykrywaniem nagłówka i
mapowaniem układu kolumn na schemat kanoniczny, z 4-poziomowym cache
(schemat L1, Parquet L2, wyniki zapytań L3, odwrócony indeks L4) i
unieważnianiem po odcisku pliku. Gotowa ścieżka migracji do SQL
(ingest/to_sql.py + SqlDataProvider, przełączane przez DATA_PROVIDER).

Zawiera docker-compose, Makefile, generator danych przykładowych.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:35:09 +02:00