diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c3822f..04b3b97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,39 @@
# Changelog — FlowDeck
-## v5.1.2 (2026-09-07) — Éditeur style Notion AI (hint, menu slash, AI inline)
+## v5.1.3 (2026-09-07) — Corrections : AI inline (400), bloc vide, menu `/`, poignée, contour
+
+> Résout les régressions du composer AI et de l'éditeur Notion-style v5.1.2.
+
+### AI inline — erreur 400 DeepSeek (espace → AI)
+- `fdAgent.generate` passait par l'engine à outils (`/conversations/{id}/run`) qui
+ envoie un schéma `tools`/`tool_choice` que DeepSeek rejette en **400 Bad Request**.
+- Bascule de `generate` sur l'endpoint **`/api/agent/generate`** (sans outils, prompt
+ propre + contexte) : le composer AI, « Ask AI », la note de réunion et la synthèse
+ renvoient désormais un simple texte Markdown. Le bloc « 400 » disparaît.
+
+### Bloc vide (placeholder)
+- Le hint « Press 'space' for AI or '/' for commands » était rendu trop discret
+ (`--text-dim`). Il passe à `--text-secondary` avec opacité, plus lisible.
+ (Le texte n'apparaît que sur un paragraphe réellement vide — comportement Notion.)
+
+### Slash menu — flèches ↑/↓
+- La sélection n'était **pas visible** au démarrage (rien de surligné) → les flèches
+ semblaient inactives. `_rs()` est désormais appelé à l'ouverture et après filtrage,
+ et l'item sélectionné reçoit un fond accentué + liseré gauche clair. Testé en
+ jsdom : ↓/↑ déplacent la sélection, Entrée applique l'élément.
+
+### Contour du bloc sélectionné
+- Suppression des anneaux de focus (`outline`/`box-shadow`) sur tous les blocs
+ éditables (`[data-bid]`, `.block-content`, titre de page) quand sélectionné.
+
+### Poignée de déplacement (⠿)
+- La poignée 6 points restait invisible (seule `.block-column-wrapper:hover` la
+ montrait). Ajout de `.block-wrapper:hover`/`:focus-within .block-handle { opacity:1 }`
+ pour les blocs normaux.
+
+### Tests / validation
+- 271 tests pytest verts ; rendu édition 200 (hint/AIC/trigger présents) ;
+ route `/api/agent/generate` enregistrée ; JS validé (`node --check` + jsdom).
> Adapte le visuel des blocs à l'expérience Notion AI : hint sur bloc vide, menu
> `/` compact avec raccourcis + aperçu au survol, et composer AI inline
diff --git a/VERSION b/VERSION
index 1b47e8f..935851a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.1.2
\ No newline at end of file
+5.1.3
\ No newline at end of file
diff --git a/app/main.py b/app/main.py
index 030b285..eb7bf9f 100644
--- a/app/main.py
+++ b/app/main.py
@@ -60,7 +60,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="FlowDeck",
- version="5.1.2",
+ version="5.1.3",
docs_url="/docs" if settings.log_level == "DEBUG" else None,
redoc_url=None,
lifespan=lifespan,
diff --git a/app/templates/_page_editor_scripts.html b/app/templates/_page_editor_scripts.html
index 27819e9..65da18a 100644
--- a/app/templates/_page_editor_scripts.html
+++ b/app/templates/_page_editor_scripts.html
@@ -238,7 +238,7 @@
const r=el.getBoundingClientRect();SM._e.style.display='flex';
SM._e.style.top=Math.max(4,(r.bottom+4>window.innerHeight-360)?r.top-364:r.bottom+4)+'px';
SM._e.style.left=Math.min(r.left,window.innerWidth-340)+'px';
- setTimeout(()=>SM._in.focus(),10);SM._o=true;},
+ SM._rs();setTimeout(()=>SM._in.focus(),10);SM._o=true;},
close(){if(SM._e)SM._e.style.display='none';SM._o=false;SM._in&&(SM._in.value='');if(SM._pv)SM._pv.style.display='none';},
flt(){if(!SM._li)return;SM._it=[];const q=(SM._in.value||'').toLowerCase();SM._q=q;let h='';
for(const g of CMDS){let items=g.items;if(q)items=items.filter(c=>c.name.toLowerCase().includes(q)||c.id.includes(q));if(!items.length)continue;
@@ -248,7 +248,7 @@
${c.icon}
${c.name}
${_SHORTCUT[c.id]||''}`;});}
- SM._li.innerHTML=h;},
+ SM._li.innerHTML=h;SM._rs();},
_kd(e){if(e.key==='ArrowDown'){e.preventDefault();SM._s=Math.min(SM._s+1,SM._it.length-1);SM._rs();}
else if(e.key==='ArrowUp'){e.preventDefault();SM._s=Math.max(SM._s-1,0);SM._rs();}
else if(e.key==='Enter'){e.preventDefault();const c=SM._it[SM._s];if(c)SM.sel(c.id);}
diff --git a/app/templates/agent_panel.html b/app/templates/agent_panel.html
index ce2628f..8e6619d 100644
--- a/app/templates/agent_panel.html
+++ b/app/templates/agent_panel.html
@@ -457,44 +457,24 @@
self.setComposerText(t);
setTimeout(function(){ self.send(); }, 60);
};
+ // Headless text generation (no tools) via /agent/generate — the clean
+ // path used by "Ask AI" / "AI meeting note" / summarization. Sending a
+ // plain write request through the tool-enabled conversation engine sends
+ // a tools/tool_choice schema that some providers (e.g. DeepSeek) reject
+ // with a 400. This endpoint returns {ok, text, offline, model}.
window.fdAgent.generate = function(message, context){
message = (message || '').trim();
if(!message) return Promise.resolve('');
- var payload = {message: message};
+ var payload = {prompt: message};
if(context && context.trim()) payload.context = String(context).slice(0, 20000);
- return fetch('/api/agent/conversations', {
+ return fetch('/api/agent/generate', {
method:'POST', headers:{'Content-Type':'application/json'},
- body: JSON.stringify({title:'Agent'})
- }).then(function(r){ return r.json(); }).then(function(conv){
- var cid = conv && conv.id;
- if(!cid) throw new Error('Impossible de créer une conversation agent');
- return fetch('/api/agent/conversations/' + cid + '/run', {
- method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)
- }).then(function(resp){
- if(!resp.ok){ return resp.json().then(function(j){ throw new Error(j.detail || ('HTTP ' + resp.status)); }); }
- var reader = resp.body.getReader();
- var dec = new TextDecoder();
- var buf = '', finalText = '', lastError = '';
- function pump(){
- return reader.read().then(function(res){
- if(res.done){ if(lastError) throw new Error(lastError); return finalText; }
- buf += dec.decode(res.value, {stream:true});
- var parts = buf.split('\n\n');
- buf = parts.pop();
- for(var i=0;i