From bb9e7b1c6e8436acdce1ef47ddfff460749fe71f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 28 Jul 2025 12:20:14 +0300 Subject: [PATCH] fix(hidden_subtree): visible launchers broken due to branch enforcement --- .../src/services/hidden_subtree.spec.ts | 34 +++++++++++++++++++ apps/server/src/services/hidden_subtree.ts | 20 ++++++----- packages/commons/src/lib/hidden_subtree.ts | 6 ++++ 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 apps/server/src/services/hidden_subtree.spec.ts diff --git a/apps/server/src/services/hidden_subtree.spec.ts b/apps/server/src/services/hidden_subtree.spec.ts new file mode 100644 index 000000000..c05fcc3df --- /dev/null +++ b/apps/server/src/services/hidden_subtree.spec.ts @@ -0,0 +1,34 @@ +import { describe, it } from "vitest"; +import cls from "./cls.js"; +import hiddenSubtreeService from "./hidden_subtree.js"; +import sql_init from "./sql_init.js"; +import branches from "./branches.js"; +import becca from "../becca/becca.js"; + +describe("Hidden Subtree", () => { + describe("Launcher movement persistence", () => { + beforeAll(async () => { + sql_init.initializeDb(); + await sql_init.dbReady; + cls.init(() => hiddenSubtreeService.checkHiddenSubtree()); + }); + + it("should persist launcher movement between visible and available after integrity check", async () => { + // Move backend log to visible launchers. + const backendLogBranch = becca.getBranchFromChildAndParent("_lbBackendLog", "_lbAvailableLaunchers"); + expect(backendLogBranch).toBeDefined(); + + // Move launcher to visible launchers. + cls.init(() => { + branches.moveBranchToNote(backendLogBranch!, "_lbVisibleLaunchers"); + hiddenSubtreeService.checkHiddenSubtree(); + }); + + // Ensure the launcher is still in visible launchers. + const childBranches = backendLogBranch?.childNote.getParentBranches() + .filter((b) => !b.isDeleted); + expect(childBranches).toBeDefined(); + expect(childBranches![0].parentNoteId).toStrictEqual("_lbVisibleLaunchers"); + }); + }); +}); diff --git a/apps/server/src/services/hidden_subtree.ts b/apps/server/src/services/hidden_subtree.ts index 90d454108..94584b837 100644 --- a/apps/server/src/services/hidden_subtree.ts +++ b/apps/server/src/services/hidden_subtree.ts @@ -369,16 +369,18 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree // 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 - const expectedParents = getExpectedParentIds(item.id, hiddenSubtreeDefinition); - const currentBranches = note.getParentBranches(); + if (item.enforceBranches) { + const expectedParents = getExpectedParentIds(item.id, hiddenSubtreeDefinition); + const currentBranches = note.getParentBranches(); - for (const currentBranch of currentBranches) { - // Only delete branches that are not in the expected locations - // and are within the hidden subtree structure (avoid touching user-created clones) - if (!expectedParents.includes(currentBranch.parentNoteId) && - isWithinHiddenSubtree(currentBranch.parentNoteId)) { - log.info(`Removing unexpected branch for note '${item.id}' from parent '${currentBranch.parentNoteId}'`); - currentBranch.markAsDeleted(); + for (const currentBranch of currentBranches) { + // Only delete branches that are not in the expected locations + // and are within the hidden subtree structure (avoid touching user-created clones) + if (!expectedParents.includes(currentBranch.parentNoteId) && + isWithinHiddenSubtree(currentBranch.parentNoteId)) { + log.info(`Removing unexpected branch for note '${item.id}' from parent '${currentBranch.parentNoteId}'`); + currentBranch.markAsDeleted(); + } } } } diff --git a/packages/commons/src/lib/hidden_subtree.ts b/packages/commons/src/lib/hidden_subtree.ts index 8440fdeb3..e33276da9 100644 --- a/packages/commons/src/lib/hidden_subtree.ts +++ b/packages/commons/src/lib/hidden_subtree.ts @@ -43,4 +43,10 @@ export interface HiddenSubtreeItem { | "quickSearch" | "aiChatLauncher"; command?: keyof typeof Command; + /** + * If set to true, then branches will be enforced to be in the correct place. + * This is useful for ensuring that the launcher is always in the correct place, even if + * the user moves it around. + */ + enforceBranches?: boolean; }