fix: la section Aide est positionnée comme Settings (v7.69.3)
FlowDeck CI / lint (push) Successful in 1m32s
FlowDeck CI / test (push) Failing after 16m17s
FlowDeck CI / docker (push) Skipped

La section Aide et la section Settings partagent le même gabarit
(`.settings-overlay` / `.settings-panel`) et la même feuille de style
(`static/css/settings.css`), mais affichaient deux rendus différents.

Cause : `help.html` neutralisait son overlay via un style INLINE sur la balise
— `position:static`, `background:none`, `backdrop-filter:none`, `padding:0` —
et imposait par-dessus un panneau `width:100%;max-width:1050px;
height:calc(100vh - 120px);margin:0 auto`. Settings, lui, laissait
`settings.css` faire son travail : overlay `fixed;inset:0` avec fond flouté,
panneau `85vh` centré par `margin:auto`.

Résultat perçu : Aide s'affichait comme un bloc posé dans la page, Settings
comme une modale centrée floutant l'arrière-plan — malgré un code quasi
identique.

Correctif : les surcharges inline sont retirées de `help.html`. La géométrie
vient désormais entièrement de `settings.css`, commun aux deux pages. Aucune
valeur n'est dupliquée ; les deux balises sont strictement identiques.

Vérifié en e2e en comparant les géométries CALCULÉES des deux pages (pas des
valeurs de code) : overlay `fixed` avec fond opère des deux côtés, panneau
1050x765 centré à top:68px, bouton close à 13/13px du coin du panneau — les
deux jeux de mesures sont identiques.

Tests : `tests/test_v7693_help_positioning.py` (5) verrouille l'absence de
surcharges inline et l'identité de balise avec Settings ;
`e2e/v7693_help_positioning.spec.js` compare les géométries calculées.

NON INCLUS — double-clic sur le bouton close de Settings :
le bug a été REPRODUIT en e2e et sa cause racine identifiée, mais le correctif
n'est volontairement PAS dans ce commit. Voir ROADMAP.md § v7.69.3 pour le
relevé complet et la prochaine étape.

pytest 1421 passed / 0 failed · ruff OK · e2e 15/15 verts sur l'instance déployée
This commit is contained in:
2026-10-08 21:07:17 -04:00
parent b9a776b626
commit 162eb257c7
8 changed files with 195 additions and 5 deletions
+27
View File
@@ -1,5 +1,32 @@
# Changelog - FlowDeck
## v7.69.3 (2026-10-08) — La section Aide est positionnée comme Settings
### Fixed
- **Aide s'affichait autrement que Settings** alors qu'ils partagent le même
gabarit (`settings-overlay` / `settings-panel`) et le même CSS. `help.html`
neutralisait son overlay **en style inline** : `position:static`,
`background:none`, `backdrop-filter:none`, `padding:0`, et forçait un panneau
`100% / max-width:1050px / height:calc(100vh - 120px)`. Résultat : Aide
était un bloc posé dans la page, Settings une modale centrée floutant le fond.
- → les surcharges inline sont retirées ; la géométrie vient entièrement de
`settings.css`, commun aux deux pages. Vérifié en e2e : **mêmes valeurs
mesurées** des deux côtés (overlay `fixed` avec fond, panneau 1050×765
centré à `top:68px`, bouton close à 13/13 px du coin du panneau).
### Tests
- `tests/test_v7693_help_positioning.py` (5) verrouillent l'absence de
surcharge inline et l'identité de balise avec Settings ;
`e2e/v7693_help_positioning.spec.js` compare les géométries calculées.
> **Non traité dans ce lot** : le double-clic sur le bouton close de Settings.
> Cause racine identifiée mais correctif NON appliqué — Alpine ne lie pas le
> composant `x-data="settingsInit()"` dans certains contextes, donc
> `@click="closeSettings()"` est inerte au premier clic. Détail et prochaine
> étape dans `ROADMAP.md § v7.69.3`.
## v7.69.2 (2026-10-08) — Le favicon est enfin le vrai logo
### Fixed
+45
View File
@@ -1581,6 +1581,51 @@ le marqueur de troncature du message d'erreur, **pas** un bug produit — une he
passée à chercher une fuite inexistante. Vérifier `JSON.stringify()` de la valeur
réelle avant de croire un affichage tronqué.
---
### v7.69.3 — Aide alignée sur Settings ✅ · double-clic close ⏳ ouvert (2026-10-08)
**Livré** : la section Aide partage maintenant le positionnement exact de la
section Settings. `help.html` neutralisait son overlay en style inline
(`position:static`, fond transparent, sans blur, sans padding) et forçait un
panneau `1050px / 100vh-120px` — les deux pages pourtant identiques au CSS
près produisaient deux rendus. Surcharges retirées ; e2e compare les
géométries calculées : **identiques des deux côtés** (overlay `fixed` avec
fond, panneau 1050×765 centré, close à 13/13 px du coin).
**Non livré — double-clic sur le bouton close de Settings.** Cause racine
trouvée, correctif NON appliqué (non vérifié sur le parcours réel).
Relevé d'investigation, à ne pas refaire :
- **Cause racine** : Alpine ne LIE PAS le composant `x-data="settingsInit()"`
dans certains contextes. Mesure sur un onglet où Settings a été ouvert via
`target="_blank"` :
`window.Alpine` existe, mais `button.settings-close._x_dataStack` est absent
→ `@click="closeSettings()"` est **inerte**. Le premier clic ne fait rien du
tout — ce qui correspond au symptôme rapporté. Ce n'est PAS la logique de
navigation qui est en cause.
- **Le bug se reproduit en e2e** : ouvrir `/settings` depuis un onglet distinct
(`target="_blank"` avec `rel="opener"`), cliquer `button.settings-close` →
l'URL reste `/settings`. Un test l'a confirmé (`APRES 1 clic= /settings`),
sans `pageerror` ni message console : le handler ne s'exécute simplement pas.
- **Ce qu'il ne faut PAS faire** : réécrire `closeSettings()`. L'ancienne
implémentation (`window.opener` → `window.close()`, sinon `history.back()` +
`reload()` après 100 ms) a été modifiée puis **annulée** : elle n'avait aucun
effet sur le cas reproduit. Le `return` inconditionnel de la branche
`window.opener` reste un défaut réel (`window.close()` échoue en silence si
l'onglet n'a pas été ouvert par du script), mais il n'est pas la cause
rapportée.
- **Thèse de la course réfutée** : un retour plus lent que 100 ms ne provoque
pas le symptôme (testé avec 400 ms de latence, un seul clic suffit).
- Parcours de référence qui, lui, **fonctionne en un clic** : `/local-workspace`
→ menu avatar → Settings (chargement de page complet).
**Prochaine étape concrète** : déterminer pourquoi `settingsInit()` échoue à
s'initialiser — `settings.js` est-il chargé dans ce contexte ? Vérifier
`x-data` au chargement et la présence d'une erreur CSP/nonce sur le script.
---
## ✅ Fonctionnalités livrées hors roadmap (bonus détectés dans le code)
+1 -1
View File
@@ -1 +1 @@
7.69.2
7.69.3
+1 -1
View File
@@ -1,6 +1,6 @@
# WORKLOAD — FlowDeck Notion Clone
> **Début**: 2026-07-08 | **Version**: v7.69.2 (sélection multi-blocs fonctionnelle : drag souris + Maj+↓ + Ctrl+C/X, clic droit qui préserve le surlignage ; identité visuelle logo/bannière/favicon)
> **Début**: 2026-07-08 | **Version**: v7.69.3 (sélection multi-blocs fonctionnelle ; identité visuelle logo/bannière/favicon ; Aide alignée sur Settings — ⏳ double-clic close Settings non reproduit)
> **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
@@ -186,7 +186,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
version="7.69.2",
version="7.69.3",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
+5 -2
View File
@@ -30,8 +30,11 @@
.help-badge.sso{background:rgba(217,115,13,.18);color:#E0952B;}
</style>
<div class="settings-overlay" x-data="helpInit()" style="background:none;backdrop-filter:none;position:static;z-index:1;padding:0;">
<div class="settings-panel" style="width:100%;max-width:1050px;height:calc(100vh - 120px);margin:0 auto;" @keydown.escape="navOpen = false">
<!-- v7.69.3 : positionnement IDENTIQUE à la section Settings — les surcharges
inline (position:static / background:none / panneau 1050px) faisaient
d'Aide un bloc posé dans la page au lieu de l'overlay centré de Settings. -->
<div class="settings-overlay" x-data="helpInit()">
<div class="settings-panel" @keydown.escape="navOpen = false">
<button class="settings-menu-btn" @click="navOpen = !navOpen" title="Sections" aria-label="Open sections menu">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
+62
View File
@@ -0,0 +1,62 @@
// v7.69.3 — la section Aide doit avoir le MÊME positionnement que Settings
const { test, expect } = require('@playwright/test');
const FD_BASE = process.env.FD_BASE_URL || 'http://localhost:8081';
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');
await page.waitForURL('**/workspaces', { timeout: 8000 }).catch(() => {});
}
// géométrie de l'overlay + du panneau, telle que perçue à l'écran
const geo = (page) =>
page.evaluate(() => {
const ov = document.querySelector('.settings-overlay');
const pn = document.querySelector('.settings-panel');
const o = getComputedStyle(ov);
const p = getComputedStyle(pn);
const r = pn.getBoundingClientRect();
return {
ovPosition: o.position,
ovBgSet: o.backgroundColor !== 'rgba(0, 0, 0, 0)',
pnWidth: Math.round(r.width),
pnHeight: Math.round(r.height),
pnCentered: Math.abs(r.left - (window.innerWidth - r.width) / 2) < 24,
pnTop: Math.round(r.top),
closeTop: Math.round(document.querySelector('.settings-close').getBoundingClientRect().top - r.top),
closeRight: Math.round(r.right - document.querySelector('.settings-close').getBoundingClientRect().right),
};
});
test('Aide et Settings partagent le même positionnement', async ({ page }) => {
await login(page);
await page.goto(`${FD_BASE}/help`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(400);
const h = await geo(page);
await page.goto(`${FD_BASE}/settings`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(400);
const s = await geo(page);
console.log('HELP =', JSON.stringify(h));
console.log('SETTING=', JSON.stringify(s));
// l'overlay de Aide n'est plus désactivé en inline
expect(h.ovPosition).toBe(s.ovPosition);
expect(h.ovPosition).toBe('fixed');
expect(h.ovBgSet).toBe(true);
// panneau centré et de la même taille que Settings
expect(h.pnCentered).toBe(true);
expect(h.pnWidth).toBe(s.pnWidth);
expect(h.pnHeight).toBe(s.pnHeight);
// le bouton close occupe la même place relative au panneau
expect(h.closeTop).toBe(s.closeTop);
expect(h.closeRight).toBe(s.closeRight);
await page.goto(`${FD_BASE}/help`, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(300);
await page.screenshot({ path: 'shots/v7693-help-overlay.png' });
});
+53
View File
@@ -0,0 +1,53 @@
"""v7.69.3 — la section Aide doit être positionnée comme la section Settings.
Auparavant `help.html` neutralisait l'overlay en inline
(`position:static;background:none;backdrop-filter:none`) et forçait un panneau
`1050px / 100vh-120px` : Aide s'affichait comme un bloc posé dans la page
tandis que Settings était une modale centrée. Le même gabarit partagé
(`.settings-overlay` / `.settings-panel`) produisait donc deux rendus.
"""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SETTINGS = (ROOT / "app" / "templates" / "settings.html").read_text(encoding="utf-8")
HELP = (ROOT / "app" / "templates" / "help.html").read_text(encoding="utf-8")
def _opening_tag(src: str, css_class: str) -> str:
"""La balise d'ouverture qui porte cette classe, sans ses attributs."""
line = next(row for row in src.splitlines() if f'class="{css_class}"' in row)
# coupe avant x-data ET avant n'importe quel handler @keydown(.escape)
return line.split(" x-data=")[0].split(" @keydown")[0].strip()
class TestHelpMatchesSettingsPositioning:
def test_help_overlay_no_longer_disables_itself(self):
# l'overlay d'Aide EST celui de Settings : aucune surcharge inline ne
# peut subsister sur cette balise (le positionnement vient du CSS
# partagé). On asserte sur la BALISE, pas sur le fichier entier — le
# commentaire de tête du template mentionne ces valeurs à l'infinitif.
assert _opening_tag(HELP, "settings-overlay") == '<div class="settings-overlay"'
def test_overlay_markup_is_identical(self):
assert _opening_tag(HELP, "settings-overlay") == _opening_tag(
SETTINGS, "settings-overlay"
)
def test_panel_carries_no_inline_geometry(self):
# toute la géométrie vient de settings.css, partagé avec Settings
assert _opening_tag(HELP, "settings-panel") == '<div class="settings-panel"'
assert "max-width:1050px" not in HELP
assert "calc(100vh - 120px)" not in HELP
def test_both_pages_use_the_shared_stylesheet(self):
# le CSS qui pose l'overlay centré est bien chargé par les deux
base = (ROOT / "app" / "templates" / "base.html").read_text(encoding="utf-8")
assert "/static/css/settings.css" in base
def test_close_button_keeps_the_same_slot(self):
# même classe que Settings -> même position absolue dans le panneau
for src, name in ((HELP, "help"), (SETTINGS, "settings")):
assert 'class="settings-close"' in src, name