herbatrium/STATUS.md
Till JS f204538bf9
Some checks are pending
CI / validate (push) Waiting to run
docs(status): Rechtsklick-Kontextmenü dokumentiert
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:05:22 +02:00

21 KiB

Herbatrium — Status

Lebt-Doku. Bei Phasen-Wechsel hier aktualisieren, nicht im Playbook. Das Playbook ist der ursprüngliche Plan, dieses STATUS.md zeigt, wo wir tatsächlich stehen.

Rechtsklick-Kontextmenü auf Beobachtungen (2026-06-03)

Geteiltes @mana/shared-ui/organisms ContextMenu auf jeder Observation (/my/observations): Status setzen (Phänologie via updateObservationState: blühend/fruchtend/…) + Zurückziehen (unpublishObservation, nur wenn nicht privat) + Löschen. Veröffentlichen bewusst NICHT im Menü — braucht den Lizenz-Picker (Invariante 5, kein stiller Default; besonders heikel bei sensiblen Arten). svelte-check 0 Fehler, build grün, Screenshot verifiziert, live auf app.herbatrium.com. (0643fd1)

Hinweis: Die öffentliche Landing (/) zeigt noch „IM AUFBAU · η-0", obwohl /plants + /my/observations real laufen — Landing-Text hängt dem tatsächlichen Stand hinterher.

Aktuelle Phase: Welle-1-Landing-Cutover LIVE (2026-05-22)

2026-05-22 20:00 — Subdomain-Migration (Landing-Cutover)

LANDING_CUTOVER.md ausgeführt. herbatrium.com zeigt jetzt auf die Astro-Marketing- Landing (herbatrium-landing:3204); die App lebt unter app.herbatrium.com (herbatrium-web:3104, war vorher herbatrium.com).

  • GET https://herbatrium.com/200 Marketing-Landing (Title: „herbatrium — Pflanzen-Tagebuch mit Naturschutz-Disziplin")
  • GET https://herbatrium.com/{ueber,mitwirken}200
  • GET https://herbatrium.com/sitemap.xml200 (4 Marketing-URLs)
  • GET https://herbatrium.com/robots.txt200, Allow + Sitemap- Referenz
  • GET https://herbatrium.com/.well-known/apple-app-site-association200 application/json (Universal Links bleiben funktional)
  • GET https://herbatrium.com/{plants/*,capture,my,auth/*,datenschutz,offline}301 → app.herbatrium.com/...
  • GET https://app.herbatrium.com/200 Web-App
  • GET https://api.herbatrium.com/healthz{"status":"ok"}

Offene Punkte aus dem Cutover:

  • Native iOS-App-AASA hardcoded auf herbatrium.com — Universal-Links auf /plants/* werden weiterhin vom Landing-nginx korrekt ausgeliefert (gleicher JSON-Payload, gleiche App-IDs). Im nächsten TestFlight-Build verifizieren.
  • Erst-Login auf app.herbatrium.com bei einem User, der vorher auf herbatrium.com eingeloggt war: mana-auth-Cookie auf .mana.how ist nicht betroffen; App-eigene Cookies auf der alten Apex-Domain verwaisen (falls vorhanden).

Aktuelle Phase: η-3' — Production-Deploy auf Mac Mini LIVE (2026-05-17)

2026-05-17 — Auth-Integration + Production-Container

Auth (Cards-Pattern portiert):

  • lib/auth.ts Session-Class mit JWT-Token-LocalStorage, tryRefresh gegen mana-auth /api/v1/auth/refresh, ensureFreshToken vor jedem API-Call, Dev-Stub als localhost-Fallback.
  • lib/api.ts Authorization: Bearer aus session.token.
  • /auth/callback-Route mit Return-URL-Restore.
  • (app)/+layout.svelte „Mit mana-Konto anmelden"-Button, Dev-Stub als <details> nur auf localhost sichtbar.

mana-Plattform live aktualisiert:

  • services/mana-auth/src/auth/sso-origins.ts: herbatrium.mana.how + herbatrium-api.mana.how zu PRODUCTION_TRUSTED_ORIGINS. Plus localhost:3101/3102. sso-config.spec.ts 8/8 grün.
  • services/mana-auth-web/src/lib/apps.ts: herbatrium-Eintrag mit Moos-Grün-Theme (HSL 104 29% 32%).
  • auth.apps-Insert auf mana_platform-DB durch.
  • mana-auth + mana-auth-web Container neu gebaut + restartet.
  • CORS_ORIGINS der mana-auth Service-Env in zentraler Compose erweitert.

Container live auf mana-server:

  • /Users/mana/projects/herbatrium/ aus Forgejo geklont.
  • 3 Container im manacore-monorepo-Netzwerk:
    • herbatrium-postgres (postgis/postgis:16-3.4) → 127.0.0.1:5449
    • herbatrium-api → host 3103 (memoro-audio-server belegt 3101)
    • herbatrium-web → host 3104
  • Schema appliziert via pnpm db:push im API-Container.
  • Cloudflared-Config in managarten/cloudflared-config.yml erweitert + launchctl stop/start com.cloudflare.cloudflared.

Lokale Smokes (auf mana-server):

  • curl http://localhost:3103/healthz → 200 ok
  • curl http://localhost:3103/readyz → 200 db:ok
  • curl http://localhost:3104/ → 200

🟢 LIVE auf https://herbatrium.mana.how (2026-05-17)

DNS-CNAMEs via cloudflared tunnel route dns angelegt — kein Dashboard-Klick nötig:

cloudflared tunnel route dns 1435166a-0e3f-4222-8de6-744f32cea5c9 herbatrium.mana.how
cloudflared tunnel route dns 1435166a-0e3f-4222-8de6-744f32cea5c9 herbatrium-api.mana.how

Live-Smoke 2026-05-17 (via 1.1.1.1):

  • https://herbatrium-api.mana.how/healthz → 200 {"status":"ok"}
  • https://herbatrium-api.mana.how/readyz → 200 {"db":"ok"}
  • https://herbatrium-api.mana.how/.well-known/mana-app.json → 200 Manifest
  • https://herbatrium.mana.how/ → 200

End-to-End-Browser-Test:

  1. https://herbatrium.mana.how/plants öffnen
  2. „Mit mana-Konto anmelden" → Redirect zu auth.mana.how/?app=herbatrium
  3. Mana-Konto-Login (Brand: Moos-Grün)
  4. Callback → /plants
  5. „+ Manuell" → Form ausfüllen (Name + Species + GPS + Notes) → Speichern
  6. Pflanze in Liste sehen, Detail öffnen, Inline-Name editieren
  7. „+ Neues Foto" → Tracking-Eintrag (ohne Foto klappt; Foto-Upload braucht mana-media erreichbar von herbatrium-api aus)

Aktuelle Phase: η-3' — MVP-Pivot Pflanzen-Tagebuch LIVE (2026-05-17)

η-3' 2026-05-17 — Single-User-Pflanzen-Tagebuch

Begründung Pivot: Till hat den ursprünglichen Citizen-Science-MVP auf einen Single-User-Pflanzen-Tagebuch-MVP verschoben (analog Nutriphi): Fotos hochladen → AI klassifiziert → Pflanze anlegen + benennen + tracken. Public-/Sensitivity-/Compliance-Pipeline bleibt im Schema schlafend (visibility=private Default), aktiviert sich erst wenn du wirklich publishen willst.

Schema-Anpassung:

  • specimen.species_id von NOT NULL → nullable. Drizzle-Migration 0001 (0001_nebulous_earthquake.sql). Erlaubt Pflanzen anzulegen, bevor AI klassifiziert hat.

API-Routes neu/erweitert:

  • POST /api/v1/specimens — Pflanze manuell anlegen mit display_label, description, lat/lng, optional species_scientific_name. species_id darf NULL bleiben bis /identify aufgerufen wird.
  • PATCH /api/v1/specimens/:id — Name + Beschreibung + Standort editieren. Owner-Check via first_seen_by.
  • GET /api/v1/specimens/my — Pflanzen-Liste mit Cover-Photo + Species- Info + Observation-Count. Sortiert nach updated_at desc.
  • GET /api/v1/specimens/:id — Detail mit allen Observations + Photos (chronologisch).
  • POST /api/v1/observations erweitert: specimen_id ist optional.
    • Mit specimen_id: Tracking-Modus — neue Observation an existierende Pflanze, observation_count wird inkrementiert.
    • Ohne specimen_id: Create-Modus wie bisher (neues Specimen + Observation in einem).

Web-UI komplett umgebaut:

  • /plants — Karten-Grid mit Cover-Foto, Name, Species, Eintrag-Count.
  • /plants/:id — Detail mit Foto-Timeline aller Observations, inline-editierbarer Pflanzen-Name, „+ Neues Foto"-Button.
  • /plants/:id/track — Tracking-Page: Foto + Zustand + Notes → an bestehende Pflanze anhängen.
  • /plants/newFoto-First-Flow (Nutriphi-Style):
    1. Foto(s) auswählen + GPS
    2. „Identifizieren" → POST specimen + POST observation + POST photos + POST identify
    3. Top-3 LLM-Vorschläge + optional Pl@ntNet-Verifier-Treffer
    4. User wählt + benennt + speichert
  • Navigation umgestellt: /capture → 307-Redirect auf /plants/new.
  • /my/observations bleibt als Legacy-Sicht mit Hinweis-Banner.

Smoke 2026-05-17:

  • pnpm type-check ✓ (API + Web, 0 errors / 0 warnings, 259 Files)

Was im Schema schlafend bleibt (für späteren Citizen-Science-Pivot):

  • visibility=public/unlisted, Lizenz-Picker (η-4)
  • FFH/IUCN-Sensitivity-Worker (läuft trotzdem, schadet nicht — kennzeichnet Schutzart-Verdacht in der Detail-View)
  • geo_visibility=region_only für sensitive species
  • photo_original_access_log, takedown_request (Tabellen da, ohne UI)
  • Public-Feed, Take-Down-UI

Diese Features sind im Code/Schema, aber UI nicht freigeschaltet. Re-Aktivierung jederzeit per Phase η-4 möglich.


Aktuelle Phase: η-2 — Klassifikation + Sensitivity LIVE (2026-05-17)

η-2 2026-05-17

Klassifikations-Pipeline analog Playbook §Klassifikations-Pipeline.

  • lib/mana-llm.ts (Port 3025, mana/vision-Alias, OpenAI-kompatibel): display-Layer als base64-data-URL, JSON-Schema-System-Prompt erzwingt { candidates[1..3], phenology, damage_indicators[], notes }-Output. fetchAsBase64DataUrl()-Helper für mana-media-Display-URLs.
  • lib/gbif.ts (kein Key, höflicher User-Agent): lookupGbif(scientificName) → match + IUCN-Kategorie + family + lifeForm→growthForm-Mapping. 5min-Cache.
  • lib/ffh-list.ts (~30 Taxa Stand η-2, BfN- + EU-92/43/EEC-Annex-IV): isFfhAnnexIv() mit Genus+Species-Binomial-Match (Subspezies-tolerant).
  • lib/plantnet.ts: Verifier-Client, returns null wenn PLANTNET_API_KEY leer. Default no-store Outbound (User-Opt-In für CC-BY-SA-Pool kommt in η-4-Lizenz-Picker).
  • lib/sensitivity.ts resolveSpeciesSensitivity() async: ruft GBIF + FFH-Match, setzt is_sensitive=false NUR bei erfolgreichem Lookup ohne Schutz-Indikation. Fail-closed bei Fehler.
  • POST /api/v1/identify: { observation_id, force_verifier? }. Owner-Check → photos→display→base64 → mana-llm-Vision → optional Pl@ntNet wenn LLM-Confidence < 0.6 oder Sensitivity-Verdacht (FFH-IV- Genera-Heuristik). Combined-Sort mit Pl@ntNet-Tiebreak → chosen. Species-upsert (fail-closed neu, sonst existing), Auto-Confirm wenn confidence ≥ 0.7 UND nicht sensitive, sonst pending. identification_proposal-Rows pro Treffer (Audit-Trail). Sensitivity-Worker async-getriggert (void-Promise).

Smoke 2026-05-17:

  • pnpm type-check ✓ (API + Web)
  • POST /api/v1/identify ohne Auth → 401 ✓
  • Fail-secure dev-stub bestätigt ✓

Offen für η-2 Live-Smoke:

  • mana-llm-Service hochfahren (docker-compose up in mana/services/mana-llm/)
  • Mit echtem Foto im DB + Display-URL erreichbar → Identify-Endpoint testen
  • Pl@ntNet-Key besorgen (operativ via Playbook §Operative Helfer)

Aktuelle Phase: η-1b — Foto-Upload an mana-media LIVE (2026-05-17)

η-1b 2026-05-17

mana-media-Delegation statt eigener Sharp-Pipeline. Begründung: mana-media (3015) macht selbst Content-Hash-Dedup + 3-Layer-Variants on-the-fly, Lokal kein Sharp-Build nötig.

  • lib/mana-media.ts — multipart-Upload mit app=herbatrium + userId + X-Service-Key. URL-Builder für original/display/thumb.
    • display = ?w=2048&format=webpPflicht für Pl@ntNet-Outbound (WebP-Encode strippt EXIF beim on-the-fly-Transform; Pflicht-Verifikation beim ersten Live-Test).
    • original bleibt Owner-only, EXIF erhalten.
  • lib/exif.ts (exifr ^7.1.3): server-side EXIF-Extraktor (taken_at, GPS-Lat/Lng/Alt, Camera-Make/Model, Brennweite, Orientation). Gespeichert in photo.exif_json als JSONB.
  • POST /api/v1/photos Pipeline:
    1. Auth + Tier-Check (beta)
    2. Multipart-Parse + MIME-Whitelist (jpeg/png/heic/webp/avif) + 30 MB Cap
    3. Owner-Check der observation_id
    4. EXIF aus Buffer extrahieren
    5. Upload an mana-media (Content-addressed, Dedup)
    6. Photo-Row mit media_id in allen 3 Storage-Keys
    7. Response: 3 URLs + EXIF-JSON
  • GET /api/v1/photos?observation_id=... Owner-only-Liste.
  • .env.example korrigiert: MANA_MEDIA_URL 3065 → 3015.

Smoke 2026-05-17:

  • pnpm type-check
  • POST /api/v1/photos ohne Auth → 401 ✓
  • GET /api/v1/photos ohne Auth → 401 ✓

Offen für η-1b Live-Smoke:

  • mana-media-Service hochfahren (lokal :3015 oder prod)
  • MANA_SERVICE_KEY in .env setzen
  • Echter Foto-Upload mit Multipart-Request (HTTPie/curl/UI)
  • EXIF-Strip im display-Layer verifizieren — falls mana-media's WebP-Transform EXIF behält, müssen wir Pre-Strip serverseitig einbauen (Sharp dann doch nötig).
  • Capture-Page um Foto-Picker erweitern (UI-Stück, η-1c-Anschluss).

Aktuelle Phase: η-1c — Capture-UI + Liste LIVE (2026-05-17)

η-1c 2026-05-17 (Web-Schleife geschlossen)

  • Dev-Auth-Stub in apps/web/src/lib/auth.ts: localStorage-User-ID, Header X-User-Id an die API. Production-JWT-Pfad folgt η-5.
  • API-Client in apps/web/src/lib/api.ts: type-safe Fetch-Wrapper mit Auth-Header. api.createObservation() + api.listMyObservations().
  • (app)/+layout.svelte Auth-Gate mit User-ID-Eingabe und Header-Navigation (Beobachten / Meine).
  • (app)/capture/+page.svelte Form mit:
    • Wissenschaftlicher Name (Pflicht) + volkstümlicher Name
    • Browser-Geolocation-Button (high-accuracy)
    • State-Picker (gesund/blühend/fruchtend/geschädigt/welkend/abgestorben/ruhend)
    • Notes
    • Submit → POST /api/v1/observations (visibility=private)
    • Erfolgs-Redirect zu /my/observations#created-{id}
  • (app)/my/observations/+page.svelte mit Visibility-Badge, Species-Anzeige (kursiv), Zustand-Label, GPS-Koord (Owner sieht exakt), Datum, Sensitive-Species-Hinweis.
  • Svelte 5 Runes-Modus in svelte.config.js (compilerOptions.runes = true).
  • Smoke 2026-05-17: pnpm check → 0 errors / 0 warnings (250 Files).

End-to-End-Live-Pfad (manueller Test, sobald Docker hochgefahren):

docker compose up -d                       # PostGIS :5449
cd apps/api && pnpm db:push                # Schema → DB
HERBATRIUM_AUTH_DEV_STUB=true \
  DATABASE_URL=postgresql://herbatrium:devpassword@localhost:5449/mana_herbatrium \
  bun run --hot src/index.ts &             # API :3101
cd ../web && pnpm dev                      # Web :3102
# → http://localhost:3102/capture, Dev-Login "dev-till" eingeben,
#   Form ausfüllen, GPS holen, speichern. Eintrag in /my/observations.

η-1b (Foto-Pipeline + mana-media) noch offen — braucht Sharp + laufenden mana-media-Service. Wird separat angegangen, wenn der Capture-Pfad ohne Foto im Live-Smoke grün ist.


Aktuelle Phase: η-1a — DB-Layer + Auth + Observations LIVE (2026-05-17)

η-1a 2026-05-17

  • Drizzle-Schema mit 7 Tabellen + PostGIS-customType geometry(Point, 4326):
    • species (fail-closed is_sensitive=true Default + IUCN/FFH-Felder)
    • specimen (3-Stufen-geo_visibility Default region_only, GIST-Index auf geo_point)
    • observation (Default visibility='private', license nullable, published_at nullable)
    • photo (3-Layer-Storage-Keys: original/display/thumb)
    • identification_proposal (Audit-Trail mana-llm + Pl@ntNet)
    • photo_original_access_log (η-4-Hard-Gate)
    • takedown_request (η-7-Pflicht)
  • Drizzle-Migration 0000 generiert (7 Tabellen, 11 Indizes, 6 FKs) → apps/api/src/db/migrations/0000_salty_franklin_storm.sql
  • JWT-Middleware inline (apps/api/src/middleware/auth.ts):
    • Bearer-JWT gegen mana-auth/api/auth/jwks (createRemoteJWKSet-Cache)
    • HERBATRIUM_AUTH_DEV_STUB=true opt-in für X-User-Id-Fallback
    • requireTier('beta') für Create-Endpoints
  • Routes:
    • POST /api/v1/observations — fail-closed species-Anlage, Specimen-Auto-Anlage (η-3 macht PostGIS-Match), Default visibility=private
    • GET /api/v1/observations/my — Owner-Filter inkl. private
    • /readyz aktiviert mit DB-Ping (503 wenn DATABASE_URL fehlt)
  • Verifikation 2026-05-17:
    • pnpm type-check ✓ (kein Fehler)
    • bun src/index.ts startet
    • /healthz 200, /readyz 503 ohne DB (klare Reason)
    • /api/v1/observations/my ohne Auth → 401 mit Detail
    • X-User-Id ohne dev-stub-Flag → 401 (fail-secure bestätigt)

Offen für η-1b/c: Foto-Upload-Pipeline (3-Layer-Resize mit Sharp, mana-media-Integration), Capture-UI, Observation+Photo-Bundle-Endpoint, DB-Live-Smoke (braucht Docker hochgefahren).


Aktuelle Phase: η-0 — Repo-Skelett LIVE (2026-05-17)

Erledigt 2026-05-17

Skelett angelegt + verifiziert:

  • Repo-Struktur Code/herbatrium/ (29 Files)
  • Workspace-Root (package.json, pnpm-workspace, turbo, tsconfig.base, Prettier, .gitignore, .npmrc, .env.example)
  • app-manifest.json mit den 4 neuen Share-Types (mana/photo, mana/geo-point, mana/plant-observation, mana/plant-specimen) + 3 Tools mit Beschreibung → validiert gegen @mana/shared-share-protocol@0.4.0 (2 shares, 2 accepts, 3 tools, search: yes)
  • docker-compose.yml mit postgis/postgis:16-3.4 auf Port 5449, PostGIS-Extension via init-postgis.sql
  • apps/api/ Hono+Bun mit /healthz, /readyz, /healthz/details, /.well-known/mana-app.jsonalle 4 Smoke-Endpoints grün (Bun-Runtime, Port 3101)
  • apps/web/ SvelteKit 2 + Svelte 5 + Tailwind v4 + Herbarium-Theme, Landing-Page mit Phasen-Plan-Anzeige → pnpm check: 0 errors / 0 warnings (242 Files)
  • CLAUDE.md, README.md, dieser STATUS.md
  • CI-Workflow .github/workflows/ci.yml

Live deployed:

  • Forgejo-Repo: https://git.mana.how/till/herbatrium (public, default-branch main, Initial-Commit gepusht 2026-05-17)
  • Verdaccio: @mana/shared-share-protocol@0.4.0 published auf npm.mana.how mit den 4 neuen Share-Types (Underscore→Dash- Rename: Manifest-Validator erzwingt Kebab-Case nach ^mana/[a-z][a-z0-9-]+$)
  • mana/-Repo: Commit 8b0dc34 mit Share-Protocol-Erweiterung + Playbook + ECOSYSTEM/PORTS-Updates, gepusht zu main

Cross-Repo (mana/-Repo)

  • @mana/shared-share-protocol@0.4.0 (von 0.2.0 → 0.3.0 → 0.4.0 nach Kebab-Case-Korrektur)
  • 4 neue Share-Types in packages/shared-share-protocol/src/types/: photo.ts, geo-point.ts, plant-observation.ts, plant-specimen.ts mit Zod-Schemas + JSON-Schema-Generierung
  • 66 Tests grün, type-check grün, 19 JSON-Schemas generiert (meal nachträglich in den Generator nachgezogen, war Lücke)

Verifikation η-0 (live ausgeführt 2026-05-17)

pnpm install                                # 150 packages, gegen npm.mana.how
pnpm validate-manifest                      # → ✓ 2 shares, 2 accepts, 3 tools, search: yes
cd apps/api && bun run src/index.ts &       # listening on :3101
curl http://localhost:3101/healthz          # → {"status":"ok","app":"herbatrium"}
curl http://localhost:3101/readyz           # → {"status":"ready","db":"not-wired-yet"}
curl http://localhost:3101/healthz/details  # → {"app":"herbatrium","version":"0.0.1","phase":"η-0"}
curl http://localhost:3101/.well-known/mana-app.json  # → vollständiges Manifest
cd apps/web && pnpm check                   # → 0 errors / 0 warnings (242 Files)

Docker compose nicht verifiziert — Docker Desktop lief beim Smoke nicht. PostGIS-Image wird beim ersten docker compose up -d gezogen, DB ist in η-0 ohnehin nicht verdrahtet (Drizzle-Schema folgt η-1).

Nächste Schritte

Vor η-1

  1. Pl@ntNet-Citizen-Science-Account anlegen (Antrag-Vorlage in Playbook §„Operative Helfer").
  2. Pl@ntNet-DPA bei AGPV anfordern (E-Mail-Template in Playbook).
  3. Compliance-Re-Audit gegen v0.2-Plan (optional vor η-1, Pflicht vor η-4 Public-Gang).
  4. mana-auth App-Registrierung für herbatrium (sso-origins.ts + apps-Tabelle + Service-Key — wenn η-1 das erste Mal Login braucht).

η-1 — Solo-Upload (geplant)

  • PostGIS-Schema mit allen Tabellen aus dem Playbook (species, specimen, observation, photo, identification_proposal, photo_original_access_log, takedown_request)
  • 3-Layer-Foto-Upload gegen mana-media
  • POST/GET CRUD für Observations
  • JWT-Middleware (JWKS-Cache aus mana-auth)
  • UI: /capture mit File-Input + Browser-Geolocation, /my/observations
  • Keine Klassifikation, keine Karte, keine Public-Sicht.

Architektur-Hard-Gates (Live-Voraussetzungen)

Gate Phase Status
photo_original_access_log läuft η-4 offen
is_sensitive-Worker fail-closed live η-4 offen
Lizenz-Picker beim Publish η-4 offen
Pl@ntNet sendet nur display-Layer (Netz-Trace) η-2 offen
Take-Down-UI + Auto-is_sensitive η-7 offen
legal/+page.svelte-AGB η-7 offen
Pl@ntNet-DPA unterschrieben vor erstem Live-Identify-Call offen
Compliance-Re-Audit grün vor η-0-Live-Deploy offen

Stand-Schema-Version

  • @mana/shared-share-protocol: 0.4.0 live auf npm.mana.how mit 4 neuen Types (mana/photo, mana/geo-point, mana/plant-observation, mana/plant-specimen). Kebab-Case-Naming wegen Manifest-Validator- Regex ^mana/[a-z][a-z0-9-]+$.
  • Manifest-schema_version: 0.1 (kein Bump nötig, neue Types sind additiv-rückwärtskompatibel)