From 82cae8ffbfc7c86a9f230084f3b753e093320902 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 22 Feb 2026 11:13:37 +0200 Subject: [PATCH] fix(hidden_subtree): clean up LLM launch item --- .../src/services/hidden_subtree.spec.ts | 22 +++++++++++++ apps/server/src/services/hidden_subtree.ts | 31 ++++++++++--------- .../services/hidden_subtree_launcherbar.ts | 6 ++++ packages/commons/src/lib/hidden_subtree.ts | 4 +++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/apps/server/src/services/hidden_subtree.spec.ts b/apps/server/src/services/hidden_subtree.spec.ts index 98d8193b09..1296bb74df 100644 --- a/apps/server/src/services/hidden_subtree.spec.ts +++ b/apps/server/src/services/hidden_subtree.spec.ts @@ -6,6 +6,7 @@ import branches from "./branches.js"; import cls from "./cls.js"; import hiddenSubtreeService from "./hidden_subtree.js"; import { changeLanguage } from "./i18n.js"; +import notes from "./notes.js"; import sql_init from "./sql_init.js"; describe("Hidden Subtree", () => { @@ -158,5 +159,26 @@ describe("Hidden Subtree", () => { cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); expect(hiddenSubtree.hasLabel("excludeFromNoteMap")).toBeFalsy(); }); + + it("cleans up item to be deleted", async () => { + const noteId = "_lbLlmChat"; + let llmNote = becca.getNote(noteId); + if (!llmNote) { + llmNote = notes.createNewNote({ + parentNoteId: "_lbVisibleLaunchers", + noteId, + title: "LLM chat", + type: "launcher", + content: "" + }).note; + } + + cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); + expect(llmNote.isDeleted).toBeTruthy(); + + // Check twice for idempotency. + cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); + expect(llmNote.isDeleted).toBeTruthy(); + }); }); }); diff --git a/apps/server/src/services/hidden_subtree.ts b/apps/server/src/services/hidden_subtree.ts index 97fdebff1a..ceadf3ab4a 100644 --- a/apps/server/src/services/hidden_subtree.ts +++ b/apps/server/src/services/hidden_subtree.ts @@ -1,15 +1,15 @@ -import BAttribute from "../becca/entities/battribute.js"; -import BBranch from "../becca/entities/bbranch.js"; import type { HiddenSubtreeItem } from "@triliumnext/commons"; +import { t } from "i18next"; import becca from "../becca/becca.js"; -import noteService from "./notes.js"; -import log from "./log.js"; -import migrationService from "./migration.js"; -import { t } from "i18next"; -import { cleanUpHelp, getHelpHiddenSubtreeData } from "./in_app_help.js"; +import BAttribute from "../becca/entities/battribute.js"; +import BBranch from "../becca/entities/bbranch.js"; import buildLaunchBarConfig from "./hidden_subtree_launcherbar.js"; import buildHiddenSubtreeTemplates from "./hidden_subtree_templates.js"; +import { cleanUpHelp, getHelpHiddenSubtreeData } from "./in_app_help.js"; +import log from "./log.js"; +import migrationService from "./migration.js"; +import noteService from "./notes.js"; export const LBTPL_ROOT = "_lbTplRoot"; export const LBTPL_BASE = "_lbTplBase"; @@ -350,7 +350,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree noteId: item.id, title: item.title, type: item.type, - parentNoteId: parentNoteId, + parentNoteId, content: item.content ?? "", ignoreForbiddenParents: true })); @@ -363,16 +363,19 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree note.setContent(item.content); } - // Clean up any branches that shouldn't exist according to the meta definition - // For hidden subtree notes, we want to ensure they only exist in their designated locations - if (item.enforceBranches || item.id.startsWith("_help")) { + if (item.enforceDeleted) { + note.deleteNote(); + } else if (item.enforceBranches || item.id.startsWith("_help")) { + // Clean up any branches that shouldn't exist according to the meta definition + // For hidden subtree notes, we want to ensure they only exist in their designated locations + // If the note exists but doesn't have a branch in the expected parent, // create the missing branch to ensure it's in the correct location if (!branch) { log.info(`Creating missing branch for note ${item.id} under parent ${parentNoteId}.`); branch = new BBranch({ noteId: item.id, - parentNoteId: parentNoteId, + parentNoteId, notePosition: item.notePosition !== undefined ? item.notePosition : undefined, isExpanded: item.isExpanded !== undefined ? item.isExpanded : false }).save(); @@ -417,7 +420,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree } else if (item.targetNoteId) { attrs.push({ type: "relation", name: "template", value: LBTPL_NOTE_LAUNCHER }); attrs.push({ type: "relation", name: "target", value: item.targetNoteId }); - } else { + } else if (!item.enforceDeleted) { throw new Error(`No action defined for launcher ${JSON.stringify(item)}`); } } @@ -466,7 +469,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree } for (const attr of attrs) { - const attrId = note.noteId + "_" + attr.type.charAt(0) + attr.name; + const attrId = `${note.noteId}_${attr.type.charAt(0)}${attr.name}`; const existingAttribute = note.getAttributes().find((attr) => attr.attributeId === attrId); diff --git a/apps/server/src/services/hidden_subtree_launcherbar.ts b/apps/server/src/services/hidden_subtree_launcherbar.ts index b8df932b18..2b72d60ec9 100644 --- a/apps/server/src/services/hidden_subtree_launcherbar.ts +++ b/apps/server/src/services/hidden_subtree_launcherbar.ts @@ -104,6 +104,12 @@ export default function buildLaunchBarConfig() { targetNoteId: "_globalNoteMap", icon: "bx bxs-network-chart" }, + { + id: "_lbLlmChat", + title: t("hidden-subtree.llm-chat-title"), + type: "launcher", + enforceDeleted: true + }, { id: "_lbCalendar", ...sharedLaunchers.calendar diff --git a/packages/commons/src/lib/hidden_subtree.ts b/packages/commons/src/lib/hidden_subtree.ts index 1fe5771ed2..f30311fc37 100644 --- a/packages/commons/src/lib/hidden_subtree.ts +++ b/packages/commons/src/lib/hidden_subtree.ts @@ -57,6 +57,10 @@ export interface HiddenSubtreeItem { * definitions will be removed. */ enforceAttributes?: boolean; + /** + * If set to true, if a note with the same ID is found, it will be deleted. This is useful to deactivate features in future versions, for example the launch bar. + */ + enforceDeleted?: boolean; /** * Optionally, a content to be set in the hidden note. If undefined, an empty string will be set instead. *