Dokumentacja dla programistów

Co DiPAgE przechowuje, co przyjmie od Ciebie i z czym się komunikuje. Napisane dla osób budujących importery, eksportery i integracje albo hostujących aplikację samodzielnie.

Angular 22 · zoneless IndexedDB · bez backendu MIT PWA · offline-first

Naciśnij /, aby skupić wyszukiwanie · Esc, aby wyczyścić

Jeśli szukasz pomocy w korzystaniu z aplikacji, to przejdź do dokumentacji użytkownika

Szukasz standardu danych lub przykładowych eksportów? Przejdź do plików do pobrania

Kluczowe zasoby dla programistów

Dokumentacja pól rekordu

Każde pole PSM-Anwendungsdatensatz — łącznie 69 — z typem, ograniczeniami, przykładem i zachowaniem, którego nie widać w schemacie.

Otwórz dokumentację pól
Standard danych i pliki przykładowe

Schemat JSON, wariant XSD oraz przykładowe eksporty w formatach CSV, JSON i XML — wszystko w jednym miejscu.

Przejdź do plików do pobrania
Szukasz przewodnika użytkownika?

Instalacja aplikacji, rejestrowanie zabiegu, eksport na potrzeby kontroli — dokumentacja zadaniowa ma własną stronę.

Przejdź do dokumentacji użytkownika
Zacznij tutaj

Pytania programistów.

Pytania, które pojawiają się przy pierwszej próbie integracji z DiPAgE, mniej więcej w kolejności ich pojawiania się.

10 pytań

Nie. DiPAgE nie ma własnego backendu ani serwerowej bazy danych — nie ma się do czego uwierzytelnić ani punktu końcowego zwracającego rekordy. Każdy rekord znajduje się w IndexedDB przeglądarki, w której go wprowadzono. Integracja odbywa się przez pliki: eksport z aplikacji, import do aplikacji. Formaty opisano w sekcji „Import, eksport i wymiana”, a struktura rekordu ma własną stronę dokumentacji. Kod działający wewnątrz strony to inna sprawa — zobacz API JavaScript w następnym wpisie.

Tak — od tego jest window.dipageApi. Aplikacja przy starcie zamraża na window niewielki obiekt (obecnie wersja 1.1.0), aby kod działający w stronie mógł czytać i zapisywać rekordy przez metody, zamiast przeklikiwać się przez interfejs; to przewidziana ścieżka dla agenta zbierającego lub wprowadzającego dane. Udostępnia createRecord, openNewRecord, updateRecord, deleteRecord, getRecord, getRecords, searchPsm, searchCrops, getProfile, exportRecords i getTemplates. Wywołania przechodzą przez tę samą warstwę zapisu co interfejs, a więc przez ten sam sanitizer schematu — API nie zapisze zatem kształtu rekordu, którego nie mógłby wytworzyć formularz. To powierzchnia wyłącznie wewnątrz strony: nie stoi za nią endpoint sieciowy, istnieje tylko w karcie przeglądarki z otwartym DiPAgE i znika razem z nią.

To celowe, nie literówka. Szerokość ograniczono do ±100, a długość do ±50 jako decyzję produktową, co przy okazji odrzuca współrzędne daleko poza zamierzonym obszarem użycia. Granice są zduplikowane w dwóch miejscach — w schemacie JSON i w pliku stałych TypeScript — ponieważ JSON nie może importować TypeScript. Jeśli forkujesz aplikację i poszerzasz jedną, poszerz drugą w tej samej zmianie, inaczej walidacja zacznie przeczyć samej sobie.

Nie jako pole schematu. Format rekordu jest ustalony przez standard PSM i nie przewiduje na to miejsca. Aplikacja zamiast tego zapisuje w notizen kody znaczników niezależne od języka (PPS_EA, PPS_NZ, ŚOR_ZN) i odczytuje je przy edycji oraz imporcie. Ten znacznik jest jedynym trwałym sygnałem przetrwającym eksport i ponowny import. Dwie konsekwencje dla własnych narzędzi: nie nadpisuj notizen w całości, bo go usuniesz; i nie oczekuj zulassungsnummer przy takim środku, bo środek z zezwoleniem awaryjnym go nie ma.

Wcale — jest wnioskowana. Kilka środków zastosowanych w jednym przejeździe daje kilka rekordów, po jednym na środek, zgodnie z niemieckim obowiązkiem ewidencyjnym. Nie ma flagi tankmischung. Aplikacja rozpoznaje grupę później po trzech wspólnych cechach: miejscu zabiegu, dacie zabiegu i znaczniku czasu utworzenia rekordu — dlatego zapis wsadowy stempluje rekordy co 10 ms od wspólnej wartości bazowej, zamiast odczytywać rzeczywisty zegar dla każdego. Dawka jest celowo wyłączona z tego klucza grupowania, ponieważ każdy środek w mieszaninie zachowuje własną dawkę.

To dwie różne rzeczy i żadna nie jest polem schematu. Rekord roboczy to rekord zapisany z miękką walidacją; w IndexedDB nosi status: „draft”, ale schemat celowo wyklucza status — sanitizer usuwa go przy każdym odczycie i zapisie, a warstwa zapisu dołącza go z powrotem, aby rekord roboczy przetrwał przeładowanie strony. W eksporcie status nigdy się nie pojawia. „Niekompletny” nie jest w ogóle przechowywany, lecz obliczany przez ponowne sprawdzenie zapisanego rekordu pod kątem brakujących pól wymaganych. Kontrola eksportu odrzuca rekordy niekompletne i robocze z komunikatem; pełne kopie zapasowe zachowują jedne i drugie. Pola wyłącznie interfejsowe środka — indikation, anwendungszeitpunkt i notfallzulassung — sanitizer również usuwa.

Dla JSON kontraktem jest schemat — waliduj względem niego i gotowe. Dla formatów tabelarycznych dochodzi jedna reguła: CSV i skoroszyt Excel mają dwuwierszowy nagłówek. Wiersz 1 zawiera zlokalizowane etykiety dla ludzi, wiersz 2 maszynowe ścieżki pól (np. anwendung_zeitpunkt.datum), a dane zaczynają się od wiersza 3. Importer wyszukuje wiersz ścieżek, zamiast zakładać jego pozycję — dlatego plik wyeksportowany po niemiecku importuje się poprawnie do interfejsu angielskiego. Zapisz poprawnie wiersz 2, a w wierszu 1 może być cokolwiek zechcesz.

Klient jest objęty licencją MIT. Repozytorium na serwerze git JKI nie jest jeszcze publiczne — napisz do zespołu po dostęp; forki pod kątem procesów konkretnych instytucji są potem wprost mile widziane. Jeśli planujesz wnosić zmiany z powrotem, a nie odchodzić własną drogą, liczą się dwie konwencje: commity zgodne z Conventional Commits (wymuszane przez commitlint) oraz prowadzony przez projekt dziennik decyzji, które wyglądają błędnie, ale nimi nie są. Obecnie nie ma go w repozytorium; poproś o niego zespół, zanim którekolwiek z tych miejsc „naprawisz” — kilka niespodzianek z tej strony to właśnie jego wpisy.

Wyłącznie zapytania wyszukujące i tylko do origins ustalonych w Content-Security-Policy: synops.julius-kuehn.de po środki, wskazania, uprawy i zezwolenia awaryjne; nominatim.openstreetmap.org po geokodowanie kodów pocztowych; oraz GeoServer JKI wraz z hostami kafelków OSM/Esri na potrzeby map i wyboru geometrii pola. Te origins są zapisane w Content-Security-Policy, więc nic innego nie może zostać wywołane, nawet gdyby próbowała tego jakaś zależność. Nie ma telemetrii ani analityki. Twoje rekordy nie opuszczają urządzenia, dopóki sam ich nie wyeksportujesz.

Nie, a nazewnictwo faktycznie wprowadza w błąd: eksport .xml to skoroszyt SpreadsheetML 2003, a nie dziedzinowy dokument XML. Excel otwiera go natywnie, ze stylizowanymi nagłówkami i arkuszem referencyjnym wszystkich dopuszczonych kodów jednostek, a aplikacja importuje własny eksport bez zmian — dzięki czemu nadaje się on jako szablon do ręcznego wypełniania. Nie ma osobnego formatu „excel”; to jest właśnie ten. Jedno zastrzeżenie, którego nie pokryje żaden test automatyczny: ustawienia blokowania plików w Centrum zaufania na zarządzanej instalacji Windows mogą wprost odrzucić SpreadsheetML 2003.

Tematy techniczne.

Każdy temat jest samodzielny. Otwórz ten, którego potrzebujesz; reszta pozostanie zwinięta.

Kształt całości

DiPAgE to jednostronicowa aplikacja Angular działająca w całości w przeglądarce. Nie ma serwera aplikacyjnego, bazy danych do przygotowania ani systemu kont. Jedyny zaangażowany serwer serwuje statyczny pakiet i na tym jego rola się kończy. Wszystko, co aplikacja wie, znajduje się w IndexedDB na urządzeniu, na którym to wprowadzono.

Wybory technologiczne warte poznania

  • Angular 22 z komponentami standalone i sygnałami wszędzie. Wykrywanie zmian jest zoneless, każdy komponent OnPush.
  • TypeScript w trybie strict, łącznie ze strictTemplates. Zwróć uwagę na lukę: tsc nie sprawdza szablonów, więc nieaktualne powiązanie ujawnia się dopiero przy budowaniu AOT.
  • Tailwind CSS 4 z DaisyUI 5. Własny CSS wyłącznie tam, gdzie klasa narzędziowa nie wyraża reguły.
  • UUIDv7 dla każdego generowanego ID, dzięki czemu identyfikatory sortują się według czasu utworzenia.

Co kosztuje zoneless

Jeśli piszesz testy dla tej bazy kodu, warto poznać jedną pułapkę od razu: przy wykrywaniu zmian zoneless subskrypcji debounceTime lub delay utworzonej w konstruktorze komponentu nie da się przepchnąć przez tick() wewnątrz fakeAsync. Asercja przechodzi wtedy, w ogóle nie wykonując opóźnienia. Twórz subskrypcję w ciele testu.

Co kosztuje lokalne przechowywanie danych

Brak synchronizacji, brak scalania między urządzeniami, brak kopii zapasowej po stronie serwera. Przeniesienie danych między urządzeniami to jawny eksport i import, a towarzyszące temu rozwiązywanie konfliktów jest widocznym przepływem, a nie algorytmem działającym w tle. To świadomy kompromis na rzecz pracy bez łączności i nieprzechowywania danych osobowych centralnie.

Jednostka przechowywania

Przechowywaną encją jest PSM-Anwendungsdatensatz, identyfikowany jako urn:psm:anwendung-datensatz:1.0.0 i zdefiniowany w record.schema.json (JSON Schema draft 2020-12). Jeden rekord opisuje jeden środek zastosowany raz. Kilka środków w jednym przejeździe albo kilka części poddanych zabiegowi daje kilka rekordów — formularz rozmnaża je przy zapisie.

Gdzie naprawdę leży złożoność

Większość schematu jest płaska i przewidywalna. Wyjątkiem jest behandlungsort, tablica, której standort_kennung_wert jest siedmioelementową unią rozróżnianą przez standort_kennung_art: współrzędne, identyfikator działki, odniesienie InVeKoS, geometria powierzchni, odcinek torów, adres leśny lub droga. Jeden z nich — drogi — zagnieżdża w sobie kolejną unię. Schemat wyraża to jako łańcuchy if/then w allOf, a nie jako nazwane warianty, więc generyczny walidator sobie z tym poradzi, ale walidacja pisana ręcznie musi odtworzyć to rozgałęzienie.

Dwie reguły spoza schematu

  1. Sanitizer jest bramą kształtu. Każdy odczyt i zapis do magazynu jest filtrowany względem schematu. Pole, którego schemat nie zna, jest po cichu odrzucane — a nie głośno odrzucone. Jeśli zapisane przez Ciebie pole potem znika, to właśnie dlatego.
  2. Niektóre pola istnieją w modelu, ale nie pojawiają się w żadnym eksporcie. Pola środka indikation, anwendungszeitpunkt i notfallzulassung służą wyłącznie interfejsowi i nie są też zapisywane. Wyjątkiem jest status rekordu: sanitizer go usuwa, ale warstwa zapisu dołącza potem draft/saved z powrotem, aby rekord roboczy przetrwał przeładowanie strony.

Pole po polu

Każde pole, jego typ, ograniczenia, gotowy do wklejenia przykład oraz zachowanie niewidoczne w schemacie opisano na osobnej stronie, wraz ze schematem do pobrania i kompletnym przykładowym rekordem obejmującym wszystkie siedem wariantów lokalizacji.

Układ

Wszystko znajduje się w jednej bazie IndexedDB, psm-application-db, obecnie w DB_VERSION 13. Sześć magazynów obiektów: records (rekordy zabiegów), partial_data (szablony danych podstawowych, indeksowane typem), settings, crops (zsynchronizowana lista upraw), notfallzulassungen (zsynchronizowane zezwolenia awaryjne) i logs.

Sanitizer leży na każdej ścieżce

Odczyty i zapisy przechodzą przez filtr schematu. To utrzymuje obce lub nieaktualne pola poza magazynem i zarazem jest powodem, dla którego ręczny zapis do IndexedDB nie jest wspieraną drogą integracji — Twoje dodatkowe klucze nie przetrwają następnego odczytu. Do debugowania możesz jednak otworzyć bazę w dowolnym panelu narzędzi deweloperskich (magazyn records) — wyłącznie jako diagnostykę do odczytu.

Trwałość jest proszona, nie gwarantowana

Aplikacja wywołuje przy inicjalizacji navigator.storage.persist(). Jeśli użytkownik odmówi albo przeglądarka zignoruje prośbę, zapisane dane stają się usuwalne pod presją pamięci — a mobilne Safari jest tu zwykłym podejrzanym. Dla aplikacji do zapisu polowego oznacza to realną utratę danych, więc wdrażając ją użytkownikom, uczyń eksport kopii zapasowej częścią wdrożenia, a nie funkcją do samodzielnego odkrycia.

Jeśli zmieniasz schemat

Istnieją trzy niezależne numery wersji, których nie wolno mylić: datensatz_format_version jest w schemacie zamrożone jako const na „1.0.0” i opisuje format rekordu; nieformalny licznik („v12”, „v13”) w komentarzach i changelogu śledzi przebiegi zmian schematu i nie ma go w żadnym rekordzie; DB_VERSION to wersja aktualizacji IndexedDB i nigdy nie pojawia się w pliku eksportu. Zmiana schematu niekoniecznie przesuwa DB_VERSION. Numery wersji są powiązane z miejscami, których nie znajdziesz uruchamiając testy: helpery end-to-end i test integralności schematu na sztywno zawierają DB_VERSION. Zmiana w jednym miejscu bez pozostałych daje kilkaset niepowodzeń wyglądających jak niestabilność testów, a nie jak niezgodność wersji.

Formaty eksportu

  • JSON — sformatowane rekordy, dokładnie w kształcie schematu. Format, pod który się buduje.
  • XMLnie dziedzinowy dokument XML, lecz skoroszyt SpreadsheetML 2003, który Excel otwiera natywnie: stylizowane nagłówki, zamrożony wiersz nagłówka i arkusz referencyjny ze wszystkimi 284 dopuszczonymi kodami jednostek. Nie ma osobnego formatu „excel”; to jest właśnie ten.
  • CSV — spłaszczony, z BOM, wartości wyglądające jak formuły unieszkodliwione przeciwko wstrzyknięciu CSV.
  • PDF — układ sekcyjny nastawiony na kontrolę, z danymi firmowymi operatora. Geometria pola jest tu streszczona jako typ, liczba wierzchołków i centroid zamiast surowych współrzędnych; każdy inny format zachowuje pełną tablicę.

Dwuwierszowy nagłówek

CSV i skoroszyt dzielą jeden kontrakt, który warto zrozumieć przed napisaniem parsera. Wiersz 1 zawiera zlokalizowane etykiety dla ludzi. Wiersz 2 zawiera maszynowe ścieżki pól — anwendung_zeitpunkt.datum, behandlungsort[0].bezeichnung. Dane zaczynają się od wiersza 3. Importer lokalizuje wiersz ścieżek, zamiast zakładać jego pozycję, i radzi sobie także ze starszym plikiem o jednym wierszu nagłówka. To właśnie pozwala plikowi wyeksportowanemu z interfejsu niemieckiego zaimportować się poprawnie do angielskiego.

Import

Przyjmowane są .json, .csv, .xml oraz .zip zawierający powyższe. Importy są normalizowane (rozpłaszczanie ścieżek kropkowych, dopasowanie typów dla każdego pola, niemieckie przecinki dziesiętne i daty DD.MM.RRRR), a następnie sprawdzane względem schematu. Rekordy bez wymaganych pól trafiają do etapu przeglądu, w którym użytkownik decyduje, czy mimo to je zaimportować; wpisy strukturalnie bezużyteczne są zliczane i raportowane. Trzy pola systemowe są uzupełniane podczas normalizacji, gdy ich brakuje, więc ręcznie wypełniony szablon z pustą kolumną ID i tak się zaimportuje.

Konflikty

Import o tym samym ID i innej treści otwiera wybór wersji dla każdego rekordu — wersja importowana kontra bieżąca, z opcją zachowania obu. Trafienie odciskiem treści przy różnych ID stanowi własną klasę konfliktu; to przypadek dwukrotnie zaimportowanego szablonu. Odcisk jest stabilny niezależnie od kolejności kluczy, ignoruje ID, znacznik czasu utworzenia, notatki i status, a reaguje na środek, dawkę, datę, uprawę, lokalizację i wykonawców.

Kopie zapasowe

Pełna kopia zapasowa to ZIP zawierający records.json, master_data.json, profiles.json, settings.json i metadata.json. Przywracanie jest celowo etapowe: najpierw jawne potwierdzenie, potem parsowanie bez zapisu, potem klasyfikacja konfliktów, na końcu zatwierdzenie. Rekordy są przywracane tak, jak je zapisano — również robocze i niekompletne — ponieważ zgodność ze schematem egzekwuje się przy eksporcie, a nie przy przywracaniu. Jeśli zapisy zawiodą w połowie, aplikacja zgłasza przywrócenie częściowe, zamiast zachęcać do ponowienia, które zastosowałoby dane podwójnie.

Dwie warstwy i luka między nimi

Pierwsza warstwa to schemat JSON: pola wymagane, typy, enumy, formaty, wszędzie additionalProperties: false oraz zawężenia warunkowe wyrażone łańcuchami if/then. Druga warstwa to kod aplikacji i egzekwuje reguły, których schemat nie potrafi wyrazić. Budując własne narzędzia, licz się z tą luką — rekord może spełniać schemat i mimo to zostać odrzucony przez aplikację.

Reguły istniejące tylko w kodzie aplikacji

  • Głębokość zagnieżdżenia geometrii musi odpowiadać typowi geometrii. Schemat wymaga jedynie, by koordinaten było tablicą.
  • Dawka musi być większa od zera. Schemat mówi number.
  • Godzina zabiegu staje się obowiązkowa, gdy wybrany środek ma klasyfikację zagrożenia dla pszczół.
  • Sprawdzenia pól dla poszczególnych rodzajów lokalizacji, wykraczające poza to, co pokrywa łańcuch if/then.

Dwie pułapki w samym schemacie

Warto je znać, zanim zaczniesz debugować rozbieżność walidatorów. Po pierwsze anwendung_zeitpunkt.uhrzeit ma format: time, co oznacza postać full-time według RFC 3339 — przesunięcie jest obowiązkowe. „06:15:00” nie przechodzi, „06:15:00Z” przechodzi. Wpis examples w samym schemacie pokazuje postać bez przesunięcia, więc go nie kopiuj. Po drugie pola kilometrażu kolejowego używają multipleOf: 0.1, co źle zachowuje się w binarnej arytmetyce zmiennoprzecinkowej: typowy walidator liczy wartość / 0.1 i sprawdza, czy wynik jest całkowity, a 0.3 / 0.1 daje 2,9999999999999996. Wartość 0,3 — którą schemat również podaje jako przykład — łamie zatem własne ograniczenie.

Własny walidator DiPAgE jest w obu punktach celowo łagodniejszy: przesunięcie jest opcjonalne, a multipleOf porównywane jest z tolerancją. Pliki eksportowane przez aplikację mogą więc zawierać wartości, które ścisły walidator odrzuci — skonfiguruj swój walidator tak samo.

Twarde i miękkie

Nie każda reguła blokuje. Szablony danych podstawowych używają wyłącznie miękkiej walidacji: ostrzeżenia nigdy nie uniemożliwiają zapisu. W formularzu rekordu kilka sprawdzeń międzypolowych ma charakter wyłącznie doradczy — rok zbioru niezgodny z datą zabiegu, powierzchnia zabiegu większa od lokalizacji, odwrócony zakres BBCH. Pojawiają się jako ostrzeżenia i przepuszczają zapis.

Rekordy niekompletne

„Niekompletny” jest obliczany, a nie przechowywany: zapisany rekord jest ponownie sprawdzany pod kątem brakujących pól wymaganych i oznaczany listą braków. Rekordy niekompletne trafiają na górę listy i są odrzucane przez kontrolę eksportu z komunikatem, ale zachowuje się je w pełnych kopiach zapasowych, aby przywracanie było bezstratne.

SynOPS — ten merytoryczny

synops.julius-kuehn.de dostarcza cztery rzeczy: wyszukiwanie środków po nazwie handlowej lub numerze zezwolenia, zawężone do wybranej uprawy i pogrupowane według statusu dopuszczenia; wskazania dla pary środek–uprawa, wraz z kodami zagrożeń i propozycjami dawkowania automatycznie wypełniającymi dawkę; listę upraw, synchronizowaną dla każdego języka do IndexedDB, aby wyszukiwanie działało offline; oraz zezwolenia awaryjne, również synchronizowane lokalnie. Zapytania o środki i wskazania są na żywo i opóźniane, a nieaktualne odpowiedzi są anulowane. Uprawy i zezwolenia awaryjne to zbiory synchronizowane ze sprawdzeniem wieku, a nie pobierane przy każdym zapytaniu.

Geokodowanie i dane mapowe

nominatim.openstreetmap.org rozwiązuje kody pocztowe dla selektora mapy. GeoServer JKI obsługuje selektor geometrii pola przez WFS: urzędowe bloki pól wraz z numerami FLIK i działek dla Nadrenii Północnej-Westfalii (inv:NRW_FB_<rok>) oraz obrysy z rozpoznawania upraw bez żadnej tożsamości pola dla reszty Niemiec (cora:CORA_<rok>); hosty OSM i Esri dostarczają kafelki. Po wybraniu wielokąta użytkownik zaznacza, które dodatkowe wpisy tożsamości (odniesienie InVeKoS i/lub współrzędne centroidu) zostaną dodane — nic nie jest zaznaczone domyślnie. Z wielokątem łączy je tylko wspólne pole bezeichnung, ponieważ schemat nie ma pola powiązania.

Wszystko to jest przypięte

Każdy z tych origins figuruje w Content-Security-Policy. Nic innego nie może zostać wywołane i to właśnie jest mechanizm stojący za stwierdzeniem „brak telemetrii”, a nie obietnica. Jeśli forkujesz aplikację i kierujesz ją na własne hosty, dodaj każdy z tych origins we wszystkich miejscach wymienionych w temacie self-hostingu, inaczej żądania zawiodą po cichu.

Degradacja

Awarie i stan offline przełączają globalny baner między offline a ograniczonym. Nic lokalnego nie jest dotknięte: każde pole nadal można wypełnić ręcznie, rekordy się zapisują, eksporty działają. Milkną wyłącznie zapytania wyszukujące.

Jeśli integrujesz

To są systemy nadrzędne aplikacji, a nie powierzchnia API, którą DiPAgE Ci oferuje. Nie ma endpointu DiPAgE do wywołania — zobacz pierwszy wpis FAQ.

Model buforowania

Zdolność offline pochodzi z service workera Angulara, konfigurowanego w ngsw-config.json. Powłoka aplikacji i zasoby są buforowane z wyprzedzeniem; zewnętrzne origins API mają własne grupy danych. Po pierwszym udanym wczytaniu aplikacja jest w pełni operacyjna bez sieci — wprowadzanie rekordów, walidacja, przechowywanie, eksport i generowanie PDF działają lokalnie.

Zbiory synchronizowane a zapytania na żywo

Dwa różne mechanizmy, łatwe do pomylenia. Uprawy, zezwolenia awaryjne i kody zagrożenia dla pszczół są synchronizowane do IndexedDB i stamtąd odczytywane, ze sprawdzeniem wieku przy starcie zamiast pobierania przy każdym zapytaniu — dlatego autouzupełnianie upraw działa bez zasięgu. Wyszukiwanie środków i wskazania działają na żywo i zanikają offline.

Kafelki mapy omijają service workera

Każdy adres kafelka niesie stały parametr ngsw-bypass, więc kafelki w ogóle nie przechodzą przez service workera, a nieudane wczytania są ponawiane przez własny punkt wejścia OpenLayers, a nie przez warstwę cache. Warto to wiedzieć, badając, dlaczego kafelki zachowują się inaczej niż każde inne żądanie.

Instalacja i wykrywanie

Aplikację można zainstalować jako PWA i wykrywa ona tryb standalone. Możliwość instalacji różni się między platformami w sposób, który jest raczej kwestią wsparcia niż kodu — dokumentacja użytkownika opisuje, które przeglądarki i systemy operacyjne na to pozwalają.

Gdzie naprawdę leży ryzyko

Nie ma serwera, sesji ani wielodostępności, więc większość typowej powierzchni ataku webowego po prostu nie istnieje. Pozostaje to, że aplikacja parsuje pliki, które podaje jej użytkownik — JSON, CSV, XML, ZIP — ze źródeł, za które nie może ręczyć. Wokół tej granicy zbudowano utwardzenie.

Utwardzenie importu

  • ZIP: lista dozwolonych nazw wpisów, limity rozmiaru i liczby, ograniczenie rozmiaru po rozpakowaniu dla każdego wpisu. Pełne archiwa kopii zapasowych są odrzucane strukturalnie przez importer rekordów, zanim jakikolwiek wpis zostanie sparsowany.
  • XML: <!DOCTYPE> odrzucane wprost, z limitem 2 MB.
  • Prototype pollution: każda iteracja po niezaufanych kluczach jest zabezpieczona. Waży to tu więcej niż zwykle, bo rozpłaszczanie ścieżek kropkowych z założenia przechodzi po ścieżkach kluczy dostarczonych przez atakującego.
  • Przywracanie kopii: rekordy są przywracane tak, jak je zapisano (pomijane są tylko wpisy niebędące obiektami), i przy każdym odczycie przechodzą przez sanitizer; szablony są sprawdzane względem kształtu, profile przez listę dozwolonych pól. Wszystko, co odrzucono, jest zliczane i raportowane, a nie po cichu pomijane.

Utwardzenie eksportu

Wstrzyknięcie CSV jest unieszkodliwiane przy eksporcie: wartość wyglądająca jak formuła zostaje zabezpieczona tak, by nie wykonała się po otwarciu w arkuszu kalkulacyjnym. W ścieżce skoroszytu wartości wyglądające jak formuły są mimo to zachowywane dosłownie, aby obieg tam i z powrotem nie uszkadzał danych.

Content-Security-Policy

CSP przypina origins wymienione w sekcji o zewnętrznych API. Jest zdefiniowana w meta-tagu index.html oraz w nagłówku odpowiedzi serwera, który serwuje aplikację — obie są egzekwowane osobno i muszą się zgadzać; frame-ancestors występuje tylko w nagłówku, bo CSP w meta-tagu nie potrafi tego wyrazić. Obok tego: X-Frame-Options, nosniff, polityka referrera i zabezpieczenie przed path traversal w serwerze statycznym.

Zgoda przed zapisem

Outlet routera renderuje się dopiero po udzieleniu zgody na przechowywanie, więc nic nie jest zapisywane, zanim użytkownik się zgodzi. Jeśli piszesz testy zasiewające zgodę bezpośrednio do IndexedDB, przeładuj potem stronę — aplikacja nie odczytuje zgody reaktywnie, a pominięcie tego przeładowania rozbiło już całe zestawy testów.

Gdzie CSP i testy wizualne się rozjeżdżają

Jedna pułapka warta nazwania: zasób zablokowany przez CSP renderuje się konsekwentnie zepsuty, więc jego zrzut wizualny nadal pasuje do konsekwentnie zepsutego wzorca. Nowy zewnętrzny origin trzeba zweryfikować na zbudowanej i serwowanej wersji; zielone testy end-to-end nie dowodzą, że działa.

Tłumaczenia

Trzy języki — de (domyślny), en i pl — utrzymywane przy pełnej zgodności kluczy, z notacją kropkową i interpolacją {param}. Formatowanie zależne od lokalizacji obejmuje daty, godziny i generowane nazwy plików. Preferencja języka jest zapisana w IndexedDB i niezależna od języka urządzenia.

Dwie reguły kształtujące kod

  1. Żadnych surowych napisów w interfejsie. Nawet komunikaty błędów pojawiają się jako przetłumaczone klucze; szczegóły trafiają do magazynu logów.
  2. translate() zwraca przy chybieniu klucz, nigdy wartość pustą. Brakujące tłumaczenie ujawnia się więc jako widoczny klucz, a nie puste miejsce — celowo, bo pustą etykietę znacznie łatwiej przeoczyć podczas przeglądu.

Dostępność

Celem jest WCAG 2.2, egzekwowany z trzech stron: reguły dostępności ESLint na szablonach, przebiegi axe-core end-to-end w obu motywach oraz test przejścia klawiaturą. Komunikaty dla czytników ekranu idą przez dwa obszary ARIA live, jeden uprzejmy i jeden stanowczy, zarządzane przez jedną usługę. Zmiany trasy, przełączenia motywu, języka i skali czcionki oraz pojawienie się okien modalnych są ogłaszane.

Motywy i skalowanie

Dwa motywy, jkiLight i jkiDark, ustawiane przez data-theme i zapamiętywane. Skala czcionki to 100, 150 lub 200 procent, stosowana do głównego rozmiaru czcionki, również zapamiętywana. Dodając komponent, musisz utrzymać go przy 200 procentach w obu motywach — to właśnie ta kombinacja psuje układ jako pierwsza.

Warstwy

  • Lint — styl i reguły dostępności szablonów.
  • Sprawdzanie typów — TypeScript w plikach .ts. Nie szablony.
  • Niezmienniki — skrypt spójności między plikami: trójka DB_VERSION, para CSP meta i nagłówek, grupy danych service workera, granice geograficzne w TypeScript względem schematu, maksymalna długość notizen, zgodność wygenerowanego walidatora schematu, additionalProperties: false w każdym obiekcie, zgodność kluczy i18n i klucze osierocone oraz minimalny wiek nowych pakietów pnpm.
  • Testy jednostkowe — ponad 3400 testów Karma/Jasmine, mniej więcej 92% pokrycia instrukcji i 85% gałęzi. Próg nie jest skonfigurowany, więc to pomiar, a nie bramka.
  • Budowanie (AOT)jedyna bramka wychwytująca nieaktualne powiązanie w szablonie.
  • End-to-end — rzeczywiste przepływy plus zrzuty wizualne na desktopie, mobilnym Chrome i mobilnym Safari.
  • Dostępność — axe-core w obu motywach plus przejście klawiaturą.
  • Bezpieczeństwo serwera — odrzucanie path traversal i nagłówki bezpieczeństwa.

Gdzie zielony przebieg kłamie

Warto to przyswoić, zanim zaufasz przechodzącemu potokowi:

  1. Zielone sprawdzanie typów nie oznacza, że zmiana nazwy dotarła wszędzie — szablony sprawdzane są dopiero przy budowaniu AOT.
  2. Fixture uciszone przez kompilator (rzutowania as unknown as na kształty rekordów) przechodzą dalej względem nieaktualnego schematu po zmianie pola.
  3. Lista szpiegów bez nowo dodanej metody zwraca undefined i daje mylące „nigdy nie wywołano”.
  4. Zasób zablokowany przez CSP nadal pasuje do zepsutego wzorca, więc testy wizualne nie dowodzą, że nowy origin działa.

Polityka zrzutów

Wzorce wizualne aktualizuje człowiek, nigdy automat i nigdy CI. Różnica jest sygnałem przeglądu; regenerowanie jej przy niepowodzeniu wyrzuca dokładnie to, co test mierzył.

Wymagania wstępne

Node.js 22 lub nowszy i pnpm 12 lub nowszy do zbudowania. Nic więcej — żadnej bazy danych, brokera komunikatów ani zewnętrznej usługi do rejestracji. Środowiskiem docelowym jest dowolna nowoczesna przeglądarka z ES2022, IndexedDB i obsługą service workerów.

Serwowanie

Budowanie tworzy statyczny pakiet. Do samodzielnego hostingu dołączony jest mały serwer Node, np. do uruchomienia pod PM2; referencyjne wdrożenie JKI go jednak nie używa, lecz serwuje pakiet jako pliki statyczne pod /app/ nadrzędnej witryny, która ustawia własne nagłówki bezpieczeństwa. Dołączony serwer obsługuje konfigurowalną ścieżkę bazową, więc wdrożenie w podkatalogu działa bez przepisywania adresów zasobów. Egzekwuje zabezpieczenie przed path traversal, ustawia nagłówki bezpieczeństwa i serwuje zasoby z hashem jako niezmienne, a powłokę aplikacji jako niebuforowaną.

HTTPS nie jest opcjonalne

Service workery wymagają bezpiecznego kontekstu. Bez HTTPS nie dostajesz gorszego doświadczenia instalacji, tylko żadnej zdolności offline — co usuwa centralną przesłankę aplikacji.

Prawdziwym zadaniem jest Content-Security-Policy

Nowy zewnętrzny origin trzeba dodać w trzech miejscach: w meta-tagu index.html, w nagłówku odpowiedzi tego, co serwuje aplikację (dołączonego serwera lub nadrzędnej witryny), oraz — w przypadku hostów API — w dataGroups pliku ngsw-config.json; jeśli go tam brakuje, service worker odpowiada na nieudane pobranie kodem 504. CSP z meta-tagu i z nagłówka są egzekwowane osobno; frame-ancestors występuje wyłącznie w nagłówku, bo CSP w meta-tagu nie potrafi tego wyrazić. Jeśli skierujesz aplikację na własne lustro SynOPS, własny geokoder lub własny serwer kafelków, każdy z tych origins trzeba dodać we wszystkich trzech miejscach. Pominięty origin zawodzi w czasie działania, po cichu, a przechodzący zestaw testów Ci tego nie powie — sprawdź na zbudowanej i serwowanej wersji.

Potok

W referencyjnym CI lint i testy jednostkowe są warunkiem budowania AOT, a budowanie jest warunkiem wdrożenia przez SSH. Testy end-to-end biegną równolegle, ale nie blokują wdrożenia; skrypt niezmienników i test bezpieczeństwa serwera nie są uruchamiane w CI. Warto naśladować raczej kolejność niż narzędzia: budowanie AOT stoi po testach jednostkowych celowo, bo to bramka wychwytująca uszkodzenia szablonów, których wcześniejsze bramki nie widzą.

Licencja i repozytorium

Klient jest objęty licencją MIT. Repozytorium na serwerze git JKI nie jest jeszcze publiczne — napisz do zespołu po dostęp. Forki pod procesy konkretnych instytucji są potem wprost mile widziane, podobnie zgłoszenia i łatki.

Konwencje

Commity zgodne z Conventional Commits, wymuszane przez commitlint w haku Husky. Lintowanie to ESLint z zestawami reguł Angulara i TypeScriptu. Żadne z nich nie podlega negocjacji w CI, skonfiguruj je więc lokalnie przed pierwszym pushem, a nie po pierwszym odrzuconym potoku.

Najpierw poproś o dziennik decyzji

Projekt prowadzi dziennik decyzji, które wyglądają błędnie, ale nimi nie są, każdą wraz z uzasadnieniem i wyraźną adnotacją „nie rób tego”. Kilka niespodzianek z tej strony to jego wpisy: niestandardowe granice współrzędnych, znacznik zezwolenia awaryjnego w polu tekstowym, wnioskowana zamiast przechowywanej mieszanina zbiornikowa, celowo skrócone słownictwo jednostek. Dziennik nie znajduje się obecnie w repozytorium; poproś o niego zespół. Jeśli coś w kodzie wygląda na oczywisty błąd, wyjaśnij to, zanim otworzysz poprawkę — może być nośne.

Jeśli zmieniasz kształt rekordu

Zmiana pola ma miejsca lustrzane, których nie wyliczy Ci pojedyncze niepowodzenie testu: schemat, interfejsy TypeScript, konstruktory formularzy wraz z walidatorami, mappery płaskie–zagnieżdżone, dopasowanie typów przy imporcie, zestawy kolumn eksportu, helpery zasiewające dla testów end-to-end oraz zlokalizowane etykiety kolumn we wszystkich trzech językach. Projekt utrzymuje mapę tych miejsc, której również nie ma jeszcze w repozytorium — poproś o nią i przejdź ją, zamiast gonić czerwone potoki po kolei.

Szukasz konkretnego pola? Otwórz dokumentację pól rekordu

Zacznij od razu

Korzystaj z DiPAgE bezpośrednio w przeglądarce. Bez rejestracji i bezpłatnie.

Otwórz aplikację i zacznij od razu dokumentować swoje zabiegi środkami ochrony roślin. Instalacja nie jest wymagana, jednak na obsługiwanych urządzeniach można dodatkowo zainstalować DiPAgE i korzystać z niego w trybie offline.

Bezpośrednio w przeglądarce
Instalacja nie jest wymagana. Otwórz aplikację i zacznij od razu.
Bez rejestracji
Z DiPAgE można korzystać bez konta użytkownika i bez rejestracji.
Dane zapisywane lokalnie
Twoje dane ewidencyjne pozostają na używanym urządzeniu.
Dostępne offline
Na obsługiwanych urządzeniach DiPAgE można zainstalować i korzystać z niego bez połączenia z Internetem.