fix: navigation partielle Alpine sans course + scripts de page idempotents (v7.45.2)
- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé uniquement), démontage unique quand tous les <script src> ont exécuté (load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) — fini les cascades « Undefined variable » (éditeur) et « reading 'has' » ($store.fdCtx) : la zone s'initialisait avant composants et stores. - 5 scripts de page gardés contre la ré-exécution (SyntaxError « Identifier 'LW' has already been declared » à la 2e visite partielle) : local_workspace, board, settings, database_table, page_editor_realtime. - if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init (déjà passé sur swap) : 8 scripts + 7 templates inline. - app.css : purge des 7 blocs @font-face Inter orphelins (fichiers inexistants → 302 HTML → « Failed to decode downloaded font »). - Gates : e2e/regression_editor_mount + e2e/regression_partial_nav (7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke = 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK · OpenAPI 7.45.2.
This commit is contained in:
@@ -326,6 +326,67 @@
|
||||
fdLoad(window.location.href, false);
|
||||
});
|
||||
|
||||
// ── Swap partiel : monter proprement la zone swapée sous Alpine ──
|
||||
// Lors d'une navigation partielle (fdLoad), les <script src> de la page
|
||||
// s'exécutent APRÈS le microtask MutationObserver d'Alpine : toute la zone
|
||||
// .main-wrapper serait initialisée avant que composants ET stores (menu
|
||||
// contextuel $store.fdCtx, …) existent → « Undefined variable: … » puis
|
||||
// « reading 'has' » sur les stores. On pose x-ignore sur TOUTE la zone
|
||||
// pendant le chargement des scripts, puis on démarle quand tous les
|
||||
// <script src> ont exécuté (load/error) — filet de sécurité 4 s.
|
||||
// (initTree est idempotent : le _x_marker posé au premier passage évite
|
||||
// tout double montage.)
|
||||
var fdLastMw = document.querySelector('.main-wrapper'); // nœud du chargement complet
|
||||
window.fdRefreshXData = function (name) {
|
||||
document.querySelectorAll('[x-data="' + name + '()"]').forEach(function (el) {
|
||||
if (el.__fdRefreshed) return;
|
||||
var st = el._x_dataStack;
|
||||
if (!st) return; // sous x-ignore → done() montera
|
||||
if (st[0] && Object.keys(st[0]).length) return; // déjà monté correctement
|
||||
el.__fdRefreshed = true; // monté cassé (hors watcher) → on remonte
|
||||
var fresh = el.cloneNode(true);
|
||||
el.parentNode.replaceChild(fresh, el);
|
||||
if (window.htmx) { try { htmx.process(fresh); } catch { /* volontaire */ } }
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('htmx:afterSwap', function () {
|
||||
var mw = document.querySelector('.main-wrapper');
|
||||
// nœud inchangé = swap hors navigation (sidebar, vues) : rien à faire
|
||||
if (!mw || mw === fdLastMw || mw.__fdSwapWatch) return;
|
||||
fdLastMw = mw;
|
||||
mw.__fdSwapWatch = true;
|
||||
mw.setAttribute('x-ignore', '');
|
||||
var pending = mw.querySelectorAll('script[src]').length;
|
||||
var done = function () {
|
||||
if (!mw.__fdSwapWatch) return;
|
||||
mw.__fdSwapWatch = false;
|
||||
document.removeEventListener('load', onScript, true);
|
||||
document.removeEventListener('error', onScript, true);
|
||||
if (!mw.isConnected || !mw.hasAttribute('x-ignore')) return;
|
||||
mw.removeAttribute('x-ignore');
|
||||
mw._x_ignore = false; // propriété posée par Alpine au checkpoint
|
||||
try { Alpine.initTree(mw); } catch (e) { console.warn('[FlowDeck] mount', e); }
|
||||
};
|
||||
var onScript = function (e) {
|
||||
if (!e.target || e.target.tagName !== 'SCRIPT') return;
|
||||
if (--pending <= 0) done();
|
||||
};
|
||||
if (!pending) {
|
||||
// Avant le microtask MutationObserver d'Alpine : retirer x-ignore suffit,
|
||||
// le MO initialisera au checkpoint (initTree de sécurité = no-op).
|
||||
mw.removeAttribute('x-ignore');
|
||||
mw.__fdSwapWatch = false;
|
||||
setTimeout(function () {
|
||||
if (mw.isConnected) { try { Alpine.initTree(mw); } catch (e) { console.warn('[FlowDeck] mount', e); } }
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
document.addEventListener('load', onScript, true);
|
||||
document.addEventListener('error', onScript, true);
|
||||
setTimeout(done, 4000);
|
||||
});
|
||||
|
||||
// Ctrl/Cmd + \ : toggle the sidebar (Notion-style).
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === '\\' || e.code === 'Backslash')) {
|
||||
|
||||
+7
-4
@@ -1,3 +1,5 @@
|
||||
if (!window.__fdBoardScriptsLoaded) {
|
||||
window.__fdBoardScriptsLoaded = true;
|
||||
/* exported setActiveTab, kanbanBoard, filterSystem, sortSystem, newIssueForm, showNewIssue -- appeles depuis les attributs HTML des templates */
|
||||
const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
@@ -22,7 +24,7 @@ const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.
|
||||
|
||||
// globales window — probe « Undefined variable: kanbanBoard »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('kanbanBoard', kanbanBoard); });
|
||||
if (window.Alpine) { Alpine.data('kanbanBoard', kanbanBoard); fdRefreshXData('kanbanBoard'); } else document.addEventListener('alpine:init', function () { Alpine.data('kanbanBoard', kanbanBoard); });
|
||||
function kanbanBoard() {
|
||||
return {
|
||||
collapsedGroups: [],
|
||||
@@ -42,7 +44,7 @@ const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.
|
||||
|
||||
// globales window — probe « Undefined variable: filterSystem »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('filterSystem', filterSystem); });
|
||||
if (window.Alpine) { Alpine.data('filterSystem', filterSystem); fdRefreshXData('filterSystem'); } else document.addEventListener('alpine:init', function () { Alpine.data('filterSystem', filterSystem); });
|
||||
function filterSystem() {
|
||||
return {
|
||||
activeFilters: [],
|
||||
@@ -84,7 +86,7 @@ const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.
|
||||
|
||||
// globales window — probe « Undefined variable: sortSystem »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('sortSystem', sortSystem); });
|
||||
if (window.Alpine) { Alpine.data('sortSystem', sortSystem); fdRefreshXData('sortSystem'); } else document.addEventListener('alpine:init', function () { Alpine.data('sortSystem', sortSystem); });
|
||||
function sortSystem() {
|
||||
return {
|
||||
sorts: [],
|
||||
@@ -111,7 +113,7 @@ const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.
|
||||
|
||||
// globales window — probe « Undefined variable: newIssueForm »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('newIssueForm', newIssueForm); });
|
||||
if (window.Alpine) { Alpine.data('newIssueForm', newIssueForm); fdRefreshXData('newIssueForm'); } else document.addEventListener('alpine:init', function () { Alpine.data('newIssueForm', newIssueForm); });
|
||||
function newIssueForm() {
|
||||
return {
|
||||
title: '', status: 'todo',
|
||||
@@ -169,3 +171,4 @@ const BD=(()=>{try{const el=document.getElementById('bd-config');return el?JSON.
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
if (!window.__fdDbTableScriptsLoaded) {
|
||||
window.__fdDbTableScriptsLoaded = true;
|
||||
const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=el?JSON.parse(el.textContent):null;return v===undefined?null:v}catch{return null}})();
|
||||
|
||||
(function() {
|
||||
@@ -1309,3 +1311,4 @@ const DB_CONFIG=(()=>{try{const el=document.getElementById('db-config');const v=
|
||||
.catch(function(){ return rowId; });
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
var _regGiteaWs = () => {
|
||||
Alpine.data('giteaWorkspace', () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const owner = params.get('owner') || '';
|
||||
@@ -637,4 +637,10 @@ document.addEventListener('alpine:init', () => {
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
if (window.Alpine) {
|
||||
_regGiteaWs();
|
||||
fdRefreshXData('giteaWorkspace');
|
||||
} else {
|
||||
document.addEventListener('alpine:init', _regGiteaWs);
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
// globales window — probe « Undefined variable: importWizard »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('importWizard', importWizard); });
|
||||
if (window.Alpine) { Alpine.data('importWizard', importWizard); fdRefreshXData('importWizard'); } else document.addEventListener('alpine:init', function () { Alpine.data('importWizard', importWizard); });
|
||||
function importWizard() {
|
||||
return {
|
||||
sources: [],
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
// A20 phase 3 : enregistrement Alpine.data — le build CSP ne résout que le
|
||||
// registre (probe : globale window → « Undefined variable: libraryPage »).
|
||||
document.addEventListener('alpine:init', function () {
|
||||
Alpine.data('libraryPage', libraryPage);
|
||||
});
|
||||
if (window.Alpine) { Alpine.data('libraryPage', libraryPage); fdRefreshXData('libraryPage'); }
|
||||
else document.addEventListener('alpine:init', function () { Alpine.data('libraryPage', libraryPage); });
|
||||
|
||||
function libraryPage() {
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
if (!window.__fdLocalWorkspaceScriptsLoaded) {
|
||||
window.__fdLocalWorkspaceScriptsLoaded = true;
|
||||
const LW=(()=>{try{const el=document.getElementById('lw-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
console.log('FLOWDECK v2.4.6 — IIFE running, about to set _wsInitData');
|
||||
@@ -2065,7 +2067,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(_wsI
|
||||
|
||||
// A20 ph3 : registre Alpine.data (le build CSP ne résout que le registre ;
|
||||
// x-data="wsInitData()" au lieu de l'identifiant global _wsInitData).
|
||||
document.addEventListener('alpine:init', function () {
|
||||
var _regWs = function () {
|
||||
// objet partagé JS↔Alpine via closure LEXICALE (const hors window →
|
||||
// non snapshoté par ji ; probe4 : factory retournant l'objet = accepté).
|
||||
Alpine.data('wsInitData', function () { return _wsInitData; });
|
||||
@@ -2089,4 +2091,12 @@ document.addEventListener('alpine:init', function () {
|
||||
};
|
||||
return t;
|
||||
});
|
||||
});
|
||||
};
|
||||
if (window.Alpine) {
|
||||
_regWs();
|
||||
fdRefreshXData('wsInitData');
|
||||
fdRefreshXData('wsPreview');
|
||||
} else {
|
||||
document.addEventListener('alpine:init', _regWs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
if (!window.__fdRtScriptsLoaded) {
|
||||
window.__fdRtScriptsLoaded = true;
|
||||
const RT=(()=>{try{const el=document.getElementById('rt-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -530,3 +532,4 @@ window.__fdRT = (function () {
|
||||
|
||||
return { start, syncNow };
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -2535,6 +2535,10 @@ applyAIBlocks(text){
|
||||
|
||||
// globales window — probe « Undefined variable: editorState »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('editorState', editorState); });
|
||||
// Navigation partielle (fdLoad) : Alpine démarre déjà → 'alpine:init'
|
||||
// ne sera plus émis, on enregistre tout de suite (pattern _ctx_menu.js)
|
||||
// et on remonte la racine montée avant l'enregistrement.
|
||||
if (window.Alpine) { Alpine.data('editorState', editorState); fdRefreshXData('editorState'); }
|
||||
else document.addEventListener('alpine:init', function () { Alpine.data('editorState', editorState); });
|
||||
window.editorState = editorState;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
if (!window.__fdSettingsScriptsLoaded) {
|
||||
window.__fdSettingsScriptsLoaded = true;
|
||||
/* exported settingsInit -- appeles depuis les attributs HTML des templates */
|
||||
const ST=(()=>{try{const el=document.getElementById('st-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
@@ -5,7 +7,7 @@ const ST=(()=>{try{const el=document.getElementById('st-config');return el?JSON.
|
||||
|
||||
// globales window — probe « Undefined variable: settingsInit »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('settingsInit', settingsInit); });
|
||||
if (window.Alpine) { Alpine.data('settingsInit', settingsInit); fdRefreshXData('settingsInit'); } else document.addEventListener('alpine:init', function () { Alpine.data('settingsInit', settingsInit); });
|
||||
function settingsInit() {
|
||||
return {
|
||||
// ── A20 ph3 : expressions hostiles au parseur CSP (window/Date) ──
|
||||
@@ -1233,3 +1235,4 @@ function settingsInit() {
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// globales window — probe « Undefined variable: workspacesPage »).
|
||||
|
||||
document.addEventListener('alpine:init', function () { Alpine.data('workspacesPage', workspacesPage); });
|
||||
if (window.Alpine) { Alpine.data('workspacesPage', workspacesPage); fdRefreshXData('workspacesPage'); } else document.addEventListener('alpine:init', function () { Alpine.data('workspacesPage', workspacesPage); });
|
||||
function workspacesPage() {
|
||||
return {
|
||||
workspaces: [],
|
||||
|
||||
Reference in New Issue
Block a user