Steriles Weiss, kaum Rahmen, kaum Icons, Akzent Schwarz, Ticket als eigene Seite. Pakete P0-P10, jedes einzeln ueber ein Tag live. Gegen den Code geprueft (4 Pruefer + Gegenpruefung, 66 Befunde eingearbeitet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 KiB
WOMS „Weißes Design“ – Konzept
Stand 2026-10-04. Freigegeben im Gespräch:
- Prototyp angesehen, Akzentfarbe „Schwarz passt“.
- Ticket öffnet als eigene Seite.
- Paketablauf und alle Standardentscheidungen mit „ja passt“.
Danach gegen den Code geprüft: 4 Prüfer, Gegenprüfung jedes Befunds, 66 bestätigte Befunde eingearbeitet.
Grundlage:
- Klick-Prototyp im Repo
WEBklar/ticketsystem-design, Vorschauhttps://ticketsystem-design.project.webklar.commit Mitarbeiter-Login. Kopie unter/root/reports/ticketsystem-design-2026-10-04/quelle/. - Bestandsaufnahme aller 104 Quelldateien in
2026-10-04-weisses-design-umbaukarte.md(gleicher Ordner). Bei Widersprüchen gilt dieses Dokument.
Ziel
Das ganze Ticketsystem (ticket.webklar.com) bekommt ein neues Aussehen:
- steriles Weiß, sehr minimalistisch,
- kaum Rahmen, kaum Icons,
- angelehnt an das Stripe-Dashboard und trotzdem übersichtlich.
Das dunkle Design entfällt vollständig. Es gibt keinen Umschalter.
Der Umbau ist rein visuell, mit zwei Ausnahmen:
- die in „Bewusste Änderungen am Verhalten“ genannten Punkte,
- vor allem: Ein Ticket öffnet als eigene Seite mit eigenem Link (
/tickets/<Nummer>), statt in der Liste aufzuklappen.
Erfolg heißt:
- Jede Seite folgt den Gestaltungsregeln.
- Keine heutige Funktion geht verloren, außer den ausdrücklich genannten.
- Alle Tests sind grün.
- Jedes Paket geht einzeln live.
- Das jüngste Paket lässt sich per Revert zurücknehmen.
Nicht im Umfang:
- Dark Mode, neue Funktionen
- Änderungen am Datenmodell, an Rechten oder am Server
- das Kundenportal, auch nicht dessen Chat-Composer
- Terminplaner TP2
- flächige Rechtschreibkorrekturen (ae/ue) in Bestandstexten
Gestaltungsregeln
-
Weiß überall.
- Seite, Menü und Dialoge haben
#ffffff. - Es gibt keine Verläufe, Leuchteffekte, Glas-Effekte oder Hintergrundanimationen.
- Einzige Ausnahme sind Bildansicht und Kamera des fremden Chat-Composers. Sie bleiben dunkel, weil sie Medienbetrachter sind.
- Seite, Menü und Dialoge haben
-
Kaum Rahmen.
- Struktur entsteht durch Weißraum, Schriftgrößen und einzelne 1-px-Haarlinien
#ebeef1. - Haarlinien stehen zwischen Tabellen- und Listenzeilen, unter Reitern, rechts neben dem Menü, zwischen Schlüsselwerten (
.fact) und zwischen den Tagesspalten im Planboard. - Karten oder Kästen um Inhalte gibt es nicht.
- Eingabefelder und Zweitknöpfe tragen statt Rahmen einen feinen Ring-Schatten.
- Struktur entsteht durch Weißraum, Schriftgrößen und einzelne 1-px-Haarlinien
-
Kaum Icons.
- Menü, Knöpfe, Überschriften und Badges tragen keine Icons.
- Handelnde Zeichen werden als Schriftzeichen gesetzt, nicht als Icon-Komponente: Schließen „ד, Pfeile „← →“, Sortierung „↑ ↓“.
- Am Ende fliegen
react-iconsund@tabler/icons-reactauspackage.json.
-
Eine Akzentfarbe: Schwarz
#111111.- Sparsam eingesetzt: Primärknöpfe, aktiver Reiter, aktiver Menüpunkt, heutige Datumspille im Planboard.
- Pro Seitenkopf höchstens ein schwarzer Knopf.
- Links sind am feinen Unterstrich erkennbar, nicht an der Farbe.
-
Statusfarben sind leicht getönte Pillen ohne Rahmen: Radius 4 px, Höhe 20 px, Schrift 12 px/500.
Ton Hintergrund Schrift Ticketstatus (gespeicherte Werte) grau #ebeef1 #414552 Offen ( Open), leer oder unbekanntblau #e0f2fe #075985 In Arbeit ( Occupied,Assigned,Added Info,In Kontakt)orange #fcedb9 #a82c00 Wartet auf Kunde ( Awaiting,Halted)violett #efeafe #5b21b6 Prüfung ( In Test)grün #d7f7c2 #006908 Erledigt ( Closed,Zugesagt)plain – #5e6678 Storniert ( Cancelled,Aborted,Abgesagt)rot #ffe7f2 #b3093c kein Status: Badge „Dringend“ -
Schrift.
- Systemschrift
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif. Lato undsrc/assets/fontsentfallen. - Größen: h1 28/34 700 (Handy 24/30), Abschnitt-h2 16/24 600, Dialogtitel 18/26 600, Text 14/20, klein 13, sehr klein 12/16, Kennzahl 22/30 600.
- Gewicht 800 entfällt. Großbuchstaben mit Sperrung gibt es nur noch für die Wochentage im Planboard.
- Zahlen und Beträge nutzen
tabular-nums.
- Systemschrift
-
Breakpoints der WOMS-Seiten: 1100 px und 860 px.
- Unter 1100 px werden Nebenspalten ausgeblendet (
.hide-md). - Unter 860 px:
- Das Menü wird zur Schublade mit Menü-Knopf.
- Seitenköpfe stapeln sich.
- Tabellen zeigen nur Kernspalten (
.hide-sm). - Dialoge erscheinen als Bottom-Sheet.
- Der Chat wechselt zwischen Liste und Verlauf (heute 720 px).
- Ausgenommen:
- die Container-Queries der Finanztabellen (560/640/860)
- die Breakpoints des fremden Chat-Composers (640/880)
- Unter 1100 px werden Nebenspalten ausgeblendet (
-
Lesbarkeit.
- Text
#1a1f36, Nebentext#5e6678(5,8:1). - Tertiärtext
#8a93a3(3,1:1) nur für Nicht-Essenzielles, nämlich Nummern, „–“ und leere Zustände. Inhalte und Status nutzen ihn nie. - Fehlerrot
#c0123c. - Fokus ist immer sichtbar:
- Knöpfe und Links: 2 px durchgezogen
#111111mit 2 px Abstand. - Felder: 1 px
#111111plus Ringrgba(17,17,17,.14).
- Knöpfe und Links: 2 px durchgezogen
- Deaktiviert: Deckkraft .55, Cursor
not-allowed. Schreibgeschützte Felder bekommen den Hintergrund#f8f9fb.
- Text
Design-Tokens
Die Tokens stehen in :root in src/styles/global.css. Quelle ist der Prototyp mit festem Akzent, ohne html[data-accent] und ohne Prototyp-Leiste.
:root {
--bg: #ffffff; --bg-hover: #f6f8fa; --bg-subtle: #f8f9fb;
--text: #1a1f36; --text-2: #5e6678; --text-3: #8a93a3;
--line: #ebeef1; --line-strong: #d8dde3;
--accent: #111111; --accent-hover: #2b2b2b; --accent-ink: #111111;
--accent-soft: rgba(17,17,17,.06); --accent-ring: rgba(17,17,17,.14);
--grey-bg: #ebeef1; --grey-fg: #414552; --blue-bg: #e0f2fe; --blue-fg: #075985;
--orange-bg: #fcedb9; --orange-fg: #a82c00; --violet-bg: #efeafe; --violet-fg: #5b21b6;
--green-bg: #d7f7c2; --green-fg: #006908; --red-bg: #ffe7f2; --red-fg: #b3093c;
--danger: #c0123c; --danger-ring: rgba(192,18,60,.18);
--radius: 6px; --radius-lg: 12px;
--ring: 0 0 0 1px rgba(60,66,87,.16), 0 1px 1px rgba(0,0,0,.06);
--shadow-btn: 0 1px 1px rgba(0,0,0,.12), 0 2px 5px rgba(60,66,87,.08);
--shadow-pop: 0 0 0 1px rgba(60,66,87,.06), 0 12px 32px rgba(26,31,54,.14), 0 2px 6px rgba(26,31,54,.06);
--overlay: rgba(26,31,54,.32); --scrim: rgba(26,31,54,.24);
--nav-dot: #e5a000;
--appt-akquise: #fdf4e3; --appt-akquise-hover: #fbeccd; --appt-hover: #eef1f4;
--sidebar-w: 232px;
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
html { color-scheme: light; }
Alte Token-Namen bleiben bis P10 als Aliase. Fremd-CSS und JSX benutzen sie noch, so wird Bestandscode sofort weiß.
| Alter Name | Neuer Wert |
|---|---|
--surface-1, --surface-3, --surface, --dark-bg, --dark-card-bg |
--bg |
--surface-2, --surface-hover |
--bg-hover |
--border, --gray-700, --admin-line |
--line |
--border-strong, --admin-line-strong |
--line-strong |
--border-accent |
--accent-ring |
--text-muted, --text-faint, --gray-800 |
--text-2 (Altcode setzt --text-faint auch für Inhalte ein) |
--dark-text, --gray-900 |
--text |
--accent-2, --green-secondary |
--accent-hover |
--green-primary |
--accent |
--info |
--blue-fg |
--warn |
--orange-fg |
--ok |
--green-fg |
--r-sm |
--radius |
--r-md, --r-lg |
--radius-lg |
--r-pill |
4px |
--shadow-1 |
none |
--shadow-2 |
--shadow-btn |
--shadow-3 |
--shadow-pop |
--sp-1…6 |
bleiben unverändert |
--sidebar-w-collapsed |
entfällt |
Diagrammfarben. Canvas kann kein var() lesen. Deshalb stehen die Farben der Diagramme (uPlot) als Konstanten in der neuen Datei src/components/serverpuls/chartColors.js. Sie wird schon in P1 angelegt und von Chart.jsx und ServerpulsPage genutzt.
| Element | Farbe |
|---|---|
| Linie | #111111 |
| Füllung | rgba(17,17,17,.06) |
| Spitze | #8a93a3, gestrichelt |
| Achsen | #5e6678 |
| Raster | #ebeef1, nur horizontal |
| Ticks | #d8dde3 |
Bausteine
Die Klassennamen folgen dem Prototyp (styles.css). Wo eine Komponente existiert, behält sie ihre Schnittstelle, außer wo unten anders vermerkt. Es ändern sich Markup und Klassen.
| Baustein | Umsetzung |
|---|---|
| App-Gerüst | .shell mit Menü <aside class="sidebar"> (fest, 232 px) und .main. In .main liegen .mobile-bar und <main id="inhalt"> mit den Routen, ohne eigene Klasse .page. Jede Seite behält ihre Wurzel mit Klasse page, die den Prototyp-Look bekommt (max. 1160 px, Abstand 36/48/80). ProjectsPage wechselt in P1 von main-content auf page. AdminPage wechselt in P1 von <main className="page admin-page"> auf <div className="page admin-page">, die maximale Breite 1480 px von .admin-page bleibt. Gescrollt wird das Dokument, ein innerer Scroll-Container entfällt. Der Footer entfällt, sein Text steht künftig im Login-Fuß. Unter 860 px ist das Menü eine Schublade mit .scrim. Sie schließt bei Klick daneben, bei Esc (Listener nur aktiv, solange sie offen ist) und bei Wechsel des Pfads. Die Seite springt nur bei Wechsel von location.pathname nach oben, nie bei Änderungen am Suchteil der URL. |
| Menü | Reine Textliste .nav-item mit Unterpunkten .sub und Zählern .nav-count. Chat-Ungelesene bekommen .hot (neues Feld hot am Chat-Eintrag in navLinks.js). „Leads“ erscheint grau mit „pausiert“. Serverpuls-Punkt: .nav-dot gelb bei medium, .nav-dot.critical rot bei critical. data-testid="sp-nav-dot", Titel, 5-Minuten-Abfrage und useSidebarCounts bleiben. Fuß .sidebar-foot: Name (gekürzt mit Ellipsis) und „Abmelden“. Das Einklappen entfällt samt lib/sidebar.js und Test. Reihenfolge, Texte und Ziele in navLinks.js bleiben. |
| Logo | Neue components/Logo.jsx mit dem SVG-Zeichen aus dem Prototyp, 24 px im Menü und in der Mobil-Leiste, 40 px im Login. Dazu public/favicon.svg und <link rel="icon"> in index.html. <title> bleibt. |
| Seitenkopf | PageHeader rendert .page-head mit h1, .sub und .actions. Neue optionale Props eyebrow und crumb. Brotkrumen .crumb, z. B. „Kunden / Name“. |
| Abschnitt | Card rendert <section> mit <h2 class="section">: Titel links, Aktion rechts, ohne Hintergrund, Rand, Schatten und Innenabstand. pad, bodyStyle und style wirken nicht mehr. className bleibt, Finanzen braucht es für Container-Queries. |
| Knöpfe | Button: Standard und ghost werden .btn (weiß mit Ring), primary wird .btn.btn-primary (schwarz). Neu ist quiet als .btn.btn-quiet. danger wird .link-danger (roter Textlink). Die Prop size wird angenommen, wirkt aber nicht: Alle Knöpfe sind 32 px hoch, nur der Login-Knopf 36 px. Zeilenaktionen in Tabellen sind Textlinks .link. Während einer Anfrage sind Knöpfe disabled und zeigen Text („Speichert …“), keinen Spinner. |
| Links | a.link mit feinem Unterstrich (--line-strong, beim Überfahren currentColor). Basisregel a:not([class]) mit demselben Unterstrich, damit auch klassenlose Links (Rechtstext-Dateien, AVV-Link der Analyse-Zustimmung u. a.) erkennbar bleiben. Neuer Code vergibt immer eine Klasse. |
| Badge | <span class="badge {ton}">. Die Ton-Namen der Logik (muted, info, ok, warn, danger) bleiben, Tests pinnen sie. Badge.jsx übersetzt: muted → grau, info → blau, ok → grün, warn → orange, danger → rot. Neu sind violet und plain. Die Prop dot entfällt. |
| Ticketstatus | Neue Funktion statusTone(status) in lib/ticketStatus.js mit den Rückgaben '' | 'blue' | 'orange' | 'violet' | 'green' | 'plain' nach der Tabelle oben. Leer, undefined und unbekannt ergeben ''. Der Test deckt jeden gespeicherten Wert ab. StatusPill rendert badge {ton} direkt, ohne das Mapping in Badge.jsx. UrgentPill rendert „Dringend“ in Rot. statusClass entfällt mit P2. |
| Reiter | Tabs rendert .tabs und .tab mit 2-px-Unterstrich und optionalem Zähler <span class="count">. Auf dem Handy scrollen die Reiter waagerecht. Zähler werden beim Rendern ergänzt, nie in lib/customerTabs.js. |
| Umschalter | .seg: graue Schiene, der aktive Teil ist weiß mit Ring, dazu aria-pressed. |
| Tabelle | .table mit Kopf 12 px/500 und Haarlinien. tr.row ist klickbar und wird beim Überfahren --bg-subtle. Weitere Klassen: .cell-title, .cell-sub, .id, .right, .num, .table-foot, .hide-md, .hide-sm. Die erste Zelle enthält immer ein echtes Bedienelement für Tastatur und Mittelklick: einen Link oder, wenn die Zeile einen Dialog öffnet, einen <button class="link">. Klicks auf a, button, select, input und label in der Zeile lösen keine Zeilenaktion aus. |
| Liste | .list-row mit Haarlinie. Bei Link-Zeilen färbt sich beim Überfahren der Titel. |
| Schlüsselwerte | .facts/.fact für den Ticketkopf. .stats/.stat für Kennzahlen: .v.warn orange, neu .v.neg rot für negative Beträge. .kv für Beschriftung und Wert, mobil einspaltig. Dazu .two-col und .prose (Fließtext mit Zeilenumbrüchen). Neue Komponente Stat mit Prop tone, ohne Icon und ohne Farbstreifen. Kpi bleibt bis P7 als Export erhalten und rendert Stat: accent = var(--danger) wird neg, alle anderen accent werden ignoriert. |
| Formular | .field > label (500) mit .input, .select oder .textarea (32 px, Ring statt Rand, zweistufiger Fokus) und .hint. Mehrspaltige Formulare nutzen .field-row und .field-row-3, Kästchen .check. Fehler zeigt ein roter Ring (--danger, --danger-ring), auch bei [aria-invalid="true"]. Field, FormField, DateTimeInput, DurationSelect und AutocompleteInput übernehmen die Klassen. Die Vorschlagsliste ist ein weißes Popover mit --shadow-pop. |
| Dialog | Neue ui/Dialog.jsx mit den Props title, onClose, busy, wide, footer. Aufbau: .overlay (--overlay) mit .dialog (weiß, Radius 12, --shadow-pop, 520 px, mit wide 760 px). Der Kopf enthält h3 und den Knopf „ד (aria-label="Schließen"), danach folgen .dialog-body (scrollt) und .dialog-foot. Attribute: role="dialog", aria-modal, aria-labelledby. Den Fokus regelt useDialogFocus. Unter 860 px erscheint der Dialog als Bottom-Sheet. Schließen geht nur über × und Esc. Ein Klick auf den Hintergrund schließt nicht, damit lange Eingaben nicht verloren gehen (Abweichung vom Prototyp). Esc greift nur, wenn !e.defaultPrevented, so schließt eine offene Vorschlagsliste zuerst sich selbst. Solange busy gilt, wirken × und Esc nicht, und × ist disabled. Beide Wege rufen genau das onClose des Aufrufers. AssignDialog übergibt sein close() samt refresh. Die eigenen Esc-Listener in InvoiceCreateModal und AssignDialog entfallen. lockRef und busyRef bleiben als Doppelklick-Sperre. Die native required-Prüfung bleibt. |
| Leerer Zustand | <div class="empty"><strong>Titel</strong>Hinweis</div> mit optionaler Aktion. Die Prop icon entfällt. |
| Meldungen | Ohne Kasten: .msg.err (rot, role="alert"), .msg.ok (grün), .msg.warn (orange), alle 13 px. Flüchtige Bestätigungen dürfen .toast nutzen. |
| Laden | .loading mit Text „Wird geladen …“. AdminRoute behält wörtlich <p>Wird geladen …</p> ohne Klasse, weil ein Test darauf prüft. Der Seitenabstand kommt dort vom Elternelement. |
| Dringend-Schalter | UrgentToggle rendert den kleinen Schalter .sw mit dem Wort „Dringend“. Eingeschaltet ist er rot. aria-pressed und die Logik bleiben. |
| Login | Aufbau: .login > .login-box (360 px), Logo, h1 „Anmelden“, .sub „Ticketsystem für das WEBklar-Team“, Felder „E-Mail“ und „Passwort“ mit autocomplete, Knopf „Weiter“. Danach die Schritte „Code senden“ und „Code eingeben“ (h1 „Code eingeben“, .code-input, Links „Code erneut senden“ · „Abbrechen“). Die erklärenden Texte der Code-Schritte bleiben wörtlich wie heute: „Wir schicken dir einen Code an deine E-Mail-Adresse.“, „Wir haben dir einen Code an {E-Mail} geschickt.“, „Code aus der E-Mail“, „Neuer Code ist unterwegs.“. Die Prototyp-Hinweise zum Testbetrieb kommen nicht ins echte System. .login-foot zeigt „Webklar Ticket-Plattform · © {Jahr}“. Die Logik (login, sendCode, verifyCode, cancelMfa, NO_STAFF_ACCESS, safeReturnPath) bleibt unverändert. LetterGlitch.jsx entfällt. |
Übergangsschicht KOMPAT (P1 bis P10)
Ein markierter Block „KOMPAT“ in global.css bildet alte Klassen auf den neuen Look ab. So sind noch nicht umgebaute Seiten weiß, kastenlos und lesbar. Er gilt von P1 bis P10.
| Alte Klasse | Darstellung im neuen Look |
|---|---|
.ui-card* |
Abschnitt ohne Kasten, .ui-card-head h3 wie h2.section |
.ui-btn* |
wie .btn / .btn-primary / .link-danger |
.form-group, .form-label, .form-control |
wie .field, Label, .input / .select / .textarea |
.badge-*, .status-*, .priority-* |
Badge-Töne |
.overlay (Altform) |
behält overflow-y: auto, align-items: flex-start und 24/16 px Abstand, Hintergrund --overlay |
.overlay-content |
weiße Box: width: 100%, max-width: 520px (Inline-maxWidth der Aufrufer wirkt weiter, .fin-dialog 760 px), margin: 0 auto, Radius 12, --shadow-pop, Abstand 24, Schrift --text |
.overlay-close |
fest oben rechts im Fenster, 32×32 px, weißer runder Knopf mit „ד in --text-2 |
.seg button.active |
wie .seg button.on |
.bg-red (mit oder ohne .text-white) |
kein Hintergrund, Schrift --red-fg, 13 px |
.text-white allein |
keine Regel |
.text-grey |
--text-2 |
.text-red |
--red-fg |
.side-count |
kleine graue Zahl, bis P6 (Chat-Liste) |
.side-logo |
Initiale ohne Verlauf, bis P4 (Kundenliste) |
.btn-dark, .btn-teal |
wie .btn-primary |
.card*, .row, .col*, .dropdown* |
neutral |
.spinner |
neutraler grauer Ring |
.page .page, .page .main-content |
Abstand und Breite neutralisiert, falls eine Seite verschachtelt |
Links in Bestandsklassen, die bisher --accent als Farbe nutzten (.fin-link, .legal-files a, .credit-cols a, .area-rows a) |
feiner Unterstrich |
Alle !important-Regeln des Altbestands werden gelöscht, nicht überschrieben. Tote Klassen löscht P1 nur nach einer Liste aus einem Abgleich-Skript:
- Das Skript vergleicht alle Klassen in
global.cssmit Klassen-Literalen in JSX, mit Template-Präfixen (z. B.fin-fixed-${…},legal-tone-${…}) und mit Zeichenketten inlib/*.js. - Gelöscht wird erst nach Gegenprobe mit
rg -w <name> src --glob '!*.css'. - Dynamisch gebaute Klassen gelten nicht als tot. Beispiele:
fin-neg,fin-fixed-off,legal-tone-*,legal-version-*,badge-*,status-*,ticket-tab-active,ui-btn-*. - Die Liste steht im Plan.
P10 entfernt den KOMPAT-Block.
Ticket als eigene Seite
Route und Links
- Route
/tickets/:ref/:tab?.- Sie ist wie alle anderen eine
ProtectedRouteund steht vor dem Fallback*. tabistdetails(Standard) oderarbeitsblaetter. Ein unbekanntertableitet mitreplaceauf Details um.- Das Menü markiert „Tickets“ auch auf Unterseiten.
- Sie ist wie alle anderen eine
- Neue
lib/ticketRoutes.jsmitticketPath(ref, tab)und Test.refistticket.woid || ticket.$id. Reine woid-Aufrufer übergeben die woid.- Rückgabe ist
/tickets/+encodeURIComponent(ref), beiarbeitsblaettermit/arbeitsblaetter. - Ein leeres
refergibt/tickets. - Die angezeigte Kurznummer
$id.slice(-5)ist nie Linkziel.
- Bestehende Links auf
/tickets?woid=Ngibt es heute an sechs Stellen. Alle nutzen künftigticketPath:PlanboardPageAssignProjectPage(2×)ServicesPageProjectContextPanelCustomerFunktionen
- Neue Links:
- Zeilen der Ticketliste (P2)
- Tickets-Reiter der Kundenseite (P4, heute ohne Link)
- Ticket-Spalte der Projekte (P5,
project.ticketIdist eine$id)
- Alte Links
/tickets?woid=Nleiten mitreplaceauf/tickets/Num.- Das bleibt dauerhaft, auch nach P10, weil Portal-Mails so verlinken.
- Die Umleitung liegt innerhalb der geschützten Route, damit
safeReturnPathsamt Suchteil nach dem Login greift.
Laden
Neuer Hook useWorkorder(ref):
- Zuerst sucht er per
Query.equal('woid', [String(ref)]),Query.orderDesc('$createdAt'),Query.limit(1). - Findet er nichts, holt er das Ticket per
getDocument(ref)als$id. - 400 und 404 gelten beide als „Ticket nicht gefunden“.
Der Hook liefert dasselbe Schreib-API wie useWorkorders (updateWorkorder). Er kennt die Zustände Laden, Fehler und „Ticket nicht gefunden“.
Arbeitsblätter und CreateWorksheetModal nutzen immer ticket.woid des geladenen Tickets, nie den URL-Parameter. Für Tickets ohne woid zeigt der Reiter Arbeitsblätter den Hinweis „Für dieses Ticket gibt es keine Ticketnummer, Arbeitsblätter sind nicht möglich.“ statt eines Fehlers. worksheets.woid ist Pflicht.
Seite pages/TicketPage.jsx
- Kopf:
- Brotkrumen „Tickets / Nummer“. Der Link „Tickets“ führt mit dem Suchteil zurück, den die Liste per Link-State
listSearchmitgibt. Ohne State führt er auf/tickets. - Eyebrow „Nummer · Art“. Die Art kommt aus
ticketTypeLabel, „Webpage“ bleibt „Webpage“. - h1 mit dem Betreff (Rückfall „Kein Betreff“),
.submit dem Kunden als Link und dem Ort. - Aktionen: Dringend-Schalter, Status-Auswahl und der schwarze Knopf „Arbeitsblatt hinzufügen“. Der Knopf erscheint für alle Ticketarten, auch Akquise, wie heute. Der Prototyp blendet ihn bei Akquise aus, das wird nicht übernommen.
- Brotkrumen „Tickets / Nummer“. Der Link „Tickets“ führt mit dem Suchteil zurück, den die Liste per Link-State
.facts:- Zuständig als Auswahl
- Termin:
startDatemitstartTime–endTime, ohne Rückfall auf das Erstelldatum, leer „Kein Termin“ - Fällig:
deadline, leer „–“ - Einsatz:
serviceTypeLabel - Angefragt von, leer „–“
- Erstellt:
formatDistanceToNow, deutsch, wie heute
- Datumsfelder sind Freitext.
- Angezeigt wird über
parseGermanDateundformatTimeauslib/planboard.js. - Ist ein Wert nicht lesbar, steht der Rohtext da.
- Nie wird
new Date()oderformat()direkt auf den gespeicherten Text angewendet.
- Angezeigt wird über
- Reiter: „Details“ und „Arbeitsblätter (n)“.
- Details zeigt die Beschreibung als
.prose, den Projekt-Abschnitt (TicketProjects, optisch bis P5 unverändert) und „Weitere Tickets von {Kunde}“ (ProjectContextPanel, Sichtbarkeitsregel wie heute). - Arbeitsblätter zeigt
.statsund die Arbeitsblatt-Zeilen.
- Details zeigt die Beschreibung als
- Status-Auswahl (natives
<select>):- Sie bietet die sechs Werte aus
TICKET_STATUSESan, für Akquise ebenfalls. - Ist der aktuelle Wert nicht dabei (Akquise- oder Altwert), steht er zusätzlich als gewählte Option darin.
- Geschrieben wird nur
{ status }.
- Sie bietet die sechs Werte aus
- Zuständig (natives
<select>):- Optionen: „Nicht zugewiesen“ und die Mitarbeiter mit vollem Namen.
- Ist
assignedTonicht in der Mitarbeiterliste, steht zusätzlich eine gewählte Option mitassignedName, sonst „Unbekannte Person“. - Solange die Liste lädt, ist die Auswahl deaktiviert.
- Geschrieben wird nur
{ assignedTo }, für niemanden ein leerer String. useEmployeesläuft einmal pro Seite.
TicketCard.jsxentfällt.- Vorher wandert die ungetestete Logik aus
handleCreateWorksheetin eine Lib-Funktion mit Test: Status zurückschreiben und Akquise-Ergebnis setzen. - Testfälle: Akquise + Zugesagt → Kunde fest, Akquise + Abgesagt → verloren, Akquise + In Kontakt → keine Änderung, Nicht-Akquise → nie eine Änderung.
useWorksheets(ticket.woid)unduseCustomerAbolaufen einmal pro Seite.aboHoursAllowed,onTogglePlan,canEditWorksheetTimes,canAddOwnSupportund die drei Dialoge werden unverändert verdrahtet.
- Vorher wandert die ungetestete Logik aus
Ticketliste TicketsPage
- Reiter mit Zählern: Aktiv · Offen · In Arbeit · Wartet auf Kunde · Prüfung · Erledigt · Storniert · Alle.
- Aktiv =
DEFAULT_STATUS_GROUPS(offen, in Arbeit, wartend, Prüfung). - Die übrigen Reiter entsprechen je einer Gruppe aus
STATUS_GROUPS. - Alle bedeutet: kein Statusfilter.
- „Storniert“ ist neu gegenüber dem Prototyp, damit die heutige Auswahl „nur stornierte“ nicht verloren geht.
- Startzustand: Reiter Aktiv, Art Aufträge, „Nur dringende“ aus.
- Aktiv =
- Art-Auswahl (
.select-quiet) mit allen sechs heutigenTYPE_FILTERS: Aufträge (Standard, ohne Akquise) · Webpage · Migration · Projekt · Akquise · „Aufträge und Akquise“ (heute „Alle“). - Kästchen „Nur dringende“.
- Suche:
- Sie läuft im Browser über
ticketMatches, wie heute. - Sie startet 300 ms nach der letzten Eingabe oder per Enter.
- Der Knopf „Anwenden“ entfällt.
- Sie läuft im Browser über
- Veraltete Antworten:
useWorkordersdarf als einzige Hook-Änderung veraltete Antworten verwerfen (Anfrage-Zähler). So kann eine ältere Antwort nie eine neuere überschreiben. - URL-Zustand (
useSearchParams):- Reiter, Art, Dringend, Suche und
limit(Anzahl geladener Tickets, keine Seiten) stehen in der URL. - Geschrieben wird immer mit
replace, Standardwerte werden nicht geschrieben. - Ungültige Werte fallen auf den Standard zurück.
limitwird auf 10 bis 500 begrenzt. - Jede Filteränderung setzt
limitauf 10 zurück. ?woid=hat Vorrang und leitet um.- Die Helfer stehen in neuer
lib/ticketListUrl.jsmit Test.
- Reiter, Art, Dringend, Suche und
- Nachladen:
- Beim Nachladen bleiben die alten Zeilen sichtbar, im Fuß steht dezent „Wird geladen …“.
- Den leeren Ladezustand gibt es nur beim allerersten Laden.
- „Mehr laden“ bleibt (+10), ohne Suche wie heute.
- Zähler:
- Je Reiter läuft eine Abfrage mit
Query.limit(1), dietotalliest. Sie nutzt dieselbe Art und denselben Dringend-Filter wie die Liste, nie den Suchtext. - Die Zähler laden beim Öffnen, bei Wechsel von Art oder Dringend und nach dem Anlegen eines Tickets, nicht bei Reiterwechsel oder Tastendruck.
- Bei aktiver Suche werden sie ausgeblendet, der Fuß zeigt dann die Trefferzahl.
- Schlägt eine Abfrage fehl, fehlt nur der Zähler.
- Eigener Hook,
useWorkordersbleibt dafür unberührt.
- Je Reiter läuft eine Abfrage mit
- Tabellenspalten:
- Ticket: Nummer, Betreff, Badge „Dringend“, darunter Kunde · Ort.
- Status.
- Zuständig: „Vorname N.“ aus der Mitarbeiterliste, Rückfall
assignedName, sonst „Unbekannt“. Ohne Zuweisung steht „Niemand“ in Grau. - Termin: wie in den Facts, Fehlwert „–“.
- Zeit: siehe unten.
- Erstellt.
- Spalte „Zeit“:
- Wert ist die Summe von
worksheetMinutesüber alle Arbeitsblätter ohne Kommentar. Das ist dieselbe Regel wie „Arbeitszeit gesamt“ auf der Ticketseite. Formatiert wird mitstepLabel. - Geladen wird für die geladenen Zeilen in Blöcken von höchstens 100 woids:
Query.equal('woid', block),Query.select([...]),Query.limit(5000), höchstens 3 Blöcke gleichzeitig. - Das Ergebnis wird je woid zwischengespeichert, „Mehr laden“ holt nur neue Zeilen.
- Die Zeit wird höchstens für die ersten 200 Zeilen berechnet. Sind mehr geladen, nennt der Fuß das.
- Ein Block mit 5000 Treffern könnte abgeschnitten sein. Er und ein Fehler zeigen „–“, nie eine falsche Zahl. Die Liste bleibt bedienbar.
- Tickets ohne woid zeigen „–“.
- Die Summenfunktion bekommt einen Test.
- Wert ist die Summe von
- Sortierung: Sie bleibt
$createdAtabsteigend, der Fuß sagt „Neueste zuerst“. - Navigation:
- Die Zeilen verlinken mit
state={{ listSearch: location.search }}. - Nach „Neues Ticket“ springt die App auf die neue Ticket-Seite (
createWorkorderliefert die woid).
- Die Zeilen verlinken mit
Arbeitsblätter (WorksheetList neu)
- Aufbau: flache Zeilen
.wsin drei Bereichen:- links Datum und Uhrzeit
- in der Mitte Person, Einsatz (bei Kommentaren das Wort „Kommentar“), Nummer, Badges, Text, Unterstützung, „Status danach: X“ und Aktionen
- rechts die Zeit
- Es gibt kein Aufklappen, keine Verläufe und keine Animation.
- Kommentare stehen in Nebentextfarbe.
- Git-Push: ohne Zeit Badge „Zeit offen“ (orange), mit Zeit „Zeit eingetragen“ (grün).
- Abo: Der Umschalter heißt „Abo“ beziehungsweise „+ Abo“. Seine Sichtbarkeitsregel bleibt exakt:
onTogglePlan && !isComment && totalTime > 0 && (aboHoursAllowed || countsToPlan). - Bedingungen, die bleiben:
canEditTimes,canAddSupport,isGitundneedsTimeEntry. - Texte, die bleiben: „Zeit nachtragen“, „Zeiten bearbeiten“, „Ich habe unterstützt“ und „Meine Unterstützung bearbeiten“. Letzteres erscheint, sobald man schon unterstützt.
OwnSupportModalbehält „Eintrag entfernen“ undmaxLength200. - Reihenfolge: neueste zuerst.
- Zeitformat: überall
stepLabel(„1 h 30 min“, heute „1h 30min“). - Kennzahlen:
- Arbeitszeit gesamt = Summe
worksheetMinutesohne Kommentare - davon Abo-Stunden = dieselbe Summe über
countsToPlan && !isComment - Zeiten offen = Anzahl
needsTimeEntry, orange bei mehr als 0
- Arbeitszeit gesamt = Summe
Bewusste Änderungen am Verhalten
| Bisher | Künftig |
|---|---|
| Ticket klappt in der Liste auf | eigene Seite mit Link |
| Statusfilter als Mehrfachauswahl mit „Anwenden“ | Reiter (Einfachwahl) |
| Suche nach Klick auf „Anwenden“ | Suche nach Tipp-Pause |
| Footer auf jeder Seite | Footer entfällt, Text steht im Login-Fuß |
| Menü einklappbar | nicht mehr einklappbar, auf dem Handy eine Schublade |
| Dialoge schließen nur mit × | Dialoge schließen mit × und neu mit Esc |
| Statusfarben: Offen grün, Erledigt grau | Farben wie in der Tabelle oben. Das Team wird beim Live-Gang von P1 informiert. |
| Kein Link auf die Tickets einer Kundenseite oder in der Projektliste | beide verlinkt |
Alle anderen Funktionen bleiben erhalten.
Pakete
Arbeitsweise
- Branch: Gearbeitet wird im Worktree
dev-designauffeature/design-weiss, abgezweigt vontest603d08c. - Commits: Jedes Paket ist ein eigener Commit-Stapel.
git addnur mit ausdrücklichen Pfaden. - Rebase: Vor jedem Paket und unmittelbar vor jeder Abnahme läuft
git fetch origin && git rebase origin/test, kein Merge. - Tag: Erst danach wird das Tag
design-p<N>auf den abgenommenen Stand gesetzt und nicht mehr bewegt. Korrekturen nach dem Push sind Folge-Commits mit eigenem Tagdesign-p<N>-fix<k>. - Live-Gang: Live geht ein Paket nur über das Tag, nie über die Branch-Spitze. Der Eigentümer führt aus:
git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p<N>^{commit}:refs/heads/test'Das ist ein reiner Fast-Forward. Seit 2026-09-30 deployt nurtest. Vor dem Push nennt der Agent die genaue Zeile, den SHA undgit log --oneline origin/test..design-p<N>. - Nach dem Push:
- Der Agent prüft im Log von
webklar-ticket-builddie Zeile „Deployment completed successfully“. - Er vergleicht den Asset-Hash von
https://ticket.webklar.com/mitdist/index.html. - Er macht einen kurzen Sichttest im Chrome des Eigentümers, nur lesend.
- Der Agent prüft im Log von
- Zurücknehmen: Per Revert-Commit, immer das jüngste Paket. Ein früheres Paket geht nur zusammen mit allen späteren:
git revert --no-commit design-p<N-1>..design-p<N>. - Reihenfolge:
- Live gehen die Pakete in der Reihenfolge P1 bis P10.
- P3 bis P5 brauchen
ticketPathaus P2. - P4 liegt vor P5, weil P4 den Aufruf von
CreateCustomerForminAssignProjectPageumstellt. - P7, P8 und P9 hängen fachlich nicht voneinander ab, gehen aber auf demselben Branch in dieser Reihenfolge live.
- Parallelarbeit: Am nächsten Paket wird erst nach dem Tag des vorigen gearbeitet. Die Commits liegen hinter dem Tag und gehen deshalb nicht mit live.
Inhalt
| Paket | Umfang | Danach sichtbar |
|---|---|---|
| P0 Vorbereitung | LeadsPage.jsx und hooks/useLeads.js löschen. Beide sind toter Code. Die /leads-Umleitung, der pausierte Menüpunkt und lib/leads.js bleiben. node_modules wird aus Git genommen (git rm -r --cached node_modules; .gitignore führt es schon). Der Deploy installiert ohnehin neu. Im Worktree folgt ein echtes npm ci unter systemd-run mit Speichergrenze, ohne Symlink. Danach muss git status --short ohne ??-Zeilen leer sein. Das Screenshot-Werkzeug entsteht außerhalb des Repos (siehe Abnahme), und es werden Vorher-Bilder erzeugt. Fertig, wenn npm test und npm run build im Worktree grün laufen und ein Befehl alle Vorher-Bilder erzeugt. |
nichts |
| P1 Fundament | Tokens, Aliase und KOMPAT, Basis, Gerüst, Menü, Logo, Favicon. Alle ui/*-Bausteine samt Dialog und Stat, außerdem statusTone und der neue Login. Löschen: Lato, Footer.jsx, LetterGlitch.jsx, lib/sidebar.js samt Test und tote CSS-Regeln laut Skript-Liste. Pflicht-Fixes gegen Unlesbares: Chat-Variablen in chat.css unter .woms-chat (--chat-on-accent: #fff, --chat-line: var(--line-strong), --chat-muted: var(--text-2), Fehler- und Hinweistexte --red-fg/--text-2). .admin-primary-button bekommt weiße Schrift, Hover --accent-hover. Farbliterale umstellen in AssignedProjectCard, PreviewBlock, PreviewLinkButton, GiteaLinkButton, AnalyseRow, CreateWorksheetModal und WorksheetTimesModal (#a0aec0) sowie die helle Warn- und Fehlerschrift in admin-areas.css, serverpuls.css, AdminPage.css und im Finanz- und Rechtlich-Teil von global.css. chartColors.js wird angelegt und in Chart.jsx und ServerpulsPage benutzt. Abschluss-Prüfung: rg über alle Farbliterale in src. Jedes Literal mit Kontrast unter 3:1 auf Weiß, jede dunkle Schrift auf --accent und jedes Weiß-Alpha ist entweder behoben oder im Plan mit Zielpaket als bewusst offen eingetragen. |
ganze App weiß, Menü als Text, neues Login, alle Dialoge weiß; Innenlayouts noch alt, aber lesbar |
| P2 Tickets | Ticketliste, Ticket-Seite, Route, ticketRoutes.js, ticketListUrl.js und Arbeitsblätter. Dialoge CreateTicketModal, CreateWorksheetModal, WorksheetTimesModal, OwnSupportModal; SupportersEditor als Feldblock in den Arbeitsblatt-Dialogen. Native Auswahlfelder statt StatusDropdown/EditorDropdown, dazu der Dringend-Schalter und „Weitere Tickets“. Die sechs Link-Stellen wechseln auf ticketPath. Altklassen .tcard*, .dropdown*, .ticket-tab*, .worksheet-summary und .chevron löschen. „Neues Ticket“ bleibt inhaltlich wie heute, also automatische Zuweisung und kein Einsatzfeld. |
Ticket-Arbeitsplatz wie im Prototyp |
| P3 Planboard | .week mit sieben Spalten, Haarlinien und Wochenende --bg-subtle. Heute als schwarze Datumspille, Termine als Links auf ticketPath. Akquise-Termine hell orange, stornierte bleiben sichtbar wie heute. Kopf: „Kalenderwoche N · Datum bis Datum · n Termine“, Aktionen ← · Heute · →. Unter 860 px eine Spalte. |
Planboard ohne Karten |
| P4 Kunden | Kundenliste mit Reitern Feste Kunden · Leads · Archiviert · Alle. Die Zähler nutzen dieselbe Filterfunktion wie die Liste (lib/leads.js). Dazu Suche und Tabelle mit Unternehmen und Ansprechpartner, Kundennummer, Standort, E-Mail, Telefon. Badges „Lead“, „Abgesagt“, „Archiviert“ neben dem Namen wie heute; „Wichtig“ als Badge statt Stern. Kopf: für Admins „Neuer Kunde“ schwarz und „Neuer Lead“ als Standardknopf, für andere nur „Neuer Lead“ als Standardknopf. CreateCustomerForm und CreateLeadForm werden Dialoge, Hinweis- und Knopftexte bleiben; der Aufruf in AssignProjectPage wird mit umgestellt. Kundenseite mit Brotkrumen, Eyebrow und Reitern; ihr Tickets-Reiter verlinkt die Tickets. Übersicht als .two-col mit .kv, Stammdaten bleiben direkt bearbeitbar. CustomerFunktionen als Tabelle, die weiter abgeschaltetText zeigt (kommt aus tp2, siehe Risiken). Außerdem AboStatusCard, CustomerViewLog, KundenansichtButton (Standardknopf) und Grundstil von admin-areas.css. |
Kundenbereich wie im Prototyp |
| P5 Projekte | ProjectsPage als eine Tabelle: Projekt + Repo, Kunde (Auswahl je Zeile wie heute, deaktiviert bei archiviert oder während der Zuordnung, Rückfrage beim Entfernen), Art, Adresse, Ticket als Link, Status, Zuletzt geändert = $updatedAt, Aktionen. Die Aktionen sind Textlinks, nur wenn zutreffend: „Vorschau öffnen“, „In Gitea öffnen“, „Hosten“ (mit Ladezustand), „Aktualisieren“. Die erste Zelle ist ein <button class="link"> mit dem Projektnamen und öffnet „Projekt bearbeiten“. Auf dem Handy bleiben Projekt, Kunde, Status und Aktionen sichtbar. Die heutigen sechs Ansichten werden 1:1 Reiter: Aktive Projekte · Unzugeordnet · Zugeordnet · Vorlagen (nur Admins) · Archiviert · Alle inkl. Archiv. Dazu Suche, Auswahl Kunde und Mitarbeiter, „Weitere Treffer“ und Hinweiszeile. „Neues Projekt“ als Dialog mit Repo-Name. PreviewBlock steht außerhalb des Formulars, der Dialog ist wide. Die Liste zeigt „Wird geladen …“ nur beim ersten Laden. Außerdem: AssignProjectPage mit Schritten als Abschnitten und Auswahl auch als Text markiert; ServicesPage; TicketProjects; AssignedProjectCard; AnalyseRow (Zustimmung als Dialog, Wortlaut unverändert); PreviewBlock; PreviewLinkButton („Vorschau öffnen“); GiteaLinkButton („In Gitea öffnen“); CopyableCredential („Anzeigen“/„Verbergen“, „Kopieren“/„Kopiert“). |
Projektbereich wie im Prototyp |
| P6 Kunden-Unterseiten | Chat: Liste als Haarlinien-Zeilen. Alle Regeln, die Elemente im Verlauf und in der Eingabezeile gestalten, tragen das Präfix .woms-chat, bei Bedarf .woms-chat.woms-chat, nie !important. Das Seitenlayout (.chat-page, .chat-side, .chat-list*, .chat-main, .chat-back) bleibt ohne Präfix. Fremde Blase --bg-hover, eigene --grey-bg. loadChatComposer.js, sein Test und die Container in ChatThread.jsx bleiben unverändert. Gutschriften: Filter als Reiter, Tabelle mit Spaltenpriorität, Detail als .two-col, Knöpfe Genehmigen (schwarz), Rückfrage (Standard), Ablehnen (rot als Text). Abo-Angebote: Tabelle, Formular als Dialog. E-Mail-Speicher: Tabelle mit data-storage-done. |
Chat und Kunden-Unterseiten hell |
| P7 Finanzen | Finanz-CSS neu (ca. 320 Zeilen), alle Finanzkomponenten. LiquidityHead wechselt von Kpi auf Stat, der Kpi-Export wird gelöscht. Dialoge AssignDialog und InvoiceCreateModal (wide). Bleiben: die Anker INVOICES_ANCHOR und finanzen-buchungen samt scroll-margin-top, die Klassen-Hooks, data-label, role-Attribute und Container-Queries; container-type wandert auf die Wrapper. Beträge behalten ihr Vorzeichen, negative sind rot. |
Finanzen ohne Karten |
| P8 Rechtliches, Admin | LegalPage, LegalTextsTab, ProviderContractsTab als Tabelle, ContractForm/VersionForm als Dialoge. AdminPage mit Tabelle, Formular als Dialog und Textaktionen „Bearbeiten“/„Löschen“. Fehler erscheinen als .msg.err im Dialog, der erst bei Erfolg schließt. Die einmal angezeigten Zugangsdaten bleiben ein Abschnitt auf der Seite. AdminPage.css wird neu geschrieben; .credential*, .sr-only und .spin ziehen nach global.css um. |
Rechtliches und Admin ohne Karten |
| P9 Serverpuls | Kopf mit Text-Badge statt Emoji. Warnungen als Listenzeilen mit Stufen-Badge, Kacheln als .stats mit Sparkline. Deltas als Textfarbe (rot = gestiegen, grün = gesunken), Umschalter .seg. Alle data-testid bleiben. |
Serverpuls hell |
| P10 Aufräumen | react-icons und @tabler/icons-react deinstallieren, sobald rg "react-icons|@tabler" src 0 Treffer liefert. KOMPAT-Block, Aliase und tote Klassen löschen. previewEnv.dotColor samt Test entfernen. Die Kommentare zu Icons in navLinks.js und im Test anpassen. Die Bundle-Größe vorher und nachher dokumentieren. Die ?woid=-Umleitung bleibt. |
nichts Neues, kleineres Bundle |
Seiten ohne Prototyp-Vorlage
Gemeint sind P6 bis P9, ServicesPage und AssignProjectPage. Sie entstehen aus denselben Bausteinen nach festen Ersetzungen:
- Karte → Abschnitt
- Kachelraster → Tabelle oder Liste
- farbiger Streifen → Badge
- Icon-Knopf → Textknopf oder Textlink
Der Eigentümer nimmt diese Pakete anhand von Screenshots ab, bevor er pusht.
Texte
Texte bleiben wörtlich erhalten. Ausnahmen sind nur diese neuen oder geänderten Texte:
Login: „Anmelden“, „E-Mail“, „Passwort“, „Weiter“, „Code eingeben“, „Code erneut senden“, „Abbrechen“ sowie der Untertitel „Ticketsystem für das WEBklar-Team“.
Menü und Laden: „Abmelden“, „Menü“, „Wird geladen …“ statt „Loading...“.
Ticketliste:
- Reiter und Art-Eintrag „Aufträge und Akquise“
- Fuß „Neueste zuerst“
- „Niemand“, „Unbekannt“
Ticketseite:
- „Nicht zugewiesen“ (statt „(Unassigned)“), „Unbekannte Person“
- „Kein Termin“
- „Weitere Tickets von {Kunde}“
- „Status danach: X“
- Hinweis für Tickets ohne Ticketnummer
- Zeitformat
stepLabel
Weitere Seiten:
- Planboard-Kopf
- Badge „Wichtig“
- Spaltenköpfe der neuen Tabellen
Knöpfe: Textknöpfe, die bisher Icons waren:
- „Vorschau öffnen“, „In Gitea öffnen“
- „Bearbeiten“, „Löschen“, „Entfernen“, „Lösen“
- „Anzeigen“, „Verbergen“, „Kopieren“, „Kopiert“
- „Erneut versuchen“, „Aktualisieren“, „Hosten“
Ladetexte in Knöpfen: „Speichert …“, „Sendet …“, „Wird geladen …“.
Was gleich bleibt
Verhalten:
- Alle Hooks und alle Lib-Logik. Einzige Ausnahme: das Verwerfen veralteter Antworten in
useWorkorders. Neue Hooks sind erlaubt (useWorkorder, Zähler, Zeitsummen). - Auth und 2FA.
- Alle Abfrage-Intervalle.
- Pflichtfelder und 15-Minuten-Schritte.
- Doppelklick-Sperren.
- Alle
window.confirm-Rückfragen. - Alle
data-testid. - Die URL-Parameter
?tab=,?antrag=,?kunde=.
Daten:
- Appwrite-Schema, Rechte und gespeicherte Werte (englische Status, die das Kundenportal auswertet).
- Server-APIs.
- Portal-Repo.
.env*: Latin-1, nicht anfassen.
Texte:
- Alle rechtlich relevanten Texte:
- Zustimmung zur Website-Analyse
- Abo-, Gutschrift- und § 19-Hinweise
- Steuer-Hinweis
- Mail- und Chat-Vorlagen.
- Fehlermeldungen.
Risiken und Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|---|---|
| Ein einziges CSS-Bündel: umbenannte Klassen brechen Seiten, die gerade niemand ansieht | KOMPAT ab P1. Am Ende von P1 ein Skript, das alle in JSX und lib/*.js benutzten Klassen gegen die CSS-Regeln abgleicht; Ergebnis im Plan. Screenshot-Pflichtliste nach jedem Paket. |
Namensgleiche Klassen mit anderer Bedeutung: .btn, .badge, .page, .field, .seg, .toolbar, .empty, .list-row, .urgent-toggle, .overlay, .id |
in P1 bewusst ersetzen, jede Altverwendung im Screenshot prüfen |
79 !important im Altbestand |
löschen statt überschreiben |
| Alt-Dialoge bleiben bis P2/P5/P7/P8 in Betrieb | KOMPAT-Regeln für .overlay, .overlay-content, .overlay-close. P1-Abnahme: jedes der 10 Alt-Dialoge bei 390×700 und 1440×700, Speichern-Knopf erreichbar. |
| Fremder Chat-Composer, sein CSS lädt nach dem WOMS-CSS | Präfix-Regel und --chat-*-Variablen, siehe P1 und P6 |
| Diagramme auf Canvas | Farbkonstanten statt Variablen, ab P1 hell |
| Statusfarben ändern ihre Bedeutung | Hinweis an das Team beim Live-Gang von P1 |
Dokument-Scroll statt innerem Scroller: position: sticky in AdminPage, Scroll-Anker der Finanzen |
in der Abnahme gezielt prüfen |
| Vorschlagslisten in scrollenden Dialogen werden abgeschnitten | in P2, P6 und P7 prüfen |
| Freitext-Datumsfelder, keine Fehlergrenze: ein Parserfehler legt die App lahm | nur über parseGermanDate/formatTime anzeigen. Testdaten mit Altwerten: 2.5.2026, 31.02.2026, ISO-Datum, leer, ohne woid, unbekannter Status. |
Paralleler Branch feature/terminplaner-tp2: Commit 6f03712 ändert CustomerFunktionen.jsx, lib/funktionenView.js (neue Funktion abgeschaltetText) und den Test (+4 Tests) |
vor P4 nach test bringen (Eigentümer) oder per git cherry-pick 6f03712 in dev-design holen. Die Testzahl steigt dann um 4. |
node_modules aus Git nehmen (P0) |
betrifft nur Git, nicht den Deploy, weil deploy.sh löscht und neu installiert. Andere Worktrees mit Symlink sind unkritisch. |
| Speicher knapp | Installation, Build und Screenshots unter systemd-run mit Speichergrenze |
Abnahme
Ein Paket ist fertig, wenn alles Folgende erfüllt ist:
- Tests:
npm testläuft im Worktree grün. Die Testzahl wird je Paket notiert. Ausgangsstand 312; P1 minus 3 (sidebar) plus neue Tests; nach tp2 plus 4. Neue Tests gibt es fürstatusTone,ticketPath,ticketListUrl, die ausgelagerte Arbeitsblatt-Logik und die Zeitsummen. - Build:
npm run buildist grün, die Bundle-Größe wird notiert. - Screenshots mit 1440 px und 390 px über die Pflichtliste:
- Login inklusive beider Code-Schritte
- Ticketliste (mit und ohne Suche), Ticket mit beiden Reitern, alle Ticket-Dialoge
- Planboard
- Kundenliste und Kundenseite mit allen Reitern
- Chat
- Gutschriften, Abo-Angebote, E-Mail-Speicher
- Projekte mit beiden Dialogen, Zuordnen, Services
- Zustimmung der Website-Analyse
- Finanzen mit Dialogen
- Rechtliches
- Admin inklusive „Mitarbeiter anlegen“
- Serverpuls mit Diagrammen
- in P1 zusätzlich die 10 Alt-Dialoge
- Screenshot-Werkzeug:
- Es liegt unter
/root/reports/ticketsystem-design-2026-10-04/werkzeug/, außerhalb des Repos, und ändert wederpackage.jsonnoch Lock nochtests/. - Aufbau: esbuild-Bündel und Playwright Headless mit nachgebildetem Appwrite. Es nutzt nur Testdaten, inklusive der Altwerte aus der Risikotabelle, keine Produktionsdaten und keine echten Logins.
- Es läuft unter
systemd-runmitMemoryMax. - Die Ausgabe liegt je Paket unter
shots/p<N>/1440undshots/p<N>/390.
- Es liegt unter
- Code-Hygiene im umgebauten Bereich:
- keine Icon-Importe
- keine neuen
style={{…}}, außer für Werte, die erst zur Laufzeit feststehen (z. B. berechnete Breiten) - Texte nur wie unter „Texte“ geändert
- Code-Review: Ein Review (sonnet) ist gelaufen, wichtige Befunde sind behoben.
- Freigabe: Der Eigentümer hat die Screenshots gesehen, bevor er das Tag pusht.