Skip to content

Harden, fix and animate the analysis re-run trigger - #1068

Open
skerbis wants to merge 3 commits into
feature/non-blocking-analysis-page-v2from
feature/analysis-trigger-polish
Open

Harden, fix and animate the analysis re-run trigger#1068
skerbis wants to merge 3 commits into
feature/non-blocking-analysis-page-v2from
feature/analysis-trigger-polish

Conversation

@skerbis

@skerbis skerbis commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Independent sibling of #1068 (the "lower level" hint PR) — both are based on #1067 but not on each other. Combines what used to be three separate stacked PRs (#1062, #1063, #1064) into one, since all three touch the same small set of files around the same re-run trigger/toolbar and only really make sense read together.

  • No-JS fallback + timestamp: the re-run trigger is now a real, server-rendered <a href="...&rerun=1"> link that works even without JS (full page reload instead of the AJAX flow), and shows "Ergebnis vom …" once a cached result exists. JS intercepts the same link to avoid the reload when it did load.
  • Fix the re-run button silently doing nothing: the script tag added via rex_view::addJsFile() from the page controller could finish loading after DOMContentLoaded had already fired, in which case that listener alone never called init() — no click handler was ever bound. Now also binds via rex:ready and calls init() unconditionally at load time (with a guard against double-binding), and the JS file itself is registered from boot.php (gated to this subpage) rather than the page.
  • Animated spinner: the "running in background" placeholder gets a real CSS spinner instead of a static hourglass emoji, both for the JS-driven placeholder and the server-rendered initial one.

Test plan

  • Verified the plain link works with JS disabled (full reload, correct running/result state).
  • Verified the click handler binds and the AJAX flow works when the script loads after DOMContentLoaded.
  • Verified the spinner animates in both the server-rendered initial state and the JS-swapped state.
  • php -l + PHPStan on all changed files: 0 findings.

🤖 Generated with Claude Code

skerbis and others added 3 commits September 5, 2026 00:18
The trigger was only ever a JS-created <button>, with nothing
server-rendered at all if JS never ran. During development, a
forgotten `assets:sync` after editing assets/rexstan-analysis.js meant
the button silently never appeared - reloading the page didn't help
either, since nothing about the trigger was server-side.

The trigger is now a normal server-rendered link (?rerun=1), so it
always renders and works even without JS - clicking it reloads the
page, which starts the background run and shows the running state.
JS still intercepts the same click to avoid the reload when it did
load, and no longer auto-starts a run on the very first page view
(previously silent/surprising) in favor of this always-visible link.

Also shows the timestamp the currently displayed result was generated
at (RexStanRunStore::getCachedResultTimestamp()), so it's clear when
what's on screen isn't from the just-finished run; updated to "just
now" client-side once a poll completes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The button's click handler was only ever bound from a
DOMContentLoaded listener. A script added via rex_view::addJsFile()
can finish loading/executing after that event already fired (common
on a backend page with many other scripts), in which case the
listener never runs at all - no handler ever gets bound, and the
link's href="#" just does nothing on click, which is exactly what was
being seen.

Fixed the same way this project's own ai-chat-warm-cache.js already
does it: try a jQuery "rex:ready" listener (also covers REDAXO's own
AJAX-driven content swaps), fall back to DOMContentLoaded, and call
init() unconditionally as well to cover the case where the DOM is
already ready by the time this script runs. init() now guards against
running its setup more than once per page load (a dataset flag on the
app root), since it can legitimately fire from multiple triggers now.

Also moved the script registration from pages/analysis.php into
boot.php, gated to this subpage specifically - matches this addon's
own existing confetti.min.js pattern instead of introducing a second,
different way of loading a JS file for the same page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The running-state placeholder referenced a rexstan-analysis-spinner
CSS class that was never actually defined - just a static hourglass
emoji with no animation at all, easy to miss.

Added a small rotating CSS spinner (assets/rexstan.css) and render
the exact same placeholder markup server-side in pages/analysis.php
as well as from JS, so it's visible and animated purely via CSS
immediately on page load when a run is already in progress, without
depending on JS having initialized yet.

Also updates the CHANGELOG with the previous commit's button-click
fix, which hadn't been documented there yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@staabm

staabm commented Sep 4, 2026

Copy link
Copy Markdown
Member

wozu der "neu analysiern" button?

grafik

wenn ich oben auf das "Web UI Analyse" tab klicke wird die ansicht auch neu geladen.

Comment thread lib/RexStanRunStore.php
/**
* @return int|null unix timestamp the cached result was generated at, null if there is none
*/
public static function getCachedResultTimestamp(): ?int

@staabm staabm Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ob das mit dem cachen sinnvoll ist - bin ich mir unsicher. phpstan selbst cached bereits, wieso hier noch einen?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ist kein Duplikat vom phpstan-eigenen Cache, sondern eine andere Baustelle: PHPStans Cache beschleunigt nur die Berechnung selbst (überspringt unveränderte Dateien), aber der Aufruf braucht trotzdem noch Prozess-Start, Autoloading, Container-Bootstrap etc. - das kann je nach Projektgröße/Level auch mit warmem Cache noch mehrere Sekunden bis Minuten dauern (und bei größeren Änderungen/Level-Wechsel/nach einem Pull greift der Cache eh nicht bzw. nur teilweise).

Genau das wollten wir nicht mehr synchron bei jedem Seitenaufruf erzwingen - das war ja der ursprüngliche Blocking-Bug, den #1067 löst. RexStanRunStore cached deswegen nicht die Analyse selbst, sondern nur das zuletzt fertig berechnete JSON-Ergebnis (+ Zeitstempel), damit die Seite sofort etwas anzeigen kann, ganz ohne phpstan überhaupt anzustoßen.

Also zwei verschiedene Cache-Ebenen für zwei verschiedene Probleme - macht für mich schon Sinn, aber sag gerne wenn du das trotzdem anders siehst.

@skerbis

skerbis commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

Guter Punkt, Markus! Wichtig hier: der Tab-Klick lädt die Seite nur neu und zeigt das letzte gecachte Ergebnis – er startet keinen neuen PHPStan-Lauf mehr. Das ist eigentlich der ganze Sinn der Umstellung in #1067: vorher hat jeder Seitenaufruf phpstan synchron neu laufen lassen, daher das Timeout-Risiko, das wir damit beheben wollten.

"Ansehen" (schnell, aus dem Cache) und "neu analysieren" (bewusst getriggert, läuft im Hintergrund, kann je nach Level/Projekt Minuten dauern) sind jetzt also zwei getrennte Aktionen. Ohne den Button gäbe es gar keine Möglichkeit mehr, überhaupt einen neuen Lauf anzustoßen (außer beim allerersten Aufruf ganz ohne Cache, der automatisch startet).

Falls dir das trotzdem konfus vorkommt können wir gerne am Wording/UX feilen, z.B. deutlicher machen dass der Tab nur die letzte Ansicht zeigt. Sag Bescheid was du davon hältst 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants