+ )
+}
diff --git a/src/components/workers/WorkersKpis.jsx b/src/components/workers/WorkersKpis.jsx
new file mode 100644
index 0000000..f19fe4d
--- /dev/null
+++ b/src/components/workers/WorkersKpis.jsx
@@ -0,0 +1,13 @@
+import Stat from '../ui/Stat'
+import { kpiTiles } from '../../lib/workersView'
+
+// Sechs Kennzahlen des Hustler-Dienstes (state.kpis). Fehlende Daten ergeben Striche, nie NaN (kpiTiles).
+export default function WorkersKpis({ kpis }) {
+ return (
+
+ {kpiTiles(kpis).map((tile) => (
+
+ ))}
+
+ )
+}
diff --git a/tests/unit/workersComponents.test.mjs b/tests/unit/workersComponents.test.mjs
new file mode 100644
index 0000000..4651c50
--- /dev/null
+++ b/tests/unit/workersComponents.test.mjs
@@ -0,0 +1,26 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { readFileSync, readdirSync } from 'node:fs'
+
+// Die React-Teile der Hustler-Seite sind unter Node nicht ladbar (JSX, Canvas, Router): geprüft wird der Quelltext
+// (Muster adminRoute.test.mjs). Pfade relativ zu src/components/workers/.
+const read = (p) => readFileSync(new URL('../../src/components/workers/' + p, import.meta.url), 'utf8')
+
+test('WorkerCard: Links nur über safeLink, extern mit rel noopener; deutsche Beschriftungen (Review Focus 5)', () => {
+ const src = read('WorkerCard.jsx')
+ assert.match(src, /safeLink\(/); assert.match(src, /rel="noopener noreferrer"/); assert.match(src, /target="_blank"/)
+ for (const t of ['Macht gerade', 'Tokens heute', 'Kosten heute', 'Letzte Aktionen']) assert.ok(src.includes(t), t)
+ assert.match(src, /MODEL_IDS/); assert.match(src, /wk-chip-/)
+ // jeder href und jedes to kommt aus dem safeLink-Ergebnis, nie aus dem Ereignis selbst
+ assert.match(src, /href=\{safe\.href\}/); assert.match(src, /to=\{safe\.href\}/)
+ assert.doesNotMatch(src, /\b(?:href|to)=\{(?!safe\.href\})/)
+})
+test('kein innerHTML in Komponenten und Engine', () => {
+ for (const dir of ['', 'engine/']) for (const f of readdirSync(new URL('../../src/components/workers/' + dir, import.meta.url))) { if (!/\.(jsx|js)$/.test(f)) continue; const s = read(dir + f); assert.doesNotMatch(s, /dangerouslySetInnerHTML|innerHTML/, dir + f) }
+})
+test('WorkersKpis nutzt Stat und kpiTiles; WorkerList: Knopf je Worker mit data-wk-worker, aria-pressed und Statustext', () => {
+ assert.match(read('WorkersKpis.jsx'), /kpiTiles\(/); assert.match(read('WorkersKpis.jsx'), /]|]|href=/)
+})