Trois défauts d'affichage, une cause commune pour deux d'entre eux.
Le panneau des critères et la puce « mode Code » étaient insérés AVANT
#composer, qui est en position:absolute : ils restaient dans le flux de
la page et passaient donc SOUS la carte de saisie. Ils vivent maintenant
DANS le composeur, alignés sur sa colonne, et les critères s'ouvrent
repliés (cinq lignes dépliées mangeaient la moitié de l'écran).
Chrono du tour complet au-dessus de la carte : le temps entre l'envoi du
message et le moment où l'on peut reparler — appels d'outils, passes de
vérification et compaction compris. Les durées portées par les cartes ne
mesurent que la génération de texte ; celle-ci répond à « ça a pris
combien de temps, en tout ? ». Il avance à la seconde puis se fige sur le
total.
Anneau d'activité de la liste des discussions : l'animation portait sur
le <svg>, dont l'origine de rotation dépend de transform-box — défaut
qui a changé au fil des versions, d'où un anneau figé. Elle porte
désormais sur le conteneur HTML, qui tourne partout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sélecteur Chat|Code par discussion dans le pied du composeur ; en mode
chat, une détection serveur suggère la bascule (puce ignorable, jamais
automatique). Conception reprise d'OpenFox (MIT), réécrite en Go — voir
NOTICE.md.
- Outils fichiers : read (lignes numérotées, borné), grep, glob, et le
tracker « lu avant d'écrire » qui refuse write/edit sur un fichier non
lu ou modifié depuis la lecture ; edit préserve les fins de ligne CRLF.
- Politique d'exécution : commandes catastrophiques refusées (rm -rf /,
mkfs, reboot…), chemins bornés au dossier de la discussion en mode
code, mutex par fichier.
- Critères d'acceptation : contrat posé via l'outil criteria (éditable
dans l'UI), passe de vérification indépendante sur contexte isolé —
seule habilitée à marquer « passed » — puis corrections plafonnées.
- Rôles embarqués (agents/*.md) : builder, planner, verifier, explorer,
code-reviewer ; badge de rôle dans le fil.
- LSP : gopls / typescript-language-server / pyright (inclus dans
l'image), diagnostics injectés dans le retour de write/edit.
- Git natif : git_status, git_diff, git_clone (borné à la discussion).
- Jobs d'arrière-plan bash_bg/bash_tail (serveur de dev, build long).
- Auto-retry : un appel d'outil écrit en texte (default_api:…,
<tool_call>…) relance le tour une fois avec consigne corrective.
- Outil ask : question à choix rendue en carte à boutons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux défauts révélés par un tour d'agent d'une heure (~50 appels d'outils)
sur un modèle très quantifié.
1. La vitesse affichée sous les réponses tombait de 17 tok/s à 0,9 au fil du
tour, ce qui donnait à croire que le moteur s'effondrait. Il n'en était
rien : un tour d'agent ouvre une bulle NEUVE après chaque appel d'outil
(le flux repasse contentEl à null), mais les compteurs n'étaient jamais
remis à zéro. Chaque bulle affichait donc le CUMUL de tout le tour divisé
par le temps écoulé depuis le tout premier token — exécution des outils,
pages web et prefill compris. La vitesse convergeait mécaniquement vers
« tokens générés ÷ durée totale du tour ».
Les compteurs sont maintenant remis à zéro à la CRÉATION de la bulle, ce
qui couvre tout chemin qui en ouvre une neuve, aujourd'hui comme demain.
Rejoué sur un tour synthétique où le moteur décode à 20 tok/s constants
entre deux outils de deux minutes : 20,5 / 0,6 / 0,5 tok/s avant, 20,5 sur
les trois bulles après. La durée « travail », elle, reste bien celle du
tour entier — c'est sa définition.
2. Rien n'exerçait de pression sur un tour qui tourne en rond. Le plafond
d'itérations avait été retiré en v0.6.3 (il coupait des recherches
légitimes) et la déduplication d'appels ne rattrape pas ce cas : sa clé est
« nom + arguments bruts », or relire le même fichier par tranches
(`sed -n '1,80p'` puis `sed -n '80,160p'`) produit des clés différentes.
D'où un budget SOUPLE : au-delà de 24 appels d'outils sur un tour, on
rappelle au modèle combien il en a déjà faits et on lui demande de
conclure. Le rappel revient à chaque palier en durcissant le ton, et ne
coupe jamais le tour. Il est ajouté EN FIN d'historique, ce qui laisse
intact le préfixe déjà en cache côté llama-server, et n'est pas persisté.
`AGENT_BUDGET` dans config.env règle le palier, `off` le désactive.
Élargir plutôt la clé de déduplication à la CIBLE de l'appel a été écarté :
deux tranches d'un même fichier renvoient un contenu différent, les
confondre casserait toute lecture paginée légitime.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5UndZ9DedRXPuAoRXbmDb
Deux manques dans la présentation d'un tour :
1. Rien ne disait sur QUELLE discussion un agent était en train de
travailler. La ligne concernée porte maintenant un anneau qui tourne,
à gauche du titre — et l'en-tête aussi, puisque la barre latérale est
escamotée sur téléphone. L'état vient de deux sources : le flux SSE
(instantané) et /api/conversations, qui expose désormais `busy` pour
que la page ouverte en cours de génération s'anime sans attendre le
rejeu du journal. L'indicateur est posé et retiré SANS redessiner la
liste : un redessin relancerait l'animation à chaque événement.
2. Le pied des réponses ne montrait que la vitesse du moteur, jamais le
temps que le tour avait pris. Il porte maintenant « travail 1 min 04 s »
— de la question envoyée à la fin du tour, raisonnement, appels
d'outils et attentes compris. La durée avance à la seconde pendant le
tour et se fige au turn_done ; elle apparaît aussi sur l'indicateur
« … », là où il n'y a pas encore de réponse sous laquelle écrire.
La bulle de raisonnement gagne au passage sa propre durée.
La mesure est prise sur les HORODATAGES SERVEUR (événements `user` et
`turn_done`) : elle est donc juste en direct comme au rejeu, où tout
arrive d'un bloc côté client. Le compteur vivant se recale sur l'écart
entre l'horloge du navigateur et celle du serveur, réévalué à chaque
événement reçu en direct — un téléphone n'est pas à la même heure que la
machine.
« Masquer la vitesse de génération » ne masque plus que la vitesse : la
durée n'est pas une mesure de moteur, c'est ce que la réponse a coûté.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5UndZ9DedRXPuAoRXbmDb
La flèche était décalée de 8 px vers la gauche dans son pavé — mesuré, pas
supposé : centre du bouton 1339, centre du dessin 1331.
La cause n'était pas dans la feuille de style mais dans le JS. syncSendBtn
montrait et cachait les deux boutons avec `style.display = 'inline-block'` ; ce
style INLINE l'emportait sur le `display:flex` de la règle commune. Le bouton
gardait donc sa boîte de 34 px, mais son icône de 18 px se collait au bord
gauche, `justify-content` n'ayant plus rien à centrer. Invisible tant que
l'icône était un glyphe de texte (qui remplit sa ligne), flagrant depuis qu'elle
est un SVG de taille fixe.
L'état « génération en cours » passe désormais par un attribut sur <html>
(data-busy) et c'est la feuille qui décide lequel des deux boutons s'affiche —
comme pour le thème, la barre latérale et le panneau Fichiers. Le style inline
`display:none` du bouton d'arrêt disparaît aussi du gabarit.
Vérifié au pixel dans les deux états : écart nul entre le centre du bouton et
celui du dessin, pour les trois commandes de la barre (dépôt, fichiers, envoi)
comme pour le bouton d'arrêt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
Deux retours, deux vrais défauts.
Boutons de la saisie
Ils avaient trois générations de règles empilées — pilule large « Envoyer »,
aplat noir, aplat sauge — si bien que chaque bouton tirait sa taille, sa forme
et son alignement d'une couche différente : rond pour l'envoi et l'arrêt,
carré de 15 px pour l'icône stop contre 16 pour ses voisines, et un
align-self:center sur deux boutons seulement (les deux autres tombaient au
bas de la pilule). Une seule règle les décrit désormais : 34x34, coins doux
de 10 px — jamais un rond, la maquette n'en a pas —, icône de 18 px centrée,
fond seulement au survol. Seule la couleur les distingue : atténué pour
joindre et fichiers, sauge pour envoyer, rouille pour arrêter.
Étiquettes des bulles
Une bulle changeait de nom en cours de route. Envoyée en direct, elle
s'appelait « USER » (confirmPending réécrivait l'étiquette en dur) ; rejouée
au chargement, elle portait l'avatar et le prénom. Même chose côté réponse :
labelTokens remplaçait « Loki » par « assistant · 75 tok · 21.5 tok/s »
pendant la génération, et le nom ne revenait qu'au rechargement de la page.
L'étiquette porte maintenant l'identité DANS TOUS LES CAS ; les mesures de
génération vont dans la ligne dédiée, à l'endroit exact où les stats de fin
de tour les écrivent déjà. L'envoi en cours se lit à la bulle grisée et à son
infobulle, plus à un libellé qui écrase le prénom.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
Les fichiers du chat vivaient dans un pot commun : on changeait de discussion
et on revoyait les mêmes pièces jointes, et supprimer une discussion laissait
derrière elle tout ce qu'on y avait déposé ou fait écrire à l'agent (seules ses
captures, déjà rangées par identifiant, partaient avec).
Chaque discussion a désormais son dossier, <workspace>/discussions/<id>/ :
- les dépôts (uploads/), les captures (captures/) et ce que l'agent écrit y
atterrissent ; le shell et les chemins relatifs du modèle y sont résolus ;
- le panneau Fichiers s'ouvre sur ce dossier et n'en sort pas, et se redessine
quand la discussion change (bascule ou vidage, signalés par le flux SSE) ;
- supprimer une discussion — ou la vider — emporte ses fichiers. Les deux gestes
le disent maintenant avant de demander confirmation ; « clear chat » en
demandait aucune.
La racine du dossier de travail reste la borne de sécurité : les liens des
anciens messages (uploads/x.pdf, captures/<id>/y.jpg) continuent d'ouvrir leur
fichier par un chemin de repli. Au démarrage, une migration range les captures
dans le dossier de leur discussion et rend chaque dépôt à la discussion qui le
mentionne dans son journal ; ce que personne ne réclame reste à la racine,
atteignable par le bouton « hors discussion » du panneau, qui disparaît une fois
le ménage fait.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
L'amont ne connaît qu'un fil unique (bkChat/conversation) que « clear chat »
effaçait définitivement. On garde toute la machinerie (un seul conv en
mémoire, mêmes flux SSE, même compactage) mais rangée par discussion :
bkChat/index liste des discussions (métadonnées seules)
bkChat/active discussion ouverte, partagée par tous les appareils
bkChat/conv:<id> état complet d'une discussion
Basculer réutilise le mécanisme d'epoch du reset : les abonnés SSE reçoivent
{reset:true} et rejouent le nouveau fil — aucun code de rendu à toucher. Le
fil unique existant est repris comme première discussion au premier
démarrage, et sa clé d'origine est laissée intacte.
- routes /api/conversations (liste, new, switch, rename, delete)
- barre latérale : liste (titre déduit du 1er message, date, nb d'échanges),
bouton +, renommer, supprimer ; lignes construites en DOM et non en
innerHTML, les titres venant de messages utilisateur
- suppression de la dernière discussion : convCreate et non convNew, qui
aurait réenregistré celle qu'on vient d'effacer
Le bouton « Vérifier les mises à jour » répondait « GitHub a répondu 404 » :
il interrogeait les releases du dépôt du fork, qui n'en publie aucune. En
conteneur, remplacer le binaire n'a de toute façon pas de sens — l'UI, l'API
et la CLI renvoient désormais « docker compose pull ».
Vérifié en conteneur : création, bascule, renommage (titre accentué avec
< > &), suppression de l'active puis de la dernière ; /api/update et
loki update renvoient la note Docker.
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki)
- LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ;
units loki-engine / loki-ui ; binaire et aide CLI
- updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les
binaires AJEAN amont)
Conservé à l'identique : le domaine ajean.link (service de tunnel amont),
les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et
LICENSE (historique et licence de l'amont).
go build/vet/test : verts.