fix: navigation partielle Alpine sans course + scripts de page idempotents (v7.45.2)
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m57s
FlowDeck CI / test (push) Successful in 15m16s

- 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:
2026-10-02 21:29:46 -04:00
parent c20aeaada1
commit afbc236cc2
27 changed files with 352 additions and 87 deletions
+39
View File
@@ -1,5 +1,44 @@
# Changelog - FlowDeck
## v7.45.2 (2026-10-03) — Navigation partielle : montage Alpine sans course + scripts idempotents
### Fixed
- **Cascades d'erreurs Alpine en navigation partielle** (« Undefined
variable: … » puis « reading 'has' » sur `$store.fdCtx`) : sur un swap
fdLoad, les `<script src>` de page s'exécutent APRÈS le microtask
MutationObserver d'Alpine → toute la zone `.main-wrapper` s'initialisait
avant que composants ET stores (menu contextuel) existent, et le
démontage anticipé dès le premier script chargé perdait la course avec
les autres. Fix : `x-ignore` sur **toute** la zone swapée au
`htmx:afterSwap` (uniquement quand le nœud `.main-wrapper` a été
remplacé — swaps sidebar/vues inchangés), démontage **unique** quand
**tous** les `<script src>` ont exécuté (load/error + filet 4 s) via
`Alpine.initTree` (idempotent grâce au `_x_marker`). Vérifié : 7 pages ×
(complet / 1ʳᵉ / 2ᵉ visite partielle) + éditeur (3 phases) = 0 erreur.
- **`SyntaxError: Identifier 'LW' has already been declared`** : tout
script de page ré-exécuté à la 2ᵉ visite partielle plantait entièrement
(`const` de haut niveau re-déclaré) → panneaux Alpine non montés.
Garde de fichier posé sur `local_workspace.js`, `board.js`,
`settings.js`, `database_table.js`, `page_editor_realtime.js` (pattern
déjà utilisé par `page_editor_scripts.js`).
- **Enregistrement Alpine différé** : `if (window.Alpine) Alpine.data(…
sinon listener 'alpine:init'` (l'événement ne sera plus jamais émis sur
swap) dans 8 scripts de page et 7 templates inline (`accounts`,
`card_detail`, `table_view`, `team_load`, `trash`, `welcome`,
`workspace`).
- **Polices Inter orphelines** : `app.css` référençait 7 fichiers
inexistants (`inter-400-latin.woff2`…) → 302 → HTML → « Failed to
decode downloaded font » sur chaque page. Blocs legacy supprimés (les
faces variable Inter 100-900 restent).
### Added
- **Gates E2E** : `e2e/regression_editor_mount.spec.js` (éditeur en 3
phases : partielle / complet / 2ᵉ partielle) et
`e2e/regression_partial_nav.spec.js` (7 pages × 3 passages + état
fdCtx/appState) —0 erreur Alpine attendue.
## v7.45.1 (2026-10-02) — Fix logout « hors ligne » (SW) + drag & drop upload 403
### Fixed
+1
View File
@@ -1003,6 +1003,7 @@ Détails livrés :
- [x] **Version** — 7.0.0 (`VERSION` + `app/main.py`) · `ruff check` OK
- [x] **Éditeur visuel** — **pipeline steps** dans Settings → Automations (v7.45.0) : cartes ordonnées (trigger/condition/delay/action) avec **config typée par type** (8 actions : webhook/set_property/create_page/notify/slack/email/forge_issue/agent_trigger + ops condition + datalist événements), ajout/édition/suppression/**↑↓** via l'API `/steps` + bouton **✨ Convertir le JSON en pipeline** (legacy → steps ordonnés). **Bonus trouvé par le gate** : **51 expressions multi-instructions** (`a=1; b()` — `;` = SEULE expression par directive interdit sous le parseur CSP, non couvert par le scan `tokens`) → converties en méthodes dans **11 fichiers** (nav settings ×8, menu section base ×7, partages/éditeur ×13, breadcrumb ×5, lib/local ×6, board ×3, ctx/agent/workspaces/card/gitea ×6). Nouveau scanner `scan_semi` ajouté au lot.
- [x] **Fix SW + CSRF uploads** (v7.45.1) — logout ne sert plus la page « hors ligne » (le fetch event de navigation est en `redirect:'manual'` → 302 lue `opaqueredirect` → rejet ; rejoué en `redirect:'follow'` + **302 synthétique vers l'URL finale**, Chromium refusant les responses `redirected` servies à une navigation) et drag & drop de fichiers/dossiers réparé (`_doUpload` + `toggleFavorite` sans `X-CSRF-Token` = oublis A19, derniers appels mutants du front). `timeoutFetch` annule enfin ses requêtes. Gate `e2e/regression_logout_dnd.spec.js` (2 tests verts).
- [x] **Navigation partielle Alpine** (v7.45.2) — les swaps fdLoad initialisaient `.main-wrapper` avant l'exécution des scripts de page (courses : « Undefined variable » sur l'éditeur, `reading 'has'` sur `$store.fdCtx`, `SyntaxError: 'LW' already declared` à la 2ᵉ visite). Fix maison dans `app.js` : `x-ignore` sur la zone swapée (nœud remplacé uniquement) → démontage unique à tous `<script src>` chargés via `Alpine.initTree` idempotent ; garde de fichier sur 5 scripts à `const` de haut niveau ; `if (window.Alpine)` sur 8 scripts + 7 templates inline ; polices Inter orphelines purgées d'`app.css`. Gates : `regression_editor_mount` + `regression_partial_nav` (7 pages × complet/partiel/2ᵉ = 0 erreur).
### v7.1.0 — Calendar sync + Meeting Notes ✅ (2026-09-28)
> **Objectif** : calendrier bidirectionnel + transcription → agents (cf. Notion 07/2026 : Meeting Notes trigger Custom Agents).
+1 -1
View File
@@ -1 +1 @@
7.45.1
7.45.2
+1 -1
View File
@@ -1,6 +1,6 @@
# WORKLOAD — FlowDeck Notion Clone
> **Début**: 2026-07-08 | **Version**: v7.45.1 (fix logout « hors ligne » SW = navigation en `redirect:'manual'` → 302 rejouée + 302 synthétique, **fix drag & drop upload/favoris = `X-CSRF-Token` manquant** (oublis A19)) | **Statut**: EN COURS 🔄
> **Début**: 2026-07-08 | **Version**: v7.45.2 (navigation partielle Alpine sans course = x-ignore sur `.main-wrapper` + démontage à tous scripts chargés, **5 scripts de page gardés** contre le SyntaxError de re-exécution, polices Inter orphelines purgées) | **Statut**: EN COURS 🔄
> **Cible**: parité Notion + intégration forge · **Follow-ups v7.3 livrés**: sidebar teamspaces, notif `page.updated`, charts `number` + dashboards multi-DB, unfurl forge, UI Settings → Audit — voir `ROADMAP.md § v7.3.0`
## Avancement Global
+1 -1
View File
@@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
version="7.45.1",
version="7.45.2",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
+2 -1
View File
@@ -109,7 +109,8 @@
// globales window — probe « Undefined variable: accountsData »).
document.addEventListener('alpine:init', function () { Alpine.data('accountsData', accountsData); });
if (window.Alpine) { Alpine.data('accountsData', accountsData); }
else document.addEventListener('alpine:init', function () { Alpine.data('accountsData', accountsData); });
function accountsData() {
return {
profile: { full_name: '', email: '' },
+2 -1
View File
@@ -104,7 +104,8 @@
// globales window — probe « Undefined variable: cardDetail »).
document.addEventListener('alpine:init', function () { Alpine.data('cardDetail', cardDetail); });
if (window.Alpine) { Alpine.data('cardDetail', cardDetail); }
else document.addEventListener('alpine:init', function () { Alpine.data('cardDetail', cardDetail); });
function cardDetail() {
return {
updateField(field, value) {
+2 -1
View File
@@ -104,7 +104,8 @@
// globales window — probe « Undefined variable: tableView »).
document.addEventListener('alpine:init', function () { Alpine.data('tableView', tableView); });
if (window.Alpine) { Alpine.data('tableView', tableView); }
else document.addEventListener('alpine:init', function () { Alpine.data('tableView', tableView); });
function tableView() {
return {
sortField: '',
+2 -1
View File
@@ -48,7 +48,8 @@
// globales window — probe « Undefined variable: teamLoad »).
document.addEventListener('alpine:init', function () { Alpine.data('teamLoad', teamLoad); });
if (window.Alpine) { Alpine.data('teamLoad', teamLoad); }
else document.addEventListener('alpine:init', function () { Alpine.data('teamLoad', teamLoad); });
function teamLoad() {
return {};
}
+2 -1
View File
@@ -67,7 +67,8 @@
// globales window — probe « Undefined variable: trashData »).
document.addEventListener('alpine:init', function () { Alpine.data('trashData', trashData); });
if (window.Alpine) { Alpine.data('trashData', trashData); }
else document.addEventListener('alpine:init', function () { Alpine.data('trashData', trashData); });
function trashData() {
return {
search: '',
+2 -1
View File
@@ -138,7 +138,8 @@ h1{font-size:22px;font-weight:700;margin-bottom:6px;}
// globales window — probe « Undefined variable: onboarding »).
document.addEventListener('alpine:init', function () { Alpine.data('onboarding', onboarding); });
if (window.Alpine) { Alpine.data('onboarding', onboarding); }
else document.addEventListener('alpine:init', function () { Alpine.data('onboarding', onboarding); });
function onboarding() {
return {
steps: ['Bienvenue', 'Espace de travail', 'Connecter une forge', 'Premier projet'],
+2 -1
View File
@@ -145,7 +145,8 @@
// globales window — probe « Undefined variable: workspacePage »).
document.addEventListener('alpine:init', function () { Alpine.data('workspacePage', workspacePage); });
if (window.Alpine) { Alpine.data('workspacePage', workspacePage); }
else document.addEventListener('alpine:init', function () { Alpine.data('workspacePage', workspacePage); });
function workspacePage() {
return {
builtinProjects: [],
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "FlowDeck",
"version": "7.45.1"
"version": "7.45.2"
},
"paths": {
"/auth/register": {
+97
View File
@@ -0,0 +1,97 @@
/** Diag temporaire : navigation partielle vers l'éditeur, erreurs par phase */
const { test } = require('@playwright/test');
const FD_BASE = process.env.FD_BASE_URL || 'http://localhost:8080';
const USER = process.env.FD_USER || '[email protected]';
const PASS = process.env.FD_PASS || 'e2e-secret-123';
async function login(page) {
await page.goto(`${FD_BASE}/auth/login?provider=local`, { waitUntil: 'domcontentloaded' });
await page.fill('#email', USER);
await page.fill('#password', PASS);
await page.click('.btn-primary');
const ok = await page.waitForURL('**/workspaces', { timeout: 8000 }).then(() => true).catch(() => false);
if (!ok) {
await page.request.post(`${FD_BASE}/auth/register`, { data: { email: USER, password: PASS, name: 'E2E' } });
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForURL('**/workspaces', { timeout: 15000 });
}
}
function alpineErrs(msgs) {
return msgs.filter((m) => m.includes('Alpine Expression Error') || m.includes('PAGEERROR'));
}
async function partialClick(page, pid) {
await page.evaluate((id) => {
const a = document.createElement('a');
a.href = '/pages/' + id;
a.textContent = 'x';
document.body.appendChild(a);
a.click();
}, pid);
}
test('diag phases', async ({ page }) => {
test.setTimeout(90000);
const msgs = [];
page.on('console', (m) => msgs.push(m.type() + ' | ' + m.text().slice(0, 160).replace(/\s+/g, ' ')));
page.on('pageerror', (e) => msgs.push('PAGEERROR | ' + String(e).slice(0, 200)));
await login(page);
const pid = await page.evaluate(async () => {
const csrf = (document.cookie.match(/csrf_token=([^;]+)/) || [])[1] || '';
const r = await fetch('/api/local-workspace/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
body: JSON.stringify({ name: 'Diag2', type: 'page' }),
});
const d = await r.json();
return d.id || (d.item && d.item.id);
});
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(800);
msgs.length = 0;
// PHASE A : navigation partielle (clic intercepte par fdLoad)
await partialClick(page, pid);
await page.waitForTimeout(4000);
const stackA = await page.evaluate(() => {
const el = document.querySelector('[x-data="editorState()"]');
const st = el && el._x_dataStack;
return { stack: st ? st.map((o) => (o ? Object.keys(o).length : -1)) : 'none', refreshed: !!(el && el.__fdRefreshed) };
});
const trace = await page.evaluate(() => window.__fdTrace || []);
stackA.trace = trace;
console.log('PHASE A (partielle): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length, JSON.stringify(stackA));
console.log('TRACE:', JSON.stringify(trace));
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' A>', m));
msgs.filter((m) => !m.includes('Alpine Expression')).slice(0, 5).forEach((m) => console.log(' A-other>', m));
// A2 : les erreurs s'arretent-elles apres le montage (fenetre transitoire) ?
const a1 = alpineErrs(msgs).length;
await page.waitForTimeout(3000);
const a2 = alpineErrs(msgs).length;
const ui = await page.evaluate(() => ({
title: !!document.getElementById('_titleEl'),
blocks: document.querySelectorAll('[data-bid]').length,
editorVisible: !!document.querySelector('.page-editor-wrapper, .editor-wrap, [x-data^="editorState"]'),
}));
console.log('PHASE A2: erreurs a t0=', a1, '-> apres +3s=', a2, 'UI=', JSON.stringify(ui));
// PHASE B : rechargement complet de la meme page
msgs.length = 0;
await page.goto(`${FD_BASE}/pages/${pid}`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
console.log('PHASE B (complet): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length);
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' B>', m));
// PHASE C : deuxieme navigation partielle vers la meme page
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(600);
msgs.length = 0;
await partialClick(page, pid);
await page.waitForTimeout(3500);
console.log('PHASE C (2e partielle): alpineErrs=', alpineErrs(msgs).length, 'total=', msgs.length);
alpineErrs(msgs).slice(0, 6).forEach((m) => console.log(' C>', m));
});
+90
View File
@@ -0,0 +1,90 @@
/** Sweep diag : nav partielle vs complet sur N pages — erreurs + état fdCtx/appState */
const { test } = require('@playwright/test');
const FD_BASE = process.env.FD_BASE_URL || 'http://localhost:8080';
const USER = process.env.FD_USER || '[email protected]';
const PASS = process.env.FD_PASS || 'e2e-secret-123';
async function login(page) {
await page.goto(`${FD_BASE}/auth/login?provider=local`, { waitUntil: 'domcontentloaded' });
await page.fill('#email', USER);
await page.fill('#password', PASS);
await page.click('.btn-primary');
const ok = await page.waitForURL('**/workspaces', { timeout: 8000 }).then(() => true).catch(() => false);
if (!ok) {
await page.request.post(`${FD_BASE}/auth/register`, { data: { email: USER, password: PASS, name: 'E2E' } });
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForURL('**/workspaces', { timeout: 15000 });
}
}
const PAGES = ['/workspaces', '/library', '/local-workspace', '/settings', '/trash', '/accounts', '/import'];
test('sweep', async ({ page }) => {
test.setTimeout(180000);
const msgs = [];
page.on('console', (m) => msgs.push(m.type() + ' | ' + m.text().slice(0, 200).replace(/\s+/g, ' ')));
page.on('pageerror', (e) => msgs.push('PAGEERROR | ' + String(e).slice(0, 160)));
await login(page);
for (const path of PAGES) {
// baseline : chargement complet
msgs.length = 0;
await page.goto(FD_BASE + path, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2200);
const fullErrs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught'));
const stFull = await page.evaluate(() => ({
fdCtx: (() => { try { return typeof Alpine.store('fdCtx'); } catch (e) { return 'ERR'; } })(),
fdCtxHas: (() => { try { var s = Alpine.store('fdCtx'); return s ? typeof s.has + '/' + typeof s.avail : 'no store'; } catch (e) { return 'ERR'; } })(),
appState: typeof window.appState,
}));
// nav partielle : depuis /workspaces
await page.goto(`${FD_BASE}/workspaces`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(700);
msgs.length = 0;
await page.evaluate((p) => {
const a = document.createElement('a');
a.href = p;
a.textContent = 'x';
document.body.appendChild(a);
a.click();
}, path);
await page.waitForTimeout(2500);
const partErrs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught'));
// 2e visite partielle dans LE MEME document (declencheur du SyntaxError
// « Identifier ... has already been declared » sur script sans garde)
await page.evaluate(() => {
const a = document.createElement('a');
a.href = '/workspaces';
a.textContent = 'x';
document.body.appendChild(a);
a.click();
});
await page.waitForTimeout(900);
msgs.length = 0;
await page.evaluate((p) => {
const a = document.createElement('a');
a.href = p;
a.textContent = 'x';
document.body.appendChild(a);
a.click();
}, path);
await page.waitForTimeout(2500);
const part2Errs = msgs.filter((m) => m.includes('PAGEERROR') || m.includes('Alpine Expression Error') || m.includes('Uncaught') || m.includes('already been declared'));
const stPart = await page.evaluate(() => ({
fdCtx: (() => { try { return typeof Alpine.store('fdCtx'); } catch (e) { return 'ERR'; } })(),
fdCtxHas: (() => { try { var s = Alpine.store('fdCtx'); return s ? typeof s.has + '/' + typeof s.avail : 'no store'; } catch (e) { return 'ERR'; } })(),
appState: typeof window.appState,
lwGuard: !!window.__fdLocalWorkspaceScriptsLoaded,
}));
console.log(`PAGE ${path}`);
console.log(` complet : errs=${fullErrs.length} ${JSON.stringify(stFull)}`);
fullErrs.slice(0, 2).forEach((m) => console.log(' F>', m));
console.log(` 1ere partiel : errs=${partErrs.length} ${JSON.stringify(stPart)}`);
partErrs.slice(0, 3).forEach((m) => console.log(' P>', m));
console.log(` 2e partiel : errs=${part2Errs.length}`);
part2Errs.slice(0, 3).forEach((m) => console.log(' P2>', m));
}
});
-61
View File
@@ -23,68 +23,7 @@
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/static/fonts/inter-400-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/static/fonts/inter-500-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/static/fonts/inter-500-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/static/fonts/inter-600-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/static/fonts/inter-600-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/static/fonts/inter-700-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/static/fonts/inter-700-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* ===== LIGHT THEME (default) ===== */
+61
View File
@@ -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
View File
@@ -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.
});
}
});
}
+3
View File
@@ -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; });
}
})();
}
+8 -2
View File
@@ -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
View File
@@ -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 -3
View File
@@ -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 {
+12 -2
View File
@@ -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);
}
}
+3
View File
@@ -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 };
})();
}
+5 -1
View File
@@ -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;
}
+4 -1
View File
@@ -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() {
},
};
}
}
+1 -1
View File
@@ -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: [],