Hustler-Pixelwelt T12: Prüfstand auf die Pixelwelt umgestellt (Szenarien A–M), Handy-Kopfzeile zweireihig
Prüfstand: entry.jsx zeigt nur noch die echte Seite WorkersPage (BrowserRouter, Adresse mit ?tab= lesbar); shot.cjs neu mit den Szenarien A–M (Hochhaus, Regie, Handy, weniger Bewegung, Notbremse, Ausfall, Detail, Etagen, Pause, Pult P3, Pult P4, Keller, Handy Pult/Keller), harte Prüfungen und Bildern mit Szenario-Vorsilbe; Befunde an der App stehen getrennt von den Verstößen. Skript-API: Gründe der Bot-Leads wörtlich wie im Dienst. Kopfzeile am Handy: zwei Reihen (Geld + Pause/Zahnrad, dann die fünf Kennzahlen), schmaler Panel-Rand, 40-px-Knöpfe, Querformat unter 500 px Höhe in einer Reihe; hustlerKopf.test um die Zusicherungen dazu ergänzt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ebab573803
commit
2470f05acb
6 files changed
+1431
-2053
No files matched your search
@@ -3,7 +3,7 @@
|
||||
// outDir muss ein neuer oder leerer Ordner unter dem Temp-Ordner (TMPDIR, sonst /tmp) oder unter .superpowers/ im Repo sein.
|
||||
// Die Schrift /fonts/* bleibt außen vor: der Server von shot.cjs liefert sie aus public/fonts.
|
||||
// Drei Module der App hängen an Appwrite (Anmeldung, Mitarbeiterliste, Sitzungs-JWT) und werden nur im Prüfstand ausgetauscht, damit die
|
||||
// echte Seite WorkersPage (Reiter Hochhaus, Freigabe und Bot-Leads) samt PultList, ProspectPanel und BotLeadList ohne Netz läuft: AuthContext -> stub-auth.js,
|
||||
// echte Seite WorkersPage (Bildschirme Hochhaus, Pult und Keller samt Brief, Akte, ProspectPanel und Einstellungen) ohne Netz läuft: AuthContext -> stub-auth.js,
|
||||
// useEmployees -> stub-employees.js, workersClient -> stub-client.js. Am Quelltext der App ändert das nichts.
|
||||
import esbuild from 'esbuild'
|
||||
import fs from 'node:fs'
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
// Einstiegspunkt des Prüfstands: das Hochhaus-Board der Hustler-Seite mit der Skript-API, ohne Anmeldung und ohne Appwrite.
|
||||
// Drumherum steht dieselbe Hülle wie in der App (Menüleiste links, Seitenrand), damit die Bühne genauso breit wird wie im echten Betrieb.
|
||||
// ?halted=1 startet mit gesetzter Bounce-Notbremse (H4). window.__wk gibt es nur hier: die Bühne und die Skript-API für shot.cjs.
|
||||
// ?fault=none|down|session|hang schaltet zwischen Board und Skript-API den echten API-Client (createWorkersApi) mit einer Fetch-Weiche
|
||||
// (scripted-fetch.js): window.__wk.control.fault lässt sich zur Laufzeit auf null | 'down' | 'session' | 'hang' setzen (Ausfall-Szenario).
|
||||
// Ohne den Parameter bekommt das Board die Skript-API direkt (Szenarien A bis E).
|
||||
// ?page=1 zeigt statt des Boards die echte Seite WorkersPage (Reiter Hochhaus, Freigabe und Bot-Leads; PultList, ProspectPanel, BotLeadList); AuthContext,
|
||||
// useEmployees und workersClient sind dort durch stub-*.js ersetzt (build.mjs). ?tab=freigabe öffnet den Reiter gleich (Deep-Link), ebenso ?tab=botleads
|
||||
// mit ?bot=<Id> (Einzelansicht aus dem Ereignisstrom) und ?botleads=leer (die Liste ist leer: Leerzustand ohne Filter). Die Skript-API liefert 60 feste
|
||||
// Bot-Leads samt drei gekürzten Stubs (scripted-api.js, „Bot-Leads (P6)“).
|
||||
// Mit ?page=1&fault=none läuft die Seite über den echten API-Client und die Fetch-Weiche (Pult, Aktionen, Interessent).
|
||||
// ?p4=1 (nur mit ?page=1) nimmt den Datensatz für die Gespräche und die Werkstatt (P4: als Mensch antworten, Vorschau freigeben und bauen,
|
||||
// Antwort des Kunden einfügen) statt der P3-Einträge.
|
||||
import { StrictMode, createRef } from 'react'
|
||||
// Einstiegspunkt des Prüfstands: die echte Seite WorkersPage der Pixelwelt (Hochhaus, Pult, Keller) mit der Skript-API, ohne Anmeldung und ohne Appwrite.
|
||||
// Drumherum steht dieselbe Hülle wie in der App (Menüleiste links, Seitenrand), damit die Welt genauso groß wird wie im echten Betrieb.
|
||||
// Der Router ist ein BrowserRouter: ?tab=… und ?bot=… stehen in der echten Adresse des Browsers, ein Wechsel des Bildschirms (Etage Freigabe, „◂ Hochhaus“)
|
||||
// ändert sie wie in der App; die Adresse lässt sich also lesen und prüfen. Die Parameter des Prüfstands selbst werden nur beim Laden gelesen.
|
||||
// AuthContext, useEmployees und workersClient sind durch stub-*.js ersetzt (build.mjs); die Seite bekommt ihre API über harness-bridge.js.
|
||||
// Parameter (alle optional):
|
||||
// ?tab=hochhaus|freigabe|botleads öffnet den Bildschirm gleich (Deep-Link wie aus dem Ereignisstrom); mit &bot=<Id> den Einzelfall im Keller
|
||||
// ?halted=1 startet mit gesetzter Bounce-Notbremse (Banner „Versand gestoppt“)
|
||||
// ?fault=none|down|session|hang die Seite läuft über den echten API-Client (createWorkersApi) und die Fetch-Weiche scripted-fetch.js;
|
||||
// window.__wk.control.fault lässt sich zur Laufzeit auf null | 'down' | 'session' | 'hang' setzen (Ausfall).
|
||||
// Ohne den Parameter bekommt die Seite die Skript-API direkt.
|
||||
// ?p4=1 nimmt für das Pult den Datensatz der Gespräche und der Werkstatt (als Mensch antworten, Vorschau freigeben und
|
||||
// bauen, Antwort des Kunden einfügen) statt der P3-Einträge
|
||||
// ?botleads=leer die Bot-Lead-Liste ist leer (Leerzustand ohne Filter)
|
||||
// window.__wk = { api, control }: die Skript-API (Schalter faults, pultEmpty, pultCalls, prospectCalls, pauseCalls, botleadCalls …) und die Steuerung der
|
||||
// Fetch-Weiche (fault, calls, jwts) für shot.cjs.
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import '../../src/styles/global.css'
|
||||
import '../../src/styles/workers.css'
|
||||
import PageHeader from '../../src/components/ui/PageHeader'
|
||||
import WorkersBoard from '../../src/components/workers/WorkersBoard'
|
||||
import WorkersPage from '../../src/pages/WorkersPage'
|
||||
import { createWorkersApi } from '../../src/lib/workersApi.js'
|
||||
import { createScriptedApi } from './scripted-api.js'
|
||||
@@ -25,15 +25,13 @@ import { createScriptedFetch } from './scripted-fetch.js'
|
||||
import { holder } from './harness-bridge.js'
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const pageMode = params.get('page') === '1'
|
||||
const scripted = createScriptedApi({
|
||||
now: () => performance.now(),
|
||||
base: Date.now(),
|
||||
halted: params.get('halted') === '1',
|
||||
pult: pageMode ? (params.get('p4') === '1' ? 'p4' : true) : false,
|
||||
pult: params.get('p4') === '1' ? 'p4' : true,
|
||||
botleadsEmpty: params.get('botleads') === 'leer',
|
||||
})
|
||||
const stage = createRef()
|
||||
const fault = params.get('fault')
|
||||
const control = { fault: fault && fault !== 'none' ? fault : null, calls: [], jwts: [] }
|
||||
// Zeitgrenze im Prüfstand kurz (1,5 s statt 20 s), damit der Ausfall „Antwort kommt nie“ in Sekunden sichtbar wird
|
||||
@@ -47,31 +45,20 @@ const api = fault === null
|
||||
fetchImpl: createScriptedFetch(scripted, control),
|
||||
timeoutMs: 1500,
|
||||
})
|
||||
window.__wk = { stage, api: scripted, control }
|
||||
window.__wk = { api: scripted, control }
|
||||
holder.api = api
|
||||
|
||||
const tab = params.get('tab')
|
||||
const bot = params.get('bot')
|
||||
const entries = [pageMode && tab ? `/hustler?tab=${encodeURIComponent(tab)}${bot ? `&bot=${encodeURIComponent(bot)}` : ''}` : '/hustler']
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<MemoryRouter initialEntries={entries}>
|
||||
<BrowserRouter>
|
||||
<div className="shell">
|
||||
<div className="sidebar" aria-hidden="true" />
|
||||
<div className="main">
|
||||
<main id="inhalt">
|
||||
{pageMode ? (
|
||||
<WorkersPage />
|
||||
) : (
|
||||
<div className="page wk-page">
|
||||
<PageHeader title="Hustler" subtitle="Die Nachtschicht deiner Claude-Armee" />
|
||||
<WorkersBoard api={api} isAdmin stageRef={stage} />
|
||||
</div>
|
||||
)}
|
||||
<WorkersPage />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</MemoryRouter>
|
||||
</BrowserRouter>
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -494,14 +494,23 @@ export const BOTLEAD_IDS = Object.freeze({
|
||||
const BOTLEAD_ROOM_LABEL = Object.freeze({ aerzte: 'Ärzte & Praxen', handwerk: 'Handwerk', selbst: 'Selbstständige', laden: 'Läden & Gastro' })
|
||||
const BOTLEAD_ANCHOR_MS = Date.parse('2026-10-17T20:00:00.000Z')
|
||||
const BOTLEAD_STEP_MS = 14.5 * 60 * 60 * 1000
|
||||
// Die 14 festen Grundtexte des Dienstes (workers-dev-p6/src/scoring/business.js, Konstante GRUND; wörtlich wie tests/fixtures/botlead-gruende.json): die Akte im Keller
|
||||
// erkennt sie am Wortlaut und zeigt Symbol und Erklärung dazu. Die Stelle 2 („Website in gutem Zustand“) bestimmt unten, welche Einträge eine Website haben.
|
||||
const BOTLEAD_REASONS = Object.freeze([
|
||||
'Keine veröffentlichte E-Mail-Adresse gefunden (nur ein Kontaktformular).',
|
||||
'Gehört zu einer Kette oder einem Filialverbund, wird nicht angeschrieben.',
|
||||
'Website ist in gutem Zustand (modern, mobil nutzbar), kein Bedarf erkennbar.',
|
||||
'Kein Mail-Platz mehr in dieser Nacht (Tageslimit erreicht).',
|
||||
'Mail nur über eine Sammeladresse des Verbunds, nicht eindeutig dem Betrieb zuzuordnen.',
|
||||
'Lesen der Seite fehlgeschlagen (Zeitüberschreitung).',
|
||||
'Name fehlt in Impressum und Karte.',
|
||||
'keine veröffentlichte Mail',
|
||||
'Kette oder Franchise',
|
||||
'Website in gutem Zustand',
|
||||
'Punktzahl unter 55',
|
||||
'Adresse nicht zustellbar',
|
||||
'veraltet (nicht ausgewählt)',
|
||||
'schon bekannt (E-Mail oder Telefon)',
|
||||
'schon bekannt (Name und PLZ)',
|
||||
'Sperrliste',
|
||||
'Lesen fehlgeschlagen',
|
||||
'Impressum passt nicht zum Betrieb',
|
||||
'Lauf unterbrochen',
|
||||
'Bewertung ausgefallen',
|
||||
'Name fehlt',
|
||||
])
|
||||
const BOTLEAD_LONG_REASON = 'Impressum nennt nur eine Postfachadresse und ein Kontaktformular; die einzige Mail-Adresse (verwaltung@sehr-langer-domainname-der-nicht-umbricht-handwerksbetrieb-westpfalz-gruppe.example) gehört einem Dritten.'
|
||||
const BOTLEAD_LONG_NAME = 'Elektroinstallation-und-Haustechnik-Hoffmann-Sonnenschein-Wittgensteiner GmbH'
|
||||
|
||||
+1357
-2002
File diff suppressed because it is too large.
Load diff
@@ -49,7 +49,7 @@
|
||||
.hk-hochhaus .hk-kopf .hk-nur-tastatur { font: inherit; }
|
||||
.hk-hochhaus .hk-hud-knopf {
|
||||
display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -6px 0; padding: 0;
|
||||
border: 0; background: none; cursor: pointer;
|
||||
border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.hk-hochhaus .hk-hud-knopf:disabled { opacity: 0.5; cursor: progress; }
|
||||
|
||||
@@ -88,19 +88,39 @@
|
||||
.hk-hochhaus .hk-rueckfrage-text { margin: 0; color: #b8c2f0; }
|
||||
.hk-hochhaus .hk-rueckfrage-knoepfe { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
||||
|
||||
/* ---------- Handy: die Kopfzeile bricht um, Schrift etwas kleiner ---------- */
|
||||
/* ---------- Handy: kompakte Panels, bei Hochformat in zwei Reihen ----------
|
||||
Kompakt (Hochformat unter 720 px und Querformat unter 500 px Höhe): schmaler Rand (8 px statt 16 px, die Rahmenkachel wird 1:1 statt 2:1 gezeichnet), Schrift
|
||||
kleiner, Knöpfe 40 px (Tippfläche). Hochformat unter 720 px: Reihe 1 = Geld + Pause/Zahnrad, Reihe 2 = die fünf Kennzahlen; das Geld-Panel nimmt den Platz,
|
||||
den das rechte Panel übrig lässt, und bricht erst bei sehr langen Beträgen um. Quer (kurz, aber breit) passt alles in eine Reihe. */
|
||||
@media (max-width: 719px), (max-height: 500px) {
|
||||
.hk-hochhaus .hk-kopf .hk-panel { border-width: 8px; }
|
||||
.hk-hochhaus .hk-kopf-geld { gap: 12px; padding: 0 4px; }
|
||||
.hk-hochhaus .hk-kopf-wert { gap: 6px; }
|
||||
.hk-hochhaus .hk-kopf-betrag { font-size: 11px; line-height: 16px; }
|
||||
.hk-hochhaus .hk-kopf-rechts { gap: 0; }
|
||||
.hk-hochhaus .hk-hud-knopf { width: 40px; height: 40px; margin: -4px 0; }
|
||||
.hk-hochhaus .hk-kopf-zahlen { gap: 4px 12px; padding: 0 4px; font-size: 11px; line-height: 16px; }
|
||||
.hk-hochhaus .hk-kopf-zahl { gap: 4px; }
|
||||
}
|
||||
@media (max-width: 719px) {
|
||||
.hk-hochhaus .hk-kopf { left: 8px; right: 8px; top: 8px; gap: 8px; font-size: 14px; line-height: 18px; }
|
||||
.hk-hochhaus .hk-kopf-zeile { gap: 6px 8px; }
|
||||
.hk-hochhaus .hk-kopf-zeile { gap: 8px; align-items: stretch; }
|
||||
.hk-hochhaus .hk-kopf-abstand { display: none; }
|
||||
.hk-hochhaus .hk-kopf-geld { order: 1; gap: 12px; }
|
||||
.hk-hochhaus .hk-kopf-betrag { font-size: 12px; line-height: 16px; }
|
||||
.hk-hochhaus .hk-kopf-rechts { order: 2; margin-left: auto; }
|
||||
.hk-hochhaus .hk-kopf-zahlen { order: 3; flex: 1 1 100%; gap: 6px 10px; font-size: 11px; line-height: 16px; }
|
||||
.hk-hochhaus .hk-kopf-geld { order: 1; flex: 1 1 0; min-width: 0; justify-content: space-between; gap: 8px; }
|
||||
.hk-hochhaus .hk-kopf-rechts { order: 2; flex: none; }
|
||||
.hk-hochhaus .hk-kopf-zahlen { order: 3; flex: 1 1 100%; justify-content: space-between; flex-wrap: nowrap; gap: 4px; }
|
||||
.hk-hochhaus .hk-meldung { padding: 6px 10px 6px 40px; }
|
||||
.hk-hochhaus .hk-rueckfrage-hintergrund { padding: 12px; }
|
||||
.hk-hochhaus .hk-rueckfrage-titel { font-size: 13px; }
|
||||
}
|
||||
/* Sehr schmale Handys (bis 379 px): Beträge und Kennzahlen eine Stufe kleiner */
|
||||
@media (max-width: 379px) {
|
||||
.hk-hochhaus .hk-kopf-geld { gap: 4px; }
|
||||
.hk-hochhaus .hk-kopf-wert { gap: 4px; }
|
||||
.hk-hochhaus .hk-kopf-betrag { font-size: 9px; line-height: 16px; }
|
||||
.hk-hochhaus .hk-kopf-zahl { gap: 2px; }
|
||||
.hk-hochhaus .hk-kopf-zahlen { font-size: 9px; }
|
||||
}
|
||||
|
||||
/* ---------- Weniger Bewegung: kein Blinken (der Betrag bleibt auch ohne Blinken erkennbar) ---------- */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -775,6 +775,13 @@ test('CSS hustler-kopf.css: nur hk-Klassen unter .hk-hochhaus, Keyframes hk-kopf
|
||||
for (const k of keyframes) assert.match(k, /^hk-kopf-/)
|
||||
for (const m of css.matchAll(/animation:\s*([\w-]+)/g)) assert.ok(keyframes.includes(m[1]) || m[1] === 'none', m[1])
|
||||
assert.match(css, /@media \(max-width: 719px\)/)
|
||||
// Handy (T12): hochkant genau zwei Reihen – Geld und rechtes Panel in Reihe 1 (order 1/2), die fünf Kennzahlen als eigene Reihe 2 (order 3, volle Breite);
|
||||
// kompakte Panels (schmaler Rand) und Knöpfe von 40 px auch im Querformat unter 500 px Höhe
|
||||
assert.match(css, /@media \(max-width: 719px\) \{[^@]*\.hk-hochhaus \.hk-kopf-geld \{[^}]*order: 1;/)
|
||||
assert.match(css, /@media \(max-width: 719px\) \{[^@]*\.hk-hochhaus \.hk-kopf-rechts \{[^}]*order: 2;/)
|
||||
assert.match(css, /@media \(max-width: 719px\) \{[^@]*\.hk-hochhaus \.hk-kopf-zahlen \{[^}]*order: 3;[^}]*flex: 1 1 100%;[^}]*flex-wrap: nowrap;/)
|
||||
assert.match(css, /@media \(max-width: 719px\), \(max-height: 500px\) \{[^@]*\.hk-hochhaus \.hk-kopf \.hk-panel \{[^}]*border-width: 8px;/)
|
||||
assert.match(css, /@media \(max-width: 719px\), \(max-height: 500px\) \{[^@]*\.hk-hochhaus \.hk-hud-knopf \{[^}]*width: 40px;[^}]*height: 40px;/)
|
||||
assert.match(css, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.hk-api-warn\s*\{\s*animation:\s*none/)
|
||||
// Meldungen sind nur Text (Dekor per ::before), die Hülle fängt keine Zeigerereignisse ab
|
||||
assert.match(css, /\.hk-hochhaus \.hk-kopf \{[^}]*pointer-events: none/)
|
||||
|
||||
Reference in new issue
Block a user