Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53f54b1b79 | ||
|
|
80fc260a40 |
@@ -1,5 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.37.1 — 2026-10-26
|
||||||
|
|
||||||
|
### Ajouté — version du service d'agents dans le panneau Spark (mode sidebar)
|
||||||
|
|
||||||
|
Carte fusion #64. Le pied du panneau Spark (`static/js/draco-panneau.js`)
|
||||||
|
affiche désormais la ligne discrète et permanente « Service d'agents
|
||||||
|
vX », comme la console pleine page. La version est lue **en direct**
|
||||||
|
dans la santé du service (`GET /healthz` de l'origine que le panneau
|
||||||
|
connaît déjà), à l'ouverture du panneau, avec cache de session — jamais
|
||||||
|
une valeur codée en dur ; service injoignable ou panneau non configuré
|
||||||
|
= rien d'affiché, aucune erreur visible.
|
||||||
|
|
||||||
|
## 0.37.0 — 2026-10-26
|
||||||
|
|
||||||
|
### Ajouté — tri « En attente de revue » sur le dashboard
|
||||||
|
|
||||||
|
Demande de Bruno : pouvoir ordonner les projets — section
|
||||||
|
**Épinglés** en tête — par ce qui attend sa validation.
|
||||||
|
|
||||||
|
- Nouveau critère dans le sélecteur de tri du dashboard,
|
||||||
|
en première place après « Par défaut » : **🔍 En attente
|
||||||
|
de revue ↓** — les projets s'ordonnent par le nombre de
|
||||||
|
cartes dans la colonne de validation (clé `review` de
|
||||||
|
l'espace), décroissant.
|
||||||
|
- Le compteur était déjà calculé pour les chips de
|
||||||
|
progression des cartes : il est maintenant exposé en
|
||||||
|
`data-review` sur chaque carte projet et consommé par le
|
||||||
|
moteur de tri partagé — le critère s'applique à **toutes
|
||||||
|
les grilles** (Épinglés, Actifs, Inactifs, Archivés),
|
||||||
|
avec ou sans regroupement par propriétaire, et la
|
||||||
|
préférence est mémorisée comme les autres tris.
|
||||||
|
- Les critères existants (activité Gitea, activité kanban,
|
||||||
|
issues ouvertes, nom) sont inchangés.
|
||||||
|
|
||||||
|
### Corrigé — sens des tris numériques du dashboard
|
||||||
|
|
||||||
|
Le contrôle en navigateur réel a révélé que le tri
|
||||||
|
« Issues ouvertes ↓ » triait en réalité **croissant**
|
||||||
|
depuis son introduction : le comparateur partagé inversait
|
||||||
|
les clés numériques. Les modes numériques (`open` et le
|
||||||
|
nouveau `review`) trient maintenant en soustraction de
|
||||||
|
clés — le plus grand compteur en premier, comme l'annonce
|
||||||
|
le « ↓ ». Vérifié sur les données réelles : Épinglés
|
||||||
|
ordonnés 196, 95, 32, 5, 4 en attente de revue.
|
||||||
|
|
||||||
|
|
||||||
## 0.36.0 — 2026-10-26
|
## 0.36.0 — 2026-10-26
|
||||||
|
|
||||||
### Ajouté — outil MCP `move_issue` (suivi de la vague H3 du service d'agents)
|
### Ajouté — outil MCP `move_issue` (suivi de la vague H3 du service d'agents)
|
||||||
|
|||||||
+1
-1
@@ -177,7 +177,7 @@ from urllib.parse import urlparse
|
|||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
APP_VERSION = "0.36.0"
|
APP_VERSION = "0.37.1"
|
||||||
|
|
||||||
|
|
||||||
def _env_bool(name: str, default: bool) -> bool:
|
def _env_bool(name: str, default: bool) -> bool:
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
<label class="dash-tool">Tri
|
<label class="dash-tool">Tri
|
||||||
<select id="dash-sort">
|
<select id="dash-sort">
|
||||||
<option value="default">Par défaut</option>
|
<option value="default">Par défaut</option>
|
||||||
|
<option value="review">🔍 En attente de revue ↓</option>
|
||||||
<option value="updated">Activité Gitea récente</option>
|
<option value="updated">Activité Gitea récente</option>
|
||||||
<option value="activity">Activité kanban récente</option>
|
<option value="activity">Activité kanban récente</option>
|
||||||
<option value="open">Issues ouvertes ↓</option>
|
<option value="open">Issues ouvertes ↓</option>
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
data-full="{{ full }}" data-home="{{ repo.home_tab or '' }}"
|
data-full="{{ full }}" data-home="{{ repo.home_tab or '' }}"
|
||||||
data-name="{{ repo.name|lower }}" data-owner="{{ repo.owner.login }}"
|
data-name="{{ repo.name|lower }}" data-owner="{{ repo.owner.login }}"
|
||||||
data-updated="{{ repo.updated_at }}" data-open="{{ repo.open_issues_count or 0 }}"
|
data-updated="{{ repo.updated_at }}" data-open="{{ repo.open_issues_count or 0 }}"
|
||||||
|
data-review="{{ stats.get(full, {}).get('review', 0) if stats else 0 }}"
|
||||||
data-activity="{{ repo.activity.last_at if repo.activity else '' }}"
|
data-activity="{{ repo.activity.last_at if repo.activity else '' }}"
|
||||||
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
|
data-search="{{ (repo.name ~ ' ' ~ repo.full_name ~ ' ' ~ (repo.description or ''))|lower }}"
|
||||||
x-show="search === '' || $el.dataset.search.includes(search.toLowerCase())">
|
x-show="search === '' || $el.dataset.search.includes(search.toLowerCase())">
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "kanbante"
|
name = "kanbante"
|
||||||
version = "0.36.0"
|
version = "0.37.1"
|
||||||
description = "Kanban project manager integrated with Gitea"
|
description = "Kanban project manager integrated with Gitea"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
function cardKey(card, mode) {
|
function cardKey(card, mode) {
|
||||||
if (mode === 'name') return (card.getAttribute('data-name') || '').toLowerCase();
|
if (mode === 'name') return (card.getAttribute('data-name') || '').toLowerCase();
|
||||||
if (mode === 'open') return -(parseInt(card.getAttribute('data-open') || '0', 10));
|
if (mode === 'open') return -(parseInt(card.getAttribute('data-open') || '0', 10));
|
||||||
|
if (mode === 'review') return -(parseInt(card.getAttribute('data-review') || '0', 10));
|
||||||
if (mode === 'updated') return card.getAttribute('data-updated') || '';
|
if (mode === 'updated') return card.getAttribute('data-updated') || '';
|
||||||
if (mode === 'activity') return card.getAttribute('data-activity') || '';
|
if (mode === 'activity') return card.getAttribute('data-activity') || '';
|
||||||
return null; // 'default' : ordre serveur, on ne touche à rien
|
return null; // 'default' : ordre serveur, on ne touche à rien
|
||||||
@@ -52,6 +53,13 @@
|
|||||||
if (isNaN(tb)) return -1;
|
if (isNaN(tb)) return -1;
|
||||||
return tb - ta;
|
return tb - ta;
|
||||||
}
|
}
|
||||||
|
// Modes numériques (open, review) : les clés sont
|
||||||
|
// des compteurs niés (-n) — ka - kb donne donc le
|
||||||
|
// plus grand compteur en premier, comme l'annonce
|
||||||
|
// le « ↓ » des options. (Avant 0.37.0, la
|
||||||
|
// comparaison générique ci-dessous inversait ces
|
||||||
|
// deux tris : ils ressortaient croissants.)
|
||||||
|
if (mode === 'open' || mode === 'review') return ka - kb;
|
||||||
if (ka < kb) return mode === 'name' ? -1 : 1;
|
if (ka < kb) return mode === 'name' ? -1 : 1;
|
||||||
if (ka > kb) return mode === 'name' ? 1 : -1;
|
if (ka > kb) return mode === 'name' ? 1 : -1;
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
var etatConnu = false;
|
var etatConnu = false;
|
||||||
var root = null, msgsEl = null, etatEl = null, inputEl = null, sendBtn = null;
|
var root = null, msgsEl = null, etatEl = null, inputEl = null, sendBtn = null;
|
||||||
var titreEl = null, tiroirEl = null;
|
var titreEl = null, tiroirEl = null;
|
||||||
|
var versionEl = null, versionConnue = "", versionMaj = 0;
|
||||||
|
|
||||||
function domaine() {
|
function domaine() {
|
||||||
try { return localStorage.getItem("draco_domain") || "pro"; } catch (e) { return "pro"; }
|
try { return localStorage.getItem("draco_domain") || "pro"; } catch (e) { return "pro"; }
|
||||||
@@ -268,6 +269,7 @@
|
|||||||
' <input type="text" placeholder="Écrire à Spark…" autocomplete="off" aria-label="Message à Spark">' +
|
' <input type="text" placeholder="Écrire à Spark…" autocomplete="off" aria-label="Message à Spark">' +
|
||||||
' <button type="submit">Envoyer</button></form>' +
|
' <button type="submit">Envoyer</button></form>' +
|
||||||
' <div class="draco-panneau-note">Conversation réelle, sous-agents (J3) et missions longues (J5) vers les exécuteurs configurés ; toute écriture dans les apps passe par une approbation.</div>' +
|
' <div class="draco-panneau-note">Conversation réelle, sous-agents (J3) et missions longues (J5) vers les exécuteurs configurés ; toute écriture dans les apps passe par une approbation.</div>' +
|
||||||
|
' <div class="draco-panneau-version" style="margin-top:6px;font-size:11.5px;opacity:.6" hidden></div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
document.body.appendChild(root);
|
document.body.appendChild(root);
|
||||||
msgsEl = root.querySelector(".draco-panneau-msgs");
|
msgsEl = root.querySelector(".draco-panneau-msgs");
|
||||||
@@ -276,6 +278,7 @@
|
|||||||
sendBtn = root.querySelector("button[type=submit]");
|
sendBtn = root.querySelector("button[type=submit]");
|
||||||
titreEl = root.querySelector(".d-titre");
|
titreEl = root.querySelector(".d-titre");
|
||||||
tiroirEl = root.querySelector(".draco-discussions");
|
tiroirEl = root.querySelector(".draco-discussions");
|
||||||
|
versionEl = root.querySelector(".draco-panneau-version");
|
||||||
root.querySelector(".fermer").addEventListener("click", fermer);
|
root.querySelector(".fermer").addEventListener("click", fermer);
|
||||||
root.querySelector('[data-disc="liste"]').addEventListener("click", basculerTiroir);
|
root.querySelector('[data-disc="liste"]').addEventListener("click", basculerTiroir);
|
||||||
root.querySelector('[data-disc="nouvelle"]').addEventListener("click", nouvelleDiscussion);
|
root.querySelector('[data-disc="nouvelle"]').addEventListener("click", nouvelleDiscussion);
|
||||||
@@ -330,6 +333,35 @@
|
|||||||
titreEl.title = titreEl.textContent;
|
titreEl.title = titreEl.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Version du service : lue en direct dans la santé de
|
||||||
|
* l'agent (GET /healthz → champ « version », la même source que
|
||||||
|
* la console) à l'origine que le panneau connaît déjà. Cache de
|
||||||
|
* session (5 min), rafraîchie à l'ouverture du panneau, au même
|
||||||
|
* rythme que la vérification d'identité. Jamais de valeur codée
|
||||||
|
* en dur : source injoignable ou réponse illisible = la ligne
|
||||||
|
* reste masquée — pas de fausse version, pas d'erreur visible. */
|
||||||
|
function afficherVersion() {
|
||||||
|
if (!versionEl) return;
|
||||||
|
if (versionConnue) {
|
||||||
|
versionEl.textContent = "Service d'agents v" + versionConnue;
|
||||||
|
versionEl.hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function chargerVersion() {
|
||||||
|
if (!API || !versionEl) return;
|
||||||
|
if (versionConnue && Date.now() - versionMaj < 300000) { afficherVersion(); return; }
|
||||||
|
fetch(API + "/healthz")
|
||||||
|
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||||
|
.then(function (d) {
|
||||||
|
if (d && typeof d.version === "string" && /^[0-9]+\.[0-9]+\.[0-9]+/.test(d.version)) {
|
||||||
|
versionConnue = d.version;
|
||||||
|
versionMaj = Date.now();
|
||||||
|
afficherVersion();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () { /* service injoignable : rien d'affiché */ });
|
||||||
|
}
|
||||||
|
|
||||||
function verifierIdentite() {
|
function verifierIdentite() {
|
||||||
if (etatConnu) return;
|
if (etatConnu) return;
|
||||||
if (!API) {
|
if (!API) {
|
||||||
@@ -812,6 +844,7 @@
|
|||||||
root.classList.add("open");
|
root.classList.add("open");
|
||||||
appliquerTheme();
|
appliquerTheme();
|
||||||
verifierIdentite();
|
verifierIdentite();
|
||||||
|
chargerVersion();
|
||||||
setTimeout(function () { inputEl.focus(); }, 300);
|
setTimeout(function () { inputEl.focus(); }, 300);
|
||||||
}
|
}
|
||||||
function fermer() { if (root) root.classList.remove("open"); }
|
function fermer() { if (root) root.classList.remove("open"); }
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
"""Palier 25 (0.37.0): tri « En attente de revue » du dashboard —
|
||||||
|
les projets (section Épinglés incluse) s'ordonnent par le nombre
|
||||||
|
de cartes dans la colonne de validation."""
|
||||||
|
|
||||||
|
from app import models
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_sort_offers_review_mode(client):
|
||||||
|
r = client.get("/")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert '<option value="review">🔍 En attente de revue ↓</option>' in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_cards_carry_review_count(client):
|
||||||
|
# Aucune carte en révision au départ.
|
||||||
|
r = client.get("/")
|
||||||
|
assert 'data-review="0"' in r.text
|
||||||
|
# Une carte passe en Révision : le compteur de SA carte monte.
|
||||||
|
models.move_issue("Projets/demo", 5, "review")
|
||||||
|
models.move_issue("Projets/demo", 1, "review")
|
||||||
|
r = client.get("/")
|
||||||
|
assert 'data-review="2"' in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_js_knows_review_mode(client):
|
||||||
|
# Le moteur de tri partagé est servi tel quel : le mode
|
||||||
|
# 'review' doit y figurer (tri décroissant sur data-review).
|
||||||
|
r = client.get("/static/js/dashboard.js")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "mode === 'review'" in r.text
|
||||||
|
assert "data-review" in r.text
|
||||||
|
# Le sens : les modes numeriques trient en ka - kb (cles
|
||||||
|
# niees = plus grand compteur d\'abord). Sans cette
|
||||||
|
# branche, le comparateur generique inversait le tri :
|
||||||
|
# bug attrape en navigateur reel sur le tri historique.
|
||||||
|
assert "return ka - kb" in r.text
|
||||||
Reference in New Issue
Block a user