Projekt P-5050 · Kunde K-1050 · Dok-Nr. P-5050-K1a · Stand 27.10.2026, 13:48 Uhr

Konzept im ÜberblickWas Bululu kann

Kapitel 05 von 16

Startseite, Jobsuche und Stellen

Der erste Bauabschnitt: die öffentlichen Seiten, die jeder Besucher ohne Konto sieht, also Startseite, Trefferliste mit Umkreissuche, Stellenanzeige und Fehlerseiten.

  • Lesezeit rund 42 Min.
  • Für Bewerber, Alle

Auf einen Blick

Das Wichtigste aus diesem Kapitel in einer Minute. Alle Einzelheiten stehen darunter.

  • Startseite, Jobsuche und Stellenanzeige zuerstDieser Abschnitt legt das Fundament: die Startseite, die Suche mit Umkreis, die Trefferliste und die Stellenseite mit Teilen und Drucken.
  • Suche nach Beruf, Ort und UmkreisBewerber suchen nach „Was“ und „Wo“, der Umkreis steht anfangs auf 25 km. Filter für Fachbereich, Beschäftigung und Deutschkenntnisse engen die Treffer ein.
  • „Passt das?“ auf jeder StellenanzeigeOben in jeder Anzeige zeigen vier Kacheln das Wichtigste auf einen Blick: Ort, Deutschkenntnisse, Beschäftigung sowie Gehalt oder Start.
  • Nichts zeigen, was es noch nicht gibtKnöpfe wie „Lebenslauf hinterlegen“ oder „Anzeige melden“ erscheinen erst, wenn die Funktion dahinter fertig ist, statt ins Leere zu führen.
  • Aufrufe zählen ohne Cookies und IPBululu zählt ab dem ersten Tag, wie oft eine Stelle aufgerufen wird und woher der Besuch kam. Das dient später der Statistik für Arbeitgeber.
25kmUmkreis, mit dem die Suche startet
20Stellenje Seite in der Trefferliste
3Top-Stellenhöchstens auf der Startseite

Stand 22.10.2026, Nomad; ergänzt 26.10.. Grundlage: PLAN-BACKEND Fassung 5 (Architektur, Bauordnung, Listen, Zustände, Laden, Sicherheit, Schnittstellen, Zeitplan 1a), USABILITY-KONZEPT 3.1 bis 3.3, Zustandsseiten, Teil A und B, TEXTE-UND-MAILS 1.1 bis 1.3, 2, 4.3, 4.4, 5.4, 7.4, FUNKTIONEN-CHECK (Matrix Besucher), Entwurf B (index.html, jobs.html, stelle.html, assets/style.css 1.473 Z., assets/app.js 1.593 Z., gemeinsam/).

Umfang 1a laut Zeitplan: Betrieb, Bausteine aus B, Startseite, Suche mit Umkreis, Jobs, Stelle mit Teilen und Druckansicht, Zustands- und Sonderseiten, Zählung der Aufrufe ab Tag 1 mit Quelle, Felder für fremde Kennung und Herkunft an Stellen und Firmen, Mail-Dienst mit Warteschlange und Protokoll als Fundament (ohne Vorlagen, 26.10.), Felder Befristung und Paket, leere Tabelle für Vorab-Fragen des Betriebs. Nicht in 1a (nur verlinkt, Ziel liefert 1b/2a): Fachbereiche, Firmen, Für Arbeitgeber, Merkliste-Seite, Job-Alarm-Seite, Anmelden, Bewerben-Fenster, Recht, Kontakt. Links darauf zeigen in 1a auf die späteren Adressen; bis dahin antwortet dort die 404-Seite (Annahme A1).

Fundstellen: B:index.html:100 = Datei und Zeile in entwurf-b/; css:497 = assets/style.css; js:693 = assets/app.js; T oeffentlich.suche.was = Textschlüssel aus TEXTE-UND-MAILS; U 3.2 = Usability-Bildschirm.

1

Seiten

Wie Seitenkopf, Startseite, Trefferliste, Stellenanzeige und Fehlerseiten von oben nach unten aufgebaut sind, am Handy und am Computer.

Adressen: [locale] = de, en, tr (ar ab 2b). / leitet nach Browsersprache bzw. Cookie „sprache“ auf /de/ usw. (proxy.ts, U B.7). Alle öffentlichen Seiten liegen in app/[locale]/(oeffentlich)/. Der Seitenrahmen (Kopf, Kompaktkopf, Handy-Menü, Fußzeile, Cookie-Hinweis, Toast-Behälter, Ladebalken) kommt aus app/[locale]/layout.tsx und steht auf jeder Seite; er wird je Seite nicht wiederholt.

1.1 Seitenrahmen (jede Seite)

Von oben nach unten, Handy und Desktop gleich im Quelltext:

BausteinB-FundstelleHinweis
Sprungmarke „Zum Inhalt springen“index.html:59T gemeinsam.kopf.zumInhalt
Kopf: Logo · Jobs · Für Arbeitgeber · Sprache · Anmelden (orange) · Stelle inserieren (grün) · Burgerindex.html:60–70, css:111–166, 1340–1346, 1419„Anmelden“ bleibt orange. Handy (<900 px): Logo · Merkliste-Herz · Sprache · Burger; Anmelden und Inserieren wandern ins Menü (css:153, U 3.1). Konto-Knopf erst ab 2a. Sprachwahl nur freigegebene Sprachen (3 statt 8 in B); angemeldet schreibt die Sprachwahl zusätzlich konten.sprache (2a), damit Mails derselben Sprache folgen, eine zweite Sprachwahl in den Einstellungen gibt es nicht.
Handy-Menü: Jobs · Für Arbeitgeber · Kontakt · Merkliste · Anmelden · Stelle inserierenindex.html:71–79, js:306 menueSchaltenbody.menue-offen bleibt (U A.1)
Kompaktkopf beim Scrollen: Logo · Suchzeile Was/Wo/Umkreis/Suchen · Anmelden · Sprache · Burgerindex.html:81–96, css:1070–1105, js:1525–1593Suchzeile = Baustein Suchleiste in Variante „kompakt“
Hauptinhalt <main id="inhalt">index.html:97
Fußzeile: Marke + Telefon + Sozial-Links · Für Bewerber · Für Arbeitgeber · bululu · Rechtliches (Impressum · Datenschutz · Nutzungsbedingungen · AGB; Nutzungsbedingungen neu 27.10., T gemeinsam.fuss.nutzungsbedingungen) · Copyright · Made-by-Nomadindex.html:321–391, css:423–442, 857„Job-Pakete“ (index.html:350) und „Firmenidentität“ (:368) entfallen (Plan Menüs). Sozial-Links als reine Links; sie erscheinen erst, wenn die Adressen in einstellungen.betreiber stehen, bis dahin fehlen die Symbole (D-9, entschieden 26.10.; Annahme A2). „Lebenslauf hinterlegen“ fehlt bis Abschnitt 2b (D-10, entschieden 26.10.; A3). „Cookie-Einstellungen“ entfällt (D-8, entschieden 26.10.): Rechtliches hat drei Links.
Toast-Behälter (eine Live-Region)index.html:392, js:27 toastnur Behälter aria-live, nicht je Toast (U A.1)
Cookie-Hinweisjs:1419–1469, css:1237–1254nur „notwendige“: ein Satz (T gemeinsam.cookies.text), Knopf „Verstanden“, Textlink „Mehr dazu“ (Datenschutz); kein Schalter, kein Fenster mit Auswahl, kein Fußzeilen-Knopf „Cookie-Einstellungen“. Der Hinweis erscheint einmal und nach Löschen des Gerätespeichers erneut. Schlüssel gemeinsam.cookies.notwendig und .speichern bleiben ungenutzt.
Ladebalken oben (ab 0,3 s), Logo-Intro nur beim ersten Besuch von außenindex.html:56–57, css:1431–1451Plan „Laden und Rückmeldung“, U A.2

1.2 Startseite

Adresse /[locale] · Datei app/[locale]/(oeffentlich)/page.tsx (nur Reihenfolge der Bereiche, ~40 Z.) · Bereiche in _start/.

Reihenfolge Handy = Desktop (ein Quelltext, Umbruch per CSS). Die Suchkarte steht im Quelltext direkt nach der h1 (U A.2).

NrBereichBausteineB-FundstelleDaten
1Heroh1 „Einfach bewerben, Job finden.“, Bühne (hero-b.webp), Suchkarte = Suchleiste Variante „gross“ (Was · Wo · Umkreis Standard 25 km · „Jobs finden“), „Oft gesucht:“ 4 Chipsindex.html:100–140, css:176–213, 1149–1178, 1354–1366, 1466Chips aus _start/hero/chips.ts (feste Liste: Schweißer, Elektroniker, Werkstoffprüfer, Ausbildung) · „Job finden.“ in dunkler Schrift mit grünem Markerstreifen
2Einstiegezwei Karten „Ich suche einen Job“ (→ /jobs) und „Ich suche Mitarbeiter“ (→ /fuer-arbeitgeber), Zitat-Zeileindex.html:143–162, css:260–293, 1179–1213, 1415–1418Text :150 „Ganz ohne Konto.“ ersetzt (U 3.1)
3Top-Stellen + Neu dazugekommenKopf mit „Alle N Stellen ansehen“ (N nur echt), Raster top-raster aus Stellenkarte Variante „top“ (bis 3), h3 „Neu dazugekommen“, jobliste aus Stellenkarte Variante „zeile“ (6)index.html:165–179, css:294–336, js:405–435 startseite, js:224 jobZeile, js:234 topKarte_start/top-stellen/daten.ts, _start/neueste-stellen/daten.ts über fachlich/stelle/abfragen.ts
4Fachbereichekat-raster, Kachel mit Anzahl; ohne Stellen „Job-Alarm“ statt Zahl; höchstens 8, „Alle Fachbereiche“ → /fachbereicheindex.html:182–196, css:337–349, js:405ff (data-kategorien)fachlich/fachbereich/anzahl.ts
5Werbefläche Start (Billboard 970×250, Handy 320×100)Werbefläche Variante „start“index.html:199–212, css:350–366, 1013–1025, 1125–1132, gemeinsam/werbungZum Start: ohne Kampagne zeigt die Fläche das Platzhalter-Motiv von B mit „Hier werben“ (→ /fuer-arbeitgeber#werbung). Kampagnen-Tabelle erst Abschnitt 3 (Platzhalter).
6So einfach geht es3 Schritte; der Fuß „Lebenslauf hinterlegen“ fehlt bis Abschnitt 2bindex.html:215–236, css:367–376Texte T oeffentlich.start.schritt1–3; Satz :222 ersetzt; Fuß-Knopf erst mit dem Lebenslauf-Baukasten 2b (D-10, entschieden 26.10.; Annahme A3)
7Für ArbeitgeberTeaser mit Bild, 3 Haken, Textlink mit Pfeil „Stelle kostenlos inserieren →“ (→ /fuer-arbeitgeber#inserieren; a.textlink, kein Knopf); darunter Firmen-Raster „Arbeitgeber auf bululu“index.html:239–266, css:377–396, js:436 firmenKachelnKnopf wird Textlink (D-3, entschieden 26.10.: drittes Ziel „Arbeitgeber“ auf der Seite neben Kopf-Knopf und Einstiegskarte, Reizüberflutung S-02); Stil textlink aus B (css, vorhandene Werte). „Job-Pakete ansehen“ (:257) raus. Raster nur mit ≥4 freigegebenen Logos, sonst fällt es weg (U 3.1); zum Start also weg.
8VertrauenBox mit Satz T oeffentlich.start.vertrauen, Silhouette, Telefonindex.html:269–294, css:397–411Zahlenreihe (:276–280) raus; „Seit {jahr}“ nur wenn belegt
9Job-AlarmHinweis mit Formular (Beruf, E-Mail, „Job-Alarm starten“)index.html:297–318, css:412–422, js:446 alarmFormularIn 1a nur die Ansicht; das Absenden (Alarm anlegen, Mail B16) baut 1b. Bis dahin leitet der Knopf auf /job-alarm?q=… (Annahme A4).

Karte der Region (js:465 karteZeichnen, css:230–259): in B nicht auf index.html eingebunden (kein data-karte in index.html), entfällt (U 3.1 Bedingung nicht erfüllt).

1.3 Jobs (Trefferliste)

Adresse /[locale]/jobs?q=&ort=&umkreis=&fachbereich=&art=&deutsch=&datum=&ho=&sort=&seite= · Datei app/[locale]/(oeffentlich)/jobs/page.tsx · Teile in jobs/_treffer/. Parameter-Namen wie B (js:519–527: q, ort, umkreis, kat, art, datum, ho, deutsch, sort), kat wird fachbereich, dazu seite (Plan: „Mehr Stellen laden“ ist echter Link auf Seite 2).

Handy, von oben: Kompaktkopf mit Suchzeile (Rahmen) · Ergebnis-Satz h1 · Werkzeugleiste (Filter-Knopf mit Zahl, Sortierung) · aktive Filter-Chips · Liste · „Mehr Stellen laden“ · Filter-Blatt (Dialog, auf Klick). Desktop: Suchleiste in voller Breite · dreispaltig Filter links (offen, erste Gruppe „Ort“ mit den Stadt-Chips „In Ihrer Nähe“) · Liste · Halfpage-Werbung rechts. Die Chips stehen nicht mehr als eigene Zeile unter der Suche (D-4, entschieden 26.10.: zwei Steuerungen über der Liste statt vier).

NrBereichBausteineB-FundstelleHinweis
1Suchleiste (Seite)Suchleiste Variante „zeile“ (Was · Wo · Umkreis · Suchen)jobs.html:95–120, css:458–464, 1460, js:1485–1516 VorschlägeDesktop oben; am Handy versteckt, dort reicht der Kompaktkopf (U 3.2). Die Schnellfilter-Zeile (css:839–848) wandert in die Filter-Spalte (Nr. 2, D-4)
2FilterFieldsets: Ort (Stadt-Chips „In Ihrer Nähe“ aus Schnellfilter.tsx, gleiche Chips, gleiche Adressen ?ort=&umkreis=25; D-4, entschieden 26.10.; am Handy oben im Filter-Blatt), Fachbereich (mit Anzahl), Beschäftigung (4 Haken; Treffer, wenn einer der gewählten Werte in der Liste der Stelle steht, 1a W3 B), Deutschkenntnisse (3 Haken: Keine nötig / Grundkenntnisse / Gut / Sehr gut = 4 Stufen laut T; Annahme A5: 4 Haken), <details> Weitere Filter: Online seit (Radio 3), Homeoffice; Fuß (Handy): „{n} Stellen anzeigen“ + „Zurücksetzen“jobs.html:124–160, css:465–480, 736–802, 1367, js:504 filterBlattSchaltenDeutsch und Beschäftigung aus <details> heraus (U 3.2). Handy: Dialog (showModal)
3Halfpage-Werbung (nur Desktop)Werbefläche Variante „seite“ (300×600)jobs.html:161–169, css:1016
4Werkzeugleisteh1 Ergebnis-Satz (T oeffentlich.suche.ergebnis*), Filter-Knopf (Handy), Sortierung (Passend · Neueste · Nächste nur mit Ort)jobs.html:174–184, css:481–488, js:588h1 tabindex=-1, nicht selbst Live-Region; Zahl als höfliche Meldung (U A.2)
5Aktive FilterChips mit „Filter „{name}“ entfernen“jobs.html:185, css:486, js:592ff
6Liste<ol class="jobliste"> aus Stellenkarte Variante „zeile“ (Marke Neu/Top · Firma · Titel h2 · Ort · Entfernung „{n} km“ nur, wenn ein Ort gesucht wurde · Art · Deutsch · vor n Tagen · Herz); nach Treffer 4 Werbekarte (Variante „liste“), nach Treffer 10 Textanzeige (Variante „suche“); 20 je Seitejobs.html:186, js:515–691 trefferliste, js:612–618, js:240 anzeigeNativ, js:249 anzeigeText, css:318–336Hinweis-Satz jobs.html:187 entfällt. Werbekarte ohne Herz und ohne Monogramm-Kreis (U 3.2)
7Mehr Stellen laden„{gezeigt} von {gesamt} Stellen“ + Knopf, der ein <a data-kein-ziel="1"> ist; nach Laden Fokus auf ersten neuen Titeljs:618, 677, css:1255–1258
8LeerBaustein Leerzustand: Satz T oeffentlich.leer.treffer, „Umkreis auf 50 km erweitern“ (primär, nur wenn Ort und Umkreis < 100), „Job-Alarm für diese Suche anlegen“ (Rand, → /job-alarm?q&ort&umkreis), „Alle Filter zurücksetzen“ nur bei aktiven Filtern; bei 100 km T oeffentlich.leer.trefferMaxUmkreisjs:603–609, css:489–495

1.4 Stelle

Adresse /[locale]/stelle/[adresse] mit lesbarer Adresse schweisser-duisburg-a7k3 (Titel-Ort-Kurzkennung; die Kurzkennung entscheidet, Titelteil darf abweichen und leitet dann auf die richtige Adresse um) · Datei app/[locale]/(oeffentlich)/stelle/[adresse]/page.tsx · Teile in stelle/[adresse]/_stelle/.

Handy, von oben: Brotkrumen · Kopf (Firma, Titel h1, Marken) · „Passt das?“-Reihe (4 Kacheln) · Merken + Teilen · Klebeleiste unten fest mit „Jetzt bewerben“ (im Quelltext direkt nach dem Kopf) · gelbe Zeile „angefangen“ (erst 2a) · Einleitung · Aufgaben · Das brauchen Sie (+ Gut, aber nicht nötig) · Das bietet die Firma · Bewerben-Box · Arbeitgeber-Kasten · Eckdaten als <details> · Werbefläche 300×250 · Ähnliche Stellen (3) · leise: Drucken · Anzeige melden. Desktop: zweispaltig (stelle-layout): links Kopf mit Aktionen (Bewerben oben rechts im Kopf, Merken, Teilen-Ausklapp) und Text; rechts Seitenleiste Arbeitgeber · Eckdaten · Werbung; Klebeleiste ausgeblendet (css:531).

NrBereichBausteineB-FundstelleHinweis
1Brotkrumen Jobs › Fachbereichbrotkrumencss:454–456 (Markup in B nur auf Unterseiten, Stelle hat keine): neu nach MusterT oeffentlich.stelle.brotJobs
2Kopfstelle-kopf: Monogramm/Logo, Marken, Firma, h1, metajs:712–714, css:497–502metaListe (js:218) wird durch die „Passt das?“-Reihe ergänzt
3Passt das?neue <dl> mit Ort · Deutsch · Beschäftigung · Gehalt oder StartU 3.3, T oeffentlich.stelle.passt*neues CSS beim Baustein (vorhandene Werte), kein neuer Farbwert; bleibt bei vier Kacheln (F2 A, 26.10.): Beschäftigung als Komma-Liste „Vollzeit, Teilzeit“, Befristung steht nur in den Eckdaten (Nr. 9)
4Aktionen„Jetzt bewerben“ (Desktop im Kopf; in 1a öffnet er noch kein Fenster, siehe Teil 4), Herz Merken, Teilen (Handy: navigator.share; Desktop: Ausklapp WhatsApp · LinkedIn · Facebook · Xing · Link kopieren)js:715–723, 744–750, css:524–527Satz js:715 entfällt; Drucken aus dem Kopf ans Seitenende
5Klebeleiste (Handy)nur „Jetzt bewerben“; Zustände „Beworben am … · Stand ansehen“ (2a), „Zur Bewerbung beim Betrieb“ (Karriereseite)stelle.html:98–101, css:531–533, 746ffDruck-Knopf :99 entfällt
6TextEinleitung, h2 Ihre Aufgaben, h2 Das brauchen Sie (h3 Gut, aber nicht nötig), h2 Das bietet die Firmajs:725–727, css:503–510Feldnamen aus Teil 3
7Bewerben-BoxT oeffentlich.stelle.boxTitel/boxText + Knopfjs:728, css:511–514
8Arbeitgeber-KastenLogo (nur mit dateien.zustand geprueft, sonst Monogramm), Name, Ort, „Zur Firma“ (→ /firmen/[adresse], 1b), Ansprechperson mit Name, Telefon nur wenn die Person es unter Team freigegeben hat (firmen_nutzer.telefon_in_anzeigen; Rückfall-Felder siehe Teil 3 stellen.ansprechperson_id)js:730–732, css:515–519zweiter Link „Alle Stellen dieser Firma“ entfällt
9Eckdaten<dl class="fakten">: Beschäftigung (Komma-Liste aller Werte), Ort mit PLZ, Fachbereich, Deutsch, Homeoffice, Gehalt, Start, Befristung, Online seit; Handy <details> „Alle Eckdaten“js:733, css:520–523Zeile „Befristung“ nur, wenn stellen.befristet gesetzt ist: „Befristet bis {datum}“ (mit befristet_bis), „Befristet“ (ohne Datum) oder „Unbefristet“ (T oeffentlich.stelle.ecBefristung, .befristetBis, .befristet, .unbefristet, T 7.9; F2 A, 26.10.)
10WerbungWerbefläche Variante „stelle“ (300×250)js:734–735
11Ähnliche Stellen3 × Stellenkarte „zeile“, aehnliche spalten-n; Abschnitt fehlt ganz, wenn keinejs:708, 737–739, css:528–530gleicher Fachbereich, Top zuerst, dann neueste, dazu Nähe (Plan Listen)
12Seitenende leise„Drucken“ (window.print), „Anzeige melden“js:744, U 3.3„Anzeige melden“ kommt mit Abschnitt 3 (Tabelle meldungen, Mail V03); in 1a fehlt der Knopf, weil nichts gezeigt wird, was es noch nicht gibt (Annahme A6).
13Druckansicht@media print aus Bcss:820–838, 1060, 1104, 1132Kopf, Klebeleiste, Werbung, Ähnliche, Fußzeile ausgeblendet; Seitentitel „{Titel} · {Firma} · bululu“
14Google for Jobs (JSON-LD)fachlich/stelle/google.tsPlan ArchitekturAusgabe in 1a vorbereitet, Freigabe der Sitemap in 1b; „wartet auf Prüfung“ ohne JSON-LD. employmentType als Liste aus beschaeftigungsart (FULL_TIME, PART_TIME, …), dazu TEMPORARY bei befristet = true; die url trägt ?von=google (Quelle der Aufrufe, Teil 3)

Seitentitel: „{Stellentitel} · {Firma} · bululu“ (U A.1). Übersetzte Anzeige (tr/en): übersetzter Titel groß, deutscher Titel klein mit lang="de", Hinweis „automatisch übersetzt“ mit Umschalter an Ort und Stelle; fehlt die Übersetzung, deutscher Text mit Hinweis (U B.7). Die Übersetzung selbst ist die Hintergrundaufgabe aus Karte 2a 1.18 (Paket 2a.11a, H143; Umschalter H144); in 1a zeigt die Seite in jeder Sprache den deutschen Text mit dem Hinweis (Annahme A7).

1.5 Zustands- und Sonderseiten

FallAdresse / DateiAufbauTextschlüssel
404 allgemeinapp/[locale]/not-found.tsxRahmen · h1 · Satz · Suchleiste Variante „zeile“ · „Zur Startseite“fehler.404.titel, .text
Stelle nicht mehr verfügbar (abgelaufen = pausiert, beendet, Nachbessern, gesperrt, gelöscht, fremde Firma gesperrt)stelle/[adresse]/page.tsx liefert Status 410 mit Ansicht _stelle/NichtVerfuegbar.tsxh1 · Satz · Ähnliche Stellen (3) · Knopf „Job-Alarm für {fachbereich} anlegen“ (→ /job-alarm?fachbereich=)fehler.404.stelle, .stelleText; bei gesperrter Firma fehler.gesperrt.firmaBewerber; B-Vorbild js:697–699
Fehler 500app/[locale]/error.tsx und app/global-error.tsxh1 · Satz · „Erneut versuchen“ · „Zur Startseite“ · Fehlernummer kleinfehler.500.titel, .text, .code
Wartungpublic/wartung.html (statisch, vom Webserver ausgeliefert, wenn die App nicht antwortet) in de/en/tr untereinanderh1 · Satz · Telefonfehler.wartung.*
Bereich lädtBaustein Platzhalter (weiße Flächen mit dünnem Rand, ruhig pulsierend, „Bereich wird geladen“)je _start/*-Bereich eine loading-Fassung über Suspensekein Text außer Vorlese-Satz
Laden scheitertBaustein Ladefehler: Satz + „Erneut versuchen“je Bereich error boundaryfehler.netz.allgemein
LeerBaustein Leerzustand (Satz + höchstens zwei Knöpfe)oeffentlich.leer.*
Keine Rechte, Sitzung abgelaufenerst 2a (es gibt in 1a keine Anmeldung); Dateien vorgesehen unter bausteine/sonderseiten/fehler.rechte., fehler.sitzung.
Sprachhinweis erster BesuchZeile über dem Kopf „Bu sayfa Türkçe de var …“ nur, wenn Browsersprache ≠ Seitensprache und freigegeben; „✕“ merkt sich das im Gerätespeicherbausteine/seitenrahmen/SprachHinweis.tsxSchlüssel fehlen in T, Teil 7
2

Bausteinkatalog 1a

Welche wiederverwendbaren Teile gebaut werden, damit jede Seite genau wie im abgestimmten Entwurf aussieht.

Dieser Abschnitt beschreibt nur die technische Umsetzung. Mit dem Schalter „Technische Angaben“ blenden Sie ihn ein.

Regel (Plan Bauordnung): Was zwei Seiten brauchen, liegt in bausteine/, fachlich/ oder dienste/; was nur eine Seite braucht, liegt bei ihr in _start/, jobs/_treffer/, stelle/[adresse]/_stelle/. CSS von B bleibt unverändert in stile/b/ (style.css, vorschlaege.css, werbung.css; konfigurator.css erst 1b/3). Neues CSS nur beim Baustein (bausteine/<name>/<name>.css) mit vorhandenen Werten. Die React-Bausteine erzeugen exakt das HTML von B (gleiche Klassen, gleiche Verschachtelung); die Bedienung aus app.js wird je Baustein neu geschrieben, nichts wird als Ganzes übernommen. Jeder Knopf oder Link, der in Teil 4 einer Karte eine H-Nummer hat, trägt zusätzlich die stabile Kennung data-h="H<nummer>" (Plan Bauordnung, 26.10.); sie ändert das Aussehen nicht.

2.1 Gemeinsam genutzt: bausteine/

Baustein (Ordner)DateienAus B: HTMLAus B: CSS-KlassenAus B: app.jsBenutzt von
seitenrahmen/Kopf.tsx, KompaktKopf.tsx (Client, Scroll-Logik), HandyMenue.tsx (Client), Fusszeile.tsx, SprachWahl.tsx (Client), SprachHinweis.tsx (Client), CookieHinweis.tsx (Client), Toasts.tsx (Client), Ladebalken.tsx (Client), Logo.tsx, LogoIntro.tsxindex.html:56–96, 321–392kopfzeile*, hauptnav, kopf-rechts, sprache*, ausklapp, flagge, lang-, btn-anmelden, btn-inserieren, icon-btn, burger, mobilmenue, kompaktkopf, mitsuche*, fuss*, sozial, tel-link, badge-nomad, ns-badge (Inline-CSS index.html:18–53 wandert nach seitenrahmen.css), toast-ort, toast, cookie*, fuss-knopf, logo*, lb-*, logo-intro259 ausklappZu, 306 menueSchalten, 319 kopfRand, 1419–1469 cookieHinweis, 1525–1593 Kompaktkopf, 27 toastjede Seite (layout.tsx)
suche/Suchleiste.tsx (Varianten gross · zeile · kompakt; Felder Was, Wo, Umkreis, Knopf), Vorschlaege.tsx (Client, Combobox-Muster), Schnellfilter.tsx (Stadt-Chips, ab 26.10. als Gruppe „Ort“ in der Filter-Spalte, D-4), suche.css (nur falls nötig)index.html:110–138 (gross), jobs.html:97–118 (zeile), index.html:84–89 (kompakt)suchkarte, suche, feld, feld--was/wo/umkreis, eingabe, suche__beliebt, mitsuche__*, schnellfilter*, vorschlaege.css komplett1485–1516 (Anbindung), gemeinsam/suche/vorschlaege.js 806 Z. (Vorschlagslogik wird neu in fachlich/suche/vorschlaege.ts, Bedienung in Vorschlaege.tsx)Start, Jobs, Kompaktkopf, 404
stellenkarte/Stellenkarte.tsx (Varianten zeile · top · merkliste), Marken.tsx (Neu/Top), Monogramm.tsx (Logo oder Kürzel), MetaListe.tsx (Ort · Entfernung „{n} km“ nur mit gesuchtem Ort, A28 · Art als Komma-Liste · Deutsch · Zeit · Homeoffice), HerzKnopf.tsx (Client)index.html:175–177 (Ziel), js-erzeugtjob, ist-top, job__marken, job__firma, marke-top, marke-neu, mono*, mono--gross, meta, top-raster, top-karte*, jobliste, herz*218 metaListe, 224 jobZeile, 234 topKarte, 80–91 firmenLogo/monogramm/monoKlasse, 92 tageText, 99 deutschStufe, 179–216 Merkliste (Lesen/Schreiben → dienste/geraetespeicher)Start, Jobs, Stelle (ähnliche), 404-Stelle; später Merkliste, Firmenprofil, Fachbereich
werbeflaeche/Werbeflaeche.tsx (Varianten start · seite · liste · suche · stelle), werbeflaeche.css (= werbung.css aus gemeinsam, unverändert)index.html:199–212, jobs.html:162–168, js:240–257ad-zone, ad-kennung, ad-banner, ad-hier, ad-platz, ad-karte*, ad-suche*, ad-nur-desktop, halfpage-huellegemeinsam/werbung/werbung.js 39 Z. (Klick-Hinweis) entfällt; Klick geht auf /fuer-arbeitgeber#werbungStart, Jobs, Stelle; ab 1b Ratgeber (Variante „ratgeber“); Firmen und Fachbereiche haben wie in B keine Fläche
leerzustand/Leerzustand.tsx (Satz, bis zwei Knöpfe, Puzzle-Grafik)js:607leer603–609Jobs, Stelle (ähnliche fehlt ganz), 404; später alle Listen
platzhalter/Platzhalter.tsx (Skelett-Flächen), Ladefehler.tsx („Erneut versuchen“)neu (Plan „Laden und Rückmeldung“)neues platzhalter.css mit Werten aus B (--linie, --r-l, Puls-Animation unter prefers-reduced-motion gedämpft)alle Seiten mit Bereichen, die für sich laden
sonderseiten/Fehlerseite.tsx (404/500/Rechte/Sitzung gemeinsamer Aufbau: h1, Satz, Knöpfe), Wartung (statisch)js:697 nicht-gefundennicht-gefunden, u-kopf, leadnot-found.tsx, error.tsx, Stelle nicht verfügbar; 2a Rechte/Sitzung
zaehler/imBrowser.ts (sendBeacon, bricht bei navigator.webdriver ab; liest den Adress-Parameter von und gibt ihn als Quelle mit, Teil 3 stellen_aufrufe_tag)neu (U C.4)Suchleiste, Stelle, später Bewerben-Fenster
brotkrumen/Brotkrumen.tsxcss:454brotkrumenStelle; später Unterseiten

2.2 Fachlich und Dienste (gemeinsam)

OrdnerDateienInhalt
fachlich/stelle/abfragen.ts (topStellen(locale), neuesteStellen(n), stelleNachAdresse(adresse), aehnlicheStellen(stelle, n)), sichtbarkeit.ts (istOeffentlichSichtbar(stelle): aktiv, läuft ab oder wartet auf Prüfung; Firma nicht gesperrt), adresse.ts (adresseBilden(titel, ort, kurzkennung), adresseLesen), google.ts (JSON-LD JobPosting; nur bei aktiv/läuft ab), zustaende.ts (Zustands-Typ aus Plan Lebenslauf: entwurf, pruefung, aktiv, laeuft_ab, abgelaufen (für die Firma „pausiert“), beendet, nachbessern, gesperrt, geloescht), felder.ts (Typ Stelle, Deutschstufe, Beschäftigungsart, Bewerbungsweg, Gehaltseinheit)Regeln je Ding; keine Seite fragt die Datenbank direkt
fachlich/suche/suchen.ts (suchen(p: Suchparameter): {treffer, gesamt, seite}; Sortierung passend/neueste/nächste; Seitengröße 20), umkreis.ts (ortAufloesen(text) → {plz, name, lat, lng}; Kastenfilter + genaue Entfernung), vorschlaege.ts (Berufe aus gemeinsam/suche + Begriffe je Fachbereich; Orte aus plz-Tabelle), parameter.ts (lesen/schreiben der Adresse: q, ort, umkreis, fachbereich, art, deutsch, datum, ho, sort, seite; Werte prüfen)gleiche Suche für Job-Alarm (1b)
fachlich/fachbereich/anzahl.ts (offene Stellen je Fachbereich), liste.tsListe der Fachbereiche mit Name je Sprache
fachlich/firma/oeffentlich.ts (Firma lesend: Name, Ort, Logo nur mit geprüfter Datei, Adresse, gesperrt?), adresse.ts (firmenAdresseBilden(name, ort) mit Dubletten-Zähler, adresseLesen; Teil 3 firmen.adresse), felder.tsFirmenprofil-Seite erst 1b
fachlich/funktionen/liste.ts (Namen aller Freischaltungen), istFrei.ts (istFrei(funktion, kontext))in 1a nur „premium“ (Top-Stellen) abgefragt; Schalter-Werte aus Tabelle einstellungen
fachlich/aktion.tsder eine Weg für Server-Aktionen (Rechte, Fehler, Rückmeldung)in 1a nur Gerüst + Test, erste Aktion ist der Beacon-Zähler
dienste/db/client.ts (Drizzle), schema/ (siehe Teil 3)
dienste/zaehler/ereignisse.ts (erlaubte Namen als Typ), zaehlen.ts (zaehle(ereignis, merkmale) Upsert Tagessumme), aufrufe.ts (stellenAufruf(stelleId, quelle))ohne Cookies, ohne IP
dienste/geraetespeicher.tslesen/schreiben für Merkliste ohne Konto (Format {stellen: [{id, gemerktAm}]}, gemerktAm als ISO-Zeit; 1b sortiert danach, 2a übernimmt es ins Konto), Cookie-Wahl, Sprachhinweis-Ausblendung; einzige Stelle mit localStorage/Cookie-Zugriffeslint-Regel
dienste/mail/senden.ts (SMTP über den eigenen Nomad-Mailserver; Absender aus einstellungen.betreiber (ab 1b), in test die Nomad-Adresse; Umleitung MAIL_TEST_EMPFAENGER und Tagesgrenze MAIL_TAGESGRENZE aus Karte 0 Teil 3 und 5), warteschlange.ts (einreihen(mail) schreibt mail_warteschlange; Versand nur im Hintergrund mit Wiederholung: 3 Versuche nach 1, 10 und 60 Minuten, dann zustand fehler und Betriebsmail-Zeile), kopfzeilen.ts (List-Unsubscribe), vorlagen/rahmen.tsx (mail.rahmen aus T 6.0, Text- und HTML-Fassung, in dunklen Mailprogrammen lesbar)Fundament (26.10., aus Karte 1b hierher gezogen): in 1a ohne Vorlage und ohne Aufruf, nur Dienst und Test; 1b bringt b15 bis b17 und v04, 2a und 3 die übrigen Vorlagen. Kein Versand außerhalb der Warteschlange (eslint-Regel: senden.ts nur aus warteschlange.ts)
dienste/protokoll.tsprotokolliere(...) (Teil 3 protokoll)Fundament; ab 2a von jeder Aktion mit Protokollpflicht genutzt, ab 3 mit Trigger
dienste/geo/plz-import.ts (GeoNames-Datei → Tabelle plz, einmalig per Skript)Lizenz-Nennung im Impressum (1b)
sprachen/{de,en,tr}/gemeinsam.json (1.1), suche.json (1.2), stelle.json (1.3), start.json (7.4 Start), leer.json (2), fehler.json (4.3/4.4), status.json (5.4)je Bereich eine Datei; Verwaltung nur de (später)

2.3 Nur in 1a (seitenspezifisch)

OrtDateien
app/[locale]/layout.tsxRahmen: <html lang dir>, Schriften (next/font, Manrope + Outfit lokal), CSS-Einbindung, Kopf, KompaktKopf, HandyMenue, main, Fusszeile, CookieHinweis, Toasts, Ladebalken, SprachHinweis
app/[locale]/(oeffentlich)/page.tsxReihenfolge der 9 Bereiche
…/_start/hero/Hero.tsx, chips.ts
…/_start/einstiege/Einstiege.tsx
…/_start/top-stellen/TopStellen.tsx, daten.ts
…/_start/neueste-stellen/NeuesteStellen.tsx, daten.ts
…/_start/fachbereiche/Fachbereiche.tsx, daten.ts
…/_start/werbung/Werbung.tsx (ruft Werbeflaeche „start“)
…/_start/schritte/Schritte.tsx
…/_start/fuer-arbeitgeber/FuerArbeitgeber.tsx, FirmenRaster.tsx, daten.ts
…/_start/vertrauen/Vertrauen.tsx
…/_start/job-alarm/JobAlarmHinweis.tsx
…/jobs/page.tsxliest Parameter, ruft suchen(), ordnet Bereiche
…/jobs/_treffer/Filter.tsx (Client, Dialog am Handy), FilterFuss.tsx, Werkzeugleiste.tsx, AktiveFilter.tsx, Trefferliste.tsx (Liste + Werbeplätze), MehrLaden.tsx, Ergebnissatz.tsx, treffer.css (nur falls B-CSS nicht reicht; Erwartung: reicht); die Sortierung liegt von Anfang an in bausteine/sortierung/Sortierung.tsx (Client, Variante jobs; 1b nutzt sie für Unternehmen)
…/stelle/[adresse]/page.tsxlädt Stelle, prüft Sichtbarkeit (sonst NichtVerfuegbar mit 410), zählt Aufruf (Browser-Beacon), ordnet Bereiche, generateMetadata (Titel, Beschreibung, hreflang), JSON-LD
…/stelle/[adresse]/_stelle/StellenKopf.tsx, PasstDas.tsx, Aktionen.tsx (Client: Teilen, Merken), Klebeleiste.tsx (Client), StellenText.tsx, BewerbenBox.tsx, ArbeitgeberKasten.tsx, Eckdaten.tsx, AehnlicheStellen.tsx, Seitenende.tsx (Drucken, Client), NichtVerfuegbar.tsx, passt-das.css
app/[locale]/not-found.tsx, error.tsx, app/global-error.tsxSonderseiten
app/api/z/route.tsnimmt Beacons an (U C.4)
proxy.tsSprache erkennen, „/“ weiterleiten, Cookie „sprache“ lesen
hintergrund/index.tsStartpunkt pg-boss; in 1a die Aufgaben „stelle-zustaende-taeglich“ (aktiv → läuft ab → abgelaufen), „zaehler-aufraeumen“, „mail-versand“ (jede Minute: wartende Mails der Warteschlange senden, Tagesgrenze beachten) und „mail-aufraeumen“ (gesendete älter als 3 Monate löschen)
db/drizzle.config.ts, migrationen/0001_fundament.sql + rueckweg, musterdaten/ (aus gemeinsam/daten-neu.js: 2 Firmen, Stellen, Fachbereiche; nur Testumgebung)
pruefung/siehe Teil 6

Nicht übernommen aus B (bewusst): umschalter.js, konfigurator.js (erst 3), werbung.js, Inline-Styles in index.html (:176 h3 style, :199, :239, :269, :297 → Klassen abschnitt--ohne-oben u. ä. im Baustein-CSS, Plan verbietet Inline-Styles), meta robots noindex (nur Testadresse), Google-Fonts-Links (Schriften lokal).

3

Datenmodell 1a

Welche Angaben zu Firmen und Stellen gespeichert werden, etwa Beschäftigungsart, Deutschkenntnisse, Befristung und Ansprechperson.

Dieser Abschnitt beschreibt nur die technische Umsetzung. Mit dem Schalter „Technische Angaben“ blenden Sie ihn ein.

PostgreSQL, Drizzle, Schema in dienste/db/schema/<tabelle>.ts, Migration db/migrationen/0001_fundament.sql mit Rückweg. Kennungen sind Zufallswerte (UUID v7 als Primärschlüssel, nie fortlaufend; Plan Sicherheit). Jede Tabelle: angelegt_am, geaendert_am (timestamptz). Zeilensperre je Firma (RLS) wird in 1a nur vorbereitet; Rollen, Pools und Rechte stehen allein in Karte 0 Teil 7 (in 1a genutzt: bululu_<u>_oeffentlich liest nur sichtbare Stellen, bululu_<u>_formular schreibt Zähler und Sicherheitsprotokoll über Datenbank-Funktionen, bululu_<u>_hintergrund für den Mail-Versand); die Policies je Tabelle kommen mit 2a und 3. Alles, wonach gefiltert oder sortiert wird, ist ein echtes Feld (Plan: kein JSON für Fachliches).

3.1 Tabellen

firmen

FeldTypHinweis
iduuid PK
nametext, nicht leer
adressetext, eindeutiglesbare Adresse für /firmen/[adresse] (1b); gebildet von fachlich/firma/adresse.ts firmenAdresseBilden(name, ort): Kleinbuchstaben, Umlaute aufgelöst (ä → ae), nur a–z, 0–9 und Bindestrich, Muster name-ort, bei Dublette Zähler -2, -3; bei Umbenennung neue Adresse, die alte in firmen_adressen (Umleitung 301). Füllt 1a für Musterdaten/Import, 2a firmaRegistrieren() und profilFeldSpeichern('name') rufen dieselbe Funktion (Annahme A20)
strasse, plz, orttextplz 5 Zeichen; ort Pflicht
plz_idFK plzStandort für Anzeige; Stelle hat eigenen Standort
telefontext
logo_datei_iduuid, leer erlaubtFK dateien (Tabelle 2a; in 1a Spalte ohne FK, Platzhalter)
zustandenum firma_zustand: registriert, aktiv, gesperrt, geloeschtPlan Lebenslauf Firma
sperr_grundtext, leer
geprueftboolean, Standard falsesolange false, läuft jede veröffentlichte Stelle über die Prüfung (Karte 3 noetig.ts; in der Regel nur die erste); true setzen Freigabe, automatische Freigabe nach Frist, Prüfstufe „ohne Prüfung“ und Anlegen durch die Verwaltung
premium_bisdate, leerFreischaltung Premium (Karte 3 setzt es mit top_plaetze)
top_plaetzesmallint, Standard 3
paketenum paket: basis; Standard basisPaket-Feld je Firma. Zum Livegang nur dieser eine Wert, keine Oberfläche, keine Abrechnung; weitere Werte kommen mit dem Abrechnungsmodell nach dem Livegang als neue Enum-Werte. fachlich/funktionen/istFrei.ts liest später Paket und premium_bis gemeinsam, in 1a nur premium_bis. Rechnungsanschrift und USt-ID brauchen jetzt kein Feld (später eigene Tabelle)
paket_bisdate, leerEnde des Pakets, leer = unbegrenzt
fremde_kennungtext, leerKennung in der Quelle (Import/Schnittstelle)
herkunftenum herkunft: hand, import, schnittstelleStandard hand
lieferung_iduuid, leerFK lieferungen
Index(herkunft, fremde_kennung) eindeutig, wenn fremde_kennung gesetzt; (zustand)

stellen

FeldTypHinweis
iduuid PK
firma_iduuid FK firmen, nicht leer
kurzkennungtext(4–6), eindeutigTeil der Adresse, z. B. a7k3; aus Zufall, keine Verwechslungszeichen, nur ASCII; wird nie neu vergeben, auch nicht nach Löschung (ist später die Referenznummer bei der Bundesagentur, _berichte/recherche/bundesagentur.md Frage 6)
adressetext, eindeutigtitel-ort-kurzkennung, bei Titeländerung neu, alte Adresse in stellen_adressen (Umleitung)
titeltext, Pflicht, mit (m/w/d)
fachbereich_idFK fachbereiche, Pflicht
beschaeftigungsartbeschaeftigungsart[] (enum vollzeit, teilzeit, ausbildung, praktikum), mindestens ein Wert, GIN-IndexPflicht; Liste, weil „Vollzeit oder Teilzeit“ in Industrie und Handwerk häufig ist. Filter „oder“ (Treffer, wenn ein gewählter Wert in der Liste steht); Anzeige als Komma-Liste in MetaListe, Passt-das-Kachel und Eckdaten (F2 A); Google employmentType als Liste; Bundesagentur: je Wert ein Eintrag (HR-BA-XML, Zuordnung in _berichte/recherche/bundesagentur.md); Formular 2a: die vier Pillen erlauben Mehrfachwahl, Vollzeit vorgewählt
homeofficeboolean, Pflicht
deutschenum deutschstufe: keine, grund, gut, sehr_gutPflicht, Filter
arbeitsort, arbeits_plztext, PflichtAnzeige „Duisburg“
plz_idFK plz, PflichtStandort für Umkreis; aus arbeits_plz aufgelöst
lat, lngdoubleKopie aus plz für den Kastenfilter (kein Join in der Suche)
einleitungtext, leer2–3 Sätze
aufgabentext[]Pflicht, je Eintrag ein Punkt
brauchentext[]Pflicht („Das brauchen Sie“)
gut_nicht_noetigtext[]freiwillig
bietentext[]freiwillig
gehalt_von, gehalt_bisinteger, leer
gehalt_einheitenum: monat, stunde, jahr, leer
start_amdate, leer; start_sofort boolean„Start“ in Passt-das
befristetboolean, leerleer = keine Angabe, true = befristet, false = unbefristet. Anzeige nur in der Eckdaten-Liste (1.4 Nr. 9, F2 A), kein Filter zum Livegang (erst ab etwa 100 Stellen sinnvoll); Formularfeld 2a nach „Start“, freiwillig; Google employmentType zusätzlich TEMPORARY bei true; Bundesagentur HR-BA-XML Befristung (Zuordnung in _berichte/recherche/bundesagentur.md)
befristet_bisdate, leernur mit befristet = true; Eckdaten „Befristet bis {datum}“, ohne Datum „Befristet“
bewerbungswegenum: bululu, karriereseitePflicht
karriere_urltext, leer; Pflicht wenn karriereseitehttps-Prüfung
ansprechperson_iduuid, leer in 1aFK konten (ab 2a, Karte 2a W3/C8). Dazu dauerhaft die Felder ansprech_anrede (enum anrede: herr, frau, ohne), ansprech_vorname, ansprech_nachname (je text ≤ 30, getrennt wegen Bundesagentur), ansprech_telefon: Rückfall, wenn ansprechperson_id leer ist (Stellen aus Schnittstelle/Import, Karte 3 1.13; Musterdaten). Anzeige (1a Arbeitgeber-Kasten, Bundesagentur) liest zuerst das Konto (Name, konten.anrede, konten.telefon; Telefon nur mit firmen_nutzer.telefon_in_anzeigen), sonst die Rückfall-Felder (ansprech_telefon erscheint, wenn gefüllt). Kein Feld ansprech_telefon_frei (Annahme A21)
zustandenum stelle_zustand: entwurf, pruefung, aktiv, abgelaufen, beendet, nachbessern, gesperrt, geloeschtDer Code-Typ in zustaende.ts kennt zusätzlich laeuft_ab (aktiv und laeuft_bis <= 5 Tage), abgeleitet beim Lesen, kein Speicherwert und kein Enum-Wert in der Datenbank: Annahme A9. abgelaufen heißt für die Firma „Pausiert“ und bleibt ohne Frist verlängerbar (2a 1.11 Nr. 4, C24); geloescht nur nach dem endgültigen Löschen, wenn die Stelle hinter Bewerbungen bleiben muss (2a 1.17 Nr. 6); im Papierkorb liegt eine Stelle allein über geloescht_am, ihr Fachzustand bleibt erhalten
veroeffentlicht_amtimestamptz, leer„vor n Tagen“, „Online seit“, Datum-Filter, Marke Neu (<= 1 Tag)
laeuft_bisdate, leerveroeffentlicht + 30 Tage; leer bei Schnittstelle
topboolean, Standard falseTop-Stelle (nur wirksam mit premium_bis der Firma)
ba_beruf_idinteger, leer, IndexBeruf nach der Liste der Bundesagentur; in 1a ohne Fremdschlüssel, Tabelle ba_berufe kommt mit 2b; Formularfeld erst 2a
sozialversicherungspflichtigboolean, Standard truePflichtangabe bei der Bundesagentur; Haken im Stellenformular 2a, vorausgewählt, nur bei Ausnahmen (Minijob, Praktikum ohne Vergütung, freie Mitarbeit) abwählen
anzahl_stellensmallint, Standard 1, > 0Anzahl offener Stellen (Bundesagentur NumberToFill)
nachbessern_grund, sperr_grundtext, leer
suchtexttsvector (generiert aus titel, einleitung, aufgaben, brauchen, gut_nicht_noetig; deutsch)Volltext
fremde_kennungtext, leerz. B. COPLA-Kurzname
herkunftenum herkunftStandard hand
lieferung_iduuid, leer
Indizes(zustand, veroeffentlicht_am desc); (fachbereich_id, zustand); (firma_id, zustand); GIN suchtext; (lat, lng) für Kastenfilter (btree auf beiden reicht bei ~1.000 Stellen; PostGIS nicht nötig, Annahme A10); (herkunft, fremde_kennung) eindeutig je Firma; GIN trigram auf titel für Ähnlichkeit (pg_trgm)

stellen_adressen (alte Adressen → Stelle): alte_adresse text PK, stelle_id FK, angelegt_am. Umleitung 301.

firmen_adressen (alte Adressen → Firma, nach Umbenennung): alte_adresse text PK, firma_id FK, angelegt_am. Umleitung 301; 1b firmaNachAdresse() löst sie auf.

stellen_uebersetzungen (Platzhalter für 2a, in 1a angelegt, leer): stelle_id, sprache (en, tr, ar), titel, einleitung, aufgaben, brauchen, gut_nicht_noetig, bieten, stand (maschinell, geprueft), quelle_geaendert_am. PK (stelle_id, sprache). Karte 2a Teil 3 ergänzt zustand (wartet, fertig, fehler), versuche, fehler_text, uebersetzt_am für die Hintergrundaufgabe (2a 1.18).

stellen_fragen: id uuid PK, stelle_id FK stellen, reihenfolge smallint, frage text (≤ 200), typ enum frage_typ (ja_nein, text), ko boolean Standard false (K.-o.-Frage), pflicht boolean Standard true. Zum Livegang keine Oberfläche, weder im Stellenformular noch im Bewerben-Fenster; die Freischaltung fragen in einstellungen.funktionen steht auf false. Die Antworten (bewerbungs_antworten) legt Karte 2a Teil 3 an, den Einhängepunkt im Bewerben-Fenster nennt Karte 2a 1.3 Nr. 3. Die Rechtsfrage, welche Fragen Betriebe stellen dürfen (AGG), wird erst mit der Oberfläche fällig.

fachbereiche: id uuid PK, schluessel text eindeutig (metall, elektro, …), sortierung smallint, zustand enum (aktiv, zusammengefuehrt), zusammengefuehrt_in FK, begriffe text[] (Begriffsliste je Fachbereich für Suche und Vorschläge; deutsch), angezeigt_auf_start siehe unten; Namen und Begriffe je Sprache in fachbereiche_fassungen (fachbereich_id FK, sprache text, name text, begriffe text[]; UNIQUE (fachbereich_id, sprache); End-Check Befund 25: eine neue Sprache ist eine Zeile, keine Spalte). sprache ist überall text und wird im Code gegen einstellungen.sprachen_frei geprüft, nie ein Enum (gilt für stellen_uebersetzungen, inhalte_fassungen, konten.sprache, job_alarme.sprache), angezeigt_auf_start boolean. Liste aus Plan Kundenunterlagen 1.3 + daten-neu.js (12 Einträge; Endliste ist Teil 7).

plz: id uuid PK (Ziel aller plz_id-Fremdschlüssel in firmen, stellen, stellen_orte, konten, job_alarme; Annahme A19), plz text, ort text, lat double, lng double, bundesland text, bundesland_vam smallint (Code der Bundesagentur, beim Import aus bundesland gefüllt); UNIQUE (plz, ort); Index (plz), Index (ort lower). Quelle GeoNames DE. Dazu orte_vorschlag materialisiert: ort text, plz_erste text, lat, lng (ein Eintrag je Ortsname für „Wo?“-Vorschläge).

berufe_vorschlag: begriff text PK, fachbereich_id FK leer, ba_beruf_id integer leer (Vorschlag eines Berufs der Bundesagentur), gewicht smallint. Erstbestand aus gemeinsam/suche (Berufeliste von B) als Musterdaten, danach Verwaltung (2b+).

zaehler_tag (U C.4): tag date, ereignis text, merkmale jsonb (nur erlaubte Schlüssel laut ereignisse.ts; hier ausnahmsweise jsonb, weil nie fachlich gefiltert, nur summiert), anzahl integer; PK (tag, ereignis, merkmale). 25 Monate. suchwort_monat: monat date, wort text(40), treffer_klasse enum (null, wenig, viele), anzahl; PK (monat, wort). Nur Suchen ohne Treffer, 13 Monate. stellen_aufrufe_tag: stelle_id FK, tag date, quelle enum quelle (direkt, google, job_alarm, merkliste, folgen, geteilt, unbekannt), aufrufe integer, karriere_klicks integer; PK (stelle_id, tag, quelle). Für die Arbeitgeber-Statistik später; ohne IP, ohne Cookie. Die Quelle kommt allein aus dem Adress-Parameter von (Werte wie das Enum), den Mails und Teilen-Links tragen: Job-Alarm-Mail B15 von=job_alarm, Merklisten-Erinnerung B13 von=merkliste, Mail „Firma folgen“ von=folgen, Teilen-Baustein von=geteilt, JSON-LD-Adresse von=google; ohne Parameter „direkt“, fremder Wert „unbekannt“. Dieselbe Quelle schreibt 2a an jede Bewerbung (bewerbungen.quelle, Karte 2a Teil 3).

lieferungen: id uuid PK, art enum (import, schnittstelle, hand), quelle text, firma_id leer, begonnen_am, beendet_am, zeilen_gesamt, zeilen_ok, zeilen_fehler, zurueckgenommen_am leer. In 1a nur angelegt, erste Nutzung Abschnitt 3.

einstellungen: schluessel text PK, wert jsonb, geaendert_am, geaendert_von leer. In 1a: sprachen_frei ["de","en","tr"], funktionen {premium: true, fragen: false, …}, laufzeit_tage 30, top_plaetze_standard 3, gehalt_pflicht false.

protokoll (Schema und Schreibweg in 1a, damit 2a und 3 nur nutzen; 26.10.): id, zeitpunkt, wer (nutzer_id leer), rolle, aktion, ding_art, ding_id, firma_id, grund, details jsonb. 12 Monate. Geschrieben wird nur über dienste/protokoll.ts protokolliere({aktion, dingArt, dingId, firmaId?, grund?, details?}) in derselben Transaktion wie die Änderung (Karte 3 D18 setzt darauf den Trigger). In 1a schreibt außer dem Test niemand hinein.

mail_warteschlange (Mail-Dienst als Fundament, 26.10. aus Karte 1b hierher gezogen, damit 1b, 2a und 3 ihn nur nutzen): id, an citext, betreff, text, html, kopfzeilen jsonb (List-Unsubscribe; jsonb erlaubt, weil nie gefiltert), vorlage text, bezug_art text und bezug_id uuid leer (1b: alarm, kontaktanfrage; 2a: bewerbung, konto, firma; 3: pruefung, anfrage, kampagne), vorrang boolean Standard false, zustand enum mail_zustand (wartet, gesendet, fehler), versuche smallint, naechster_versuch_am, gesendet_am, fehler_text. Zeile 3 Monate; text und html werden 7 Tage nach dem Versand und sofort bei Kontolöschung geleert. Rückläufer-Felder (pausiert) kommen mit Abschnitt 4 (Karte 0 Teil 5). In 1a wird keine Mail erzeugt; Dienst, Warteschlange und Hintergrundlauf stehen und sind getestet.

3.2 Was später dazukommt (nur Hinweis)

konten, sitzungen, zweiter Faktor (Better Auth, 2a) · firmen_nutzer, einladungen (2a) · bewerbungen, bewerbungs_entwuerfe, nachrichten, einladungen_gespraech, dateien, einwilligungen (2a) · job_alarme, merkliste_konto (1b/2a) · meldungen, kontaktanfragen, premium_anfragen (3) · werbung_kampagnen, werbung_anfragen (3) · inhalte (Ratgeber, Rechtstexte), mailvorlagen (3), bewerbungs_antworten zu stellen_fragen (2a) · monatswerte (nach Livegang). Die 1a-Tabellen bekommen dafür keine Umbauten: Fremdschlüssel werden ergänzt, keine Spalte umbenannt.

3.3 Abfragen 1a (Signaturen in fachlich/)

  • suchen({q, ort, umkreis, fachbereich, art[], deutsch[], datum, ho, sort, seite, locale}) → {treffer: StellenKarte[], gesamt, seite, seiten, ortAufgeloest}: Sichtbarkeit (aktiv, pruefung) und Firma nicht gesperrt; Kastenfilter lat/lng ± Umkreis, danach Haversine; mit ortAufgeloest trägt jede StellenKarte entfernungKm (ganze Kilometer, Luftlinie zwischen den PLZ-Mittelpunkten; A28), sonst fehlt das Feld; Volltext + Ähnlichkeit (Titel-Treffer vor Text-Treffer); Sortierung passend = Top zuerst, dann Titel-Treffer, Nähe, Aktualität. Umkreis 0 = gleicher Ort (plz_id-Ort).
  • topStellen(n=3): top = true, Firma premium_bis >= heute, zustand aktiv, zufällig je Tag (Annahme A11: Reihenfolge nach veroeffentlicht_am desc).
  • neuesteStellen(n=6), aehnlicheStellen(stelle, n=3), stelleNachAdresse(adresse) (löst auch stellen_adressen), anzahlJeFachbereich(), gesamtAnzahlOffen().
  • Zählung: zaehle("stelle_aufruf", {stelle, quelle}), zaehle("suche", {trefferKlasse, umkreis, mitOrt}), zaehle("karriere_klick", {stelle}), zaehle("teilen", {dienst}), zaehle("drucken").
4

Handlungen der Rolle Besucher in 1a

Jeder Knopf und Link, den ein Besucher in diesem Abschnitt nutzen kann, mit seiner Wirkung und wie er geprüft wird.

Aus der Aktionen-Matrix (FUNKTIONEN-CHECK, Besucher). In 1a nur die Zeilen, deren Bildschirm in 1a liegt; die übrigen (Job-Alarm anlegen, Melden, Folgen, Kontakt, Werbung planen, Anmelden, Kennwort, Merkliste-Seite) kommen in 1b bis 3 und sind hier nur als Link-Ziel genannt. Jeder Test liegt unter pruefung/ und läuft ohne Browser (PGlite + React-Render ohne DOM-Browser; Bedienung mit Testing Library im jsdom, keine Playwright-Läufe).

Kennungen (L3, 26.10.): Jeder Knopf und Link mit einer H-Nummer (hier und in allen späteren Karten) trägt im Markup data-h="H<nummer>", z. B. data-h="H16"; steht derselbe Baustein auf mehreren Bildschirmen (Herz H6, Sprache H24), trägt er die Nummer der Zeile, die ihn beschreibt, und zusätzlich data-ort mit dem Bildschirm, wenn die Zeile mehrere nennt. Die Kennung ändert sich nie, auch wenn Wortlaut, Platz oder Baustein wechseln. _planung/knopfliste.md (aus den Teil-4-Tabellen aller Karten erzeugt) ist die Knopfliste für den Nachtlauf am Ende (Plan „Prüfen im Browser“, Karte 4 Teil 2b); der Wächter pruefung/waechter/knopfliste.ts prüft je gebautem Abschnitt, dass jede Nummer genau einmal gerendert wird und keine fremde Kennung vorkommt. Ohne Browser testbar: Testing Library findet die Knöpfe über data-h, nicht über den Text.

NrBildschirmKnopf/Link (Wortlaut T)WirkungTest (Datei · prüft)
H1Startseite„Jobs finden“ (Suchkarte)Navigation zu /jobs?q&ort&umkreis=25; Zählung „suche“ im Browserpruefung/seiten/start.test.ts · Formular hat action /[locale]/jobs, Felder q/ort/umkreis, Standard 25; pruefung/fachlich/suche/parameter.test.ts
H2StartseiteOft-gesucht-Chip/jobs?q=Schweißer bzw. ?art=ausbildungstart.test.ts · 4 Chips mit richtigen Adressen
H3StartseiteFachbereich-Kachel/jobs?fachbereich=metall; Kachel ohne Stellen → /job-alarm?fachbereich=metallstart.test.ts · Kachel mit 0 Stellen trägt Job-Alarm-Link; pruefung/fachlich/fachbereich/anzahl.test.ts
H4Startseite„Alle N Stellen ansehen“/jobs; N = gesamtAnzahlOffen, Zeile ohne Zahl, wenn 0start.test.ts · N stimmt mit Musterdaten
H5Startseite„Stellen ansehen“ / „Stelle inserieren“ (Einstiege), Textlink „Stelle kostenlos inserieren →“ (Teaser, D-3)Links auf /jobs, /fuer-arbeitgeber#inserieren; „Lebenslauf hinterlegen“ fehlt bis 2b (D-10)start.test.ts · Adressen je Sprache; Teaser-Link ist a.textlink, kein btn-*; kein Link „Lebenslauf hinterlegen“ auf der Seite
H6Startseite, Jobs, StelleHerz „Merken“Stelle im Gerätespeicher ({stellen: [{id, gemerktAm}]}); aria-pressed wechselt; Toast „Gemerkt: {titel}“ mit Link Merkliste; Kopf-Herz zähltpruefung/bausteine/stellenkarte/herz.test.ts · Klick schreibt über dienste/geraetespeicher, zweiter Klick entfernt, Zähler im Kopf
H7Startseite„Job-Alarm starten“1a: Weiterleitung auf /job-alarm?q=&mail= (1b baut das Anlegen)start.test.ts · Formular zeigt auf /job-alarm
H8JobsSuchzeile ändern + „Suchen“neue Adresse, Liste neu, h1 Ergebnis-Satz, Seite springt auf 1; mit Ort trägt jede Zeile „{n} km“ (A28)pruefung/seiten/jobs.test.ts · suchen() mit q „Schweißer“ findet auch „Schweißfachmann“ (Begriffsliste); Ergebnis-Satz je Fall (mit Ort / ohne Ort / ohne Was); mit Ort steht „{n} km“ in jeder Zeile, ohne Ort nirgends; umkreis.test.ts · Entfernung Duisburg–Oberhausen rund 10 km
H9JobsFilter setzen (Fachbereich, Art, Deutsch, Online seit, Homeoffice)Desktop sofort; Handy nach „{n} Stellen anzeigen“; Adresse trägt die Wahl; Chips erscheinen; Zahl im Filter-Knopfjobs.test.ts · jede Filterart engt die Treffer in den Musterdaten ein; parameter.test.ts · Adresse ↔ Zustand verlustfrei
H10JobsSortierungpassend/neueste/nächste; „Nächste zuerst“ nur mit Ortpruefung/fachlich/suche/suchen.test.ts · Reihenfolge je Sortierung; Option fehlt ohne Ort
H11JobsChip „Filter „{name}“ entfernen“, „Zurücksetzen“Filter weg, Adresse ohne Parameterjobs.test.ts
H12Jobs„Mehr Stellen laden“echter Link ?seite=2; ohne JS ganze Seite 2; mit JS Anhängen + Fokus auf ersten neuen Titeljobs.test.ts · Link vorhanden ab 21 Treffern, Seite 2 liefert Treffer 21–40; Werbeplätze nur auf Seite 1 nach 4 und 10
H13Jobs leer„Umkreis auf 50 km erweitern“ · „Job-Alarm für diese Suche anlegen“ · „Alle Filter zurücksetzen“Adresse mit umkreis=50 / 100; Link /job-alarm?q&ort&umkreis; Zurücksetzen nur bei Filternjobs.test.ts · 0 Treffer zeigt genau die erlaubten Knöpfe je Fall; bei 100 km Text trefferMaxUmkreis
H14Jobs (Filter-Spalte, Gruppe „Ort“; Handy: Filter-Blatt)Stadt-Chip „In Ihrer Nähe“?ort=Duisburg&umkreis=25jobs.test.ts · Chips aus Liste der Orte mit meisten Stellen; Chips stehen im Filter, nicht über der Liste (D-4)
H15StelleÖffnenAufruf gezählt (Browser-Beacon, nicht beim Server-Rendern); Titel, Passt-das, Textpruefung/seiten/stelle.test.ts · Bausteinliste wie B; pruefung/dienste/zaehler.test.ts · Beacon erhöht stellen_aufrufe_tag um 1, bekannter Bot nicht
H16Stelle„Jetzt bewerben“1a: Knopf vorhanden, führt auf /anmelden?weiter=/stelle/… (Bewerben-Fenster kommt 2a). Annahme A12stelle.test.ts · Knopf da, Adresse
H17Stelle (Karriereseite)„Zur Bewerbung beim Betrieb“öffnet karriere_url in neuem Fenster (rel noopener), Klick gezählt „karriere_klick“stelle.test.ts · bewerbungsweg karriereseite zeigt diesen Knopf statt Bewerben; zaehler.test.ts
H18Stelle„Teilen“Handy: navigator.share({title, url}); Desktop: Ausklapp WhatsApp, LinkedIn, Facebook, Xing (reine Links mit vorbereiteter Adresse), „Link kopieren“ (Clipboard, Toast „Link kopiert.“); gezählt „teilen“; jede geteilte Adresse trägt ?von=geteilt (Quelle der Aufrufe, Teil 3)pruefung/bausteine/stelle/teilen.test.ts · mit share → Aufruf; ohne share → Ausklapp mit 5 Einträgen, Adressen enthalten die Stellenadresse
H19Stelle„Drucken“ (Seitenende)window.print(); Druck-CSS blendet Rahmen ausstelle.test.ts · Knopf nur am Ende, nicht in der Klebeleiste; pruefung/stile/druck.test.ts · @media print verbirgt .kopfzeile, .klebeleiste, .ad-zone, .fuss
H20Stelle„Zur Firma“/firmen/[adresse] (Seite 1b; bis dahin 404)stelle.test.ts · Link
H21StelleÄhnliche Stelle öffnenwie H15stelle.test.ts · 3 Karten gleicher Fachbereich, Abschnitt fehlt bei 0
H22Stelle nicht verfügbar„Job-Alarm für {fachbereich} anlegen“, ähnliche StellenStatus 410, Text fehler.404.stellestelle.test.ts · abgelaufene, beendete, gesperrte, Nachbessern-, Entwurf-Stelle und gesperrte Firma liefern 410 mit Ähnlichen; „pruefung“ liefert 200 ohne JSON-LD
H23404Suche, „Zur Startseite“pruefung/seiten/sonderseiten.test.ts
H24Jede SeiteSprache wählenLink auf dieselbe Seite unter /tr/…; Cookie „sprache“ gesetzt (Gerätespeicher-Baustein; Laufzeit 12 Monate, so in der Datenschutzerklärung 2.6, RECHTSTEXTE P-5050-RT); „/“ leitet danach auf /tr/pruefung/proxy.test.ts · Accept-Language tr → /tr/, Cookie schlägt Browser; pruefung/bausteine/seitenrahmen/sprachwahl.test.ts · nur Sprachen aus einstellungen.sprachen_frei
H25Jede SeiteSprachhinweis „✕“ / „Türkçe göster“ausblenden (Gerätespeicher) / wechselnsprachhinweis.test.ts
H26Jede SeiteBurger-Menü öffnen/schließenFokus auf ersten Eintrag, Esc schließt, body.menue-offenhandymenue.test.ts
H27Jede SeiteCookie-Hinweis „Verstanden“ · „Mehr dazu“Wahl im Gerätespeicher; Hinweis erscheint erst wieder, wenn der Gerätespeicher gelöscht ist; kein Fußzeilen-Knopf „Cookie-Einstellungen“ (D-8)cookie.test.ts · Fußzeile ohne Cookie-Link, Hinweis ohne Schalter
H28Jede SeiteSuchvorschläge Was/WoCombobox: Pfeil runter, Enter, Esc; „Keine Vorschläge …“-Satzvorschlaege.test.ts · Berufe + Fachbereichsbegriffe, Orte aus plz; Bedienung per Tastatur

Nicht-Handlungen, die ein Test trotzdem sichert: Werbekarte hat kein Herz und kein h2 (jobs.test.ts); „Job-Pakete“ nirgends verlinkt (pruefung/seiten/links.test.ts: alle Links jeder 1a-Seite zeigen auf bekannte Adressen aus einer Liste); kein Text im Code außer über Sprachdateien (eslint); Bausteinliste je Seite identisch mit B (start.test.ts, jobs.test.ts, stelle.test.ts vergleichen die Klassen-Reihenfolge mit einer festen Liste, nicht mit B-Dateien).

5

Zustände je Seite

Was jede Seite zeigt, wenn nichts gefunden wird, sie gerade lädt, ein Fehler auftritt oder eine Stelle nicht mehr verfügbar ist.

SeiteleerlädtFehlernicht gefundennicht mehr verfügbar
StartseiteTop-Stellen 0: Block „Top-Stellen“ fehlt, „Neu dazugekommen“ rückt hoch (Überschrift h2); Neueste 0: der ganze Block fehlt (Startseite ohne Stellen ist Testumgebung ohne Musterdaten); Fachbereiche: Kachel 0 → „Job-Alarm“ (T oeffentlich.leer.fachbereich als Kacheltext „Job-Alarm einrichten“); Firmen-Raster < 4 Logos: Block fehltje Bereich Platzhalter (Suspense), „Bereich wird geladen“je Bereich Ladefehler fehler.netz.allgemein + „Erneut versuchen“; die übrige Seite stehtentfälltentfällt
Jobsoeffentlich.leer.treffer / .trefferMaxUmkreis mit Knöpfen laut H13; Ergebnis-Satz „0 Stellen …“ bleibt als h1Liste als Platzhalter (8 Zeilenformen), Filter sofort bedienbar; Sortierung während Laden aria-disabledLadefehler in der Liste, Filter bleiben; Adresse unverändertungültige Parameter (z. B. seite=0, umkreis=7) werden auf Standard gesetzt, keine Fehlerseite; Fachbereich unbekannt → Filter ignoriert, Chip fehltSeite > letzte Seite → letzte Seite (keine 404)
Stelleähnliche 0 → Abschnitt fehlt; keine Einleitung → kein Block; kein Gehalt und kein Start → Passt-das mit 3 Kacheln; bieten leer → Block fehlt; gut_nicht_noetig leer → h3 fehlt; Telefon der Ansprechperson nicht freigegeben (telefon_in_anzeigen false) → nur NameSeite wird auf dem Server gebaut, kein Platzhalter; Ladebalken oben beim Wechsel; Ähnliche Stellen als eigener Bereich mit Platzhaltererror.tsx fehler.500.*; Beacon-Fehler still (Zählung ist nie Blocker, wird aber protokolliert)Adresse unbekannt → not-found 404 (fehler.404.titel); Adresse mit alter Titelform → 301 auf aktuellezustand ∉ {aktiv, pruefung} oder Firma gesperrt/gelöscht → 410 NichtVerfuegbar (fehler.404.stelle, .stelleText, bei Firma gesperrt fehler.gesperrt.firmaBewerber), Ähnliche + Job-Alarm-Knopf; Google-Daten fehlen
Stelle, wartet auf Prüfungwie aktiv; Hinweis oeffentlich.stelle.pruefungFirma nur für die Firma (2a)kein JSON-LD, nicht im Job-Alarm
404fehler.404.titel/.text + Suchleiste + „Zur Startseite“
500fehler.500.titel/.text/.code; „Erneut versuchen“ (reset) · „Zur Startseite“; Nomad bekommt Fehlerprotokoll
Wartungfehler.wartung.titel/.text/.textOhneZeit/.telefon (statisch, dreisprachig)
Rahmen (jede Seite)Merkliste-Zähler 0 → Herz ohne Zahl (gemeinsam.kopf.merkliste statt merklisteMitZahl)Ladebalken ab 0,3 sBeacon-Fehler still (nur Protokoll); Clipboard-Fehler als Toast („Kopieren war nicht möglich …“, Schlüssel fehlt in T, Teil 7)

Zustände der Stelle (status.stelle.*, T 5.4) sind Firmensicht; Besucher sehen nur online oder nicht verfügbar (T 5.4 Kopfzeile). Chips erscheinen in 1a nirgends öffentlich.

Rückmeldung nach Klick (Plan „Laden und Rückmeldung“): in 1a betroffen nur „Suchen“/„Jobs finden“ (Navigation mit Ladebalken), „Mehr Stellen laden“ (Knopf zeigt „Wird geladen …“ mit den drei Ladepunkten aus B, aria-disabled), „Link kopieren“ (Toast). Logo-Intro: nur erster Besuch der Startseite von außen (Referrer nicht bululu.de), gedämpft bei prefers-reduced-motion.

6

Dateien mit geschätzter Größe und Wegweiser-Zeilen

Liste aller Programmdateien dieses Abschnitts mit geschätztem Umfang, als Arbeitsgrundlage für den Bau.

Dieser Abschnitt beschreibt nur die technische Umsetzung. Mit dem Schalter „Technische Angaben“ blenden Sie ihn ein.

Richtwert 600 Zeilen, Notbremse 1.500 (Plan Bauordnung). Übernommenes CSS von B (style.css 1.473) ist ausgenommen und bleibt eine Datei. Schätzungen in Zeilen.

6.1 Dateiliste

DateiZ.Inhalt
CLAUDE.md, REGELN-BAU.md, ARCHITEKTUR.md, STAND.md, AENDERUNGEN.mdje 40–150Regeln, Wegweiser, Stand, Protokoll
package.json, tsconfig.json, next.config.ts, drizzle.config.ts, eslint.config.ts, .prettierrc, stylelint.config.ts, knip.json, .jscpd.jsonje 10–60feste Fassungen: next ≥ 16.1.7, drizzle-orm, drizzle-kit, next-intl, pg, pg-boss, @electric-sql/pglite (Test), vitest, @testing-library/react, jsdom, axe-core, knip, jscpd, gitleaks (CI), prettier, eslint
proxy.ts60Sprache, Cookie, „/“-Weiterleitung, hreflang-Hilfe
app/[locale]/layout.tsx80Rahmen
app/[locale]/not-found.tsx · error.tsx · app/global-error.tsx30 · 40 · 40
app/[locale]/(oeffentlich)/page.tsx40Reihenfolge
…/_start/hero/Hero.tsx · chips.ts70 · 15
…/_start/einstiege/Einstiege.tsx50SVG-Puzzle inline
…/_start/top-stellen/TopStellen.tsx · daten.ts50 · 20
…/_start/neueste-stellen/NeuesteStellen.tsx · daten.ts40 · 15
…/_start/fachbereiche/Fachbereiche.tsx · daten.ts50 · 15
…/_start/werbung/Werbung.tsx20
…/_start/schritte/Schritte.tsx50
…/_start/fuer-arbeitgeber/FuerArbeitgeber.tsx · FirmenRaster.tsx · daten.ts60 · 40 · 20
…/_start/vertrauen/Vertrauen.tsx40
…/_start/job-alarm/JobAlarmHinweis.tsx50
…/jobs/page.tsx90Parameter, suchen(), Metadaten, Aufbau
…/jobs/_treffer/Filter.tsx · FilterFuss.tsx · Werkzeugleiste.tsx · AktiveFilter.tsx · Trefferliste.tsx · MehrLaden.tsx · Ergebnissatz.tsx180 · 40 · 50 · 50 · 90 · 60 · 40Filter ist Client (Dialog am Handy, Live-Zahl)
bausteine/sortierung/Sortierung.tsx60Varianten jobs (1a) · firmen (1b)
…/stelle/[adresse]/page.tsx120Laden, Sichtbarkeit, 301/410, Metadaten, JSON-LD, Aufbau
…/stelle/[adresse]/_stelle/StellenKopf.tsx · PasstDas.tsx · Aktionen.tsx · Klebeleiste.tsx · StellenText.tsx · BewerbenBox.tsx · ArbeitgeberKasten.tsx · Eckdaten.tsx · AehnlicheStellen.tsx · Seitenende.tsx · NichtVerfuegbar.tsx · passt-das.css60 · 50 · 120 · 50 · 70 · 30 · 60 · 70 · 40 · 40 · 50 · 40
app/api/z/route.ts80Beacon-Annahme, Bot-Filter, Bremse
bausteine/seitenrahmen/Kopf.tsx · KompaktKopf.tsx · HandyMenue.tsx · Fusszeile.tsx · SprachWahl.tsx · SprachHinweis.tsx · CookieHinweis.tsx · Toasts.tsx · Ladebalken.tsx · Logo.tsx · LogoIntro.tsx · seitenrahmen.css90 · 120 · 80 · 110 · 90 · 60 · 90 · 70 · 40 · 60 · 40 · 60Logo.tsx enthält die beiden SVG-Pfade (B index.html:62, :83); seitenrahmen.css nimmt das Inline-CSS der ns-badge (index.html:18–53) auf
bausteine/suche/Suchleiste.tsx · Vorschlaege.tsx · Schnellfilter.tsx120 · 220 · 50
bausteine/stellenkarte/Stellenkarte.tsx · Marken.tsx · Monogramm.tsx · MetaListe.tsx · HerzKnopf.tsx · icons.tsx90 · 30 · 50 · 50 · 90 · 80icons.tsx = IC aus app.js:45–72
bausteine/werbeflaeche/Werbeflaeche.tsx · motive.ts120 · 40motive.ts: Platzhalter-Motive aus gemeinsam/werbung (SVG nach public/werbung/)
bausteine/leerzustand/Leerzustand.tsx50
bausteine/platzhalter/Platzhalter.tsx · Ladefehler.tsx · platzhalter.css50 · 40 · 40
bausteine/sonderseiten/Fehlerseite.tsx60
bausteine/brotkrumen/Brotkrumen.tsx30
bausteine/zaehler/imBrowser.ts40
fachlich/stelle/abfragen.ts · sichtbarkeit.ts · adresse.ts · google.ts · zustaende.ts · felder.ts160 · 40 · 60 · 90 · 40 · 80
fachlich/suche/suchen.ts · umkreis.ts · vorschlaege.ts · parameter.ts220 · 90 · 100 · 120suchen.ts ist der größte Kandidat; bei > 300 Z. Sortierung in sortierung.ts abtrennen
fachlich/fachbereich/anzahl.ts · liste.ts40 · 60
fachlich/firma/oeffentlich.ts · adresse.ts · felder.ts50 · 60 · 50
fachlich/funktionen/liste.ts · istFrei.ts30 · 40
fachlich/aktion.ts90Gerüst
dienste/db/client.ts · schema/firmen.ts · stellen.ts · stellen_adressen.ts · stellen_uebersetzungen.ts · stellen_fragen.ts · fachbereiche.ts · plz.ts · vorschlaege.ts · zaehler.ts · lieferungen.ts · einstellungen.ts · protokoll.ts · mail_warteschlange.ts · index.ts30 · 70 · 150 · 20 · 40 · 30 · 50 · 40 · 30 · 60 · 40 · 20 · 40 · 50 · 20
dienste/zaehler/ereignisse.ts · zaehlen.ts · aufrufe.ts40 · 60 · 30
dienste/geraetespeicher.ts60
dienste/geo/plz-import.ts80Skript
hintergrund/index.ts · aufgaben/stellen-zustaende.ts · aufgaben/zaehler-aufraeumen.ts · aufgaben/mail-versand.ts · aufgaben/mail-aufraeumen.ts50 · 60 · 30 · 80 · 30
dienste/mail/senden.ts · warteschlange.ts · kopfzeilen.ts · vorlagen/rahmen.tsx · dienste/protokoll.ts80 · 120 · 30 · 120 · 50Fundament 26.10.; Vorlagen kommen ab 1b
db/migrationen/0001_fundament.sql · 0001_rueckweg.sql · musterdaten/index.ts · musterdaten/stellen.ts · firmen.ts · fachbereiche.ts220 · 60 · 40 · 300 · 40 · 60Musterdaten aus daten-neu.js übersetzt, Felder laut Teil 3 ergänzt (deutsch, brauchen, bewerbungsweg, plz)
sprachen/de/gemeinsam.json · suche.json · stelle.json · start.json · leer.json · fehler.json · status.json; en/, tr/ gleichje 30–120aus TEXTE 1.1–1.3, 2, 4.3, 4.4, 5.4, 7.4; ar leer bis 2b
stile/b/style.css · vorschlaege.css · werbung.css1.473 · ~150 · ~120unverändert (Ausnahme)
public/bilder/… · public/werbung/… · public/schriften/ · public/wartung.html · robots.txt (noindex auf Test)hero-b.webp, arbeitgeber.webp, Logos COPLA/Induserv
pruefung/seiten/start.test.ts · jobs.test.ts · stelle.test.ts · sonderseiten.test.ts · links.test.ts120 · 260 · 260 · 60 · 60
pruefung/bausteine/… (herz, teilen, sprachwahl, sprachhinweis, handymenue, cookie, vorschlaege)je 60–120
pruefung/fachlich/suche/suchen.test.ts · umkreis.test.ts · parameter.test.ts · adresse.test.ts200 · 80 · 100 · 60
pruefung/dienste/zaehler.test.ts · mail-warteschlange.test.ts · protokoll.test.ts · pruefung/hintergrund/mail-versand.test.ts · pruefung/proxy.test.ts · pruefung/stile/druck.test.ts · pruefung/stile/kontrast.test.ts80 · 100 · 40 · 60 · 60 · 40 · 80kontrast.test.ts rechnet die vier Paare aus Plan Bauordnung; mail-warteschlange.test.ts: einreihen, Wiederholung, Umleitung (jede Mail an MAIL_TEST_EMPFAENGER mit Betreff „[TEST → …]“, ohne Variable in test kein Versand), Tagesgrenze, kein Versand außerhalb der Warteschlange; zaehler.test.ts: Quelle aus von, fremder Wert → unbekannt
pruefung/waechter/ (bausteinliste.ts, kommentare.ts, knopfliste.ts)80 · 40 · 60Bausteinliste je Seite als feste Liste, Kommentar-Anteil < 25 %; knopfliste.ts: jede H-Nummer der gebauten Abschnitte aus _planung/knopfliste.md kommt als data-h genau einmal vor, keine fremde Kennung (L3)

Summe grob: ~170 Dateien, ~11.700 Zeilen inkl. CSS von B und Tests; keine Datei über 300 außer style.css und musterdaten/stellen.ts (300, Daten).

6.2 Zeilen für ARCHITEKTUR.md

Seite → Dateien:

  • Start = app/[locale]/(oeffentlich)/page.tsx + _start/{hero, einstiege, top-stellen, neueste-stellen, fachbereiche, werbung, schritte, fuer-arbeitgeber, vertrauen, job-alarm} · Bausteine suche, stellenkarte, werbeflaeche, platzhalter · fachlich/stelle/abfragen, fachlich/fachbereich/anzahl, fachlich/firma/oeffentlich · sprachen start.json, suche.json, leer.json · Test pruefung/seiten/start.test.ts
  • Jobs = …/jobs/page.tsx + jobs/_treffer/ · Bausteine suche, stellenkarte, werbeflaeche, leerzustand, platzhalter, zaehler · fachlich/suche/{suchen, umkreis, parameter, vorschlaege}, fachlich/fachbereich/liste · sprachen suche.json, leer.json · Test pruefung/seiten/jobs.test.ts, pruefung/fachlich/suche/
  • Stelle = …/stelle/[adresse]/page.tsx + _stelle/* · Bausteine stellenkarte (ähnliche), werbeflaeche, brotkrumen, leerzustand, zaehler, sonderseiten (nicht verfügbar) · fachlich/stelle/{abfragen, sichtbarkeit, adresse, google}, fachlich/firma/oeffentlich · dienste/zaehler/aufrufe · sprachen stelle.json, fehler.json · Test pruefung/seiten/stelle.test.ts
  • 404 = app/[locale]/not-found.tsx · bausteine/sonderseiten, suche · sprachen fehler.json
  • 500 = app/[locale]/error.tsx, app/global-error.tsx · bausteine/sonderseiten
  • Wartung = public/wartung.html (vom Webserver bei Ausfall der App)
  • Rahmen (alle) = app/[locale]/layout.tsx · bausteine/seitenrahmen/* · dienste/geraetespeicher · sprachen gemeinsam.json · proxy.ts

Baustein → wo benutzt:

  • seitenrahmen → layout.tsx (alle Seiten)
  • suche/Suchleiste → Start (gross), Jobs (zeile), KompaktKopf (kompakt), 404 (zeile); Vorschlaege → Suchleiste; Schnellfilter → Jobs (Filter-Spalte, Gruppe „Ort“, D-4)
  • stellenkarte → Start (top, zeile), Jobs (zeile), Stelle ähnliche (zeile), NichtVerfuegbar (zeile); ab 1b Merkliste (merkliste), Firmenprofil, Fachbereich
  • werbeflaeche → Start (start), Jobs (seite, liste, suche), Stelle (stelle)
  • leerzustand → Jobs, Fachbereich-Kachel (Satz), ab 1b alle Listen
  • platzhalter → Start-Bereiche, Jobs-Liste, Stelle-Ähnliche
  • sonderseiten → not-found, error, NichtVerfuegbar; ab 2a Rechte, Sitzung
  • brotkrumen → Stelle; ab 1b Unterseiten
  • zaehler/imBrowser → Suchleiste, Stelle (Aufruf, Karriere-Klick, Teilen, Drucken); ab 2a Bewerben-Fenster
  • fachlich/suche → Jobs, ab 1b Job-Alarm (dieselbe Suche)
  • fachlich/stelle/sichtbarkeit → Stelle, suchen(), google.ts, ab 1b Sitemap und Job-Alarm
  • dienste/geraetespeicher → HerzKnopf, CookieHinweis, SprachHinweis, SprachWahl
  • dienste/mail → in 1a niemand (Fundament); ab 1b fachlich/jobalarm, fachlich/kontakt; ab 2a alle Konto-, Bewerber- und Firmen-Mails; ab 3 Verwaltungs- und Werbe-Mails
  • dienste/protokoll → in 1a niemand; ab 2a jede Aktion mit Protokollpflicht, ab 3 Trigger und Protokoll-Ansicht (H129)
7

Offene Punkte (echte Weichen) und Annahmen

Sechs Grundsatzfragen, etwa vier Stufen bei den Deutschkenntnissen, und die getroffenen Annahmen, die jederzeit korrigierbar sind.

7.1 Vor dem Bau zu klären (Weichen, die Arbeit wertlos machen würden)

NrWeicheA (mein Vorschlag)B
W1Deutschkenntnisse: T 1.2 nennt vier Stufen (Keine nötig · Grundkenntnisse · Gut · Sehr gut), U 3.2 sagt „3 Haken“. Das Feld ist Pflicht an jeder Stelle und Filter; eine spätere Änderung wandert durch Datenbank, Formular (3.11), Filter, Texte, Google-Daten.vier Stufen (T gilt, weil ausformuliert); Filter zeigt vier Hakendrei Stufen, „Keine nötig“ fällt mit „Grundkenntnisse“ zusammen
W2Stellenadresse: Plan nennt /stelle/schweisser-duisburg-a7k3. Offen, ob die alte Adresse der Live-Seite (bululu.de, Altseite) ein Muster hat, das weitergeleitet werden muss (Kundenliste: „Alte Adressen werden auf die neuen weitergeleitet“). Betrifft stellen_adressen und proxy.ts.Muster der Altseite wird mit dem Domain-Zugang aus der Altseite gelesen (Karte 4 V2); Umleitungs-Tabelle alt_adressen (alte Adresse → neue) kommt in Abschnitt 3 (Karte 4 Teil 2, H200 bis H202, Paket 3.12 der Baureihenfolge); 1a baut nur stellen_adressen und hält in proxy.ts den Einhängepunkt freiAltadressen werden nicht weitergeleitet (widerspricht Kundenliste)
W3Mehrfache Beschäftigungsart je Stelle (Vollzeit und Teilzeit)? B hat ein Feld; Gesamtkonzept 5.3 nennt eine Auswahl.mehrere (enum[]), Filter „oder“, entschieden 26.10. (Teil 3 beschaeftigungsart[], 2a 1.11 Nr. 2)eine Art je Stelle (enum), verworfen
W4Zählung „Mehr Stellen laden“: Plan sagt Browser-Beacon, damit Google nicht als Suche zählt; der erste Seitenaufruf von /jobs ohne JS (Google) zählt dann gar nicht, auch echte Nutzer ohne JS nicht. Hinnehmbar?ja, Beacon nur (U C.4 so entschieden)zusätzlich serverseitig mit Bot-Filter
W5Sichtbarkeit „wartet auf Prüfung“ in der Suche: Plan sagt „auf Bululu sichtbar, für Google gesperrt, nicht im Job-Alarm“. In der Trefferliste damit sichtbar, aber die Google-Sitemap darf sie nicht enthalten; in 1a zählt gesamtAnzahlOffen sie mit.sichtbar in Liste und Zahl, kein JSON-LD, nicht in Sitemapnur über direkte Adresse sichtbar
W6Werbeflächen ohne Kampagne: Plan baut die Kampagnen-Tabelle erst in Abschnitt 3. Was zeigen Start, Jobs und Stelle bis dahin und zum Livegang ohne Buchung?Platzhalter-Motiv aus B („Hier werben“) bleibt, als Eigenwerbung für die Werbeplanung; mit Abschnitt 3 kommt die Tabelle, die Fläche zeigt dann die Kampagne oder den PlatzhalterFläche fehlt ganz ohne Kampagne (Layout rückt zusammen)

Entschieden (Annahme, jederzeit korrigierbar): W1, W2, W4 bis W6 je Spalte A (22.10.); W3 Spalte B. W6 passt zu Usability 3.18: Die Fläche zeigt ohne Buchung das Eigenmotiv „Werbung planen“; ist die Eigenwerbung in der Verwaltung aus, fehlt die Fläche ganz.

Alles andere ist durch Plan, Usability und Texte entschieden; Befunde aus FUNKTIONEN-CHECK betreffen keine 1a-Seite außer Nr. 21 (Melden, Abschnitt 3).

7.2 Annahmen

NrAnnahme
A1Links auf Seiten aus 1b bis 3 (Fachbereiche, Firmen, Für Arbeitgeber, Merkliste, Job-Alarm, Anmelden, Kontakt, Recht) stehen in 1a schon mit ihrer endgültigen Adresse; bis zum Bau antwortet dort die 404-Seite. Kein „bald verfügbar“.
A2Sozial-Links in der Fußzeile (Facebook, Instagram, Xing, LinkedIn) sind in B Platzhalter (href="#"). Die Symbole fehlen, bis Adressen in einstellungen.betreiber stehen (Dezember, Plan „Zugänge zu Facebook …“); mit Adresse erscheint das Symbol als reiner Link (D-9, entschieden 26.10.).
A3„Lebenslauf hinterlegen“ (Start Nr. 6, Fußzeile) fehlt bis Abschnitt 2b (D-10, entschieden 26.10.; nichts zeigen, was es nicht gibt). Mit dem Lebenslauf-Baukasten 2b führt der Link auf /mein-bereich/lebenslauf, ohne Sitzung über /anmelden?modus=registrieren&weiter=/mein-bereich/lebenslauf; die Schlüssel gemeinsam.fuss.lebenslauf und oeffentlich.start.lebenslauf bleiben bis dahin ungenutzt.
A4Job-Alarm-Formular auf der Startseite ist in 1a nur Ansicht und leitet auf /job-alarm mit vorausgefüllten Feldern; das Anlegen (Tabelle, Mail B16) baut 1b.
A5Vier Deutschstufen (siehe W1), bis W1 anders entschieden ist.
A6„Anzeige melden“ erscheint erst mit Abschnitt 3 (Tabelle meldungen, Verwaltung Prüfen). In 1a fehlt der Knopf, statt ins Leere zu führen.
A7Übersetzte Anzeigen (en/tr) kommen mit 2a (Karte 2a 1.18: Hintergrundaufgabe stellen-uebersetzen, Dienst Azure, Paket 2a.11a; zugesagt zum Livegang). In 1a zeigen /en/ und /tr/ den deutschen Anzeigentext mit dem Hinweis „noch nicht übersetzt“ (U B.7); Oberfläche ist übersetzt. Die Oberflächen-Übersetzung en/tr für 1a-Schlüssel schreibe ich mit; Gegenlesen (tr) vor dem Livegang.
A8Beschäftigungsart ist eine Liste mit mindestens einem Wert (W3 B, 26.10.); Formular 2a: die vier Pillen erlauben Mehrfachwahl, Vollzeit vorgewählt; Anzeige „Vollzeit, Teilzeit“ in Karte, Passt-das-Kachel und Eckdaten, keine fünfte Kachel (F2 A).
A9„läuft ab“ ist kein gespeicherter Zustand, sondern aus aktiv und laeuft_bis abgeleitet; der Datenbank-Enum stelle_zustand enthält den Wert nicht (1a.2 legt ihn ohne laeuft_ab an), nur der Code-Typ in zustaende.ts; die tägliche Hintergrundaufgabe setzt nur aktiv → abgelaufen.
A10Umkreis ohne PostGIS: Kastenfilter auf lat/lng plus Haversine reicht für die erwartete Menge (unter 10.000 Stellen). Wird es mehr, kommt earthdistance/PostGIS ohne Änderung der Schnittstelle suchen().
A11Top-Stellen der Woche: nach Veröffentlichungsdatum absteigend, höchstens 3; keine Zufallsmischung.
A12„Jetzt bewerben“ in 1a führt auf /anmelden?weiter=… (Seite aus 1b), weil das Bewerben-Fenster erst 2a kommt; die Klebeleiste ist damit schon in 1a fertig gestaltet und bekommt in 2a nur die Fenster-Logik.
A13Fachbereiche zum Start: die 12 aus daten-neu.js (metall, elektro, industrie, chemie, pruefung, it, logistik, ingenieur, verwaltung, einkauf, handwerk, service) sind die Startliste zum Livegang (Fragebogen 1.3: wie große Portale, dazu Administration = verwaltung). Sie ist nur Daten, kein Schema; die Verwaltung pflegt sie danach selbst (Karte 3, Inhalte › Fachbereiche: zusammenführen, ausblenden, neu).
A14Sprachen in 1a: de, en, tr freigegeben (einstellungen.sprachen_frei); ar vorbereitet (Ordner, dir="rtl" im Rahmen), aber nicht wählbar.
A15Stellen-Aufrufe werden als Tagessumme je Stelle gespeichert (stellen_aufrufe_tag), nicht je Aufruf; das genügt für die Arbeitgeber-Statistik (Aufrufe je Stelle, Verlauf).
A16Seitengröße 20 und Werbeplätze nach Treffer 4 und 10 nur auf Seite 1.
A17Die Inline-Styles aus index.html (Abschnittsabstände, h3-Abstand) werden zu Klassen im Baustein-CSS mit denselben Werten; Optik unverändert.
A18Der Kompaktkopf übernimmt die Scroll-Logik von B (js:1525–1593) eins zu eins als Client-Baustein; Schwelle und Dauer wie in B.
A19Tabelle plz hat eine eigene Kennung id uuid als Primärschlüssel; (plz, ort) ist nur eindeutig. Alle plz_id-Fremdschlüssel (firmen, stellen, stellen_orte, konten, job_alarme) zeigen darauf (26.10., Fremdleser S3).
A20Firmenadresse (firmen.adresse) bildet fachlich/firma/adresse.ts aus Name und Ort mit Dubletten-Zähler; Umbenennung ergibt eine neue Adresse, die alte leitet über firmen_adressen um (wie stellen_adressen).
A21Ansprechperson-Rückfallfelder (ansprech_anrede/-vorname/-nachname/-telefon) bleiben dauerhaft an der Stelle für Schnittstellen- und Import-Stellen ohne Konto; die Telefon-Freigabe liegt je Person in firmen_nutzer.telefon_in_anzeigen (2a Team), nicht je Stelle.
A22Befristung (26.10., B2): zwei freiwillige Felder befristet, befristet_bis; Anzeige nur als Zeile in den Eckdaten (F2 A), kein Filter und keine Passt-das-Kachel zum Livegang; Formularfeld in 2a nach „Start“.
A23Paket je Firma (26.10., B3): firmen.paket mit dem einzigen Wert basis und paket_bis; keine Oberfläche, keine Abrechnung, keine Rechnungsfelder zum Livegang.
A24Quelle der Aufrufe (26.10., B4): allein aus dem Adress-Parameter von (job_alarm, merkliste, folgen, geteilt, google), sonst direkt; kein Cookie, keine IP, keine Referrer-Auswertung. Die Mails B13, B15 und die Folgen-Mail sowie der Teilen-Baustein hängen den Parameter an; 2a übernimmt ihn in bewerbungen.quelle.
A25Mail-Dienst (dienste/mail: senden, warteschlange, kopfzeilen, rahmen), Tabelle mail_warteschlange, Hintergrundlauf mail-versand und dienste/protokoll.ts sind Fundament in 1a (26.10.), ohne eine einzige Vorlage und ohne Aufruf; Karte 1b bringt die ersten Vorlagen. So nutzen 1b, 2a und 3 denselben Weg, und die Umleitung an Nomad und die Tagesgrenze aus Karte 0 gelten von der ersten Mail an.
A26Vorab-Fragen des Betriebs (26.10., B1): Tabelle stellen_fragen in 1a leer angelegt, Freischaltung fragen aus; keine Oberfläche zum Livegang. Die Antwort-Tabelle und der leere Einhängepunkt im Bewerben-Fenster stehen in Karte 2a.
A27Kennungen data-h="H<nummer>" an jedem Knopf und Link mit H-Nummer ab 1a (L3, 26.10.); _planung/knopfliste.md ist die Knopfliste des Nachtlaufs, der Wächter knopfliste.ts prüft Vollständigkeit je Abschnitt. Während des Baus läuft kein Browser (Plan „Prüfen im Browser“).
A28Entfernung in der Stellenzeile: sobald ein Ort gesucht wurde, steht nach dem Ort „{n} km“ (T oeffentlich.suche.entfernung; ganze Kilometer, Luftlinie zwischen den PLZ-Mittelpunkten aus umkreis.ts, unter 1 km „0 km“); ohne gesuchten Ort fehlt die Angabe, auch auf Start, Stelle (ähnliche) und Merkliste. Keine H-Nummer, weil es keine Handlung ist (geprüft über H8). Eine Zeile mehr in der MetaListe, sonst keine Änderung an B.
A29Stadt-Chips (D-4, entschieden 26.10.): dieselben Chips wie in B als erste Gruppe „Ort“ der Filter-Spalte (Desktop) bzw. oben im Filter-Blatt (Handy); gleiche Adressen, gleiche Herkunft (Orte mit den meisten Stellen). Der Baustein Schnellfilter.tsx bleibt, nur sein Platz wechselt; CSS aus B (schnellfilter*) wird im Filter wiederverwendet.

7.3 Textschlüssel (am 22.10. in TEXTE-UND-MAILS nachgetragen bzw. vorhanden)

  • Cookie-Hinweis: vorhanden als gemeinsam.cookies.text, .verstanden, .mehr; gemeinsam.fuss.cookies, gemeinsam.cookies.notwendig und .speichern entfallen (D-8, entschieden 26.10.).
  • Entfernung in der Stellenzeile: neu oeffentlich.suche.entfernung „{km} km“ (26.10., T 7.9; A28).
  • Sprachhinweis erster Besuch: gemeinsam.sprache.hinweis, .hinweisWechseln, .hinweisSchliessen (neu).
  • Link kopiert: vorhanden gemeinsam.ok.linkKopiert; neu gemeinsam.ok.linkKopierenFehler.
  • „Mehr Stellen laden“ während des Ladens: vorhanden gemeinsam.laden; Vorlesetext an Platzhaltern neu gemeinsam.ladenBereich.
  • Seitentitel: neu gemeinsam.titel.start, .jobs, .stelle (Trenner „ | “).
  • Teilen über WhatsApp: neu oeffentlich.stelle.teilenText.
  • Übersetzungs-Hinweis: vorhanden gemeinsam.sprache.maschinell, .deutschZeigen, .nochNichtUebersetzt.
  • Zahlen- und Datumsformat je Sprache über next-intl (kein Schlüssel, Hinweis für den Bauer).
  • Befristung (26.10., T 7.9): neu oeffentlich.stelle.ecBefristung, .befristetBis, .befristet, .unbefristet. Beschäftigung als Liste: vorhandene Wörter aus T 1.2 mit Trenner „, “, kein neuer Schlüssel.