From 2fde103e93db2a2b237876f70483e8ce626894ff Mon Sep 17 00:00:00 2001 From: Spark Date: Sun, 11 Oct 2026 20:15:23 -0400 Subject: [PATCH] =?UTF-8?q?feat(nav):=20header=20unifi=C3=A9=20desktop/mob?= =?UTF-8?q?ile=20(issue=20#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partiel _header.html unique pour les 19 pages (fin des copies qui dérivaient): desktop une ligne — primaires + menu Plus + Ctrl K, page active déduite du gabarit dans render(); mobile ≤900px burger + panneau ≥44px, Échap/clic extérieur; wallboard sans header et triage sur le même partiel. nav.js partagé. --- app/main.py | 22 +++++++++++ app/templates/_header.html | 63 +++++++++++++++++++++++++++++++ app/templates/activity.html | 24 +----------- app/templates/board.html | 25 +----------- app/templates/briefing.html | 22 +---------- app/templates/capture.html | 21 +---------- app/templates/dashboard.html | 23 +---------- app/templates/digest.html | 18 +-------- app/templates/error.html | 13 +------ app/templates/focus.html | 24 +----------- app/templates/forecast.html | 19 +--------- app/templates/health.html | 14 +------ app/templates/health_project.html | 10 +---- app/templates/my.html | 22 +---------- app/templates/rules.html | 13 +------ app/templates/search.html | 22 +---------- app/templates/sprint.html | 12 +----- app/templates/standup.html | 12 +----- app/templates/today.html | 21 +---------- app/templates/triage.html | 21 +---------- app/templates/triage_session.html | 9 +---- static/css/style.css | 45 ++++++++++++++++++++++ static/js/nav.js | 55 +++++++++++++++++++++++++++ 23 files changed, 204 insertions(+), 326 deletions(-) create mode 100644 app/templates/_header.html create mode 100644 static/js/nav.js diff --git a/app/main.py b/app/main.py index 51904e9..06634fb 100644 --- a/app/main.py +++ b/app/main.py @@ -292,6 +292,27 @@ _tpl_env = Environment( ) +# Which main-nav entry a template represents (issue #16): the +# shared _header.html partial highlights it from this one place +# instead of 19 hand-maintained copies. +_NAV_BY_TEMPLATE = { + "dashboard.html": "home", + "focus.html": "focus", + "today.html": "today", + "my.html": "my", + "activity.html": "activity", + "briefing.html": "briefing", + "standup.html": "standup", + "search.html": "search", + "capture.html": "capture", + "digest.html": "digest", + "forecast.html": "forecast", + "rules.html": "rules", + "health.html": "health", + "health_project.html": "health", +} + + def render( request: Request, name: str, status_code: int = 200, **context ) -> HTMLResponse: @@ -307,6 +328,7 @@ def render( token = secrets.token_urlsafe(32) context.setdefault("csrf_token", token) context.setdefault("version", APP_VERSION) + context.setdefault("active_nav", _NAV_BY_TEMPLATE.get(name, "")) html = _tpl_env.get_template(name).render(**context) response = HTMLResponse(html, status_code=status_code) if generated: diff --git a/app/templates/_header.html b/app/templates/_header.html new file mode 100644 index 0000000..70bb446 --- /dev/null +++ b/app/templates/_header.html @@ -0,0 +1,63 @@ +{# ── Header global partagé (issue #16) ───────────────────────────── + NE PAS dupliquer ce markup dans les pages : c'est l'unique source. + Page active : variable `active_nav` (déduite du gabarit dans + render(), app/main.py). Fil d'Ariane projet : `owner`+`repo` + (board) ou `full_name` (pages projet). Lien Gitea externe si + `repo_info.html_url` est présent (board). + Desktop : primaires + menu « Plus ▾ ». Mobile : burger + panneau. + Le wallboard (TV) n'inclut pas ce partiel, volontairement. #} +
+
+ + {% if owner is defined and repo is defined %} + / {{ owner }} / {{ repo }} + {% elif full_name is defined %} + / {{ full_name }} + {% endif %} +
+ +
+ {% if repo_info is defined and repo_info and repo_info.html_url %} + 🔗 Gitea ↗ + {% endif %} + + +
+ + +
diff --git a/app/templates/activity.html b/app/templates/activity.html index 756c875..f0549eb 100644 --- a/app/templates/activity.html +++ b/app/templates/activity.html @@ -8,29 +8,7 @@ -
-
- - / 📰 Activité -
- -
+ {% include "_header.html" %}

Les {{ events|length }} derniers événements du journal — déplacements, créations, fermetures et commentaires, tous projets confondus.

diff --git a/app/templates/board.html b/app/templates/board.html index 6ea1689..b84f5cc 100644 --- a/app/templates/board.html +++ b/app/templates/board.html @@ -12,30 +12,7 @@ -
-
- - / {{ owner }} / {{ repo }} -
- -
+ {% include "_header.html" %} {% if milestones %}
diff --git a/app/templates/briefing.html b/app/templates/briefing.html index f0f7dce..96642de 100644 --- a/app/templates/briefing.html +++ b/app/templates/briefing.html @@ -9,27 +9,7 @@ -
-
- - / 📊 Briefing -
- -
+ {% include "_header.html" %}

Le briefing du {{ data.date }} — le même contenu que celui poussé sur ntfy.

diff --git a/app/templates/capture.html b/app/templates/capture.html index fbf97da..684fd57 100644 --- a/app/templates/capture.html +++ b/app/templates/capture.html @@ -8,26 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 50d2d6e..9930b77 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -9,28 +9,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/digest.html b/app/templates/digest.html index c724c74..3032b19 100644 --- a/app/templates/digest.html +++ b/app/templates/digest.html @@ -8,23 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/error.html b/app/templates/error.html index 52875f7..38b52fe 100644 --- a/app/templates/error.html +++ b/app/templates/error.html @@ -7,18 +7,7 @@ -
- - -
+ {% include "_header.html" %}

⚠️ Quelque chose s'est mal passé

diff --git a/app/templates/focus.html b/app/templates/focus.html index 7dbed1b..4d08ab7 100644 --- a/app/templates/focus.html +++ b/app/templates/focus.html @@ -8,29 +8,7 @@ -
-
- - / 🎯 Focus -
- -
+ {% include "_header.html" %}

Tout ce qui est En cours ou En révision, tous projets actifs confondus.

diff --git a/app/templates/forecast.html b/app/templates/forecast.html index 7545bc6..a25c32f 100644 --- a/app/templates/forecast.html +++ b/app/templates/forecast.html @@ -8,24 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/health.html b/app/templates/health.html index b26f2c1..adc43a2 100644 --- a/app/templates/health.html +++ b/app/templates/health.html @@ -8,19 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/health_project.html b/app/templates/health_project.html index 98df4b9..07b2527 100644 --- a/app/templates/health_project.html +++ b/app/templates/health_project.html @@ -8,15 +8,7 @@ -
- - -
+ {% include "_header.html" %}
{% if health is none %} diff --git a/app/templates/my.html b/app/templates/my.html index d035dac..663bc8c 100644 --- a/app/templates/my.html +++ b/app/templates/my.html @@ -8,27 +8,7 @@ -
-
- - / 👤 Mes issues -
- -
+ {% include "_header.html" %}
{% if not user %} diff --git a/app/templates/rules.html b/app/templates/rules.html index 3730eac..07ea268 100644 --- a/app/templates/rules.html +++ b/app/templates/rules.html @@ -8,18 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/search.html b/app/templates/search.html index 0dca2b3..3cba274 100644 --- a/app/templates/search.html +++ b/app/templates/search.html @@ -8,27 +8,7 @@ -
-
- - / 🔍 Recherche -
- -
+ {% include "_header.html" %}
diff --git a/app/templates/sprint.html b/app/templates/sprint.html index a3088b7..2f1b3ad 100644 --- a/app/templates/sprint.html +++ b/app/templates/sprint.html @@ -10,17 +10,7 @@ -
- - -
+ {% include "_header.html" %}
{% if markdown %} diff --git a/app/templates/standup.html b/app/templates/standup.html index 4725668..ae13dee 100644 --- a/app/templates/standup.html +++ b/app/templates/standup.html @@ -8,17 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/today.html b/app/templates/today.html index addfcfc..04c4c82 100644 --- a/app/templates/today.html +++ b/app/templates/today.html @@ -8,26 +8,7 @@ -
- - -
+ {% include "_header.html" %}

🌅 {{ date_label }}

diff --git a/app/templates/triage.html b/app/templates/triage.html index c93977a..7c29d1c 100644 --- a/app/templates/triage.html +++ b/app/templates/triage.html @@ -8,26 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/app/templates/triage_session.html b/app/templates/triage_session.html index d227a94..0900d76 100644 --- a/app/templates/triage_session.html +++ b/app/templates/triage_session.html @@ -8,14 +8,7 @@ -
- - -
+ {% include "_header.html" %}
diff --git a/static/css/style.css b/static/css/style.css index 4f98e27..bdb6a5b 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1164,3 +1164,48 @@ footer { .health-table th, .health-table td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border, #22304a); } .health-slowest td { color: var(--warn, #e8a33d); font-weight: 600; } .zombie-actions { display: flex; gap: 6px; flex-shrink: 0; } + +/* ── Header unifié (0.15.0, #16) ── */ +.main-nav { flex-wrap: nowrap; } +.header-actions { display: flex; gap: 6px; align-items: center; margin-left: auto; } +.nav-more { position: relative; } +.nav-more-btn { background: transparent; cursor: pointer; } +.nav-more-btn.active-soft { color: var(--text); border-color: var(--accent, #58a6ff); } +.nav-more-menu { + position: absolute; right: 0; top: calc(100% + 8px); z-index: 120; + min-width: 232px; display: flex; flex-direction: column; gap: 2px; + background: var(--surface-1, #131a29); border: 1px solid var(--border); + border-radius: 12px; padding: 6px; + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45); +} +.nav-more-menu .nav-pill { display: flex; border-radius: 8px; } +.nav-more-menu[hidden], .mobile-nav[hidden] { display: none !important; } +.nav-burger { + display: none; width: 40px; height: 40px; border-radius: 10px; + background: var(--surface-2); border: 1px solid var(--border); + color: var(--text); font-size: 1.05rem; cursor: pointer; + align-items: center; justify-content: center; +} +.mobile-nav { + position: absolute; top: 100%; left: 0; right: 0; z-index: 110; + display: flex; flex-direction: column; gap: 2px; + background: rgba(16, 19, 28, 0.97); backdrop-filter: blur(14px); + border-bottom: 1px solid var(--border); padding: 10px 14px 16px; +} +.mobile-nav a { + display: flex; align-items: center; min-height: 44px; + padding: 10px 12px; border-radius: 10px; + color: var(--text-2); font-size: 1rem; text-decoration: none; +} +.mobile-nav a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; } +.mobile-nav a.active { + color: #fff; + background: linear-gradient(135deg, rgba(124,92,252,.85), rgba(91,138,247,.85)); +} +.crumb a { color: inherit; text-decoration: none; } +.crumb a:hover { text-decoration: underline; } +@media (max-width: 900px) { + .main-nav { display: none; } + .nav-burger { display: inline-flex; } + .crumb { display: none; } +} diff --git a/static/js/nav.js b/static/js/nav.js new file mode 100644 index 0000000..f485543 --- /dev/null +++ b/static/js/nav.js @@ -0,0 +1,55 @@ +/** + * Kanbante — header navigation (issue #16): the "Plus" dropdown on + * desktop and the burger panel on mobile. One shared partial + * (_header.html), one shared behaviour: this file. + */ +(function () { + var moreBtn = document.getElementById('nav-more-btn'); + var moreMenu = document.getElementById('nav-more-menu'); + var burger = document.getElementById('nav-burger'); + var mobileNav = document.getElementById('mobile-nav'); + + function setOpen(menu, btn, open) { + if (!menu || !btn) return; + menu.hidden = !open; + btn.setAttribute('aria-expanded', open ? 'true' : 'false'); + } + function closeAll() { + setOpen(moreMenu, moreBtn, false); + setOpen(mobileNav, burger, false); + if (burger) { + burger.textContent = '☰'; + burger.setAttribute('aria-label', 'Ouvrir le menu'); + } + } + + if (moreBtn && moreMenu) { + moreBtn.addEventListener('click', function (e) { + e.stopPropagation(); + var willOpen = moreMenu.hidden; + closeAll(); + setOpen(moreMenu, moreBtn, willOpen); + }); + moreMenu.addEventListener('click', function (e) { e.stopPropagation(); }); + } + if (burger && mobileNav) { + burger.addEventListener('click', function (e) { + e.stopPropagation(); + var willOpen = mobileNav.hidden; + closeAll(); + setOpen(mobileNav, burger, willOpen); + burger.textContent = willOpen ? '✕' : '☰'; + burger.setAttribute('aria-label', willOpen ? 'Fermer le menu' : 'Ouvrir le menu'); + }); + } + document.addEventListener('click', function () { closeAll(); }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAll(); + }); + window.addEventListener('resize', function () { + if (window.innerWidth > 900) { + closeAll(); + if (burger) burger.textContent = '☰'; + } + }); +})();