16 Commits

Author SHA1 Message Date
gitea fd79513ce2 astrololo: ekrany i eksport jako moduły, katalog z rejestracji (3/5)
Testy / Testy warstwy logicznej (silnik) (pull_request) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Failing after 3s
Testy / Testy astrodemo (pull_request) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 2s
Testy / Kontrola składni wszystkich warstw (pull_request) Failing after 3s
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
build / build (push) Successful in 9s
Trzeci z pięciu kroków. Zmieniłem jego zakres wobec planu i warto wiedzieć
dlaczego: pierwotnie miała to być deduplikacja pięciu kopii link_crypto.py, ale
po zrobieniu PR 2 widać, że astroklienta blokuje co innego — ekrany, których nie
ma mieć, siedzą wewnątrz jednego main.py. Deduplikacja kryptografii jest realnym
długiem, ale niczego nie blokuje.

PODZIAŁ. main.py (1023 linie) rozpadł się na podstawa.py (wspólne obiekty
i pomocnicy), jedenaście modułów w app/ekrany/ i main.py, który jest już samym
ZŁOŻENIEM: lista importów JEST definicją produktu. Podział zrobiony mechanicznie,
z osobnym sprawdzeniem, że żadna sekcja nie wołała pomocnika z innej (nie wołała).

KATALOG Z REJESTRACJI. Dotąd wszystkie funkcje były wypisane w features.py, więc
obraz produktu, który części z nich nie ma, i tak niósł ich nazwy — spis funkcji,
których nie ma jak włączyć. Teraz ekran zgłasza siebie, swoje trasy i swoje
zasoby przy imporcie własnego modułu, a features.py nie wymienia ani jednego
ekranu. Kolejność w nawigacji jest jawna (`kolejnosc`), żeby nie rządziła nią
kolejność importów.

To samo dotyczy nawigacji administratora: odsyłacz do ekranu kont był wpisany na
sztywno w base.html, więc w węższym produkcie zostawał martwy link i nazwa
ekranu, którego nie ma.

EKSPORT JAKO MODUŁ. Zgodnie z ustaleniem eksport jest funkcją administracyjną,
więc musi dać się usunąć. app/moduly/eksport/ zabiera arkusz, trasę PDF-a i akcję
formularza. „Można, ale nie temu kontu" i „nie ma takiej możliwości" to dwie
różne gwarancje, a eksport wynosi najwięcej treści baz naraz.

MOST ODKRYWA MODUŁY. Skoro modułów jest więcej niż jeden, most nie może ich znać
z nazwy — nazwa nieobecnego modułu jechałaby do obrazu, w którym go nie ma.
Przechodzi więc po podkatalogach app/moduly/ i pyta każdy, co wnosi. Katalog
generowania przeniesiony z app/dodatki na app/moduly/dodatki.

ZNALEZIONE PRZY OKAZJI. Po wydzieleniu eksportu okazało się, że jego ścieżki
SZCZĘŚLIWEJ nie sprawdzał żaden test — badano wyłącznie odmowę dla konta bez
uprawnienia. Moduł dostaje zależności z wywołania montującego, więc brak jednej
z nich wyszedłby dopiero przy pierwszym kliknięciu. Dopisany test funkcjonalny
(realny arkusz, sprawdzany aż do nagłówka ZIP-a) i brakujące zależności.

Cztery komentarze w plikach współdzielonych wymieniały zakładkę „Skompiluj",
w tym wheelzoom.js ze wzmianką o „przyszłej zakładce" — to samo zgłoszenie, które
audyt podnosił wcześniej.

TEST ZŁOŻENIA. Buduje węższy produkt NAPRAWDĘ: kopiuje drzewo, usuwa cztery
ekrany i oba moduły, uruchamia aplikację w OSOBNYM PROCESIE (importy są
zapamiętywane, więc sprawdzanie tego w procesie, który moduł już zaimportował,
dawałoby wynik fałszywie pozytywny) i sprawdza, że wstaje, że zachowane ekrany
oddają 200, że usunięte oddają 404 (nie 403 i nie 500), że katalog opisuje ten
obraz, i że w nawigacji nie ma martwych odsyłaczy.

Napisałem najpierw ostrzejszy test — „nazwa ekranu nie pada poza jego modułem" —
i go wyrzuciłem: zgłaszał wzmianki o Horoskopie w plikach współdzielonych, choć
astroklient Horoskop MA. Ślad ma znaczenie wyłącznie wobec konkretnego złożenia,
więc sprawdzenie należy do produktu, nie do mechanizmu.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:32:49 +02:00
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 1d0ff2f72e PRE-27: generowanie tekstu przez model znika bez uprawnienia
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m20s
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 5s
build / build (push) Successful in 1m39s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 6s
Wymaganie było mocniejsze niż schowanie przycisku: po niedostępnej funkcji nie
może zostać śladu w źródle strony. Największy wyciek nie był przyciskiem —
_prompt_block wstrzykiwał w stronę CAŁY katalog modeli jako JSON (dostawcy,
nazwy modeli, rozmiary okien kontekstu), na każdym ekranie z generowaniem,
niezależnie od uprawnień konta.

Druga dziura była głębsza: handlery nie sprawdzały nic. Trasa /interpret musi
być dostępna dla konta z Interpretacjami, więc granica przebiega WEWNĄTRZ niej,
po polu `action` — spreparowany formularz z action=prompt generował tekst, a
action=export pobierał arkusz, mimo że szablon chował oba przyciski.

Akcja bez uprawnienia wraca do akcji domyślnej ekranu zamiast dawać błąd:
komunikat „brak uprawnień do generowania" sam w sobie mówiłby, że taka funkcja
istnieje.

Ślady wycięte także tam, gdzie nie były kontrolką: znaczniki natalNote/
reportNatal, pliki models.js/progress.js/natal.js/predictions.js oraz podtytuł
ekranu Skompiluj, który wymieniał interpretację od AI z nazwy.

12 testów; 9 z nich pada na kodzie sprzed poprawki (sprawdzone przez cofnięcie
zmian w app/). Kontrola pozytywna pilnuje, żeby nie przechodziły dlatego, że
generowanie jest zepsute dla wszystkich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:10:16 +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 a0d1135db1 feat(prezentacja): cache-busting plików statycznych (PRE-26)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m36s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 11s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 50s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m51s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 11s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Po deployu przeglądarka trzymała stare styles.css / *.js — ten sam URL, więc
serwowała z cache mimo nowej wersji. Doklejamy do URL-a krótki HASH TREŚCI pliku:
zmienił się plik → zmienił się URL → przeglądarka pobiera nowy; bez zmian URL
zostaje ten sam i cache dalej działa (bustujemy tylko to, co się zmieniło).

- `static_url(name)` + globalny helper Jinja `static()`: `/static/x?v=<md5[:8]>`.
  Hash liczony raz na proces (lru_cache) — nowy pod po deployu = świeży hash;
  brak pliku → `?v=0`, nie wywala strony.
- Wszystkie odwołania w szablonach (styles.css, nasze *.js, vendor Leaflet) idą
  teraz przez helper zamiast surowego `/static/...`.

Testy: +5 (hash w URL, zależny od treści, brak-pliku-bezpieczny, żaden szablon nie
serwuje surowej ścieżki, base używa helpera). Test kolejności skryptów zaktualizowany
pod nowy format. Prezentacja 214.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 11:22:38 +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 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 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 4ef90b30bc feat(logic): dostawcy LLM i pisanie horoskopu (LOG-31)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 24s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 24s
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
Domyslnie model LOKALNY — prompt niesie oryginalne opisy z baz, wiec
domyslnie NIC nie opuszcza sieci. Chmura wlaczana swiadomie (LOG-32).

- app/llm/: LLMProvider (jak EphemerisEngine z LOG-24) + dwie implementacje.
  Lokalny serwer modelu (Ollama/vLLM/llama.cpp) i OpenAI mowia TYM SAMYM
  protokolem /chat/completions, wiec obsluguje je jedna klasa; Anthropic ma
  wlasny /v1/messages. Napisane na samym httpx — bez SDK openai/anthropic:
  mniej zaleznosci i pelna kontrola nad tym, co wychodzi z sieci.
- Kazdy dostawca deklaruje `leaves_lan` — interfejs MUSI jawnie mowic, czy
  tresc baz opuszcza siec; UI na tej podstawie ostrzega.
- Ponawianie z backoffem (429/5xx), timeouty, czytelne bledy zamiast stacktrace.
- Klucz wylacznie z LLM_API_KEY (sekret), nigdy w repo ani w UI.
- POST /chart/horoscope + GET /llm/health.

WAZNE: prompt jest zwracany ZAWSZE — takze gdy model padnie lub brakuje
klucza. Dzieki temu awaria dostawcy nie blokuje pracy: prompt mozna
skopiowac i uzyc recznie.

Prezentacja: wybor modelu (lokalny/Anthropic/OpenAI), przycisk „Napisz
horoskop (AI)", wynik z informacja kto go napisal, czy dane opuscily siec,
ile wskazan weszlo, oraz zastrzezenie ze to nie porada medyczna (PRE-15).

Testy: 13 nowych (transport podstawiony — zaden prawdziwy model nie wolany),
calosc 131 passed / 1 skipped. Zweryfikowane e2e na atrapie serwera modelu:
horoskop napisany, leaves_lan=false, tokeny zliczone; a przy padnietym
modelu / braku klucza / zlym dostawcy — czytelny blad i zachowany prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 20:34:07 +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 de5d58958c feat(presentation): domyslna lokalizacja = Szpital Barlickiego, Lodz
build / build (push) Successful in 1m9s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m43s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 47s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
QoL: formularze (horoskop / interpretacje / kalendarz) maja wstepnie
wpisana lokalizacje urodzenia wlasciciela — Szpital Barlickiego w Lodzi
(51.7739N, 19.4829E; potwierdzone reverse-geokodowaniem OSM: Kopcinskiego
22/28). Nie trzeba jej wpisywac za kazdym razem.

- config.py: jedno zrodlo prawdy (DEFAULT_LAT/LON/LABEL, nadpisywalne ENV)
  + helper default_form().
- main.py: GET wstrzykuje default_form() + location_label do 3 formularzy
  z polami lokalizacji (significators pominiete — nie ma tam lat/lon).
- szablony: dyskretna podpowiedz z nazwa lokalizacji, widoczna tylko na
  czystym formularzu (po POST znika, wygrywa wpisana wartosc).
- „Tu i teraz" nadal nadpisuje domyslne wspolrzedne geolokalizacja.

Zweryfikowane TestClientem: 3 strony renderuja 51.7739/19.4829 + etykiete;
POST z innymi wspolrzednymi je zachowuje i chowa podpowiedz.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 01:16:24 +00:00
gitea c4a181b810 Spięcie osi czasu z bazą interpretacji i widokiem (LOG-14, 1B->2B)
Realizuje przepływ 1B->2B z notes2: predykcyjne sygnifikatory (z datami)
dopasowane do interpretacji z bazy.

Logika:
- timeline.py: zdarzenia niosą strukturę (directed/aspect/target dla dyrekcji,
  lord/sign dla profekcji) do budowy tokenów.
- significators.interpret_events + _event_tokens: z każdego zdarzenia buduje
  tokeny bazy (dyrekcja: [planeta][aspekt][cel]; profekcja: [władca][znak]) i
  dopina interpretacje reużywając _facet_samples (AND tokenów, dedup, rozwinięcie).
- /chart/timeline: flaga interpret=true.

Prezentacja:
- nowa strona /timeline "Kalendarz": formularz (urodzenie + zakres dat) -> oś
  czasu z technikami, datami i interpretacjami; nawigacja + wspólne now.js.

Walidacja E2E na realnym main_base.xlsx (2025-2026):
- dyr. Saturn kwadratura MC -> 50 interpret. ("...5th house" -> "abortion/miscarriage")
- Władca Roku Saturn (wiek 42) -> 63; strona renderuje badge dat/technik.
71 testów przechodzi (nowy test_timeline_interpret).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:31:50 +02:00