diff --git a/CHANGELOG.md b/CHANGELOG.md index f188f36..e85c57c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ROADMAP.md b/ROADMAP.md index c90838e..1c1e3cb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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) diff --git a/VERSION b/VERSION index 855b0c4..c652fec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.69.2 +7.69.3 diff --git a/WORKLOAD.md b/WORKLOAD.md index e2c2be4..b3994fd 100644 --- a/WORKLOAD.md +++ b/WORKLOAD.md @@ -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 diff --git a/app/main.py b/app/main.py index f3e5a2f..f67de8b 100644 --- a/app/main.py +++ b/app/main.py @@ -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, diff --git a/app/templates/help.html b/app/templates/help.html index 5c79132..79efbe1 100644 --- a/app/templates/help.html +++ b/app/templates/help.html @@ -30,8 +30,11 @@ .help-badge.sso{background:rgba(217,115,13,.18);color:#E0952B;} -
-
+ +
+
diff --git a/e2e/v7693_help_positioning.spec.js b/e2e/v7693_help_positioning.spec.js new file mode 100644 index 0000000..2c94502 --- /dev/null +++ b/e2e/v7693_help_positioning.spec.js @@ -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 || 'e2e@flowdeck.local'; +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' }); +}); diff --git a/tests/test_v7693_help_positioning.py b/tests/test_v7693_help_positioning.py new file mode 100644 index 0000000..2738d52 --- /dev/null +++ b/tests/test_v7693_help_positioning.py @@ -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") == '
même position absolue dans le panneau + for src, name in ((HELP, "help"), (SETTINGS, "settings")): + assert 'class="settings-close"' in src, name