diff --git a/lang/de/secrets.php b/lang/de/secrets.php
index 234f6c5..0cddd04 100644
--- a/lang/de/secrets.php
+++ b/lang/de/secrets.php
@@ -22,6 +22,17 @@ return [
'unlocked_note' => 'Entsperrt. Änderungen wirken sofort auf den laufenden Betrieb.',
'lock_again' => 'Wieder sperren',
+ // Welcher der zwei Plätze dieses Eintrags jetzt gilt — Speichern UND
+ // Lesen, weil save()/render() ohne Modus-Argument auf
+ // OperatingMode::current() auflösen. Benennt den PLATZ, nicht den Modus:
+ // „Testbetrieb"/„Livebetrieb" steht schon in der Plakette oben auf der
+ // Seite, hier geht es darum, in welche der zwei Zeilen ein neuer Wert
+ // tatsächlich landet.
+ 'slot' => [
+ 'test' => 'Testplatz aktiv',
+ 'live' => 'Live-Platz aktiv',
+ ],
+
'source_stored' => 'Hier hinterlegt',
'source_environment' => 'Aus der Serverdatei',
'source_none' => 'Nicht gesetzt',
diff --git a/lang/en/secrets.php b/lang/en/secrets.php
index ffe9c07..40fb833 100644
--- a/lang/en/secrets.php
+++ b/lang/en/secrets.php
@@ -22,6 +22,17 @@ return [
'unlocked_note' => 'Unlocked. Changes take effect on the running system immediately.',
'lock_again' => 'Lock again',
+ // Which of the two slots this entry resolves to right now — for both
+ // saving and reading, since save()/render() resolve against
+ // OperatingMode::current() without a mode argument. Names the SLOT, not
+ // the mode: "Test mode"/"Live mode" already sits in the badge at the top
+ // of the page; this is about which of the two rows a new value actually
+ // lands in.
+ 'slot' => [
+ 'test' => 'Test slot active',
+ 'live' => 'Live slot active',
+ ],
+
'source_stored' => 'Stored here',
'source_environment' => 'From the server file',
'source_none' => 'Not set',
diff --git a/resources/views/components/admin/secret-field.blade.php b/resources/views/components/admin/secret-field.blade.php
index 9e3ec5d..7aefcb7 100644
--- a/resources/views/components/admin/secret-field.blade.php
+++ b/resources/views/components/admin/secret-field.blade.php
@@ -1,4 +1,4 @@
-@props(['entry', 'unlocked'])
+@props(['entry', 'unlocked', 'mode'])
{{--
One vault entry — outline, new-value field, test/save/forget — reusable
across every integration section on App\Livewire\Admin\Integrations. Kept
@@ -17,13 +17,28 @@
{{ $entry['label'] }}
{{ $entry['key'] }}
-
-
- {{ __('secrets.source_'.$entry['source']) }}
-
+
+ {{-- Which of the two slots THIS card reads and writes right now —
+ fix round, Important: save()/render() resolve without a mode
+ argument, so they always act on OperatingMode::current()'s
+ slot, but nothing on the card said so. The badge in the
+ header above (Integrations::mode_title) is not enough: it is
+ not visible at the card an operator is about to type into,
+ and repeating "Testbetrieb"/"Livebetrieb" there would only
+ restate the MODE, not name the PLATZ a value lands in. Same
+ colours as that header badge, for one visual language. --}}
+
+ {{ __('secrets.slot.'.$mode->value) }}
+
+
+
+ {{ __('secrets.source_'.$entry['source']) }}
+
+
@if (! $unlocked)
diff --git a/resources/views/livewire/admin/integrations.blade.php b/resources/views/livewire/admin/integrations.blade.php
index 4a68714..04b335d 100644
--- a/resources/views/livewire/admin/integrations.blade.php
+++ b/resources/views/livewire/admin/integrations.blade.php
@@ -103,7 +103,7 @@
{{ __('integrations.payments_title') }}
{{ __('integrations.payments_body') }}
-
+
@if ($unlocked)
{{ __('secrets.webhook_secret_note') }}
@endif
@@ -127,7 +127,7 @@
@if ($canSecrets)
@if ($canInfra)
@endif
-
+
@endif
@endif
@@ -153,7 +153,7 @@
@if ($canSecrets)
@if ($canInfra)
@endif
-
+
@endif
@endif
@@ -172,7 +172,7 @@
@if ($canSecrets)
@if ($canInfra)
@endif
-
+
@endif
@endif
@@ -218,7 +218,7 @@
@if ($canSecrets)
@if ($canInfra)
@endif
-
+
@endif
@endif
diff --git a/tests/Feature/SwitchOperatingModeTest.php b/tests/Feature/SwitchOperatingModeTest.php
index 15e5bd0..a995dc7 100644
--- a/tests/Feature/SwitchOperatingModeTest.php
+++ b/tests/Feature/SwitchOperatingModeTest.php
@@ -1,5 +1,6 @@
toBe(OperatingMode::Live);
});
+
+// ---- ConfirmSwitchMode selbst — nach dem Vorbild der ConfirmSaveSecret/
+// ConfirmForgetSecret-Tests in tests/Feature/Admin/IntegrationsPageTest.php.
+// Ein Modal ist OHNE die Route-Middleware der Seite erreichbar (R23-
+// Kopfkommentar in CLAUDE.md) — "gelesen und für korrekt befunden" ist keine
+// Prüfung, ein eigener Test schon.
+it('does not open the switch confirmation without the capability', function () {
+ $admin = Operator::factory()->role('Admin')->create(); // hosts.manage, nicht secrets.manage
+
+ Livewire::actingAs($admin, 'operator')
+ ->test(ConfirmSwitchMode::class, ['mode' => 'test'])
+ ->assertForbidden();
+});
+
+it('confirms a switch through the modal without changing the mode itself', function () {
+ $owner = Operator::factory()->role('Owner')->create();
+
+ Livewire::actingAs($owner, 'operator')
+ ->test(ConfirmSwitchMode::class, ['mode' => 'test'])
+ ->call('confirm')
+ ->assertDispatched('mode-switch-confirmed', mode: 'test');
+
+ // Das Modal selbst ruft OperatingMode::set() nirgends auf — nur
+ // Integrations::switchMode() darf das, hinter guardSecrets(). Bestätigen
+ // im Modal allein darf den Modus nicht bewegen.
+ expect(OperatingMode::current())->toBe(OperatingMode::Live);
+});
+
+// ---- Fix-Runde, Befund 1 (Important): keine Karte sagte, in welchen Platz
+// sie schreibt. save()/render() lösen ohne Modus-Argument gegen
+// OperatingMode::current() auf — funktional kein Problem, aber die Karte sah
+// in Test und Live identisch aus. Reine Darstellung (secret-field.blade.php
+// liest nur $mode, das Integrations::render() schon berechnet — kein neuer
+// Aufruf von source()/outline()/updatedAt()), deshalb genügt ein assertSee je
+// Modus statt eines erfundenen Verhaltens-Tests.
+it('names the slot a saved value would land in, on the card itself', function () {
+ $owner = Operator::factory()->role('Owner')->create();
+
+ Livewire::actingAs($owner, 'operator')
+ ->test(Integrations::class)
+ ->set('confirmablePassword', 'passwort-fuer-tests')
+ ->call('confirmPassword')
+ ->assertSee(__('secrets.slot.live'))
+ ->assertDontSee(__('secrets.slot.test'));
+
+ OperatingMode::set(OperatingMode::Test);
+
+ Livewire::actingAs($owner, 'operator')
+ ->test(Integrations::class)
+ ->set('confirmablePassword', 'passwort-fuer-tests')
+ ->call('confirmPassword')
+ ->assertSee(__('secrets.slot.test'))
+ ->assertDontSee(__('secrets.slot.live'));
+});