Harden, fix and animate the analysis re-run trigger - #1068
Conversation
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>
| /** | ||
| * @return int|null unix timestamp the cached result was generated at, null if there is none | ||
| */ | ||
| public static function getCachedResultTimestamp(): ?int |
There was a problem hiding this comment.
ob das mit dem cachen sinnvoll ist - bin ich mir unsicher. phpstan selbst cached bereits, wieso hier noch einen?
There was a problem hiding this comment.
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.
|
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 🙂 |

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.
<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.rex_view::addJsFile()from the page controller could finish loading afterDOMContentLoadedhad already fired, in which case that listener alone never calledinit()— no click handler was ever bound. Now also binds viarex:readyand callsinit()unconditionally at load time (with a guard against double-binding), and the JS file itself is registered fromboot.php(gated to this subpage) rather than the page.Test plan
DOMContentLoaded.php -l+ PHPStan on all changed files: 0 findings.🤖 Generated with Claude Code