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. = Datei und Zeile in entwurf-b/; css:497 = assets/style.css; js:693 = assets/app.js; T oeffentlich. = Textschlüssel aus TEXTE-UND-MAILS; U 3. = Usability-Bildschirm.
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 / usw. (proxy.ts, U B.7). Alle öffentlichen Seiten liegen in app/. Der Seitenrahmen (Kopf, Kompaktkopf, Handy-Menü, Fußzeile, Cookie-Hinweis, Toast-Behälter, Ladebalken) kommt aus app/ 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:
| Baustein | B-Fundstelle | Hinweis |
|---|---|---|
| Sprungmarke „Zum Inhalt springen“ | index.html:59 | T gemeinsam.kopf.zumInhalt |
| Kopf: Logo · Jobs · Für Arbeitgeber · Sprache · Anmelden (orange) · Stelle inserieren (grün) · Burger | index.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 inserieren | index.html:71–79, js:306 menueSchalten | body.menue-offen bleibt (U A.1) |
| Kompaktkopf beim Scrollen: Logo · Suchzeile Was/Wo/Umkreis/Suchen · Anmelden · Sprache · Burger | index.html:81–96, css:1070–1105, js:1525–1593 | Suchzeile = 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-Nomad | index.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. 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 toast | nur Behälter aria-live, nicht je Toast (U A.1) |
| Cookie-Hinweis | js:1419–1469, css:1237–1254 | nur „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ßen | index.html:56–57, css:1431–1451 | Plan „Laden und Rückmeldung“, U A.2 |
1.2 Startseite
Adresse / · Datei app/ (nur Reihenfolge der Bereiche, ~40 Z.) · Bereiche in _.
Reihenfolge Handy = Desktop (ein Quelltext, Umbruch per CSS). Die Suchkarte steht im Quelltext direkt nach der h1 (U A.2).
| Nr | Bereich | Bausteine | B-Fundstelle | Daten |
|---|---|---|---|---|
| 1 | Hero | h1 „Einfach bewerben, Job finden.“, Bühne (hero-b.webp), Suchkarte = Suchleiste Variante „gross“ (Was · Wo · Umkreis Standard 25 km · „Jobs finden“), „Oft gesucht:“ 4 Chips | index.html:100–140, css:176–213, 1149–1178, 1354–1366, 1466 | Chips aus _ (feste Liste: Schweißer, Elektroniker, Werkstoffprüfer, Ausbildung) · „Job finden.“ in dunkler Schrift mit grünem Markerstreifen |
| 2 | Einstiege | zwei Karten „Ich suche einen Job“ (→ /jobs) und „Ich suche Mitarbeiter“ (→ /fuer-arbeitgeber), Zitat-Zeile | index.html:143–162, css:260–293, 1179–1213, 1415–1418 | Text :150 „Ganz ohne Konto.“ ersetzt (U 3.1) |
| 3 | Top-Stellen + Neu dazugekommen | Kopf 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 | _, _ über fachlich/stelle/abfragen.ts |
| 4 | Fachbereiche | kat-raster, Kachel mit Anzahl; ohne Stellen „Job-Alarm“ statt Zahl; höchstens 8, „Alle Fachbereiche“ → /fachbereiche | index.html:182–196, css:337–349, js:405ff (data-kategorien) | fachlich/fachbereich/anzahl.ts |
| 5 | Werbefläche Start (Billboard 970×250, Handy 320×100) | Werbefläche Variante „start“ | index.html:199–212, css:350–366, 1013–1025, 1125–1132, gemeinsam/werbung | Zum Start: ohne Kampagne zeigt die Fläche das Platzhalter-Motiv von B mit „Hier werben“ (→ /fuer-arbeitgeber#werbung). Kampagnen-Tabelle erst Abschnitt 3 (Platzhalter). |
| 6 | So einfach geht es | 3 Schritte; der Fuß „Lebenslauf hinterlegen“ fehlt bis Abschnitt 2b | index.html:215–236, css:367–376 | Texte T oeffentlich.start.schritt1–3; Satz :222 ersetzt; Fuß-Knopf erst mit dem Lebenslauf-Baukasten 2b (D-10, entschieden 26.10.; Annahme A3) |
| 7 | Für Arbeitgeber | Teaser mit Bild, 3 Haken, Textlink mit Pfeil „Stelle kostenlos inserieren →“ (→ /fuer-arbeitgeber#inserieren; a., kein Knopf); darunter Firmen-Raster „Arbeitgeber auf bululu“ | index.html:239–266, css:377–396, js:436 firmenKacheln | Knopf 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. |
| 8 | Vertrauen | Box mit Satz T oeffentlich.start.vertrauen, Silhouette, Telefon | index.html:269–294, css:397–411 | Zahlenreihe (:276–280) raus; „Seit {jahr}“ nur wenn belegt |
| 9 | Job-Alarm | Hinweis mit Formular (Beruf, E-Mail, „Job-Alarm starten“) | index.html:297–318, css:412–422, js:446 alarmFormular | In 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 / · Datei app/ · Teile in jobs/. 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).
| Nr | Bereich | Bausteine | B-Fundstelle | Hinweis |
|---|---|---|---|---|
| 1 | Suchleiste (Seite) | Suchleiste Variante „zeile“ (Was · Wo · Umkreis · Suchen) | jobs.html:95–120, css:458–464, 1460, js:1485–1516 Vorschläge | Desktop 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) |
| 2 | Filter | Fieldsets: 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 filterBlattSchalten | Deutsch und Beschäftigung aus <details> heraus (U 3.2). Handy: Dialog (showModal) |
| 3 | Halfpage-Werbung (nur Desktop) | Werbefläche Variante „seite“ (300×600) | jobs.html:161–169, css:1016 | |
| 4 | Werkzeugleiste | h1 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:588 | h1 tabindex=-1, nicht selbst Live-Region; Zahl als höfliche Meldung (U A.2) |
| 5 | Aktive Filter | Chips mit „Filter „{name}“ entfernen“ | jobs.html:185, css:486, js:592ff | |
| 6 | Liste | <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 Seite | jobs.html:186, js:515–691 trefferliste, js:612–618, js:240 anzeigeNativ, js:249 anzeigeText, css:318–336 | Hinweis-Satz jobs.html:187 entfällt. Werbekarte ohne Herz und ohne Monogramm-Kreis (U 3.2) |
| 7 | Mehr Stellen laden | „{gezeigt} von {gesamt} Stellen“ + Knopf, der ein <a data-kein-ziel="1"> ist; nach Laden Fokus auf ersten neuen Titel | js:618, 677, css:1255–1258 | |
| 8 | Leer | Baustein 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.trefferMaxUmkreis | js:603–609, css:489–495 |
1.4 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/ · Teile in 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).
| Nr | Bereich | Bausteine | B-Fundstelle | Hinweis |
|---|---|---|---|---|
| 1 | Brotkrumen Jobs › Fachbereich | brotkrumen | css:454–456 (Markup in B nur auf Unterseiten, Stelle hat keine): neu nach Muster | T oeffentlich.stelle.brotJobs |
| 2 | Kopf | stelle-kopf: Monogramm/Logo, Marken, Firma, h1, meta | js:712–714, css:497–502 | metaListe (js:218) wird durch die „Passt das?“-Reihe ergänzt |
| 3 | Passt das? | neue <dl> mit Ort · Deutsch · Beschäftigung · Gehalt oder Start | U 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) |
| 4 | Aktionen | „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–527 | Satz js:715 entfällt; Drucken aus dem Kopf ans Seitenende |
| 5 | Klebeleiste (Handy) | nur „Jetzt bewerben“; Zustände „Beworben am … · Stand ansehen“ (2a), „Zur Bewerbung beim Betrieb“ (Karriereseite) | stelle.html:98–101, css:531–533, 746ff | Druck-Knopf :99 entfällt |
| 6 | Text | Einleitung, h2 Ihre Aufgaben, h2 Das brauchen Sie (h3 Gut, aber nicht nötig), h2 Das bietet die Firma | js:725–727, css:503–510 | Feldnamen aus Teil 3 |
| 7 | Bewerben-Box | T oeffentlich.stelle.boxTitel/boxText + Knopf | js:728, css:511–514 | |
| 8 | Arbeitgeber-Kasten | Logo (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_; Rückfall-Felder siehe Teil 3 stellen.ansprechperson_id) | js:730–732, css:515–519 | zweiter Link „Alle Stellen dieser Firma“ entfällt |
| 9 | Eckdaten | <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–523 | Zeile „Befristung“ nur, wenn stellen. 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.) |
| 10 | Werbung | Werbefläche Variante „stelle“ (300×250) | js:734–735 | |
| 11 | Ähnliche Stellen | 3 × Stellenkarte „zeile“, aehnliche spalten-n; Abschnitt fehlt ganz, wenn keine | js:708, 737–739, css:528–530 | gleicher Fachbereich, Top zuerst, dann neueste, dazu Nähe (Plan Listen) |
| 12 | Seitenende 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). |
| 13 | Druckansicht | @media print aus B | css:820–838, 1060, 1104, 1132 | Kopf, Klebeleiste, Werbung, Ähnliche, Fußzeile ausgeblendet; Seitentitel „{Titel} · {Firma} · bululu“ |
| 14 | Google for Jobs (JSON-LD) | fachlich/stelle/google.ts | Plan Architektur | Ausgabe 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
| Fall | Adresse / Datei | Aufbau | Textschlüssel |
|---|---|---|---|
| 404 allgemein | app/ | Rahmen · 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/ liefert Status 410 mit Ansicht _ | h1 · 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 500 | app/ und app/ | h1 · Satz · „Erneut versuchen“ · „Zur Startseite“ · Fehlernummer klein | fehler.500.titel, .text, .code |
| Wartung | public/ (statisch, vom Webserver ausgeliefert, wenn die App nicht antwortet) in de/en/tr untereinander | h1 · Satz · Telefon | fehler.wartung.* |
| Bereich lädt | Baustein Platzhalter (weiße Flächen mit dünnem Rand, ruhig pulsierend, „Bereich wird geladen“) | je _-Bereich eine loading-Fassung über Suspense | kein Text außer Vorlese-Satz |
| Laden scheitert | Baustein Ladefehler: Satz + „Erneut versuchen“ | je Bereich error boundary | fehler.netz.allgemein |
| Leer | Baustein Leerzustand (Satz + höchstens zwei Knöpfe) | oeffentlich.leer.* | |
| Keine Rechte, Sitzung abgelaufen | erst 2a (es gibt in 1a keine Anmeldung); Dateien vorgesehen unter bausteine/ | fehler.rechte., fehler.sitzung. | |
| Sprachhinweis erster Besuch | Zeile über dem Kopf „Bu sayfa Türkçe de var …“ nur, wenn Browsersprache ≠ Seitensprache und freigegeben; „✕“ merkt sich das im Gerätespeicher | bausteine/seitenrahmen/SprachHinweis.tsx | Schlüssel fehlen in T, Teil 7 |
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 _, jobs/, stelle/. CSS von B bleibt unverändert in stile/ (style.css, vorschlaege.css, werbung.css; konfigurator.css erst 1b/3). Neues CSS nur beim Baustein (bausteine/) 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) | Dateien | Aus B: HTML | Aus B: CSS-Klassen | Aus B: app.js | Benutzt 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.tsx | index.html:56–96, 321–392 | kopfzeile*, 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-intro | 259 ausklappZu, 306 menueSchalten, 319 kopfRand, 1419–1469 cookieHinweis, 1525–1593 Kompaktkopf, 27 toast | jede 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 komplett | 1485–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-erzeugt | job, 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–257 | ad-zone, ad-kennung, ad-banner, ad-hier, ad-platz, ad-karte*, ad-suche*, ad-nur-desktop, halfpage-huelle | gemeinsam/werbung/werbung.js 39 Z. (Klick-Hinweis) entfällt; Klick geht auf /fuer-arbeitgeber#werbung | Start, 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:607 | leer | 603–609 | Jobs, 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-gefunden | nicht-gefunden, u-kopf, lead | not-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.tsx | css:454 | brotkrumen | Stelle; später Unterseiten |
2.2 Fachlich und Dienste (gemeinsam)
| Ordner | Dateien | Inhalt |
|---|---|---|
| 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.ts | Liste 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.ts | Firmenprofil-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.ts | der 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.ts | lesen/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-Zugriff | eslint-Regel |
| dienste/mail/ | senden.ts (SMTP über den eigenen Nomad-Mailserver; Absender aus einstellungen.betreiber (ab 1b), in test die Nomad-Adresse; Umleitung MAIL_ und Tagesgrenze MAIL_ 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. nur aus warteschlange.) |
| dienste/protokoll.ts | protokolliere(. (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)
| Ort | Dateien |
|---|---|
| app/[locale]/layout.tsx | Rahmen: <html lang dir>, Schriften (next/font, Manrope + Outfit lokal), CSS-Einbindung, Kopf, KompaktKopf, HandyMenue, main, Fusszeile, CookieHinweis, Toasts, Ladebalken, SprachHinweis |
| app/[locale]/(oeffentlich)/page.tsx | Reihenfolge 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.tsx | liest 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/ (Client, Variante jobs; 1b nutzt sie für Unternehmen) |
| …/stelle/[adresse]/page.tsx | lä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.tsx | Sonderseiten |
| app/api/z/route.ts | nimmt Beacons an (U C.4) |
| proxy.ts | Sprache erkennen, „/“ weiterleiten, Cookie „sprache“ lesen |
| hintergrund/index.ts | Startpunkt 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).
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/, Migration db/ mit Rückweg. Kennungen sind Zufallswerte (UUID v7 als Primärschlüssel, nie fortlaufend; Plan Sicherheit). Jede Tabelle: angelegt_, geaendert_ (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_ liest nur sichtbare Stellen, bululu_ schreibt Zähler und Sicherheitsprotokoll über Datenbank-Funktionen, bululu_ 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
| Feld | Typ | Hinweis |
|---|---|---|
| id | uuid PK | |
| name | text, nicht leer | |
| adresse | text, eindeutig | lesbare Adresse für /firmen/[adresse] (1b); gebildet von fachlich/ 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_ (Umleitung 301). Füllt 1a für Musterdaten/Import, 2a firmaRegistrieren() und profilFeldSpeichern('name') rufen dieselbe Funktion (Annahme A20) |
| strasse, plz, ort | text | plz 5 Zeichen; ort Pflicht |
| plz_id | FK plz | Standort für Anzeige; Stelle hat eigenen Standort |
| telefon | text | |
| logo_datei_id | uuid, leer erlaubt | FK dateien (Tabelle 2a; in 1a Spalte ohne FK, Platzhalter) |
| zustand | enum firma_zustand: registriert, aktiv, gesperrt, geloescht | Plan Lebenslauf Firma |
| sperr_grund | text, leer | |
| geprueft | boolean, Standard false | solange false, läuft jede veröffentlichte Stelle über die Prüfung (Karte 3 noetig.; in der Regel nur die erste); true setzen Freigabe, automatische Freigabe nach Frist, Prüfstufe „ohne Prüfung“ und Anlegen durch die Verwaltung |
| premium_bis | date, leer | Freischaltung Premium (Karte 3 setzt es mit top_plaetze) |
| top_plaetze | smallint, Standard 3 | |
| paket | enum paket: basis; Standard basis | Paket-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/ 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_bis | date, leer | Ende des Pakets, leer = unbegrenzt |
| fremde_kennung | text, leer | Kennung in der Quelle (Import/Schnittstelle) |
| herkunft | enum herkunft: hand, import, schnittstelle | Standard hand |
| lieferung_id | uuid, leer | FK lieferungen |
| Index | (herkunft, fremde_kennung) eindeutig, wenn fremde_kennung gesetzt; (zustand) |
stellen
| Feld | Typ | Hinweis |
|---|---|---|
| id | uuid PK | |
| firma_id | uuid FK firmen, nicht leer | |
| kurzkennung | text(4–6), eindeutig | Teil 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, _ Frage 6) |
| adresse | text, eindeutig | titel-ort-kurzkennung, bei Titeländerung neu, alte Adresse in stellen_ (Umleitung) |
| titel | text, Pflicht, mit (m/w/d) | |
| fachbereich_id | FK fachbereiche, Pflicht | |
| beschaeftigungsart | beschaeftigungsart[] (enum vollzeit, teilzeit, ausbildung, praktikum), mindestens ein Wert, GIN-Index | Pflicht; 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 _); Formular 2a: die vier Pillen erlauben Mehrfachwahl, Vollzeit vorgewählt |
| homeoffice | boolean, Pflicht | |
| deutsch | enum deutschstufe: keine, grund, gut, sehr_gut | Pflicht, Filter |
| arbeitsort, arbeits_plz | text, Pflicht | Anzeige „Duisburg“ |
| plz_id | FK plz, Pflicht | Standort für Umkreis; aus arbeits_plz aufgelöst |
| lat, lng | double | Kopie aus plz für den Kastenfilter (kein Join in der Suche) |
| einleitung | text, leer | 2–3 Sätze |
| aufgaben | text[] | Pflicht, je Eintrag ein Punkt |
| brauchen | text[] | Pflicht („Das brauchen Sie“) |
| gut_nicht_noetig | text[] | freiwillig |
| bieten | text[] | freiwillig |
| gehalt_von, gehalt_bis | integer, leer | |
| gehalt_einheit | enum: monat, stunde, jahr, leer | |
| start_am | date, leer; start_sofort boolean | „Start“ in Passt-das |
| befristet | boolean, leer | leer = 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 _) |
| befristet_bis | date, leer | nur mit befristet = true; Eckdaten „Befristet bis {datum}“, ohne Datum „Befristet“ |
| bewerbungsweg | enum: bululu, karriereseite | Pflicht |
| karriere_url | text, leer; Pflicht wenn karriereseite | https-Prüfung |
| ansprechperson_id | uuid, leer in 1a | FK 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_), sonst die Rückfall-Felder (ansprech_telefon erscheint, wenn gefüllt). Kein Feld ansprech_ (Annahme A21) |
| zustand | enum stelle_zustand: entwurf, pruefung, aktiv, abgelaufen, beendet, nachbessern, gesperrt, geloescht | Der Code-Typ in zustaende. kennt zusätzlich laeuft_ (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_, ihr Fachzustand bleibt erhalten |
| veroeffentlicht_am | timestamptz, leer | „vor n Tagen“, „Online seit“, Datum-Filter, Marke Neu (<= 1 Tag) |
| laeuft_bis | date, leer | veroeffentlicht + 30 Tage; leer bei Schnittstelle |
| top | boolean, Standard false | Top-Stelle (nur wirksam mit premium_bis der Firma) |
| ba_beruf_id | integer, leer, Index | Beruf nach der Liste der Bundesagentur; in 1a ohne Fremdschlüssel, Tabelle ba_berufe kommt mit 2b; Formularfeld erst 2a |
| sozialversicherungspflichtig | boolean, Standard true | Pflichtangabe bei der Bundesagentur; Haken im Stellenformular 2a, vorausgewählt, nur bei Ausnahmen (Minijob, Praktikum ohne Vergütung, freie Mitarbeit) abwählen |
| anzahl_stellen | smallint, Standard 1, > 0 | Anzahl offener Stellen (Bundesagentur NumberToFill) |
| nachbessern_grund, sperr_grund | text, leer | |
| suchtext | tsvector (generiert aus titel, einleitung, aufgaben, brauchen, gut_nicht_noetig; deutsch) | Volltext |
| fremde_kennung | text, leer | z. B. COPLA-Kurzname |
| herkunft | enum herkunft | Standard hand |
| lieferung_id | uuid, 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_ (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_-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_, 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_ ["de","en","tr"], funktionen {premium: true, fragen: false, …}, laufzeit_ 30, top_ 3, gehalt_ 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/ 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; mitortAufgeloestträgt jede StellenKarteentfernungKm(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").
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. _ (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/ 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.
| Nr | Bildschirm | Knopf/Link (Wortlaut T) | Wirkung | Test (Datei · prüft) |
|---|---|---|---|---|
| H1 | Startseite | „Jobs finden“ (Suchkarte) | Navigation zu /jobs?q&ort&umkreis=25; Zählung „suche“ im Browser | pruefung/seiten/start.test.ts · Formular hat action /[locale]/jobs, Felder q/ort/umkreis, Standard 25; pruefung/fachlich/suche/parameter.test.ts |
| H2 | Startseite | Oft-gesucht-Chip | /jobs?q=Schweißer bzw. ?art=ausbildung | start.test.ts · 4 Chips mit richtigen Adressen |
| H3 | Startseite | Fachbereich-Kachel | /jobs?fachbereich=metall; Kachel ohne Stellen → /job-alarm?fachbereich=metall | start.test.ts · Kachel mit 0 Stellen trägt Job-Alarm-Link; pruefung/fachlich/fachbereich/anzahl.test.ts |
| H4 | Startseite | „Alle N Stellen ansehen“ | /jobs; N = gesamtAnzahlOffen, Zeile ohne Zahl, wenn 0 | start.test.ts · N stimmt mit Musterdaten |
| H5 | Startseite | „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., kein btn-*; kein Link „Lebenslauf hinterlegen“ auf der Seite |
| H6 | Startseite, Jobs, Stelle | Herz „Merken“ | Stelle im Gerätespeicher ({stellen: [{id, gemerktAm}]}); aria-pressed wechselt; Toast „Gemerkt: {titel}“ mit Link Merkliste; Kopf-Herz zählt | pruefung/bausteine/stellenkarte/herz.test.ts · Klick schreibt über dienste/geraetespeicher, zweiter Klick entfernt, Zähler im Kopf |
| H7 | Startseite | „Job-Alarm starten“ | 1a: Weiterleitung auf /job-alarm?q=&mail= (1b baut das Anlegen) | start.test.ts · Formular zeigt auf /job-alarm |
| H8 | Jobs | Suchzeile ä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 |
| H9 | Jobs | Filter setzen (Fachbereich, Art, Deutsch, Online seit, Homeoffice) | Desktop sofort; Handy nach „{n} Stellen anzeigen“; Adresse trägt die Wahl; Chips erscheinen; Zahl im Filter-Knopf | jobs.test.ts · jede Filterart engt die Treffer in den Musterdaten ein; parameter.test.ts · Adresse ↔ Zustand verlustfrei |
| H10 | Jobs | Sortierung | passend/neueste/nächste; „Nächste zuerst“ nur mit Ort | pruefung/fachlich/suche/suchen.test.ts · Reihenfolge je Sortierung; Option fehlt ohne Ort |
| H11 | Jobs | Chip „Filter „{name}“ entfernen“, „Zurücksetzen“ | Filter weg, Adresse ohne Parameter | jobs.test.ts |
| H12 | Jobs | „Mehr Stellen laden“ | echter Link ?seite=2; ohne JS ganze Seite 2; mit JS Anhängen + Fokus auf ersten neuen Titel | jobs.test.ts · Link vorhanden ab 21 Treffern, Seite 2 liefert Treffer 21–40; Werbeplätze nur auf Seite 1 nach 4 und 10 |
| H13 | Jobs 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 Filtern | jobs.test.ts · 0 Treffer zeigt genau die erlaubten Knöpfe je Fall; bei 100 km Text trefferMaxUmkreis |
| H14 | Jobs (Filter-Spalte, Gruppe „Ort“; Handy: Filter-Blatt) | Stadt-Chip „In Ihrer Nähe“ | ?ort=Duisburg&umkreis=25 | jobs.test.ts · Chips aus Liste der Orte mit meisten Stellen; Chips stehen im Filter, nicht über der Liste (D-4) |
| H15 | Stelle | Öffnen | Aufruf gezählt (Browser-Beacon, nicht beim Server-Rendern); Titel, Passt-das, Text | pruefung/seiten/stelle.test.ts · Bausteinliste wie B; pruefung/dienste/zaehler.test.ts · Beacon erhöht stellen_aufrufe_tag um 1, bekannter Bot nicht |
| H16 | Stelle | „Jetzt bewerben“ | 1a: Knopf vorhanden, führt auf /anmelden?weiter=/stelle/… (Bewerben-Fenster kommt 2a). Annahme A12 | stelle.test.ts · Knopf da, Adresse |
| H17 | Stelle (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 |
| H18 | Stelle | „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 |
| H19 | Stelle | „Drucken“ (Seitenende) | window.print(); Druck-CSS blendet Rahmen aus | stelle.test.ts · Knopf nur am Ende, nicht in der Klebeleiste; pruefung/stile/druck.test.ts · @media print verbirgt .kopfzeile, .klebeleiste, .ad-zone, .fuss |
| H20 | Stelle | „Zur Firma“ | /firmen/[adresse] (Seite 1b; bis dahin 404) | stelle.test.ts · Link |
| H21 | Stelle | Ähnliche Stelle öffnen | wie H15 | stelle.test.ts · 3 Karten gleicher Fachbereich, Abschnitt fehlt bei 0 |
| H22 | Stelle nicht verfügbar | „Job-Alarm für {fachbereich} anlegen“, ähnliche Stellen | Status 410, Text fehler.404.stelle | stelle.test.ts · abgelaufene, beendete, gesperrte, Nachbessern-, Entwurf-Stelle und gesperrte Firma liefern 410 mit Ähnlichen; „pruefung“ liefert 200 ohne JSON-LD |
| H23 | 404 | Suche, „Zur Startseite“ | pruefung/seiten/sonderseiten.test.ts | |
| H24 | Jede Seite | Sprache wählen | Link 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 |
| H25 | Jede Seite | Sprachhinweis „✕“ / „Türkçe göster“ | ausblenden (Gerätespeicher) / wechseln | sprachhinweis.test.ts |
| H26 | Jede Seite | Burger-Menü öffnen/schließen | Fokus auf ersten Eintrag, Esc schließt, body.menue-offen | handymenue.test.ts |
| H27 | Jede Seite | Cookie-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 |
| H28 | Jede Seite | Suchvorschläge Was/Wo | Combobox: Pfeil runter, Enter, Esc; „Keine Vorschläge …“-Satz | vorschlaege.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).
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.
| Seite | leer | lädt | Fehler | nicht gefunden | nicht mehr verfügbar |
|---|---|---|---|---|---|
| Startseite | Top-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 fehlt | je Bereich Platzhalter (Suspense), „Bereich wird geladen“ | je Bereich Ladefehler fehler.netz.allgemein + „Erneut versuchen“; die übrige Seite steht | entfällt | entfällt |
| Jobs | oeffentlich.leer.treffer / .trefferMaxUmkreis mit Knöpfen laut H13; Ergebnis-Satz „0 Stellen …“ bleibt als h1 | Liste als Platzhalter (8 Zeilenformen), Filter sofort bedienbar; Sortierung während Laden aria-disabled | Ladefehler in der Liste, Filter bleiben; Adresse unverändert | ungültige Parameter (z. B. seite=0, umkreis=7) werden auf Standard gesetzt, keine Fehlerseite; Fachbereich unbekannt → Filter ignoriert, Chip fehlt | Seite > 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 Name | Seite wird auf dem Server gebaut, kein Platzhalter; Ladebalken oben beim Wechsel; Ähnliche Stellen als eigener Bereich mit Platzhalter | error.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 aktuelle | zustand ∉ {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üfung | wie aktiv; Hinweis oeffentlich.stelle.pruefungFirma nur für die Firma (2a) | kein JSON-LD, nicht im Job-Alarm | |||
| 404 | fehler.404.titel/.text + Suchleiste + „Zur Startseite“ | ||||
| 500 | fehler.500.titel/.text/.code; „Erneut versuchen“ (reset) · „Zur Startseite“; Nomad bekommt Fehlerprotokoll | ||||
| Wartung | fehler.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 s | Beacon-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.
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
| Datei | Z. | Inhalt |
|---|---|---|
| CLAUDE.md, REGELN-BAU.md, ARCHITEKTUR.md, STAND.md, AENDERUNGEN.md | je 40–150 | Regeln, Wegweiser, Stand, Protokoll |
| package.json, tsconfig.json, next.config.ts, drizzle.config.ts, eslint.config.ts, .prettierrc, stylelint.config.ts, knip.json, .jscpd.json | je 10–60 | feste 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.ts | 60 | Sprache, Cookie, „/“-Weiterleitung, hreflang-Hilfe |
| app/[locale]/layout.tsx | 80 | Rahmen |
| app/[locale]/not-found.tsx · error.tsx · app/global-error.tsx | 30 · 40 · 40 | |
| app/[locale]/(oeffentlich)/page.tsx | 40 | Reihenfolge |
| …/_start/hero/Hero.tsx · chips.ts | 70 · 15 | |
| …/_start/einstiege/Einstiege.tsx | 50 | SVG-Puzzle inline |
| …/_start/top-stellen/TopStellen.tsx · daten.ts | 50 · 20 | |
| …/_start/neueste-stellen/NeuesteStellen.tsx · daten.ts | 40 · 15 | |
| …/_start/fachbereiche/Fachbereiche.tsx · daten.ts | 50 · 15 | |
| …/_start/werbung/Werbung.tsx | 20 | |
| …/_start/schritte/Schritte.tsx | 50 | |
| …/_start/fuer-arbeitgeber/FuerArbeitgeber.tsx · FirmenRaster.tsx · daten.ts | 60 · 40 · 20 | |
| …/_start/vertrauen/Vertrauen.tsx | 40 | |
| …/_start/job-alarm/JobAlarmHinweis.tsx | 50 | |
| …/jobs/page.tsx | 90 | Parameter, suchen(), Metadaten, Aufbau |
| …/jobs/_treffer/Filter.tsx · FilterFuss.tsx · Werkzeugleiste.tsx · AktiveFilter.tsx · Trefferliste.tsx · MehrLaden.tsx · Ergebnissatz.tsx | 180 · 40 · 50 · 50 · 90 · 60 · 40 | Filter ist Client (Dialog am Handy, Live-Zahl) |
| bausteine/sortierung/Sortierung.tsx | 60 | Varianten jobs (1a) · firmen (1b) |
| …/stelle/[adresse]/page.tsx | 120 | Laden, 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.css | 60 · 50 · 120 · 50 · 70 · 30 · 60 · 70 · 40 · 40 · 50 · 40 | |
| app/api/z/route.ts | 80 | Beacon-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.css | 90 · 120 · 80 · 110 · 90 · 60 · 90 · 70 · 40 · 60 · 40 · 60 | Logo.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.tsx | 120 · 220 · 50 | |
| bausteine/stellenkarte/Stellenkarte.tsx · Marken.tsx · Monogramm.tsx · MetaListe.tsx · HerzKnopf.tsx · icons.tsx | 90 · 30 · 50 · 50 · 90 · 80 | icons.tsx = IC aus app.js:45–72 |
| bausteine/werbeflaeche/Werbeflaeche.tsx · motive.ts | 120 · 40 | motive.ts: Platzhalter-Motive aus gemeinsam/werbung (SVG nach public/werbung/) |
| bausteine/leerzustand/Leerzustand.tsx | 50 | |
| bausteine/platzhalter/Platzhalter.tsx · Ladefehler.tsx · platzhalter.css | 50 · 40 · 40 | |
| bausteine/sonderseiten/Fehlerseite.tsx | 60 | |
| bausteine/brotkrumen/Brotkrumen.tsx | 30 | |
| bausteine/zaehler/imBrowser.ts | 40 | |
| fachlich/stelle/abfragen.ts · sichtbarkeit.ts · adresse.ts · google.ts · zustaende.ts · felder.ts | 160 · 40 · 60 · 90 · 40 · 80 | |
| fachlich/suche/suchen.ts · umkreis.ts · vorschlaege.ts · parameter.ts | 220 · 90 · 100 · 120 | suchen.ts ist der größte Kandidat; bei > 300 Z. Sortierung in sortierung.ts abtrennen |
| fachlich/fachbereich/anzahl.ts · liste.ts | 40 · 60 | |
| fachlich/firma/oeffentlich.ts · adresse.ts · felder.ts | 50 · 60 · 50 | |
| fachlich/funktionen/liste.ts · istFrei.ts | 30 · 40 | |
| fachlich/aktion.ts | 90 | Gerü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.ts | 30 · 70 · 150 · 20 · 40 · 30 · 50 · 40 · 30 · 60 · 40 · 20 · 40 · 50 · 20 | |
| dienste/zaehler/ereignisse.ts · zaehlen.ts · aufrufe.ts | 40 · 60 · 30 | |
| dienste/geraetespeicher.ts | 60 | |
| dienste/geo/plz-import.ts | 80 | Skript |
| hintergrund/index.ts · aufgaben/stellen-zustaende.ts · aufgaben/zaehler-aufraeumen.ts · aufgaben/mail-versand.ts · aufgaben/mail-aufraeumen.ts | 50 · 60 · 30 · 80 · 30 | |
| dienste/mail/senden.ts · warteschlange.ts · kopfzeilen.ts · vorlagen/rahmen.tsx · dienste/protokoll.ts | 80 · 120 · 30 · 120 · 50 | Fundament 26.10.; Vorlagen kommen ab 1b |
| db/migrationen/0001_fundament.sql · 0001_rueckweg.sql · musterdaten/index.ts · musterdaten/stellen.ts · firmen.ts · fachbereiche.ts | 220 · 60 · 40 · 300 · 40 · 60 | Musterdaten 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/ gleich | je 30–120 | aus 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.css | 1.473 · ~150 · ~120 | unverä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.ts | 120 · 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.ts | 200 · 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.ts | 80 · 100 · 40 · 60 · 60 · 40 · 80 | kontrast.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 · 60 | Bausteinliste je Seite als feste Liste, Kommentar-Anteil < 25 %; knopfliste.ts: jede H-Nummer der gebauten Abschnitte aus _ 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 _· 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.tsstart/ {hero, einstiege, top-stellen, neueste-stellen, fachbereiche, werbung, schritte, fuer-arbeitgeber, vertrauen, job-alarm} - Jobs =
…/+jobs/ page. tsx jobs/· 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/_ treffer/ - Stelle =
…/+stelle/ [adresse]/ page. tsx _· 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.tsstelle/ * - 404 =
app/· bausteine/sonderseiten, suche · sprachen fehler.json[locale]/ not-found. tsx - 500 =
app/,[locale]/ error. tsx app/· bausteine/sonderseitenglobal-error. tsx - Wartung =
public/(vom Webserver bei Ausfall der App)wartung. html - Rahmen (alle) =
app/· bausteine/seitenrahmen/* · dienste/geraetespeicher · sprachen gemeinsam.json · proxy.ts[locale]/ layout. tsx
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)
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)
| Nr | Weiche | A (mein Vorschlag) | B |
|---|---|---|---|
| W1 | Deutschkenntnisse: 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 Haken | drei Stufen, „Keine nötig“ fällt mit „Grundkenntnisse“ zusammen |
| W2 | Stellenadresse: Plan nennt /. 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_ (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. den Einhängepunkt frei | Altadressen werden nicht weitergeleitet (widerspricht Kundenliste) |
| W3 | Mehrfache 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 |
| W4 | Zä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 |
| W5 | Sichtbarkeit „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 Sitemap | nur über direkte Adresse sichtbar |
| W6 | Werbeflä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 Platzhalter | Flä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
| Nr | Annahme |
|---|---|
| A1 | Links 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“. |
| A2 | Sozial-Links in der Fußzeile (Facebook, Instagram, Xing, LinkedIn) sind in B Platzhalter (href="#"). Die Symbole fehlen, bis Adressen in einstellungen. 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. |
| A4 | Job-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. |
| A5 | Vier 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. |
| A8 | Beschä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_ abgeleitet; der Datenbank-Enum stelle_ enthält den Wert nicht (1a.2 legt ihn ohne laeuft_ an), nur der Code-Typ in zustaende.; die tägliche Hintergrundaufgabe setzt nur aktiv → abgelaufen. |
| A10 | Umkreis 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(). |
| A11 | Top-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. |
| A13 | Fachbereiche 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). |
| A14 | Sprachen in 1a: de, en, tr freigegeben (einstellungen.sprachen_frei); ar vorbereitet (Ordner, dir="rtl" im Rahmen), aber nicht wählbar. |
| A15 | Stellen-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). |
| A16 | Seitengröße 20 und Werbeplätze nach Treffer 4 und 10 nur auf Seite 1. |
| A17 | Die Inline-Styles aus index.html (Abschnittsabstände, h3-Abstand) werden zu Klassen im Baustein-CSS mit denselben Werten; Optik unverändert. |
| A18 | Der Kompaktkopf übernimmt die Scroll-Logik von B (js:1525–1593) eins zu eins als Client-Baustein; Schwelle und Dauer wie in B. |
| A19 | Tabelle plz hat eine eigene Kennung id uuid als Primärschlüssel; (plz, ort) ist nur eindeutig. Alle plz_-Fremdschlüssel (firmen, stellen, stellen_orte, konten, job_alarme) zeigen darauf (26.10., Fremdleser S3). |
| A20 | Firmenadresse (firmen.) bildet fachlich/ aus Name und Ort mit Dubletten-Zähler; Umbenennung ergibt eine neue Adresse, die alte leitet über firmen_ um (wie stellen_adressen). |
| A21 | Ansprechperson-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_ (2a Team), nicht je Stelle. |
| A22 | Befristung (26.10., B2): zwei freiwillige Felder befristet, befristet_; Anzeige nur als Zeile in den Eckdaten (F2 A), kein Filter und keine Passt-das-Kachel zum Livegang; Formularfeld in 2a nach „Start“. |
| A23 | Paket je Firma (26.10., B3): firmen. mit dem einzigen Wert basis und paket_; keine Oberfläche, keine Abrechnung, keine Rechnungsfelder zum Livegang. |
| A24 | Quelle 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. |
| A25 | Mail-Dienst (dienste/: senden, warteschlange, kopfzeilen, rahmen), Tabelle mail_warteschlange, Hintergrundlauf mail-versand und dienste/ 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. |
| A26 | Vorab-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. |
| A27 | Kennungen data-h="H<nummer>" an jedem Knopf und Link mit H-Nummer ab 1a (L3, 26.10.); _ 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“). |
| A28 | Entfernung 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., 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. |
| A29 | Stadt-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.undcookies. notwendig .entfallen (D-8, entschieden 26.10.).speichern - Entfernung in der Stellenzeile: neu
oeffentlich.„{km} km“ (26.10., T 7.9; A28).suche. entfernung - Sprachhinweis erster Besuch:
gemeinsam.,sprache. hinweis .,hinweisWechseln .(neu).hinweisSchliessen - Link kopiert: vorhanden
gemeinsam.; neuok. linkKopiert gemeinsam..ok. linkKopierenFehler - „Mehr Stellen laden“ während des Ladens: vorhanden
gemeinsam.; Vorlesetext an Platzhaltern neuladen gemeinsam..ladenBereich - Seitentitel: neu
gemeinsam.,titel. start .,jobs .(Trenner „ | “).stelle - 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 .. Beschäftigung als Liste: vorhandene Wörter aus T 1.2 mit Trenner „, “, kein neuer Schlüssel.unbefristet