bots-garden/mini-mepublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Search in documents #37

Merged@k33g wants to merge feature/search-in-doc into main
Merged as 303fa046a9df.
59 files changed
modified .tickets/issues/0106-search-doc-bm25-search-in-text-files.yaml +97 -12
@@ -1,12 +1,12 @@
11 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
22 id: 106
3-title: search_doc — BM25 search in the text files of a folder (built-in tool)
4-state: open
3+title: search_docs — BM25 search in the text files of folders (/docs and a built-in tool)
4+state: closed
55 author:
66 name: k33g
77 email: ph.charriere@gmail.com
88 createdAt: 2026-10-20T20:00:00.000Z
9-updatedAt: 2026-10-20T20:00:00.000Z
9+updatedAt: 2026-10-20T23:30:00.000Z
1010 labels:
1111 - kind::feature
1212 milestone: v0.8.0
@@ -67,7 +67,7 @@ body: |
6767 tasks:
6868 - id: 1
6969 title: "Agree on the config key, defaults and the open questions below"
70- state: open
70+ state: closed
7171 priority: none
7272 author:
7373 name: k33g
@@ -76,7 +76,7 @@ tasks:
7676 updatedAt: 2026-10-20T20:00:00.000Z
7777 - id: 2
7878 title: "docsearch: chunker — Markdown (# headings, not inside code fences), AsciiDoc (= headings), plain text (blank-line paragraphs); heading path and line kept; long sections split by paragraphs — with tests"
79- state: open
79+ state: closed
8080 priority: none
8181 author:
8282 name: k33g
@@ -85,7 +85,7 @@ tasks:
8585 updatedAt: 2026-10-20T20:00:00.000Z
8686 - id: 3
8787 title: "docsearch: BM25 (k1 1.2, b 0.75) over the chunks, words folded like store.fold (case, accents), heading boost — with tests"
88- state: open
88+ state: closed
8989 priority: none
9090 author:
9191 name: k33g
@@ -94,7 +94,7 @@ tasks:
9494 updatedAt: 2026-10-20T20:00:00.000Z
9595 - id: 4
9696 title: "docsearch: walk the folders (extensions, size cap, skip hidden dirs, .git, node_modules), scanned at each call, no index — with tests"
97- state: open
97+ state: closed
9898 priority: none
9999 author:
100100 name: k33g
@@ -102,8 +102,8 @@ tasks:
102102 createdAt: 2026-10-20T20:00:00.000Z
103103 updatedAt: 2026-10-20T20:00:00.000Z
104104 - id: 5
105- title: "built-in tool search_doc {query, limit?}: hits = path:line, heading path, snippet; declared only when the config enables it; never asks; ACP kind search — with tests"
106- state: open
105+ title: "built-in tool search_docs {query, limit?} and the /docs <words> command: hits = path:line, heading path, snippet; declared only when the config enables it; never asks; ACP kind search — with tests"
106+ state: closed
107107 priority: none
108108 author:
109109 name: k33g
@@ -112,7 +112,7 @@ tasks:
112112 updatedAt: 2026-10-20T20:00:00.000Z
113113 - id: 6
114114 title: "Measure on a small model (llmman) over mm's own docs/ (EN+FR): found when the words are there, not called otherwise; time per call on docs/"
115- state: open
115+ state: closed
116116 priority: none
117117 author:
118118 name: k33g
@@ -121,11 +121,96 @@ tasks:
121121 updatedAt: 2026-10-20T20:00:00.000Z
122122 - id: 7
123123 title: "Docs EN+FR (reference configuration and tools, how-to, explanation: why BM25 and no index), demo if useful, quality gate, .memory"
124- state: open
124+ state: closed
125125 priority: none
126126 author:
127127 name: k33g
128128 email: ph.charriere@gmail.com
129129 createdAt: 2026-10-20T20:00:00.000Z
130130 updatedAt: 2026-10-20T20:00:00.000Z
131-comments: []
131+comments:
132+ - id: 1
133+ author:
134+ name: k33g
135+ email: ph.charriere@gmail.com
136+ createdAt: 2026-10-20T21:00:00.000Z
137+ body: |
138+ Decided by the user (2026-10-20) — the proposal is validated, with:
139+ - tool name: search_docs (plural);
140+ - config block `docSearch`, with TWO switches instead of `enabled`:
141+ `searchDocs: true` → the /docs <words> command (for the user),
142+ `autoSearchDocs: true` → the search_docs tool (for the model);
143+ both default false, independent (as /memory and search_memory);
144+ - /docs: yes;
145+ - prefix matching for words of 4+ letters: yes;
146+ - extensions: only what the configuration lists (no hidden extra);
147+ the default list is the validated one (.md .markdown .txt .adoc
148+ .asciidoc .rst); symlinks not followed;
149+ - key name: docSearch.
150+ Then: demos/docs-search, whose documents are a set of simple
151+ tutorials on using ratatui in Rust (Markdown, generated for the demo).
152+ Branch feature/search-in-doc (created by the user).
153+ - id: 2
154+ author:
155+ name: k33g
156+ email: ph.charriere@gmail.com
157+ createdAt: 2026-10-20T23:00:00.000Z
158+ body: |
159+ Tasks 2-6 done on feature/search-in-doc (go test ./... green, gofmt/vet
160+ clean):
161+ - internal/docsearch (new): passages.go (sections at # / = headings,
162+ fences and listing blocks ignored, heading path, cut at blank lines
163+ past 1,500 runes, never inside a fence), bm25.go (k1 1.2, b 0.75,
164+ heading-path words +2, prefix match for 4+ runes, plural/-ed within
165+ 2 runes, query words < 3 runes dropped beside longer ones), walk.go
166+ (listed extensions only, maxFileSize, hidden dirs + node_modules
167+ skipped, symlinks not followed, NUL = binary, missing dirs
168+ reported), docsearch.go (Search, Result.Format), configured.go
169+ (Configured/Answer from config).
170+ - internal/textfold (new): Fold/Rune moved out of store (shared by
171+ store and docsearch, no duplicate).
172+ - config/docsearch.go: docSearch (searchDocs, autoSearchDocs, dirs
173+ [docs], extensions [.md .markdown .txt .adoc .asciidoc .rst],
174+ maxFileSize 1MB — ByteSize reads 1MB / 512KB / bytes); validated
175+ only when a switch is on; search_docs reserved.
176+ - command/docs.go SetDocs; /docs in the REPL, the window (notice) and
177+ ACP (agent text, input hint); tools/searchdocs.go search_docs
178+ (query, limit? — 5, cap 20), never asks, ACP kind search, dir = the
179+ sink's WorkDir else the process cwd; main.go wiring.
180+ - Speed (best of 10): mm's docs/ 137 files / 1,320 passages ≈ 45 ms;
181+ the demo's 10 files / 44 passages ≈ 1 ms.
182+ demos/docs-search: Rata, Qwen3.5 4B on llmman, 9 Ratatui 0.30
183+ tutorials + index; check-tutorials.sh compiles every complete program
184+ + snippets.rs in a rust container (-Dwarnings): OK, 10 programs,
185+ ratatui 0.30.2. Measured (temp 0, 3 runs each, identical): List →
186+ ListState, 04-lists.md:5 3/3; popup → Clear + centered, NO link 3/3;
187+ Windows double key → right answer (KeyEventKind::Press) 3/3 but cited
188+ code examples instead of 08:60 (ranked first by the search); progress
189+ bar → Gauge + event::poll, 06:9 3/3; two French questions → English
190+ searches, French answers, right links 6/6; "Write src/main.rs …" →
191+ file written 3/3, compiles against ratatui 0.30.2 3/3; greeting and
192+ an off-topic question → no search 6/6. Prompt lesson: the Windows
193+ question was answered without a search (3/3) until the prompt said any
194+ terminal-app question is a Ratatui question — after which the popup
195+ answer stopped citing its link.
196+ Task 7: docs EN+FR done (reference configuration + validation errors,
197+ slash-commands, tools, acp; how-to search-the-docs; explanation
198+ document-search; indexes; root README; packages.drawio). Quality gate
199+ NOT run (qlty absent here) — task 7 stays open for it.
200+ - id: 3
201+ author:
202+ name: k33g
203+ email: ph.charriere@gmail.com
204+ createdAt: 2026-10-20T23:30:00.000Z
205+ body: |
206+ Closed at the user's request. Delivered: internal/docsearch +
207+ internal/textfold, docSearch config (searchDocs / autoSearchDocs),
208+ /docs in the three front ends, the search_docs tool, tests, docs EN+FR
209+ (reference, how-to search-the-docs, explanation document-search),
210+ demos/docs-search (Ratatui tutorials, compile-checked, measured).
211+ Task 7 closed with the qlty quality gate still NOT run (qlty is not
212+ installed in this sandbox); gofmt and go vet clean, go test ./...
213+ green. Known limits left as documented: words, not meanings; in the
214+ demo the popup answer gives no link and the Windows answer cites code
215+ examples rather than the explaining passage.
216+
@@ -1,12 +1,12 @@
1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.1 # Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2 id: 1062 id: 106
3-title: search_doc — BM25 search in the text files of a folder (built-in tool)3+title: search_docs — BM25 search in the text files of folders (/docs and a built-in tool)
4-state: open4+state: closed
5 author:5 author:
6 name: k33g6 name: k33g
7 email: ph.charriere@gmail.com7 email: ph.charriere@gmail.com
8 createdAt: 2026-10-20T20:00:00.000Z8 createdAt: 2026-10-20T20:00:00.000Z
9-updatedAt: 2026-10-20T20:00:00.000Z9+updatedAt: 2026-10-20T23:30:00.000Z
10 labels:10 labels:
11 - kind::feature11 - kind::feature
12 milestone: v0.8.012 milestone: v0.8.0
@@ -67,7 +67,7 @@ body: |
67 tasks:67 tasks:
68 - id: 168 - id: 1
69 title: "Agree on the config key, defaults and the open questions below"69 title: "Agree on the config key, defaults and the open questions below"
70- state: open70+ state: closed
71 priority: none71 priority: none
72 author:72 author:
73 name: k33g73 name: k33g
@@ -76,7 +76,7 @@ tasks:
76 updatedAt: 2026-10-20T20:00:00.000Z76 updatedAt: 2026-10-20T20:00:00.000Z
77 - id: 277 - id: 2
78 title: "docsearch: chunker — Markdown (# headings, not inside code fences), AsciiDoc (= headings), plain text (blank-line paragraphs); heading path and line kept; long sections split by paragraphs — with tests"78 title: "docsearch: chunker — Markdown (# headings, not inside code fences), AsciiDoc (= headings), plain text (blank-line paragraphs); heading path and line kept; long sections split by paragraphs — with tests"
79- state: open79+ state: closed
80 priority: none80 priority: none
81 author:81 author:
82 name: k33g82 name: k33g
@@ -85,7 +85,7 @@ tasks:
85 updatedAt: 2026-10-20T20:00:00.000Z85 updatedAt: 2026-10-20T20:00:00.000Z
86 - id: 386 - id: 3
87 title: "docsearch: BM25 (k1 1.2, b 0.75) over the chunks, words folded like store.fold (case, accents), heading boost — with tests"87 title: "docsearch: BM25 (k1 1.2, b 0.75) over the chunks, words folded like store.fold (case, accents), heading boost — with tests"
88- state: open88+ state: closed
89 priority: none89 priority: none
90 author:90 author:
91 name: k33g91 name: k33g
@@ -94,7 +94,7 @@ tasks:
94 updatedAt: 2026-10-20T20:00:00.000Z94 updatedAt: 2026-10-20T20:00:00.000Z
95 - id: 495 - id: 4
96 title: "docsearch: walk the folders (extensions, size cap, skip hidden dirs, .git, node_modules), scanned at each call, no index — with tests"96 title: "docsearch: walk the folders (extensions, size cap, skip hidden dirs, .git, node_modules), scanned at each call, no index — with tests"
97- state: open97+ state: closed
98 priority: none98 priority: none
99 author:99 author:
100 name: k33g100 name: k33g
@@ -102,8 +102,8 @@ tasks:
102 createdAt: 2026-10-20T20:00:00.000Z102 createdAt: 2026-10-20T20:00:00.000Z
103 updatedAt: 2026-10-20T20:00:00.000Z103 updatedAt: 2026-10-20T20:00:00.000Z
104 - id: 5104 - id: 5
105- title: "built-in tool search_doc {query, limit?}: hits = path:line, heading path, snippet; declared only when the config enables it; never asks; ACP kind search — with tests"105+ title: "built-in tool search_docs {query, limit?} and the /docs <words> command: hits = path:line, heading path, snippet; declared only when the config enables it; never asks; ACP kind search — with tests"
106- state: open106+ state: closed
107 priority: none107 priority: none
108 author:108 author:
109 name: k33g109 name: k33g
@@ -112,7 +112,7 @@ tasks:
112 updatedAt: 2026-10-20T20:00:00.000Z112 updatedAt: 2026-10-20T20:00:00.000Z
113 - id: 6113 - id: 6
114 title: "Measure on a small model (llmman) over mm's own docs/ (EN+FR): found when the words are there, not called otherwise; time per call on docs/"114 title: "Measure on a small model (llmman) over mm's own docs/ (EN+FR): found when the words are there, not called otherwise; time per call on docs/"
115- state: open115+ state: closed
116 priority: none116 priority: none
117 author:117 author:
118 name: k33g118 name: k33g
@@ -121,11 +121,96 @@ tasks:
121 updatedAt: 2026-10-20T20:00:00.000Z121 updatedAt: 2026-10-20T20:00:00.000Z
122 - id: 7122 - id: 7
123 title: "Docs EN+FR (reference configuration and tools, how-to, explanation: why BM25 and no index), demo if useful, quality gate, .memory"123 title: "Docs EN+FR (reference configuration and tools, how-to, explanation: why BM25 and no index), demo if useful, quality gate, .memory"
124- state: open124+ state: closed
125 priority: none125 priority: none
126 author:126 author:
127 name: k33g127 name: k33g
128 email: ph.charriere@gmail.com128 email: ph.charriere@gmail.com
129 createdAt: 2026-10-20T20:00:00.000Z129 createdAt: 2026-10-20T20:00:00.000Z
130 updatedAt: 2026-10-20T20:00:00.000Z130 updatedAt: 2026-10-20T20:00:00.000Z
131-comments: []131+comments:
132+ - id: 1
133+ author:
134+ name: k33g
135+ email: ph.charriere@gmail.com
136+ createdAt: 2026-10-20T21:00:00.000Z
137+ body: |
138+ Decided by the user (2026-10-20) — the proposal is validated, with:
139+ - tool name: search_docs (plural);
140+ - config block `docSearch`, with TWO switches instead of `enabled`:
141+ `searchDocs: true` → the /docs <words> command (for the user),
142+ `autoSearchDocs: true` → the search_docs tool (for the model);
143+ both default false, independent (as /memory and search_memory);
144+ - /docs: yes;
145+ - prefix matching for words of 4+ letters: yes;
146+ - extensions: only what the configuration lists (no hidden extra);
147+ the default list is the validated one (.md .markdown .txt .adoc
148+ .asciidoc .rst); symlinks not followed;
149+ - key name: docSearch.
150+ Then: demos/docs-search, whose documents are a set of simple
151+ tutorials on using ratatui in Rust (Markdown, generated for the demo).
152+ Branch feature/search-in-doc (created by the user).
153+ - id: 2
154+ author:
155+ name: k33g
156+ email: ph.charriere@gmail.com
157+ createdAt: 2026-10-20T23:00:00.000Z
158+ body: |
159+ Tasks 2-6 done on feature/search-in-doc (go test ./... green, gofmt/vet
160+ clean):
161+ - internal/docsearch (new): passages.go (sections at # / = headings,
162+ fences and listing blocks ignored, heading path, cut at blank lines
163+ past 1,500 runes, never inside a fence), bm25.go (k1 1.2, b 0.75,
164+ heading-path words +2, prefix match for 4+ runes, plural/-ed within
165+ 2 runes, query words < 3 runes dropped beside longer ones), walk.go
166+ (listed extensions only, maxFileSize, hidden dirs + node_modules
167+ skipped, symlinks not followed, NUL = binary, missing dirs
168+ reported), docsearch.go (Search, Result.Format), configured.go
169+ (Configured/Answer from config).
170+ - internal/textfold (new): Fold/Rune moved out of store (shared by
171+ store and docsearch, no duplicate).
172+ - config/docsearch.go: docSearch (searchDocs, autoSearchDocs, dirs
173+ [docs], extensions [.md .markdown .txt .adoc .asciidoc .rst],
174+ maxFileSize 1MB — ByteSize reads 1MB / 512KB / bytes); validated
175+ only when a switch is on; search_docs reserved.
176+ - command/docs.go SetDocs; /docs in the REPL, the window (notice) and
177+ ACP (agent text, input hint); tools/searchdocs.go search_docs
178+ (query, limit? — 5, cap 20), never asks, ACP kind search, dir = the
179+ sink's WorkDir else the process cwd; main.go wiring.
180+ - Speed (best of 10): mm's docs/ 137 files / 1,320 passages ≈ 45 ms;
181+ the demo's 10 files / 44 passages ≈ 1 ms.
182+ demos/docs-search: Rata, Qwen3.5 4B on llmman, 9 Ratatui 0.30
183+ tutorials + index; check-tutorials.sh compiles every complete program
184+ + snippets.rs in a rust container (-Dwarnings): OK, 10 programs,
185+ ratatui 0.30.2. Measured (temp 0, 3 runs each, identical): List →
186+ ListState, 04-lists.md:5 3/3; popup → Clear + centered, NO link 3/3;
187+ Windows double key → right answer (KeyEventKind::Press) 3/3 but cited
188+ code examples instead of 08:60 (ranked first by the search); progress
189+ bar → Gauge + event::poll, 06:9 3/3; two French questions → English
190+ searches, French answers, right links 6/6; "Write src/main.rs …" →
191+ file written 3/3, compiles against ratatui 0.30.2 3/3; greeting and
192+ an off-topic question → no search 6/6. Prompt lesson: the Windows
193+ question was answered without a search (3/3) until the prompt said any
194+ terminal-app question is a Ratatui question — after which the popup
195+ answer stopped citing its link.
196+ Task 7: docs EN+FR done (reference configuration + validation errors,
197+ slash-commands, tools, acp; how-to search-the-docs; explanation
198+ document-search; indexes; root README; packages.drawio). Quality gate
199+ NOT run (qlty absent here) — task 7 stays open for it.
200+ - id: 3
201+ author:
202+ name: k33g
203+ email: ph.charriere@gmail.com
204+ createdAt: 2026-10-20T23:30:00.000Z
205+ body: |
206+ Closed at the user's request. Delivered: internal/docsearch +
207+ internal/textfold, docSearch config (searchDocs / autoSearchDocs),
208+ /docs in the three front ends, the search_docs tool, tests, docs EN+FR
209+ (reference, how-to search-the-docs, explanation document-search),
210+ demos/docs-search (Ratatui tutorials, compile-checked, measured).
211+ Task 7 closed with the qlty quality gate still NOT run (qlty is not
212+ installed in this sandbox); gofmt and go vet clean, go test ./...
213+ green. Known limits left as documented: words, not meanings; in the
214+ demo the popup answer gives no link and the Windows answer cites code
215+ examples rather than the explaining passage.
216+
modified README.md +2 -0
@@ -121,6 +121,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
121121 - **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).
122122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).
123123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.
124+- **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`.
124125 - **Search earlier sessions**: with sessions on, `/memory <words>` searches the project's stored conversations (`sessions.searchMemory`), and the `search_memory` tool lets the model do it when a question refers to something decided before (`sessions.autoSearchMemory`); both off by default. The search matches words, not meanings. See [Search earlier sessions](docs/en/how-to/search-earlier-sessions.md) and `demos/memory-search/`.
125126 - **Docker Sandboxes**: the sandbox kit lives in its own repository, [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit); `sbx/` explains how to use it (`sbx run …`), with two mixins (`go-toolchain`, `cli-tools`) and ready-made configurations — see `sbx/README.md` and [Run mini-me in a Docker Sandbox](docs/en/how-to/run-in-a-docker-sandbox.md).
126127
@@ -155,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
155156 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |
156157 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |
157158 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |
159+| `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results |
158160 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |
159161 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
160162
@@ -121,6 +121,7 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
121 - **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).121 - **Plans**: the built-in `plan` tool lets the agent turn a list of tasks into a tracked plan and show its progress after each answer. See [Manage plans and tasks](docs/en/how-to/manage-plans-and-tasks.md).
122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).122 - **Save an answer**: `/save` writes the last answer to a Markdown file; with `saveAnswerTool: true`, the model does it itself when you ask in your own words (`save_answer`). Never overwrites a file. See [Save an answer to a file](docs/en/how-to/save-an-answer.md).
123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.123 - **Unload a model**: `/unload [model]` frees a model from the server's memory — in the terminal, the window and an editor —, and the `unload_model` tool lets the agent do it itself, e.g. on another agent's model once it has answered; llmman, Ollama and llama-server in router mode. `unloadModels: false` turns both off. See [Free a model's memory](docs/en/how-to/unload-a-model.md) and `demos/unload-models/`.
124+- **Search a documentation folder**: `/docs <words>` searches the text files of the folders named in `docSearch.dirs` (`.md`, `.adoc`, `.txt`… — only the extensions listed) and returns the best passages with their `path:line` link, ranked with BM25 — no index, no model (`docSearch.searchDocs`); the `search_docs` tool lets the model do it before it answers (`docSearch.autoSearchDocs`); both off by default. See [Search a documentation folder](docs/en/how-to/search-the-docs.md) and `demos/docs-search/`.
124 - **Search earlier sessions**: with sessions on, `/memory <words>` searches the project's stored conversations (`sessions.searchMemory`), and the `search_memory` tool lets the model do it when a question refers to something decided before (`sessions.autoSearchMemory`); both off by default. The search matches words, not meanings. See [Search earlier sessions](docs/en/how-to/search-earlier-sessions.md) and `demos/memory-search/`.125 - **Search earlier sessions**: with sessions on, `/memory <words>` searches the project's stored conversations (`sessions.searchMemory`), and the `search_memory` tool lets the model do it when a question refers to something decided before (`sessions.autoSearchMemory`); both off by default. The search matches words, not meanings. See [Search earlier sessions](docs/en/how-to/search-earlier-sessions.md) and `demos/memory-search/`.
125 - **Docker Sandboxes**: the sandbox kit lives in its own repository, [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit); `sbx/` explains how to use it (`sbx run …`), with two mixins (`go-toolchain`, `cli-tools`) and ready-made configurations — see `sbx/README.md` and [Run mini-me in a Docker Sandbox](docs/en/how-to/run-in-a-docker-sandbox.md).126 - **Docker Sandboxes**: the sandbox kit lives in its own repository, [mini-me-kit](https://rickub.com/bots-garden/mini-me-kit); `sbx/` explains how to use it (`sbx run …`), with two mixins (`go-toolchain`, `cli-tools`) and ready-made configurations — see `sbx/README.md` and [Run mini-me in a Docker Sandbox](docs/en/how-to/run-in-a-docker-sandbox.md).
126 127
@@ -155,6 +156,7 @@ The `demos/` directory holds ready-made set-ups (most with a README explaining h
155 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |156 | `demos/guard/` | one guard model (Granite Guardian 3.2 3B-A800M on llmman) answering `Yes`/`No` to "is this question harmful?"; why its `system:` line is a risk name, not a prompt; custom risks |
156 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |157 | `demos/guards/` | an assistant (Gemma 4 E4B) protected by seven guard agents, one per risk: a Node.js `preGeneration` hook asks them in parallel without the history, the assistant refuses or answers according to their verdicts; `displayHooks: true`; measured results |
157 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |158 | `demos/unload-models/` | a coordinator (Qwen3.5 9B) hands programming questions to an expert (Qwen3.5 4B) with `ask_agent`, a `preAsk` hook unloads the coordinator's own model before the expert starts and a `postAsk` hook unloads the expert's once it has answered — the two models are never loaded together; `/unload` and `unload_model` on request; llmman, measured results |
159+| `demos/docs-search/` | an assistant answering from a documentation folder: nine Ratatui tutorials (Rust terminal UIs, each program checked against ratatui 0.30 by `check-tutorials.sh`), `/docs <words>` for you and the `search_docs` tool for the model; Qwen3.5 4B on llmman, measured results |
158 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |160 | `demos/memory-search/` | an assistant that remembers a project's decisions: four earlier sessions (port, storage, logs in French, deployment as a `/compact` summary), `/memory <words>` for you and the `search_memory` tool for the model; Qwen3.5 4B on llmman, measured results — including what a word search cannot find |
159 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |161 | `demos/declared-tools/` | an ops desk with no `bash` and no skills, acting only through four tools declared under `tools:` — release, version bump, service logs, slow requests — covering every parameter type (string, enum, boolean, integer, number), a safe default (`dryRun: true`) and an optional argument; Mellum2 12B on DMR, measured results |
160 162
added demos/docs-search/README.en.md +75 -0
new file mode 100644
@@ -0,0 +1,75 @@
1+# mini-me demo: answering from a documentation folder
2+
3+*[Version française](README.fr.md)*
4+
5+Rata helps people build terminal user interfaces in Rust with [Ratatui](https://ratatui.rs). It knows nothing about Ratatui by itself that you should trust: before it answers, it searches `docs/` — nine short tutorials — with the `search_docs` tool, answers from the passages it gets back, and gives their `path:line` link. You can search the same folder yourself with `/docs <words>`. The search is BM25 over the Markdown files: no index, no model. The chat model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman).
6+
7+## What is here
8+
9+| File | Role |
10+|---|---|
11+| `agent.yaml` | Rata: `docSearch` with `searchDocs: true` (the `/docs` command) and `autoSearchDocs: true` (the `search_docs` tool) over `docs/`, `.md` only; `read_file` to open a whole tutorial, `write_file` to write a program; no `bash`. |
12+| `docs/` | Nine Ratatui 0.30 tutorials — getting started, layout, blocks and paragraphs, lists, tables, gauges and ticks, tabs, app state and events, popups — and their index. Each holds a complete program. |
13+| `check-tutorials.sh` | Compiles every complete program of `docs/` (and `snippets.rs`, the expressions quoted in the prose) against the current ratatui 0.30, in a throwaway `rust` container, warnings as errors. Needs Docker only. |
14+| `snippets.rs` | The short expressions the tutorials quote, so that the text cannot drift from the API. |
15+
16+## Run the demo
17+
18+```bash
19+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
20+cd demos/docs-search
21+mm -tui agent.yaml
22+```
23+
24+Search by hand:
25+
26+```text
27+/docs liststate
28+```
29+
30+```text
31+🔎 5 passage(s) for "liststate" — 10 file(s) in docs
32+
33+── docs/04-lists.md:5 · Lists and selection › Keep the selection with ListState
34+## Keep the selection with ListState
35+
36+`ListState` remembers which item is selected and how far the list is scrolled. …
37+```
38+
39+Then ask Rata:
40+
41+```text
42+How do I keep the selected item of a List in ratatui?
43+Every key I press acts twice on Windows. Why?
44+Comment afficher un tableau avec une ligne sélectionnée ?
45+Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit.
46+```
47+
48+Each search shows as `📚 search_docs: "<words>"` followed by the passages. Check the tutorials still compile with `./check-tutorials.sh` (`OK: 10 programs compile against ratatui 0.30.2` on 2026-10-20).
49+
50+## Measured results (2026-10-20, `mm -p`, llmman)
51+
52+Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run. Three runs per question; the same search and the same answer each time.
53+
54+| Question | Searched | Answer | Link given | |
55+|---|---|---|---|---|
56+| How do I keep the selected item of a List in ratatui? | `List selected item index` | `ListState`, `render_stateful_widget`, `state.selected()` | `docs/04-lists.md:5` | 3/3 |
57+| How do I show a popup in the middle of the screen? | `popup center middle screen` | `Clear` first, then the dialog in a centered area | none | 3/3 |
58+| Every key I press acts twice on Windows. Why? | `Windows key repeat double press` | Press and Release events; check `KeyEventKind::Press` | `01-getting-started.md:15`, `03-blocks-and-paragraphs.md:11` (code using the check), not the passage that explains it, `08-app-state-and-events.md:60` — which the search ranked first | 3/3 |
59+| How can I animate a progress bar without waiting for a key? | `animate progress bar key event` | `Gauge` + `event::poll` with a timeout, a tick when no event | `docs/06-gauges-and-ticks.md:9` | 3/3 |
60+| Comment découper l'écran en en-tête, corps et pied de page ? | `header footer body layout` | `Layout::vertical` with `Length` / `Min`, in French | `docs/02-layout.md:16` | 3/3 |
61+| Comment afficher un tableau avec une ligne sélectionnée ? | `table selection line` | `Table` + `TableState` + `row_highlight_style`, in French | `docs/05-tables.md:1` | 3/3 |
62+| Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit. | `list widget` | `src/main.rs` written — it **compiles** against ratatui 0.30.2 (checked in the `rust` container) | — | 3/3 |
63+| Bonjour, qui es-tu ? | — | one line, in French, no search | — | 3/3 |
64+| What is the capital of Japan? One word. | — | `Tokyo`, no search | — | 3/3 |
65+
66+14 to 38 s per question with a search, under 2 s without; two runs took 164 s and 316 s with the same answer as the others (cause not established — the model server is shared). One search over `docs/` takes about 1 ms.
67+
68+## Good to know
69+
70+- **The model translates, not the search.** The tutorials are in English and the search matches words: French words would find nothing. Told to search "with English words — widget and type names", the model turned both French questions into English searches every time, and answered in French.
71+- **What the prompt had to say.** Without "any question about a terminal app … is a Ratatui question, even when it does not name Ratatui", the Windows question got a generic answer without a search (3/3). With it, the other answers stayed right, but the popup answer stopped giving its link (it did give `02-layout.md:68` and `09-popups.md:9` before) — a small model's citations move with the prompt.
72+- **Links are `path:line`**: an editor opens them, and `read_file` reads the whole tutorial when a passage is not enough.
73+- **Each switch on its own.** `searchDocs` gives you `/docs`; `autoSearchDocs` gives the model `search_docs`. Both are off by default.
74+
75+See also: [How to search a documentation folder](../../docs/en/how-to/search-the-docs.md), [`search_docs`](../../docs/en/reference/tools.md#search_docs), [Why it works this way](../../docs/en/explanation/document-search.md).
new file mode 100644
@@ -0,0 +1,75 @@
1+# mini-me demo: answering from a documentation folder
2+
3+*[Version française](README.fr.md)*
4+
5+Rata helps people build terminal user interfaces in Rust with [Ratatui](https://ratatui.rs). It knows nothing about Ratatui by itself that you should trust: before it answers, it searches `docs/` — nine short tutorials — with the `search_docs` tool, answers from the passages it gets back, and gives their `path:line` link. You can search the same folder yourself with `/docs <words>`. The search is BM25 over the Markdown files: no index, no model. The chat model is Qwen3.5 4B, served by [llmman](https://github.com/llmmanorg/llmman).
6+
7+## What is here
8+
9+| File | Role |
10+|---|---|
11+| `agent.yaml` | Rata: `docSearch` with `searchDocs: true` (the `/docs` command) and `autoSearchDocs: true` (the `search_docs` tool) over `docs/`, `.md` only; `read_file` to open a whole tutorial, `write_file` to write a program; no `bash`. |
12+| `docs/` | Nine Ratatui 0.30 tutorials — getting started, layout, blocks and paragraphs, lists, tables, gauges and ticks, tabs, app state and events, popups — and their index. Each holds a complete program. |
13+| `check-tutorials.sh` | Compiles every complete program of `docs/` (and `snippets.rs`, the expressions quoted in the prose) against the current ratatui 0.30, in a throwaway `rust` container, warnings as errors. Needs Docker only. |
14+| `snippets.rs` | The short expressions the tutorials quote, so that the text cannot drift from the API. |
15+
16+## Run the demo
17+
18+```bash
19+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
20+cd demos/docs-search
21+mm -tui agent.yaml
22+```
23+
24+Search by hand:
25+
26+```text
27+/docs liststate
28+```
29+
30+```text
31+🔎 5 passage(s) for "liststate" — 10 file(s) in docs
32+
33+── docs/04-lists.md:5 · Lists and selection › Keep the selection with ListState
34+## Keep the selection with ListState
35+
36+`ListState` remembers which item is selected and how far the list is scrolled. …
37+```
38+
39+Then ask Rata:
40+
41+```text
42+How do I keep the selected item of a List in ratatui?
43+Every key I press acts twice on Windows. Why?
44+Comment afficher un tableau avec une ligne sélectionnée ?
45+Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit.
46+```
47+
48+Each search shows as `📚 search_docs: "<words>"` followed by the passages. Check the tutorials still compile with `./check-tutorials.sh` (`OK: 10 programs compile against ratatui 0.30.2` on 2026-10-20).
49+
50+## Measured results (2026-10-20, `mm -p`, llmman)
51+
52+Qwen3.5 4B Q4_K_M, `temperature: 0`, thinking off, this `agent.yaml`, a fresh copy of the demo for every run. Three runs per question; the same search and the same answer each time.
53+
54+| Question | Searched | Answer | Link given | |
55+|---|---|---|---|---|
56+| How do I keep the selected item of a List in ratatui? | `List selected item index` | `ListState`, `render_stateful_widget`, `state.selected()` | `docs/04-lists.md:5` | 3/3 |
57+| How do I show a popup in the middle of the screen? | `popup center middle screen` | `Clear` first, then the dialog in a centered area | none | 3/3 |
58+| Every key I press acts twice on Windows. Why? | `Windows key repeat double press` | Press and Release events; check `KeyEventKind::Press` | `01-getting-started.md:15`, `03-blocks-and-paragraphs.md:11` (code using the check), not the passage that explains it, `08-app-state-and-events.md:60` — which the search ranked first | 3/3 |
59+| How can I animate a progress bar without waiting for a key? | `animate progress bar key event` | `Gauge` + `event::poll` with a timeout, a tick when no event | `docs/06-gauges-and-ticks.md:9` | 3/3 |
60+| Comment découper l'écran en en-tête, corps et pied de page ? | `header footer body layout` | `Layout::vertical` with `Length` / `Min`, in French | `docs/02-layout.md:16` | 3/3 |
61+| Comment afficher un tableau avec une ligne sélectionnée ? | `table selection line` | `Table` + `TableState` + `row_highlight_style`, in French | `docs/05-tables.md:1` | 3/3 |
62+| Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit. | `list widget` | `src/main.rs` written — it **compiles** against ratatui 0.30.2 (checked in the `rust` container) | — | 3/3 |
63+| Bonjour, qui es-tu ? | — | one line, in French, no search | — | 3/3 |
64+| What is the capital of Japan? One word. | — | `Tokyo`, no search | — | 3/3 |
65+
66+14 to 38 s per question with a search, under 2 s without; two runs took 164 s and 316 s with the same answer as the others (cause not established — the model server is shared). One search over `docs/` takes about 1 ms.
67+
68+## Good to know
69+
70+- **The model translates, not the search.** The tutorials are in English and the search matches words: French words would find nothing. Told to search "with English words — widget and type names", the model turned both French questions into English searches every time, and answered in French.
71+- **What the prompt had to say.** Without "any question about a terminal app … is a Ratatui question, even when it does not name Ratatui", the Windows question got a generic answer without a search (3/3). With it, the other answers stayed right, but the popup answer stopped giving its link (it did give `02-layout.md:68` and `09-popups.md:9` before) — a small model's citations move with the prompt.
72+- **Links are `path:line`**: an editor opens them, and `read_file` reads the whole tutorial when a passage is not enough.
73+- **Each switch on its own.** `searchDocs` gives you `/docs`; `autoSearchDocs` gives the model `search_docs`. Both are off by default.
74+
75+See also: [How to search a documentation folder](../../docs/en/how-to/search-the-docs.md), [`search_docs`](../../docs/en/reference/tools.md#search_docs), [Why it works this way](../../docs/en/explanation/document-search.md).
added demos/docs-search/README.fr.md +75 -0
new file mode 100644
@@ -0,0 +1,75 @@
1+# Démo mini-me : répondre à partir d'un dossier de documentation
2+
3+*[English version](README.en.md)*
4+
5+Rata aide à construire des interfaces en mode terminal en Rust avec [Ratatui](https://ratatui.rs). Il ne sait rien de Ratatui par lui-même qui mérite confiance : avant de répondre, il cherche dans `docs/` — neuf tutoriels courts — avec l'outil `search_docs`, répond à partir des passages qu'il reçoit et donne leur lien `chemin:ligne`. Vous pouvez chercher vous-même dans le même dossier avec `/docs <mots>`. La recherche est un BM25 sur les fichiers Markdown : ni index, ni modèle. Le modèle de chat est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman).
6+
7+## Contenu
8+
9+| Fichier | Rôle |
10+|---|---|
11+| `agent.yaml` | Rata : `docSearch` avec `searchDocs: true` (la commande `/docs`) et `autoSearchDocs: true` (l'outil `search_docs`) sur `docs/`, `.md` seulement ; `read_file` pour ouvrir un tutoriel entier, `write_file` pour écrire un programme ; pas de `bash`. |
12+| `docs/` | Neuf tutoriels Ratatui 0.30 — premiers pas, layout, blocs et paragraphes, listes, tableaux, jauges et ticks, onglets, état de l'application et événements, popups — et leur index. Chacun contient un programme complet. |
13+| `check-tutorials.sh` | Compile chaque programme complet de `docs/` (et `snippets.rs`, les expressions citées dans le texte) avec la ratatui 0.30 actuelle, dans un conteneur `rust` jetable, avertissements traités comme des erreurs. Ne demande que Docker. |
14+| `snippets.rs` | Les petites expressions que citent les tutoriels, pour que le texte ne puisse pas s'écarter de l'API. |
15+
16+## Lancer la démo
17+
18+```bash
19+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
20+cd demos/docs-search
21+mm -tui agent.yaml
22+```
23+
24+Chercher à la main :
25+
26+```text
27+/docs liststate
28+```
29+
30+```text
31+🔎 5 passage(s) for "liststate" — 10 file(s) in docs
32+
33+── docs/04-lists.md:5 · Lists and selection › Keep the selection with ListState
34+## Keep the selection with ListState
35+
36+`ListState` remembers which item is selected and how far the list is scrolled. …
37+```
38+
39+Puis demander à Rata :
40+
41+```text
42+How do I keep the selected item of a List in ratatui?
43+Every key I press acts twice on Windows. Why?
44+Comment afficher un tableau avec une ligne sélectionnée ?
45+Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit.
46+```
47+
48+Chaque recherche s'affiche sous la forme `📚 search_docs: "<mots>"` suivie des passages. Vérifiez que les tutoriels compilent toujours avec `./check-tutorials.sh` (`OK: 10 programs compile against ratatui 0.30.2` le 2026-10-20).
49+
50+## Résultats mesurés (2026-10-20, `mm -p`, llmman)
51+
52+Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution. Trois exécutions par question ; la même recherche et la même réponse à chaque fois.
53+
54+| Question | Recherché | Réponse | Lien donné | |
55+|---|---|---|---|---|
56+| How do I keep the selected item of a List in ratatui? | `List selected item index` | `ListState`, `render_stateful_widget`, `state.selected()` | `docs/04-lists.md:5` | 3/3 |
57+| How do I show a popup in the middle of the screen? | `popup center middle screen` | `Clear` d'abord, puis le dialogue dans une zone centrée | aucun | 3/3 |
58+| Every key I press acts twice on Windows. Why? | `Windows key repeat double press` | événements Press et Release ; tester `KeyEventKind::Press` | `01-getting-started.md:15`, `03-blocks-and-paragraphs.md:11` (du code qui fait ce test), pas le passage qui l'explique, `08-app-state-and-events.md:60` — que la recherche avait classé premier | 3/3 |
59+| How can I animate a progress bar without waiting for a key? | `animate progress bar key event` | `Gauge` + `event::poll` avec délai, un tick quand rien n'arrive | `docs/06-gauges-and-ticks.md:9` | 3/3 |
60+| Comment découper l'écran en en-tête, corps et pied de page ? | `header footer body layout` | `Layout::vertical` avec `Length` / `Min`, en français | `docs/02-layout.md:16` | 3/3 |
61+| Comment afficher un tableau avec une ligne sélectionnée ? | `table selection line` | `Table` + `TableState` + `row_highlight_style`, en français | `docs/05-tables.md:1` | 3/3 |
62+| Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit. | `list widget` | `src/main.rs` écrit — il **compile** avec ratatui 0.30.2 (vérifié dans le conteneur `rust`) | — | 3/3 |
63+| Bonjour, qui es-tu ? | — | une ligne, en français, sans recherche | — | 3/3 |
64+| What is the capital of Japan? One word. | — | `Tokyo`, sans recherche | — | 3/3 |
65+
66+De 14 à 38 s par question avec recherche, moins de 2 s sans ; deux exécutions ont pris 164 s et 316 s avec la même réponse que les autres (cause non établie — le serveur de modèles est partagé). Une recherche dans `docs/` prend environ 1 ms.
67+
68+## Bon à savoir
69+
70+- **C'est le modèle qui traduit, pas la recherche.** Les tutoriels sont en anglais et la recherche porte sur les mots : des mots français ne trouveraient rien. Invité à chercher « avec des mots anglais — noms de widgets et de types », le modèle a transformé les deux questions en français en recherches en anglais à chaque fois, et a répondu en français.
71+- **Ce que le prompt a dû dire.** Sans « toute question sur une application terminal … est une question Ratatui, même quand elle ne nomme pas Ratatui », la question sur Windows recevait une réponse générique, sans recherche (3/3). Avec cette phrase, les autres réponses sont restées justes, mais la réponse sur la popup a cessé de donner son lien (elle donnait `02-layout.md:68` et `09-popups.md:9` avant) — les citations d'un petit modèle bougent avec le prompt.
72+- **Les liens sont des `chemin:ligne`** : un éditeur les ouvre, et `read_file` lit le tutoriel entier quand un passage ne suffit pas.
73+- **Chaque interrupteur séparément.** `searchDocs` vous donne `/docs` ; `autoSearchDocs` donne `search_docs` au modèle. Les deux sont désactivés par défaut.
74+
75+Voir aussi : [Chercher dans un dossier de documentation](../../docs/fr/how-to/search-the-docs.md), [`search_docs`](../../docs/fr/reference/tools.md#search_docs), [Pourquoi elle fonctionne ainsi](../../docs/fr/explanation/document-search.md).
new file mode 100644
@@ -0,0 +1,75 @@
1+# Démo mini-me : répondre à partir d'un dossier de documentation
2+
3+*[English version](README.en.md)*
4+
5+Rata aide à construire des interfaces en mode terminal en Rust avec [Ratatui](https://ratatui.rs). Il ne sait rien de Ratatui par lui-même qui mérite confiance : avant de répondre, il cherche dans `docs/` — neuf tutoriels courts — avec l'outil `search_docs`, répond à partir des passages qu'il reçoit et donne leur lien `chemin:ligne`. Vous pouvez chercher vous-même dans le même dossier avec `/docs <mots>`. La recherche est un BM25 sur les fichiers Markdown : ni index, ni modèle. Le modèle de chat est Qwen3.5 4B, servi par [llmman](https://github.com/llmmanorg/llmman).
6+
7+## Contenu
8+
9+| Fichier | Rôle |
10+|---|---|
11+| `agent.yaml` | Rata : `docSearch` avec `searchDocs: true` (la commande `/docs`) et `autoSearchDocs: true` (l'outil `search_docs`) sur `docs/`, `.md` seulement ; `read_file` pour ouvrir un tutoriel entier, `write_file` pour écrire un programme ; pas de `bash`. |
12+| `docs/` | Neuf tutoriels Ratatui 0.30 — premiers pas, layout, blocs et paragraphes, listes, tableaux, jauges et ticks, onglets, état de l'application et événements, popups — et leur index. Chacun contient un programme complet. |
13+| `check-tutorials.sh` | Compile chaque programme complet de `docs/` (et `snippets.rs`, les expressions citées dans le texte) avec la ratatui 0.30 actuelle, dans un conteneur `rust` jetable, avertissements traités comme des erreurs. Ne demande que Docker. |
14+| `snippets.rs` | Les petites expressions que citent les tutoriels, pour que le texte ne puisse pas s'écarter de l'API. |
15+
16+## Lancer la démo
17+
18+```bash
19+llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
20+cd demos/docs-search
21+mm -tui agent.yaml
22+```
23+
24+Chercher à la main :
25+
26+```text
27+/docs liststate
28+```
29+
30+```text
31+🔎 5 passage(s) for "liststate" — 10 file(s) in docs
32+
33+── docs/04-lists.md:5 · Lists and selection › Keep the selection with ListState
34+## Keep the selection with ListState
35+
36+`ListState` remembers which item is selected and how far the list is scrolled. …
37+```
38+
39+Puis demander à Rata :
40+
41+```text
42+How do I keep the selected item of a List in ratatui?
43+Every key I press acts twice on Windows. Why?
44+Comment afficher un tableau avec une ligne sélectionnée ?
45+Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit.
46+```
47+
48+Chaque recherche s'affiche sous la forme `📚 search_docs: "<mots>"` suivie des passages. Vérifiez que les tutoriels compilent toujours avec `./check-tutorials.sh` (`OK: 10 programs compile against ratatui 0.30.2` le 2026-10-20).
49+
50+## Résultats mesurés (2026-10-20, `mm -p`, llmman)
51+
52+Qwen3.5 4B Q4_K_M, `temperature: 0`, réflexion désactivée, cet `agent.yaml`, une copie neuve de la démo à chaque exécution. Trois exécutions par question ; la même recherche et la même réponse à chaque fois.
53+
54+| Question | Recherché | Réponse | Lien donné | |
55+|---|---|---|---|---|
56+| How do I keep the selected item of a List in ratatui? | `List selected item index` | `ListState`, `render_stateful_widget`, `state.selected()` | `docs/04-lists.md:5` | 3/3 |
57+| How do I show a popup in the middle of the screen? | `popup center middle screen` | `Clear` d'abord, puis le dialogue dans une zone centrée | aucun | 3/3 |
58+| Every key I press acts twice on Windows. Why? | `Windows key repeat double press` | événements Press et Release ; tester `KeyEventKind::Press` | `01-getting-started.md:15`, `03-blocks-and-paragraphs.md:11` (du code qui fait ce test), pas le passage qui l'explique, `08-app-state-and-events.md:60` — que la recherche avait classé premier | 3/3 |
59+| How can I animate a progress bar without waiting for a key? | `animate progress bar key event` | `Gauge` + `event::poll` avec délai, un tick quand rien n'arrive | `docs/06-gauges-and-ticks.md:9` | 3/3 |
60+| Comment découper l'écran en en-tête, corps et pied de page ? | `header footer body layout` | `Layout::vertical` avec `Length` / `Min`, en français | `docs/02-layout.md:16` | 3/3 |
61+| Comment afficher un tableau avec une ligne sélectionnée ? | `table selection line` | `Table` + `TableState` + `row_highlight_style`, en français | `docs/05-tables.md:1` | 3/3 |
62+| Write src/main.rs: a list of three fruits I can move through with the arrow keys, q to quit. | `list widget` | `src/main.rs` écrit — il **compile** avec ratatui 0.30.2 (vérifié dans le conteneur `rust`) | — | 3/3 |
63+| Bonjour, qui es-tu ? | — | une ligne, en français, sans recherche | — | 3/3 |
64+| What is the capital of Japan? One word. | — | `Tokyo`, sans recherche | — | 3/3 |
65+
66+De 14 à 38 s par question avec recherche, moins de 2 s sans ; deux exécutions ont pris 164 s et 316 s avec la même réponse que les autres (cause non établie — le serveur de modèles est partagé). Une recherche dans `docs/` prend environ 1 ms.
67+
68+## Bon à savoir
69+
70+- **C'est le modèle qui traduit, pas la recherche.** Les tutoriels sont en anglais et la recherche porte sur les mots : des mots français ne trouveraient rien. Invité à chercher « avec des mots anglais — noms de widgets et de types », le modèle a transformé les deux questions en français en recherches en anglais à chaque fois, et a répondu en français.
71+- **Ce que le prompt a dû dire.** Sans « toute question sur une application terminal … est une question Ratatui, même quand elle ne nomme pas Ratatui », la question sur Windows recevait une réponse générique, sans recherche (3/3). Avec cette phrase, les autres réponses sont restées justes, mais la réponse sur la popup a cessé de donner son lien (elle donnait `02-layout.md:68` et `09-popups.md:9` avant) — les citations d'un petit modèle bougent avec le prompt.
72+- **Les liens sont des `chemin:ligne`** : un éditeur les ouvre, et `read_file` lit le tutoriel entier quand un passage ne suffit pas.
73+- **Chaque interrupteur séparément.** `searchDocs` vous donne `/docs` ; `autoSearchDocs` donne `search_docs` au modèle. Les deux sont désactivés par défaut.
74+
75+Voir aussi : [Chercher dans un dossier de documentation](../../docs/fr/how-to/search-the-docs.md), [`search_docs`](../../docs/fr/reference/tools.md#search_docs), [Pourquoi elle fonctionne ainsi](../../docs/fr/explanation/document-search.md).
added demos/docs-search/agent.yaml +79 -0
new file mode 100644
@@ -0,0 +1,79 @@
1+# Docs search — an assistant that answers from a documentation folder:
2+#
3+# /docs <words> the user's search through docs/ (docSearch.searchDocs)
4+# search_docs the same search, called by the model before it answers
5+# (docSearch.autoSearchDocs)
6+#
7+# docs/ holds nine short tutorials on Ratatui (Rust terminal UIs), each a
8+# complete program checked by ./check-tutorials.sh against ratatui 0.30.
9+#
10+# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
11+# llmman serve
12+#
13+# cd demos/docs-search
14+# mm -tui agent.yaml or mm -p "…" agent.yaml
15+
16+provider: llmman
17+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+baseUrl: http://127.0.0.1:17434/v1
19+fallback: http://host.docker.internal:17434/v1
20+contextWindow: 16384
21+
22+docSearch:
23+ # /docs <words>, for you.
24+ searchDocs: true
25+ # search_docs, for the model.
26+ autoSearchDocs: true
27+ dirs: [docs]
28+ extensions: [.md]
29+ maxFileSize: 1MB
30+
31+# read_file to open a whole tutorial from a link, write_file to write a
32+# program the user asks for; no bash.
33+yolo: true
34+bashTool: false
35+editTools: true
36+planTool: false
37+askUserTool: false
38+skillsDir: no-skills
39+
40+agentsMd:
41+ enabled: false
42+
43+sampling:
44+ temperature: 0.0
45+ parallel_tool_calls: false
46+ max_tokens: 3072
47+ chat_template_kwargs:
48+ enable_thinking: false
49+
50+maxTurns: 8
51+watchdogTimeout: 2m
52+firstTokenTimeout: 3m
53+displayCommands: true
54+
55+acp:
56+ name: rata
57+ title: 🐀 Rata (Ratatui docs)
58+
59+system: |
60+ Your name is Rata. You help people build terminal user interfaces in Rust
61+ with Ratatui. The folder docs/ holds Ratatui tutorials, written in English.
62+
63+ Any question about a terminal app — widgets, layout, keys, events,
64+ colors, the terminal itself — is a Ratatui question, even when it does
65+ not name Ratatui. For each one, call search_docs first, with English
66+ words — widget and type names work best (List, ListState, Layout, Gauge,
67+ Tabs, Clear, event::poll…). Answer from the passages it returns, and end
68+ with the link of the passage you used (path:line). If a passage is not
69+ enough, read_file the tutorial it comes from. Never invent an API: if
70+ search_docs finds nothing, try other English words once, then say the
71+ tutorials do not cover it.
72+
73+ When the user asks you to write or create a file, call write_file with
74+ the whole content; do not only show the code.
75+
76+ For anything else (a greeting, a question that is not about Ratatui),
77+ answer directly, without search_docs.
78+
79+ Always answer in the language of the user's question.
new file mode 100644
@@ -0,0 +1,79 @@
1+# Docs search — an assistant that answers from a documentation folder:
2+#
3+# /docs <words> the user's search through docs/ (docSearch.searchDocs)
4+# search_docs the same search, called by the model before it answers
5+# (docSearch.autoSearchDocs)
6+#
7+# docs/ holds nine short tutorials on Ratatui (Rust terminal UIs), each a
8+# complete program checked by ./check-tutorials.sh against ratatui 0.30.
9+#
10+# llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
11+# llmman serve
12+#
13+# cd demos/docs-search
14+# mm -tui agent.yaml or mm -p "…" agent.yaml
15+
16+provider: llmman
17+model: hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
18+baseUrl: http://127.0.0.1:17434/v1
19+fallback: http://host.docker.internal:17434/v1
20+contextWindow: 16384
21+
22+docSearch:
23+ # /docs <words>, for you.
24+ searchDocs: true
25+ # search_docs, for the model.
26+ autoSearchDocs: true
27+ dirs: [docs]
28+ extensions: [.md]
29+ maxFileSize: 1MB
30+
31+# read_file to open a whole tutorial from a link, write_file to write a
32+# program the user asks for; no bash.
33+yolo: true
34+bashTool: false
35+editTools: true
36+planTool: false
37+askUserTool: false
38+skillsDir: no-skills
39+
40+agentsMd:
41+ enabled: false
42+
43+sampling:
44+ temperature: 0.0
45+ parallel_tool_calls: false
46+ max_tokens: 3072
47+ chat_template_kwargs:
48+ enable_thinking: false
49+
50+maxTurns: 8
51+watchdogTimeout: 2m
52+firstTokenTimeout: 3m
53+displayCommands: true
54+
55+acp:
56+ name: rata
57+ title: 🐀 Rata (Ratatui docs)
58+
59+system: |
60+ Your name is Rata. You help people build terminal user interfaces in Rust
61+ with Ratatui. The folder docs/ holds Ratatui tutorials, written in English.
62+
63+ Any question about a terminal app — widgets, layout, keys, events,
64+ colors, the terminal itself — is a Ratatui question, even when it does
65+ not name Ratatui. For each one, call search_docs first, with English
66+ words — widget and type names work best (List, ListState, Layout, Gauge,
67+ Tabs, Clear, event::poll…). Answer from the passages it returns, and end
68+ with the link of the passage you used (path:line). If a passage is not
69+ enough, read_file the tutorial it comes from. Never invent an API: if
70+ search_docs finds nothing, try other English words once, then say the
71+ tutorials do not cover it.
72+
73+ When the user asks you to write or create a file, call write_file with
74+ the whole content; do not only show the code.
75+
76+ For anything else (a greeting, a question that is not about Ratatui),
77+ answer directly, without search_docs.
78+
79+ Always answer in the language of the user's question.
added demos/docs-search/check-tutorials.sh +37 -0
new file mode 100755
@@ -0,0 +1,37 @@
1+#!/bin/sh
2+# check-tutorials.sh — compiles every complete program of docs/*.md (the
3+# ```rust blocks that hold a `fn main`) against the current ratatui 0.30, in
4+# a throwaway Rust container: nothing to install but Docker. Also compiles
5+# snippets.rs, the short expressions the tutorials quote in their prose.
6+#
7+# ./check-tutorials.sh → "OK: N programs compile" or cargo's errors
8+set -eu
9+cd "$(dirname "$0")"
10+work=$(mktemp -d)
11+trap 'rm -rf "$work"' EXIT
12+mkdir -p "$work/src/bin"
13+cat > "$work/Cargo.toml" <<'TOML'
14+[package]
15+name = "tutorials"
16+version = "0.1.0"
17+edition = "2021"
18+
19+[dependencies]
20+ratatui = "0.30"
21+TOML
22+n=0
23+for doc in docs/*.md; do
24+ # one file per ```rust block holding fn main (```rust,ignore blocks are skipped)
25+ awk -v out="$work/src/bin/$(basename "$doc" .md)" '
26+ /^```rust$/ { inblock = 1; buf = ""; next }
27+ /^```/ && inblock { inblock = 0; if (buf ~ /fn main/) { k++; f = out "-" k ".rs"; printf "%s", buf > f; close(f) } next }
28+ inblock { buf = buf $0 "\n" }
29+ ' "$doc"
30+done
31+cp snippets.rs "$work/src/bin/snippets.rs"
32+n=$(ls "$work/src/bin" | wc -l | tr -d ' ')
33+# As the calling user (files in $work stay removable), with a cargo home
34+# inside $work since the image's own one belongs to root.
35+docker run --rm --user "$(id -u):$(id -g)" -e CARGO_HOME=/work/.cargo -e RUSTFLAGS=-Dwarnings \
36+ -v "$work":/work -w /work rust:1-slim cargo build --quiet --bins
37+echo "OK: $n programs compile against ratatui $(grep -A1 'name = "ratatui"' "$work/Cargo.lock" | sed -n 's/version = "\(.*\)"/\1/p')"
new file mode 100755
@@ -0,0 +1,37 @@
1+#!/bin/sh
2+# check-tutorials.sh — compiles every complete program of docs/*.md (the
3+# ```rust blocks that hold a `fn main`) against the current ratatui 0.30, in
4+# a throwaway Rust container: nothing to install but Docker. Also compiles
5+# snippets.rs, the short expressions the tutorials quote in their prose.
6+#
7+# ./check-tutorials.sh → "OK: N programs compile" or cargo's errors
8+set -eu
9+cd "$(dirname "$0")"
10+work=$(mktemp -d)
11+trap 'rm -rf "$work"' EXIT
12+mkdir -p "$work/src/bin"
13+cat > "$work/Cargo.toml" <<'TOML'
14+[package]
15+name = "tutorials"
16+version = "0.1.0"
17+edition = "2021"
18+
19+[dependencies]
20+ratatui = "0.30"
21+TOML
22+n=0
23+for doc in docs/*.md; do
24+ # one file per ```rust block holding fn main (```rust,ignore blocks are skipped)
25+ awk -v out="$work/src/bin/$(basename "$doc" .md)" '
26+ /^```rust$/ { inblock = 1; buf = ""; next }
27+ /^```/ && inblock { inblock = 0; if (buf ~ /fn main/) { k++; f = out "-" k ".rs"; printf "%s", buf > f; close(f) } next }
28+ inblock { buf = buf $0 "\n" }
29+ ' "$doc"
30+done
31+cp snippets.rs "$work/src/bin/snippets.rs"
32+n=$(ls "$work/src/bin" | wc -l | tr -d ' ')
33+# As the calling user (files in $work stay removable), with a cargo home
34+# inside $work since the image's own one belongs to root.
35+docker run --rm --user "$(id -u):$(id -g)" -e CARGO_HOME=/work/.cargo -e RUSTFLAGS=-Dwarnings \
36+ -v "$work":/work -w /work rust:1-slim cargo build --quiet --bins
37+echo "OK: $n programs compile against ratatui $(grep -A1 'name = "ratatui"' "$work/Cargo.lock" | sed -n 's/version = "\(.*\)"/\1/p')"
added demos/docs-search/docs/01-getting-started.md +58 -0
new file mode 100644
@@ -0,0 +1,58 @@
1+# Getting started with Ratatui
2+
3+Ratatui is a Rust library for building terminal user interfaces (TUIs). It draws widgets — blocks, paragraphs, lists, tables, gauges — into a buffer, and a backend (Crossterm by default) writes that buffer to the terminal. Ratatui does not read the keyboard itself: events come from the backend, here Crossterm, which Ratatui re-exports as `ratatui::crossterm`.
4+
5+## Create the project
6+
7+```sh
8+cargo new hello-tui
9+cd hello-tui
10+cargo add ratatui
11+```
12+
13+`cargo add ratatui` adds the current version (0.30 at the time of writing). The Crossterm backend is enabled by default, so there is nothing else to add: use `ratatui::crossterm` rather than a separate `crossterm` dependency, and the two versions can never disagree.
14+
15+## The smallest app
16+
17+Replace `src/main.rs` with:
18+
19+```rust
20+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
21+use ratatui::DefaultTerminal;
22+
23+fn main() -> std::io::Result<()> {
24+ let mut terminal = ratatui::init();
25+ let result = run(&mut terminal);
26+ ratatui::restore();
27+ result
28+}
29+
30+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
31+ loop {
32+ terminal.draw(|frame| {
33+ frame.render_widget("Hello, Ratatui! Press q to quit.", frame.area());
34+ })?;
35+ if let Event::Key(key) = event::read()? {
36+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
37+ return Ok(());
38+ }
39+ }
40+ }
41+}
42+```
43+
44+Run it with `cargo run`. The text fills the top-left of the terminal; `q` quits.
45+
46+## What init and restore do
47+
48+`ratatui::init()` switches the terminal to raw mode (keys arrive one by one, unechoed) and to the alternate screen (your shell's content comes back when the app ends), and installs a panic hook that restores the terminal if the app panics. `ratatui::restore()` undoes both. Always call `restore` before `main` returns, even on error — that is why the loop lives in its own `run` function whose result is returned after `restore`.
49+
50+## The draw loop
51+
52+`terminal.draw` takes a closure that receives a `Frame`. `frame.area()` is the whole terminal as a `Rect`; `frame.render_widget(widget, area)` draws a widget into a part of it. Ratatui redraws the whole UI every time: it keeps the previous buffer and only sends the cells that changed, so drawing everything each loop is cheap. A `&str` is itself a widget, which is why the smallest app can render a plain string.
53+
54+## Next steps
55+
56+- Split the screen into areas: see the layout tutorial.
57+- Draw borders, titles and wrapped text: see the blocks and paragraphs tutorial.
58+- Keep state and react to keys: see the app state and events tutorial.
new file mode 100644
@@ -0,0 +1,58 @@
1+# Getting started with Ratatui
2+
3+Ratatui is a Rust library for building terminal user interfaces (TUIs). It draws widgets — blocks, paragraphs, lists, tables, gauges — into a buffer, and a backend (Crossterm by default) writes that buffer to the terminal. Ratatui does not read the keyboard itself: events come from the backend, here Crossterm, which Ratatui re-exports as `ratatui::crossterm`.
4+
5+## Create the project
6+
7+```sh
8+cargo new hello-tui
9+cd hello-tui
10+cargo add ratatui
11+```
12+
13+`cargo add ratatui` adds the current version (0.30 at the time of writing). The Crossterm backend is enabled by default, so there is nothing else to add: use `ratatui::crossterm` rather than a separate `crossterm` dependency, and the two versions can never disagree.
14+
15+## The smallest app
16+
17+Replace `src/main.rs` with:
18+
19+```rust
20+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
21+use ratatui::DefaultTerminal;
22+
23+fn main() -> std::io::Result<()> {
24+ let mut terminal = ratatui::init();
25+ let result = run(&mut terminal);
26+ ratatui::restore();
27+ result
28+}
29+
30+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
31+ loop {
32+ terminal.draw(|frame| {
33+ frame.render_widget("Hello, Ratatui! Press q to quit.", frame.area());
34+ })?;
35+ if let Event::Key(key) = event::read()? {
36+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
37+ return Ok(());
38+ }
39+ }
40+ }
41+}
42+```
43+
44+Run it with `cargo run`. The text fills the top-left of the terminal; `q` quits.
45+
46+## What init and restore do
47+
48+`ratatui::init()` switches the terminal to raw mode (keys arrive one by one, unechoed) and to the alternate screen (your shell's content comes back when the app ends), and installs a panic hook that restores the terminal if the app panics. `ratatui::restore()` undoes both. Always call `restore` before `main` returns, even on error — that is why the loop lives in its own `run` function whose result is returned after `restore`.
49+
50+## The draw loop
51+
52+`terminal.draw` takes a closure that receives a `Frame`. `frame.area()` is the whole terminal as a `Rect`; `frame.render_widget(widget, area)` draws a widget into a part of it. Ratatui redraws the whole UI every time: it keeps the previous buffer and only sends the cells that changed, so drawing everything each loop is cheap. A `&str` is itself a widget, which is why the smallest app can render a plain string.
53+
54+## Next steps
55+
56+- Split the screen into areas: see the layout tutorial.
57+- Draw borders, titles and wrapped text: see the blocks and paragraphs tutorial.
58+- Keep state and react to keys: see the app state and events tutorial.
added demos/docs-search/docs/02-layout.md +82 -0
new file mode 100644
@@ -0,0 +1,82 @@
1+# Splitting the screen with Layout
2+
3+A `Layout` cuts a `Rect` into smaller ones, vertically or horizontally, following a list of constraints. Layouts nest: split the screen into rows, then split a row into columns.
4+
5+## Constraints
6+
7+| Constraint | Meaning |
8+|---|---|
9+| `Constraint::Length(n)` | exactly `n` rows (or columns) |
10+| `Constraint::Min(n)` | at least `n`, takes what is left |
11+| `Constraint::Max(n)` | at most `n` |
12+| `Constraint::Percentage(p)` | `p` % of the space |
13+| `Constraint::Ratio(a, b)` | `a / b` of the space |
14+| `Constraint::Fill(w)` | shares the remaining space with the other `Fill`s, by weight `w` |
15+
16+## Header, body, footer
17+
18+`Layout::vertical` stacks areas from top to bottom; `areas` returns them as an array you can destructure, so the number of areas is checked at compile time.
19+
20+```rust
21+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
22+use ratatui::layout::{Constraint, Layout};
23+use ratatui::widgets::{Block, Paragraph};
24+use ratatui::{DefaultTerminal, Frame};
25+
26+fn main() -> std::io::Result<()> {
27+ let mut terminal = ratatui::init();
28+ let result = run(&mut terminal);
29+ ratatui::restore();
30+ result
31+}
32+
33+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
34+ loop {
35+ terminal.draw(draw)?;
36+ if let Event::Key(key) = event::read()? {
37+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
38+ return Ok(());
39+ }
40+ }
41+ }
42+}
43+
44+fn draw(frame: &mut Frame) {
45+ let [header, body, footer] = Layout::vertical([
46+ Constraint::Length(3),
47+ Constraint::Min(0),
48+ Constraint::Length(1),
49+ ])
50+ .areas(frame.area());
51+
52+ let [left, right] =
53+ Layout::horizontal([Constraint::Percentage(30), Constraint::Fill(1)]).areas(body);
54+
55+ frame.render_widget(Paragraph::new("My app").block(Block::bordered()), header);
56+ frame.render_widget(Block::bordered().title("Menu"), left);
57+ frame.render_widget(Block::bordered().title("Content"), right);
58+ frame.render_widget("q quit", footer);
59+}
60+```
61+
62+The header is always 3 rows high, the footer 1, and the body takes the rest. The body is then split into a menu (30 % of the width) and a content panel that fills the remaining space.
63+
64+## Spacing and margins
65+
66+`Layout::vertical([...]).spacing(1)` leaves one empty row between areas. `.margin(1)` keeps one cell free all around the area being split. `Block::bordered()` already takes one cell on each side for its border; draw the content in `block.inner(area)` to stay inside it.
67+
68+## Centering an area
69+
70+To center something, give the layout a single constraint and `Flex::Center`:
71+
72+```rust,ignore
73+use ratatui::layout::{Constraint, Flex, Layout, Rect};
74+
75+fn centered(area: Rect, width: u16, height: u16) -> Rect {
76+ let [area] = Layout::horizontal([Constraint::Length(width)]).flex(Flex::Center).areas(area);
77+ let [area] = Layout::vertical([Constraint::Length(height)]).flex(Flex::Center).areas(area);
78+ area
79+}
80+```
81+
82+The popups tutorial uses this to draw a dialog in the middle of the screen.
new file mode 100644
@@ -0,0 +1,82 @@
1+# Splitting the screen with Layout
2+
3+A `Layout` cuts a `Rect` into smaller ones, vertically or horizontally, following a list of constraints. Layouts nest: split the screen into rows, then split a row into columns.
4+
5+## Constraints
6+
7+| Constraint | Meaning |
8+|---|---|
9+| `Constraint::Length(n)` | exactly `n` rows (or columns) |
10+| `Constraint::Min(n)` | at least `n`, takes what is left |
11+| `Constraint::Max(n)` | at most `n` |
12+| `Constraint::Percentage(p)` | `p` % of the space |
13+| `Constraint::Ratio(a, b)` | `a / b` of the space |
14+| `Constraint::Fill(w)` | shares the remaining space with the other `Fill`s, by weight `w` |
15+
16+## Header, body, footer
17+
18+`Layout::vertical` stacks areas from top to bottom; `areas` returns them as an array you can destructure, so the number of areas is checked at compile time.
19+
20+```rust
21+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
22+use ratatui::layout::{Constraint, Layout};
23+use ratatui::widgets::{Block, Paragraph};
24+use ratatui::{DefaultTerminal, Frame};
25+
26+fn main() -> std::io::Result<()> {
27+ let mut terminal = ratatui::init();
28+ let result = run(&mut terminal);
29+ ratatui::restore();
30+ result
31+}
32+
33+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
34+ loop {
35+ terminal.draw(draw)?;
36+ if let Event::Key(key) = event::read()? {
37+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
38+ return Ok(());
39+ }
40+ }
41+ }
42+}
43+
44+fn draw(frame: &mut Frame) {
45+ let [header, body, footer] = Layout::vertical([
46+ Constraint::Length(3),
47+ Constraint::Min(0),
48+ Constraint::Length(1),
49+ ])
50+ .areas(frame.area());
51+
52+ let [left, right] =
53+ Layout::horizontal([Constraint::Percentage(30), Constraint::Fill(1)]).areas(body);
54+
55+ frame.render_widget(Paragraph::new("My app").block(Block::bordered()), header);
56+ frame.render_widget(Block::bordered().title("Menu"), left);
57+ frame.render_widget(Block::bordered().title("Content"), right);
58+ frame.render_widget("q quit", footer);
59+}
60+```
61+
62+The header is always 3 rows high, the footer 1, and the body takes the rest. The body is then split into a menu (30 % of the width) and a content panel that fills the remaining space.
63+
64+## Spacing and margins
65+
66+`Layout::vertical([...]).spacing(1)` leaves one empty row between areas. `.margin(1)` keeps one cell free all around the area being split. `Block::bordered()` already takes one cell on each side for its border; draw the content in `block.inner(area)` to stay inside it.
67+
68+## Centering an area
69+
70+To center something, give the layout a single constraint and `Flex::Center`:
71+
72+```rust,ignore
73+use ratatui::layout::{Constraint, Flex, Layout, Rect};
74+
75+fn centered(area: Rect, width: u16, height: u16) -> Rect {
76+ let [area] = Layout::horizontal([Constraint::Length(width)]).flex(Flex::Center).areas(area);
77+ let [area] = Layout::vertical([Constraint::Length(height)]).flex(Flex::Center).areas(area);
78+ area
79+}
80+```
81+
82+The popups tutorial uses this to draw a dialog in the middle of the screen.
added demos/docs-search/docs/03-blocks-and-paragraphs.md +63 -0
new file mode 100644
@@ -0,0 +1,63 @@
1+# Blocks, paragraphs and styled text
2+
3+## Block: borders and titles
4+
5+A `Block` draws a border and a title around an area. `Block::bordered()` is a block with all four borders; `.title("…")` sets the title; `.border_type(BorderType::Rounded)` rounds the corners. Most widgets take a block with `.block(...)` and draw inside it.
6+
7+## Paragraph: text that wraps
8+
9+`Paragraph::new(text)` draws text. Without wrapping, a long line is cut at the edge; `.wrap(Wrap { trim: true })` wraps it at word boundaries and trims the leading spaces of wrapped lines. `.scroll((y, 0))` scrolls it down by `y` lines, `.alignment(Alignment::Center)` centers it.
10+
11+## Styled text: Span, Line, Text
12+
13+Text is built from three types:
14+
15+- a `Span` is a piece of text with one style;
16+- a `Line` is a row of spans;
17+- a `Text` is several lines.
18+
19+The `Stylize` trait adds short methods to strings, spans and widgets: `"warning".red().bold()`, `"ok".green()`, `Paragraph::new("x").on_blue()`.
20+
21+```rust
22+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
23+use ratatui::style::Stylize;
24+use ratatui::text::{Line, Text};
25+use ratatui::widgets::{Block, BorderType, Paragraph, Wrap};
26+use ratatui::{DefaultTerminal, Frame};
27+
28+fn main() -> std::io::Result<()> {
29+ let mut terminal = ratatui::init();
30+ let result = run(&mut terminal);
31+ ratatui::restore();
32+ result
33+}
34+
35+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
36+ loop {
37+ terminal.draw(draw)?;
38+ if let Event::Key(key) = event::read()? {
39+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
40+ return Ok(());
41+ }
42+ }
43+ }
44+}
45+
46+fn draw(frame: &mut Frame) {
47+ let text = Text::from(vec![
48+ Line::from(vec!["Status: ".into(), "running".green().bold()]),
49+ Line::from(vec!["Errors: ".into(), "2".red()]),
50+ Line::from(""),
51+ Line::from("This long line is wrapped at word boundaries when the window is narrower than the text."),
52+ ]);
53+ let block = Block::bordered()
54+ .border_type(BorderType::Rounded)
55+ .title(" Report ".bold());
56+ let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
57+ frame.render_widget(paragraph, frame.area());
58+}
59+```
60+
61+## Colors
62+
63+`Stylize` covers the named colors (`.red()`, `.on_blue()` for the background, `.light_green()`…) and the modifiers (`.bold()`, `.italic()`, `.underlined()`, `.reversed()`, `.dim()`). For anything else, build a `Style`: `Style::new().fg(Color::Rgb(255, 128, 0)).add_modifier(Modifier::BOLD)`, then `.style(style)` on a widget or `Span::styled("text", style)`.
new file mode 100644
@@ -0,0 +1,63 @@
1+# Blocks, paragraphs and styled text
2+
3+## Block: borders and titles
4+
5+A `Block` draws a border and a title around an area. `Block::bordered()` is a block with all four borders; `.title("…")` sets the title; `.border_type(BorderType::Rounded)` rounds the corners. Most widgets take a block with `.block(...)` and draw inside it.
6+
7+## Paragraph: text that wraps
8+
9+`Paragraph::new(text)` draws text. Without wrapping, a long line is cut at the edge; `.wrap(Wrap { trim: true })` wraps it at word boundaries and trims the leading spaces of wrapped lines. `.scroll((y, 0))` scrolls it down by `y` lines, `.alignment(Alignment::Center)` centers it.
10+
11+## Styled text: Span, Line, Text
12+
13+Text is built from three types:
14+
15+- a `Span` is a piece of text with one style;
16+- a `Line` is a row of spans;
17+- a `Text` is several lines.
18+
19+The `Stylize` trait adds short methods to strings, spans and widgets: `"warning".red().bold()`, `"ok".green()`, `Paragraph::new("x").on_blue()`.
20+
21+```rust
22+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
23+use ratatui::style::Stylize;
24+use ratatui::text::{Line, Text};
25+use ratatui::widgets::{Block, BorderType, Paragraph, Wrap};
26+use ratatui::{DefaultTerminal, Frame};
27+
28+fn main() -> std::io::Result<()> {
29+ let mut terminal = ratatui::init();
30+ let result = run(&mut terminal);
31+ ratatui::restore();
32+ result
33+}
34+
35+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
36+ loop {
37+ terminal.draw(draw)?;
38+ if let Event::Key(key) = event::read()? {
39+ if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
40+ return Ok(());
41+ }
42+ }
43+ }
44+}
45+
46+fn draw(frame: &mut Frame) {
47+ let text = Text::from(vec![
48+ Line::from(vec!["Status: ".into(), "running".green().bold()]),
49+ Line::from(vec!["Errors: ".into(), "2".red()]),
50+ Line::from(""),
51+ Line::from("This long line is wrapped at word boundaries when the window is narrower than the text."),
52+ ]);
53+ let block = Block::bordered()
54+ .border_type(BorderType::Rounded)
55+ .title(" Report ".bold());
56+ let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
57+ frame.render_widget(paragraph, frame.area());
58+}
59+```
60+
61+## Colors
62+
63+`Stylize` covers the named colors (`.red()`, `.on_blue()` for the background, `.light_green()`…) and the modifiers (`.bold()`, `.italic()`, `.underlined()`, `.reversed()`, `.dim()`). For anything else, build a `Style`: `Style::new().fg(Color::Rgb(255, 128, 0)).add_modifier(Modifier::BOLD)`, then `.style(style)` on a widget or `Span::styled("text", style)`.
added demos/docs-search/docs/04-lists.md +60 -0
new file mode 100644
@@ -0,0 +1,60 @@
1+# Lists and selection
2+
3+A `List` shows items, one per row. To let the user move a selection, keep a `ListState` next to the list and render it with `render_stateful_widget`.
4+
5+## Keep the selection with ListState
6+
7+`ListState` remembers which item is selected and how far the list is scrolled. It lives in your app, not in the widget: the widget is rebuilt at each draw, the state survives between draws. `state.select_next()` and `state.select_previous()` move the selection; `state.select(Some(0))` selects the first item; `state.selected()` tells which one is selected.
8+
9+## Highlight the selected item
10+
11+`.highlight_style(...)` styles the selected row and `.highlight_symbol(">> ")` puts a marker before it. `HighlightSpacing::Always` keeps the room for the symbol even when nothing is selected, so the text does not jump.
12+
13+## A list you move through with the arrows
14+
15+```rust
16+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
17+use ratatui::style::Style;
18+use ratatui::widgets::{Block, HighlightSpacing, List, ListState};
19+use ratatui::DefaultTerminal;
20+
21+fn main() -> std::io::Result<()> {
22+ let mut terminal = ratatui::init();
23+ let result = run(&mut terminal);
24+ ratatui::restore();
25+ result
26+}
27+
28+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
29+ let books = ["Dune", "Neuromancer", "Hyperion", "Foundation", "Solaris"];
30+ let mut state = ListState::default();
31+ state.select(Some(0));
32+ loop {
33+ terminal.draw(|frame| {
34+ let list = List::new(books)
35+ .block(Block::bordered().title(" Books — ↑/↓ to move, q to quit "))
36+ .highlight_style(Style::new().black().on_yellow())
37+ .highlight_symbol(">> ")
38+ .highlight_spacing(HighlightSpacing::Always);
39+ frame.render_stateful_widget(list, frame.area(), &mut state);
40+ })?;
41+ if let Event::Key(key) = event::read()? {
42+ if key.kind != KeyEventKind::Press {
43+ continue;
44+ }
45+ match key.code {
46+ KeyCode::Char('q') => return Ok(()),
47+ KeyCode::Down | KeyCode::Char('j') => state.select_next(),
48+ KeyCode::Up | KeyCode::Char('k') => state.select_previous(),
49+ _ => {}
50+ }
51+ }
52+ }
53+}
54+```
55+
56+`select_next` stops at the last item; when the list is taller than the area, `ListState` scrolls it so the selected item stays visible.
57+
58+## Items with several styles
59+
60+An item can be any `Line` or `Text`: `ListItem::new(Line::from(vec!["Dune".bold(), " — Herbert".dim()]))`. `List::new` takes anything that iterates over items, strings included.
new file mode 100644
@@ -0,0 +1,60 @@
1+# Lists and selection
2+
3+A `List` shows items, one per row. To let the user move a selection, keep a `ListState` next to the list and render it with `render_stateful_widget`.
4+
5+## Keep the selection with ListState
6+
7+`ListState` remembers which item is selected and how far the list is scrolled. It lives in your app, not in the widget: the widget is rebuilt at each draw, the state survives between draws. `state.select_next()` and `state.select_previous()` move the selection; `state.select(Some(0))` selects the first item; `state.selected()` tells which one is selected.
8+
9+## Highlight the selected item
10+
11+`.highlight_style(...)` styles the selected row and `.highlight_symbol(">> ")` puts a marker before it. `HighlightSpacing::Always` keeps the room for the symbol even when nothing is selected, so the text does not jump.
12+
13+## A list you move through with the arrows
14+
15+```rust
16+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
17+use ratatui::style::Style;
18+use ratatui::widgets::{Block, HighlightSpacing, List, ListState};
19+use ratatui::DefaultTerminal;
20+
21+fn main() -> std::io::Result<()> {
22+ let mut terminal = ratatui::init();
23+ let result = run(&mut terminal);
24+ ratatui::restore();
25+ result
26+}
27+
28+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
29+ let books = ["Dune", "Neuromancer", "Hyperion", "Foundation", "Solaris"];
30+ let mut state = ListState::default();
31+ state.select(Some(0));
32+ loop {
33+ terminal.draw(|frame| {
34+ let list = List::new(books)
35+ .block(Block::bordered().title(" Books — ↑/↓ to move, q to quit "))
36+ .highlight_style(Style::new().black().on_yellow())
37+ .highlight_symbol(">> ")
38+ .highlight_spacing(HighlightSpacing::Always);
39+ frame.render_stateful_widget(list, frame.area(), &mut state);
40+ })?;
41+ if let Event::Key(key) = event::read()? {
42+ if key.kind != KeyEventKind::Press {
43+ continue;
44+ }
45+ match key.code {
46+ KeyCode::Char('q') => return Ok(()),
47+ KeyCode::Down | KeyCode::Char('j') => state.select_next(),
48+ KeyCode::Up | KeyCode::Char('k') => state.select_previous(),
49+ _ => {}
50+ }
51+ }
52+ }
53+}
54+```
55+
56+`select_next` stops at the last item; when the list is taller than the area, `ListState` scrolls it so the selected item stays visible.
57+
58+## Items with several styles
59+
60+An item can be any `Line` or `Text`: `ListItem::new(Line::from(vec!["Dune".bold(), " — Herbert".dim()]))`. `List::new` takes anything that iterates over items, strings included.
added demos/docs-search/docs/05-tables.md +63 -0
new file mode 100644
@@ -0,0 +1,63 @@
1+# Tables
2+
3+A `Table` shows rows and columns, with an optional header. Like a list, it can keep a selected row in a `TableState`.
4+
5+## Rows, cells and column widths
6+
7+`Row::new(vec!["Dune", "Herbert", "1965"])` is a row of cells. `Table::new(rows, widths)` needs the column widths as constraints, the same ones as `Layout`: `[Constraint::Fill(2), Constraint::Fill(1), Constraint::Length(4)]`. `.header(Row::new(...))` adds a header row, `.column_spacing(2)` the space between columns.
8+
9+## Select a row with TableState
10+
11+`TableState` works like `ListState`: `select_next`, `select_previous`, `selected`. Style the selected row with `.row_highlight_style(...)` and mark it with `.highlight_symbol("> ")`.
12+
13+```rust
14+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
15+use ratatui::layout::Constraint;
16+use ratatui::style::{Style, Stylize};
17+use ratatui::widgets::{Block, Row, Table, TableState};
18+use ratatui::DefaultTerminal;
19+
20+fn main() -> std::io::Result<()> {
21+ let mut terminal = ratatui::init();
22+ let result = run(&mut terminal);
23+ ratatui::restore();
24+ result
25+}
26+
27+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
28+ let books = [
29+ ["Dune", "Frank Herbert", "1965"],
30+ ["Neuromancer", "William Gibson", "1984"],
31+ ["Hyperion", "Dan Simmons", "1989"],
32+ ];
33+ let mut state = TableState::default().with_selected(Some(0));
34+ loop {
35+ terminal.draw(|frame| {
36+ let rows = books.iter().map(|b| Row::new(b.to_vec()));
37+ let widths = [Constraint::Fill(2), Constraint::Fill(2), Constraint::Length(4)];
38+ let table = Table::new(rows, widths)
39+ .header(Row::new(vec!["Title", "Author", "Year"]).bold().underlined())
40+ .block(Block::bordered().title(" Library "))
41+ .row_highlight_style(Style::new().reversed())
42+ .highlight_symbol("> ")
43+ .column_spacing(2);
44+ frame.render_stateful_widget(table, frame.area(), &mut state);
45+ })?;
46+ if let Event::Key(key) = event::read()? {
47+ if key.kind != KeyEventKind::Press {
48+ continue;
49+ }
50+ match key.code {
51+ KeyCode::Char('q') => return Ok(()),
52+ KeyCode::Down => state.select_next(),
53+ KeyCode::Up => state.select_previous(),
54+ _ => {}
55+ }
56+ }
57+ }
58+}
59+```
60+
61+## Cells with their own style
62+
63+A cell can be styled on its own: `Cell::from("overdue").red()`, then `Row::new(vec![Cell::from("Dune"), Cell::from("overdue").red()])`. A whole row too: `Row::new(...).on_dark_gray()`. `.height(2)` gives a row two lines.
new file mode 100644
@@ -0,0 +1,63 @@
1+# Tables
2+
3+A `Table` shows rows and columns, with an optional header. Like a list, it can keep a selected row in a `TableState`.
4+
5+## Rows, cells and column widths
6+
7+`Row::new(vec!["Dune", "Herbert", "1965"])` is a row of cells. `Table::new(rows, widths)` needs the column widths as constraints, the same ones as `Layout`: `[Constraint::Fill(2), Constraint::Fill(1), Constraint::Length(4)]`. `.header(Row::new(...))` adds a header row, `.column_spacing(2)` the space between columns.
8+
9+## Select a row with TableState
10+
11+`TableState` works like `ListState`: `select_next`, `select_previous`, `selected`. Style the selected row with `.row_highlight_style(...)` and mark it with `.highlight_symbol("> ")`.
12+
13+```rust
14+use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
15+use ratatui::layout::Constraint;
16+use ratatui::style::{Style, Stylize};
17+use ratatui::widgets::{Block, Row, Table, TableState};
18+use ratatui::DefaultTerminal;
19+
20+fn main() -> std::io::Result<()> {
21+ let mut terminal = ratatui::init();
22+ let result = run(&mut terminal);
23+ ratatui::restore();
24+ result
25+}
26+
27+fn run(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
28+ let books = [
29+ ["Dune", "Frank Herbert", "1965"],
30+ ["Neuromancer", "William Gibson", "1984"],
31+ ["Hyperion", "Dan Simmons", "1989"],
32+ ];
33+ let mut state = TableState::default().with_selected(Some(0));
34+ loop {
35+ terminal.draw(|frame| {
36+ let rows = books.iter().map(|b| Row::new(b.to_vec()));
37+ let widths = [Constraint::Fill(2), Constraint::Fill(2), Constraint::Length(4)];
38+ let table = Table::new(rows, widths)
39+ .header(Row::new(vec!["Title", "Author", "Year"]).bold().underlined())
40+ .block(Block::bordered().title(" Library "))
41+ .row_highlight_style(Style::new().reversed())
42+ .highlight_symbol("> ")
43+ .column_spacing(2);
44+ frame.render_stateful_widget(table, frame.area(), &mut state);
45+ })?;
46+ if let Event::Key(key) = event::read()? {
47+ if key.kind != KeyEventKind::Press {
48+ continue;
49+ }
50+ match key.code {
51+ KeyCode::Char('q') => return Ok(()),
52+ KeyCode::Down => state.select_next(),
53+ KeyCode::Up => state.select_previous(),
54+ _ => {}
55+ }
56+ }
57+ }
58+}
59+```
60+
61+## Cells with their own style
62+
63+A cell can be styled on its own: `Cell::from("overdue").red()`, then `Row::new(vec![Cell::from("Dune"), Cell::from("overdue").red()])`. A whole row too: `Row::new(...).on_dark_gray()`. `.height(2)` gives a row two lines.
added demos/docs-search/docs/06-gauges-and-ticks.md +56 -0
Large diff collapsed to keep the page fast — load it when you need it.
added demos/docs-search/docs/07-tabs.md +64 -0
Large diff collapsed to keep the page fast — load it when you need it.
added demos/docs-search/docs/08-app-state-and-events.md +70 -0
Large diff collapsed to keep the page fast — load it when you need it.
added demos/docs-search/docs/09-popups.md +67 -0
Large diff collapsed to keep the page fast — load it when you need it.
added demos/docs-search/docs/README.md +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added demos/docs-search/snippets.rs +27 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/diagrams/packages.drawio +1 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/README.md +2 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/en/explanation/document-search.md +31 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/en/how-to/search-the-docs.md +68 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/acp.md +1 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/configuration.md +20 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/slash-commands.md +3 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/en/reference/tools.md +20 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/README.md +2 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/explanation/document-search.md +31 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs/fr/how-to/search-the-docs.md +68 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/acp.md +1 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/configuration.md +20 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/slash-commands.md +3 -1
Large diff collapsed to keep the page fast — load it when you need it.
modified docs/fr/reference/tools.md +20 -0
Large diff collapsed to keep the page fast — load it when you need it.
added docs_assemble_test.go +36 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/acp/acp.go +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/acp/docs_test.go +53 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/agent/agent.go +3 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/agent/memory_test.go +23 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/command/command.go +2 -1
Large diff collapsed to keep the page fast — load it when you need it.
added internal/command/docs.go +23 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/command/docs_test.go +27 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/config.go +13 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/config/docsearch.go +110 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/config/docsearch_test.go +64 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/config/tools.go +1 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/bm25.go +146 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/configured.go +27 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/docsearch.go +133 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/docsearch_test.go +196 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/passages.go +157 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/docsearch/walk.go +85 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/store/search.go +4 -31
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/store/search_test.go +0 -17
Large diff collapsed to keep the page fast — load it when you need it.
added internal/textfold/textfold.go +43 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/textfold/textfold_test.go +20 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/searchdocs.go +67 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/searchdocs_test.go +49 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tui/commands.go +3 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified internal/tui/turn_test.go +18 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified main.go +6 -0
Large diff collapsed to keep the page fast — load it when you need it.