feat: frontend error capture — diagnostic instantané pour Hermes
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

- app.js: intercepte window.onerror + unhandledrejection
  Envoie automatiquement au backend via POST /api/frontend-error
  Déduplication, throttling 1/sec, max 50 erreurs buffer local
  window.__flowdeck_errors accessible en console debug

- api.py: 2 nouvelles routes
  POST /api/frontend-error — reçoit erreurs JS, déduplique, logge
  GET /api/frontend-errors?clear=true — Hermes lit les erreurs

- csrf.py: exemption /api/frontend-error du CSRF

Usage Hermes après chaque déploiement:
  curl -s http://localhost:8080/api/frontend-errors | jq .
  → Voir TOUTES les erreurs JS en temps réel
This commit is contained in:
2026-07-13 07:52:52 -04:00
parent 3718ad488c
commit ae3f8b473a
3 changed files with 106 additions and 1 deletions
+53
View File
@@ -1,6 +1,59 @@
// FlowDeck Notion UI — Client-side logic
// Alpine.js + SortableJS + HTMX + Mobile support
// ── Frontend Error Capture ──────────────────────────────────
// Intercepte TOUTES les erreurs JS et les envoie au backend.
// Hermes peut les consulter via GET /api/frontend-errors.
(function() {
'use strict';
const MAX_ERRORS = 50;
var _fd_errors = [];
var _fd_throttle = 0;
function _fd_send(error) {
_fd_errors.push(error);
if (_fd_errors.length > MAX_ERRORS) _fd_errors.shift();
// Throttle: max 1 POST per second
var now = Date.now();
if (now - _fd_throttle < 1000) return;
_fd_throttle = now;
try {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/frontend-error', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(error));
} catch(e) { /* fail silently */ }
}
window.addEventListener('error', function(e) {
_fd_send({
type: 'error',
message: e.message || 'Unknown error',
source: e.filename || '',
line: e.lineno || 0,
col: e.colno || 0,
stack: (e.error && e.error.stack) ? String(e.error.stack).substring(0, 2000) : '',
url: window.location.href,
userAgent: navigator.userAgent.substring(0, 200),
time: new Date().toISOString()
});
});
window.addEventListener('unhandledrejection', function(e) {
_fd_send({
type: 'unhandledrejection',
message: e.reason ? (e.reason.message || String(e.reason)) : 'Promise rejected',
stack: (e.reason && e.reason.stack) ? String(e.reason.stack).substring(0, 2000) : '',
url: window.location.href,
userAgent: navigator.userAgent.substring(0, 200),
time: new Date().toISOString()
});
});
// Expose pour le debug console
window.__flowdeck_errors = _fd_errors;
})();
(function() {
'use strict';