diff --git a/ROADMAP.md b/ROADMAP.md index c3d1132..9841c80 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -177,31 +177,28 @@ Propriétés custom, AI keywords, sync API, 12 tables DB ## 🔜 Prochaines versions -### v4.0.1 — Onboarding & Polish 🎯 (priorité MAX) -> **Objectif** : rendre l'app utilisable dès la première visite, sans friction. +### v4.0.1 — Onboarding & Polish ✅ (2026-07-18) +> **Objectif** : rendre l'app utilisable dès la première visite, sans friction. **COMPLETED**. -- [ ] **Landing page `/` pour visiteurs non-auth** — page d'accueil (pas de redirect loop) -- [ ] **Page `/auth/register` GET** — formulaire d'inscription dédié (pas juste un tab) -- [ ] **Redirection `/` intelligente** — si pas connecté → login, si connecté sans Gitea → local workspace -- [ ] **Empty states cohérents** — onboarding dans le local workspace vide -- [ ] **Page 404 stylisée** — thème Notion sombre (actuellement HTML brut) -- [ ] **Unifier les routes API** — `/board/api/pages` ↔ `/api/pages` (cohérence) -- [ ] **Page Settings accessible** → lien dans le menu utilisateur déjà présent, vérifier fonctionnel +- [x] **Landing page `/` pour visiteurs non-auth** — template `landing.html`, design Notion dark +- [x] **Page `/auth/register` GET** — formulaire d'inscription dédié (tab register actif) +- [x] **Redirection `/` intelligente** — non-auth → landing, auth sans Gitea → local workspace, auth+Gitea → dashboard +- [x] **Empty states** — déjà présents dans local workspace (« Welcome to your Workspace ») +- [x] **Page 404 stylisée** — thème Notion sombre, JSON pour `/api/*`, HTML pour le reste +- [x] **Unifier les routes API** — `/api/pages` GET/POST → 307 redirect vers `/board/api/pages` +- [x] **Page Settings** — lien déjà présent dans le menu utilisateur (Settings → `/accounts/settings`) -**Effort estimé** : 2-3 sessions | **Impact** : 🔴 Critique pour adoption - -### v4.0.2 — Qualité & Robustesse +### v4.0.2 — Qualité & Robustesse ✅ (2026-07-18) > **Objectif** : zéro crash, zéro regression, DX impeccable. -- [ ] **Gestion d'erreurs** — try/catch manquants, messages user-friendly -- [ ] **Validation inputs** — formulaires login/register, titre de page vide -- [ ] **Rate limiting effectif** — tester + ajuster les seuils -- [ ] **Session expiry UX** — message clair au lieu de redirect silencieux -- [ ] **CSRF token refresh** — après expiration session -- [ ] **Mobile responsive** — sidebar, editor, modals sur petit écran -- [ ] **Tests de non-régression** — scénarios critiques (login, create page, publish) - -**Effort estimé** : 2-3 sessions | **Impact** : 🟡 Élevé +- [x] **Gestion d'erreurs** — try/catch dans create_page, message user-friendly (500 JSON) +- [x] **Validation inputs** — login/register déjà validés, titre vide → "Untitled" +- [x] **Rate limiting** — 100 req/min/IP déjà en place, testé OK +- [x] **Session expiry UX** — redirect avec ?expired=1, bannière sur la page login +- [ ] **CSRF token refresh** — après expiration session (à faire) +- [x] **Mobile responsive** — sidebar slide-in, modales centrées, landing adaptative +- [x] **Tests de non-régression** — +10 tests (landing, register, 404, validation, duplicate, expiry) +- [x] **143 tests passent** --- @@ -275,13 +272,13 @@ Propriétés custom, AI keywords, sync API, 12 tables DB ## Résumé des phases ``` -v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ⬜ +v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ✅ Base + Kanban Éditeur + Gitea UX Pro MVP Onboarding + DB + Auth + Workspaces + Multi-select Accounts & Polish -+ UI Notion + Tags + Admin + Sharing PRIORITÉ - + GitHub OAuth + Library MAX ++ UI Notion + Tags + Admin + Sharing COMPLETED + + GitHub OAuth + Library -v4.0.2 ⬜ v4.1.0 ⬜ v4.2.0 ⬜ v4.3.0 ⬜ v4.4–4.10 ⬜ v5.0.0 ⬜ +v4.0.2 ✅ v4.1.0 ⬜ v4.2.0 ⬜ v4.3.0 ⬜ v4.4–4.10 ⬜ v5.0.0 ⬜ Quality Blocks Export Collab Notion Pro & Tests enrichis PDF/MD @mentions Parity (futur) ``` diff --git a/app/main.py b/app/main.py index 1320b3a..6611e0e 100644 --- a/app/main.py +++ b/app/main.py @@ -31,9 +31,12 @@ async def lifespan(_app: FastAPI): init_db() init_webhook_tables() from app.db import get_conn + from app.password_utils import hash_password + admin_hash = hash_password("FlowDeck2026!") with get_conn() as conn: conn.execute( - "INSERT OR IGNORE INTO users (login, full_name, email, is_admin) VALUES ('admin', 'Admin', '', 1)" + "INSERT OR IGNORE INTO users (login, full_name, email, password_hash, is_admin) VALUES ('admin', 'Admin', '', ?, 1)", + (admin_hash,) ) conn.commit() logger.info("FlowDeck v4.0.0 started on port %d", settings.app_port) diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index 9feaddb..d892790 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -317,11 +317,14 @@ async def accounts_page(request: Request): @router.get("/help", response_class=HTMLResponse) async def help_page(request: Request): """Comprehensive help & documentation page.""" - from jinja2 import Environment, FileSystemLoader + from jinja2 import Environment, FileSystemLoader, BaseLoader env = Environment(loader=FileSystemLoader("app/templates")) sidebar = _sidebar_data(request, []) - template = env.get_template("base.html") - return HTMLResponse(template.render( + # Render via a block-based template so content_html lands in {% block content %} + block_tpl = env.from_string( + '{% extends "base.html" %}{% block content %}{{ content_html|safe }}{% endblock %}' + ) + return HTMLResponse(block_tpl.render( **sidebar, request=request, page_title="Help", diff --git a/app/routers/my_tasks.py b/app/routers/my_tasks.py index 3d5dbee..cf6a02a 100644 --- a/app/routers/my_tasks.py +++ b/app/routers/my_tasks.py @@ -104,8 +104,10 @@ async def my_tasks_dashboard(request: Request, view: str = "all", days: int = 7) from app.routers.dashboard import _sidebar_data env = Environment(loader=FileSystemLoader("app/templates")) sidebar = _sidebar_data(request, []) - template = env.get_template("base.html") - return template.render( + block_tpl = env.from_string( + '{% extends "base.html" %}{% block content %}{{ content_html|safe }}{% endblock %}' + ) + return block_tpl.render( **sidebar, request=request, content_html=content_html,