From 0671e8e11926391f0c8c8ff3bca902c5f6309ca7 Mon Sep 17 00:00:00 2001 From: nexxo Date: Wed, 29 Jul 2026 10:20:40 +0200 Subject: [PATCH] Show one design while updating, not two swapping mid-run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported from live: pressing update showed a blue panel first, which then vanished and was replaced by a completely different "we are updating" screen. Both were mine. The console drew its own overlay in the app's light tokens; a moment later the containers went down and Laravel's 503 page took over with its own standalone styling. One event, two faces, swapping while an operator watches. There is now one panel — resources/views/partials/updating-panel.blade.php — rendered by both. It carries its own inline CSS and cpu- prefixed class names because the 503 page is served with the application down: no compiled stylesheet, no Tailwind, nothing but what is in that file. Dark mode comes from prefers-color-scheme for the same reason. The operator detail (step, running since, log tail) rides inside the panel behind a flag, since the same file is shown to customers on the 503 page. Co-Authored-By: Claude Opus 5 --- resources/views/errors/503.blade.php | 29 +------- resources/views/layouts/admin.blade.php | 52 ++++---------- .../views/partials/updating-panel.blade.php | 71 +++++++++++++++++++ tests/Feature/Admin/UpdateButtonTest.php | 29 ++++++++ 4 files changed, 116 insertions(+), 65 deletions(-) create mode 100644 resources/views/partials/updating-panel.blade.php diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php index 80fcda2..bf4d881 100644 --- a/resources/views/errors/503.blade.php +++ b/resources/views/errors/503.blade.php @@ -13,33 +13,8 @@ mid-deploy or freshly installed, which is exactly when the Vite manifest may not exist. Pulling it in through @vite would throw an exception instead of rendering the reassurance the visitor came for. --}} - - -
-
-
{{ __('updating.badge') }}
-

{{ __('updating.title') }}

-

{{ __('updating.body') }}

-

{{ __('updating.auto') }}

-
+ + @include('partials.updating-panel') diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index f6dc560..f647cb6 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -119,47 +119,23 @@ x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" - class="fixed inset-0 z-[100] flex items-center justify-center bg-bg px-4" + class="fixed inset-0 z-[100] overflow-auto" role="alert" aria-live="assertive" > -
-
- -
- -

{{ __('admin_settings.update_overlay_title') }}

-

{{ __('admin_settings.update_offline_hint') }}

- - {{-- Queued: the agent has not picked the request up yet, so there - is no step yet either. - - This used to count down to the agent's next tick. It was wrong - twice over. Wrong in fact: the countdown target was recomputed - on every poll, and where the reported interval was shorter than - the timer actually installed it landed in the past every time - and reset to a full minute — so it ran backwards four seconds - and jumped, forever. And wrong in kind: with the agent woken - the moment the request lands there is nothing left to wait for. - A sentence, not a clock. --}} -

- {{ $updateState['instant'] ? __('admin_settings.update_starting') : __('admin_settings.update_starting_queued') }} -

- - {{-- Running: the step, how long it has been going, and the tail - of the log — a run that IS stuck is then visibly stuck AT a - step, not just a spinner nobody can read anything from. --}} -
-

-

-

-            
-
+ {{-- The SAME panel the 503 page renders. It used to be a light card in + the app's own tokens, which meant an operator saw one design while + the application was still up and a completely different one the + moment the containers went down — two faces for one event, swapping + mid-update. The operator detail rides along inside it rather than + beside it. --}} + @include('partials.updating-panel', [ + 'body' => __('admin_settings.update_offline_hint'), + 'detail' => true, + 'starting' => $updateState['instant'] + ? __('admin_settings.update_starting') + : __('admin_settings.update_starting_queued'), + ]) @livewire('wire-elements-modal') diff --git a/resources/views/partials/updating-panel.blade.php b/resources/views/partials/updating-panel.blade.php new file mode 100644 index 0000000..6dcf8df --- /dev/null +++ b/resources/views/partials/updating-panel.blade.php @@ -0,0 +1,71 @@ +{{-- + "We are updating" — one panel, used in both places it can appear. + + It showed up twice with two different faces: the console's own overlay in the + app's light tokens while the application was still up, and then this, the + 503 page, once the containers went down. Two designs for one event, one + replacing the other mid-update. Reported as "the first screen must not be + there", and rightly. + + The styles are inline and self-contained rather than Tailwind classes, + because the other place this renders is the 503 page — shown while the + application is mid-deploy or freshly installed, exactly when the Vite + manifest may not exist. A stylesheet reference there would throw instead of + rendering the reassurance the visitor came for. + + Prefixed class names: this is injected into the console's own document, + which has a stylesheet of its own, and `.badge` is not a name to claim + globally. +--}} + + +
+
+
+
{{ __('updating.badge') }}
+

{{ __('updating.title') }}

+

{{ $body ?? __('updating.body') }}

+ + {{-- Only in the console. A customer looking at the 503 has no use for a + deployment step, and an operator watching a run that is stuck has + no use for anything else: "läuft gerade" alone is what sends + somebody to the shell. The markup is Alpine-driven and inert + wherever Alpine is not — which is why it is behind a flag rather + than always present. --}} + @if ($detail ?? false) +
+

{{ $starting ?? '' }}

+

+

+

+            
+ @endif +

{{ __('updating.auto') }}

+
+
diff --git a/tests/Feature/Admin/UpdateButtonTest.php b/tests/Feature/Admin/UpdateButtonTest.php index f63a433..30de3f0 100644 --- a/tests/Feature/Admin/UpdateButtonTest.php +++ b/tests/Feature/Admin/UpdateButtonTest.php @@ -761,3 +761,32 @@ it('does not answer the watcher for somebody who is not an operator', function ( ->get(route('admin.update.state')) ->assertForbidden(); }); + +it('shows one design while updating, not two swapping mid-run', function () { + // Reported as "the first screen must not be there". It was the console's + // own overlay in the app's light tokens; then the containers went down and + // the 503 page took over with a completely different face. Two designs for + // one event, replacing each other mid-update. + // + // Both render the same partial now, so there is nothing left that can + // disagree. + $console = Illuminate\Support\Facades\File::get(resource_path('views/layouts/admin.blade.php')); + $maintenance = Illuminate\Support\Facades\File::get(resource_path('views/errors/503.blade.php')); + + expect($console)->toContain("partials.updating-panel") + ->and($maintenance)->toContain("partials.updating-panel") + // And nothing left behind that would render a second look. + ->and($console)->not->toContain('update_overlay_title') + ->and($maintenance)->not->toContain('