Trois gains de place, mesurés au navigateur en 1500×900.
- Raisonnement, outils et mémoire se posent CÔTE À CÔTE dans une rangée qui
déborde à la ligne : un tour d'agent en enchaîne parfois dix, et empilées
elles repoussaient la réponse hors de l'écran alors qu'elles ne portent
qu'une étiquette tant qu'elles sont repliées. Mesuré : 8 bulles occupent
2 lignes au lieu de 8. Dépliée, une bulle reprend toute la largeur — son
contenu ne doit pas être comprimé dans une colonne étroite.
- Les sections de la barre latérale sont réunies en DEUX cartes (réglages
d'IA / réglages d'application) au lieu d'une carte chacune : onze arrondis
et autant de gouttières faisaient déborder la colonne. Mesuré : 876 px de
contenu pour 876 px visibles — plus d'ascenseur.
- Section « Accès distant » supprimée, ainsi que son module JS (13-remote.js)
et son chargement : elle pilotait le tunnel ajean.link, sans objet ici.
Le code serveur du relais reste en place mais inerte (aucun jeton) : le
retirer créerait des conflits à chaque reprise de l'amont.
Sur un écran large, le fil se réduisait à ~590 px de texte entouré de vide :
la mesure était plafonnée à 760 px et centrée sur la fenêtre, alors que la
charte fait courir le texte sur toute la largeur du panneau.
Elle était en plus codée à TROIS endroits avec des valeurs différentes
(450 / 380 / 360 px), donc condamnée à diverger entre le fil et la carte de
saisie. Une seule variable --measure les pilote désormais tous les deux.
La borne haute reste : au-delà, l'œil perd le début de la ligne suivante.
1080 px ≈ 110 caractères — large, mais encore lisible.
Mesuré au navigateur en 2000 px : réponse 1050 px (contre 589), question
788 px (75 %, conforme à la charte), carte de saisie alignée sur le fil.
Reprise de la maquette fournie (LOKI — direction Terre) : bruns chauds, vert
sauge, argile, et des surfaces en CARTES arrondies séparées par une gouttière
au lieu d'une colonne à filets.
- Palette transposée sur les jetons EXISTANTS (--bg, --panel, --accent…) et
non par réécriture des 1200 lignes : les 300 usages de variables suivent
d'eux-mêmes. Rôles clarifiés — sauge = sélection et action, argile =
chiffres et avertissements, terre cuite = arrêt et danger.
- Polices Bricolage Grotesque + IBM Plex Mono EMBARQUÉES dans le binaire et
servies par Loki (/fonts/), pas par Google Fonts : une instance locale ou
coupée d'internet doit s'afficher correctement, sans qu'un tiers apprenne
qui consulte l'interface. Sous-ensemble latin, 106 Ko au total.
- Structure en cartes : barre latérale en pile de cartes, colonne de chat en
panneau unique, carte de saisie posée un ton au-dessus. Liseré sauge sur la
discussion active, bulle de question au coin bas-droit rentré.
- Bouton d'envoi en sauge : il reprenait --text, donc un brun indistinct du
reste ; l'arrêt reste en terre cuite.
Bornage au bureau (min-width:721px) : sur téléphone la barre latérale est un
tiroir plein écran et le fil occupe toute la largeur — des cartes n'y
grignoteraient que des pixels utiles.
Vérifié au navigateur, thèmes clair ET sombre, plus un rendu 390 px :
polices réellement chargées (200 sur les trois woff2), aucun débordement
horizontal, tiroir mobile inchangé.
Les libellés étaient bien construits — mon test précédent vérifiait le HTML
produit, pas le style appliqué — mais la feuille de style les masquait tous :
#chat .msg .label{display:none}
C'était un choix justifié de l'amont, où le libellé ne disait que « user » ou
« assistant », deux mots que l'alignement des bulles rend redondants. Depuis
qu'ils portent un avatar et un prénom, ils identifient réellement
l'interlocuteur : on les réaffiche pour user et assistant, en ligne et
discrets, aligné à droite côté question. Les bulles techniques non repliables
restent muettes.
Vérifié dans un navigateur : display=flex, boîte non vide, textes rendus
« 🙂 Michael » et « 🦊 Loki » — cette fois sur le style calculé et sur une
capture d'écran, pas seulement sur le DOM.
Outil web_screenshot : Chromium piloté par Playwright photographie une page
RENDUE (JavaScript exécuté) dans le dossier de travail, et rend au modèle la
ligne markdown exacte à recopier — lui laisser composer l'URL reviendrait à
lui faire inventer un chemin, donc une image cassée.
INDÉPENDANT DE LA VISION, souvent confondu : visionEnabled() (clé MMPROJ) ne
décide que d'une chose, l'envoi d'images AU MODÈLE. Capturer et afficher ne
passent pas par le modèle — sans projecteur, l'agent photographie sans
regarder, ce qui suffit à illustrer une conversation.
- route /api/chat/image : sert UNIQUEMENT des images du dossier de travail,
en ligne. handleChatFile force le téléchargement de tout pour qu'un .html
du modèle ne s'exécute pas dans l'origine de l'UI ; ici la même règle est
tenue autrement — type déduit du CONTENU (pas de l'extension), nosniff, et
CSP default-src 'none'. Un faux .png contenant du HTML est refusé en 415.
- UI : une balise <img> ne peut pas porter d'en-tête Authorization, or /api/*
exige la clé dès qu'elle est définie. Les images sont donc récupérées par
fetch authentifié puis posées en blob:.
- l'outil n'est déclaré au modèle QUE si Playwright est réellement présent :
annoncer un outil absent envoie le modèle en boucle de réessai.
- Node 22 (NodeSource) au lieu du Node 18 d'Ubuntu, exigé par Playwright et
par la plupart des serveurs MCP. PLAYWRIGHT=0 bâtit une image sans navigateur.
- CONFIG ACTIVE affichait « llama.cpp personnalisé » pour le moteur de
l'image : il annonce maintenant « llama.cpp de l'image ».
Description de l'outil tenue au plus court : TestSystemPromptStaysLean a
attrapé le dépassement du budget de préambule (7888 car pour 7500).
Vérifié dans l'image : capture réelle d'une page, servie en image/png avec
CSP ; faux PNG rejeté (415) ; évasion du workspace bloquée (403).
Playwright ajoute 816 Mo à l'image.
- Identité : prénom et avatars (emoji) affichés en tête des bulles, à la
place des libellés « user » / « assistant ». Stockés dans les préférences
SERVEUR, donc partagés entre appareils comme le thème. La liste d'emojis
est servie par /api/prefs : pas de seconde copie côté client.
Validation stricte — prénom nettoyé de ses caractères de contrôle et borné
à 24 runes, avatar refusé s'il n'est pas dans la liste fermée.
- Barre latérale : séparateur « Paramètres » qui regroupe Identité,
Apparence, Accès OpenAI, Accès distant et Actions, sous les réglages d'IA
(Machine, Presets, Mode agent, System prompt, Moteur). Un séparateur plutôt
qu'un second niveau de repli, illisible dans une colonne aussi étroite.
- Logo : 10 → 17 px, il était minuscule sur un écran dense.
Le prénom vient de l'utilisateur : les libellés sont écrits en textContent,
jamais en innerHTML.
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.
Le sélecteur de l'éditeur de modèle proposait encore « Précompilé » et
« Compilé » — deux installations que Loki ne fait pas en conteneur : cliquer
répondait « installez d'abord llama.cpp… ». Pire, le moteur de l'image ne
correspondant à aucun des deux, il était classé « Personnalisé ».
En conteneur, ces deux options laissent place à « Image » (sélectionnée par
défaut, y compris pour un preset sans BIN) ; « Personnalisé » reste pour un
binaire déposé dans /data/backends.
Le moteur de l'image est désormais déclaré par l'image elle-même via
LOKI_ENGINE_BIN (Dockerfile + entrypoint) au lieu d'être deviné d'après le
chemin : /api/llamacpp expose provided et provided_bin.
Attribution : sous le nom Loki, « fork de AJEAN » avec lien vers le dépôt
d'origine.
Vérifié dans l'image : provided=true, provided_bin=/app/llama-server,
BIN semé identique ; build, vet et tests verts.
- 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.