diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbbeb3a..5bae654 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ incompatibles. Chaque ligne renvoie à un commit dédié (un artefact = un commi
## [Unreleased]
+## [0.23.0] — 2026-06-23
+### Added
+- UI : vue **Chat** (`web/src/pages/Chat.tsx`) — conversation avec l'agent via
+ `POST /api/chat`, bulles user/assistant, état "réfléchit", auto-scroll, gestion
+ d'erreur, déconnexion auto sur 401. Build OK.
+
## [0.22.0] — 2026-06-23
### Added
- Package **`web/`** : scaffold UI (React 19 + Vite 8 + Tailwind 4 + react-router 7,
diff --git a/web/src/pages/Chat.tsx b/web/src/pages/Chat.tsx
index fbd37e0..a9fb07d 100644
--- a/web/src/pages/Chat.tsx
+++ b/web/src/pages/Chat.tsx
@@ -1,4 +1,87 @@
-// Vue Chat — remplie en v0.23.0.
-export function Chat() {
- return
Chat (v0.23.0)…
;
+import { useEffect, useRef, useState, type FormEvent } from "react";
+import { useAuth } from "../auth";
+import { api, ApiError } from "../api";
+
+interface Msg {
+ role: "user" | "assistant";
+ text: string;
+}
+
+export function Chat() {
+ const { token, logout } = useAuth();
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState("");
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState(null);
+ const bottom = useRef(null);
+
+ useEffect(() => {
+ bottom.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages, busy]);
+
+ const send = async (e: FormEvent): Promise => {
+ e.preventDefault();
+ const text = input.trim();
+ if (!text || busy || !token) return;
+ setInput("");
+ setError(null);
+ setMessages((m) => [...m, { role: "user", text }]);
+ setBusy(true);
+ try {
+ const { reply } = await api.chat(token, text);
+ setMessages((m) => [...m, { role: "assistant", text: reply }]);
+ } catch (err) {
+ if (err instanceof ApiError && err.status === 401) {
+ logout();
+ return;
+ }
+ setError(err instanceof Error ? err.message : "Erreur");
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ return (
+
+
+ {messages.length === 0 && (
+
Pose une question à CHLOVA…
+ )}
+ {messages.map((m, i) => (
+
+ ))}
+ {busy &&
CHLOVA réfléchit…
}
+ {error &&
{error}
}
+
+
+
+
+
+ );
}