fix(ai): contexte applicatif de l'assistant, dossier vide et liens fiables (#88, BUG-041, BUG-042)
CI / lint (push) Successful in 1m16s
CI / security (push) Successful in 50s
CI / test (push) Successful in 3m0s
CI / build (push) Successful in 47s
CI / e2e (push) Successful in 10m58s

- BUG-041 : le contexte d'un dossier vide ne renvoie plus 404 ; il degrade
  vers le prompt General enrichi d'un bloc « Dossier vide ».
- BUG-042 : liens de fichiers deterministes (nom -> presse-papiers,
  dossier -> arborescence, chemin -> viewer) et resolution du chemin contre
  l'index du vault (exact -> suffixe -> basename) avant ouverture, fin des
  'File not found'.
- #88 : app_context (documents ouverts, repertoire/vault courants, recherche
  + resultats affiches) et fichiers recemment modifies injectes dans le
  prompt General.

Tests : pytest 963 passed / 6 skipped, ruff 0, mypy 0, frontend 52/52 +
validate-imports 36 modules.
This commit is contained in:
2026-09-14 11:28:30 -04:00
parent 05ed36240b
commit 314e37c433
11 changed files with 464 additions and 32 deletions
+41 -14
View File
@@ -408,6 +408,13 @@ async function main() {
assert.ok(html.includes('data-path="docs/guide.md"'), "markdown link linkified");
});
await test("_classifyPath treats a bare filename as a name, not a file", () => {
const b = new BooksLM();
assert.equal(b._classifyPath("readme.md"), "name", "bare filename → copy");
assert.equal(b._classifyPath("notes/a.md"), "file", "dir+file → open");
assert.equal(b._classifyPath("projets/2026"), "dir", "dir path → reveal");
});
await test("_renderMarkdown keeps external links external", () => {
const b = new BooksLM();
const html = b._renderMarkdown("[site](https://example.com/page)");
@@ -415,25 +422,45 @@ async function main() {
assert.ok(!html.includes("bookslm-path-link"), "external link not treated as a vault path");
});
await test("clicking a path link opens a file or reveals a directory", () => {
await test("_activatePath opens files, reveals dirs, copies unknown names", async () => {
const b = new BooksLM();
b._activeVault = () => "TestVault";
const opened = [];
const revealed = [];
const copied = [];
b._openFileLink = (p) => { opened.push(p); return Promise.resolve(); };
b._revealPath = (p) => { revealed.push(p); return Promise.resolve(); };
b._copyLinkName = (p) => { copied.push(p); };
// Resolved file → open the canonical path (not the raw mention).
b._resolveExistingPath = async () => ({ path: "notes/a.md", name: "a.md", type: "file" });
await b._activatePath("a.md", "file");
assert.deepEqual(opened, ["notes/a.md"]);
// Resolved directory → reveal.
b._resolveExistingPath = async () => ({ path: "projets/2026", name: "2026", type: "directory" });
await b._activatePath("projets/2026", "dir");
assert.deepEqual(revealed, ["projets/2026"]);
// Unresolvable file mention → copy the name instead of a dead link.
b._resolveExistingPath = async () => null;
await b._activatePath("bidule.md", "file");
assert.deepEqual(copied, ["bidule.md"]);
});
await test("clicking a bare-name link copies the filename to the clipboard", async () => {
const b = new BooksLM();
b._panel = b._render();
document.body.appendChild(b._panel);
const opened = [];
const revealed = [];
b._openFileLink = (p) => { opened.push(p); return Promise.resolve(); };
b._revealPath = (p) => { revealed.push(p); return Promise.resolve(); };
const copied = [];
b._copyLinkName = (p) => { copied.push(p); };
const container = b._panel.querySelector(".bookslm-messages");
container.innerHTML = b._renderMarkdown("`notes/a.md` et `dossier/sous-dossier`");
const fileLink = container.querySelector('a[data-kind="file"]');
const dirLink = container.querySelector('a[data-kind="dir"]');
assert.ok(fileLink, "file link rendered");
assert.ok(dirLink, "dir link rendered");
fileLink.click();
dirLink.click();
assert.deepEqual(opened, ["notes/a.md"]);
assert.deepEqual(revealed, ["dossier/sous-dossier"]);
container.innerHTML = b._renderMarkdown("Voir `readme.md` pour tout.");
const nameLink = container.querySelector('a[data-kind="name"]');
assert.ok(nameLink, "bare filename rendered as a name link");
nameLink.click();
assert.deepEqual(copied, ["readme.md"]);
b._panel.remove();
});