diff --git a/src/components/workers/WorkerCard.jsx b/src/components/workers/WorkerCard.jsx
index 8424243..ffb289a 100644
--- a/src/components/workers/WorkerCard.jsx
+++ b/src/components/workers/WorkerCard.jsx
@@ -17,6 +17,8 @@ const CHIP = {
const PORTRAIT_W = 72
const PORTRAIT_H = 96
+// Beginnt die Karte tiefer als so viele Pixel über der Unterkante des Fensters, wird sie nach oben geholt
+const MIN_VISIBLE = 120
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
@@ -52,6 +54,23 @@ function TraceText({ link, children }) {
// Karte eines Workers: Porträt, Rolle, Modell, Status, Tageswerte und die letzten Aktionen.
// worker = Zustandseintrag (status, task, counters, connected), trace = Antwort von api.trace (oder null).
export default function WorkerCard({ workerId, worker, trace, loading, error, onClose }) {
+ const sectionRef = useRef(null)
+ const headingRef = useRef(null)
+
+ // Neue Auswahl: Der Name nimmt den Fokus (Screenreader sagen die Karte an), und liegt der Kartenanfang außerhalb des Fensters,
+ // wird die Karte an den oberen Rand geholt. Auf Handy und Tablet steht die Karte über der langen Liste, aus der man wählt.
+ useEffect(() => {
+ if (!workerId) return
+ const heading = headingRef.current
+ if (heading && typeof heading.focus === 'function') heading.focus({ preventScroll: true })
+ const section = sectionRef.current
+ if (!section || typeof section.scrollIntoView !== 'function') return
+ const top = section.getBoundingClientRect().top
+ const viewport = window.innerHeight || document.documentElement.clientHeight
+ const margin = parseFloat(window.getComputedStyle(section).scrollMarginTop) || 0
+ if (top < margin || top > viewport - MIN_VISIBLE) section.scrollIntoView({ block: 'start' })
+ }, [workerId])
+
if (!workerId) {
return (
@@ -74,10 +93,10 @@ export default function WorkerCard({ workerId, worker, trace, loading, error, on
const rows = traceRows(trace && trace.events, Date.now())
return (
-
+
{onClose && }
- {name}
+ {name}
{roleText && {roleText}
}
{modelId && {modelId}}
diff --git a/src/components/workers/WorkersBoard.jsx b/src/components/workers/WorkersBoard.jsx
index e9f5afd..dfb7bab 100644
--- a/src/components/workers/WorkersBoard.jsx
+++ b/src/components/workers/WorkersBoard.jsx
@@ -28,6 +28,7 @@ export default function WorkersBoard({ api, isAdmin, stageRef }) {
const ownRef = useRef(null)
const ref = stageRef ?? ownRef
const aliveRef = useRef(true)
+ const sideRef = useRef(null)
const [workers, setWorkers] = useState({})
const [selectedId, setSelectedId] = useState(null)
const [settingsOpen, setSettingsOpen] = useState(false)
@@ -68,6 +69,19 @@ export default function WorkersBoard({ api, isAdmin, stageRef }) {
const paused = Boolean(state.paused)
const worker = selectedId && Object.hasOwn(workers, selectedId) ? workers[selectedId] : undefined
+ // Karte schließen: der Fokus geht zurück auf den Listeneintrag des Workers (sonst fiele er mit dem Knopf „ד ins Leere)
+ const closeCard = () => {
+ const id = selectedId
+ setSelectedId(null)
+ if (!id || !sideRef.current) return
+ for (const button of sideRef.current.querySelectorAll('[data-wk-worker]')) {
+ if (button.getAttribute('data-wk-worker') === id) {
+ button.focus()
+ return
+ }
+ }
+ }
+
// Pause für alle Mitarbeiter: der Dienst bekommt den Zielwert, danach wird der Zustand sofort neu geholt
const togglePause = async () => {
if (pausing) return
@@ -103,14 +117,14 @@ export default function WorkersBoard({ api, isAdmin, stageRef }) {
-
+
setSelectedId(null)}
+ onClose={closeCard}
/>
diff --git a/src/styles/workers.css b/src/styles/workers.css
index b46dd68..04f01e3 100644
--- a/src/styles/workers.css
+++ b/src/styles/workers.css
@@ -73,7 +73,7 @@
/* ---------- Worker-Karte ---------- */
-.wk-card { position: relative; padding-bottom: 20px; }
+.wk-card { position: relative; padding-bottom: 20px; scroll-margin-top: 12px; }
.wk-card > .btn-quiet { position: absolute; top: 0; right: 0; }
.wk-card h3 { margin: 20px 0 8px; font-size: 13px; line-height: 20px; font-weight: 600; color: var(--text-2); }
.wk-portrait {
@@ -180,6 +180,8 @@
}
@media (max-width: 860px) {
+ /* unter der festen Leiste der Handy-Ansicht (52 px) */
+ .wk-card { scroll-margin-top: 64px; }
.wk-kpis.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 24px; }
.wk-zoom { gap: 6px; }
.wk-zoom .btn { min-width: 36px; min-height: 36px; }
diff --git a/tests/unit/workersComponents.test.mjs b/tests/unit/workersComponents.test.mjs
index 775fde5..4a5a8a2 100644
--- a/tests/unit/workersComponents.test.mjs
+++ b/tests/unit/workersComponents.test.mjs
@@ -65,3 +65,20 @@ test('WorkersBoard: Zustände und Rechte (Review Focus 1)', () => {
// PF-08: jeder Knopf ein type="button" (WOMS-Button hat keinen Standard-type)
assert.equal((s.match(/