From 871b87fac093191671ba8c3e0e336d8b9537c0fe Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 23:09:42 +0000 Subject: [PATCH] =?UTF-8?q?Workers=20P4=20Endpr=C3=BCfung=20W2:=20?= =?UTF-8?q?=E2=80=9EVorschau=20bauen=E2=80=9C=20fragt=20vor=20dem=20Start?= =?UTF-8?q?=20nach=20und=20fehlt=20bei=20toten=20Interessenten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Knopf startete einen Opus-Lauf mit einem einzigen Klick (Handy: voll breit zwischen anderen Knöpfen), ohne dass sich der Bau in der Oberfläche zurücknehmen lässt. Wie Freigeben und Verwerfen fragt er jetzt zuerst nach („Wirklich bauen?“, 4 s) und zeigt dabei, dass ein Opus-Lauf startet und die Vorschau gebaut wird, als hätte der Interessent Ja gesagt (ohne „nur wenn Ja“: der Knopf gilt gerade für unklare Antworten). rowModel blendet den Knopf bei gesperrtem, verlorenem oder abgeschlossenem Interessenten (gesperrt, verloren, deal) aus; ohne Kurzfassung entscheidet weiter der Dienst. Prüfstand: die Fehler- und Fokus-Läufe klicken zweimal, Szenario bauen prüft Frage, Hinweis und Verfall. Doppeltipp-Schutz und Dienst-Härtung (bauLauf nur nach Ja) bleiben offen, siehe final-fix-report-woms.md. Co-Authored-By: Claude Sonnet 5.5 --- scripts/workers-harness/shot.cjs | 17 +++++++--- src/components/workers/PultList.jsx | 28 +++++++++++----- src/lib/workersPult.js | 8 +++-- tests/unit/workersPult.test.mjs | 15 +++++++++ tests/unit/workersPultUi.test.mjs | 50 ++++++++++++++++++++++++++--- 5 files changed, 99 insertions(+), 19 deletions(-) diff --git a/scripts/workers-harness/shot.cjs b/scripts/workers-harness/shot.cjs index 68b5ff0..97136d0 100644 --- a/scripts/workers-harness/shot.cjs +++ b/scripts/workers-harness/shot.cjs @@ -1405,18 +1405,20 @@ async function bauen(browser, base) { // 1. Fehler vom Dienst await setPultFault(page, 'start-build', { status: 409, message: 'Für diesen Interessenten gibt es schon eine Vorschau.' }) await build.click() + await build.click() await uns.locator('.msg.err').waitFor() check('bauen: 409 ⇒ „Für diesen Interessenten gibt es schon eine Vorschau.“ in der Zeile (rot, role=alert), Zeile bleibt', (await textOf(uns.locator('.msg.err'))) === 'Für diesen Interessenten gibt es schon eine Vorschau.' && (await uns.locator('.msg.err').getAttribute('role')) === 'alert' && (await uns.count()) === 1, await textOf(uns.locator('.msg.err'))) check('bauen: nach dem Fehler ist „Vorschau bauen“ wieder frei', await uns.getByRole('button', { name: 'Vorschau bauen', exact: true }).isEnabled()) await shoot(page, 'bauen-409.png') await setPultFault(page, 'start-build', { status: 409, message: GONE }) await build.click() + await build.click() await page.waitForFunction((t) => Array.from(document.querySelectorAll('li.wk-pult-row .msg.err')).some((el) => el.textContent === t), GONE) check('bauen: zweiter 409 („Dieser Kontakt ist nicht mehr aktiv.“) ersetzt die Meldung', (await uns.locator('.msg').count()) === 1 && (await textOf(uns.locator('.msg'))) === GONE) await setPultFault(page, 'start-build', null) - // 2. Fragt „Vorschau bauen“ nach? Festgestellt wird nur (metrics.bauenFragtNach): ruft der erste Klick den Dienst auf, gibt es keine Rückfrage - // (anders als bei Freigeben und Verwerfen). Fragt der Knopf nach, bestätigt der zweite Klick, damit der Rest des Szenarios gleich läuft. + // 2. „Vorschau bauen“ fragt wie Freigeben und Verwerfen zuerst nach (Endprüfung W2): der erste Klick ruft den Dienst nicht auf, die Frage verfällt nach 4 s, + // erst der zweite Klick startet den Bau. metrics.bauenFragtNach hält fest, ob der erste Klick folgenlos blieb. await setDelay(page, 1500) const before = (await callsOf(page, 'start-build')).length await build.click() @@ -1424,7 +1426,12 @@ async function bauen(browser, base) { const afterFirst = (await callsOf(page, 'start-build')).length metrics.bauenFragtNach = afterFirst === before metrics.bauenErsterKlickRuftDienst = afterFirst === before + 1 - if (afterFirst === before) await build.click() + check('bauen: erster Klick fragt „Wirklich bauen?“ (mit Hinweis zu Opus-Lauf), noch keine Aktion am Dienst', (await textOf(build)) === 'Wirklich bauen?' && afterFirst === before && (await textOf(uns.locator('.wk-pult-actions'))).includes('Startet einen Opus-Lauf'), await textOf(build)) + await shoot(page, 'bauen-frage.png') + await sleep(4400) + check('bauen: nach 4 s verfällt die Rückfrage (wieder „Vorschau bauen“, kein Hinweis), noch immer keine Aktion', (await textOf(build)) === 'Vorschau bauen' && (await callsOf(page, 'start-build')).length === before && !(await textOf(uns.locator('.wk-pult-actions'))).includes('Opus'), await textOf(build)) + await build.click() + await build.click() check('bauen: während der Bau angelegt wird sind alle Knöpfe der Zeile gesperrt', (await uns.getByRole('button', { name: 'Ich kümmere mich', exact: true }).isDisabled()) && (await uns.getByRole('button', { name: 'Als Mensch antworten', exact: true }).isDisabled()) && (await uns.getByRole('button', { name: 'Erledigt', exact: true }).isDisabled()) && (await uns.getByRole('button', { name: 'Verwerfen', exact: true }).isDisabled()) && (await uns.getByRole('button', { name: 'Vorschau bauen', exact: true }).isDisabled())) await shoot(page, 'bauen-pending.png') await finishedLine(page).first().waitFor() @@ -1518,7 +1525,9 @@ async function fokus(browser, base) { check('fokus: nach „Freigeben & senden“ (Zeile verschwindet) steht der Fokus nicht auf ', noBody(fApprove), fApprove) const uns = row4(page, T4.uns) - await uns.getByRole('button', { name: 'Vorschau bauen', exact: true }).click() + const buildBtn = uns.getByRole('button', { name: /^(Vorschau bauen|Wirklich bauen\?)$/ }) + await buildBtn.click() + await buildBtn.click() await page.waitForFunction((t) => document.querySelector('.wk-pult-filters ~ .msg') && document.querySelector('.wk-pult-filters ~ .msg').textContent.includes(t), 'wird gebaut') await sleep(200) const fBuild = await focusInfo(page) diff --git a/src/components/workers/PultList.jsx b/src/components/workers/PultList.jsx index 1fe2f5b..5512b74 100644 --- a/src/components/workers/PultList.jsx +++ b/src/components/workers/PultList.jsx @@ -10,7 +10,9 @@ import { } from '../../lib/workersPult' import ProspectPanel from './ProspectPanel' -const CONFIRM_MS = 4000 // so lange gilt die Rückfrage „Wirklich verwerfen?“ bzw. „Wirklich freigeben?“ nach dem ersten Klick +const CONFIRM_MS = 4000 // so lange gilt die Rückfrage „Wirklich verwerfen?“, „Wirklich freigeben?“ bzw. „Wirklich bauen?“ nach dem ersten Klick +// Kosten und Wirkung von „Vorschau bauen“ (Rückfrage und Hinweis am Knopf); bewusst ohne „nur wenn der Interessent Ja gesagt hat“: der Knopf gilt gerade für unklare Antworten +const BUILD_HINT = 'Startet einen Opus-Lauf und baut die Vorschau, als hätte der Interessent Ja gesagt.' const NO_FIELDS = Object.freeze({}) // Eintrag ohne bearbeitete Felder // Leerzustand je Filter @@ -65,7 +67,7 @@ function TextPanel({ className, label, hint, value, onChange, submitLabel, onSub // Reihenfolge (409 mit deutschem Satz); danach wird die Liste neu geholt. function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFinished, onDetails }) { const [busy, setBusy] = useState(false) - const [confirming, setConfirming] = useState(null) // null | 'discard' | 'approve': welche Rückfrage gerade gilt + const [confirming, setConfirming] = useState(null) // null | 'discard' | 'approve' | 'build': welche Rückfrage gerade gilt const [open, setOpen] = useState(null) // null | 'reply' | 'paste': welches Feld aufgeklappt ist const [note, setNote] = useState(null) // { kind: 'ok' | 'err', text } const busyRef = useRef(false) // sperrt schon im selben Takt, in dem busy noch nicht greift (Doppelklick) @@ -76,7 +78,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin return () => { aliveRef.current = false } }, []) - // Die Rückfrage vor dem Verwerfen und vor dem Freigeben verfällt nach 4 s + // Die Rückfrage vor dem Verwerfen, Freigeben und Bauen verfällt nach 4 s useEffect(() => { if (confirming === null) return undefined const timer = window.setTimeout(() => setConfirming(null), CONFIRM_MS) @@ -154,8 +156,15 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin run(() => api.pultAction(row.id, 'approve-preview'), 'freigegeben. Max sendet den Link im nächsten Sendefenster.', true, 'approve-preview') } - // Bau starten wie bei einem „Ja“ des Interessenten (für Einträge „unsicher“) - const startBuild = () => run(() => api.pultAction(row.id, 'start-build'), 'wird gebaut.', true, 'start-build') + // Bau starten wie bei einem „Ja“ des Interessenten (für Einträge „unsicher“): ein Opus-Lauf, der sich nicht zurücknehmen lässt. Wie beim Freigeben + // fragt der erste Klick nach, der zweite binnen 4 s startet den Bau. + const startBuild = () => { + if (confirming !== 'build') { + setConfirming('build') + return + } + run(() => api.pultAction(row.id, 'start-build'), 'wird gebaut.', true, 'start-build') + } // Die Felder „Als Mensch antworten“ und „Antwort einfügen“ sind getrennt: die Antwort ist mit dem Entwurf (oder dem bearbeiteten Text) // vorbelegt, das Einfüge-Feld beginnt leer. Beide liegen in der Liste, damit der Text einen Wechsel des Filters überlebt. @@ -280,9 +289,12 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin )} {row.canStartBuild && ( - + <> + + {confirming === 'build' && {BUILD_HINT}} + )} {row.canPaste && (