fix: og:image et twitter:image en URL absolue (v7.69.1)
FlowDeck CI / lint (push) Successful in 1m34s
FlowDeck CI / test (push) Failing after 3h12m7s
FlowDeck CI / docker (push) Skipped

Les meta `og:image` / `twitter:image` ajoutés en v7.69.0 pointaient vers une
URL relative (`/static/img/logo-512.jpg`). Les crawlers — LinkedIn, Slack,
Discord, iMessage — ignorent une URL relative : la carte de partage de FlowDeck
était donc vide partout où un lien est collé.

Correction via le motif déjà en place dans `app/templating.py` : un global
Jinja `app_base_url()` construit à partir de `settings.app_base_url`, qui est
LA MÊME source que les liens de partage de pages (`collaboration.py:32`).
Si cette valeur est mal renseignée, ces liens l'étaient déjà — comportement
cohérent plutôt qu'une deuxième source de vérité à maintenir.

Lecture paresseuse de `settings` dans la fonction, à l'image de
`_plugin_enabled` : un import au chargement du module créerait un import
circulaire.

Rendu vérifié sur l'instance déployée :
  og:image" content="http://localhost:8080/static/img/logo-512.jpg

Tests : `test_og_image_url_is_absolute_via_base_url` vérifie le global ET
l'absence de la forme relative dans les deux templates. Piège évité au passage
— la forme absolue CONTIENT l'ancienne en sous-chaîne, donc l'assertion
négative porte sur `content="/static/img/` (le guillemet avant la barre) et
non sur l'URL complète.

pytest 1413 passed / 0 failed · ruff OK · OpenAPI 526 chemins / 7.69.1
e2e 14/14 verts sur l'instance redéployée
This commit is contained in:
2026-10-08 19:00:13 -04:00
parent 3d3b46b58b
commit 674baf6954
9 changed files with 44 additions and 9 deletions
+11
View File
@@ -1,5 +1,16 @@
# Changelog - FlowDeck
## 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
+1 -1
View File
@@ -1 +1 @@
7.69.0
7.69.1
+1 -1
View File
@@ -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.1 (sélection multi-blocs fonctionnelle : drag souris + Maj+↓ + Ctrl+C/X, clic droit qui préserve le surlignage ; identité visuelle logo/bannière) | **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
@@ -186,7 +186,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
version="7.69.0",
version="7.69.1",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
+2 -2
View File
@@ -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 }}">
+2 -2
View File
@@ -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;
+12
View File
@@ -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:
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "FlowDeck",
"version": "7.69.0"
"version": "7.69.1"
},
"paths": {
"/auth/register": {
+13 -1
View File
@@ -123,10 +123,22 @@ 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_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