Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162eb257c7 | ||
|
|
b9a776b626 | ||
|
|
674baf6954 |
@@ -1,5 +1,69 @@
|
||||
# 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
|
||||
|
||||
- **Le favicon était un placeholder** : un carré bleu `#2383E2` avec les lettres
|
||||
« FD » écrites en `<text>` SVG — génériques, sans lien avec la marque, et
|
||||
rendus avec la police du visiteur.
|
||||
- → `static/favicon.ico` généré depuis `assets/logo.jpg` : 16/32/48 px dans un
|
||||
seul fichier (un `<link>` pour tous les navigateurs), 4,1 Ko.
|
||||
|
||||
**Pourquoi un fond sombre** : le logo est blanc à ~82 % (mesuré : 50,7 % de
|
||||
blanc pur + 32 % de blancs proches). Posé nu, il disposerait sur un onglet clair
|
||||
— cas nominal de Firefox et Safari en thème clair. Il est donc posé sur un
|
||||
carré `#191919` arrondi, **le même fond que les icônes PWA** générées en v7.69.0,
|
||||
ce qui garde une identité cohérente. Contraste mesuré à 16 px : σ = 106 (contre
|
||||
σ = 49 pour le logo seul — le fond sombre double la lisibilité).
|
||||
|
||||
Mise à jour des **5 templates** qui référençaient l'ancien SVG (`base`,
|
||||
`import`, `landing`, `public_page`, `welcome`) + la liste de pré-cache du
|
||||
service worker. `static/favicon.svg` **supprimé** : plus aucune référence.
|
||||
|
||||
### Tests
|
||||
|
||||
- `test_favicon_is_the_real_logo` (tailles ICO, absence du placeholder) +
|
||||
`test_every_template_points_at_the_real_favicon` (5 templates + `sw.js`).
|
||||
|
||||
## v7.69.1 (2026-10-08) — `og:image` en URL absolue
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`og:image` / `twitter:image` étaient des URL relatives** — les crawlers
|
||||
(LinkedIn, Slack, Discord, iMessage…) ignorent une URL relative : la carte de
|
||||
partage était vide. → global Jinja `app_base_url()` construit à partir de
|
||||
`settings.app_base_url`, **la même source que les liens de partage de pages**
|
||||
(`collaboration.py`) : si elle est mal renseignée, ces liens l'étaient déjà.
|
||||
Lecture paresseuse, comme `_plugin_enabled`, pour éviter l'import circulaire.
|
||||
|
||||
## v7.69.0 (2026-10-08) — La sélection multi-blocs fonctionne enfin (+ identité visuelle)
|
||||
|
||||
### Fixed
|
||||
|
||||
+45
@@ -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
@@ -1,6 +1,6 @@
|
||||
# WORKLOAD — FlowDeck Notion Clone
|
||||
|
||||
> **Début**: 2026-07-08 | **Version**: v7.69.0 (sélection multi-blocs fonctionnelle : drag souris + Maj+↓, clic droit qui la préserve) | **Statut**: EN COURS 🔄
|
||||
> **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
@@ -186,7 +186,7 @@ async def lifespan(_app: FastAPI):
|
||||
|
||||
app = FastAPI(
|
||||
title="FlowDeck",
|
||||
version="7.69.0",
|
||||
version="7.69.3",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
lifespan=lifespan,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FlowDeck — {% block title_prefix %}Home{% endblock %}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" href="/static/favicon.ico" sizes="48x48">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<meta name="theme-color" content="#191919">
|
||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||
@@ -14,11 +14,11 @@
|
||||
<meta property="og:site_name" content="FlowDeck">
|
||||
<meta property="og:title" content="FlowDeck — votre espace Notion auto-hébergé">
|
||||
<meta property="og:description" content="Éditeur de blocs, Kanban, bases de données et intégration Gitea/GitHub, sur votre propre serveur.">
|
||||
<meta property="og:image" content="/static/img/logo-512.jpg">
|
||||
<meta property="og:image" content="{{ app_base_url() }}/static/img/logo-512.jpg">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="FlowDeck — votre espace Notion auto-hébergé">
|
||||
<meta name="twitter:description" content="Éditeur de blocs, Kanban, bases de données et intégration Gitea/GitHub.">
|
||||
<meta name="twitter:image" content="/static/img/logo-512.jpg">
|
||||
<meta name="twitter:image" content="{{ app_base_url() }}/static/img/logo-512.jpg">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="stylesheet" href="/static/css/app.css?v={{ asset_version }}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Importer — FlowDeck</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" href="/static/favicon.ico" sizes="48x48">
|
||||
<script src="/static/js/alpine.csp.min.js" defer data-cfasync="false"></script>
|
||||
<style>
|
||||
:root{--bg:#191919;--bg2:#1F1F1F;--bg3:#2D2D2D;--border:#333;--text:#fff;--dim:#9B9A97;--accent:#2383E2;--accent-h:#1a6bc0;--success:#0F7B6C;--danger:#E03E3E;--warn:#D9730D;}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% from '_icons.html' import fd_icon %}
|
||||
<title>FlowDeck — All-in-one workspace</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" href="/static/favicon.ico" sizes="48x48">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<meta name="theme-color" content="#191919">
|
||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||
@@ -17,11 +17,11 @@
|
||||
<meta property="og:site_name" content="FlowDeck">
|
||||
<meta property="og:title" content="FlowDeck — votre espace Notion auto-hébergé">
|
||||
<meta property="og:description" content="Éditeur de blocs, Kanban, bases de données et intégration Gitea/GitHub, sur votre propre serveur.">
|
||||
<meta property="og:image" content="/static/img/logo-512.jpg">
|
||||
<meta property="og:image" content="{{ app_base_url() }}/static/img/logo-512.jpg">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="FlowDeck — votre espace Notion auto-hébergé">
|
||||
<meta name="twitter:description" content="Éditeur de blocs, Kanban, bases de données et intégration Gitea/GitHub.">
|
||||
<meta name="twitter:image" content="/static/img/logo-512.jpg">
|
||||
<meta name="twitter:image" content="{{ app_base_url() }}/static/img/logo-512.jpg">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #191919;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% from '_icons.html' import fd_icon %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }} — FlowDeck</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" href="/static/favicon.ico" sizes="48x48">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #191919;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bienvenue sur FlowDeck</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="icon" href="/static/favicon.ico" sizes="48x48">
|
||||
<script src="/static/js/alpine.csp.min.js" defer data-cfasync="false"></script>
|
||||
<style>
|
||||
:root{--bg:#191919;--bg2:#1F1F1F;--bg3:#2D2D2D;--border:#333;--text:#fff;--dim:#9B9A97;--accent:#2383E2;--accent-h:#1a6bc0;--success:#0F7B6C;--danger:#E03E3E;}
|
||||
|
||||
@@ -79,6 +79,18 @@ ENV.globals["csp_nonce"] = lambda: CSP_NONCE.get()
|
||||
ENV.globals["csrf_token"] = lambda: CSRF_TOKEN.get()
|
||||
# Plugins activés : conditionne les blocs de UI rendus côté serveur.
|
||||
ENV.globals["plugin_enabled"] = lambda slug: _plugin_enabled(slug)
|
||||
# v7.69 : URL de base absolue pour og:image / twitter:image — les crawlers
|
||||
# ignorent les URL relatives. Lecture paresseuse comme _plugin_enabled
|
||||
# (évite l'import circulaire au chargement du module). Même source que les
|
||||
# liens de partage de pages (collaboration.py) : si elle est mal renseignée,
|
||||
# ces liens le sont déjà.
|
||||
ENV.globals["app_base_url"] = lambda: _app_base_url()
|
||||
|
||||
|
||||
def _app_base_url() -> str:
|
||||
from app.config import settings
|
||||
|
||||
return (settings.app_base_url or "").rstrip("/")
|
||||
|
||||
|
||||
def _plugin_enabled(slug: str) -> bool:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FlowDeck",
|
||||
"version": "7.69.0"
|
||||
"version": "7.69.2"
|
||||
},
|
||||
"paths": {
|
||||
"/auth/register": {
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#2383E2"/>
|
||||
<text x="16" y="22" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" font-weight="bold" fill="#fff">FD</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 253 B |
+1
-1
@@ -30,7 +30,7 @@ const PRECACHE_URLS = [
|
||||
'/static/js/katex.min.js?v=0.16.11',
|
||||
'/static/js/prism.min.js',
|
||||
// Icônes & métadonnées
|
||||
'/static/favicon.svg',
|
||||
'/static/favicon.ico',
|
||||
'/static/manifest.json',
|
||||
'/static/icons/icon-192x192.png',
|
||||
'/static/icons/icon-512x512.png',
|
||||
|
||||
@@ -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
|
||||
@@ -123,10 +123,44 @@ class TestBranding:
|
||||
|
||||
def test_base_head_declares_open_graph(self):
|
||||
base = (ROOT / "app" / "templates" / "base.html").read_text(encoding="utf-8")
|
||||
assert 'property="og:image" content="/static/img/logo-512.jpg"' in base
|
||||
# URL ABSOLUE : les crawlers ignorent les URL relatives
|
||||
assert 'content="{{ app_base_url() }}/static/img/logo-512.jpg"' in base
|
||||
assert 'name="twitter:card" content="summary_large_image"' in base
|
||||
assert '<meta name="description"' in base
|
||||
|
||||
def test_og_image_url_is_absolute_via_base_url(self):
|
||||
# le global expose la même source que les liens de partage de pages
|
||||
templating = (ROOT / "app" / "templating.py").read_text(encoding="utf-8")
|
||||
assert 'ENV.globals["app_base_url"]' in templating
|
||||
for tpl in ("base.html", "landing.html"):
|
||||
src = (ROOT / "app" / "templates" / tpl).read_text(encoding="utf-8")
|
||||
assert 'content="{{ app_base_url() }}/static/img/logo-512.jpg"' in src, tpl
|
||||
# forme RELATIVE interdite (le guillemet avant la barre est ce qui
|
||||
# distingue : la bonne valeur contient l'ancienne en sous-chaîne)
|
||||
assert 'content="/static/img/' not in src, tpl
|
||||
|
||||
def test_favicon_is_the_real_logo(self):
|
||||
"""L'ancien favicon était un placeholder « FD » en SVG — pas la marque."""
|
||||
ico = ROOT / "static" / "favicon.ico"
|
||||
assert ico.exists()
|
||||
assert ico.stat().st_size < 20_000 # un favicon pèse des Ko, pas des Mo
|
||||
# 16/32/48 : la taille de l'onglet ET le repli rétro
|
||||
from PIL import Image
|
||||
|
||||
assert Image.open(ico).info.get("sizes") >= {(16, 16), (32, 32), (48, 48)}
|
||||
# le placeholder SVG doit avoir disparu
|
||||
assert not (ROOT / "static" / "favicon.svg").exists()
|
||||
|
||||
def test_every_template_points_at_the_real_favicon(self):
|
||||
for tpl in ("base.html", "import.html", "landing.html", "public_page.html", "welcome.html"):
|
||||
src = (ROOT / "app" / "templates" / tpl).read_text(encoding="utf-8")
|
||||
assert 'rel="icon" href="/static/favicon.ico"' in src, tpl
|
||||
assert "favicon.svg" not in src, tpl
|
||||
# le service worker pré-met en cache la bonne URL
|
||||
sw = (ROOT / "static" / "sw.js").read_text(encoding="utf-8")
|
||||
assert "'/static/favicon.ico'," in sw
|
||||
assert "favicon.svg" not in sw
|
||||
|
||||
def test_landing_shows_logo_and_banner(self):
|
||||
landing = (ROOT / "app" / "templates" / "landing.html").read_text(encoding="utf-8")
|
||||
# le logo remplace l'icône générique dans la barre de navigation
|
||||
|
||||
Reference in New Issue
Block a user