Compare commits

..
Author SHA1 Message Date
bruno 40f034e86e Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 0s
CI / test (pull_request) Failing after 1s
2026-10-29 11:35:48 -04:00
bruno 911f07362f Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 1s
2026-10-29 11:35:47 -04:00
bruno ee837972be Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 0s
2026-10-29 11:35:45 -04:00
bruno 4470e6ccbc Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 0s
2026-10-29 11:35:43 -04:00
bruno d8cb4664e5 Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 4m39s
2026-10-29 11:35:41 -04:00
bruno f2e702a3d9 Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 1s
2026-10-29 11:35:36 -04:00
bruno 435954c31b Page Projets : copie URL dans le menu ⋯ + refonte thème barre Fusion/Spark (#44, #45)
CI / test (push) Failing after 1s
2026-10-29 11:35:33 -04:00
bruno 7f66775039 Merge pull request 'Icônes monochromes sur tout le site : emoji colorés retirés (#59, suite)' (#70) from icones-monochromes-59 into main
CI / test (push) Successful in 3m58s
2026-10-29 08:27:48 -04:00
7 changed files with 164 additions and 56 deletions
+5
View File
@@ -1,3 +1,8 @@
## 0.44.0 — 2026-10-29 — Ergonomie : tuile Projets et pied de sidebar
- **Tuile Projets** : en-tête groupé (gauche : organisation + santé, droite : date + étoile + menu) au lieu de 5 éléments éparpillés ; avancement global labellisé (« Avancement — X/Y terminées ») au lieu d'une barre anonyme ; jalon séparé par un filet, avec étiquette « Prochain jalon » et échéance distincte ; pied en 2 rangées — (1) compte d'issues + bouton Board, (2) répartition par colonne labellisée (« En cours 8 », « Terminé 59 », au lieu de chiffres nus 8 / 59) + prévision ; activité de la semaine en texte secondaire (« Cette semaine : … »).
- **Pied de sidebar** : 2 rangées séparées par un filet — (1) profil + bouton Sortir icône seule (⏻, aria-label, sans texte), (2) version + fraîcheur des données en pastille discrète (point vert + « à l'instant », infobulle expliquant les instantanés Gitea). En rail replié, la rangée méta est masquée, les icônes restent centrées.
## 0.43.0 — 2026-10-29 — Icônes monochromes sur tout le site (#59, suite)
- **Icônes colorées retirées de toutes les pages** : emoji décoratifs devant titres, boutons et liens supprimés (le texte porte le sens) ; icônes fonctionnelles passées en trait/monochrome — étoile ★/☆ (couleur texte, plus jaune), recherche en SVG, épingler déjà en SVG, thème ☀/☾ en symboles texte.
+1 -1
View File
@@ -177,7 +177,7 @@ from urllib.parse import urlparse
BASE_DIR = Path(__file__).resolve().parent.parent
APP_VERSION = "0.43.0"
APP_VERSION = "0.44.0"
def _env_bool(name: str, default: bool) -> bool:
+17 -13
View File
@@ -137,19 +137,23 @@
{% endfor %}
</nav>
<div class="side-foot">
{% if data_freshness %}
<span class="side-fresh side-label" title="Dernière actualisation des données Gitea (instantanés d'arrière-plan)">{{ data_freshness }}</span>
{% endif %}
{% if current_user %}
<a href="/profile" class="side-logout" title="Mon profil"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" style="width:18px;height:18px;display:inline-block;vertical-align:-3px"><circle cx="12" cy="8" r="3.5"/><path d="M4 20c0-3.5 3.5-5.5 8-5.5s8 2 8 5.5"/></svg><span class="side-label"> {{ current_user.display_name or current_user.username }}</span></a>
{% endif %}
<span class="side-version side-label">v{{ version }}</span>
{% if not is_admin %}
<button type="button" class="theme-toggle-foot" id="theme-toggle" title="Basculer entre thème sombre et clair" aria-label="Basculer entre thème sombre et clair"><span data-theme-ico>☀</span></button>
{% endif %}
{% if auth_enabled %}
<a href="/logout" class="side-logout" title="Se déconnecter">⏻<span class="side-label"> Sortir</span></a>
{% endif %}
<div class="side-foot-user">
{% if current_user %}
<a href="/profile" class="side-logout side-profile" title="Mon profil"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" style="width:18px;height:18px;display:inline-block;vertical-align:-3px"><circle cx="12" cy="8" r="3.5"/><path d="M4 20c0-3.5 3.5-5.5 8-5.5s8 2 8 5.5"/></svg><span class="side-label"> {{ current_user.display_name or current_user.username }}</span></a>
{% endif %}
{% if not is_admin %}
<button type="button" class="theme-toggle-foot" id="theme-toggle" title="Basculer entre thème sombre et clair" aria-label="Basculer entre thème sombre et clair"><span data-theme-ico>☀</span></button>
{% endif %}
{% if auth_enabled %}
<a href="/logout" class="side-logout logout-icon" title="Se déconnecter" aria-label="Se déconnecter">⏻</a>
{% endif %}
</div>
<div class="side-foot-meta">
<span class="side-version side-label">v{{ version }}</span>
{% if data_freshness %}
<span class="side-fresh" title="Dernière actualisation des données Gitea (instantanés d'arrière-plan) — les données affichées datent de cet instantané"><span class="fresh-dot" aria-hidden="true"></span><span class="side-label">{{ data_freshness }}</span></span>
{% endif %}
</div>
</div>
</aside>
<div class="side-backdrop" id="side-backdrop" hidden></div>
+39 -28
View File
@@ -91,14 +91,17 @@
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
x-show="search === '' || $el.dataset.search.includes(search.toLowerCase())">
<div class="card-header">
<span class="org-tag">{{ repo.owner.login }}</span>
{% if multi_conn %}<span class="conn-badge" title="Connexion {{ repo._conn_slug }}">{% if repo._conn_kind == 'github' %}GitHub{% else %}Gitea{% endif %} {{ repo._conn_slug }}</span>{% endif %}
{% if repo.health_dot %}
<a href="/health/{{ full }}" class="health-dot {{ repo.health_dot.level }}" title="Santé du flux — {{ repo.health_dot.tooltip }}" aria-label="Santé du flux : {{ repo.health_dot.tooltip }}"></a>
{% endif %}
<span class="date">{{ repo.updated_at[:10] }}</span>
{% if not archived %}
<button type="button" class="star-btn" data-full="{{ full }}" data-starred="{{ 1 if repo.starred else 0 }}" title="{{ 'Désépingler (étoile Gitea)' if repo.starred else 'Épingler (étoile Gitea)' }}" aria-label="Épingler {{ repo.name }}">{{ '' if repo.starred else '☆' }}</button>
<div class="card-header-left">
<span class="org-tag">{{ repo.owner.login }}</span>
{% if multi_conn %}<span class="conn-badge" title="Connexion {{ repo._conn_slug }}">{% if repo._conn_kind == 'github' %}GitHub{% else %}Gitea{% endif %} {{ repo._conn_slug }}</span>{% endif %}
{% if repo.health_dot %}
<a href="/health/{{ full }}" class="health-dot {{ repo.health_dot.level }}" title="Santé du flux — {{ repo.health_dot.tooltip }}" aria-label="Santé du flux : {{ repo.health_dot.tooltip }}"></a>
{% endif %}
</div>
<div class="card-header-actions">
<span class="date" title="Dernière activité">{{ repo.updated_at[:10] }}</span>
{% if not archived %}
<button type="button" class="star-btn" data-full="{{ full }}" data-starred="{{ 1 if repo.starred else 0 }}" title="{{ 'Désépingler (étoile Gitea)' if repo.starred else 'Épingler (étoile Gitea)' }}" aria-label="Épingler {{ repo.name }}">{{ '' if repo.starred else '☆' }}</button>
<div class="card-menu-wrap">
<button type="button" class="card-menu-btn" data-menu-btn aria-haspopup="true" aria-expanded="false" title="Actions rapides pour {{ repo.name }}" aria-label="Actions rapides pour {{ repo.name }}">⋯</button>
<div class="card-menu" hidden>
@@ -115,26 +118,30 @@
</div>
</div>
{% endif %}
</div>
</div>
<h3>
<a href="/project/{{ repo.owner.login }}/{{ repo.name }}">{{ repo.name }}</a>
</h3>
<p class="desc">{{ repo.description or '—' }}</p>
<p class="desc" title="{{ repo.description or '' }}">{{ repo.description or '—' }}</p>
{% if full in stats %}
{% set total = stats[full].values()|sum %}
{% set done_n = stats[full].get('done', 0) %}
{% if total > 0 %}
<div class="progress" title="{{ done_n }}/{{ total }} terminées">
<span style="width: {{ (100 * done_n / total)|round|int }}%"></span>
<div class="card-progress">
<div class="progress-head"><span>Avancement</span><span>{{ done_n }}/{{ total }} terminées</span></div>
<div class="progress" title="{{ done_n }}/{{ total }} terminées">
<span style="width: {{ (100 * done_n / total)|round|int }}%"></span>
</div>
</div>
{% endif %}
{% endif %}
{% if repo.next_milestone %}
{% set ms = repo.next_milestone %}
<div class="ms-block">
<div class="ms-head"><span class="ms-kicker">Prochain jalon</span>{% if ms.due_label %}<span class="ms-due{% if ms.late %} late{% endif %}">échéance {{ ms.due_label }}</span>{% endif %}</div>
<div class="ms-line">
<a href="/forecast#ms-{{ repo.owner.login }}-{{ repo.name }}" class="ms-title" title="Voir ce jalon dans le tableau des prévisions">{{ ms.title }}</a>
{% if ms.due_label %}<span class="ms-due{% if ms.late %} late{% endif %}">{{ '' if ms.late }}échéance {{ ms.due_label }}</span>{% endif %}
</div>
{% if ms.total %}
<div class="progress ms-progress"><span style="width: {{ ms.pct }}%"></span></div>
@@ -143,28 +150,32 @@
</div>
{% endif %}
<div class="card-footer">
<span>{{ repo.open_issues_count or 0 }} issues ouvertes</span>
<a href="/board/{{ full }}" class="card-board-link">Board</a>
{% if repo.forecast_label %}
<span class="forecast-line" title="Prévision d'après le débit réel du journal (fermetures par semaine)">{{ repo.forecast_label }}</span>
{% endif %}
{% if full in stats %}
<div class="mini-progress">
{% for col in column_keys %}
{% set n = stats[full].get(col, 0) %}
{% if n > 0 %}
<span class="chip" style="background: color-mix(in srgb, {{ COLUMN_COLORS[col] }} 25%, transparent); color: var(--text)">
{{ n }}
</span>
<div class="foot-main">
<span class="foot-count">{{ repo.open_issues_count or 0 }} issues ouvertes</span>
<a href="/board/{{ full }}" class="card-board-link btn-sm">Board →</a>
</div>
<div class="foot-dist">
{% if full in stats %}
<div class="mini-progress" aria-label="Répartition par colonne">
{% for col in column_keys %}
{% set n = stats[full].get(col, 0) %}
{% if n > 0 %}
<span class="chip" title="{{ COLUMN_LABELS[col] }}" style="background: color-mix(in srgb, {{ COLUMN_COLORS[col] }} 25%, transparent); color: var(--text)">
{{ COLUMN_LABELS[col] }} {{ n }}
</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% if repo.forecast_label %}
<span class="forecast-line" title="Prévision d'après le débit réel du journal (fermetures par semaine)">{{ repo.forecast_label }}</span>
{% endif %}
</div>
{% endif %}
</div>
{% if repo.activity %}
<div class="activity-line">
{% if repo.activity.closed or repo.activity.moved %}
{{ repo.activity.closed }} fermée(s) · {{ repo.activity.moved }} déplacement(s) cette semaine
Cette semaine : {{ repo.activity.closed }} fermée(s) · {{ repo.activity.moved }} déplacement(s)
{% elif repo.activity.idle_days is not none and repo.activity.idle_days > 7 %}
aucune activité kanban depuis {{ repo.activity.idle_days }} j
{% endif %}
+64 -13
View File
@@ -315,8 +315,15 @@ a.kpi:hover { background: var(--surface-2); text-decoration: none; }
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
gap: 12px;
margin-bottom: 12px;
}
.card-header-left, .card-header-actions {
display: flex; align-items: center; gap: 8px; min-width: 0;
}
.card-header-actions { flex-shrink: 0; }
.card-header .date { white-space: nowrap; }
.card-header .star-btn { margin-left: 0; }
.org-tag {
font-size: 0.68rem;
text-transform: uppercase;
@@ -340,20 +347,33 @@ a.kpi:hover { background: var(--surface-2); text-decoration: none; }
min-height: 2.4em;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
color: var(--text-2);
gap: 10px;
display: flex; flex-direction: column; align-items: stretch;
font-size: 0.8rem; color: var(--text-2); gap: 10px;
border-top: 1px solid var(--border);
padding-top: 12px; margin-top: 14px;
}
.mini-progress { display: flex; gap: 4px; }
.foot-main, .foot-dist {
display: flex; justify-content: space-between; align-items: center;
gap: 10px; flex-wrap: wrap;
}
.foot-count { font-weight: 600; color: var(--text); }
.card-board-link.btn-sm { text-decoration: none; }
.mini-progress { display: flex; gap: 4px; flex-wrap: wrap; }
.mini-progress .chip { white-space: nowrap; }
.forecast-line { white-space: nowrap; }
.chip {
padding: 2px 9px;
border-radius: var(--radius-full);
font-size: 0.7rem;
font-weight: 700;
}
.card-progress { margin-bottom: 12px; }
.progress-head {
display: flex; justify-content: space-between; align-items: baseline;
gap: 10px; margin-bottom: 6px;
font-size: 0.78rem; color: var(--text-2);
}
.progress-head span:last-child { font-weight: 600; color: var(--text); white-space: nowrap; }
.progress {
height: 6px;
border-radius: var(--radius-full);
@@ -1387,7 +1407,24 @@ body.nav-closed-admin [data-group="admin"] .side-chevron { transform: rotate(-90
.theme-toggle-foot:hover { color: var(--text); }
.side-foot {
margin-top: auto; padding-top: 12px;
display: flex; align-items: center; justify-content: space-between; gap: 8px;
display: flex; flex-direction: column; align-items: stretch; gap: 8px;
border-top: 1px solid var(--border);
}
.side-foot-user { display: flex; align-items: center; gap: 8px; }
.side-foot-user .side-profile { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.side-logout.logout-icon {
width: 36px; height: 36px; flex-shrink: 0;
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.side-logout.logout-icon:hover { border-color: var(--accent, #0d9488); }
.side-foot-meta {
display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.fresh-dot {
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
background: var(--accent-2); display: inline-block; margin-right: 6px;
vertical-align: 1px;
}
.side-collapse {
/* Depuis la 0.22.0 : bouton icône seule dans l'en-tête de la
@@ -1403,7 +1440,7 @@ body.nav-closed-admin [data-group="admin"] .side-chevron { transform: rotate(-90
color: var(--text); border-color: var(--accent, #0d9488);
transform: translateY(-1px);
}
.side-fresh { color: var(--text-3); font-size: 0.76rem; white-space: nowrap; }
.side-fresh { color: var(--text-3); font-size: 0.76rem; white-space: nowrap; display: inline-flex; align-items: center; }
.side-logout { color: var(--text-3); font-size: 0.85rem; white-space: nowrap; }
.side-logout:hover { color: var(--text); text-decoration: none; }
.side-collapse-ico { display: inline-block; transition: transform 0.18s ease; }
@@ -1412,7 +1449,9 @@ body.sidebar-collapsed .side-collapse-ico { transform: rotate(180deg); }
body.sidebar-collapsed .side-label { display: none; }
body.sidebar-collapsed .side-link { justify-content: center; padding-left: 0; padding-right: 0; }
body.sidebar-collapsed .side-logo { justify-content: center; padding-left: 0; padding-right: 0; }
body.sidebar-collapsed .side-foot { justify-content: center; }
body.sidebar-collapsed .side-foot { align-items: center; }
body.sidebar-collapsed .side-foot-meta { display: none; }
body.sidebar-collapsed .side-foot-user { justify-content: center; }
.side-backdrop {
position: fixed; inset: 0; z-index: 190;
background: rgba(4, 6, 10, 0.55);
@@ -1449,7 +1488,19 @@ body.sidebar-collapsed .side-foot { justify-content: center; }
.star-btn:hover { transform: scale(1.2); }
.star-btn:disabled { opacity: 0.5; }
.card-board-link { font-weight: 600; }
.ms-block { margin: 10px 0 2px; }
.ms-block {
margin: 14px 0 2px; padding-top: 12px;
border-top: 1px solid var(--border);
}
.ms-head {
display: flex; justify-content: space-between; align-items: baseline;
gap: 10px; margin-bottom: 6px;
}
.ms-kicker {
font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.6px;
color: var(--text-3); font-weight: 700;
}
.ms-block .ms-line { margin-bottom: 2px; }
.ms-line { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.ms-title { font-weight: 600; font-size: 0.92rem; }
.ms-due { font-size: 0.8rem; color: var(--text-2); white-space: nowrap; }
@@ -1485,7 +1536,7 @@ body.sidebar-collapsed .side-foot { justify-content: center; }
.proj-notes-body { white-space: pre-wrap; font-size: 0.93rem; background: var(--surface-2); border-radius: 10px; padding: 12px 14px; }
/* ── Dashboard vague 2 suite (0.18.0, #19 #21 #22 #23) ── */
.activity-line { margin-top: 8px; font-size: 0.82rem; color: var(--text-2); }
.activity-line { margin-top: 10px; font-size: 0.78rem; color: var(--text-3); }
/* Barre d'outils unique du dashboard (0.20.0) : onglets
segmentés, filtre, tri, regroupement et vue dans UN cadre. */
.dash-toolbar {
+1 -1
View File
@@ -19,7 +19,7 @@ def test_activity_line_recent_week(client, fake_gitea):
from_column="backlog", to_column="todo",
created_at=_ts(0))
page = client.get("/")
assert "2 fermée(s) · 1 déplacement(s) cette semaine" in page.text
assert "Cette semaine : 2 fermée(s) · 1 déplacement(s)" in page.text
def test_activity_line_idle_project(client, fake_gitea):
+37
View File
@@ -0,0 +1,37 @@
"""Tuile Projets et pied de sidebar : ergonomie refondue.
Tuile : en-tête groupé, avancement labellisé, jalon étiqueté,
pied en 2 rangées avec répartition labellisée par colonne.
Pied : Sortir icône seule, fraîcheur en pastille discrète.
"""
def test_tuile_structure(client, fake_gitea):
page = client.get("/")
assert 'class="card-header-left"' in page.text
assert 'class="card-header-actions"' in page.text
assert 'class="foot-main"' in page.text
assert 'class="foot-dist"' in page.text
# Répartition, avancement et jalon sont conditionnels (stats /
# jalon) : on les vérifie dans le gabarit, pas dans la page de
# test sans données.
tpl = open("app/templates/dashboard.html", encoding="utf-8").read()
assert 'aria-label="Répartition par colonne"' in tpl
assert 'class="progress-head"' in tpl
assert "Avancement" in tpl
assert 'class="ms-kicker">Prochain jalon' in tpl
assert "COLUMN_LABELS[col]" in tpl
def test_pied_sidebar_structure():
tpl = open("app/templates/_header.html", encoding="utf-8").read()
assert 'class="side-foot-user"' in tpl
assert 'class="side-foot-meta"' in tpl
assert 'class="side-logout logout-icon"' in tpl
assert 'aria-label="Se déconnecter"' in tpl
assert " Sortir</span>" not in tpl
assert 'class="fresh-dot"' in tpl
css = open("static/css/style.css", encoding="utf-8").read()
assert ".side-foot-meta" in css
assert ".fresh-dot" in css
assert ".logout-icon" in css