- Tiroir : édition du titre et de la description (PATCH
forge, journal 'edited', EVENT_TYPES étendu), création
avec description dépliable.
- Pièces jointes Gitea de bout en bout : client
list/upload/delete/download (le téléchargement suit
browser_download_url — l'endpoint asset ne sert que du
JSON, attrapé au smoke réel), routes proxifiées avec
CSRF et plafond 25 Mio, UI liste/envoi/suppression ;
GitHub : supports_attachments=False, message propre.
- Clic carte en délégation d'événement : les écouteurs par
carte mouraient au premier rafraîchissement live.
- 261 tests verts (7 nouveaux, palier 21) ; cycle réel
complet validé sur une issue jetable du vrai Gitea
(création, édition, envoi, téléchargement, suppression).
Depuis 0.24.0, /mcp exige session ou Basic : le service d'agents s'y
faisait refuser en 401 (vu en production, panneau Spark du 2026-10-21).
- Table api_tokens (empreinte SHA-256 seule), CRUD dans Mon profil
- Bearer honoré par le middleware sur /mcp et /api/ uniquement —
jamais l'interface web ; révocation et désactivation immédiates
- 235 tests verts (3 nouveaux : cycle de vie, compte désactivé,
handshake MCP par jeton quand l'auth est active)
- Comptes en base (PBKDF2), rôles, désactivation qui tue la
session, audit ; secours environnement pour l'opérateur ;
liaison draco par utilisateur ; page /profile.
- Espaces de travail : connexions, membres, sélection de
projets ; board/notes/épingles/journal scopés par espace ;
sélecteur d'espace, badge connexion.
- Panneaux /admin (utilisateurs, connexions, espaces,
paramètres — l'environnement amorce, le panneau gouverne).
- /archives : archive locale réversible, archivage forge
confirmé, purge de l'état local, export Markdown, file
« à archiver ? » avec archivage en lot.
- Correctif session : les comptes membres résolus par la base
(la session n'est plus limitée à l'opérateur d'environnement).
- 231 tests verts (12 nouveaux au palier 19).
- GET /api/repos exhaustif (épinglés, récents, archivés),
la palette garde son plafond de 60.
- repo-picker.js : liste filtrée sur les deux segments,
complétion Tab façon shell (pr Tab → Projets/ avec le
séparateur posé, kan Tab → kanbante), ↑/↓/Entrée/clic,
avertissement doux si le dépôt n'existe pas.
- _parallel_map : asyncio.gather borné (16) pour dashboard,
/health, /forecast, /triage et le mur — le patron de /focus,
généralisé. Avant : la page attendait la somme des allers-
retours Gitea de tous les dépôts, en séquentiel.
- Client Gitea : les listes (dépôts, issues, jalons, étoiles)
passent en stale-while-revalidate — une entrée périmée est
servie immédiatement et rafraîchie en tâche de fond. Les
mutations vident le cache et bumpent une génération qui
désarme les rafraîchissements en vol.
- /forecast calcule des lignes de jalons tous projets (écart
ETA/échéance) ; /health trie par sévérité et expose la
pastille ; le détail santé reçoit la liste des projets.
get_project_activity(): fermetures/déplacements 7 j et dernier
événement par dépôt en une seule requête sur board_events;
la page /capture accepte ?repo= pour pré-remplir le dépôt.
Bloc jalon (progression Gitea, échéance FR, retard, ETA journal
avec signal après-échéance — mêmes définitions que le bandeau
du board) et point santé vert/orange/rouge dérivé de
_project_health, info-bulle et lien vers /health du projet.
La diapositive ne grandit plus au-delà de l'écran: plus de barre
de défilement de page, « Ouvertes »/« Fermées »/« EN DIRECT »
toujours affichés au-dessus de la navigation des diapos; cartes
tronquées en fondu + ligne « +N autres cartes en vol »; mode
compact sous 760px de hauteur (TV 720p).
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é.
slugify + branch_name_for (issue-<n>-<slug>, reconnu par #1/#2);
client create_branch; POST /api/issues/{o}/{r}/{n}/branch (nom
libre validé, idempotent si existante); la carte passe en En
cours, journalisée source branch, étiquette synchronisée;
formulaire dans la section Code lié du panneau détail.
app/code_links.py: appariement pur (#n dans titres/corps/messages,
numéro en jeton de branche). Client: list_branches/list_pulls/
list_commits. Badge sur les cartes (board + fragments live),
section Code lié du panneau détail, /api/issues/{o}/{r}/{n}/code.
Table today_picks; POST /api/today/pin (max 3, idempotent) et
/unpin; page /today (picks en direct, candidates de l'encours,
reprises d'hier, récap du jour depuis le journal); bouton 📌 sur
les cartes (délégation capture dans today.js, fragments live
compris); sections dédiées dans le briefing du matin.
/triage (sélecteur par taille de backlog) + /triage/{o}/{r}
(session plein cadre, file embarquée): 1-5 colonnes, X fermer,
L étiquettes, A s'assigner (nouvel endpoint assign-self +
client.set_issue_assignees, idempotent), Entrée suivante, Z annuler.
Progression + résumé de session; décisions via /api/move et les
API existantes. Entrées: board + palette.
Diffusion SSE sur toutes les mutations (move, création, capture,
étiquettes) via _publish_board_changed; GET /api/board/{o}/{r}
(fragments _card.html rendus serveur, même pipeline que la page);
board-live.js: diff en place par colonne avec animation FLIP,
application différée pendant un drag, filtres/compteurs réappliqués,
repli par sondage 45 s sans webhooks. Carte extraite en partiel
_card.html partagé page/API.
POST /api/capture (X-Capture-Token ou Bearer; 503 si non configuré;
exempté Basic Auth/CSRF car machine-à-machine), JSON ou texte libre
(1re ligne = titre), dépôt par défaut KANBANTE_CAPTURE_REPO.
Module app/capture.py: parsing partagé + abonné ntfy en tâche de
fond (reconnexion, curseur kv_store anti-rejeu, tag 'kanban'...
'kanbante' ignoré). _do_capture commun: issue créée, carte placée,
événement 'created' source 'capture'.
- KANBAN_COLUMNS gagne 'qa' (🧪, #06b6d4, étiquette kanban:qa);
Focus inclut QA; dashboard et board affichent les 6 colonnes.
- _adopt_label_columns: une étiquette kanban:* unique qui diverge
de l'état local fait adopter sa colonne (board + focus), avec
événement journalisé source 'gitea'. C'est ce qui permet de
piloter le board depuis Gitea même.
Gitea 1.27.3 renvoie "pull_request": null sur les issues ordinaires:
_is_issue testait la présence de la clé et classait chaque vraie
issue comme un pull request. Board, Focus et briefing affichaient
zéro carte contre un vrai Gitea (le dashboard, basé sur les
compteurs de dépôts, semblait normal, d'où le signalement
« la nouvelle issue n'apparaît pas »). Le filtre teste maintenant
la valeur. Vérifié en réel: board bruno/flowdeck 0 -> 6 cartes.
- client: search_issues (/repos/issues/search, type=issues,
assigned_by), delete_label + forget_label_cache; models: kv_store
- /search groupe par projet avec colonne Kanban; /my = issues
ouvertes assignées à l'utilisateur du jeton (get_user)
- briefing: _briefing_data (focus, âgées, milestones <= 7 j,
activité journal), page /briefing, /api/briefing, envoi ntfy
manuel + planificateur lifespan à KANBANTE_BRIEFING_TIME
(kv briefing_last_sent, pas de doublon); app/briefing.py =
format texte + envoi ntfy (en-têtes ASCII)
- MCP: POST /mcp JSON-RPC (initialize, tools/list, tools/call,
ping, notifications 202), 7 outils en lecture seule, GET /mcp
de découverte; erreurs -32601/-32700, isError pour les outils
- focus refactoré en _collect_focus_groups réutilisé par le
briefing et l'outil MCP get_focus
- POST /webhooks/gitea: HMAC-SHA256 si KANBANTE_WEBHOOK_SECRET,
applique fermetures/réouvertures externes de façon idempotente
(l'écho de nos propres appels est un no-op, journal source=webhook),
invalide le cache, diffuse sur le hub SSE
- GET /api/stream (SSE) + LiveHub; générateur testable séparément
- synchro d'étiquettes kanban:* à chaque déplacement (ensure/add/
remove granulaires, étiquettes utilisateur intactes, flag
KANBANTE_SYNC_LABELS, échec rapporté label_sync sans casser le move)
- board ?milestone=<titre|id>: filtre, échéance, progression;
client: get_milestones, labels, invalidate_cache public
- dashboard: métriques 30 j du journal exposées au template
- board_events append-only + log_event/get_events/column_entry_times/
get_metrics (débit, temps de cycle); journalisation des moves,
créations, fermetures/rouvertures (UI et réconciliation Gitea),
commentaires; un reclassement intra-colonne ne journalise pas
- client Gitea: get_issue, get_issue_comments (paginés), create_comment
- API: GET /api/issues/{o}/{r}/{n} (404 propre), POST .../comments,
GET /api/events, GET /api/metrics, page /activity
- config: KANBANTE_WIP_LIMITS (colonne:limite) et KANBANTE_AGING_DAYS;
le board calcule le temps en colonne (journal, replis updated_at
puis création de l'issue)
- reconcile_board_state retourne aussi les numéros fermés/purgés
- Dashboard with all Gitea projects (active/archived)
- Kanban board per project (Backlog → À faire → En cours → Révision → Terminé)
- Drag & drop issues between columns (SortableJS)
- Project notes panel
- Gitea API integration (read issues, repos)
- SQLite local state for board positions
- Dark theme design system
- Docker-ready (Dockerfile + docker-compose.yml)
- FastAPI + Jinja2 + HTMX + Alpine.js stack